Customizing Table View

berk edited this page Jun 14, 2012 · 23 revisions
Clone this wiki locally

Setup

For this example we will be using a simple User model that has the following table definition:

create_table :users do |t|
   t.string    :first_name
   t.string    :last_name
   t.date      :birthday
   t.string    :sex
   t.timestamps
 end

We will also use a users_controller with a simple index page. The controller code looks like this:

class UsersController < ApplicationController
  def index
    @users = User.filter(:params => params)
  end
end

The rest of the document just modified the contents of the index.html.erb template.

Basic Table

The most basic form of the will_filter_table_tag is:

<%= will_filter_table_tag(@users) %>

This will generate a table with all available columns of the User object:

Custom Columns and Column Order

But we don't really care for the created_at and updated_at values, and we want to display only a select few columns. We also want to use a specific order of columns, so that birthday comes before sex. You can do this like that:

<%= will_filter_table_tag(@users, :columns => [:id, :first_name, :last_name, :birthday, :sex]) %>

Now you have restricted the columns to the selected few:

Custom Column Value

But what if we want instead of showing first name and last name, combine them both into a single column called name? Here is how we would do this:

<%= will_filter_table_tag(@users, :columns => [
    :id, 
    [:name, lambda{|obj| "#{obj.first_name} #{obj.last_name}"}], 
    :birthday,
    :sex
 ]) %>

Defining a column as an array is good for quick key - value mapping. But when you want to do some fun stuff with the table, a better way would be to use hashes with named attributes. So the above example can be re-written like this:

<%= will_filter_table_tag(@users, :columns => [
    :id, 
    {:key => :name, :value => lambda{ |obj| "#{obj.first_name} #{obj.last_name}" }}, 
    :birthday,
    :sex 
]) %>

And both of the above examples will result in:

Here is the full list of all possible ways to provide the column information:

:columns => [
  :id,  
  [:id, lambda{|obj| obj.id}], 
  [:id, lambda{|obj| obj.id}, "color: white"],
  [:id, lambda{|obj| obj.id}, {:style => "color: green"}], 
  [:id, {:filterable => true, :value => lambda{|obj| obj.id}}],
  [:id, {:filterable => true, :value => lambda{|obj| obj.id}, :style => "color: green"}],
  {:key => :id, :filterable => true, :title => "Custom title"}, 
]

Why so many ways? Well, some are for backwards compatibility. Others are for flexibility. The goal is to minimize the number of code that developers need to write. So you can choose whichever way works for you.

Column attributes include: key, value, title, style, filterable

Custom Column Title

Alright, so far so good. What if we were like Facebook, and decided that referring to gender as sex was not such a good idea. So we decide to use Gender instead of Sex as the column name. You can do this like that:

<%= will_filter_table_tag(@users, :columns => [
    :id, 
    {:key => :name, :value => lambda{ |obj| "#{obj.first_name} #{obj.last_name}" }}, 
    :birthday,
    {:key => :sex, :title => 'Gender'}
]) %>

Here is what it would look like now:

There is still one issue in this table, the combined name column is no longer sortable. Since it is not really a column of the model, the table is not sure what to sort on. Let's say that we want the name column to still be sortable on the last name. We can simply do it like this:

<%= will_filter_table_tag(@users, :columns => [
    :id, 
    {:key => :last_name, :value => lambda{|obj| "#{obj.first_name} #{obj.last_name}"}, :title => 'Name'},       
    :birthday,
    {:key => :sex, :title => 'Gender'}
]) %>

Here is the result:

Custom Column Title and Value

Good, but not good enough. We made the name column sortable on the last name model field. But user cannot clearly see the sorted field. So let's add some code to correct that:

<%= will_filter_table_tag(@users, :columns => [
    :id, 
    { :key => :last_name, 
 	 :value => lambda{|obj| 
         if @users.wf_filter.column_sorted?(:last_name)
 	         raw("<span style='font-weight:normal'>#{obj.first_name} <strong>#{obj.last_name}</strong></span>")
         else
 	        "#{obj.first_name} #{obj.last_name}"
         end	
       }, 
 	  :title => 'Name'
     }, 
     :birthday,
     { :key => :sex, :title => 'Gender' }
 ]) %>

Here is the result:

Custom Column Sorting

Now things start to get interesting. What if we want the name column to be sortable on the First Name or the Last Name based on the user preference. Here is how you would do this:

