{ height: 1%; } - Ruby on Rails and User Interface Design

CSS, UI Design, Ruby on Rails and cheese ... lots of cheese

AjaxScaffold 3.1.4 Released

Posted by Richard White Fri, 14 Jul 2006 16:37:19 GMT

AjaxScaffold has been deprecated in favor of ActiveScaffold

Dr. Nic Williams was kind enough to send me unit and functional tests for generated scaffolds. This was something a number of you have been asking for so I’m doing a new release with those included. Thanks Nic.

AjaxScaffold 3.1.3 Released

Posted by Richard White Fri, 14 Jul 2006 03:19:00 GMT

AjaxScaffold has been deprecated in favor of ActiveScaffold

This is mainly a bug fix release in preparation for a couple bigger moves in the near future. If you are interested in the nitty gritty of what has changed check the CHANGELOG in the AjaxScaffold download.

Even bigger news is that an AjaxScaffold based Rails Plugin (or perhaps Rails Engine) is in the works and should be released in the near future. Thanks and praise go to Scott Rutherford for his lovely <a

href=”http://blog.caronsoftware.com/articles/2006/07/02/ajax-scaffold”>writeup on ASG and for agreeing to work together (although he has the bulk of it done already) on the plugin. Aside from focusing on getting a plugin and the generator in sync we’ll also be focusing on making nesting of scaffolds easier and adding search functionality. Wish us luck.

AjaxScaffold approved for Rails Day

Posted by Richard White Wed, 14 Jun 2006 02:49:34 GMT

AjaxScaffold has been deprecated in favor of ActiveScaffold

I just got word from Eric Wagoner, of farmnotebook.com fame, that AjaxScaffoldGenerator has been approved for use during Rails Day. I’ve also heard from many rubyists in the Atlanta area that he put on a great presentation of ASG for AtlRUG. Excellent news all around.

AjaxScaffold 3.1.2 released

Posted by Richard White Thu, 27 Apr 2006 14:54:00 GMT

AjaxScaffold has been deprecated in favor of ActiveScaffold

This release mainly addresses issues people with non-standard (by Rails definition) table names.

  • Fixed default sort to work with non-standard (overridden) table names and primary keys.
  • Fixed bug that was allowing models to be generated in the plural form and in subdirectories.
  • Fixed edit/delete links so that they work with non-standard primary keys (ie not id).
  • Removed test methods from the controller functional test since they were all outdated and broken anyways.
  • Changed controller actions to explicitly call RJS templates so there aren’t any errors with conflicting RHTML templates. This was a common problem people had when upgrading from 2.0 or generating an ajax_scaffold over top of a

    basic scaffold. If there was new.rjs and new.rhtml the RHTML would win out every time. I fixed this by changing return if request.xhr? to return render :action => ‘new.rjs’ if request.xhr?.

    Thanks to Jamis Buck for his prompt email reply on this issue that allowed me to sneak the fix into this release.

  • Changed the helper methods for column header and element row id’s to be more properly namespaced. The current way they were done allowed for the possibility of the top level id for the scaffold (scaffold_id-content) being repeated on a column header if that column had the name ‘content’. A very unfortunate thing would result in that case:

I’d also like welcome Jim Morris as the newest member of the AjaxScaffoldGenerator development team (actually the only other member outside of myself). If you’ve been on the forums at all I’m sure you’ve seen him around helping many of you out. Jim will be helping with some of the upcoming improvements/tweaks to the way scaffold_columns work.

Also, I should mention that I’ve made the Backpack page which outlines upcoming scaffold development public so feel free to take a look to see where we are going and make any suggestions.

I’d also like to apologize to those of you that have emailed me or posted questions on the forum lately. I’ve been very busy this week in preparation for my trip to the west coast for Startup School 2006. I’ll try to get back to everyone as soon as possible, in the meantime thanks to the rest of the community for filling in and helping out the newbies on the forum. Thanks.

AjaxScaffold 3.1.1 released

Posted by Richard White Fri, 21 Apr 2006 13:55:27 GMT

