Skip to content

Commit

Permalink
Readme update
Browse files Browse the repository at this point in the history
  • Loading branch information
ljachymczyk committed May 14, 2014
1 parent 80803f7 commit 96f50fb
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 19 deletions.
206 changes: 202 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SmartListing

SmartListing helps creating sortable lists of ActiveRecord collections with pagination, filtering and inline editing.
SmartListing helps creating AJAX-enabled sortable lists of ActiveRecord collections or arrays with pagination, filtering, sorting and in-place editing.

## Installation

Expand All @@ -16,23 +16,221 @@ Then run:
$ bundle install
```

### Initializer

Optionally you can also install some configuration initializer:

```sh
$ rails generate smart_listing:install
```

and views in order to customize them:
It will be placed in `config/initializers/smart_listing.rb` and will allow you to tweak some configuration settings like HTML classes and data attributes names.

### Custom views

SmartListing comes with some built-in views which are by default compatible with Bootstrap 3. You can easily change them after installing:

```sh
$ rails generate smart_listing:views
```

Files will be placed in `app/views/smart_listing`.

## Usage

Pending...
Let's start with a controller. In order to use SmartListing, in most cases you need to include controller extensions and SmartListing helper methods:

```ruby
include SmartListing::Helper::ControllerExtensions
helper SmartListing::Helper
```

Next, put following code in controller action you desire:

```ruby
@users = smart_listing_create(:users, User.active, :partial => "users/listing")
```

This will create SmartListing named `:users` consisting of ActiveRecord scope `User.active` elements and rendered by partial `users/listing`. You can also use arrays instead of ActiveRecord collections. Just put @:array => true@ option just like for Kaminari.

In the main view (typically something like `index.html.erb` or `index.html.haml`), use this method to render listing:

```ruby
smart_listing_render(:users)
```

`smart_listing_render` does some magic and renders `users/listing` partial which may look like this (in HAML):

```haml
- unless smart_listing.empty?
%table
%thead
%tr
%th User name
%th Email
%tbody
- smart_listing.collection.each do |user|
%tr
%td= user.name
%td= user.email
= smart_listing.paginate
- else
%p.warning No records!
```

You can see that listing template has access to special `smart_listing` local variable which is basically an instance of `SmartListing::Helper::Builder`. It provides you with some helper methods that ease rendering of SmartListing:

* `Builder#paginate` - renders Kaminari pagination,
* `Builder#pagination_per_page_links` - display some link that allow you to customize Kaminari's `per_page`,
* `Builder#collection` - accesses underlying list of items,
* `Builder#empty?` - checks if collection is empty,
* `Builder#count` - returns collection count,
* `Builder#render` - basic template's `render` wrapper that automatically adds `smart_listing` local variable,

There are also other methods that will be described in detail below.

If you are using SmartListing with AJAX on (by default), one last thing required to make pagination (and other features) work is to create JS template for main view (typically something like `index.js.erb`):

```erb
<%= smart_listing_update(:users) %>
```

### Sorting

SmartListing supports two modes of sorting: implicit and explicit. Implicit mode is enabled by default. In this mode, you define sort columns directly in the view:

```haml
- unless smart_listing.empty?
%table
%thead
%tr
%th= smart_listing.sortable "User name", "name"
%th= smart_listing.sortable "Email", "email"
%tbody
- smart_listing.collection.each do |user|
%tr
%td= user.name
%td= user.email
= smart_listing.paginate
- else
%p.warning No records!
```

In this case `"name"` and `"email"` are sorting column names. `Builder#sortable` renders special link containing column name and sort order (either `asc`, `desc`, or empty value).

You can also specify default sort order in the controller:

```ruby
@users = smart_listing_create(:users, User.active, :partial => "users/listing", :default_sort => {"name" => "asc"})
```

Implicit mode is convenient with simple data sets. In case you want to sort by joined column names, we advise you to use explicit sorting:
```ruby
@users = smart_listing_create :users, User.active.joins(:stats), :partial => "users/listing",
:sort_attributes => [[:last_signin, "stats.last_signin_at"]],
:default_sort => {:last_signin => "desc"}
```

