├── .gitignore
├── Changes.md
├── Gemfile
├── Gemfile.lock
├── Guardfile
├── LICENSE
├── README.md
├── Rakefile
├── app
├── assets
│ └── javascripts
│ │ └── smart_listing.coffee.erb
├── helpers
│ └── smart_listing
│ │ ├── application_helper.rb
│ │ └── helper.rb
└── views
│ ├── kaminari
│ └── smart_listing
│ │ ├── _first_page.html.erb
│ │ ├── _gap.html.erb
│ │ ├── _last_page.html.erb
│ │ ├── _next_page.html.erb
│ │ ├── _page.html.erb
│ │ ├── _paginator.html.erb
│ │ └── _prev_page.html.erb
│ └── smart_listing
│ ├── _action_custom.html.erb
│ ├── _action_delete.html.erb
│ ├── _action_edit.html.erb
│ ├── _action_show.html.erb
│ ├── _item_new.html.erb
│ ├── _pagination_per_page_link.html.erb
│ ├── _pagination_per_page_links.html.erb
│ ├── _sortable.html.erb
│ ├── _update_list.js.erb
│ ├── create.js.erb
│ ├── destroy.js.erb
│ ├── edit.js.erb
│ ├── index.js.erb
│ ├── item
│ ├── _create.js.erb
│ ├── _create_continue.js.erb
│ ├── _destroy.js.erb
│ ├── _edit.js.erb
│ ├── _new.js.erb
│ ├── _remove.js.erb
│ └── _update.js.erb
│ ├── new.js.erb
│ └── update.js.erb
├── bin
└── rails
├── config
├── locales
│ └── en.yml
└── routes.rb
├── lib
├── generators
│ └── smart_listing
│ │ ├── install_generator.rb
│ │ ├── templates
│ │ └── initializer.rb
│ │ └── views_generator.rb
├── smart_listing.rb
├── smart_listing
│ ├── config.rb
│ ├── engine.rb
│ └── version.rb
└── tasks
│ └── smart_list_tasks.rake
├── smart_listing.gemspec
└── spec
├── dummy
├── Gemfile
├── Gemfile.lock
├── README.rdoc
├── Rakefile
├── app
│ ├── assets
│ │ ├── images
│ │ │ └── .keep
│ │ ├── javascripts
│ │ │ └── application.js
│ │ └── stylesheets
│ │ │ └── application.css.scss
│ ├── controllers
│ │ ├── admin
│ │ │ └── users_controller.rb
│ │ ├── application_controller.rb
│ │ ├── concerns
│ │ │ └── .keep
│ │ └── users_controller.rb
│ ├── helpers
│ │ └── application_helper.rb
│ ├── mailers
│ │ └── .keep
│ ├── models
│ │ ├── .keep
│ │ ├── application_record.rb
│ │ ├── concerns
│ │ │ └── .keep
│ │ └── user.rb
│ └── views
│ │ ├── admin
│ │ └── users
│ │ │ ├── _form.html.erb
│ │ │ ├── _item.html.erb
│ │ │ ├── _list.html.erb
│ │ │ └── index.html.erb
│ │ ├── layouts
│ │ └── application.html.erb
│ │ └── users
│ │ ├── _list.html.erb
│ │ ├── _searchable_list.html.erb
│ │ ├── _sortable_list.html.erb
│ │ ├── index.html.erb
│ │ ├── index.js.erb
│ │ ├── searchable.html.erb
│ │ └── searchable.js.erb
├── bin
│ ├── bundle
│ ├── rails
│ └── rake
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── assets.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── cookies_serializer.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ ├── session_store.rb
│ │ ├── smart_listing.rb
│ │ └── wrap_parameters.rb
│ ├── locales
│ │ └── en.yml
│ ├── routes.rb
│ └── secrets.yml
├── db
│ ├── migrate
│ │ └── 20180126065408_create_user.rb
│ ├── schema.rb
│ └── seeds.rb
├── fixtures
│ └── users.yml
├── lib
│ └── assets
│ │ └── .keep
├── log
│ └── .keep
└── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ └── favicon.ico
├── features
├── custom_filters_spec.rb
├── manage_items_spec.rb
└── view_items_spec.rb
├── helpers
└── smart_listing
│ └── helper_spec.rb
├── lib
└── smart_listing_spec.rb
├── rails_helper.rb
├── spec_helper.rb
└── support
└── capybara
└── wait_for_ajax.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.rbc
2 | *.sassc
3 | .sass-cache
4 | capybara-*.html
5 | .rspec
6 | .rvmrc
7 | /.bundle
8 | /vendor/bundle
9 | /log/*
10 | /tmp/*
11 | /db/*.sqlite3
12 | /public/system/*
13 | /coverage/
14 | /spec/tmp/*
15 | **.orig
16 | rerun.txt
17 | pickle-email-*.html
18 | .project
19 | config/initializers/secret_token.rb
20 | *.gem
21 | /test/*
22 |
23 | .ruby-version
24 | .ruby-gemset
25 |
26 | *.swp
27 | *.swo
28 |
29 | spec/dummy/db/*.sqlite3
30 | spec/dummy/log/*.log
31 | spec/dummy/tmp/
32 | tags
33 |
34 | .byebug_history
35 |
--------------------------------------------------------------------------------
/Changes.md:
--------------------------------------------------------------------------------
1 | 1.2.3
2 | -----------
3 |
4 | - Fix sorting to mitigate possible SQL-injection and improve tests [Ivan Korunkov]
5 |
6 | 1.2.2
7 | -----------
8 |
9 | - Remove duplicated href key from config template #146 [nfilzi]
10 | - Replace deprecated .any? with .present? #143 [AakLak]
11 | - Development environment update #140 [mizinsky]
12 | - Fix sanitize_params method #137 [mizinsky]
13 | - Enable to configure global remote option and it to affects sortable helper #131 [kitabatake]
14 | - Kaminari update [mizinsky]
15 | - Update Readme for Rails >= 5.1 Users [mizinsky]
16 |
17 | 1.2.1
18 | -----------
19 |
20 | - Allow to render outside of controllers [bval]
21 | - Documentation fixes [blackcofla]
22 | - Use id.to_json so integers and uuids will both work [sevgibson]
23 | - Fix popover in bootstrap 4 [sevgibson]
24 | - Fix Kaminari #num_pages deprecation warning [tylerhunt]
25 | - Add support for Turbolinks 5 [wynksaiddestroy]
26 | - Use #empty? for AC::Params [phoffer]
27 | - Fix indentation in some files [boy-papan]
28 |
29 | 1.2.0
30 | -----------
31 |
32 | - Rails 5 support and Kaminari update [akostadinov]
33 | - Better handling of nested controls params
34 | - Fix controls not fading out list. Related to #51
35 | - Config now includes element templates
36 | - Add ability to pass locals to list view [GeorgeDewar]
37 |
38 | 1.1.2
39 | -----------
40 |
41 | - Some bugfixing: #20, #46, #58
42 |
43 | 1.1.0
44 | -----------
45 |
46 | - Config profiles
47 | - Remove duplicate href key [wynksaiddestroy]
48 | - API refactoring [GCorbel]
49 | - Feature Specs [GCorbel]
50 | - Avoid smart listing controls double initialization [lacco]
51 | - Turbolinks support [wynksaiddestroy]
52 | - Better form controls handling
53 | - Possibility to specify sort directions
54 |
55 | 1.0.0
56 | -----------
57 |
58 | - JS Events triggered on item actions
59 | - Fix filter resetting
60 | - Fix new item autoshow
61 | - Possibility to pass custom title to default actions
62 | - Confirmation tweaks
63 | - Multiple smart listings isolation
64 | - New sorting architecture (and implicit sorting attributes)
65 | - Controls helper
66 | - Slightly changed item action templates
67 |
68 | 0.9.8
69 | -----------
70 |
71 | - Custom popovers support
72 |
73 | 0.9.7
74 | -----------
75 |
76 | - Some bugfixing
77 | - Fix listing sorting XSS bug
78 | - Add possibility to display new item form by default
79 | - "Save & continue" support
80 |
81 | 0.9.6
82 | -----------
83 |
84 | - Some bugfixing
85 | - Initial setup generator
86 |
87 | 0.9.5
88 | -----------
89 |
90 | - Fix collection counting bug
91 | - Add builtin show action
92 | - Make CSS class and data attribute names generic and customizable (SmartListing.configure)
93 | - Make JavaScript more customizable
94 |
95 | 0.9.4
96 | -----------
97 |
98 | - Possibility to callback action
99 | - Changes in templates
100 |
101 | 0.9.3
102 | -----------
103 |
104 | - Possibility to specify kaminari options
105 | - Possibility to generate views and customize them in the app
106 | - Better custom action handling
107 |
108 | 0.9.2
109 | -----------
110 |
111 | - Add possibility to specify available page sizes in options hash
112 |
113 | 0.9.0
114 | -----------
115 |
116 | - Initial release
117 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gemspec
4 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | smart_listing (1.2.2)
5 | coffee-rails
6 | jquery-rails
7 | kaminari (>= 0.17)
8 | rails (>= 3.2)
9 |
10 | GEM
11 | remote: https://rubygems.org/
12 | specs:
13 | actioncable (5.1.4)
14 | actionpack (= 5.1.4)
15 | nio4r (~> 2.0)
16 | websocket-driver (~> 0.6.1)
17 | actionmailer (5.1.4)
18 | actionpack (= 5.1.4)
19 | actionview (= 5.1.4)
20 | activejob (= 5.1.4)
21 | mail (~> 2.5, >= 2.5.4)
22 | rails-dom-testing (~> 2.0)
23 | actionpack (5.1.4)
24 | actionview (= 5.1.4)
25 | activesupport (= 5.1.4)
26 | rack (~> 2.0)
27 | rack-test (>= 0.6.3)
28 | rails-dom-testing (~> 2.0)
29 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
30 | actionview (5.1.4)
31 | activesupport (= 5.1.4)
32 | builder (~> 3.1)
33 | erubi (~> 1.4)
34 | rails-dom-testing (~> 2.0)
35 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
36 | activejob (5.1.4)
37 | activesupport (= 5.1.4)
38 | globalid (>= 0.3.6)
39 | activemodel (5.1.4)
40 | activesupport (= 5.1.4)
41 | activerecord (5.1.4)
42 | activemodel (= 5.1.4)
43 | activesupport (= 5.1.4)
44 | arel (~> 8.0)
45 | activesupport (5.1.4)
46 | concurrent-ruby (~> 1.0, >= 1.0.2)
47 | i18n (~> 0.7)
48 | minitest (~> 5.1)
49 | tzinfo (~> 1.1)
50 | addressable (2.5.2)
51 | public_suffix (>= 2.0.2, < 4.0)
52 | arel (8.0.0)
53 | autoprefixer-rails (7.2.5)
54 | execjs
55 | bootstrap-sass (3.3.7)
56 | autoprefixer-rails (>= 5.2.1)
57 | sass (>= 3.3.4)
58 | builder (3.2.3)
59 | byebug (9.1.0)
60 | capybara (2.13.0)
61 | addressable
62 | mime-types (>= 1.16)
63 | nokogiri (>= 1.3.3)
64 | rack (>= 1.0.0)
65 | rack-test (>= 0.5.4)
66 | xpath (~> 2.0)
67 | capybara-webkit (1.14.0)
68 | capybara (>= 2.3.0, < 2.14.0)
69 | json
70 | coderay (1.1.2)
71 | coffee-rails (4.2.2)
72 | coffee-script (>= 2.2.0)
73 | railties (>= 4.0.0)
74 | coffee-script (2.4.1)
75 | coffee-script-source
76 | execjs
77 | coffee-script-source (1.12.2)
78 | concurrent-ruby (1.0.5)
79 | crass (1.0.3)
80 | database_cleaner (1.6.2)
81 | diff-lcs (1.3)
82 | erubi (1.7.0)
83 | execjs (2.7.0)
84 | ffi (1.9.18)
85 | formatador (0.2.5)
86 | globalid (0.4.1)
87 | activesupport (>= 4.2.0)
88 | guard (2.14.2)
89 | formatador (>= 0.2.4)
90 | listen (>= 2.7, < 4.0)
91 | lumberjack (>= 1.0.12, < 2.0)
92 | nenv (~> 0.1)
93 | notiffany (~> 0.0)
94 | pry (>= 0.9.12)
95 | shellany (~> 0.0)
96 | thor (>= 0.18.1)
97 | guard-compat (1.2.1)
98 | guard-rspec (4.7.3)
99 | guard (~> 2.1)
100 | guard-compat (~> 1.1)
101 | rspec (>= 2.99.0, < 4.0)
102 | i18n (0.9.3)
103 | concurrent-ruby (~> 1.0)
104 | jquery-rails (4.3.3)
105 | rails-dom-testing (>= 1, < 3)
106 | railties (>= 4.2.0)
107 | thor (>= 0.14, < 2.0)
108 | json (2.1.0)
109 | kaminari (1.1.1)
110 | activesupport (>= 4.1.0)
111 | kaminari-actionview (= 1.1.1)
112 | kaminari-activerecord (= 1.1.1)
113 | kaminari-core (= 1.1.1)
114 | kaminari-actionview (1.1.1)
115 | actionview
116 | kaminari-core (= 1.1.1)
117 | kaminari-activerecord (1.1.1)
118 | activerecord
119 | kaminari-core (= 1.1.1)
120 | kaminari-core (1.1.1)
121 | listen (3.1.5)
122 | rb-fsevent (~> 0.9, >= 0.9.4)
123 | rb-inotify (~> 0.9, >= 0.9.7)
124 | ruby_dep (~> 1.2)
125 | loofah (2.1.1)
126 | crass (~> 1.0.2)
127 | nokogiri (>= 1.5.9)
128 | lumberjack (1.0.12)
129 | mail (2.7.0)
130 | mini_mime (>= 0.1.1)
131 | method_source (0.9.0)
132 | mime-types (3.1)
133 | mime-types-data (~> 3.2015)
134 | mime-types-data (3.2016.0521)
135 | mini_mime (1.0.0)
136 | mini_portile2 (2.3.0)
137 | minitest (5.11.2)
138 | nenv (0.3.0)
139 | nio4r (2.3.1)
140 | nokogiri (1.8.1)
141 | mini_portile2 (~> 2.3.0)
142 | notiffany (0.1.1)
143 | nenv (~> 0.1)
144 | shellany (~> 0.0)
145 | pry (0.11.3)
146 | coderay (~> 1.1.0)
147 | method_source (~> 0.9.0)
148 | public_suffix (3.0.1)
149 | rack (2.0.3)
150 | rack-test (0.8.2)
151 | rack (>= 1.0, < 3)
152 | rails (5.1.4)
153 | actioncable (= 5.1.4)
154 | actionmailer (= 5.1.4)
155 | actionpack (= 5.1.4)
156 | actionview (= 5.1.4)
157 | activejob (= 5.1.4)
158 | activemodel (= 5.1.4)
159 | activerecord (= 5.1.4)
160 | activesupport (= 5.1.4)
161 | bundler (>= 1.3.0)
162 | railties (= 5.1.4)
163 | sprockets-rails (>= 2.0.0)
164 | rails-dom-testing (2.0.3)
165 | activesupport (>= 4.2.0)
166 | nokogiri (>= 1.6)
167 | rails-html-sanitizer (1.0.3)
168 | loofah (~> 2.0)
169 | railties (5.1.4)
170 | actionpack (= 5.1.4)
171 | activesupport (= 5.1.4)
172 | method_source
173 | rake (>= 0.8.7)
174 | thor (>= 0.18.1, < 2.0)
175 | rake (12.3.0)
176 | rb-fsevent (0.10.2)
177 | rb-inotify (0.9.10)
178 | ffi (>= 0.5.0, < 2)
179 | rspec (3.7.0)
180 | rspec-core (~> 3.7.0)
181 | rspec-expectations (~> 3.7.0)
182 | rspec-mocks (~> 3.7.0)
183 | rspec-core (3.7.1)
184 | rspec-support (~> 3.7.0)
185 | rspec-expectations (3.7.0)
186 | diff-lcs (>= 1.2.0, < 2.0)
187 | rspec-support (~> 3.7.0)
188 | rspec-mocks (3.7.0)
189 | diff-lcs (>= 1.2.0, < 2.0)
190 | rspec-support (~> 3.7.0)
191 | rspec-rails (3.7.2)
192 | actionpack (>= 3.0)
193 | activesupport (>= 3.0)
194 | railties (>= 3.0)
195 | rspec-core (~> 3.7.0)
196 | rspec-expectations (~> 3.7.0)
197 | rspec-mocks (~> 3.7.0)
198 | rspec-support (~> 3.7.0)
199 | rspec-support (3.7.0)
200 | ruby_dep (1.5.0)
201 | sass (3.5.5)
202 | sass-listen (~> 4.0.0)
203 | sass-listen (4.0.0)
204 | rb-fsevent (~> 0.9, >= 0.9.4)
205 | rb-inotify (~> 0.9, >= 0.9.7)
206 | shellany (0.0.1)
207 | sprockets (3.7.2)
208 | concurrent-ruby (~> 1.0)
209 | rack (> 1, < 3)
210 | sprockets-rails (3.2.1)
211 | actionpack (>= 4.0)
212 | activesupport (>= 4.0)
213 | sprockets (>= 3.0.0)
214 | sqlite3 (1.3.13)
215 | thor (0.20.0)
216 | thread_safe (0.3.6)
217 | tzinfo (1.2.4)
218 | thread_safe (~> 0.1)
219 | websocket-driver (0.6.5)
220 | websocket-extensions (>= 0.1.0)
221 | websocket-extensions (0.1.3)
222 | xpath (2.1.0)
223 | nokogiri (~> 1.3)
224 |
225 | PLATFORMS
226 | ruby
227 |
228 | DEPENDENCIES
229 | bootstrap-sass
230 | byebug
231 | capybara (< 2.14)
232 | capybara-webkit (~> 1.14)
233 | database_cleaner
234 | guard-rspec
235 | rspec-rails
236 | smart_listing!
237 | sqlite3
238 |
239 | BUNDLED WITH
240 | 1.16.2
241 |
--------------------------------------------------------------------------------
/Guardfile:
--------------------------------------------------------------------------------
1 | # A sample Guardfile
2 | # More info at https://github.com/guard/guard#readme
3 |
4 | ## Uncomment and set this to only include directories you want to watch
5 | # directories %w(app lib config test spec features) \
6 | # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7 |
8 | ## Note: if you are using the `directories` clause above and you are not
9 | ## watching the project directory ('.'), then you will want to move
10 | ## the Guardfile to a watched dir and symlink it back, e.g.
11 | #
12 | # $ mkdir config
13 | # $ mv Guardfile config/
14 | # $ ln -s config/Guardfile .
15 | #
16 | # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17 |
18 | # Note: The cmd option is now required due to the increasing number of ways
19 | # rspec may be run, below are examples of the most common uses.
20 | # * bundler: 'bundle exec rspec'
21 | # * bundler binstubs: 'bin/rspec'
22 | # * spring: 'bin/rspec' (This will use spring if running and you have
23 | # installed the spring binstubs per the docs)
24 | # * zeus: 'zeus rspec' (requires the server to be started separately)
25 | # * 'just' rspec: 'rspec'
26 |
27 | guard :rspec, cmd: "bundle exec rspec" do
28 | require "guard/rspec/dsl"
29 | dsl = Guard::RSpec::Dsl.new(self)
30 |
31 | # Feel free to open issues for suggestions and improvements
32 |
33 | # RSpec files
34 | rspec = dsl.rspec
35 | watch(rspec.spec_helper) { rspec.spec_dir }
36 | watch(rspec.spec_support) { rspec.spec_dir }
37 | watch(rspec.spec_files)
38 |
39 | # Ruby files
40 | ruby = dsl.ruby
41 | dsl.watch_spec_files_for(ruby.lib_files)
42 |
43 | # Rails files
44 | rails = dsl.rails(view_extensions: %w(erb haml slim))
45 | dsl.watch_spec_files_for(rails.app_files)
46 | dsl.watch_spec_files_for(rails.views)
47 |
48 | watch(rails.controllers) do |m|
49 | [
50 | rspec.spec.call("routing/#{m[1]}_routing"),
51 | rspec.spec.call("controllers/#{m[1]}_controller"),
52 | rspec.spec.call("acceptance/#{m[1]}")
53 | ]
54 | end
55 |
56 | # Rails config changes
57 | watch(rails.spec_helper) { rspec.spec_dir }
58 | watch(rails.routes) { "#{rspec.spec_dir}/routing" }
59 | watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
60 |
61 | # Capybara features specs
62 | watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
63 | watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
64 |
65 | # Turnip features and steps
66 | watch(%r{^spec/acceptance/(.+)\.feature$})
67 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
68 | Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Sology (www.sology.eu)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SmartListing
2 |
3 | SmartListing helps creating AJAX-enabled lists of ActiveRecord collections or arrays with pagination, filtering, sorting and in-place editing.
4 |
5 | [See it in action](http://showcase.sology.eu/smart_listing)
6 |
7 | ## Installation
8 |
9 | Add to your Gemfile:
10 |
11 | ```ruby
12 | gem "smart_listing"
13 | ```
14 |
15 | Then run:
16 |
17 | ```sh
18 | $ bundle install
19 | ```
20 |
21 | Also, you need to add SmartListing to your asset pipeline:
22 |
23 | ```
24 | //= require smart_listing
25 | ```
26 |
27 | __Rails >= 5.1 users__: Rails 5.1 has dropped jQuery dependency from the default stack in favour of `rails-ujs`. SmartListing still requires jQuery so make sure that you use `jquery_ujs` from `jquery-rails` gem and have following requires in your asset pipeline before `smart_listing`:
28 | ```
29 | //= require jquery
30 | //= require jquery_ujs
31 | ```
32 |
33 | ### Initializer
34 |
35 | Optionally you can also install some configuration initializer:
36 |
37 | ```sh
38 | $ rails generate smart_listing:install
39 | ```
40 |
41 | 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.
42 |
43 | ### Custom views
44 |
45 | SmartListing comes with some built-in views which are by default compatible with Bootstrap 3. You can easily change them after installing:
46 |
47 | ```sh
48 | $ rails generate smart_listing:views
49 | ```
50 |
51 | Files will be placed in `app/views/smart_listing`.
52 |
53 | ## Usage
54 |
55 | Let's start with a controller. In order to use SmartListing, in most cases you need to include controller extensions and SmartListing helper methods:
56 |
57 | ```ruby
58 | include SmartListing::Helper::ControllerExtensions
59 | helper SmartListing::Helper
60 | ```
61 |
62 | Next, put following code in controller action you desire:
63 |
64 | ```ruby
65 | @users = smart_listing_create(:users, User.active, partial: "users/listing")
66 | ```
67 |
68 | 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.
69 |
70 | In the main view (typically something like `index.html.erb` or `index.html.haml`), use this method to render listing:
71 |
72 | ```ruby
73 | smart_listing_render(:users)
74 | ```
75 |
76 | `smart_listing_render` does some magic and renders `users/listing` partial which may look like this (in HAML):
77 |
78 | ```haml
79 | - unless smart_listing.empty?
80 | %table
81 | %thead
82 | %tr
83 | %th User name
84 | %th Email
85 | %tbody
86 | - smart_listing.collection.each do |user|
87 | %tr
88 | %td= user.name
89 | %td= user.email
90 |
91 | = smart_listing.paginate
92 | - else
93 | %p.warning No records!
94 | ```
95 |
96 | 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:
97 |
98 | * `Builder#paginate` - renders Kaminari pagination,
99 | * `Builder#pagination_per_page_links` - display some link that allow you to customize Kaminari's `per_page`,
100 | * `Builder#collection` - accesses underlying list of items,
101 | * `Builder#empty?` - checks if collection is empty,
102 | * `Builder#count` - returns collection count,
103 | * `Builder#render` - basic template's `render` wrapper that automatically adds `smart_listing` local variable,
104 |
105 | There are also other methods that will be described in detail below.
106 |
107 | 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`):
108 |
109 | ```erb
110 | <%= smart_listing_update(:users) %>
111 | ```
112 |
113 | ### Sorting
114 |
115 | 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:
116 |
117 | ```haml
118 | - unless smart_listing.empty?
119 | %table
120 | %thead
121 | %tr
122 | %th= smart_listing.sortable "User name", :name
123 | %th= smart_listing.sortable "Email", :email
124 | %tbody
125 | - smart_listing.collection.each do |user|
126 | %tr
127 | %td= user.name
128 | %td= user.email
129 |
130 | = smart_listing.paginate
131 | - else
132 | %p.warning No records!
133 | ```
134 |
135 | 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).
136 |
137 | You can also specify default sort order in the controller:
138 |
139 | ```ruby
140 | @users = smart_listing_create(:users, User.active, partial: "users/listing", default_sort: {name: "asc"})
141 | ```
142 |
143 | 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:
144 | ```ruby
145 | @users = smart_listing_create :users, User.active.joins(:stats), partial: "users/listing",
146 | sort_attributes: [[:last_signin, "stats.last_signin_at"]],
147 | default_sort: {last_signin: "desc"}
148 | ```
149 |
150 | Note that `:sort_attributes` are array which of course means, that order of attributes matters.
151 |
152 | There's also a possibility to specify available sort directions using `:sort_dirs` option which is by default `[nil, "asc", "desc"]`.
153 |
154 | ### List item management and in-place editing
155 |
156 | 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:
157 |
158 | ```haml
159 | - unless smart_listing.empty?
160 | %table
161 | %thead
162 | %tr
163 | %th= smart_listing.sortable "User name", "name"
164 | %th= smart_listing.sortable "Email", "email"
165 | %th
166 | %tbody
167 | - smart_listing.collection.each do |user|
168 | %tr.editable{data: {id: user.id}}
169 | = smart_listing.render partial: 'users/user', locals: {user: user}
170 | = smart_listing.item_new colspan: 3, link: new_user_path
171 |
172 | = smart_listing.paginate
173 | - else
174 | %p.warning No records!
175 | ```
176 |
177 | `
` 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:
178 |
179 | ```ruby
180 | <%= smart_listing_item :users, :new, @new_user, "users/form" %>
181 | ```
182 |
183 | Note that `new` action does not need to create SmartListing (via `smart_listing_create`). It just initializes `@new_user` and renders JS view.
184 |
185 | New partial for user (`users/user`) may look like this:
186 | ```haml
187 | %td= user.name
188 | %td= user.email
189 | %td.actions= smart_listing_item_actions [{name: :show, url: user_path(user)}, {name: :edit, url: edit_user_path(user)}, {name: :destroy, url: user_path(user)}]
190 | ```
191 |
192 | `smart_listing_item_actions` renders here links that allow to edit and destroy user item. `:show`, `:edit` and `:destroy` are built-in actions, you can also define your `:custom` actions. Again. `
13 | <% end %>
14 |
--------------------------------------------------------------------------------
/app/views/kaminari/smart_listing/_gap.html.erb:
--------------------------------------------------------------------------------
1 | <%# Non-link tag that stands for skipped pages...
2 | - available local variables
3 | current_page: a page object for the currently displayed page
4 | total_pages: total number of pages
5 | per_page: number of items to fetch per page
6 | remote: data-remote
7 | -%>
8 |
9 |
--------------------------------------------------------------------------------
/app/views/kaminari/smart_listing/_last_page.html.erb:
--------------------------------------------------------------------------------
1 | <%# Link to the "Last" page
2 | - available local variables
3 | url: url to the last page
4 | current_page: a page object for the currently displayed page
5 | total_pages: total number of pages
6 | per_page: number of items to fetch per page
7 | remote: data-remote
8 | -%>
9 | <% unless current_page.last? %>
10 |
<%# "next" class present for border styling in twitter bootstrap %>
11 | <%= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {:remote => remote} %>
12 |
13 | <% end %>
14 |
--------------------------------------------------------------------------------
/app/views/kaminari/smart_listing/_next_page.html.erb:
--------------------------------------------------------------------------------
1 | <%# Link to the "Next" page
2 | - available local variables
3 | url: url to the next page
4 | current_page: a page object for the currently displayed page
5 | total_pages: total number of pages
6 | per_page: number of items to fetch per page
7 | remote: data-remote
8 | -%>
9 | <% unless current_page.last? %>
10 |
13 | <% end %>
14 |
--------------------------------------------------------------------------------
/app/views/kaminari/smart_listing/_page.html.erb:
--------------------------------------------------------------------------------
1 | <%# Link showing page number
2 | - available local variables
3 | page: a page object for "this" page
4 | url: url to this page
5 | current_page: a page object for the currently displayed page
6 | total_pages: total number of pages
7 | per_page: number of items to fetch per page
8 | remote: data-remote
9 | -%>
10 |
13 |
--------------------------------------------------------------------------------
/app/views/kaminari/smart_listing/_paginator.html.erb:
--------------------------------------------------------------------------------
1 | <%# The container tag
2 | - available local variables
3 | current_page: a page object for the currently displayed page
4 | total_pages: total number of pages
5 | per_page: number of items to fetch per page
6 | remote: data-remote
7 | paginator: the paginator that renders the pagination tags inside
8 | -%>
9 | <%= paginator.render do -%>
10 |
25 | <% end -%>
26 |
--------------------------------------------------------------------------------
/app/views/kaminari/smart_listing/_prev_page.html.erb:
--------------------------------------------------------------------------------
1 | <%# Link to the "Previous" page
2 | - available local variables
3 | url: url to the previous page
4 | current_page: a page object for the currently displayed page
5 | total_pages: total number of pages
6 | per_page: number of items to fetch per page
7 | remote: data-remote
8 | -%>
9 | <% unless current_page.first? %>
10 |
7 |
10 | <% end %>
11 | <%= smart_listing_render %>
12 |
13 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/users/searchable.js.erb:
--------------------------------------------------------------------------------
1 | <%= smart_listing_update %>
2 |
3 |
4 |
--------------------------------------------------------------------------------
/spec/dummy/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../../config/application', __FILE__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | # Pick the frameworks you want:
4 | require "active_record/railtie"
5 | require "action_controller/railtie"
6 | require "action_mailer/railtie"
7 | require "action_view/railtie"
8 | require "sprockets/railtie"
9 | # require "rails/test_unit/railtie"
10 |
11 | Bundler.require(*Rails.groups)
12 | require "smart_listing"
13 |
14 | module Dummy
15 | class Application < Rails::Application
16 | # Settings in config/environments/* take precedence over those specified here.
17 | # Application configuration should go into files in config/initializers
18 | # -- all .rb files in that directory are automatically loaded.
19 |
20 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
21 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
22 | # config.time_zone = 'Central Time (US & Canada)'
23 |
24 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
25 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
26 | # config.i18n.default_locale = :de
27 | end
28 | end
29 |
30 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
3 |
4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: 5
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | require "jquery-rails"
5 | require 'coffee-rails'
6 | require 'bootstrap-sass'
7 |
8 | # Initialize the Rails application.
9 | Rails.application.initialize!
10 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports and disable caching.
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send.
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger.
20 | config.active_support.deprecation = :log
21 |
22 | # Raise an error on page load if there are pending migrations.
23 | config.active_record.migration_error = :page_load
24 |
25 | # Debug mode disables concatenation and preprocessing of assets.
26 | # This option may cause significant delays in view rendering with a large
27 | # number of complex assets.
28 | config.assets.debug = true
29 |
30 | # Adds additional error checking when serving assets at runtime.
31 | # Checks for improperly declared sprockets dependencies.
32 | # Raises helpful error messages.
33 | config.assets.raise_runtime_errors = true
34 |
35 | # Raises error for missing translations
36 | # config.action_view.raise_on_missing_translations = true
37 | end
38 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = false
15 | config.action_controller.perform_caching = true
16 |
17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application
18 | # Add `rack-cache` to your Gemfile before enabling this.
19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
20 | # config.action_dispatch.rack_cache = true
21 |
22 | # Disable Rails's static asset server (Apache or nginx will already do this).
23 | config.serve_static_assets = false
24 |
25 | # Compress JavaScripts and CSS.
26 | config.assets.js_compressor = :uglifier
27 | # config.assets.css_compressor = :sass
28 |
29 | # Do not fallback to assets pipeline if a precompiled asset is missed.
30 | config.assets.compile = false
31 |
32 | # Generate digests for assets URLs.
33 | config.assets.digest = true
34 |
35 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
36 |
37 | # Specifies the header that your server uses for sending files.
38 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
40 |
41 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
42 | # config.force_ssl = true
43 |
44 | # Set to :debug to see everything in the log.
45 | config.log_level = :info
46 |
47 | # Prepend all log lines with the following tags.
48 | # config.log_tags = [ :subdomain, :uuid ]
49 |
50 | # Use a different logger for distributed setups.
51 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
52 |
53 | # Use a different cache store in production.
54 | # config.cache_store = :mem_cache_store
55 |
56 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
57 | # config.action_controller.asset_host = "http://assets.example.com"
58 |
59 | # Ignore bad email addresses and do not raise email delivery errors.
60 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
61 | # config.action_mailer.raise_delivery_errors = false
62 |
63 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
64 | # the I18n.default_locale when a translation cannot be found).
65 | config.i18n.fallbacks = true
66 |
67 | # Send deprecation notices to registered listeners.
68 | config.active_support.deprecation = :notify
69 |
70 | # Disable automatic flushing of the log to improve performance.
71 | # config.autoflush_log = false
72 |
73 | # Use default logging formatter so that PID and timestamp are not suppressed.
74 | config.log_formatter = ::Logger::Formatter.new
75 |
76 | # Do not dump schema after migrations.
77 | config.active_record.dump_schema_after_migration = false
78 | end
79 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = false
9 |
10 | # Do not eager load code on boot. This avoids loading your whole application
11 | # just for the purpose of running a single test. If you are using a tool that
12 | # preloads Rails for running tests, you may have to set it to true.
13 | config.eager_load = false
14 |
15 | # Configure static asset server for tests with Cache-Control for performance.
16 | config.serve_static_assets = true
17 | config.static_cache_control = 'public, max-age=3600'
18 |
19 | # Show full error reports and disable caching.
20 | config.consider_all_requests_local = true
21 | config.action_controller.perform_caching = false
22 |
23 | # Raise exceptions instead of rendering exception templates.
24 | config.action_dispatch.show_exceptions = false
25 |
26 | # Disable request forgery protection in test environment.
27 | config.action_controller.allow_forgery_protection = false
28 |
29 | # Tell Action Mailer not to deliver emails to the real world.
30 | # The :test delivery method accumulates sent emails in the
31 | # ActionMailer::Base.deliveries array.
32 | config.action_mailer.delivery_method = :test
33 |
34 | # Print deprecation notices to the stderr.
35 | config.active_support.deprecation = :stderr
36 |
37 | # Raises error for missing translations
38 | # config.action_view.raise_on_missing_translations = true
39 | end
40 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Precompile additional assets.
7 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
8 | # Rails.application.config.assets.precompile += %w( search.js )
9 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.action_dispatch.cookies_serializer = :json
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_dummy_session'
4 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/smart_listing.rb:
--------------------------------------------------------------------------------
1 | SmartListing.configure do |config|
2 | config.global_options({
3 | #:param_names => { # param names
4 | #:page => :page,
5 | #:per_page => :per_page,
6 | #:sort => :sort,
7 | #},
8 | #:array => false, # controls whether smart list should be using arrays or AR collections
9 | #:max_count => nil, # limit number of rows
10 | #:unlimited_per_page => false, # allow infinite page size
11 | #:paginate => true, # allow pagination
12 | #:memorize_per_page => false, # save per page settings in the cookie
13 | :page_sizes => [3, 10], # set available page sizes array
14 | #:kaminari_options => {:theme => "smart_listing"}, # Kaminari's paginate helper options
15 | #:sort_dirs => [nil, "asc", "desc"], # Default sorting directions cycle of sortables
16 | })
17 |
18 | config.constants :classes, {
19 | #:main => "smart-listing",
20 | #:editable => "editable",
21 | #:content => "content",
22 | #:loading => "loading",
23 | #:status => "smart-listing-status",
24 | #:item_actions => "actions",
25 | #:new_item_placeholder => "new-item-placeholder",
26 | #:new_item_action => "new-item-action",
27 | #:new_item_button => "btn",
28 | #:hidden => "hidden",
29 | #:autoselect => "autoselect",
30 | #:callback => "callback",
31 | #:pagination_per_page => "pagination-per-page text-center",
32 | #:inline_editing => "info",
33 | #:no_records => "no-records",
34 | #:limit => "smart-listing-limit",
35 | #:limit_alert => "smart-listing-limit-alert",
36 | #:controls => "smart-listing-controls",
37 | #:controls_reset => "reset",
38 | #:filtering => "filter",
39 | #:filtering_search => "glyphicon-search",
40 | #:filtering_cancel => "glyphicon-remove",
41 | #:filtering_disabled => "disabled",
42 | #:sortable => "sortable",
43 | #:icon_new => "glyphicon glyphicon-plus",
44 | #:icon_edit => "glyphicon glyphicon-pencil",
45 | #:icon_trash => "glyphicon glyphicon-trash",
46 | #:icon_inactive => "glyphicon glyphicon-circle",
47 | #:icon_show => "glyphicon glyphicon-share-alt",
48 | #:icon_sort_none => "glyphicon glyphicon-resize-vertical",
49 | #:icon_sort_up => "glyphicon glyphicon-chevron-up",
50 | #:icon_sort_down => "glyphicon glyphicon-chevron-down",
51 | #:muted => "text-muted",
52 | }
53 |
54 | config.constants :data_attributes, {
55 | #:main => "smart-listing",
56 | #:controls_initialized => "smart-listing-controls-initialized",
57 | #:confirmation => "confirmation",
58 | #:id => "id",
59 | #:href => "href",
60 | #:callback_href => "callback-href",
61 | #:max_count => "max-count",
62 | #:item_count => "item-count",
63 | #:inline_edit_backup => "smart-listing-edit-backup",
64 | #:params => "params",
65 | #:observed => "observed",
66 | #:href => "href",
67 | #:autoshow => "autoshow",
68 | #:popover => "slpopover",
69 | }
70 |
71 | config.constants :selectors, {
72 | #:item_action_destroy => "a.destroy",
73 | #:edit_cancel => "button.cancel",
74 | #:row => "tr",
75 | #:head => "thead",
76 | #:filtering_icon => "i"
77 | #:filtering_button => "button",
78 | #:filtering_icon => "button span",
79 | #:filtering_input => ".filter input",
80 | #:pagination_count => ".pagination-per-page .count",
81 | }
82 | end
83 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | resources :users do
3 | collection do
4 | get 'sortable'
5 | get 'searchable'
6 | end
7 | end
8 |
9 | namespace :admin do
10 | resources :users do
11 | member do
12 | put 'change_name'
13 | end
14 | end
15 | end
16 | # The priority is based upon order of creation: first created -> highest priority.
17 | # See how all your routes lay out with "rake routes".
18 |
19 | # You can have the root of your site routed with "root"
20 | root 'users#index'
21 |
22 | # Example of regular route:
23 | # get 'products/:id' => 'catalog#view'
24 |
25 | # Example of named route that can be invoked with purchase_url(id: product.id)
26 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
27 |
28 | # Example resource route (maps HTTP verbs to controller actions automatically):
29 | # resources :products
30 |
31 | # Example resource route with options:
32 | # resources :products do
33 | # member do
34 | # get 'short'
35 | # post 'toggle'
36 | # end
37 | #
38 | # collection do
39 | # get 'sold'
40 | # end
41 | # end
42 |
43 | # Example resource route with sub-resources:
44 | # resources :products do
45 | # resources :comments, :sales
46 | # resource :seller
47 | # end
48 |
49 | # Example resource route with more complex sub-resources:
50 | # resources :products do
51 | # resources :comments
52 | # resources :sales do
53 | # get 'recent', on: :collection
54 | # end
55 | # end
56 |
57 | # Example resource route with concerns:
58 | # concern :toggleable do
59 | # post 'toggle'
60 | # end
61 | # resources :posts, concerns: :toggleable
62 | # resources :photos, concerns: :toggleable
63 |
64 | # Example resource route within a namespace:
65 | # namespace :admin do
66 | # # Directs /admin/products/* to Admin::ProductsController
67 | # # (app/controllers/admin/products_controller.rb)
68 | # resources :products
69 | # end
70 | end
71 |
--------------------------------------------------------------------------------
/spec/dummy/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: cb71bf666c97336b3d87e2aaf8b4fc962264491f7eadaf838f8ef290d115631b7a640c9a60704e9c3a110692d06cd105e87c4cd5591d659c3ce44c7b5128ec18
15 |
16 | test:
17 | secret_key_base: f3ab27e4cd06e477d716bce701634636d2db5b835592dde50e7e64fa59adef9db7fce71c7b7bfe581089280a6f0e39e67a0efeae559a3f89e7138a44b9fda4a1
18 |
19 | # Do not keep production secrets in the repository,
20 | # instead read values from the environment.
21 | production:
22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
23 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20180126065408_create_user.rb:
--------------------------------------------------------------------------------
1 | class CreateUser < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table :users do |t|
4 | t.string :name
5 | t.string :email
6 | t.boolean :boolean
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/dummy/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 20180126065408) do
14 |
15 | create_table "users", force: :cascade do |t|
16 | t.string "name"
17 | t.string "email"
18 | t.boolean "boolean"
19 | end
20 |
21 | end
22 |
--------------------------------------------------------------------------------
/spec/dummy/db/seeds.rb:
--------------------------------------------------------------------------------
1 | User.find_or_create_by(id: 1, name: "Betty", email: "betty@email.com", boolean: false)
2 | User.find_or_create_by(id: 2, name: "Aaron", email: "aaron@email.com", boolean: true)
3 | User.find_or_create_by(id: 3, name: "Jane", email: "jane@test.eu", boolean: false)
4 | User.find_or_create_by(id: 4, name: "Edward", email: "edward@test.eu", boolean: true)
5 | User.find_or_create_by(id: 5, name: "Nicholas", email: "salohcin@email.com", boolean: false)
6 | User.find_or_create_by(id: 6, name: "Lisa", email: "asil@email.com", boolean: true)
7 | User.find_or_create_by(id: 7, name: "Sara", email: "aras@test.eu", boolean: false)
8 | User.find_or_create_by(id: 8, name: "Robin", email: "nibor@test.eu", boolean: true)
9 |
--------------------------------------------------------------------------------
/spec/dummy/fixtures/users.yml:
--------------------------------------------------------------------------------
1 | user1:
2 | id: 1
3 | name: "Betty"
4 | email: "betty@email.com"
5 | boolean: false
6 | user2:
7 | id: 2
8 | name: "Aaron"
9 | email: "aaron@email.com"
10 | boolean: true
11 | user3:
12 | id: 3
13 | name: "Jane"
14 | email: "jane@test.eu"
15 | boolean: false
16 | user4:
17 | id: 4
18 | name: "Edward"
19 | email: "edward@test.eu"
20 | boolean: true
21 | user5:
22 | id: 5
23 | name: "Nicholas"
24 | email: "salohcin@email.com"
25 | boolean: false
26 | user6:
27 | id: 6
28 | name: "Lisa"
29 | email: "asil@email.com"
30 | boolean: true
31 | user7:
32 | id: 7
33 | name: "Sara"
34 | email: "aras@test.eu"
35 | boolean: false
36 | user8:
37 | id: 8
38 | name: "Robin"
39 | email: "nibor@test.eu"
40 | boolean: true
41 |
--------------------------------------------------------------------------------
/spec/dummy/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sology/smart_listing/08ea04a6c50485c81bd0a0b64f07ec8a5ea2539e/spec/dummy/lib/assets/.keep
--------------------------------------------------------------------------------
/spec/dummy/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sology/smart_listing/08ea04a6c50485c81bd0a0b64f07ec8a5ea2539e/spec/dummy/log/.keep
--------------------------------------------------------------------------------
/spec/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/spec/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sology/smart_listing/08ea04a6c50485c81bd0a0b64f07ec8a5ea2539e/spec/dummy/public/favicon.ico
--------------------------------------------------------------------------------
/spec/features/custom_filters_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | feature 'Combine custom filtering' do
4 | fixtures :users
5 |
6 | scenario 'The user search user, change pagination and change page', js: true do
7 |
8 | visit admin_users_path
9 | #page_sizes => [3, 10]
10 | within(".pagination-per-page") { click_on "10" }
11 | expect(page).to have_selector('tr.editable', count: 8)
12 | fill_in "filter", with: "test"
13 | expect(page).to have_selector('tr.editable', count: 4)
14 | within(".pagination-per-page") { click_on "3" }
15 | within(".pagination") { click_on "2" }
16 | expect(page).to have_selector('tr.editable', count: 1)
17 |
18 | end
19 |
20 | scenario 'The user sort users and change page', js: true do
21 |
22 | visit admin_users_path
23 | find('.name a.sortable').click
24 | expect(page).to have_content("Aaron")
25 | expect(page).to_not have_content("Jane")
26 | within(".pagination") { click_on "2" }
27 | expect(page).to have_content("Jane")
28 | expect(page).to_not have_content("Aaron")
29 |
30 | end
31 |
32 | scenario 'The user combine filters', js: true do
33 |
34 | visit admin_users_path
35 | fill_in "filter", with: "email"
36 | find('input#boolean').click
37 | expect(page).to have_selector('tr.editable', count: 2)
38 |
39 | end
40 |
41 | scenario 'The user combine filters and sort users', js: true do
42 |
43 | visit admin_users_path
44 | fill_in "filter", with: "test"
45 | find('input#boolean').click
46 | wait_for_ajax
47 | expect(page).to have_selector('tr.editable', count: 2)
48 | click_link 'Name'
49 | expect(page).to have_selector('tr.editable', count: 2)
50 | expect(page.find(:css, "tbody > tr:nth-child(1)")).to have_content("Edward")
51 | expect(page.find(:css, "tbody > tr:nth-child(2)")).to have_content("Robin")
52 |
53 | end
54 |
55 | scenario 'The user combine filters, sort and change page', js: true do
56 |
57 | visit admin_users_path
58 | check 'boolean'
59 | wait_for_ajax
60 | expect(find(:css, '.email a.sortable')[:href]).to include("boolean")
61 | click_link 'Email'
62 | expect(page.find(:css, "tbody > tr:nth-child(2)")).to have_content("Lisa")
63 | within(".pagination") { click_on "2" }
64 | expect(page.find(:css, "tbody > tr:nth-child(1)")).to have_content("Robin")
65 | expect(page.find(:css, '.count')).to have_content("4")
66 |
67 | end
68 |
69 | end
70 |
--------------------------------------------------------------------------------
/spec/features/manage_items_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | feature "Manage items" do
4 | scenario "Add a new item", js: true do
5 | visit admin_users_path
6 |
7 | click_on "New item"
8 | fill_in "Name", with: "Test name"
9 | fill_in "Email", with: "Test email"
10 | click_on "Save"
11 |
12 | expect(page).to have_content("Test name")
13 | end
14 |
15 | scenario "Edit an item", js: true do
16 | User.create(name: "Name 1", email: "Email 1")
17 | visit admin_users_path
18 |
19 | find('.edit').click
20 | fill_in "Name", with: "Name 2"
21 | fill_in "Email", with: "Email 2"
22 | click_on "Save"
23 |
24 | expect(page).to have_content("Name 2")
25 | expect(page).to_not have_content("Name 1")
26 | end
27 |
28 | scenario "Delete an item", js: true do
29 | User.create(name: "Name 1", email: "Email 1")
30 |
31 | visit admin_users_path
32 | find('.destroy').click
33 | within('.confirmation_box') { click_on "Yes" }
34 |
35 | expect(page).to_not have_content("Name 1")
36 | end
37 |
38 | scenario "Use a custom action", js: true do
39 | User.create(name: "Name 1", email: "Email 1")
40 |
41 | visit admin_users_path
42 | find('.change_name').click
43 |
44 | expect(page).to have_content("Changed Name")
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/spec/features/view_items_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | feature 'View a list of items' do
4 | fixtures :users
5 | scenario 'The user navigate through users', js: true do
6 |
7 | visit root_path
8 | #page_sizes => [3, 10]
9 | expect(page).to have_content("Betty")
10 | expect(page).to_not have_content("Edward")
11 |
12 | within(".pagination") { click_on "2" }
13 |
14 | expect(page).to have_content("Edward")
15 | expect(page).to_not have_content("Betty")
16 | end
17 |
18 | scenario "The user sort users", js: true do
19 |
20 | visit sortable_users_path
21 |
22 | find('.name a').click
23 | expect(find(:xpath, "//table/tbody/tr[1]")).to have_content("Aaron")
24 | expect(find(:xpath, "//table/tbody/tr[2]")).to have_content("Betty")
25 |
26 | find('.name a').click
27 | expect(find(:xpath, "//table/tbody/tr[1]")).to have_content("Sara")
28 | expect(find(:xpath, "//table/tbody/tr[2]")).to have_content("Robin")
29 | end
30 |
31 | scenario "The user search user", js: true do
32 | visit admin_users_path
33 |
34 | fill_in "filter", with: "ja"
35 |
36 | expect(page).to have_content("Jane")
37 | expect(page).to_not have_content("Aaron")
38 |
39 | fill_in "filter", with: "ni"
40 |
41 | expect(page).to_not have_content("Nicholas")
42 | expect(page).to_not have_content("Jane")
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/spec/helpers/smart_listing/helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 | require 'smart_listing/helper'
3 |
4 | module SmartListing::Helper
5 | class UsersController < ApplicationController
6 | include ControllerExtensions
7 | helper SmartListing::Helper
8 |
9 | attr_accessor :smart_listings
10 |
11 | def params
12 | { value: 'params' }
13 | end
14 |
15 | def cookies
16 | { value: 'cookies' }
17 | end
18 |
19 | def smart_listing_collection
20 | [1, 2]
21 | end
22 | end
23 |
24 | describe ControllerExtensions do
25 | describe "#smart_listing_create" do
26 | it "create a list with params and cookies" do
27 | controller = UsersController.new
28 | list = build_list
29 |
30 | expect(list).to receive(:setup).with(controller.params,
31 | controller.cookies)
32 |
33 | controller.smart_listing_create
34 | end
35 |
36 | it "assign a list in smart listings with the name" do
37 | controller = UsersController.new
38 | list = build_list
39 |
40 | controller.smart_listing_create
41 |
42 | expect(controller.smart_listings[:users]).to eq list
43 | end
44 |
45 | it 'return the collection of the list' do
46 | controller = UsersController.new
47 | collection1 = double
48 | collection2 = double
49 | build_list(collection: collection1)
50 |
51 | controller.smart_listing_create(collection: collection2)
52 |
53 | actual = controller.smart_listings[:users].collection
54 | expect(actual).to eq collection1
55 | end
56 |
57 | def build_list(collection: {})
58 | double(collection: collection, setup: nil).tap do |list|
59 | allow(SmartListing::Base).to receive(:new).and_return(list)
60 | end
61 | end
62 | end
63 |
64 | describe '#smart_listing' do
65 | it 'give the list with name' do
66 | controller = UsersController.new
67 | list = double
68 | controller.smart_listings = { test: list }
69 | expect(controller.smart_listing(:test)).to eq list
70 | end
71 | end
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/spec/lib/smart_listing_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | module SmartListing
4 | describe Base do
5 | describe '#per_page' do
6 |
7 | context "when there is no specification in params or cookies" do
8 | it 'take first value in the page sizes' do
9 | options = { page_sizes: [1] }
10 | list = build_list(options: options)
11 |
12 | list.setup({}, {})
13 |
14 | expect(list.per_page).to eq 1
15 | end
16 | end
17 |
18 | context 'when a value is in params' do
19 | context 'when the value is in the list of page_sizes' do
20 | it 'set the per_page as in the value' do
21 | options = { page_sizes: [1, 2] }
22 | list = build_list(options: options)
23 |
24 | list.setup({ "users_smart_listing" => { per_page: "2" } }, {})
25 |
26 | expect(list.per_page).to eq 2
27 | end
28 | end
29 |
30 | context 'when the value is not in the list of page_sizes' do
31 | it 'take first value in the page sizes' do
32 | options = { page_sizes: [1, 2] }
33 | list = build_list(options: options)
34 |
35 | list.setup({ "users_smart_listing" => { per_page: "3" } }, {})
36 |
37 | expect(list.per_page).to eq 1
38 | end
39 | end
40 | end
41 |
42 | context 'when a value is in cookies' do
43 | context 'when the memorization is enabled' do
44 | it 'set the value in the cookies' do
45 | options = { page_sizes: [1, 2], memorize_per_page: true }
46 | list = build_list(options: options)
47 |
48 | list.setup({}, { "users_smart_listing" => { per_page: "2" } })
49 |
50 | expect(list.per_page).to eq 2
51 | end
52 | end
53 |
54 | context 'when the memorization is disabled' do
55 | it 'take first value in the page sizes' do
56 | options = { page_sizes: [1, 2], memorize_per_page: false }
57 | list = build_list(options: options)
58 |
59 | list.setup({}, { "users_smart_listing" => { per_page: "2" } })
60 |
61 | expect(list.per_page).to eq 1
62 | end
63 | end
64 | end
65 |
66 | context 'when the per page value is at 0' do
67 | context 'when the unlimited per page option is enabled' do
68 | it 'set the per page at 0' do
69 | options = { page_sizes: [1, 2], unlimited_per_page: true }
70 | list = build_list(options: options)
71 |
72 | list.setup({ "users_smart_listing" => { per_page: "0" } }, {})
73 |
74 | expect(list.per_page).to eq 0
75 | end
76 | end
77 |
78 | context 'when the unlimited per page option is disabled' do
79 | it 'take first value in the page sizes' do
80 | options = { page_sizes: [1, 2], unlimited_per_page: false }
81 | list = build_list(options: options)
82 |
83 | list.setup({}, {})
84 |
85 | expect(list.per_page).to eq 1
86 | end
87 | end
88 | end
89 |
90 | context 'when the memorization of per page is enabled' do
91 | it 'save the perpage in the cookies' do
92 | options = { page_sizes: [1], memorize_per_page: true }
93 | list = build_list(options: options)
94 | cookies = {}
95 |
96 | list.setup({}, cookies)
97 |
98 | expect(cookies["users_smart_listing"][:per_page]).to eq 1
99 | end
100 | end
101 | end
102 |
103 | describe '#sort' do
104 | context 'with :implicit attributes' do
105 | context 'when there is a value in params' do
106 | it 'set sort with the given value' do
107 | list = build_list
108 | params = { "users_smart_listing" => { sort: { "name" => "asc" } } }
109 |
110 | list.setup(params, {})
111 |
112 | expect(list.sort).to eq 'name' => 'asc'
113 | expect(list.collection.order_values).to match_array(['name asc'])
114 | end
115 |
116 | it 'set sort with the given value without direction' do
117 | list = build_list
118 | params = { 'users_smart_listing' => { sort: { 'name' => '' } } }
119 |
120 | list.setup(params, {})
121 |
122 | expect(list.sort).to eq 'name' => ''
123 | expect(list.collection.order_values).to match_array(['name '])
124 | end
125 |
126 | it 'does not set sort with the unknown given value' do
127 | list = build_list
128 | params = { 'users_smart_listing' => { sort: { 'login' => '' } } }
129 |
130 | list.setup(params, {})
131 |
132 | expect(list.sort).to eq({})
133 | expect(list.collection.order_values).to match_array([])
134 | end
135 |
136 | it 'does not set sort with the given value with unknown direction' do
137 | list = build_list
138 | params = { 'users_smart_listing' => { sort: { 'name' => 'dasc' } } }
139 |
140 | list.setup(params, {})
141 |
142 | expect(list.sort).to eq({})
143 | expect(list.collection.order_values).to match_array([])
144 | end
145 | end
146 |
147 | context 'when there is no value in params' do
148 | it 'take the value in options' do
149 | options = { default_sort: { 'email' => 'asc' } }
150 | list = build_list(options: options)
151 |
152 | list.setup({}, {})
153 |
154 | expect(list.sort).to eq 'email' => 'asc'
155 | expect(list.collection.order_values).to match_array(['email asc'])
156 | end
157 | end
158 | end
159 |
160 | context 'with sort_attributes' do
161 | context 'when there is a value in params' do
162 | it 'set sort with the given value' do
163 | options = { sort_attributes: [[:username, 'users.name']] }
164 | list = build_list(options: options)
165 | params = { 'users_smart_listing' => { sort: { 'username' => 'asc' } } }
166 |
167 | list.setup(params, {})
168 |
169 | expect(list.sort).to eq username: 'asc'
170 | expect(list.collection.order_values).to match_array(['users.name asc'])
171 | end
172 |
173 | it 'set sort with the given value without direction' do
174 | options = { sort_attributes: [[:username, 'users.name']] }
175 | list = build_list(options: options)
176 | params = { 'users_smart_listing' => { sort: { 'username' => '' } } }
177 |
178 | list.setup(params, {})
179 |
180 | expect(list.sort).to eq username: ''
181 | expect(list.collection.order_values).to match_array(['users.name '])
182 | end
183 |
184 | it 'does not set sort with the unknown given value' do
185 | options = { sort_attributes: [[:username, 'users.name']] }
186 | list = build_list(options: options)
187 | params = { 'users_smart_listing' => { sort: { 'login' => 'asc' } } }
188 |
189 | list.setup(params, {})
190 |
191 | expect(list.sort).to eq({})
192 | expect(list.collection.order_values).to match_array([])
193 | end
194 |
195 | it 'does not set sort with the given value with unknown direction' do
196 | options = { sort_attributes: [[:username, 'users.name']] }
197 | list = build_list(options: options)
198 | params = { 'users_smart_listing' => { sort: { 'username' => 'dasc' } } }
199 |
200 | list.setup(params, {})
201 |
202 | expect(list.sort).to eq({})
203 | expect(list.collection.order_values).to match_array([])
204 | end
205 | end
206 |
207 | context 'when there is no value in params' do
208 | it 'take the value in options' do
209 | options = { default_sort: { username: 'desc' }, sort_attributes: [[:username, 'users.name']] }
210 | list = build_list(options: options)
211 |
212 | list.setup({}, {})
213 |
214 | expect(list.sort).to eq username: 'desc'
215 | expect(list.collection.order_values).to match_array(['users.name desc'])
216 | end
217 | end
218 | end
219 | end
220 |
221 | describe '#page' do
222 | context 'when the page is in the range' do
223 | it 'set the value with the given params' do
224 | User.create
225 | User.create
226 | options = { page_sizes: [1] }
227 | list = build_list(options: options)
228 |
229 | list.setup({ "users_smart_listing" => { page: 2 } }, {})
230 |
231 | expect(list.page).to eq 2
232 | end
233 | end
234 |
235 | context 'when the page is out of range' do
236 | it 'set the value to the last page' do
237 | User.create
238 | User.create
239 | options = { page_sizes: [1] }
240 | list = build_list(options: options)
241 |
242 | list.setup({ "users_smart_listing" => { page: 3 } }, {})
243 |
244 | expect(list.page).to eq 2
245 | end
246 | end
247 | end
248 |
249 | describe '#collection' do
250 | context 'when the collection is an array' do
251 | it 'sort the collection by the first attribute' do
252 | user1 = User.create(name: '1')
253 | user2 = User.create(name: '2')
254 | options = { array: true }
255 | list = build_list(options: options)
256 |
257 | params = { "users_smart_listing" => { sort: { "name" => "desc" } } }
258 | list.setup(params, {})
259 |
260 | expect(list.collection.first).to eq user2
261 | expect(list.collection.last).to eq user1
262 | end
263 |
264 | it 'give only the given number per page' do
265 | user1 = User.create(name: '1')
266 | user2 = User.create(name: '2')
267 | options = { page_sizes: [1], array: true }
268 | list = build_list(options: options)
269 |
270 | list.setup({},{})
271 |
272 | expect(list.collection).to include user1
273 | expect(list.collection).to_not include user2
274 | end
275 |
276 | it 'give the right page' do
277 | user1 = User.create(name: '1')
278 | user2 = User.create(name: '2')
279 | options = { page_sizes: [1], array: true }
280 | list = build_list(options: options)
281 |
282 | list.setup({ "users_smart_listing" => { page: 2 } }, {})
283 |
284 | expect(list.collection).to include user2
285 | expect(list.collection).to_not include user1
286 | end
287 | end
288 |
289 | context 'when the collection is not an array' do
290 | it 'sort the collection by the given option' do
291 | user1 = User.create(name: '1')
292 | user2 = User.create(name: '2')
293 | options = { default_sort: { 'name' => 'desc' } }
294 | list = build_list(options: options)
295 |
296 | list.setup({},{})
297 |
298 | expect(list.collection.first).to eq user2
299 | expect(list.collection.last).to eq user1
300 | end
301 |
302 | it 'give only the given number per page' do
303 | user1 = User.create(name: '1')
304 | user2 = User.create(name: '2')
305 | options = { page_sizes: [1] }
306 | list = build_list(options: options)
307 |
308 | list.setup({},{})
309 |
310 | expect(list.collection).to include user1
311 | expect(list.collection).to_not include user2
312 | end
313 |
314 | it 'give the right page' do
315 | user1 = User.create(name: '1')
316 | user2 = User.create(name: '2')
317 | options = { page_sizes: [1] }
318 | list = build_list(options: options)
319 |
320 | list.setup({ "users_smart_listing" => { page: 2 } }, {})
321 |
322 | expect(list.collection).to include user2
323 | expect(list.collection).to_not include user1
324 | end
325 | end
326 | end
327 |
328 | def build_list(options: {})
329 | Base.new(:users, User.all, options)
330 | end
331 | end
332 | end
333 |
--------------------------------------------------------------------------------
/spec/rails_helper.rb:
--------------------------------------------------------------------------------
1 | # This file is copied to spec/ when you run 'rails generate rspec:install'
2 | ENV["RAILS_ENV"] ||= 'test'
3 | require 'spec_helper'
4 | require File.expand_path("../dummy/config/environment", __FILE__)
5 | require 'rspec/rails'
6 | require 'database_cleaner'
7 |
8 | # Requires supporting ruby files with custom matchers and macros, etc, in
9 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
10 | # run as spec files by default. This means that files in spec/support that end
11 | # in _spec.rb will both be required and run as specs, causing the specs to be
12 | # run twice. It is recommended that you do not name files matching this glob to
13 | # end with _spec.rb. You can configure this pattern with the --pattern
14 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
15 |
16 | Dir[Rails.root.join("../support/**/*.rb")].each { |f| require f }
17 |
18 | # Checks for pending migrations before tests are run.
19 | # If you are not using ActiveRecord, you can remove this line.
20 | ActiveRecord::Migration.maintain_test_schema!
21 |
22 | RSpec.configure do |config|
23 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
24 | config.fixture_path = "#{::Rails.root}/fixtures"
25 |
26 | # If you're not using ActiveRecord, or you'd prefer not to run each of your
27 | # examples within a transaction, remove the following line or assign false
28 | # instead of true.
29 | config.use_transactional_fixtures = false
30 |
31 | # RSpec Rails can automatically mix in different behaviours to your tests
32 | # based on their file location, for example enabling you to call `get` and
33 | # `post` in specs under `spec/controllers`.
34 | #
35 | # You can disable this behaviour by removing the line below, and instead
36 | # explicitly tag your specs with their type, e.g.:
37 | #
38 | # RSpec.describe UsersController, :type => :controller do
39 | # # ...
40 | # end
41 | #
42 | # The different available types are documented in the features, such as in
43 | # https://relishapp.com/rspec/rspec-rails/docs
44 | config.infer_spec_type_from_file_location!
45 |
46 | config.include WaitForAjax, type: :feature
47 |
48 | config.before :each do
49 | DatabaseCleaner.start
50 | end
51 |
52 | config.after do
53 | DatabaseCleaner.clean
54 | end
55 | end
56 |
57 | DatabaseCleaner.strategy = :truncation
58 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'capybara/rspec'
2 | require 'capybara-webkit'
3 |
4 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all
5 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
6 | # The generated `.rspec` file contains `--require spec_helper` which will cause this
7 | # file to always be loaded, without a need to explicitly require it in any files.
8 | #
9 | # Given that it is always loaded, you are encouraged to keep this file as
10 | # light-weight as possible. Requiring heavyweight dependencies from this file
11 | # will add to the boot time of your test suite on EVERY test run, even for an
12 | # individual file that may not need all of that loaded. Instead, make a
13 | # separate helper file that requires this one and then use it only in the specs
14 | # that actually need it.
15 | #
16 | # The `.rspec` file also contains a few flags that are not defaults but that
17 | # users commonly want.
18 | #
19 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
20 | RSpec.configure do |config|
21 | # The settings below are suggested to provide a good initial experience
22 | # with RSpec, but feel free to customize to your heart's content.
23 | # These two settings work together to allow you to limit a spec run
24 | # to individual examples or groups you care about by tagging them with
25 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples
26 | # get run.
27 | config.filter_run :focus
28 | config.run_all_when_everything_filtered = true
29 |
30 | # Many RSpec users commonly either run the entire suite or an individual
31 | # file, and it's useful to allow more verbose output when running an
32 | # individual spec file.
33 | if config.files_to_run.one?
34 | # Use the documentation formatter for detailed output,
35 | # unless a formatter has already been configured
36 | # (e.g. via a command-line flag).
37 | # config.default_formatter = 'doc'
38 | end
39 |
40 | # Print the 10 slowest examples and example groups at the
41 | # end of the spec run, to help surface which specs are running
42 | # particularly slow.
43 | # config.profile_examples = 10
44 |
45 | # Run specs in random order to surface order dependencies. If you find an
46 | # order dependency and want to debug it, you can fix the order by providing
47 | # the seed, which is printed after each run.
48 | # --seed 1234
49 | config.order = :random
50 |
51 | # Seed global randomization in this process using the `--seed` CLI option.
52 | # Setting this allows you to use `--seed` to deterministically reproduce
53 | # test failures related to randomization by passing the same `--seed` value
54 | # as the one that triggered the failure.
55 | Kernel.srand config.seed
56 |
57 | # rspec-expectations config goes here. You can use an alternate
58 | # assertion/expectation library such as wrong or the stdlib/minitest
59 | # assertions if you prefer.
60 | config.expect_with :rspec do |expectations|
61 | # Enable only the newer, non-monkey-patching expect syntax.
62 | # For more details, see:
63 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
64 | expectations.syntax = :expect
65 | end
66 |
67 | # rspec-mocks config goes here. You can use an alternate test double
68 | # library (such as bogus or mocha) by changing the `mock_with` option here.
69 | config.mock_with :rspec do |mocks|
70 | # Enable only the newer, non-monkey-patching expect syntax.
71 | # For more details, see:
72 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
73 | mocks.syntax = :expect
74 |
75 | # Prevents you from mocking or stubbing a method that does not exist on
76 | # a real object. This is generally recommended.
77 | mocks.verify_partial_doubles = true
78 | end
79 | end
80 |
81 | Capybara.javascript_driver = :webkit
82 |
--------------------------------------------------------------------------------
/spec/support/capybara/wait_for_ajax.rb:
--------------------------------------------------------------------------------
1 | module WaitForAjax
2 | def wait_for_ajax
3 | Timeout.timeout(Capybara.default_max_wait_time) do
4 | loop until finished_all_ajax_requests?
5 | end
6 | end
7 |
8 | def finished_all_ajax_requests?
9 | page.evaluate_script('jQuery.active').zero?
10 | end
11 | end
12 |
--------------------------------------------------------------------------------