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

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

AjaxScaffold lessons: Customizing the display columns and using scaffolds as components

Posted by Richard White Tue, 07 Mar 2006 01:38:00 GMT

AjaxScaffold has been deprecated in favor of ActiveScaffold

I’ve been promising a number of you articles about how to do various, and common, customizations of your Ajax scaffolds. Rather than try to cover all your questions in one voluminous post I’ll try to tackle them a few at a time.

This first one will be on two rather simple topics, how to customize the the scaffold columns and how to create an admin console from multiple scaffolds. From here on in I am assuming that you have a least read the introductory article and have generated a scaffold or two. Without further ado, lets begin.

Customizing the columns

There is a much easier way to do this, check out the 3.0.0 release notes

Probably the first thing you want to change after you generate a scaffold are its columns. If you look in _modelname.rhtml (ex: _widget.rhtml) you’ll find the code that writes out the model’s attributes to the table:

  <% for column in Widget.content_columns %>
    <%=h widget.send(column.name) %>
  <% end %>

Using Widget.content_columns is good from the standpoint that it gets you up and running fast but obviously has a number of drawbacks:

  • You often end up with a lot of backend attributes like created_at, updated_at and other attributes you probably only need for backend processing showing up.
  • Except in the simplest of examples you will have associations (belongs_to, has_many, has_and_belongs_to_many, etc) that aren’t included.

There is no magic solution to this, just explicitly setting what columns you want to show up. We can replace the preceding code with the following:

<td><%=h widget.name %></td>
<td><%=h widget.version %></td>
<td><%=h widget.owner.name %></td>

So now we’ve specified that we want to display the name of the widget, its version and the name the owner object that is associated with it. This is assuming that we have mapped that association in our widget.rb and there is a model named Owner:

class Widget < ActiveRecord::Base

  <strong>belongs_to :owner</strong>

  validates_presence_of :name

end

Now we need to also explicitly set our column headers in list.rhtml:

  &lt;thead&gt; 
      &lt;tr class="header"&gt;
        &lt;th&gt;Name&lt;/th&gt;
        &lt;th&gt;Version&lt;/th&gt;
        &lt;th&gt;Owner&lt;/th&gt;
        &lt;th&gt;&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;

The empty &lt;th&gt; on there end there is for the column with our edit/delete actions. One final change and we’ll be done. If you look in the helper for out WidgetController you’ll see the following method:

  def num_columns
    Widget.content_columns.length + 1
  end

This method exists so that we can set the colspan for any of our inline forms (create, edit) in one place. The +1 is for the cell with the edit/delete actions. So we’ll just explicitly set this as well:

  def num_columns
    4
  end

And there you go, thats all there is to customizing the scaffold object list.

Creating admin consoles using multiple scaffolds

One of the nice things about AjaxScaffolds is how easy it is to combine them to create an admin interface or drop them into existing pages. If you open index.rhtml you find:

&lt;%= render_component :controller =&gt; 'widget_console', :action =&gt; 'list' %&gt;

Basically this means that the index action for our WidgetConsole controller is already using the scaffold as a Ruby on Rails component, this is good news as it makes it very simple to put these together. We’ll go ahead and create a new controller named AdminConsole and an index.rhtml for it. Then we’ll open up said index.rhtml and add the following lines:

&lt;%= render_component :controller =&gt; 'widget_console', :action =&gt; 'list' %&gt;
&lt;%= render_component :controller =&gt; 'customer_console', :action =&gt; 'list' %&gt;

As of release 3.0.0 this should be:

&lt;%= render_component :controller =&gt; 'widget_console', :action =&gt; 'component', :params => params %&gt;

Now if we go to /AdminConsole we should see both scaffolds on the same page (if you have an WidgetConsole and CustomerConsole controller). That’s it! Granted your page looks a bit bare, but I’ll assume you can setup the rest of the page around them. Similarly you can drop scaffolds into existing pages with the same code.

Did I say “that’s it” well I jumped the gun. There is still one more thing to do. Since you love all your user’s even the cute little ones that don’t have JavaScript enabled you want to make sure they are taken care of as well. So as I wrote in my previous post you’ll need to change both the CustomerConsole and WidgetConsole controllers return_to_main methods to look like so:

  def return_to_main
    redirect_to :controller => 'AdminConsole', :action => 'index'
  end

Make sure that whatever view you drop the scaffolds onto that the following stylesheets and javascript files are in the header of your layout (and in the same order):

  &lt;%= stylesheet_link_tag 'ajax_scaffold', :media =&gt; 'all' %&gt;
  &lt;%= javascript_include_tag 'prototype', 'effects', 'rico_corner', 'ajax_scaffold' %&gt;
There, now you really are done!

Comments

  1. ian said about 11 hours later:

    awesome, thanks, richard.

  2. Dean said about 13 hours later:

    This is really starting to wrap up to be the highlight of rail for me, Richard. The piece that I need to figure out to make this work well for me is how to tie it fully as a child. This shows how to display in the list the name of the owner. But the next step is to tie this to an owner on insert/update.

    I’m getting there, but this is the key part for me. My thought is to choose a parent record, show key details of it on the top of the page, then all child records would be listed, inserted,etc.

    So keep up the good work!!! These scaffolds are going to be a great thing as time goes on.

  3. Dean said about 13 hours later:

    One other note: If you don’t have associations, the easiest way I’ve found to adapt the columns is to override the content_columns in the model… I did it like this:

    def self.content_columns
      @content_columns ||= Customer.columns.reject { |c| c.primary || c.name =~ /(_id|_count|_at|_on|notes)$/ || c.name == inheritance_column }
    end

    to eliminate the _at, _on and notes columns.

  4. Richard White said about 15 hours later:

    Dean: You know I hadn’t actually thought about using the scaffold for child associations, I’ll ponder that one and perhaps come up with an article on that later on this week. I’d also almost consider changing the default content_columns on the generated code, but that might be a tad confusing. Anyways thanks for the tip.

  5. Sam said about 20 hours later:

    First off, just wanted to say thanks for relasing a great scaffold. Its really quite marvelous.

    Secondly, I’m having problems when i try to bundle this scaffolding into a module. I generate the module with ‘script/generate ajax_scaffold NewsItem admin::news’. I think define everything so when i go to admin/news it renders the proper controller. However if i click on Create New, or Edit, i get this error: “Routing Error

    Recognition failed for ”/admin::news/new”“

    Is there something i’m doing wrong? if so what should i change?

  6. Richard White said about 20 hours later:

    Sam: You aren’t doing anything wrong that’s a current bug that I just was made aware of last night. There is an easy workaround for it that I’ll post in a few and I should have a patch out for it by the end of the week.

  7. Richard White said about 22 hours later:

    Sam: I’ve put out a bug fix release to address the RoutingError issue. Thanks for the heads up.

  8. Sam said 1 day later:

    Hmm i’m still getting an error of

    “Routing Error

    Recognition failed for ”/news/new”“

    when i click on Create New.

  9. Richard White said 1 day later:

    Sam: Did you regen? Can you email me your new.rhtml and the exact command you used to generate the scaffold so I can try and reproduce. rrwhite@gmail.com.

  10. Daniel said 2 days later:

    This is great! Nice work. Just wanted to let you know that I generated a scaffold using a long modularized controller:

    ./script/generate ajax_scaffold Country ‘intranet/sysadmin/lookup_tables/geographics/countries’

    When I load it in Safari, it simply displays a blank page and the webbrick log says:

    127.0.0.1 – - [09/Mar/2006:13:51:05 EST] “GET /intranet/sysadmin/lookup_tables/geographics/countries HTTP/1.1” 500 0 - -> /intranet/sysadmin/lookup_tables/geographics/countries

    However, under FF it works just fine.

  11. Richard White said 2 days later:

    Daniel: Strange… It looks like its requestings the right URL, not sure why it would give a 500 error on that. Honestly I did not test these changes on Safari or IE , because it seemed like a server side issue. I’ll look at it on Safari this afternoon and get back to you.

  12. Pawel said 4 days later:

    This is an excellent work you’ve done! One thing, though… I had problems with additional parameters to survive request eg. localization info. Changing index.rhtml to <= render_component :controller => ‘widget_console’, :action => ‘list’ , :params => params> or <%= render_component params.merge(:controller => ‘widget_console’, :action => ‘list’) %> solves the problem. Anyway, I owe you couple of beers – if you ever are in Poland :)

  13. Richard White said 5 days later:

    Pawel: Thanks for pointing that out, I’ll change that in the next version due out later on this week.

  14. Chuck said 11 days later:

    I’d like to be able to handle adding more elements to a drop down this way. For example, I’ve got a list of projects and one of the columns is client. I’d like to add a new client that would apprear in the list, it would be cool if I could add a create button that would then expand a div below the current one to add the new client using my clients scaffold. I’ve tried this, but can’t get it to work. Any pointers?

    Thanks,

    Chuck

  15. Richard White said 11 days later:

    Chuck: Sounds like you want to do the same things that Dean outlined in acomment on another post. I’ll be taking what he put together and doing a writeup / demo of that scenario this week.

  16. wolfmanjm said 12 days later:

    If you also want to hide fields that you don’t want to edit it seems that you also need to edit _form.rhtml and remove those fields. The instructions above remove them from the list or table only.

  17. Richard White said 13 days later:

    wolfmanjm: You are correct. I didn’t write that in there, but perhaps I should. Editing the form is much more straightforward since all the elements are explicitly given rather than the somewhat voodoo’ish content_columns which is why I left that part out of this article (That and your listed columns need not be congruent to the elements you have exposed on your form).

  18. David R said 23 days later:

    Great generator!! But there’s still one thing I’m confused about. Perhaps this is a general Rails scaffold issue, but why doesn’t it auto-generate template code for associations (belongs_to, has_many, etc)? It seems like it should be easy enough for Rails to spot the obvious associations and auto-generate the appropriate columns and lookup code, and it’s a real hassle to always have to manually edit them in. Or is this already possible via parameters like:

    “ruby script/generate ajax_scaffold Item—belongs_to category”

  19. Richard White said 23 days later:

    DavidR: It is a general Rails scaffold issue, one which I myself have never understood. Its on my plans at some point to try and add that ability to the AjaxScaffold, I’ll have to dig into the base scaffold/ActiveRecord stuff to do it. Some other guys did some work where they extended :scaffold to do just what we are talking about I’ll have to ask them how they did it.

  20. David R said 23 days later:

    Thanks. I’m very much looking forward to it. BTW, who are those “other guys” and what is the name of their generator? TIA.

  21. Richard White said 24 days later:

    DavidR: http://wiki.rubyonrails.com/rails/pages/Scaffolding+Extensions+Plugin

  22. David R said 25 days later:

    Thanks, Richard!

  23. Roupen N. said 710 days later:

    Hah, I am stuck right now trying to do exactly that… using the content_column method where Widget is in a belongs_to relationship. How do I do that? :-/

    &lt;% for column in Widget.content_columns %&gt;
       &lt;%=h widget.send(column.name) %&gt;
     &lt;% end %&gt;

Trackbacks

Use the following link to trackback from your own site:
http://www.height1percent.com/articles/trackback/21

(leave url/email »)

   Comment Markup Help Preview comment