Note that `:sort_attributes` are array which of course means, that order of attributes matters.

### List item management and in-place editing

In order to allow managing and editing list items, we need to reorganize our views a bit. Basically, each item needs to have its own partial:

```haml
- unless smart_listing.empty?
%table
%thead
%tr
%th= smart_listing.sortable "User name", "name"
%th= smart_listing.sortable "Email", "email"
%th
%tbody
- smart_listing.collection.each do |user|
%tr.editable{:data => {"id" => user.id}}
= smart_listing.render :partial => 'users/user', :locals => {:user => user}
= smart_listing.item_new :colspan => 3, :link => new_user_path
= smart_listing.paginate
- else
%p.warning No records!
```

`<tr>` has now `editable` class and `data-id` attribute. These are essential to make it work. We've used also a new helper: `Builder#new_item`. It renders new row which is used for adding new items. `:link` needs to be valid url to new resource action which renders JS:

```ruby
<%= smart_listing_item :users, :new, @new_user, "users/form" %>
```
Note that `new` action does not need to create SmartListing (via `smart_listing_create`). It just initializes `@new_user` and renders JS view.
New partial for user (`users/user`) may look like this:
```haml
%td= user.name
%td= user.email
%td.actions= smart_listing_item_actions [{:name => :edit, :url => edit_user_path(user)}, {:name => :destroy, :url => user_path(user)}]
```

`smart_listing_item_actions` renders here links that allow to edit and destroy user item. `:edit` and `:destroy` are built-in actions, you can also define your `:custom` actions. Again. `<td>`'s class `actions` is important.

Controller actions referenced by above urls are again plain Ruby on Rails actions that render JS like:

```erb
<%= smart_listing_item :users, :new, @user, "users/form" %>
<%= smart_listing_item :users, :edit, @user, "users/form" %>
<%= smart_listing_item :users, :destroy, @user %>
```

Partial name supplied to `smart_listing_item` (`users/form`) references `@user` as `object` and may look like this:

```haml
%td{:colspan => 3}
- if object.persisted?
%p Edit user
- else
%p Add user
= form_for object, :url => object.new_record? ? users_path : user_path(object), :remote => true do |f|
%p
Name:
= f.text_field :name
%p
Email:
= f.text_field :email
%p= f.submit "Save"
```

And one last thing is `update` controller action JS view:

```ruby
<%= smart_listing_item :users, :update, @user, @user.valid? ? "users/user" : "users/form" %>
```
### Controls (filtering)
SmartListing controls allow you to change somehow presented data. This is typically used for filtering records. Let's see how view with controls may look like:
```haml
= smart_listing_controls_for(:users) do
.filter.input-append
= text_field_tag :filter, '', :class => "search", :placeholder => "Type name here", :autocomplete => "off"
%button.btn.disabled{:type => "submit"}
%i.icon.icon-search
```

This gives you nice Bootstrap-enabled filter field with keychange handler. Of course you can use any other form fields in controls too.

When form field changes its value, form is submitted and request is made. This needs to be handled in controller:

```ruby
users_scope = User.active.joins(:stats)
users_scope = users_scope.like(params[:filter]) if params[:filter]
@users = smart_listing_create :users, users_scope, :partial => "users/listing"
```

### View
Then, JS view is rendered and your SmartListing updated. That's it!

## Credits

Expand Down
24 changes: 13 additions & 11 deletions app/helpers/smart_listing/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ def initialize(smart_listing_name, smart_listing, template, options, proc)

def paginate options = {}
if @smart_listing.collection.respond_to? :current_page
@template.paginate @smart_listing.collection, {:remote => true, :param_name => @smart_listing.param_name(:page), :params => UNSAFE_PARAMS}.merge(@smart_listing.kaminari_options)
@template.paginate @smart_listing.collection, {:remote => @smart_listing.remote?, :param_name => @smart_listing.param_name(:page), :params => UNSAFE_PARAMS}.merge(@smart_listing.kaminari_options)
end
end