AjaxScaffold has been deprecated in favor of ActiveScaffold

  • Fix for some problems with autodiscovery of :eval and :sort_sql in ScaffoldColumn. Thanks to Eden and David for your work on this issue.
  • Improved the code in def component to better handle invalid sort values in the session. I had a number of people contact me in a panic because no matter what they did they got an error about evaluating nil.sort_sql because…
    Basically the sorting stuff is saved into your session and RoR sessions (unlike ones from most other applications) default to being long lived, ie they have no expiration date. At some point you looked at that scaffold when the default sort was “id” and that was stored into your session. Then subsequent visits, even after the new scaffold was generated, were still finding that your stored session sort was “id” which was a valid sort parameter in 3.0 but not in 3.1.
    Anyways that should all be fixed now.

State of the Scaffold address

Posted by Richard White Wed, 19 Apr 2006 04:54:00 GMT

AjaxScaffold has been deprecated in favor of ActiveScaffold

I’d just like to take a moment to address a common concern many of you have about the direction of scaffold development: that it is getting too complex. I’ve heard from some of you that I either jumped the shark with version 3.0 or am very close to the precipice of doing so. Obviously, adding sorting and pagination did add a level of complexity to the generated code that wasn’t there in 2.0, but it didn’t add much. If you sense that the code has become far more complex its probably due in more part to a) the shift to RJS templates and/or b) that generated code isn’t well written/documented/too refactored etc. Nothing is going to change about the former, but if anyone has any ideas on the latter I’d be more than welcome to any suggestions or criticisms of specific code segments.

I also know it made some of you a bit skittish to hear that I was planning on releasing 3.1 less than a week after 3.0. “Oh the woe of migrating scaffolds!” you proclaimed, but I hope you now see that the 3.1 release was more about reducing the overall complexity of the product. I understand the pain of migrating scaffolds as new versions are put out and my answer to that is pretty simple: you don’t have to do it. This project is still all about creating a starting point. With most things like this you’re bound to be upset a couple months after you dove in when the new version/model comes out. I know this fact very well: I got a Powerbook for Christmas.

Anyways, I didn’t really come here to chide anyone for suggesting that the code has become too complex, or that I should have stopped with version 2.0. Any and all feedback is considered around here and criticism is always worth more than praise. Release 3.1 is a major milestone. I feel that it embodies the common denominator of functionality that most projects will want. From here on in the paradigm will be shifting away from releasing new scaffold versions that add new features and instead to more education on how to add those features yourself either before or after generation.

That doesn’t mean that this is the end of the line for AjaxScaffold development. There will still be many bug fixes I’m sure and if a feature gets a groundswell of support it could be added in but I would see that as the exception rather than the rule.

Also I’d like to ask everyone, for the sake of my own sanity, to please utilize the forum for any issues you may be having with the generator rather than including them in the article comments on here.

Good night and good luck

AjaxScaffold 3.1.0 released

Posted by Richard White Tue, 18 Apr 2006 09:03:00 GMT

AjaxScaffold has been deprecated in favor of ActiveScaffold

Didn’t expect to see me again so soon… that makes two of us :)

  • Added support for defining your own scaffold columns in models by assigning an array of ScaffoldColumn’s to @scaffold_columns. If @scaffold_columns is not defined it will be created defaulting to columns for model.content_columns. I’ll explain this in more detail in the proceeding walkthrough.
  • Added cancel as an action. There was enough inline code in _new_edit.rhtml that I thought, for consistency, it should be refactored into a server-side method.
  • Refactoring: All common server side code has been moved into lib/ajax_scaffold.rb and namespaced more properly. include AjaxScaffoldUtil becomes include AjaxScaffold::Controller and include AjaxScaffold::Helper replaces include ajax_scaffold_helper
  • Changed CSS styling of sorted columns to a more subtle darker blue from the yellow.
  • Changed CSS and DOM structure (only slight changes) so support nested scaffolds
  • Fixed the fact that clicking on a page link wasn’t showing the loading indicator

The rest of this post will be a walkthrough a couple scenarios (Based on this demo) for customizing your scaffolds for model associations (belongs_to and has_many) and in the process explain how to customize the scaffold columns. At the end of this post is a fairly detailed description of how to migrate existing 3.0.x scaffolds.

I realize that a blog post isn’t always the best place to be reading reems of code: download the demo code

