smart_listing 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ce2c7ca9fc4d41156fec07c92a1c9cca87c31d49
4
+ data.tar.gz: 77aa5bdd86f016a2252c0e42c2fd2fc68e981b28
5
+ SHA512:
6
+ metadata.gz: 6383baf287499b38bf6c4216c52883ce29b223d4168f57f700f2e89d1cb11936232d13e6c0895622d8cb86fd3e64b5b839255097c810afdaf2b0d2bd04832adf
7
+ data.tar.gz: cfc2fd5a53332a3546e26f70b2f72943392989e7828bf2a5b4843a28639b42b0789d21d1ddd923e4a9028c7ad4e07f0b36e8beeb16cbcdf8d5fe2d35a642d05d
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Sology
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.
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ smart_listing
2
+ ==========
3
+
4
+ Smart listing utility gem
data/Rakefile ADDED
@@ -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("../test/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,14 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
14
+ //= require smart_listing/smart_listing
@@ -0,0 +1,317 @@
1
+ # Useful when smart list target url is different than current one
2
+ $.rails.href = (element) ->
3
+ element.attr('href') || element.data('href')
4
+
5
+ $.fn.observeField = (opts = {}) ->
6
+ field = $(this)
7
+ key_timeout = null
8
+ last_value = null
9
+ options =
10
+ onFilled: () ->
11
+ onEmpty: () ->
12
+ onChange: () ->
13
+ options = $.extend(options, opts)
14
+
15
+ keyChange = () ->
16
+ if field.val().length > 0
17
+ options.onFilled()
18
+ else
19
+ options.onEmpty()
20
+
21
+ if field.val() == last_value && field.val().length != 0
22
+ return
23
+ lastValue = field.val()
24
+
25
+ options.onChange()
26
+
27
+ field.data('observed', true)
28
+
29
+ field.bind 'keydown', (e) ->
30
+ if(key_timeout)
31
+ clearTimeout(key_timeout)
32
+
33
+ key_timeout = setTimeout(->
34
+ keyChange()
35
+ , 400)
36
+
37
+ class SmartListing
38
+ constructor: (e) ->
39
+ @container = e
40
+ @name = @container.attr('id')
41
+ @loading = @container.find('.loading')
42
+ @content = @container.find('.content')
43
+ @status = $(".smart_listing_status[data-smart-list='#{@name}']")
44
+ @confirmed = null
45
+
46
+ createPopover = (confirmation_elem, msg) =>
47
+ deletion_popover = $('<div/>').addClass('confirmation_box')
48
+ deletion_popover.append($('<p/>').html(msg))
49
+ deletion_popover.append($('<p/>')
50
+ .append($('<button/>').html('Yes').addClass('btn btn-danger ').click (event) =>
51
+ # set @confirmed element and emulate click on icon
52
+ editable = $(event.currentTarget).closest('.editable')
53
+ @confirmed = confirmation_elem
54
+ $(confirmation_elem).click()
55
+ $(confirmation_elem).popover('destroy')
56
+ )
57
+ .append($('<button/>').html('No').addClass('btn btn-small').click (event) =>
58
+ editable = $(event.currentTarget).closest('.editable')
59
+ $(confirmation_elem).popover('destroy')
60
+ )
61
+ )
62
+
63
+ @container.on 'ajax:before', () =>
64
+ @fadeLoading()
65
+
66
+ @container.on 'ajax:success', (e) =>
67
+ if $(e.target).is('.actions a.destroy')
68
+ # handle HEAD OK response for deletion request
69
+ editable = $(e.target).closest('.editable')
70
+ if @container.find(".editable").length == 1
71
+ @reload()
72
+ return false
73
+ else
74
+ editable.remove()
75
+
76
+ @changeItemCount(-1)
77
+ @refresh()
78
+
79
+ @fadeLoaded()
80
+ return false
81
+
82
+ @container.on 'click', 'button.cancel', (event) =>
83
+ editable = $(event.currentTarget).closest('.editable')
84
+ if(editable.length > 0)
85
+ # Cancel edit
86
+ @cancelEdit(editable)
87
+ else
88
+ # Cancel new record
89
+ @container.find('.new_item_placeholder').addClass('disabled')
90
+ @container.find('.new_item_action').removeClass('disabled')
91
+ false
92
+
93
+ @container.on 'click', '.actions a[data-confirmation]', (event) =>
94
+ # Check if we are confirming the right element
95
+ if(@confirmed != event.currentTarget)
96
+ # We need confirmation
97
+ @container.find('.actions a').popover('destroy')
98
+ $(event.currentTarget).popover(content: createPopover(event.currentTarget, $(event.currentTarget).data('confirmation')), html: true, trigger: 'manual')
99
+ $(event.currentTarget).popover('show')
100
+ false
101
+ else
102
+ # Confirmed, reset flag and go ahead with deletion
103
+ @confirmed = null
104
+ true
105
+
106
+ @container.on 'click', 'input[type=text].autoselect', (event) ->
107
+ $(this).select()
108
+
109
+ fadeLoading: =>
110
+ @content.stop(true).fadeTo(500, 0.2)
111
+ @loading.show()
112
+ @loading.stop(true).fadeTo(500, 1)
113
+
114
+ fadeLoaded: =>
115
+ @content.stop(true).fadeTo(500, 1)
116
+ @loading.stop(true).fadeTo 500, 0, () =>
117
+ @loading.hide()
118
+
119
+ @content.find('.play').each () ->
120
+ self.loadAudio($(this).data('key'), $(this).data('target'))
121
+
122
+ itemCount: =>
123
+ parseInt(@container.find('.pagination_per_page .count').html())
124
+
125
+ maxCount: =>
126
+ parseInt(@container.data('max-count'))
127
+
128
+ changeItemCount: (value) =>
129
+ @container.find('.pagination_per_page .count').html(@itemCount() + value)
130
+
131
+ cancelEdit: (editable) =>
132
+ if editable.data('smart_listing_edit_backup')
133
+ editable.html(editable.data('smart_listing_edit_backup'))
134
+ editable.removeClass('info')
135
+ editable.removeData('smart_listing_edit_backup')
136
+
137
+ # Callback called when record is added/deleted using ajax request
138
+ refresh: () =>
139
+ header = @content.find('thead')
140
+ footer = @content.find('.pagination_per_page')
141
+ no_records = @content.find('.no_records')
142
+
143
+ if @itemCount() == 0
144
+ header.hide()
145
+ footer.hide()
146
+ no_records.show()
147
+ else
148
+ header.show()
149
+ footer.show()
150
+ no_records.hide()
151
+
152
+ if @maxCount()
153
+ if @itemCount() >= @maxCount()
154
+ if @itemCount()
155
+ @container.find('.new_item_action').addClass('disabled')
156
+ @container.find('.new_item_action .btn').addClass('disabled')
157
+ else
158
+ @container.find('.new_item_action').removeClass('disabled')
159
+ @container.find('.new_item_action .btn').removeClass('disabled')
160
+
161
+ @status.each (index, status) =>
162
+ $(status).find('.smart_listing_limit').html(@maxCount() - @itemCount())
163
+ if @maxCount() - @itemCount() == 0
164
+ $(status).find('.smart_listing_limit_alert').show()
165
+ else
166
+ $(status).find('.smart_listing_limit_alert').hide()
167
+
168
+ # Trigger AJAX request to reload the list
169
+ reload: () =>
170
+ $.rails.handleRemote(@container)
171
+
172
+ params: () =>
173
+ @container.data('params')
174
+
175
+ #################################################################################################
176
+ # Methods executed by rails UJS:
177
+
178
+ new_item: (content) =>
179
+ new_item_action = @container.find('.new_item_action')
180
+ new_item_placeholder = @container.find('.new_item_placeholder').addClass('disabled')
181
+
182
+ @container.find('.editable').each (i, v) =>
183
+ @cancelEdit($(v))
184
+
185
+ new_item_action.addClass('disabled')
186
+ new_item_placeholder.removeClass('disabled')
187
+ new_item_placeholder.html(content)
188
+ new_item_placeholder.addClass('info')
189
+
190
+ @fadeLoaded()
191
+
192
+ create: (id, success, content) =>
193
+ new_item_action = @container.find('.new_item_action')
194
+ new_item_placeholder = @container.find('.new_item_placeholder')
195
+
196
+ if success
197
+ new_item_placeholder.addClass('disabled')
198
+ new_item_action.removeClass('disabled')
199
+
200
+ new_item = $('<tr />').addClass('editable')
201
+ new_item.attr('data-id', id)
202
+ new_item.html(content)
203
+ new_item_placeholder.before(new_item)
204
+
205
+ @changeItemCount(1)
206
+ @refresh()
207
+ else
208
+ new_item_placeholder.html(content)
209
+
210
+ @fadeLoaded()
211
+
212
+ edit: (id, content) =>
213
+ @container.find('.editable').each (i, v) =>
214
+ @cancelEdit($(v))
215
+ @container.find('.new_item_placeholder').addClass('disabled')
216
+ @container.find('.new_item_action').removeClass('disabled')
217
+
218
+ editable = @container.find(".editable[data-id=#{id}]")
219
+ editable.data('smart_listing_edit_backup', editable.html())
220
+ editable.html(content)
221
+ editable.addClass('info')
222
+
223
+ @fadeLoaded()
224
+
225
+ update: (id, success, content) =>
226
+ editable = @container.find(".editable[data-id=#{id}]")
227
+ if success
228
+ editable.removeClass('info')
229
+ editable.removeData('smart_listing_edit_backup')
230
+ editable.html(content)
231
+
232
+ @refresh()
233
+ else
234
+ editable.html(content)
235
+
236
+ @fadeLoaded()
237
+
238
+ destroy: (id, destroyed) =>
239
+ # No need to do anything here, already handled by ajax:success handler
240
+
241
+ update_list: (content, data) =>
242
+ $.each data, (key, value) =>
243
+ @container.data(key, value)
244
+
245
+ @content.html(content)
246
+
247
+ @refresh()
248
+ @fadeLoaded()
249
+
250
+ $.fn.smart_listing = () ->
251
+ map = $(this).map () ->
252
+ if !$(this).data('smart-list')
253
+ $(this).data('smart-list', new SmartListing($(this)))
254
+ $(this).data('smart-list')
255
+ if map.length == 1
256
+ map[0]
257
+ else
258
+ map
259
+
260
+ $.fn.handleSmartListingControls = () ->
261
+ $(this).each () ->
262
+ controls = $(this)
263
+ smart_listing = $("##{controls.data('smart-list')}")
264
+
265
+ controls.submit ->
266
+ # Merges smart list params with the form action url before submitting.
267
+ # This preserves smart list settings (page, sorting etc.).
268
+
269
+ prms = $.extend({}, smart_listing.smart_listing().params())
270
+ if $(this).data('reset')
271
+ prms[$(this).data('reset')] = null
272
+
273
+ if $.rails.href(smart_listing)
274
+ # If smart list has different target url than current one
275
+ controls.attr('action', $.rails.href(smart_listing) + "?" + jQuery.param(prms))
276
+ else
277
+ controls.attr('action', "?" + jQuery.param(prms))
278
+
279
+ smart_listing.trigger('ajax:before')
280
+ true
281
+
282
+ controls.find('input, select').change () ->
283
+ unless $(this).data('observed') # do not submit controls form when changed field is observed (observing submits form by itself)
284
+ controls.submit()
285
+
286
+ $.fn.handleSmartListingFilter = () ->
287
+ filter = $(this)
288
+ form = filter.closest('form')
289
+ button = filter.find('button')
290
+ icon = filter.find('i')
291
+ field = filter.find('input')
292
+
293
+ field.observeField(
294
+ onFilled: ->
295
+ console.log('onfilled')
296
+ icon.removeClass('icon-search')
297
+ icon.addClass('icon-remove')
298
+ button.removeClass('disabled')
299
+ onEmpty: ->
300
+ console.log('onempty')
301
+ icon.addClass('icon-search')
302
+ icon.removeClass('icon-remove')
303
+ button.addClass('disabled')
304
+ onChange: ->
305
+ console.log('onchange')
306
+ form.submit()
307
+ )
308
+
309
+ button.click ->
310
+ if field.val().length > 0
311
+ field.val('')
312
+ field.trigger('keydown')
313
+
314
+ $ ->
315
+ $('.smart_listing').smart_listing()
316
+ $('.smart_listing_controls').handleSmartListingControls()
317
+ $('.smart_listing_controls .filter').handleSmartListingFilter()
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,5 @@
1
+ require 'smart_listing/smart_listing_helper'
2
+ module SmartListing
3
+ module ApplicationHelper
4
+ end
5
+ end
@@ -0,0 +1,259 @@
1
+ module SmartListing
2
+ module Helper
3
+ module ControllerExtensions
4
+ def smart_listing_create name, collection, options = {}
5
+ name = name.to_sym
6
+
7
+ list = SmartListing::Base.new(name, collection, options)
8
+ list.setup(params, cookies)
9
+
10
+ @smart_listings ||= {}
11
+ @smart_listings[name] = list
12
+
13
+ list.collection
14
+ end
15
+
16
+ def smart_listing name
17
+ @smart_listings[name.to_sym]
18
+ end
19
+ end
20
+
21
+ class Builder
22
+ # Params that should not be visible in pagination links (pages, per-page, sorting, etc.)
23
+ UNSAFE_PARAMS = {:authenticity_token => nil, :utf8 => nil}
24
+
25
+ class_attribute :smart_listing_helpers
26
+
27
+ def initialize(smart_listing_name, smart_listing, template, options, proc)
28
+ @smart_listing_name, @smart_listing, @template, @options, @proc = smart_listing_name, smart_listing, template, options, proc
29
+ end
30
+
31
+ def paginate options = {}
32
+ if @smart_listing.collection.respond_to? :current_page
33
+ @template.paginate @smart_listing.collection, :remote => true, :param_name => @smart_listing.param_names[:page], :params => UNSAFE_PARAMS
34
+ end
35
+ end
36
+
37
+ def collection
38
+ @smart_listing.collection
39
+ end
40
+
41
+ def pagination_per_page_links options = {}
42
+ @template.content_tag(:div, :class => "pagination_per_page #{'disabled' if empty?}") do
43
+ if @smart_listing.count > SmartListing::Base::PAGE_SIZES.first
44
+ @template.concat(@template.t('views.pagination.per_page'))
45
+ per_page_sizes = SmartListing::Base::PAGE_SIZES.clone
46
+ per_page_sizes.push(0) if @smart_listing.unlimited_per_page?
47
+ per_page_sizes.each do |p|
48
+ name = p == 0 ? @template.t('views.pagination.unlimited') : p
49
+ if @smart_listing.per_page.to_i != p
50
+ @template.concat(@template.link_to(name, sanitize_params(@template.params.merge(@smart_listing.param_names[:per_page] => p, @smart_listing.param_names[:page] => 1)), :remote => true))
51
+ else
52
+ @template.concat(@template.content_tag(:span, name))
53
+ end
54
+ break if p > @smart_listing.count
55
+ end
56
+ @template.concat ' | '
57
+ end if @smart_listing.options[:paginate]
58
+ @template.concat(@template.t('views.pagination.total'))
59
+ @template.concat(@template.content_tag(:span, @smart_listing.count, :class => "count"))
60
+ end
61
+ end
62
+
63
+ def sortable title, attribute, options = {}
64
+ extra = options.delete(:extra)
65
+
66
+ sort_params = {
67
+ @smart_listing.param_names[:sort_attr] => attribute,
68
+ @smart_listing.param_names[:sort_order] => (@smart_listing.sort_order == "asc") ? "desc" : "asc",
69
+ @smart_listing.param_names[:sort_extra] => extra
70
+ }
71
+
72
+ @template.link_to(sanitize_params(@template.params.merge(sort_params)), :class => "sortable", :data => {:attr => attribute}, :remote => true) do
73
+ @template.concat(title)
74
+ if @smart_listing.sort_attr == attribute && (!@smart_listing.sort_extra || @smart_listing.sort_extra == extra.to_s)
75
+ @template.concat(@template.content_tag(:span, "", :class => (@smart_listing.sort_order == "asc" ? "glyphicon glyphicon-chevron-up" : "glyphicon glyphicon-chevron-down")))
76
+ else
77
+ @template.concat(@template.content_tag(:span, "", :class => "glyphicon glyphicon-resize-vertical"))
78
+ end
79
+ end
80
+ end
81
+
82
+ def update options = {}
83
+ part = options.delete(:partial) || @smart_listing.partial || @smart_listing_name
84
+
85
+ @template.render(:partial => 'smart_listing/update_list', :locals => {:name => @smart_listing_name, :part => part, :smart_listing => self})
86
+ end
87
+
88
+ # Renders the main partial (whole list)
89
+ def render_list
90
+ if @smart_listing.partial
91
+ @template.render :partial => @smart_listing.partial, :locals => {:smart_listing => self}
92
+ end
93
+ end
94
+
95
+ # Basic render block wrapper that adds smart_listing reference to local variables
96
+ def render options = {}, locals = {}, &block
97
+ if locals.empty?
98
+ options[:locals] ||= {}
99
+ options[:locals].merge!(:smart_listing => self)
100
+ else
101
+ locals.merge!({:smart_listing => self})
102
+ end
103
+ @template.render options, locals, &block
104
+ end
105
+
106
+ # Add new item button & placeholder to list
107
+ def item_new options = {}
108
+ @template.concat(@template.content_tag(:tr, '', :class => "info new_item_placeholder disabled"))
109
+ @template.concat(@template.content_tag(:tr, :class => "info new_item_action #{'disabled' if !empty? && max_count?}") do
110
+ @template.concat(@template.content_tag(:td, :colspan => options.delete(:colspan)) do
111
+ @template.concat(@template.content_tag(:p, :class => "no_records pull-left #{'disabled' unless empty?}") do
112
+ @template.concat(options.delete(:no_items_text))
113
+ end)
114
+ @template.concat(@template.link_to(options.delete(:link), :remote => true, :class => "btn pull-right #{'disabled' if max_count?}") do
115
+ @template.concat(@template.content_tag(:i, '', :class => "glyphicon glyphicon-plus"))
116
+ @template.concat(" ")
117
+ @template.concat(options.delete(:text))
118
+ end)
119
+ end)
120
+ end)
121
+ nil
122
+ end
123
+
124
+ # Check if smart list is empty
125
+ def empty?
126
+ @smart_listing.count == 0
127
+ end
128
+
129
+ # Check if smart list reached its item max count
130
+ def max_count?
131
+ return false if @smart_listing.max_count.nil?
132
+ @smart_listing.count >= @smart_listing.max_count
133
+ end
134
+
135
+ private
136
+
137
+ def sanitize_params params
138
+ params.merge(UNSAFE_PARAMS)
139
+ end
140
+ end
141
+
142
+ # Outputs smart list container
143
+ def smart_listing_for name, *args, &block
144
+ raise ArgumentError, "Missing block" unless block_given?
145
+ name = name.to_sym
146
+ options = args.extract_options!
147
+ bare = options.delete(:bare)
148
+
149
+ builder = Builder.new(name, @smart_listings[name], self, options, block)
150
+
151
+ output =""
152
+
153
+ data = {}
154
+ data['max-count'] = @smart_listings[name].max_count if @smart_listings[name].max_count && @smart_listings[name].max_count > 0
155
+ data['href'] = @smart_listings[name].href if @smart_listings[name].href
156
+
157
+ if bare
158
+ output = capture(builder, &block)
159
+ else
160
+ output = content_tag(:div, :class => "smart_listing", :id => name, :data => data) do
161
+ concat(content_tag(:div, "", :class => "loading"))
162
+ concat(content_tag(:div, :class => "content") do
163
+ concat(capture(builder, &block))
164
+ end)
165
+ end
166
+ end
167
+
168
+ output
169
+ end
170
+
171
+ # Render item action buttons (ie. edit, destroy and custom ones)
172
+ def smart_listing_item_actions actions = []
173
+ content_tag(:span) do
174
+ actions.each do |action|
175
+ next unless action.is_a?(Hash)
176
+
177
+ if action.has_key?(:if)
178
+ unless action[:if]
179
+ concat(content_tag(:i, '', :class => "glyphicon glyphicon-remove-circle"))
180
+ next
181
+ end
182
+ end
183
+
184
+ case action.delete(:name).to_sym
185
+ when :edit
186
+ url = action.delete(:url)
187
+ html_options = {
188
+ :remote => true,
189
+ :class => "edit",
190
+ :title => t("smart_listing.actions.edit")
191
+ }.merge(action)
192
+
193
+ concat(link_to(url, html_options) do
194
+ concat(content_tag(:i, '', :class => "glyphicon glyphicon-pencil"))
195
+ end)
196
+ when :destroy
197
+ url = action.delete(:url)
198
+ icon = action.delete(:icon) || "glyphicon glyphicon-trash"
199
+ html_options = {
200
+ :remote => true,
201
+ :class => "destroy",
202
+ :method => :delete,
203
+ :title => t("smart_listing.actions.destroy"),
204
+ :data => {:confirmation => action.delete(:confirmation) || t("smart_listing.msgs.destroy_confirmation")},
205
+ }.merge(action)
206
+
207
+ concat(link_to(url, html_options) do
208
+ concat(content_tag(:i, '', :class => icon))
209
+ end)
210
+ when :custom
211
+ url = action.delete(:url)
212
+ icon = action.delete(:icon)
213
+ html_options = action
214
+
215
+ concat(link_to(url, html_options) do
216
+ concat(content_tag(:i, '', :class => icon))
217
+ end)
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ def smart_listing_limit_left name
224
+ name = name.to_sym
225
+ smart_listing = @smart_listings[name]
226
+
227
+ smart_listing.max_count - smart_listing.count
228
+ end
229
+
230
+ #################################################################################################
231
+ # JS helpers:
232
+
233
+ # Updates the smart list
234
+ def smart_listing_update name
235
+ name = name.to_sym
236
+ smart_listing = @smart_listings[name]
237
+ builder = Builder.new(name, smart_listing, self, {}, nil)
238
+ render(:partial => 'smart_listing/update_list', :locals => {
239
+ :name => smart_listing.name,
240
+ :part => smart_listing.partial,
241
+ :smart_listing => builder,
242
+ :smart_listing_data => {
243
+ :params => smart_listing.all_params,
244
+ 'max-count' => smart_listing.max_count,
245
+ }
246
+ })
247
+ end
248
+
249
+ # Renders single item (i.e for create, update actions)
250
+ def smart_listing_item name, item_action, object = nil, partial = nil, options = {}
251
+ name = name.to_sym
252
+ type = object.class.name.downcase.to_sym if object
253
+ id = object.id if object
254
+ object_key = options.delete(:object_key) || :object
255
+
256
+ render(:partial => "smart_listing/item/#{item_action.to_s}", :locals => {:name => name, :id => id, :object_key => object_key, :object => object, :part => partial})
257
+ end
258
+ end
259
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ SmartListing::Engine.routes.draw do
2
+ end
@@ -0,0 +1,5 @@
1
+ module SmartListing
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace SmartListing
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module SmartListing
2
+ VERSION = "0.9.0"
3
+ end
@@ -0,0 +1,116 @@
1
+ require "smart_listing/engine"
2
+ require "kaminari"
3
+ module SmartListing
4
+ class Base
5
+ if Rails.env.development?
6
+ PAGE_SIZES = [3, 10, 20, 50, 100]
7
+ else
8
+ PAGE_SIZES = [10, 20, 50, 100]
9
+ end
10
+
11
+ attr_reader :name, :collection, :options, :per_page, :page, :sort_attr, :sort_order, :sort_extra, :partial, :count
12
+
13
+ def initialize name, collection, options = {}
14
+ @name = name
15
+
16
+ @options = {
17
+ :param_names => { # param names
18
+ :page => "#{@name}_page".to_sym,
19
+ :per_page => "#{@name}_per_page".to_sym,
20
+ :sort_attr => "#{@name}_sort_attr".to_sym,
21
+ :sort_order => "#{@name}_sort_order".to_sym,
22
+ :sort_extra => "#{@name}_sort_extra".to_sym,
23
+ },
24
+ :partial => @name, # smart list partial name
25
+ :array => false, # controls whether smart list should be using arrays or AR collections
26
+ :max_count => nil, # limit number of rows
27
+ :unlimited_per_page => false, # allow infinite page size
28
+ :sort => true, # allow sorting
29
+ :paginate => true, # allow pagination
30
+ :href => nil, # set smart list target url (in case when different than current url)
31
+ :default_sort_attr => nil, # default sort by
32
+ :memorize_per_page => false,
33
+ }.merge!(options)
34
+
35
+ if @options[:array]
36
+ @collection = collection.to_a
37
+ else
38
+ @collection = collection
39
+ end
40
+ end
41
+
42
+ def setup params, cookies
43
+ @page = params[param_names[:page]]
44
+ @per_page = !params[param_names[:per_page]] || params[param_names[:per_page]].empty? ? (@options[:memorize_per_page] && cookies[param_names[:per_page]].to_i > 0 ? cookies[param_names[:per_page]].to_i : PAGE_SIZES.first) : params[param_names[:per_page]].to_i
45
+ @sort_attr = params[param_names[:sort_attr]] || @options[:default_sort_attr]
46
+ @sort_order = ["asc", "desc"].include?(params[param_names[:sort_order]]) ? params[param_names[:sort_order]] : "desc"
47
+ @sort_extra = params[param_names[:sort_extra]]
48
+
49
+ cookies[param_names[:per_page]] = @per_page if @options[:memorize_per_page]
50
+
51
+ @count = @collection.size
52
+
53
+ if @options[:array]
54
+ @collection = @collection.sort do |x, y|
55
+ xval = x
56
+ yval = y
57
+ @sort_attr.split(".").each do |m|
58
+ xval = xval.try(m)
59
+ yval = yval.try(m)
60
+ end
61
+ xval = xval.upcase if xval.is_a?(String)
62
+ yval = yval.upcase if yval.is_a?(String)
63
+
64
+ if xval.nil? || yval.nil?
65
+ xval.nil? ? 1 : -1
66
+ else
67
+ if @sort_order == "asc"
68
+ (xval <=> yval) || (xval && !yval ? 1 : -1)
69
+ else
70
+ (yval <=> xval) || (yval && !xval ? 1 : -1)
71
+ end
72
+ end
73
+ end if @options[:sort] && @sort_attr && !@sort_attr.empty?
74
+ if @options[:paginate] && @per_page > 0
75
+ @collection = ::Kaminari.paginate_array(@collection).page(@page).per(@per_page)
76
+ if @collection.length == 0
77
+ @collection = @collection.page(@collection.num_pages)
78
+ end
79
+ end
80
+ else
81
+ @collection = @collection.order("#{@sort_attr} #{@sort_order}") if @options[:sort] && @sort_attr && !@sort_attr.empty? && @sort_order
82
+ if @options[:paginate] && @per_page > 0
83
+ @collection = @collection.page(@page).per(@per_page)
84
+ end
85
+ end
86
+ end
87
+
88
+ def partial
89
+ @options[:partial]
90
+ end
91
+
92
+ def param_names
93
+ @options[:param_names]
94
+ end
95
+
96
+ def unlimited_per_page?
97
+ !!@options[:unlimited_per_page]
98
+ end
99
+
100
+ def max_count
101
+ @options[:max_count]
102
+ end
103
+
104
+ def href
105
+ @options[:href]
106
+ end
107
+
108
+ def all_params
109
+ ap = {}
110
+ @options[:param_names].each do |k, v|
111
+ ap[v] = self.send(k)
112
+ end
113
+ ap
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :smart_list do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smart_listing
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Sology
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: coffee-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: kaminari
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Description of SmartList.
70
+ email:
71
+ - contact@sology.eu
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - app/assets/stylesheets/smart_listing/application.css
77
+ - app/assets/javascripts/smart_listing/smart_listing.coffee
78
+ - app/assets/javascripts/smart_listing/application.js
79
+ - app/helpers/smart_listing/application_helper.rb
80
+ - app/helpers/smart_listing/helper.rb
81
+ - config/routes.rb
82
+ - lib/tasks/smart_list_tasks.rake
83
+ - lib/smart_listing/engine.rb
84
+ - lib/smart_listing/version.rb
85
+ - lib/smart_listing.rb
86
+ - LICENSE
87
+ - Rakefile
88
+ - README.md
89
+ homepage: http://www.sology.eu
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.1.10
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Summary of SmartList.
113
+ test_files: []