smart_listing 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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: []