Handling associations from the belongs_to side of things

For the purpose of having a contrived scenario to base all this on I’m going to have two models: person.rb and pet.rb. A person has_many pets and a pet belongs_to a person (that’s right there are no strays in demoland). I’m going to assume that you know how to create a db migration file and generate scaffolds for pets and people and move straight into customization of these scaffolds. First thing’s first so lets define our associations in our models and also customize the scaffold columns (These are the columns you’ll see in the scaffold table, not to be confused with those on a create/edit form).

models/pet.rb

require 'ajax_scaffold'
class Pet &lt; ActiveRecord::Base

  <strong>belongs_to :person, :foreign_key =&gt; "owner_id" 

  @scaffold_columns = [ 
      AjaxScaffold::ScaffoldColumn.new(self, { :name =&gt; "name" }),
      AjaxScaffold::ScaffoldColumn.new(self, { :name =&gt; "owner", 
        :eval =&gt; "pet.person.name", :sort_sql =&gt; "people.name" })
    ]</strong>

end    

models/person.rb

require 'ajax_scaffold'
class Person &lt; ActiveRecord::Base

  <strong>has_many :pets, :foreign_key =&gt; "owner_id" 

  @scaffold_columns = [ 
      AjaxScaffold::ScaffoldColumn.new(self, { :name =&gt; "name" }),
      AjaxScaffold::ScaffoldColumn.new(self, { :name =&gt; "pets", 
        :eval =&gt; "person.pets.collect{ |pet| pet.name }.join(', ')", :sortable => false })
    ]</strong>

end    

So besides adding the standard Rails belongs_to and has_many definitions I defined my custom column set by @scaffold_columns. ScaffoldColumn takes four possible options:

  • :name (required) : unique identifier for the column. ScaffoldColumn will attempt to figure out the other variables, if not defined explicitly by you, based on the assumption that this is the name of a content_column for this model. That is to say if you are doing a column for a basic attribute of your model you can just supply the :name and be done with it.
  • :eval : code that is eval’d to create the display value of the column. Eval isn’t the most performant call in Ruby but it is the most flexible since you can define whole blocks of code to be run to generate the display value for each column. You should note that I put pet.person.name not just person.name.
  • :label : the human readable label for the column (aka the header text).
  • :sort_sql : SQL statement to be used when this column is the sorted column. Note that you need to use the table name here, as opposed to the instance name you used with :eval, so people.name instead of person.name. If any of your sortable columns are from associations, such as with people.name in the above example, you need to add the model of the association to the :include option in the controller paginate call. Don’t worry if you don’t know what I’m talking about, I’ll do it just up ahead.
  • :sortable : speaks for itself. This is for those instances where there may not be any logically way to sort the column, such as with person.pets, or you just don’t want that column to be sortable.

Next we add the :include to the @paginate call in def component ...

controllers/pet_controller.rb

...

@paginator, @pets = paginate(:pets, :order_by =&gt; @sort_by, <strong>:include =&gt; 

:person</strong>, :per_page =&gt; default_per_page)

...

... add the drop down to the form …

views/pets/_form.rhtml

...

&lt;div class="form-element"&gt;
  &lt;label for="pet_person"&gt;Owner%lt;/label&gt;
  &lt;%= select 'pet', 'owner_id' , Person.find_all.collect {|p| [ p.name, p.id ] } &&gt;
&lt;/div&gt;

...

... and Viola!

Handling associations from the has_many side of things

For the other side of the coin, I’m going to generate a seperate controller (ruby script\generate ajax_scaffold Pet person_pets) for the pet model to use as the scaffold I’m going to embed into the person scaffold. You could say that this violates the DRY principle, and you’d be right, but there a number of small changes that need to be made to the embedded scaffold and I’d rather do it this way than using if/else on a parameter everywhere. Obviously I have the luxury in this contrived example of not having a lot of other code besides what was generated that will be repeated, if you actually have a fairly customized scaffold you might want to go with the latter route.

Create a link to editing pets…

views/person/_person.rhtml