def collection
@smart_listing.collection
end

# Check if smart list is empty
def empty?
@smart_listing.count == 0
end
Expand Down Expand Up @@ -124,10 +125,10 @@ def item_new options = {}, &block
locals = {
:colspan => options.delete(:colspan),
:no_items_classes => no_records_classes,
:no_items_text => options.delete(:no_items_text),
:no_items_text => options.delete(:no_items_text) || @template.t("smart_listing.msgs.no_items"),
:new_item_button_url => options.delete(:link),
:new_item_button_classes => new_item_button_classes,
:new_item_button_text => options.delete(:text),
:new_item_button_text => options.delete(:text) || @template.t("smart_listing.actions.new"),
:new_item_autoshow => block_given?,
:new_item_content => nil,
}
Expand All @@ -148,11 +149,6 @@ def item_new options = {}, &block
end
end

# Check if smart list is empty
def empty?
@smart_listing.count == 0
end

def count
@smart_listing.count
end
Expand Down Expand Up @@ -205,14 +201,20 @@ def smart_listing_for name, *args, &block
output
end

def smart_listing_render name
smart_listing_for name do |smart_listing|
concat(smart_listing.render_list)
end
end

def smart_listing_controls_for name, *args, &block
smart_listing = @smart_listings[name]
smart_listing = @smart_listings.try(:[], name)

classes = [SmartListing.config.classes(:controls), args.first[:class]]

form_tag(smart_listing.href || {}, :remote => true, :method => :get, :class => classes, :data => {:smart_listing => name}) do
form_tag(smart_listing.try(:href) || {}, :remote => smart_listing.try(:remote?) || true, :method => :get, :class => classes, :data => {:smart_listing => name}) do
concat(content_tag(:div, :style => "margin:0;padding:0;display:inline") do
concat(hidden_field_tag("#{smart_listing.base_param}[_]", 1, :id => nil)) # this forces smart_listing_update to refresh the list
concat(hidden_field_tag("#{smart_listing.try(:base_param)}[_]", 1, :id => nil)) # this forces smart_listing_update to refresh the list
end)
concat(capture(&block))
end
Expand Down
2 changes: 1 addition & 1 deletion app/views/smart_listing/_pagination_per_page_link.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
-%>

<% name = page == 0 ? t('views.pagination.unlimited') : page %>
<%= url ? link_to(name, url, :remote => true) : content_tag(:span, name) %>
<%= url ? link_to(name, url, :remote => smart_listing.remote?) : content_tag(:span, name) %>
2 changes: 2 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ en:
smart_listing:
msgs:
destroy_confirmation: Destroy?
no_items: No items
actions:
destroy: Destroy
edit: Edit
show: Show
new: New item
views:
pagination:
per_page: Per page
Expand Down
11 changes: 8 additions & 3 deletions lib/smart_listing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ def initialize name, collection, options = {}
:per_page => :per_page,
:sort => :sort,
},
:partial => @name, # smart list partial name
:partial => @name, # SmartListing partial name
:array => false, # controls whether smart list should be using arrays or AR collections
:max_count => nil, # limit number of rows
:unlimited_per_page => false, # allow infinite page size
:sort_attributes => :implicit, # allow implicitly setting sort attributes
:default_sort => {}, # default sorting
:paginate => true, # allow pagination
:href => nil, # set smart list target url (in case when different than current url)
:callback_href => nil, # set smart list callback url (in case when different than current url)
:href => nil, # set SmartListing target url (in case when different than current url)
:remote => true, # SmartListing is remote by default
:callback_href => nil, # set SmartListing callback url (in case when different than current url)
:memorize_per_page => false,
:page_sizes => DEFAULT_PAGE_SIZES, # set available page sizes array
:kaminari_options => {}, # Kaminari's paginate helper options
Expand Down Expand Up @@ -131,6 +132,10 @@ def callback_href
@options[:callback_href]
end

def remote?
@options[:remote]
end

def page_sizes
@options[:page_sizes]
end
Expand Down

0 comments on commit 96f50fb

Please sign in to comment.