<%= will_filter_table_tag(@users, :columns => [
    :id, 
    {:key => :name, 
     :value => lambda{ |obj| 
           if @users.wf_filter.column_sorted?(:last_name)
             raw("<span style='font-weight:normal'>#{h(obj.first_name)} <strong>#{h(obj.last_name)}</strong></span>")
           elsif @users.wf_filter.column_sorted?(:first_name)	
             raw("<span style='font-weight:normal'><strong>#{h(obj.first_name)}</strong> #{h(obj.last_name)}</span>")
           else
             "#{obj.first_name} #{obj.last_name}"
           end	
      }, 
      :title => lambda{ |filter| 
          if filter.column_sorted?(:last_name)
             first_name_link = link_to("First Name", filter.to_params(:wf_order => :first_name, :wf_order_type => 'asc'), :title => "sort by first name ascending")
             last_name_link = link_to("Last Name", filter.to_params(:wf_order => :last_name, :wf_order_type => (filter.order_type == 'asc' ? 'desc' : 'asc')), 
                                  :title => "sort by last name #{(filter.order_type == 'asc' ? 'descending' : 'ascending')}", 
                                  :style=>'color:black;font-weight:bold')
          elsif filter.column_sorted?(:first_name)	
             last_name_link = link_to("Last Name", filter.to_params(:wf_order => :last_name, :wf_order_type => 'asc'), :title => "sort by last name ascending")
             first_name_link = link_to("First Name", filter.to_params(:wf_order => :first_name, :wf_order_type => (filter.order_type == 'asc' ? 'desc' : 'asc')), 
                                  :title => "sort by first name #{(filter.order_type == 'asc' ? 'descending' : 'ascending')}", 
                                  :style=>'color:black;font-weight:bold')
          else
             last_name_link = link_to("Last Name", filter.to_params(:wf_order => :last_name, :wf_order_type => 'asc'), :title => "sort by last name ascending")
             first_name_link = link_to("First Name", filter.to_params(:wf_order => :first_name, :wf_order_type => 'asc'), :title => "sort by first name ascending")
          end	

          [first_name_link, last_name_link].join(' / ').html_safe
      },
      :sortable => true,
      :sort_key => lambda{ |filter| filter.column_sorted?(:first_name) ? :first_name : :last_name}    	
    }, 
    :birthday,
    { :key => :sex, :title => 'Gender' }
]) %>

And here is what you get:

Table with Checkboxes

Let's go back to basic table and add some checkboxes to it. Here is how you would do that:

<%= will_filter_table_tag(@users, :columns => [
      [:checkbox, 'users'], 
      :id, 
      {:key => :name, :value => lambda{ |obj| "#{obj.first_name} #{obj.last_name}" }}, 
      :birthday,
      {:key => :sex, :title => 'Gender'}
 ]) %>

:checkbox is a special key that table knows about and renders as such. The second parameter of the column array should always be the name you want to call your checkboxes. So the code behind the scene generates checkboxes named 'users[]', which will pass the following parameter to the server :users => ['1','3'] where the array values are the ids of the selected rows.

Here is what this would look like:

Action Bar

Let's say you like the look and feel of the whole will_filter plugin and would love to continue using its awesome features. Well, we have another tag for you, called will_filter_action_bar_tag. Here is how you add it:

<%= will_filter_actions_bar_tag(@users, [
    ['Delete Selected Users', "
 	 if (!confirm('Are you sure you want to delete all selected users?')) return;
 	 document.getElementById('users_form').submit();	
    "]
 ]) %>

And this is what it looks like:

Complete Example

So how many lines does it take to create a complete admin tool, with a filter object, table and action bar? Let's see:

  <%= will_filter_tag(@users) %>
  <%= form_tag('', :id => 'users_form') do %>
     <%= will_filter_table_tag(@users, :columns => [
          [:checkbox, 'users'], 
          :id, 
          {:key => :name, :value => lambda{ |obj| "#{obj.first_name} #{obj.last_name}" }}, 
          :birthday,
          {:key => :sex, :title => 'Gender'},
          [:actions, lambda{ |obj|
              actions = []
              actions << link_to('edit', :action => :edit, :id => obj.id)	
              actions << link_to('delete', :action => :delete, :id => obj.id)	
              actions.join(' | ').html_safe
          }, 'padding:3px;width:80px;padding-left:10px;']
    ]) %>
    <%= will_filter_actions_bar_tag(@users, [
        ["Delete Selected Users", "
 		if (!confirm('Are you sure you want to delete all selected users?')) return;
 		document.getElementById('users_form').action = '/users/delete'
 		document.getElementById('users_form').submit();	
        "]
    ]) %>
<% end %>

And here is what this would look like: