Filtering, pagination and column sorting all work through the same mechanism in Fae.
To use any one of these, you'll need to add a route to access the built in filter
action.
# config/routes.rb
resources :people do
post 'filter', on: :collection
end
If you need to filter your content on your table views, Fae provides a system and helpers to do so.
Using the helpers provided, the filter form will POST to a filter action inherited from Fae::BaseController
. You can override this action, but by default it will pass the params to a class method in your model called filter
. It's then up you to scope the data that gets returned and rendered in the table.
Let's walk through an example. Using the Person
model from above, let's say a person belongs_to :company
and has_many :groups
. We'll want to use select filters for companies and groups, and a keyword search to filter by people and company name.
Next we'll add the form to our view as the first child of .content
:
/ app/views/admin/people/index.html.slim
/ ...
.content
== fae_filter_form do
== fae_filter_select :company
== fae_filter_select :groups
table.js-sort-column
/ ...
The search field is built into fae_filter_form
, but we'll need to provide a fae_filter_select
for each select element in our filter bar.
Heavy customization of views can confuse the AJAX function that injects results into the table. This can be solved by adding the class js-results-table
to the results table.
Finally we need to define our class methods to scope the Person
class. This data will be assigned to @items
and injected into the table via AJAX.
ModelName#filter(params)
will be the scope when data is filtered. The params
passed in will be the data directly from the fae_filter_select
helpers we defined, plus params['search']
from the search field.
From the form above we can assume our params look like this:
{
'search' => 'text from search field',
'company' => 12, # value from company select
'groups' => 3 # value from groups select
}
So let's use that data to craft our class method.
# app/models/person.rb
def self.filter(params)
# build conditions if specific params are present
conditions = {}
conditions[:company_id] = params['company'] if params['company'].present?
conditions['groups.id'] = params['groups'] if params['groups'].present?
# use good 'ol MySQL to search if search param is present
search = []
if params['search'].present?
search = ["people.name LIKE ? OR companies.name LIKE ?", "%#{params['search']}%", "%#{params['search']}%"]
end
# apply conditions and search from above to our scope
order(:name)
.includes(:company, :groups).references(:company, :groups)
.where(conditions).where(search)
end
fae_filter_form
Displays the filter form, which includes the search field, submit, and reset buttons. It accepts options, followed by an optional block.
option | type | default | description |
---|---|---|---|
action | string | "#{@index_path}/filter" | the path the form submits to |
title | string | "Search #{@klass_humanized.pluralize.titleize}" | the h2 text in the filter form |
search | boolean | true | displays the search field |
cookie_key | string | false | set your cookie name on the fae_filter_form if you want to persist the selected filtered state |
Examples
== fae_filter_form title: 'Search some stuff', search: false do
// optional form elements
fae_filter_select(attribute, options)
Dislays a select tag to be used within a fae_filter_form
.
option | type | default | description |
---|---|---|---|
label | string | attribute.to_s.titleize | label on select |
collection | ActiveRecord collection | AttributeAsClass.for_fae_index | the collection of AR objects to populate the select options |
label_method | symbol | :fae_display_field | the attribute to use as the label in the select options |
placeholder | string or boolean | "All #{options[:label]}" | the blank value in the select. It can be set to false to disable |
options | array | [] | an alternative array of options if the options aren't an ActiveRecord collection |
grouped_options | array | [] | an alternative array of grouped options if the options aren't an ActiveRecord collection |
grouped_by | symbol | a Fae association on the models in collection . The association must have a fae_display_name method |
Examples
== fae_filter_form do
== fae_filter_select :group, label: 'Groupings', collection: Groups.for_filters
== fae_filter_select :group, label: 'Groupings', collection: Groups.for_filters, grouped_by: :filter
If your index tables are stacked with items and it's taking a while to load, you may consider adding pagination to them. To do this, just follow these steps.
Verify the object resources has the filter route:
# config/routes.rb
resources :people do
post 'filter', on: :collection
end
Then call the fae_paginate
helper under the table:
app/views/admin/people/index.html.slim
/ app/views/admin/people/index.html.slim
table
/ table stuff here
== fae_paginate @items
The fae_paginate
helper can be called anywhere and will return correct links to paginate @items
, given @items
is an ActiveRecord collection. However, directly under the table is where our CSS supports it. Other places may need additional CSS.
Pagination in Fae defaults to 25 items per page, but that can be customize in Fae's initializer:
# config/initializers/fae.rb
Fae.setup do |config|
## per_page
# Sets the default number of items shown in paginated lists
# Defaults to 25
config.per_page = 50
end
Under the hood Fae uses Kaminari to power the pagination, so ther are additional ways to customize it, including:
- adding
paginates_per 50
to specific models - overriding Fae's Kaminari initializer with your own
- ignore
fae_paginate
and use Kaminari'spaginate
directly - etc...
Pagination is disabled in the Fae:BaseController by default. This can be customized with a private use_pagination
method in controllers inheriting from Fae::BaseController.
# app/controllers/admin/example_controller.rb
module Admin
class ExamplesController < Fae::BaseController
private
def use_pagination
true
end
end
end
Column sorting is easy to add in if you aren't paginating your table. Adding a js-sort-column
class to the table will make the table headers clickable, allowing you to arrange the items in the table via the data in the column.
This method relies on the data already in the table, so if you are paginating your items, only the current page you're on will be sorted. This is probably not the expected functionality, but Fae has a second way of dealing with this.
Again, verify the object resources has the filter route:
# config/routes.rb
resources :people do
post 'filter', on: :collection
end
Then remove the old js-sort-column
class from the table.
For each column you want enable, add a data-sort
attr to the <th>
following these conventions.
For an attribute on the current object:
data-sort="attribute_name"
To sort by an association's attribute:
data-sort="association_name.attribute_name"
association_name
must be an existing column and association_name
must be a defined association on the current object.
Here's an example:
/ app/views/admin/people/index.html.slim
table
thead
tr
th data-sort="first_name" Name
th data-sort="title" Title
th data-sort="slug" Slug
th data-sort="office.city" Location
th.-action-wide data-sort="updated_at" Modified
th.-action-wide On Stage
th.-action-wide On Prod
# app/models/person.rb
class Person < ActiveRecord::Base
include Fae::BaseModelConcern
belongs_to :office
end
Fae uses jQuery UI Sortable to make your tables rows drag-and-drop sortable. To take advantage of this add a position
attribute to your object when you scaffold it:
rails g fae:scaffold Person first_name last_name position:integer
If your object has already been created you'll have to do a couple steps to make it sortable.
-
Add an integer
position
column on your object with a type ofinteger
. -
Add a default scope to the object model to always order by position. Optionally you can config
acts_as_list
class Person < ApplicationRecord
acts_as_list add_new_at: :top
default_scope { order(:position) }
# ...
- In your HTML you must add the
js-sort-row
class to your<table>
, and thesortable-handle
class on the<td>
you want to make draggable. Here is an example in slim:
table.js-sort-row / <---- here
thead
tr
th.-action data-sorter='false'
th Name
tbody
- @items.each do |item|
tr id=fae_sort_id(item)
td.sortable-handle / <---- here
i.icon-sort
td = item.name