peentar-smart_listing 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +279 -0
  4. data/Rakefile +34 -0
  5. data/app/assets/javascripts/smart_listing.coffee.erb +438 -0
  6. data/app/helpers/smart_listing/application_helper.rb +5 -0
  7. data/app/helpers/smart_listing/helper.rb +366 -0
  8. data/app/views/kaminari/smart_listing/_first_page.html.erb +13 -0
  9. data/app/views/kaminari/smart_listing/_gap.html.erb +8 -0
  10. data/app/views/kaminari/smart_listing/_last_page.html.erb +13 -0
  11. data/app/views/kaminari/smart_listing/_next_page.html.erb +13 -0
  12. data/app/views/kaminari/smart_listing/_page.html.erb +12 -0
  13. data/app/views/kaminari/smart_listing/_paginator.html.erb +25 -0
  14. data/app/views/kaminari/smart_listing/_prev_page.html.erb +13 -0
  15. data/app/views/smart_listing/_action_custom.html.erb +10 -0
  16. data/app/views/smart_listing/_action_delete.html.erb +12 -0
  17. data/app/views/smart_listing/_action_edit.html.erb +11 -0
  18. data/app/views/smart_listing/_action_show.html.erb +11 -0
  19. data/app/views/smart_listing/_item_new.html.erb +30 -0
  20. data/app/views/smart_listing/_pagination_per_page_link.html.erb +12 -0
  21. data/app/views/smart_listing/_pagination_per_page_links.html.erb +19 -0
  22. data/app/views/smart_listing/_sortable.html.erb +21 -0
  23. data/app/views/smart_listing/_update_list.js.erb +2 -0
  24. data/app/views/smart_listing/create.js.erb +2 -0
  25. data/app/views/smart_listing/destroy.js.erb +1 -0
  26. data/app/views/smart_listing/edit.js.erb +1 -0
  27. data/app/views/smart_listing/index.js.erb +1 -0
  28. data/app/views/smart_listing/item/_create.js.erb +3 -0
  29. data/app/views/smart_listing/item/_create_continue.js.erb +6 -0
  30. data/app/views/smart_listing/item/_destroy.js.erb +2 -0
  31. data/app/views/smart_listing/item/_edit.js.erb +2 -0
  32. data/app/views/smart_listing/item/_new.js.erb +2 -0
  33. data/app/views/smart_listing/item/_remove.js.erb +2 -0
  34. data/app/views/smart_listing/item/_update.js.erb +2 -0
  35. data/app/views/smart_listing/new.js.erb +1 -0
  36. data/app/views/smart_listing/update.js.erb +2 -0
  37. data/config/locales/en.yml +15 -0
  38. data/config/routes.rb +2 -0
  39. data/lib/generators/smart_listing/install_generator.rb +20 -0
  40. data/lib/generators/smart_listing/templates/initializer.rb +88 -0
  41. data/lib/generators/smart_listing/views_generator.rb +23 -0
  42. data/lib/smart_listing.rb +216 -0
  43. data/lib/smart_listing/config.rb +159 -0
  44. data/lib/smart_listing/engine.rb +5 -0
  45. data/lib/smart_listing/version.rb +3 -0
  46. data/lib/tasks/smart_list_tasks.rake +4 -0
  47. metadata +245 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0c9945c464be02df429975a97ffeb2a2fbd3e9e1