...  

  &lt;td class="indicator-container"&gt;
    &lt;%= loading_indicator_tag(@options) %&gt;
  &lt;/td&gt;
  <strong>&lt;td&gt; 
    &lt;% pets_options = @options.merge(:action =&gt; 'pets') %&gt;
       &lt;%= link_to_remote "Pets", 
            { :url =&gt; pets_options, 
              :loading =&gt; "Element.show('#{loading_indicator_id(@options)}');" },
            { :href =&gt; url_for(pets_options) } %&gt;
  &lt;/td&gt;</strong>
  &lt;td&gt; 
    &lt;% edit_options = @options.merge(:action =&gt; 'edit') %&gt;

...

... create a view in which to embed the other scaffold. This will look a lot like _new_edit.rhtml so I’ll highlight the important differences …

views/person/_pets.rhtml

&lt;% if not request.xhr? %&gt;
&lt;table class="ajax-scaffold" cellpadding="0" cellspacing="0"&gt;
  &lt;tbody&gt;
&lt;% end %&gt;
&lt;tr id="&lt;%= element_row_id(@options) %&gt;" &lt;%= "style=\"display:none;\"" if 

request.xhr? %&gt;&gt;
  &lt;td id="&lt;%= element_cell_id(@options) %&gt;" class="update" colspan="&lt;%= num_columns 

%&gt;"&gt;

      <strong>&lt;h4&gt;Pets for &lt;%= @person.name %&gt;&lt;/h4&gt;</strong>

      &lt;% if request.xhr? %&gt;
        &lt;div id="&lt;%= element_messages_id(@options) %&gt;" 

class="messages-container"&gt;&lt;/div&gt;
      &lt;% else %&gt;
        &lt;%= render :partial =&gt; 'form_messages' %&gt;
      &lt;% end %&gt;

      <strong>&lt;% child_component_params = params.merge(:scaffold_id =&gt; [ 

@options[:scaffold_id], @options[:id], "pets"].join("-"), :parent_id 

=&gt; 
@options[:id]) %&gt;
      &lt;div id="&lt;%= child_component_params[:scaffold_id] %&gt;" class="ajax-scaffold"&gt;
        &lt;div id="&lt;%= scaffold_content_id(child_component_params) %&gt;"&gt;
          &lt;%= render_component :controller =&gt; '/demo/person_pets', :action =&gt; 

'component', :params =&gt; child_component_params  %&gt;
        &lt;/div&gt;
      &lt;/div&gt;</strong>

      &lt;p class="form-footer"&gt;
          <strong>&lt;% done_params = @options.merge(:controller =&gt; '/demo/person', :action 

=&gt; 'close_pets') %&gt;</strong>
          &lt;%= link_to_remote "Done",
            { :url =&gt; done_params,
              :loading =&gt; "Element.show('#{loading_indicator_id(@options)}');" },
            { :href =&gt; url_for(done_params) } %&gt;                                  
          &lt;%= loading_indicator_tag @options %&gt;
        &lt;/p&gt;  

        <strong>&lt;script type="text/javascript"&gt;
        Rico.Corner.round('&lt;%= child_component_params[:scaffold_id] %&gt;', {color: 

'#005CB8', bgColor: '#fff', compact: true});
      &lt;/script&gt;</strong>
  &lt;/td&gt;
&lt;/tr&gt;
&lt;% if not request.xhr? %&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;% end %&gt;

... create pets action (parallel in function to edit) and close_pets (indentical to pets) in the controller …

controllers/person_controller.rb

  def pets
    @person = Person.find(params[:id])
    @successful = true

    return if request.xhr?

    # Javascript disabled fallback
    if @successful
      <strong>@options = { :action =&gt; "pets", :id =&gt; params[:id] }
      render :partial =&gt; "pets", :layout =&gt; true</strong>
    else 
      return_to_main
    end 
  end

  def close_pets
    @person = Person.find(params[:id])
    @successful = true

    return if request.xhr?

    return_to_main    
  end

... create pets.rjs (parallel to edit.rjs) ...

views/person/pets.rjs

@options = { :scaffold_id =&gt; params[:scaffold_id], <strong>:action =&gt; "pets"</strong>, :id 

=&gt; params[:id] }
@view_options = @options.merge(:action =&gt; "view")

