SmartListing
Easily create AJAX-enabled data lists
with out-of-the-box support for sorting, filtering and in-place editing.
Ruby on Rails gem with built-in Twitter Bootstrap 3 support.
Get it!
Just add it to your Gemfile:
gem "smart_listing"
You can also fetch it from our GitHub repository.
Usage
You'll find here demonstration of few use cases where SmartListing comes in handy.
For more detailed technical docs, please refer to
README.md
.
Plain list
This is the simplest use of SmartListing. Just declare your list in the controller and render it in views.
def index # Create your list consisting of all active users smart_listing_create :users, User.active, partial: "users/list" end
-# Render list = smart_listing_render :users
<%# Render list for XHR requests %> <%= smart_listing_update :users %>
- unless smart_listing.empty? %table.table.table-striped %thead %th.col-md-3= "Name" %th.col-md-3= "Phone" %th.col-md-4= "Location" %th %span.glyphicon.glyphicon-shopping-cart{title: "Has credit card?"} %th.col-md-2= "Wallet saldo" %tbody - smart_listing.collection.each do |o| %tr -# User row goes here... -# Render nice pagination links fitted for Bootstrap 3 by default = smart_listing.paginate = smart_listing.pagination_per_page_links - else %p.warning No records!
Name | Phone | Location | Wallet saldo | |
---|---|---|---|---|
Kimberly Romero | 0-(135)527-3971 | Springfield Nuclear Power Plant | 1776.29 | |
Michael Sullivan | 5-(367)591-2872 | Churchill Downs | 9817.90 | |
Jessica Carroll | 9-(294)417-1260 | Try-N-Save | 7397.37 | |
Margaret Williams | 7-(120)368-7292 | The Palace of Westminster | 3934.57 | |
Betty Dean | 9-(303)520-2435 | LP Field | 8327.39 | |
Sara Howell | 8-(262)976-9431 | The Burrow | 4205.45 | |
Lois Gilbert | 8-(682)840-9489 | Qualcomm Stadium | 8324.66 | |
Robin Perkins | 4-(991)447-4770 | City Hall | 4602.92 | |
Sean Hunt | 7-(192)337-9074 | The Cathedral of Learning | 69.55 | |
Andrew Weaver | 5-(157)145-3337 | The Possum Lodge | 5505.01 |
Sorting
SmartListing supports implicit sorting by default. In order to enable it, just use
sortable
helper in view. Optionally, you can also specify default sort attribute.
For more capabilities, see explicit sorting described in the docs.
def index # Make users initally sorted by name ascending smart_listing_create :users, User.active, partial: "users/list", default_sort: {name: "asc"} end
= smart_listing_render :users
<%# Render list for XHR requests %> <%= smart_listing_update :users %>
- unless smart_listing.empty? %table.table.table-striped %thead -# Second argument of sortable is sorted attribute name %th.col-md-3= smart_listing.sortable "Name", "name" %th.col-md-3= "Phone" %th.col-md-4= smart_listing.sortable "Location", "location" %th %span.glyphicon.glyphicon-shopping-cart{title: "Has credit card?"} %th.col-md-2= smart_listing.sortable "Wallet saldo", "saldo" %tbody - smart_listing.collection.each do |o| %tr -# User row goes here... = smart_listing.paginate = smart_listing.pagination_per_page_links - else %p.warning No records!
Name | Phone | Location | Wallet saldo | |
---|---|---|---|---|
Aaron Brown | 9-(183)849-2299 | The International Space Station | 5158.30 | |
Amanda Gilbert | 0-(176)755-0146 | Ford Field | 4722.75 | |
Andrew Harper | 0-(694)078-1303 | Number 4, Privet Drive | 419.15 | |
Andrew Weaver | 5-(157)145-3337 | The Possum Lodge | 5505.01 | |
Angela Coleman | 3-(426)608-4734 | Mount Rushmore | 649.18 | |
Antonio Hansen | 7-(606)128-5986 | Starfleet Headquarters | 4452.75 | |
Betty Burns | 1-(191)313-7622 | New Meadowlands Stadium | 9210.77 | |
Betty Dean | 9-(303)520-2435 | LP Field | 8327.39 | |
Billy Lane | 0-(411)762-5273 | The International Space Station | 4345.68 | |
Cynthia Graham | 6-(928)644-6550 | Monstro Mart | 2659.59 |
In-place editing
SmartListing allows creating, editing, updating and deleting records natively. You can also define your own custom actions.
before_filter :find_user, except: [:index, :new, :create] def index smart_listing_create :users, User.active, partial: "users/list", default_sort: {name: "asc"} end def new @user = User.new end def create @user = User.create(user_params) end def edit end def update @user.update_attributes(user_params) end def destroy @user.destroy end def credit_card_switch @user.toggle :credit_card end private def find_user @user = User.find(params[:id]) end def user_params params.require(:user).permit(:name, :location, :phone, :credit_card, :saldo, :session_id) end
<%# index.js.erb %> <%= smart_listing_update(:users) %> <%# new.js.erb %> <%= smart_listing_item :users, :new, @user, "users/form" %> <%# create.js.erb %> <%= smart_listing_item :users, :create, @user, @user.valid? ? "users/user" : "users/form" %> <%# edit.js.erb %> <%= smart_listing_item :users, :edit, @user, "users/form" %> <%# update.js.erb %> <%= smart_listing_item :users, :update, @user, @user.valid? ? "users/user" : "users/form" %> <%# destroy.js.erb %> <%= smart_listing_item :users, :destroy, @user %>
- unless smart_listing.empty? %table.table.table-striped %thead %th.col-md-3= smart_listing.sortable "Name", "name" %th.col-md-3= "Phone" %th.col-md-4= smart_listing.sortable "Location", "location" %th %span.glyphicon.glyphicon-shopping-cart{title: "Has credit card?"} %th.col-md-2= smart_listing.sortable "Wallet saldo", "saldo" %tbody - smart_listing.collection.each do |o| %tr.editable{data: {id: o.id}} -# Render user row partial = smart_listing.render object: o, partial: "users/user", locals: {object: o} -# Render nice "new item" button fitted for Bootstrap 3 by default = smart_listing.item_new colspan: 6, link: new_user_path = smart_listing.paginate = smart_listing.pagination_per_page_links - else %p.warning No records!
%td= object.name %td= object.phone %td= object.location %td= display_tick object.credit_card? %td= format_decimal object.saldo %td.actions= smart_listing_item_actions [ {name: :custom, url: credit_card_switch_user_path(u), method: :put, icon: "glyphicon glyphicon-credit-card", remote: true, if: !object.credit_card?, title: "Enable credit card"}, {name: :edit, url: edit_user_path(object)}, {name: :destroy, url: user_path(object), confirmation: "Are you sure you want to delete this?"}]
%td{colspan: 6} = form_for object, url: object.new_record? ? users_path : user_path(object), remote: true, html: {class: "form-horizontal"} do |f| -# Form fields go here... = f.submit "Save", class: "btn btn-primary" %button.btn.btn-link.cancel Cancel
Name | Phone | Location | Wallet saldo | ||
---|---|---|---|---|---|
Aaron Brown | 9-(183)849-2299 | The International Space Station | 5158.30 | ||
Amanda Gilbert | 0-(176)755-0146 | Ford Field | 4722.75 | ||
Andrew Harper | 0-(694)078-1303 | Number 4, Privet Drive | 419.15 | ||
Andrew Weaver | 5-(157)145-3337 | The Possum Lodge | 5505.01 | ||
Angela Coleman | 3-(426)608-4734 | Mount Rushmore | 649.18 | ||
Antonio Hansen | 7-(606)128-5986 | Starfleet Headquarters | 4452.75 | ||
Betty Burns | 1-(191)313-7622 | New Meadowlands Stadium | 9210.77 | ||
Betty Dean | 9-(303)520-2435 | LP Field | 8327.39 | ||
Billy Lane | 0-(411)762-5273 | The International Space Station | 4345.68 | ||
Cynthia Graham | 6-(928)644-6550 | Monstro Mart | 2659.59 | ||
New item |
Controls form
SmartListing includes controls form that allows for AJAX-enabled filtering of entries. You can add it in your view using
smart_listing_controls_for
helper. You also obviously need to handle controls form parameters in your controller.
def index users_scope = User.active # Apply the search control filter. # Note: `like` method here is not built-in Rails scope. You need to define it by yourself. users_scope = users_scope.like(params[:filter]) if params[:filter] # Apply the credit card checkbox filter users_scope = users_scope.with_credit_card if params[:with_credit_card] == "1" @users = smart_listing_create :users, users_scope, partial: "users/list", default_sort: {name: "asc"} end
-# Render smart listing controls form = smart_listing_controls_for(:users, {class: "form-inline text-right"}) do -# Add checkbox to form - switching it submits the form .checkbox %label.checkbox.inline = hidden_field_tag :with_credit_card, "0" = check_box_tag :with_credit_card = "Has credit card?" -# Add search box to form - it submits the form automatically on text change .form-group.filter.input-append = text_field_tag :filter, '', class: "search form-control", placeholder: "Search...", autocomplete: :off %button.btn.btn-primary.disabled{type: :submit} %span.glyphicon.glyphicon-search
Name | Phone | Location | Wallet saldo | |
---|---|---|---|---|
Aaron Brown | 9-(183)849-2299 | The International Space Station | 5158.30 | |
Amanda Gilbert | 0-(176)755-0146 | Ford Field | 4722.75 | |
Andrew Harper | 0-(694)078-1303 | Number 4, Privet Drive | 419.15 | |
Andrew Weaver | 5-(157)145-3337 | The Possum Lodge | 5505.01 | |
Angela Coleman | 3-(426)608-4734 | Mount Rushmore | 649.18 | |
Antonio Hansen | 7-(606)128-5986 | Starfleet Headquarters | 4452.75 | |
Betty Burns | 1-(191)313-7622 | New Meadowlands Stadium | 9210.77 | |
Betty Dean | 9-(303)520-2435 | LP Field | 8327.39 | |
Billy Lane | 0-(411)762-5273 | The International Space Station | 4345.68 | |
Cynthia Graham | 6-(928)644-6550 | Monstro Mart | 2659.59 |
Cart support
You can also build some kind of cart-like functionality using SmartListing. This can be achieved using callbacks.
def index session[:selected_users] = {} if params.[:clear] @users = smart_listing_create(:users, User.all, partial: "users/list", default_sort: {name: "asc"}) set_withdrawal_status end def withdraw if params[:id] @users = User.where(id: params[:id]) else @users = User.where(id: session[:selected_users].try(:keys)) session[:selected_users] = {} end @users.update_all(saldo: 0) set_withdrawal_status end def select params[:user_selection].each do |key, value| if value == "true" session[:selected_users] ||= {} session[:selected_users][key] = 1 else session[:selected_users].delete(key) end end set_withdrawal_status end private def set_withdrawal_status @selected_users = User.where(id: session[:selected_users].try(:keys)) @selected_saldo = @selected_users.sum(:saldo) unless @selected_users.empty? end
= smart_listing_render(:users, data: {callback_href: select_users_path})
%td= check_box_tag :checkbox, "user_selection[#{object.id}]", session[:selected_users].try(:[], object.id.to_s) == 1, :class => "callback", :autocomplete => "off" %td= object.name %td= object.phone %td= object.location %td= display_tick object.credit_card? %td= format_decimal object.saldo %td.actions= smart_listing_item_actions [{:name => :custom, :url => withdraw_users_path(:id => u.id), :method => :put, :icon => "glyphicon glyphicon-send", :remote => true}]
-# Contents of "users/cart_summary" partial: - if @selected_saldo = link_to withdraw_users_path, :class => "btn btn-primary pull-right", :remote => true, :method => :put do %i.glyphicon.glyphicon-send = "Withdraw" = link_to "Clear", users_path(:clear => true), :class => "btn btn-link pull-right", :remote => true %h4 = "You have selected " = @selected_users.count = " users with overall saldo of " = "%.2f" % @selected_saldo
-# Put this into "users/list" partial: #cart_summary = render "users/cart_summary"
<%# Contents of "users/select.js.erb": %> $("#cart_summary").html("<%= escape_javascript(render("users/cart_summary")) %>");
<%# Contents of "users/withdraw.js.erb": %> <% @users.each do |user| %> <%= smart_listing_item :cart, :update, user, "users/user" %> <% end %> $("#users").smart_listing().fadeLoaded(); $("#cart_summary").html("<%= escape_javascript(render("users/cart_summary")) %>");
Name | Phone | Location | Wallet saldo | |||
---|---|---|---|---|---|---|
Aaron Brown | 9-(183)849-2299 | The International Space Station | 5158.30 | |||
Amanda Gilbert | 0-(176)755-0146 | Ford Field | 4722.75 | |||
Andrew Harper | 0-(694)078-1303 | Number 4, Privet Drive | 419.15 | |||
Andrew Weaver | 5-(157)145-3337 | The Possum Lodge | 5505.01 | |||
Angela Coleman | 3-(426)608-4734 | Mount Rushmore | 649.18 | |||
Antonio Hansen | 7-(606)128-5986 | Starfleet Headquarters | 4452.75 | |||
Betty Burns | 1-(191)313-7622 | New Meadowlands Stadium | 9210.77 | |||
Betty Dean | 9-(303)520-2435 | LP Field | 8327.39 | |||
Billy Lane | 0-(411)762-5273 | The International Space Station | 4345.68 | |||
Cynthia Graham | 6-(928)644-6550 | Monstro Mart | 2659.59 |
Custom list
SmartListing can be used not only with tables. Just create custom view definitions in SmartListing partials and you can for example show a nice FAQ list with Bootstrap 3 components using only the usual
smart_listing_render
method in main view. You can also add controls form for simple searching.
faqs_scope = Faq.all faqs_scope = faqs_scope.like(params[:filter]) if params[:filter] @faqs = smart_listing_create :faqs, faqs_scope, partial: "faqs/list"
.faqlist - unless smart_listing.empty? -# Rendering the FAQs in Bootstrap 3' s accordion .panel-group#accordion - smart_listing.collection.each do |o| -# Here goes our custom rendering of FAQ content e.g. as Bootstrap 3 panels = smart_listing.paginate = smart_listing.pagination_per_page_links - else %p.alert.alert-info No records!
= smart_listing_controls_for(:faqs, {class: "form-inline text-right"}) do .form-group.filter.input-append = text_field_tag :filter, '', class: "search form-control", placeholder: "Search...", autocomplete: :off %button.btn.btn-primary.disabled{type: :submit} %span.glyphicon.glyphicon-search = smart_listing_render(:faqs)
<%= smart_listing_update(:faqs) %>
Simplified views
Since SmartListing 1.1.0, in cases where you have single listing per controller, you don't need to create dedicated views like
index.js.erb
,
update.js.erb
etc anymore.
Take for example
UsersController
which is supposed to render single list of all your users.
Just create your controller actions like you usually do, but use SmartListing methods in their alternative syntax, without specifying the name, ie.:
smart_listing_create partial: "users/list"
In this case, SmartListing will be named automatically after your controller (ie. 'users'). One last thing you need to do is defining resource-finder and collection-finder helper methods:
def smart_listing_resource @user ||= params[:id] ? User.find(params[:id]) : User.new(params[:user]) end helper_method :smart_listing_resource def smart_listing_collection @users ||= User.all end helper_method :smart_listing_collection
Missing views that you don't need to create now, are provided by default SmartListing templates for
create
,
destroy
,
edit
,
index
,
new
,
and
update
actions.
More Customization
SmartListing 1.1.0 brings also some improvement in terms of customization. Apart from application-wide class and attribute customization (via initializer file) you can now define your named config profiles and use different config profile for every SmartListing in your app. This is particularly helpful when you are building Rails engine and don't want to interfere with main app configuration.
To start with config profiles, create an initializer and name your profile:
SmartListing.configure(:awesome_profile) do |config| # put your definitions here end
Then make a helper method named
smart_listing_config_profile
returning your profile name, ie.:
def smart_listing_config_profile :awesome_profile end helper_method :smart_listing_config_profile
This will make all SmartListing helpers refer to your
:awesome_profile
in all rendered views.
One last thing is to make Javascript helpers using your profile too.
SmartListing.config.merge()
method does that by accepting JSON with config values. This JSON on the other hand can be obtained by using helper
SmartListing.config(:awesome_profile).to_json
The typical use case for this is to dump your config profile variables as body data attribute:
%body{data: {smart_listing_config: SmartListing.config(:awesome_profile).to_json}}
And then somewhere in your Javascript code:
SmartListing.config.merge()
SmartListing.config.merge()
without parameters reads body data attribute defined above and updates Javascript config variables accordingly.
That's all folks!
If you liked SmartListing, please feel free to share!
Tweet #smart_listing
We'd love to see you contributing to SmartListing development. Start with forking our repository.
In case you have any questions, comments, or found bug - file an issue on GitHub or contact us directly.
Check out also our newest gem - MailyHerald, Ruby on Rails email processing solution.
SmartListing is created by Sology. We are a Ruby on Rails software house based in Kraków, Poland.