3.3.0
Yuri Leikind
“Yuri Leikind” <yuri.leikind at gmail dot com>
FOR RAILS 3 USE VERSION 3.x
FOR RAILS 2 USE VERSION 0.6 (github.com/leikind/wice_grid/tree/master).
WiceGrid is a Rails grid plugin.
One of the goals of this plugin was to allow the programmer to define the contents of the cell by himself, just like one does when rendering a collection via a simple table (and this is what differentiates WiceGrid from various scaffolding solutions), but automate implementation of filters, ordering, paginations, CSV export, and so on. Ruby blocks provide an elegant means for this.
WiceGrid builds the call to the ActiveRecord layer for you and creates a table view with the results of the call including:
paging
sortable columns
filtering by multiple columns
CSV export
saving queries
All working nicely together. Filters are added automatically according to the type of the underlying DB column. Filtering by more than one column at the same time is possible. More than one such grid can appear on a page, and manipulations with one grid do not have any impact on the other.
WiceGrid does not take a collection as an input, it works directly with ActiveRecord.
WiceGrid does not use AJAX calls to reload itself, instead simple GET requests are used for this, nevertheless, all other page parameters are respected and preserved. WiceGrid views do not contain forms so you can include it in your own forms.
There are two major branches of WiceGrid.
WiceGrid is known to work with MySQL and Postgres.
This tutorial is accompanied by a sample application with WiceGrid examples which you can browse online ( wicegrid.herokuapp.com ), or just view the code ( github.com/leikind/wice_grid_testbed ).
Rails version 3.2.x or newer, jQuery, jQuery Datepicker. For Rails 3.0.x and 3.1.x versions use version 3.0.4. For Rails 2 use version 0.6 (github.com/leikind/wice_grid/tree/master).
WARNING: Since 3.2.pre2 WiceGrid is not compatible with
will_paginate
because internally it uses kaminari
for pagination, and kaminari
is not compatible with
will_paginate
!
WiceGrid started as a plugin using the Prototype javascript framework. Support for jQuery was added for version 0.6. Beginning from version 3.2 only jQuery is supported.
Add the following to your Gemfile:
gem "wice_grid", '3.2.1'
and run the bundle
command.
Run the generator:
rails g wice_grid:install
This adds the config file wice_grid_config.rb
to
config/initializers/
, the locale file
wice_grid.yml
to config/locales/
, and the styles
file wice_grid.css.scss
to
app/assets/stylesheets/
.
Require WiceGrid javascript in your js index file:
//= require wice_grid
Make sure that jQuery is loaded. If the application uses Date and DateTime filters, you have to install jQuery Datepicker by yourself.
Here is an example of application.js with everything WiceGrid needs:
//= require jquery //= require jquery_ujs //= require jquery-ui //= require wice_grid //= require jquery.ui.datepicker //= require_tree .
WiceGrid provides some very basic styles, not specifying exactly how the table should look like, but if the application uses Twitter Bootstrap, the markup generated by WiceGrid will have correct classes and will fit nicely. Generally it is advised to modify WiceGrid css to match the application style.
The simplest example of a WiceGrid for one simple DB table called ApplicationAccount is the following:
Controller:
@tasks_grid = initialize_grid(Task)
It is also possible to use an ActiveRecord::Relation instance as the first argument:
@tasks_grid = initialize_grid(Task.where(:active => true))
View:
<%= grid(@tasks_grid) do |g| g.column do |task| task.id end g.column do |task| task.title end g.column do |task| task.description end g.column do |task| task.archived? ? 'Yes' : 'No' end g.column do |task| link_to('Edit', edit_task_path(task)) end end -%>
Code g.column do |task| ... end
defines everything related to
a column in the resulting view table including column names, sorting,
filtering, the content of the column cells, etc. The return value of the
block is the table cell content.
Column names are defined with parameter :name
:
<%= grid(@tasks_grid) do |g| g.column :name => 'ID' do |task| task.id end g.column :name => 'Title' do |task| task.title end g.column :name => 'Description' do |task| task.description end g.column :name => 'Archived' do |task| task.archived? ? 'Yes' : 'No' end g.column do |task| link_to('Edit', edit_task_path(task)) end end -%>
To add filtering and ordering, declare to which column in the underlying
database table(s) the view column corresponds using parameter
:attribute
:
<%= grid(@tasks_grid) do |g| g.column :name => 'ID', :attribute => 'id' do |task| task.id end g.column :name => 'Title', :attribute => 'title' do |task| task.title end g.column :name => 'Description', :attribute => 'description' do |task| task.description end g.column :name => 'Archived', :attribute => 'archived' do |task| task.archived? ? 'Yes' : 'No' end g.column do |task| link_to('Edit', edit_task_path(task)) end end -%>
This will add sorting links and filters for columns Username
and Active
. The plugin automatically creates filters according
to the type of the database column. In the above example a text field will
be created for column Title (title is a string), for column
Archived
a dropdown filter will be created with options ‘Yes’,
‘No’, and ‘–’, and for the integer ID two short text fields are added which
can contain the numeric range (more than, less than).
It is important to remember that :attribute
is the name of the
database column, not a model attribute. Of course, all database columns
have corresponding model attributes, but not all model attributes map to
columns in the same table with the same name.
Read more about available filters in the documentation for the column method.
Read the section about custom dropdown filters for more advanced filters.
For columns like
g.column :name => 'Title', :attribute => 'title' do |task| task.title end
where the block contains just a call to the same attribute declared by :attribute, the block can be omitted:
<%= grid(@tasks_grid) do |g| g.column :name => 'ID', :attribute => 'id' g.column :name => 'Title', :attribute => 'title' g.column :name => 'Description', :attribute => 'description' g.column :name => 'Archived', :attribute => 'archived' do |task| task.archived? ? 'Yes' : 'No' end g.column do |task| link_to('Edit', edit_task_path(task)) end end -%>
In this case name
will be used as the method name to send to
the ActiveRecord instance.
If only ordering is needed, and no filter, we can turn off filters using
:filter
:
g.column :name => 'ID', :attribute => 'id', :filter => false
If no ordering links are needed, use :ordering => false
:
g.column :name => 'Added', :attribute => 'created_at', :ordering => false
It is important to understand that it is up to the developer to make sure
that the value returned by a column block (the content of a cell)
corresponds to the underlying database column specified by
:attribute
(and :model
discussed below).
The filter panel can be shown and hidden clicking the icon with binoculars.
The way the filter panel is shown after the page is loaded is controlled
via parameter :show_filters
of the grid
helper.
Possible values are:
:when_filtered
- the filter is shown when the current table is
the result of filtering
:always
- always show the filter
:no
- never show the filter
Example:
<%= grid(@tasks_grid, :show_filters => :always) do |g| ...... end -%>
Filter related icons (filter icon, reset icon, show/hide icon) are placed
in the header of the last column if it doesn’t have any filter or a column
name, otherwise an additional table column is added. To always place the
icons in the additional column, set
Wice::Defaults::REUSE_LAST_COLUMN_FOR_FILTER_ICONS
to
false
in the configuration file.
Initializing the grid we can also define the column by which the record will be ordered on the first rendering of the grid, when the user has not set their ordering setting by clicking the column label, and the order direction:
@tasks_grid = initialize_grid(Task, :order => 'tasks.title', :order_direction => 'desc' )
The number of rows per page is set with :per_page
:
@tasks_grid = initialize_grid(Task, :per_page => 40)
The initialize_grid
method supports a :conditions
parameter which is passed on to the underlying ActiveRecord, so it can be
in any format processable by ActiveRecord:
@tasks_grid = initialize_grid(Task, :conditions => ["archived = false and estimated_time > ?", 100] ) @tasks_grid = initialize_grid(Task, :include => :project, :conditions => {:archived => false, :project => {:active => true}} )
A good example is substituting a common pattern like
@user_groups = @portal_application.user_groups
with WiceGrid code:
@user_groups_grid = initialize_grid(UserGroup, :conditions => ['portal_application_id = ?', @portal_application])
Alternatively, instead of a Class object as the first parameter, you can use ActiveRecord::Relation:
@tasks_grid = initialize_grid(Task. where(:archived => false, :projects => {:active => true}). joins(:project) )
Please note that though all queries inside of WiceGrid are run without the
default scope, if you use an ActiveRecord::Relation instance to initialize
grid, it will already include the default scope. Thus you might consider
using unscoped
:
@tasks_grid = initialize_grid(Task.unscoped. where(:archived => false, :projects => {:active => true}). joins(:project) )
WiceGrid also supports ActiveRecord’s :joins
and
:include
.
@products_grid = initialize_grid(Product, :include => :category, :order => 'products.name', :per_page => 20)
Note that if we want to order initially by a column from a joined table we
have to specify the table and the column name with the sql dot notation,
that is, products.name
To show columns of joined tables in the view table, the ActiveRecord model class name has to be specified, that corresponds to the joined table:
<%= grid(@products_grid) do |g| g.column :name => 'Product Name', :attribute => 'name' do |product| # primary table link_to(product.name, product_path(product)) end g.column :name => 'Category', :attribute => 'name', :model => Category |product| # joined table product.category.name end %>
Please note that the blockless definition of the column only works with
columns from the main table and it won’t work with columns with
:model
In case there are two joined associations both referring to the same table,
ActiveRecord constructs a query where the second join provides an alias for
the joined table. To enable WiceGrid to order and filter by columns
belonging to different associations but originating from the same table,
set :table_alias
to this alias:
Model:
class Project < ActiveRecord::Base belongs_to :customer, :class_name => 'Company' belongs_to :supplier, :class_name => 'Company' end
Controller:
@projects_grid = initialize_grid(Project, :include => [:customer, :supplier] )
View:
<%= grid(@projects_grid, :show_filters => :always) do |g| g.column :name => 'Project Name', :attribute => 'name' g.column :name => 'Customer company', :model => 'Company', :attribute => 'name' do |task| task.customer.name if task.customer end g.column :name => 'Supplier company', :model => 'Company', :attribute => 'name', :table_alias => ' suppliers_projects' do |task| task.supplier.name if task.supplier end end -%>
It is possible to use more that one grid on a page, each with its own
state. To do so, you must specify the name of the grid in
initialize_grid
using parameter :name
The name serves as the base name for HTTP parameters, DOM IDs, etc, so it is important that all grids on a page have different names. The default name is ‘grid’.
The name can only contain alphanumeric characters.
@projects_grid = initialize_grid(Project, :name => 'g1' ) @tasks_grid = initialize_grid(Task, :name => 'g2')
It is possible to change the way results are ordered injecting a chunk of
SQL code, for example, use ORDER BY INET_ATON(ip_address)
instead of ORDER BY ip_address
.
To do so, provide parameter :custom_order
in the
initialization of the grid with a hash where keys are fully qualified names
of database columns, and values the required chunks of SQL to use in the
ORDER BY clause.
For example:
@hosts_grid = initialize_grid(Host, :custom_order => { 'hosts.ip_address' => 'INET_ATON(hosts.ip_address)' })
It is possible to use the ‘?’ character instead of the name of the column in the hash value:
@hosts_grid = initialize_grid(Host, :custom_order => { 'hosts.ip_address' => 'INET_ATON( ? )' })
Values can also be Proc objects. The parameter supplied to such a Proc object is the name of the column:
@hosts_grid = initialize_grid(Host, :custom_order => { 'hosts.ip_address' => lambda{|f| "INET_ATON( #{f} )"} })
It is possible to construct custom dropdown filters. Depending on the value
of column
parameter:custom_filter
different modes
are available:
An array of two-element arrays or a hash are semantically identical ways of creating a custom filter.
Every first item of the two-element array is used for the label of the select option while the second element is the value of the select option. In case of a hash the keys become the labels of the generated dropdown list, while the values will be values of options of the dropdown list:
g.column :name => 'Status', :attribute => 'status', :custom_filter => {'Development' => 'development', 'Testing' => 'testing', 'Production' => 'production'} g.column :name => 'Status', :attribute => 'status', :custom_filter => [['Development', 'development'], ['Testing', 'testing'], ['Production', 'production']]
It is also possible to submit a array of strings or numbers, in this case every item will be used both as the value of the select option and as its label:
g.column :name => 'Status', :attribute => 'status', :custom_filter => ['development', 'testing', 'production']
:auto
- a powerful option which populates the dropdown list
with all unique values of the column specified by :attribute
and :model
.
g.column :name => 'Status', :attribute => 'status', :custom_filter => ['development', 'testing', 'production']
Note that in the above example all names of all possible categories will appear even if they don’t appear in the current resultset. To only show those which do appear in the resutset, use an array of symbol messages (see section ‘An array of symbols’).
In most cases custom fields are needed for one-to-many and many-to-many associations.
To correctly build a filter condition foreign keys have to be used, not the actual values rendered in the column.
For example, if there is a column:
g.column :name => 'Project Name', :attribute => 'name', :model => 'Project' do |task| task.project.name if task.project end
adding :custom_filter
like this:
g.column :name => 'Project Name', :attribute => 'name', :model => 'Project', :custom_filter => Project.find(:all).map{|pr| [pr.name, pr.name]} do |task| task.project.name if task.project end
is bad style and can fail, because the resulting condition will compare the
name of the project, projects.name
to a string, and in some
databases it is possible that different records (projects in our example)
have the same name.
To use filter with foreign keys, we have to change the declaration of the
column from projects.name
, to tasks.project_id
,
and build the dropdown with foreign keys as values:
g.column :name => 'Project Name', :attribute => 'tasks.project_id', :custom_filter => Project.find(:all).map{|pr| [pr.id, pr.name]} do |task| task.project.name if task.project end
However, this will break the ordering of the column - the column will be
ordered by the integer foreign key. To fix this, we can override the
ordering using :custom_order
:
@tasks_grid = initialize_grid(Task, :include => :project, :custom_order => { 'tasks.project_id' => 'projects.name' } )
For one symbol (different from :auto
) the dropdown list is
populated by all unique values returned by the method with this name sent
to all ActiveRecord objects throughout all pages.
The conditions set up by the user are ignored, that is, the records used are all those found on all pages without any filters active.
For an array of symbols, the first method name is sent to the ActiveRecord
object if it responds to this method, the second method name is sent to the
returned value unless it is nil
, and so on. In other words, a
single symbol mode is the same as an array of symbols where the array
contains just one element.
g.column :name => 'Version', :attribute => 'expected_version_id', :custom_filter => [:expected_version, :to_option] do |task| task.expected_version.name if task.expected_version end
There are two important differences from :auto
:
The method does not have to be a field in the result set, it is just some value computed in the method after the database call and ActiveRecord instantiation.
Filtering by any option of such a custom filter will bring a non-empty
list, unlike with :auto
.
This mode has one major drawback - this mode requires an additional query
without offset
and limit
clauses to instantiate
all ActiveRecord objects, and performance-wise it brings all the
advantages of pagination to nothing. Thus, memory- and performance-wise
this can be really bad for some queries and tables and should be used with
care.
If the final method returns a atomic value like a string or an integer, it is used for both the value and the label of the select option element:
<option value="returned value">returned value</option>
However, if the retuned value is a two element array, the first element is used for the option label and the second - for the value.
Typically, a model method like the following:
def to_option [name, id] end
together with
:custom_filter => :to_option
would do the trick:
<option value="id">name</option>
Alternatively, a hash with the single key-value pair can be used, where the key will be used for the label, and the key - for the value:
def to_option {name => id} end
Values ‘null’ and ‘not null’ in a generated custom filter are treated
specially, as SQL null
statement and not as strings. Value
‘null’ is transformed into SQL condition IS NULL
, and ‘not
null’ into IS NOT NULL
Thus, if in a filter defined by
:custom_filter => {'No' => 'null', 'Yes' => 'not null', '1' => 1, '2' => '2', '3' => '3'}
values ‘1’, ‘2’ and ‘No’ are selected (in a multi-select mode), this will result in the following SQL:
( table.field IN ( '1', '2' ) OR table.field IS NULL )
By default it is possible for any dropdown list to switch between single
and multiple selection modes. To only allow single selection use
:allow_multiple_selection
:
g.column :name => 'Expected in version', :attribute => 'expected_version_id', :custom_filter => [:expected_version, :to_option], :allow_multiple_selection => false do |task| ... end
Before version 3.2.1 the filter used for numeric columns was a range filter
with two limits. Beginning with version 3.2.1 the default is a direct
comparison filter with one input field. The old range filter can still be
loaded using parameter :filter_type
with value
:range
:
g.column :filter_type => :range do |task| ... end
Default values like can be changed in
config/initializers/wice_grid_config.rb
.
Instead of using default Submit and Reset icons you can use external HTML
elements to trigger these actions. Add a button or any other clickable HTML
element with class wg-external-submit-button
or
wg-external-reset-button
, and attribute
data-grid-name
whose value is the name of the grid:
<button class="wg-external-submit-button" data-grid-name="grid">Submit</button> <button class="wg-external-reset-button" data-grid-name="grid">Reset</button>
To hide the default icons use :hide_submit_button => true
and :hide_reset_button => true
in the grid
helper.
It is possible to configure a grid to reload itself once a filter has been changed. It works with all filter types including the JS calendar, the only exception is the standard Rails date/datetime filters.
Use option :auto_reload
in the column definiton:
<%= grid(@tasks_grid, :show_filters => :always, :hide_submit_button => true) do |g| # String g.column :name => 'Title', :attribute => 'title', :auto_reload => true # Boolean g.column :name => 'Archived', :attribute => 'archived', :auto_reload => true # Custom (dropdown) g.column :name => 'Status', :attribute => 'status_id', :custom_filter => Status.to_dropdown, :auto_reload => true do |task| task.status.name if task.status end # Datetime g.column :name => 'Added', :attribute => 'created_at', :auto_reload => true, :helper_style => :calendar do |task| task.created_at.to_s(:short) end end -%>
To make this behavior default change constant AUTO_RELOAD
in
the configuration file.
The grid
helper accepts parameter :html
which is
a hash of HTML attributes for the table tag.
Another grid
parameter is header_tr_html
which is
a hash of HTML attributes to be added to the first tr
tag (or
two first tr
‘s if the filter row is present).
:html
is a parameter for the column
method
setting HTML attributes of td
tags for a certain column.
WiceGrid offers ways to dynamically add classes and styles to
TR
and TD
based on the current ActiveRecord
instance.
For <TD>
, let the column
return an array
where the first item is the usual string output whole the second is a hash
of HTML attributes to be added for the <td>
tag of the
current cell:
g.column do |portal_application| css_class = portal_application.public? ? 'public' : 'private' [portal_application.name, {:class => css_class}] end
For adding classes/styles to <TR>
use special clause
row_attributes
, similar to column
, only
returning a hash:
<%= grid(@versions_grid) do |g| g.row_attributes do |version| if version.in_production? {:style => 'background-color: rgb(255, 255, 204);'} end end g.column{|version| ... } g.column{|version| ... } end -%>
Naturally, there can be only one row_attributes
definition for
a WiceGrid instance.
Various classes do not overwrite each other, instead, they are concatenated.
It is possible to add your own handcrafted HTML after and/or before each
grid row. This works similar to row_attributes
, by adding
blocks after_row
, before_row
, and
last_row
:
<%= grid(@tasks_grid) do |g| g.before_row do |task, number_of_columns| if task.active? "<tr><td colspan=\"10\">Custom line for #{t.name}</td></tr>" # this would add a row # before every active task row else nil end end g.last_row do |number_of_columns| # This row will always be added to the bottom of the grid content_tag(:tr, content_tag(:td, 'Last row', :colspan => 10), :class => 'last_row') end ....... end %>
It is up for the developer to return the correct HTML code, or return
nil
if no row is needed for this record. Naturally, there is
only one before_row
definition and one after_row
definition for a WiceGrid instance.
The second variable injected into to before_row
and
after_row
block, and the first parameter injected into the
last_row
is the number of columns in the current grid.
If the grid does not contain any records to show, it is possible show some alternative view instead of an empty grid. Bear in mind that if the user sets up the filters in such a way that the selection of records is empty, this will still render the grid and it will be possible to reset the grid clicking
on the Reset button. Thus, this only works if the initial number of records is 0. <%= grid(@grid) do |g| g.blank_slate do "There are no records" end g.column do |product| ... end end -%>
There are two alternative ways to do the same, submitting a string to
blank_slate
:
g.blank_slate "some text to be rendered"
Or a partial:
g.blank_slate :partial => "partial_name"
It is possible to add a column with checkboxes for each record. This is
useful for actions with multiple records, for example, deleting selected
records. Please note that action_column
only creates the
checkboxes and the ‘Select All’ and ‘Deselect All’ buttons, and the form
itself as well as processing the parameters should be taken care of by the
application code.
<%= grid(@tasks_grid, :show_filters => :always) do |g| ... g.action_column ... end -%>
By default the name of the HTTP parameter follows pattern
"#{grid_name}[#{param_name}][]"
, thus
params[grid_name][param_name]
will contain an array of object
IDs.
You can hide a certain action checkbox if you add the usual block to
g.action_column
, just like with the g.column
definition. If the block returns nil
or false
no
checkbox will be rendered.
<%= grid(@tasks_grid, :show_filters => :always) do |g| ... g.action_column do |task| task.finished? end ... end -%>
WiceGrid is form-friendly: submitting grid in a form retains the state of the form.
Imagine that the user should be able to change the behavior of the grid using some other control on the page, and not a grid filter.
For example, on a page showing tasks, change between ‘Show active tasks’ to
‘Show archived tasks’ using a dropdown box. WiceGrid allows to keep the
status of the grid with all the filtering and sorting using helper
dump_filter_parameters_as_hidden_fields
which takes a grid
object and dumps all current sorting and filtering parameters as hidden
fields. Just include
dump_filter_parameters_as_hidden_fields(@grid)
inside your
form, and the newly rendered grid will keep ordering and filtering.
<% form_tag('', :method => :get) do %> <%= dump_filter_parameters_as_hidden_fields(@tasks_grid) %> <%= select_tag 'archived', options_for_select([['View active tasks', 0], ['View archived tasks', 1]], @archived ? 1 : 0), :onchange => 'this.form.submit()' %> <% end -%>
WiceGrid uses jQuery datepicker for Date and DateTime filters by default. Because this is part of the standard jQuery codebase, it is not bundled together with the plugin, and it is the responsibility of the programmer to include all necessary assets including localization files if the application is multilingual.
jQuery datepicker does not have any time related controls, and when dealing with DateTime filters, the time value is ignored.
Another option is standard Rails helpers. It’s possible to change between
the two styles of Date/DateTime filters in
config/initializers/wice_grid_config.rb
using constant
HELPER_STYLE, or set it on per-column basis:
g.column :name => 'Due Date', :attribute => 'due_date', :helper_style => :calendar do |task| task.due_date.to_s(:short) if task.due_date end g.column :name => 'Updated', :attribute => 'updated_at', :helper_style => :standard do |task| task.created_at.to_s(:short) end
Constants DATE_FORMAT
and DATETIME_FORMAT
in the
configuration file define the format of dates the user will see, as well as
the format of the string sent in a HTTP parameter. If you change the
formats, make sure that lamdbas defined in DATETIME_PARSER
and DATE_PARSER
return valid DateTime and Date objects.
jQuery datepicker
uses a different format flavor, therefore
there is an additional constant DATE_FORMAT_JQUERY
. While
DATE_FORMAT_JQUERY
is fed to datepicker
,
DATE_FORMAT
is still used for presenting initial date values
in filters, so make sure that DATE_FORMAT_JQUERY
and
DATE_FORMAT
result in an identical date representation.
Constant DATEPICKER_YEAR_RANGE
defined the range of years in
the Datepicker year dropdown. Alternatively, you can always change this
range dynamically with the following javascript:
$( ".hasDatepicker" ).datepicker( "option", "yearRange", "2000:2042" );
It is possible to switch to the All Records mode clicking on link “show
all” in the bottom right corner. This functionality should be used with
care. To turn this mode off for all grid instances, change constant
ALLOW_SHOWING_ALL_QUERIES
in
config/initializers/wice_grid_config.rb
to false
.
To do so for a specific grid, use initializer parameter
:allow_showing_all_records
.
Configuration constant START_SHOWING_WARNING_FROM
sets the
threshold number of all records after which clicking on the link results in
a javascript confirmation dialog.
It is possible to export the data displayed on a grid to a CSV file. The dumped data is the current resultset with all the current filters and sorting applied, only without the pagination constraint (i.e. all pages).
To enable CSV export add parameters enable_export_to_csv
and
csv_file_name
to the initialization of the grid:
@projects_grid = initialize_grid(Project, :include => [:customer, :supplier], :name => 'g2', :enable_export_to_csv => true, :csv_file_name => 'projects' )
csv_file_name
is the name of the downloaded file. This
parameter is optional, if it is missing, the name of the grid is used
instead. The export icon will appear at the bottom right corner of the
grid.
Next, each grid view helper should be placed in a partial of its own, requiring it from the master template for the usual flow. There must be no HTML or ERB code in this partial except for the grid helper.
By convention the name of such a partial follows the following pattern:
_GRID_NAME_grid.html.erb
In other words, a grid named tasks
is expected to be found in
a template called _tasks_grid.html.erb
(remember that the
default name of grids is ‘grid
’.)
Next, method export_grid_if_requested
should be added to the
end of each action containing grids with enabled CSV export.
export_grid_if_requested
intercepts CSV export requests and
evaluates the partial with the required grid helper.
The naming convention for grid partials can be easily overridden by
supplying a hash parameter to export_grid_if_requested
where
each key is the name of a grid, and the value is the name of the template
(like it is specified for render
, i.e. without ‘_’ and
extensions):
export_grid_if_requested('g1' => 'tasks_grid', 'g2' => 'projects_grid')
If the request is not a CSV export request,
export_grid_if_requested
does nothing and returns
false
, if it is a CSV export request, the method returns
true
.
If the action has no explicit render
call, it’s OK to just
place export_grid_if_requested
as the last line of the action:
def index @tasks_grid = initialize_grid(Task, :name => 'g1', :enable_export_to_csv => true, :csv_file_name => 'tasks' ) @projects_grid = initialize_grid(Project, :name => 'g2', :enable_export_to_csv => true, :csv_file_name => 'projects' ) export_grid_if_requested end
Otherwise, to avoid double rendering, use the return value of the method to
conditionally call your render
:
def index ........... export_grid_if_requested || render(:action => 'my_template') end
It’s also possible to supply a block which will be called if no CSV export is requested:
def index ........... export_grid_if_requested do render(:action => 'my_template') end end
If a column has to be excluded from the CSV export, set column
parameter in_csv
to false
:
g.column :in_csv => false do |task| link_to('Edit', edit_task_path(task)) end
If a column must appear both in HTML and CSV, but with different output,
duplicate the column and use parameters in_csv
and
in_html
to include one of them to html output only, the other
to CSV only:
# html version g.column :name => 'Title', :attribute => 'title', :in_csv => false do |task| link_to('Edit', edit_task_path(task.title)) end # plain text version g.column :name => 'Title', :in_html => false do |task| task.title end
The default field separator in generated CSV is a comma, but it’s possible to override it:
@products_grid = initialize_grid(Product, :enable_export_to_csv => true, :csv_field_separator => ';', :csv_file_name => 'products')
If you need an external CSV export button , add class
wg-external-csv-export-button
to any clickable element on page
and set its attribute data-grid-name
to the name of the grid:
<button class="wg-external-csv-export-button" data-grid-name="grid">Export To CSV</button>
If you need to disable the default export icon in the grid, add
:hide_csv_button => true
to the grid
helper.
Filters can also be detached from the grid table and placed anywhere on page.
This is a 3-step process.
First, define the grid with helper define_grid
instead of
grid
. Everything should be done the same way as with
grid
, but every column which will have an external filter, add
:detach_with_id => :some_filter_name+
in the column
definition. The value of :detach_with_id
is an arbitrary
string or a symbol value which will be used later to identify the filter.
<%= define_grid(@tasks_grid, :show_filters => :always) do |g| g.column :name => 'Title', :attribute => 'title', :detach_with_id => :title_filter do |task| link_to('Edit', edit_task_path(task.title)) end g.column :name => 'Archived', :attribute => 'archived', :detach_with_id => :archived_filter do |task| task.archived? ? 'Yes' : 'No' end g.column :name => 'Added', :attribute => 'created_at', :detach_with_id => :created_at_filter do |task| task.created_at.to_s(:short) end end -%>
Then, use grid_filter(grid, :some_filter_name)
to render
filters:
<% # rendering filter with key :title_filter %> <%= grid_filter @tasks_grid, :title_filter %> <% # rendering filter with key :archived_filter %> <%= grid_filter @tasks_grid, :archived_filter %> <% # rendering filter with key :created_at_filter %> <%= grid_filter @tasks_grid, :created_at_filter %> <% # Rendering the grid body %> <%= grid(@tasks_grid) %>
Finally, use render_grid(@grid)
to actually output the grid
table .
Using custom submit and reset buttons together with
:hide_submit_button => true
and :hide_reset_button
=> true
allows to completely get rid of the default filter row
and the default icons (see section ‘Submit/Reset Buttons’).
If a column was declared with :detach_with_id
, but never
output with grid_filter
, filtering the grid in development
mode will result in an warning javascript message and the missing filter
will be ignored. There is no such message in production.
There are two ways you can access the records outside the grid - using methods of the WiceGrid object and using callbacks.
Method current_page_records
returns exactly the same list of
objects displayed on page:
<%= grid(@tasks_grid) do |g| ... end -%> <p> IDs of records on the current page: <%= @tasks_grid.current_page_records.map(&:id).to_sentence %> </p>
Method all_pages_records
returns a list of objects browsable
through all pages with the current filters:
<%= grid(@tasks_grid) do |g| ... end -%> <p> IDs of all records: <%= @tasks_grid.all_pages_records.map(&:id).to_sentence %> </p>
Mind that this helper results in an additional SQL query.
Because of the current implementation of WiceGrid these helpers work only
after the declaration of the grid in the view. This is due to the lazy
nature of WiceGrid - the actual call to the database is made during the
execution of the grid
helper, because to build the correct
query columns declarations are required.
It is possible to set up callbacks which are executed from within the plugin just after the call to the database. The callbacks are called before rendering the grid cells, so the results of this processing can be used in the grid. There are 3 ways you can set up such callbacks:
Via a lambda object:
def index @tasks_grid = initialize_grid(Task, :with_paginated_resultset => lambda do |records| ... end ) end
Via a symbol which is the name of a controller method:
def index @tasks_grid = initialize_grid(Task, :with_paginated_resultset => :process_selection ) end def process_selection(records) ... end
Via a separate block:
def index @tasks_grid = initialize_grid(Task) @tasks_grid.with_paginated_resultset do |records| ... end end
There are two callbacks:
:with_paginated_resultset
- used to process records of the
current page
:with_resultset
- used to process all records browsable
through all pages with the current filters
While the :with_paginated_resultset
callback just receives the
list of records, :with_resultset
receives an ActiveRelation
object which can be used to obtain the list of all records:
def index @tasks_grid = initialize_grid(Task) @tasks_grid.with_resultset do |active_relation| all_records = active_relation.find(:first) ... end end
This lazy nature exists for performance reasons. Reading all records leads to an additional call, and there can be cases when processing all records should be triggered only under certain circumstances:
def index @tasks_grid = initialize_grid(Task) @tasks_grid.with_resultset do |wrapper| if params[:process_all_records] all_records = wrapper.call ... end end end
Icons used by the plugin are courtesy of Mark James, the creator of the SILK icon set - www.famfamfam.com/lab/icons/silk/.