if @successful
  page.hide element_row_id(@view_options)
  page.insert_html :bottom, scaffold_tbody_id(@options), <strong>:partial =&gt; 'pets'</strong>, 

:locals =&gt; { :hidden =&gt; true }
  page &lt;&lt; "new TableRow.MoveAfter('#{element_row_id(@view_options)}', 

'#{element_row_id(@options)}');" 
  page.show element_row_id(@options)
  page.replace_html element_messages_id(@options), :partial =&gt; 'form_messages'
else
  page.replace_html scaffold_messages_id(@options), :partial =&gt; 'messages'
end

page.hide loading_indicator_id(@view_options)

... create close_pets.rjs (parallel to cancel.rjs) ...

views/person/close_pets.rjs

@options = { :scaffold_id => params[:scaffold_id], :action => "view", :id => params[:id] }
@pets_options = @options.merge(:action => "pets")

if @successful
  page.remove element_row_id(@options)
  page.insert_html :bottom, scaffold_tbody_id(@options), :partial =&gt; 'person', :locals =&gt; 

{ :hidden => true }
  page &lt;&lt; "new TableRow.MoveAfter('#{element_row_id(@pets_options)}', 

'#{element_row_id(@options)}');" 
  page.remove element_row_id(@pets_options)
  page.show element_row_id(@options)
  page << "AjaxScaffold.stripe('#{scaffold_tbody_id(@options)}');" 
  page.replace_html scaffold_messages_id(@options), :partial =&gt; 'messages'
else
  page.replace_html element_messages_id(@pets_options), :partial =&gt; 'form_messages'
  page.hide loading_indicator_id(@pets_options)
end

Now we’ve finished modifying the parent person controller and assorted actions we’ll move on to making our adjustments to the person_pets controller and actions. First change the helper for the embedded scaffold to override showing all defined @scaffold_columns and show only the name column …

helpers/person_pets_helper.rb

...

  def scaffold_columns
    [ Pet.scaffold_columns_hash["name"] ]
  end

...

... add the :conditions to @paginate in def component of the person_pets_controller so that it only displays those pets associationed with the parent_id passed in …

controllers/person_pets_controller.rb

...

    @paginator, @pets = paginate(:pets, 
      <strong>:conditions =&gt; [ "owner_id = ?", params[:parent_id] ],</strong>
      :order_by =&gt; @sort_by, :per_page =&gt; default_per_page)

...

... add the parent_id when creating a new pet …

controllers/person_pets_controller.rb

...

  def new
    @pet = Pet.new
    <strong>@pet.owner_id = params[:parent_id]</strong>
    @successful = true

...

... add the owner_id as a hidden tag into _new_edit.rhtml …

views/person_pets/_new_edit.rhtml

...

 &lt;%= hidden_field_tag "scaffold_id", @options[:scaffold_id] %&gt;
 <strong>&lt;%= hidden_field "pet", "owner_id" %&gt;</strong>

...

... and Viola!

Migrating

Here are a bunch of quick notes I made to help those of you upgrading your current 3.x scaffolds to 3.1. Go ahead and generate a 3.1 scaffold in a new directory to use as a template for copying code into older scaffolds.

Delete the following:

  • helpers/ajax_scaffold_helper.rb. Any customization you have made to this or the ajax_scaffold_lib can be migrated into ajax_scaffold.rb under the proper modules (should be fairly straightforward which ones)
  • lib/ajax_scaffold_lib.rb
  • lib/content_column_patch.rb. Remember to take the require ‘content_column_patch’ out of your environment.rb if you were using this

Changes to controllers:

  • Change include AjaxScaffoldUtil into AjaxScaffold::Controller
  • You can add in the code for making cancel a server side action, but since that’s more of a lateral refactoring move there is no real reason you need to. If you want though just copy in the controller cancel method and the cancel.rjs from a new scaffold and modify _new_edit.rhtml so that the cancel link looks like that in a new scaffold as well.
  • Your :default_sort values in component_update and can no longer be “id”. It either should be nil or the name of either a content_column or a column you’ve defined in @scaffold_columns
  • Change the @sort_by line in def component to:
    @sort_by = current_sort(params).nil? ? "people.id asc" : 
    
    Person.scaffold_columns_hash[current_sort(params)].sort_sql  + " " +   
    
    current_sort_direction(params)
    

