Skip to content

Custom Columns for the Ajax Scaffold Plugin

AjaxScaffold has been deprecated in favour of ActiveScaffold

One of the first things you will probably notice with the Ajax Scaffold plugin (ASp) is that works great if all you want to do is display a table showing all the fields a model has to offer. However, suppose we want to produce a table showing values that come from user defined methods on a model.

For example lets take a User model with the following fields: name, password, created_at, number_of_logins. To create a table that simply shows these fields is easy, simply add the following to the UsersController:

ajax_scaffold: user

but suppose we want one of the columns to contain only the name and the average number of logins per week / per month / per year….

To this end we have coded up following 3 methods in our model:

def logins_per_month
  some great code
end

def logins_per_week
...
end

def logins_per_year
..
end

Now to get ASp to use these as columns we need to declare a set of AjaxScaffold::ScaffoldColumn objects. N.B the generator does this in the model – the plugin does it in the controller

Before going into the detail of how to implement this example lets take a quick look at the ScaffoldColumn object.

AjaxScaffold::ScaffoldColumn

This class is used internally (although we will override this in a min) by ASp (and the generator) to create the columns to be displayed. Its constructor takes the following form:

AjaxScaffold::ScaffoldColumn.new(class name, options)

The constructor of the ScaffoldColumn object takes the following options:

name – declares the column on the model we are defining, this is the only required value and will be used to infer everything else unless explicitly defined.

label – represents the display name of the column. This will be displayed as the titleized version of the name unless given explicitly.

class_name – this defines a css class name to be appended to column class declaration.

eval – is where we define the method to be called. It can be declared as row.method_name, where “row” means the actual string “row”, or as model_name.method_name

sort_sql – defines the table column to be used for sorting, usually the same as the SQL column name

sort – used to define a class method to be used for sorting (like the logins_per_year)

Users Example (single table on the page)

So back to the example, with only one table on the page (i.e the :suffix option not passed to the plugin) our custom columns can be defined by adding the following declaration to the controller:

@@scaffold_columns = [
   Array of ScaffoldColumn objects here
]

Seems easy enough, what about the columns. Ok so we have 4 columns we want displaying the first is the name of the user and then the 3 with our user defined methods. This leads to the following:

@@scaffold_columns = [
        AjaxScaffold::ScaffoldColumn.new(User, { :name => "name" }),
        AjaxScaffold::ScaffoldColumn.new(User, { :name => "per week",
          :eval => "row.logins_per_week", :sort => 'logins_per_week' }),
        AjaxScaffold::ScaffoldColumn.new(Report, { :name => "per month",
          :eval => "row.logins_per_month", :sort => 'logins_per_month' }),
        AjaxScaffold::ScaffoldColumn.new(Report, { :name => "per year",
          :eval => "row.logins_per_year", :sort => 'logins_per_year' })
]

It is worth noting that if you had an association on the User model, lets say Address (in a has relationship) you could access that information for the sort by defining something like:

AjaxScaffold::ScaffoldColumn.new(User, { :name => "postcode",
   :eval => "row.address.postcode", :sort => 'address.postcode' })

This will work to as many levels as you see fit to play with.

Users Example (multiple tables on the page)

With multiple tables on a page (:suffix passed as true), you need to update the code above to include the prefix users_:

@@users_scaffold_columns = [
.......
]

You can then do the same for any other tables in the page.

Totalling Custom Columns

To cause totals of any of your columns to be calculated and displayed simply add a second array containing the names of the columns you want to total:

@@scaffold_column_totals = ['per month', 'per year']

Remember to use the prefix if you have more than one table in the controller.

30 Comments

  1. andi wrote:

    I just noticed that it stops working with single-table inheritance. the default order clause does not refer to the ‘superclass table’ but to the ‘plural_name’ property:

    Unknown column ‘images.id’ in ‘order clause’: SELECT * FROM medias WHERE ( (medias.`type` = ‘Image’ ) ) ORDER BY images.id asc LIMIT 0, 25

    as far as i found out, its around line 362 in lib/ajax_scaffold_plugin.rb, but I hesitate to change anything in the generated code there.

    another thing: I use the globalize plugin, which stops working unless i remove all .rjs and .rhtml extensions from the action names in lib/ajax_scaffold_plugin.rb:

    return render#{suffix}_template(:action => ‘edit.rjs’) if request.xhr?

    so far, nothing broke. I believe globalize uses some funky overrides with the template search path, it tries to detect language-specific template files (index_en.rhtml).

    Other than that, fine work, that plugin!

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  2. Scott Rutherford wrote:

    Hi Andi,

    Thanks for the feedback. We haven’t tried it yet with the Globalize plugin, but I will get it installed and check your fix works.

    I have to admit single-table inheritance isn’t something I have considered so far in developing the plugin. If I had, I would of realised it would not work with the current code. I will add this to the list of things to look at.

    Thanks again
    Scott.

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  3. JJ wrote:

    Hi – great plugin!

    How would you go about adding a custom column to the form only for ‘create’? I have a relationship between two models ‘Program’ and ‘Project’ – a Program has_many Projects, and a Project belongs_to a Program.

    What I want is to have a drop-down when you are creating a Project with a list of Program names. The id of whatever Program you selected would then be populated in the program_id of the new Project. You would then of course see the Program name in the table. When editing, the field could be read-only (i.e. can’t change programs once created). So far I’m using a single ‘Admin’ controller.

    thanks!

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  4. Scott Rutherford wrote:

    Hi JJ,

    Both the create and edit actions render the _new_edit.rhtml template and pass in a hash of options including :action. So the easiest thing to do would be customize (copy it from views/ajax_scaffold to views/project to get customization specific to the model) _new_edit.rhtml to do a check on @options[:action] and render a new template (say views/project/_create.rhtml) for the create action. You would also need to copy over _form.rhtml and edit that to get a read only value for the program field (maybe even call it _edit.rhtml!!)

    So something like:

    <pre>
    <% if @options[:action].match(‘create’) %>
    <%= render :partial => scaffold_partial_path(‘create’), :locals => { :scaffold_class => @scaffold_class, :scaffold_name => @scaffold_singular_name } %>
    <% else %>
    <%= render :partial => scaffold_partial_path(‘edit’), :locals => { :scaffold_class => @scaffold_class, :scaffold_name => @scaffold_singular_name } %>
    <% end %>
    </pre>

    There is an example of a custom form in the comments of this "post":http://blog.caronsoftware.com/articles/2006/09/02/getting-started-with-the-ajax-scaffold-plugin

    Does that help?

    Cheers
    Scott.

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  5. Ward wrote:

    Hi Scott,

    Building on your explanation for JJ; it seems there’s no elegant way to do for pages with many tables?

    The naming convention used elsewhere doesn’t seem to work – I had expected _new_edit_{prefix}.rhtml to do the trick, but putting that file in place makes the ajax calls die. Peeking in the code (lib/ajax_scaffold_plugin.rb line 253, 272) seems to confirm that this is not supported right now – or am I misreading the code?

    Thanks,
    Ward.

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  6. Scott Rutherford wrote:

    Hi Ward,

    Do you mean many tables on the same page? If so it is outlined in this "post":http://blog.caronsoftware.com/articles/2006/09/02/getting-started-with-the-ajax-scaffold-plugin.

    If you mean having nested tables representing a has_many relationship, not at the mo.

    Scott.

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  7. Shawn Mathews wrote:

    For everybody out there using single table inheritence, add a method like the following to the controller to fix the problem with the default sort.

    def default_{pluralname of scaffold goes here}_sort
    "{realtable_name}.id"
    end

    so if your saffold line looks like this

    ajax_scaffold :verygood_user

    your override would look something like this

    def default_verygood_users_sort
    "users.id"
    end

    Shawn

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  8. Ariel wrote:

    Thanks for a great plugin!

    When trying to have :eval => "link_to(‘ddd’, ‘http://xxx’) , I get only the html itself as TEXT in the table (i.e. <a href…>), while it should have been displayed as a clickable hyperlink.
    Can someone please advise on what am doing wrong here?
    (BTW, I’m using the plugin version)
    10x,
    Ariel

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  9. rafamvc wrote:

    It will be very nice if we can choose to add columns, instead recreating all columns. What do you think about it Scott?

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  10. Scott Rutherford wrote:

    Hi Guys,

    @Ariel – thats because the data is being sanitized. You will need to set the sanitize option on that column to false.

    @rafamvc – this is upcoming in version 4.0 of the plugin….

    Cheers
    Scott

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  11. Ariel wrote:

    Thanks a lot Scott – santize did the trick!
    I wonder how I missed that…
    Anyway, keep up the ggod work!

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  12. Romuald wrote:

    Don’t know if this is AS specific, but using not numeric id with ‘+’ generates and invalid edit link on item.
    What is the trick to have it protect id value from HTML ?

    Thanks for the unvaluable work done with this amazing plugin.

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  13. Eric Anderson wrote:

    One thing I have not seen anywhere in the documentation that I found extremely useful is that AjaxScaffold uses the content_columns method on the ActiveRecord object to get the list of columns. This is very useful to allow quick filtering of fields that are automatically maintained by rails and/or callbacks.

    Example to remove common Raols (and userstamp plugin) maintained fields. Put this in your model.

    <pre>
    <code>
    def self.content_columns
    filter = %w(created_at updated_at created_by updated_by lock_version)%
    super.reject {|c| filter.include? c.name}
    end
    </code>
    </pre>

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  14. Scott Rutherford wrote:

    @Romauld – I’m not too sure what you are asking? Do you mean when the primary key is user defined and not an integer?

    @Eric – excellent point, thanks for posting that, you might want to stick that up on the forum too if you have a minute, useful stuff.

    Cheers
    Scott.

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  15. Eric Anderson wrote:

    @Scott – I’m not sure which "forum" you are talking about. I’m pretty new to Ajax Scaffold and just got here via Google. But feel free to relay anything I post anywhere you want.

    On a related note I found another good way to "tweak" the columns without having to redefine all columns. This method allows you to add and remove columns. It also lets you determine on a per-controller basis what methods are displayed. Basically we are just going to override the scaffold_columns method. Since the method is not inherited but attached directly we can’t just override and call super. But using method chaining will work fine (method commonly used in Rails). Here is an example:

    <pre>
    <code>
    def scaffold_columns_with_page
    scaffold_columns_without_page +
    [ScaffoldColumn.new(ExpertiseArea, :name => 'Page',
    :eval => 'row.pageable.to_s', :sort => 'pageable.to_s')]
    end
    alias_method :scaffold_columns_without_page, :scaffold_columns
    alias_method :scaffold_columns, :scaffold_columns_with_page
    </code>
    </pre>

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  16. Daniel wrote:

    Hello guys,

    I’m pretty new to Ajax Scaffold and after following step by step this howto I still can’t add custom columns.
    I’m trying to add an extra column that contains a link to another controller/action but whenever I browse the page I’m only able see the new column but with no information… (it only displays "-")

    Here is the code of my controller:

    @@scaffold_columns = [
    ...
    AjaxScaffold::ScaffoldColumn.new(Eticket, { :name => "show",:eval => "eticket.show_item"})
    ]

    def show_item
    SOME CODE…
    end

    Any ideas?

    By the way… Excellent job Scott… Keep it up!

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  17. Scott Rutherford wrote:

    @Eric, there is a forum "here":http://groups.google.com/group/ajaxscaffold. Thanks for posting up that column code, might be a little neater than my "version":http://blog.caronsoftware.com/articles/2006/10/28/dynamic-columns-with-ajax-scaffold .

    @Daniel, try changing the eval to :eval => ‘row.show_item’.

    Cheers
    Scott

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  18. Daniel wrote:

    Scott,

    I tried with ‘row.show_item’ before, but I get the same behavior. The application runs perfectly and it doesn’t output errors but the values are not displayed, I only get "-" instead.

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  19. Scott Rutherford wrote:

    Hi Daniel, is the show_item method in the Eticket model class or in your controller? It should be in the model.

    Cheers
    Scott.

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  20. Daniel wrote:

    Uups…
    I had the show_item in the controller, but after I moved it to the model and restarted the server I still get the same behavior.

    I replace eval in the controller to:
    AjaxScaffold::ScaffoldColumn.new(Eticket, { :name => "show",:eval => ‘row.show_item’})

    and added the show_item to the model but nothing happened :-( .
    By the way, the show_item method is supposed to return a link to another controller/action/id

    It is clear that I’m totally missing something… At the moment I did a little work around to temporary fix the problem by creating a custom _row.rhtml but this is not the solution I’m looking for.

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  21. Terry wrote:

    Scott,
    Similar to JJ’s comment above. I’m using the plugin and it works great! Except…I have many foreign keys in my tables. I noticed that the fk columns don’t appear when displaying the layouts at all.

    What needs to be modified to allow me to see drop-downs to select from my foreign key reference tables? The id’s of the fk’s should be populated in the database table versus the values, but the "looked up" values must display.

    I have included the has_many and belongs_to in my models. I really must have missed something in the documentation, because this has to be something people do all the time.

    Terry

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  22. Scott Rutherford wrote:

    Hi Terry, you can do this using the mechanism described above. Just set the :eval parameter to the row.foreign_object.method.

    To get the drop downs you will need to create a custom _form.rhtml file for that controller.

    Cheers
    Scott.

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  23. Scott Rutherford wrote:

    @Daniel, if you return a link you should also pass :sanitize => false as a parameter or your link will be escaped.

    Other than that I can’t really tell from your description what the solution would be, sorry. If you want to email me code or details please feel free.

    Cheers
    Scott.

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  24. Doug Bromley wrote:

    I’ve got multiple tables on one page and the custom columns work fine until I click the header of one to sort by that column – after which it springs all the other columns in the table up that I didn’t want showing.

    Is this a bug or have I missed an important step?

    Hope you can help.

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  25. Scott Rutherford wrote:

    Hi Doug, I would guess you have missed something. Its kind of hard to tell though from your post what that might be. Its probably easiest if you email me (scott(AT)caronsoftware.com) your controller (or at least the ScaffoldColumns config so I can have a quick look.

    Cheers
    Scott.

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  26. R Dorsey wrote:

    Is there a way to include a column from another table (that relates) into the list.
    Example: I have a client table and an address table. when I list the clients name, I would like to list his/her address also, even though they are in different tables

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  27. Roman Romaniuk wrote:

    AjaxScaffold 3.2.2 – I’m trying to add filter capability to a table. Does anyone happen to know the magical incantation required to get the table to update once the filter has been set? I’ve tried:

    render :partial => ‘row’,
    :collection => @collection,
    :locals => { :hidden => false }

    but that doesn’t seem to work. Any ideas?

    Thanks, Roman

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  28. Shigeta Okamoto wrote:

    Hi,

    Currently, I have two models, Figure and FigureNames:

    –figures table–
    <br>
    id (auto primary)<br>
    standard_name :string<br>
    <br>
    –figure_names table–
    <br>
    id (auto primary)<br>
    figure_id :integer (key to figure’s id)<br>
    name :string<br>

    First question: AS doesn’t show the figure_id when I access /figure_names/.
    It seems that AS omits columns that has _id in their names.
    I understand that it’s possible to show the figure_id column by
    coding:<br>
    AjaxScaffold::ScaffoldColumn.new(FigureName, { :name => "figure_id"})<br>
    but why and how does it omit it by default?

    Second Question: I’d also like to customize the columns shown in Edit and New pages. How could I do this?
    I thought that AjaxScaffold::ScaffoldColumn.new Array to the controller will automatically change the two pages, but it didn’t.

    Thanks, Shigeta(@tokyo)

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  29. Ara Vartanian wrote:

    A lot of people seem to want to be able to exclude some rows. I did too! Rather than overriding model class methods, you could just add the feature to AjaxScaffold, like:

    <pre>
    <code>ajax_scaffold :some_model, :exclude => ['some_column_name']
    </pre>
    </code>

    Not so difficult, just open up <code>ajax_scaffold_plugin.rb</code> and change line <b>128</b> to:

    <pre>
    <code>options.assert_valid_keys(:class_name, :except, :rows_per_page, :suffix, :totals, :width, :rel_width, :exclude)
    </pre>
    </code>

    Add this line beneath the declaration of local variables in the <code>ajax_scaffold</code> method:

    <pre><code>excluded_columns = options[:exclude]</pre></code>

    And change what is now my line <b>438</b> to:

    <pre><code>scaffold_columns << ScaffoldColumn.new(#{class_name}, { :name => column.name }) unless #{excluded_columns.inspect}.include?(column.name) </pre></code>

    Works great!

    Tuesday, September 5, 2006 at 1:19 pm | Permalink
  30. Scott Rutherford wrote:

    Thanks for that Ara. This is a base feature for the new version of ajaxscaffold – now called ActiveScaffold, http://www.activescaffold.com

    Cheers
    Scott

    Tuesday, September 5, 2006 at 1:19 pm | Permalink