4
+ data.tar.gz: 80b5ad364b9a8cf5789edb8345d812a1602ae401
5
+ SHA512:
6
+ metadata.gz: 91c4a5b7ee56ba8c688b273dcffa64e379332fe6686101e3075c3957b97710277bf775d7f3a8cbc257980dee1def579fc570aa25be2552b5ef351839f3ab2468
7
+ data.tar.gz: 4a391032b898acd2ee6af810bcde7e871abd3350fc7d7c7c3b6b33445fab216c8ebbbac39a78ac598c71a0baa1203a736d8fbe162ac4d13c09edeca3d500d7b0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Peentar Pervasive Asia (www.peentar.com)
4
+ Copyright (c) 2013 Sology (www.sology.eu)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
+ the Software, and to permit persons to whom the Software is furnished to do so,
11
+ subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,279 @@
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
+ Original smart_listing seems no longer maintained. We fork our own version (and hopefully make it better).
8
+
9
+ ## Installation
10
+
11
+ Add to your Gemfile:
12
+
13
+ ```ruby
14
+ gem "smart_listing"
15
+ ```
16
+
17
+ Then run:
18
+
19
+ ```sh
20
+ $ bundle install
21
+ ```
22
+
23
+ Also, you need to add SmartListing to your asset pipeline:
24
+
25
+ ```
26
+ //= require smart_listing
27
+ ```
28
+
29
+ ### Initializer
30
+
31
+ Optionally you can also install some configuration initializer:
32
+
33
+ ```sh
34
+ $ rails generate smart_listing:install
35
+ ```
36
+
37
+ 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.
38
+
39
+ ### Custom views
40
+
41
+ SmartListing comes with some built-in views which are by default compatible with Bootstrap 3. You can easily change them after installing:
42
+
43
+ ```sh
44
+ $ rails generate smart_listing:views
45
+ ```
46
+
47
+ Files will be placed in `app/views/smart_listing`.
48
+
49
+ ## Usage
50
+
51
+ Let's start with a controller. In order to use SmartListing, in most cases you need to include controller extensions and SmartListing helper methods:
52
+
53
+ ```ruby
54
+ include SmartListing::Helper::ControllerExtensions
55
+ helper SmartListing::Helper
56
+ ```
57
+
58
+ Next, put following code in controller action you desire:
59
+
60
+ ```ruby
61
+ @users = smart_listing_create(:users, User.active, partial: "users/listing")
62
+ ```
63
+
64
+ 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.
65
+
66
+ In the main view (typically something like `index.html.erb` or `index.html.haml`), use this method to render listing:
67
+
68
+ ```ruby
69
+ smart_listing_render(:users)
70
+ ```
71
+
72
+ `smart_listing_render` does some magic and renders `users/listing` partial which may look like this (in HAML):
73
+
74
+ ```haml
75
+ - unless smart_listing.empty?
76
+ %table
77
+ %thead
78
+ %tr
79
+ %th User name
80
+ %th Email
81
+ %tbody
82
+ - smart_listing.collection.each do |user|
83
+ %tr
84
+ %td= user.name
85
+ %td= user.email
86
+
87
+ = smart_listing.paginate
88
+ - else
89
+ %p.warning No records!
90
+ ```
91
+
92
+ 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:
93
+
94
+ * `Builder#paginate` - renders Kaminari pagination,
95
+ * `Builder#pagination_per_page_links` - display some link that allow you to customize Kaminari's `per_page`,
96
+ * `Builder#collection` - accesses underlying list of items,
97
+ * `Builder#empty?` - checks if collection is empty,
98
+ * `Builder#count` - returns collection count,
99
+ * `Builder#render` - basic template's `render` wrapper that automatically adds `smart_listing` local variable,
100
+
101
+ There are also other methods that will be described in detail below.
102
+
103
+ 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`):
104
+
105
+ ```erb
106
+ <%= smart_listing_update(:users) %>
107
+ ```
108
+
109
+ ### Sorting
110
+
111
+ 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:
112
+
113
+ ```haml
114
+ - unless smart_listing.empty?
115
+ %table
116
+ %thead
117
+ %tr
118
+ %th= smart_listing.sortable "User name", :name
119
+ %th= smart_listing.sortable "Email", :email
120
+ %tbody
121
+ - smart_listing.collection.each do |user|
122
+ %tr
123
+ %td= user.name
124
+ %td= user.email
125
+
126
+ = smart_listing.paginate
127
+ - else
128
+ %p.warning No records!
129
+ ```
130
+
131
+ 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).
132
+
133
+ You can also specify default sort order in the controller:
134
+
135
+ ```ruby
136
+ @users = smart_listing_create(:users, User.active, partial: "users/listing", default_sort: {name: "asc"})
137
+ ```
138
+
139
+ 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:
140
+ ```ruby
141
+ @users = smart_listing_create :users, User.active.joins(:stats), partial: "users/listing",
142
+ sort_attributes: [[:last_signin, "stats.last_signin_at"]],
143
+ default_sort: {last_signin: "desc"}
144
+ ```
145
+
146
+ Note that `:sort_attributes` are array which of course means, that order of attributes matters.
147
+
148
+ There's also a possibility to specify available sort directions using `:sort_dirs` option which is by default `[nil, "asc", "desc"]`.
149
+
150
+ ### List item management and in-place editing
151
+
152
+ 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:
153
+
154
+ ```haml
155
+ - unless smart_listing.empty?
156
+ %table
157
+ %thead
158
+ %tr
159
+ %th= smart_listing.sortable "User name", "name"
160
+ %th= smart_listing.sortable "Email", "email"
161
+ %th
162
+ %tbody
163
+ - smart_listing.collection.each do |user|
164
+ %tr.editable{data: {id: user.id}}
165
+ = smart_listing.render partial: 'users/user', locals: {user: user}
166
+ = smart_listing.item_new colspan: 3, link: new_user_path
167
+
168
+ = smart_listing.paginate
169
+ - else
170
+ %p.warning No records!
171
+ ```
172
+
173
+ `<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:
174
+
175
+ ```ruby
176
+ <%= smart_listing_item :users, :new, @new_user, "users/form" %>
177
+ ```
178
+
179
+ Note that `new` action does not need to create SmartListing (via `smart_listing_create`). It just initializes `@new_user` and renders JS view.
180
+
181
+ New partial for user (`users/user`) may look like this:
182
+ ```haml
183
+ %td= user.name
184
+ %td= user.email
185
+ %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)}]
186
+ ```
187
+
188
+ `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. `<td>`'s class `actions` is important.
189
+
190
+ Controller actions referenced by above urls are again plain Ruby on Rails actions that render JS like:
191
+
192
+ ```erb
193
+ <%= smart_listing_item :users, :new, @user, "users/form" %>
194
+ <%= smart_listing_item :users, :edit, @user, "users/form" %>
195
+ <%= smart_listing_item :users, :destroy, @user %>
196
+ ```
197
+
198
+ Partial name supplied to `smart_listing_item` (`users/form`) references `@user` as `object` and may look like this:
199
+
200
+ ```haml
201
+ %td{colspan: 3}
202
+ - if object.persisted?
203
+ %p Edit user
204
+ - else
205
+ %p Add user
206
+
207
+ = form_for object, url: object.new_record? ? users_path : user_path(object), remote: true do |f|
208
+ %p
209
+ Name:
210
+ = f.text_field :name
211
+ %p
212
+ Email:
213
+ = f.text_field :email
214
+ %p= f.submit "Save"
215
+ ```
216
+
217
+ And one last thing are `create` and `update` controller actions JS view:
218
+
219
+ ```ruby
220
+ <%= smart_listing_item :users, :create, @user, @user.persisted? ? "users/user" : "users/form" %>
221
+ <%= smart_listing_item :users, :update, @user, @user.valid? ? "users/user" : "users/form" %>
222
+ ```
223
+
224
+ ### Controls (filtering)
225
+
226
+ 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:
227
+
228
+ ```haml
229
+ = smart_listing_controls_for(:users) do
230
+ .filter.input-append
231
+ = text_field_tag :filter, '', class: "search", placeholder: "Type name here", autocomplete: "off"
232
+ %button.btn.disabled{type: "submit"}
233
+ %i.icon.icon-search
234
+ ```
235
+
236
+ This gives you nice Bootstrap-enabled filter field with keychange handler. Of course you can use any other form fields in controls too.
237
+
238
+ When form field changes its value, form is submitted and request is made. This needs to be handled in controller:
239
+
240
+ ```ruby
241
+ users_scope = User.active.joins(:stats)
242
+ users_scope = users_scope.like(params[:filter]) if params[:filter]
243
+ @users = smart_listing_create :users, users_scope, partial: "users/listing"
244
+ ```
245
+
246
+ Then, JS view is rendered and your SmartListing updated. That's it!
247
+
248
+ ### Simplified views
249
+
250
+ You don't need to create all the JS views in case you want to simply use one SmartListing per controller. Just use helper methods without their first attribute (name) ie. `smart_listing_create(User.active, partial: "users/listing")`. Then define two helper methods:
251
+
252
+ * `smart_listing_resource` returning single object,
253
+ * `smart_listing_collection` returning collection of objects.
254
+
255
+ SmartListing default views will user these methods to render your list properly.
256
+
257
+ ### More customization
258
+
259
+ Apart from standard SmartListing initializer, you can also define custom config profiles. In order to do this, use following syntax:
260
+
261
+ ```ruby
262
+ SmartListing.configure(:awesome_profile) do |config|
263
+ # put your definitions here
264
+ end
265
+ ```
266
+
267
+ In order to use this profile, create helper method named `smart_listing_config_profile` returning profile name and put into your JS `SmartListing.config.merge()` function call. `merge()` function expects parameter with config attributes hash or reads body data-attribute named `smart-listing-config`. Hash of config attributes can be obtained by using helper method `SmartListing.config(:awesome_profile).to_json`.
268
+
269
+ ## Not enough?
270
+
271
+ For more information and some use cases, see the [Showcase](http://showcase.sology.eu/smart_listing)
272
+
273
+ ## Credits
274
+
275
+ SmartListing uses great pagination gem Kaminari https://github.com/amatsuda/kaminari
276
+
277
+ Created by Sology http://www.sology.eu
278
+
279
+ Initial development sponsored by Smart Language Apps Limited http://smartlanguageapps.com/
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'SmartListing'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
@@ -0,0 +1,438 @@
1
+ # Useful when SmartListing target url is different than current one
2
+ $.rails.href = (element) ->
3
+ element.attr("href") || element.data("<%= SmartListing.config.data_attributes(:href) %>") || window.location.pathname
4
+
5
+ class window.SmartListing
6
+ class Config
7
+ @options: <%= SmartListing.config.dump_json %>
8
+
9
+ @merge: (d) ->
10
+ $.extend true, @options, d || $("body").data("smart-listing-config")
11
+
12
+ @class: (name)->
13
+ @options["constants"]["classes"][name]
14
+
15
+ @class_name: (name) ->
16
+ ".#{@class(name)}"
17
+
18
+ @data_attribute: (name)->
19
+ @options["constants"]["data_attributes"][name]
20
+
21
+ @selector: (name)->
22
+ @options["constants"]["selectors"][name]
23
+
24
+ @element_template: (name)->
25
+ @options["constants"]["element_templates"][name]
26
+
27
+ @config: Config
28
+
29
+
30
+ constructor: (e) ->
31
+ @container = e
32
+ @name = @container.attr("id")
33
+ @loading = @container.find(SmartListing.config.class_name("loading"))
34
+ @content = @container.find(SmartListing.config.class_name("content"))
35
+ @status = $("#{SmartListing.config.class_name("status")} [data-#{SmartListing.config.data_attribute("main")}='#{@name}']")
36
+ @confirmed = null
37
+ @popovers = {}
38
+
39
+ @container.on "ajax:before", "#{SmartListing.config.class_name("item_actions")}, #{SmartListing.config.class_name("pagination_container")}", (e) =>
40
+ @fadeLoading()
41
+
42
+ @container.on "ajax:success", (e) =>
43
+ if $(e.target).is("#{SmartListing.config.class_name("item_actions")} #{SmartListing.config.selector("item_action_destroy")}")
44
+ # handle HEAD OK response for deletion request
45
+ editable = $(e.target).closest(SmartListing.config.class_name("editable"))
46
+ if @container.find(SmartListing.config.class_name("editable")).length == 1
47
+ @reload()
48
+ return false
49
+ else
50
+ editable.remove()
51
+
52
+ @container.trigger("smart_listing:destroy", editable)
53
+
54
+ @changeItemCount(-1)
55
+ @refresh()
56
+
57
+ @fadeLoaded()
58
+ return false
59
+
60
+ @container.on "click", SmartListing.config.selector("edit_cancel"), (event) =>
61
+ editable = $(event.currentTarget).closest(SmartListing.config.class_name("editable"))
62
+ if(editable.length > 0)
63
+ # Cancel edit
64
+ @cancelEdit(editable)
65
+ else
66
+ # Cancel new record
67
+ @container.find(SmartListing.config.class_name("new_item_placeholder")).addClass(SmartListing.config.class("hidden"))
68
+ @container.find(SmartListing.config.class_name("new_item_action")).removeClass(SmartListing.config.class("hidden"))
69
+
70
+ @setAutoshow(false)
71
+ false
72
+
73
+ @container.on "click", "#{SmartListing.config.class_name("item_actions")} a[data-#{SmartListing.config.data_attribute("confirmation")}]", (event) =>
74
+ $.fn.smart_listing.confirm $(event.currentTarget), $(event.currentTarget).data(SmartListing.config.data_attribute("confirmation"))
75
+
76
+ @container.on "click", "#{SmartListing.config.class_name("item_actions")} a[data-#{SmartListing.config.data_attribute("popover")}]", (event) =>
77
+ name = $(event.currentTarget).data(SmartListing.config.data_attribute("popover"))
78
+ if jQuery.isFunction(@popovers[name])
79
+ @popovers[name]($(event.currentTarget))
80
+ false
81
+
82
+
83
+ @container.on "click", "input[type=text]#{SmartListing.config.class_name("autoselect")}", (event) ->
84
+ $(this).select()
85
+
86
+ @container.on "change", SmartListing.config.class_name("callback"), (event) =>
87
+ checkbox = $(event.currentTarget)
88
+ id = checkbox.closest(SmartListing.config.selector("row")).data(SmartListing.config.data_attribute("id"))
89
+ data = {}
90
+ data[checkbox.val()] = checkbox.is(":checked")
91
+ $.ajax({
92
+ beforeSend: (xhr, settings) ->
93
+ xhr.setRequestHeader "accept", "*/*;q=0.5, " + settings.accepts.script
94
+ url: @container.data(SmartListing.config.data_attribute("callback_href")),
95
+ type: "POST",
96
+ data: data,
97
+ })
98
+
99
+ fadeLoading: =>
100
+ $.fn.smart_listing.onLoading(@content, @loading)
101
+
102
+ fadeLoaded: =>
103
+ $.fn.smart_listing.onLoaded(@content, @loading)
104
+
105
+ itemCount: =>
106
+ parseInt(@container.data(SmartListing.config.data_attribute("item_count")))
107
+
108
+ maxCount: =>
109
+ parseInt(@container.data(SmartListing.config.data_attribute("max_count")))
110
+
111
+ setAutoshow: (v) =>
112
+
113
+ @container.data(SmartListing.config.data_attribute("autoshow"), v)
114
+
115
+ changeItemCount: (value) =>
116
+ count = @container.data(SmartListing.config.data_attribute("item_count")) + value
117
+ @container.data(SmartListing.config.data_attribute("item_count"), count)
118
+ @container.find(SmartListing.config.selector("pagination_count")).html(count)
119
+
120
+ cancelEdit: (editable) =>
121
+ if editable.data(SmartListing.config.data_attribute("inline_edit_backup"))
122
+ editable.html(editable.data(SmartListing.config.data_attribute("inline_edit_backup")))
123
+ editable.removeClass(SmartListing.config.class("inline_editing"))
124
+ editable.removeData(SmartListing.config.data_attribute("inline_edit_backup"))
125
+
126
+ # Callback called when record is added/deleted using ajax request
127
+ refresh: () =>
128
+ header = @content.find(SmartListing.config.selector("head"))
129
+ footer = @content.find(SmartListing.config.class_name("pagination_per_page"))
130
+ no_records = @content.find(SmartListing.config.class_name("no_records"))
131
+
132
+ if @itemCount() == 0
133
+ header.hide()
134
+ footer.hide()
135
+ no_records.show()
136
+ else
137
+ header.show()
138
+ footer.show()
139
+ no_records.hide()
140
+
141
+ if @maxCount()
142
+ if @itemCount() >= @maxCount()
143
+ @container.find(SmartListing.config.class_name("new_item_placeholder")).addClass(SmartListing.config.class("hidden"))
144
+ @container.find(SmartListing.config.class_name("new_item_action")).addClass(SmartListing.config.class("hidden"))
145
+ else
146
+ if @container.data(SmartListing.config.data_attribute("autoshow"))
147
+ @container.find(SmartListing.config.class_name("new_item_placeholder")).removeClass(SmartListing.config.class("hidden"))
148
+ @container.find(SmartListing.config.class_name("new_item_action")).addClass(SmartListing.config.class("hidden"))
149
+ else
150
+ @container.find(SmartListing.config.class_name("new_item_placeholder")).addClass(SmartListing.config.class("hidden"))
151
+ @container.find(SmartListing.config.class_name("new_item_action")).removeClass(SmartListing.config.class("hidden"))
152
+
153
+ @status.each (index, status) =>
154
+ $(status).find(SmartListing.config.class_name("limit")).html(@maxCount() - @itemCount())
155
+ if @maxCount() - @itemCount() == 0
156
+ $(status).find(SmartListing.config.class_name("limit_alert")).show()
157
+ else
158
+ $(status).find(SmartListing.config.class_name("limit_alert")).hide()
159
+
160
+ # Trigger AJAX request to reload the list
161
+ reload: () =>
162
+ $.rails.handleRemote(@container)
163
+
164
+ params: (value) =>
165
+ if value
166
+ @container.data(SmartListing.config.data_attribute("params"), value)
167
+ else
168
+ @container.data(SmartListing.config.data_attribute("params"))
169
+
170
+ registerPopover: (name, callback) =>
171
+ @popovers[name] = callback
172
+
173
+ editable: (id) =>
174
+ @container.find("#{SmartListing.config.class_name("editable")}[data-#{SmartListing.config.data_attribute("id")}=#{id}]")
175
+
176
+ #################################################################################################
177
+ # Methods executed by rails UJS:
178
+
179
+ new_item: (content) =>
180
+ if !@maxCount() || (@itemCount() < @maxCount())
181
+ new_item_action = @container.find(SmartListing.config.class_name("new_item_action"))
182
+ new_item_placeholder = @container.find(SmartListing.config.class_name("new_item_placeholder")).addClass(SmartListing.config.class("hidden"))
183
+
184
+ @container.find(SmartListing.config.class_name("editable")).each (i, v) =>
185
+ @cancelEdit($(v))
186
+
187
+ new_item_action.addClass(SmartListing.config.class("hidden"))
188
+ new_item_placeholder.removeClass(SmartListing.config.class("hidden"))
189
+ new_item_placeholder.html(content)
190
+ new_item_placeholder.addClass(SmartListing.config.class("inline_editing"))
191
+
192
+ @container.trigger("smart_listing:new", new_item_placeholder)
193
+
194
+ @fadeLoaded()
195
+
196
+ create: (id, success, content) =>
197
+ new_item_action = @container.find(SmartListing.config.class_name("new_item_action"))
198
+ new_item_placeholder = @container.find(SmartListing.config.class_name("new_item_placeholder"))
199
+
200
+ if success
201
+ new_item_placeholder.addClass(SmartListing.config.class("hidden"))
202
+ new_item_action.removeClass(SmartListing.config.class("hidden"))
203
+
204
+ new_item = $(SmartListing.config.element_template("row")).addClass(SmartListing.config.class("editable"))
205
+ new_item.attr("data-#{SmartListing.config.data_attribute("id")}", id)
206
+ new_item.html(content)
207
+
208
+ if new_item_placeholder.length != 0
209
+ if new_item_placeholder.data("insert-mode") == "after"
210
+ new_item_placeholder.after(new_item)
211
+ else
212
+ new_item_placeholder.before(new_item)
213
+ else
214
+ @content.append(new_item)
215
+
216
+ @container.trigger("smart_listing:create:success", new_item)
217
+
218
+ @changeItemCount(1)
219
+ @refresh()
220
+ else
221
+ new_item_placeholder.html(content)
222
+
223
+ @container.trigger("smart_listing:create:fail", new_item_placeholder)
224
+
225
+ @fadeLoaded()
226
+
227
+ edit: (id, content) =>
228
+ @container.find(SmartListing.config.class_name("editable")).each (i, v) =>
229
+ @cancelEdit($(v))
230
+ @container.find(SmartListing.config.class_name("new_item_placeholder")).addClass(SmartListing.config.class("hidden"))
231
+ @container.find(SmartListing.config.class_name("new_item_action")).removeClass(SmartListing.config.class("hidden"))
232
+
233
+ editable = @editable(id)
234
+ editable.data(SmartListing.config.data_attribute("inline_edit_backup"), editable.html())
235
+ editable.html(content)
236
+ editable.addClass(SmartListing.config.class("inline_editing"))
237
+
238
+ @container.trigger("smart_listing:edit", editable)
239
+
240
+ @fadeLoaded()
241
+
242
+ update: (id, success, content) =>
243
+ editable = @editable(id)
244
+ if success
245
+ editable.removeClass(SmartListing.config.class("inline_editing"))
246
+ editable.removeData(SmartListing.config.data_attribute("inline_edit_backup"))
247
+ editable.html(content)
248
+
249
+ @container.trigger("smart_listing:update:success", editable)
250
+
251
+ @refresh()
252
+ else
253
+ editable.html(content)
254
+
255
+ @container.trigger("smart_listing:update:fail", editable)
256
+
257
+ @fadeLoaded()
258
+
259
+ destroy: (id, destroyed) =>
260
+ # No need to do anything here, already handled by ajax:success handler
261
+
262
+ remove: (id) =>
263
+ editable = @editable(id)
264
+ editable.remove()
265
+
266
+ @container.trigger("smart_listing:remove", editable)
267
+
268
+ update_list: (content, data) =>
269
+ @container.data(SmartListing.config.data_attribute("params"), $.extend(@container.data(SmartListing.config.data_attribute("params")), data[SmartListing.config.data_attribute("params")]))
270
+ @container.data(SmartListing.config.data_attribute("max_count"), data[SmartListing.config.data_attribute("max_count")])
271
+ @container.data(SmartListing.config.data_attribute("item_count"), data[SmartListing.config.data_attribute("item_count")])
272
+
273
+ @content.html(content)
274
+
275
+ @refresh()
276
+ @fadeLoaded()
277
+
278
+ @container.trigger("smart_listing:update_list", @container)
279
+
280
+ $.fn.smart_listing = () ->
281
+ map = $(this).map () ->
282
+ if !$(this).data(SmartListing.config.data_attribute("main"))
283
+ $(this).data(SmartListing.config.data_attribute("main"), new SmartListing($(this)))
284
+ $(this).data(SmartListing.config.data_attribute("main"))
285
+ if map.length == 1
286
+ map[0]
287
+ else
288
+ map
289
+
290
+ $.fn.smart_listing.observeField = (field, opts = {}) ->
291
+ key_timeout = null
292
+ last_value = null
293
+ options =
294
+ onFilled: () ->
295
+ onEmpty: () ->
296
+ onChange: () ->
297
+ options = $.extend(options, opts)
298
+
299
+ keyChange = () ->
300
+ if field.val().length > 0
301
+ options.onFilled()
302
+ else
303
+ options.onEmpty()
304
+
305
+ if field.val() == last_value && field.val().length != 0
306
+ return
307
+ lastValue = field.val()
308
+
309
+ options.onChange()
310
+
311
+ field.data(SmartListing.config.data_attribute("observed"), true)
312
+
313
+ field.bind "keydown", (e) ->
314
+ if(key_timeout)
315
+ clearTimeout(key_timeout)
316
+
317
+ key_timeout = setTimeout(->
318
+ keyChange()
319
+ , 400)
320
+
321
+ $.fn.smart_listing.showPopover = (elem, body) ->
322
+ elem.popover("destroy")
323
+ elem.popover(content: body, html: true, trigger: "manual", title: null)
324
+ elem.popover("show")
325
+
326
+ $.fn.smart_listing.showConfirmation = (confirmation_elem, msg, confirm_callback) ->
327
+ buildPopover = (confirmation_elem, msg) ->
328
+ deletion_popover = $("<div/>").addClass("confirmation_box")
329
+ deletion_popover.append($("<p/>").html(msg))
330
+ deletion_popover.append($("<p/>")
331
+ .append($("<button/>").html("Yes").addClass("btn btn-danger ").click (event) =>
332
+ # set @confirmed element and emulate click on icon
333
+ editable = $(event.currentTarget).closest(SmartListing.config.class_name("editable"))
334
+ confirm_callback(confirmation_elem)
335
+ $(confirmation_elem).click()
336
+ $(confirmation_elem).popover("destroy")
337
+ )
338
+ .append(" ")
339
+ .append($("<button/>").html("No").addClass("btn btn-small").click (event) =>
340
+ editable = $(event.currentTarget).closest(SmartListing.config.class_name("editable"))
341
+ $(confirmation_elem).popover("destroy")
342
+ )
343
+ )
344
+
345
+ $.fn.smart_listing.showPopover confirmation_elem, buildPopover(confirmation_elem, msg)
346
+
347
+ $.fn.smart_listing.confirm = (elem, msg) ->
348
+ if !elem.data("confirmed")
349
+ # We need confirmation
350
+ $.fn.smart_listing.showConfirmation elem, msg, (confirm_elem) =>
351
+ confirm_elem.data("confirmed", true)
352
+ false
353
+ else
354
+ # Confirmed, reset flag and go ahead with deletion
355
+ elem.data("confirmed", false)
356
+ true
357
+
358
+ $.fn.smart_listing.onLoading = (content, loader) ->
359
+ content.stop(true).fadeTo(500, 0.2)
360
+ loader.show()
361
+ loader.stop(true).fadeTo(500, 1)
362
+
363
+ $.fn.smart_listing.onLoaded = (content, loader) ->
364
+ content.stop(true).fadeTo(500, 1)
365
+ loader.stop(true).fadeTo 500, 0, () =>
366
+ loader.hide()
367
+
368
+ $.fn.smart_listing_controls = () ->
369
+ reload = (controls) ->
370
+ container = $("##{controls.data(SmartListing.config.data_attribute("main"))}")
371
+ smart_listing = container.smart_listing()
372
+
373
+ # serialize form and merge it with smart listing params
374
+ prms = {}
375
+ $.each controls.serializeArray(), (i, field) ->
376
+ if Array.isArray(prms[field.name])
377
+ prms[field.name].push field.value
378
+ else if prms[field.name]
379
+ prms[field.name] = [prms[field.name], field.value]
380
+ else
381
+ prms[field.name] = field.value
382
+ prms = $.extend(smart_listing.params(), prms)
383
+ smart_listing.params(prms)
384
+
385
+ smart_listing.fadeLoading()
386
+ smart_listing.reload()
387
+
388
+ $(this).each () ->
389
+ # avoid double initialization
390
+ return if $(this).data(SmartListing.config.data_attribute("controls_initialized"))
391
+ $(this).data(SmartListing.config.data_attribute("controls_initialized"), true)
392
+
393
+ controls = $(this)
394
+ smart_listing = $("##{controls.data(SmartListing.config.data_attribute("main"))}")
395
+ reset = controls.find(SmartListing.config.class_name("controls_reset"))
396
+
397
+ controls.submit ->
398
+ # setup smart listing params, reload and don"t actually submit controls form
399
+ reload(controls)
400
+ false
401
+
402
+ controls.find("input, select").change () ->
403
+ unless $(this).data(SmartListing.config.data_attribute("observed")) # do not submit controls form when changed field is observed (observing submits form by itself)
404
+ reload(controls)
405
+
406
+ $.fn.smart_listing_controls.filter(controls.find(SmartListing.config.class_name("filtering")))
407
+
408
+ $.fn.smart_listing_controls.filter = (filter) ->
409
+ form = filter.closest("form")
410
+ button = form.find(SmartListing.config.selector("filtering_button"))
411
+ icon = form.find(SmartListing.config.selector("filtering_icon"))
412
+ field = form.find(SmartListing.config.selector("filtering_input"))
413
+
414
+ $.fn.smart_listing.observeField(field,
415
+ onFilled: ->
416
+ icon.removeClass(SmartListing.config.class("filtering_search"))
417
+ icon.addClass(SmartListing.config.class("filtering_cancel"))
418
+ button.removeClass(SmartListing.config.class("filtering_disabled"))
419
+ onEmpty: ->
420
+ icon.addClass(SmartListing.config.class("filtering_search"))
421
+ icon.removeClass(SmartListing.config.class("filtering_cancel"))
422
+ button.addClass(SmartListing.config.class("filtering_disabled"))
423
+ onChange: ->
424
+ form.submit()
425
+ )
426
+
427
+ button.click ->
428
+ if field.val().length > 0
429
+ field.val("")
430
+ field.trigger("keydown")
431
+ return false
432
+
433
+ ready = ->
434
+ $(SmartListing.config.class_name("main")).smart_listing()
435
+ $(SmartListing.config.class_name("controls")).smart_listing_controls()
436
+
437
+ $(document).ready ready
438
+ $(document).on "page:load", ready