Changes to helpers:

  • Change include AjaxScaffoldHelper into include AjaxScaffold::Helper
  • Put these two methods in your helper (num_columns should already be in there just overwrite it) making sure to change Customer out with whatever the name of your model is:
      def num_columns
        scaffold_columns.length + 1 
      end  
    
      def scaffold_columns
        Customer.scaffold_columns
      end
    

Changes to models:

  • Add require ‘ajax_scaffold’ to your existings models

Changes to views:

  • Move the class attribute that was on the <TR> tag in _new_edit.rhtml onto the <TD> tag instead.
  • Add class ‘form-footer’ to the <p> at the bottom of _new_edit.rhtml that contains the submit button and cancel link
  • Swap out _column_headings.rhtml with that from a new scaffold. Be sure to set the :controller parameter correctly though
  • Swap out lines 6-10 of _widget.rhtml with code from a new scaffold

Closing Thoughts

I hope this update helps those that were struggling with modifying 3.0 scaffolds to work with associations. I’ll have another writeup later on today about some other useful code/modifications you can do to your scaffolds. Thanks for the patience and feedback, keep it coming.

Upcoming AjaxScaffold improvements

Posted by Richard White Sun, 16 Apr 2006 20:36:55 GMT

AjaxScaffold has been deprecated in favor of ActiveScaffold

I’ve been listening to all the feedback from the 3.0 release and come to a simple conclusion: I screwed up. I got pretty wrapped up in some parts of 3.0 and neglected to think about how “you guys” would be able to bend generated scaffolds to your will.

Case in point is my lame excuse for table data customization. Defaulting to show all the fields on a model is fine, but having the only easy option for customizing this be to defined some subset of model attributes is just weak. What about associations? belong_to, has_many and so on. And what if I’m doing single table inheritance?

Speaking of has_many and belongs_to, how can I modify the generated scaffolds to be used for creating my has_many child objects?

Fear not gentle reader (I’ve been dieing to work in the phrase ‘gentle reader’ into a post for a while now), I am currently working on a release that should make column customization much easier and more flexible. It also should make all that crusty code in _column_headings and _item a bit more readable. I currently have all the code written for what will be 3.1 and am putting it through its paces on a couple demos for associations, specifically a demo with nested scaffolds (Which is also getting some CSS love to make it look less craptacular).

I would expect everything to be released/written up in the next 48 hours.

More AjaxScaffold updates

Posted by Richard White Sat, 15 Apr 2006 00:54:38 GMT

AjaxScaffold has been deprecated in favor of ActiveScaffold

I quietly released a couple new bug fix versions today: 3.0.3 and 3.0.4. Here’s what was fixed:

  • Changed destroy.rjs to hide the loading indicator only if that call was not @successful. This was causing an ‘Element not found’ error on the client side since destroy, when successful, would have removed the DOM subtree the loading indicator was in.
  • The pagination loading indicator id was not being namespaced using the :scaffold_id. This meant that if you had multiple scaffolds on the page then navigating to a page on one scaffold would show ALL indicators on that page.
  • Revisited, and hopefully fixed for good, the issues with controllers in subdirectories (either by generating with controllers named admin/widget or admin::widget). I had to make it so that all :controller values for links and such had the full path name to the controller (eg /admin/widget).

Based on the feedback I’ve recieved it seems that people want to know how to modify the scaffolds to work with associations: belongs_to and has_many. There is already some discussion of these issues over on the forum and I’m working on putting together a demo and a writeup for this sort of thing.

AjaxScaffold 3.0.2 released

Posted by Richard White Thu, 13 Apr 2006 21:29:42 GMT

AjaxScaffold has been deprecated in favor of ActiveScaffold

A quick minor update to fix a pretty major bug and some other assorted sundries:

  • Fixed a major bug that was causing problems with scaffolds generated with :: seperated controller names (eg Admin::Widgets)
  • Added loading indicators for sorting and pagination
  • Refactored the default page size into AjaxScaffoldUtil library

Thanks to Geoff for finding and indentifying the solution to that bug.

Older posts: 1 2 3 4