peentar-smart_listing 1.1.3

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.
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,5 @@
1
+ require 'smart_listing/helper'
2
+ module SmartListing
3
+ module ApplicationHelper
4
+ end
5
+ end
@@ -0,0 +1,366 @@
1
+ module SmartListing
2
+ module Helper
3
+ module ControllerExtensions
4
+ # Creates new smart listing
5
+ #
6
+ # Possible calls:
7
+ # smart_listing_create name, collection, options = {}
8
+ # smart_listing_create options = {}
9
+ def smart_listing_create *args
10
+ options = args.extract_options!
11
+ name = (args[0] || options[:name] || controller_name).to_sym
12
+ collection = args[1] || options[:collection] || smart_listing_collection
13
+
14
+ if (view_context.respond_to?(:smart_listing_config_profile))
15
+ options = {:config_profile => view_context.smart_listing_config_profile}.merge(options)
16
+ end
17
+
18
+ list = SmartListing::Base.new(name, collection, options)
19
+ list.setup(params, cookies)
20
+
21
+ @smart_listings ||= {}
22
+ @smart_listings[name] = list
23
+
24
+ list.collection
25
+ end
26
+
27
+ def smart_listing name
28
+ @smart_listings[name.to_sym]
29
+ end
30
+
31
+ def _prefixes
32
+ super << 'smart_listing'
33
+ end
34
+ end
35
+
36
+ class Builder
37
+ # Params that should not be visible in pagination links (pages, per-page, sorting, etc.)
38
+ UNSAFE_PARAMS = {:authenticity_token => nil, :utf8 => nil}
39
+
40
+ class_attribute :smart_listing_helpers
41
+
42
+ def initialize(smart_listing_name, smart_listing, template, options, proc)
43
+ @smart_listing_name, @smart_listing, @template, @options, @proc = smart_listing_name, smart_listing, template, options, proc
44
+ end
45
+
46
+ def name
47
+ @smart_listing_name
48
+ end
49
+
50
+ def paginate options = {}
51
+ if @smart_listing.collection.respond_to? :current_page
52
+ @template.paginate @smart_listing.collection, {:remote => @smart_listing.remote?, :param_name => @smart_listing.param_name(:page), :params => UNSAFE_PARAMS}.merge(@smart_listing.kaminari_options)
53
+ end
54
+ end
55
+
56
+ def collection
57
+ @smart_listing.collection
58
+ end
59
+
60
+ # Check if smart list is empty
61
+ def empty?
62
+ @smart_listing.count == 0
63
+ end
64
+
65
+ def pagination_per_page_links options = {}
66
+ container_classes = [@template.smart_listing_config.classes(:pagination_per_page)]
67
+ container_classes << @template.smart_listing_config.classes(:hidden) if empty?
68
+
69
+ per_page_sizes = @smart_listing.page_sizes.clone
70
+ per_page_sizes.push(0) if @smart_listing.unlimited_per_page?
71
+
72
+ locals = {
73
+ :container_classes => container_classes,
74
+ :per_page_sizes => per_page_sizes,
75
+ }
76
+
77
+ @template.render(:partial => 'smart_listing/pagination_per_page_links', :locals => default_locals.merge(locals))
78
+ end
79
+
80
+ def pagination_per_page_link page
81
+ if @smart_listing.per_page.to_i != page
82
+ url = @template.url_for(sanitize_params(@template.params.merge(@smart_listing.all_params(:per_page => page, :page => 1))))
83
+ end
84
+
85
+ locals = {
86
+ :page => page,
87
+ :url => url,
88
+ }
89
+
90
+ @template.render(:partial => 'smart_listing/pagination_per_page_link', :locals => default_locals.merge(locals))
91
+ end
92
+
93
+ def sortable title, attribute, options = {}
94
+ dirs = options[:sort_dirs] || @smart_listing.sort_dirs || [nil, "asc", "desc"]
95
+
96
+ next_index = dirs.index(@smart_listing.sort_order(attribute)).nil? ? 0 : (dirs.index(@smart_listing.sort_order(attribute)) + 1) % dirs.length
97
+
98
+ sort_params = {
99
+ attribute => dirs[next_index]
100
+ }
101
+
102
+ locals = {
103
+ :order => @smart_listing.sort_order(attribute),
104
+ :url => @template.url_for(sanitize_params(@template.params.merge(@smart_listing.all_params(:sort => sort_params)))),
105
+ :container_classes => [@template.smart_listing_config.classes(:sortable)],
106
+ :attribute => attribute,
107
+ :title => title
108
+ }
109
+
110
+ @template.render(:partial => 'smart_listing/sortable', :locals => default_locals.merge(locals))
111
+ end
112
+
113
+ def update options = {}
114
+ part = options.delete(:partial) || @smart_listing.partial || @smart_listing_name
115
+
116
+ @template.render(:partial => 'smart_listing/update_list', :locals => {:name => @smart_listing_name, :part => part, :smart_listing => self})
117
+ end
118
+
119
+ # Renders the main partial (whole list)
120
+ def render_list locals = {}
121
+ if @smart_listing.partial
122
+ @template.render :partial => @smart_listing.partial, :locals => {:smart_listing => self}.merge(locals || {})
123
+ end
124
+ end
125
+
126
+ # Basic render block wrapper that adds smart_listing reference to local variables
127
+ def render options = {}, locals = {}, &block
128
+ if locals.empty?
129
+ options[:locals] ||= {}
130
+ options[:locals].merge!(:smart_listing => self)
131
+ else
132
+ locals.merge!({:smart_listing => self})
133
+ end
134
+
135
+ @template.render options, locals, &block
136
+ end
137
+
138
+ # Add new item button & placeholder to list
139
+ def item_new options = {}, &block
140
+ no_records_classes = [@template.smart_listing_config.classes(:no_records)]
141
+ no_records_classes << @template.smart_listing_config.classes(:hidden) unless empty?
142
+ new_item_button_classes = []
143
+ new_item_button_classes << @template.smart_listing_config.classes(:hidden) if max_count?
144
+
145
+ locals = {
146
+ :colspan => options.delete(:colspan),
147
+ :no_items_classes => no_records_classes,
148
+ :no_items_text => options.delete(:no_items_text) || @template.t("smart_listing.msgs.no_items"),
149
+ :new_item_button_url => options.delete(:link),
150
+ :new_item_button_classes => new_item_button_classes,
151
+ :new_item_button_text => options.delete(:text) || @template.t("smart_listing.actions.new"),
152
+ :new_item_autoshow => block_given?,
153
+ :new_item_content => nil,
154
+ }
155
+
156
+ unless block_given?
157
+ locals[:placeholder_classes] = [@template.smart_listing_config.classes(:new_item_placeholder), @template.smart_listing_config.classes(:hidden)]
158
+ locals[:new_item_action_classes] = [@template.smart_listing_config.classes(:new_item_action)]
159
+ locals[:new_item_action_classes] << @template.smart_listing_config.classes(:hidden) if !empty? && max_count?
160
+
161
+ @template.render(:partial => 'smart_listing/item_new', :locals => default_locals.merge(locals))
162
+ else
163
+ locals[:placeholder_classes] = [@template.smart_listing_config.classes(:new_item_placeholder)]
164
+ locals[:placeholder_classes] << @template.smart_listing_config.classes(:hidden) if !empty? && max_count?
165
+ locals[:new_item_action_classes] = [@template.smart_listing_config.classes(:new_item_action), @template.smart_listing_config.classes(:hidden)]
166
+
167
+ locals[:new_item_content] = @template.capture(&block)
168
+ @template.render(:partial => 'smart_listing/item_new', :locals => default_locals.merge(locals))
169
+ end
170
+ end
171
+
172
+ def count
173
+ @smart_listing.count
174
+ end
175
+
176
+ # Check if smart list reached its item max count
177
+ def max_count?
178
+ return false if @smart_listing.max_count.nil?
179
+ @smart_listing.count >= @smart_listing.max_count
180
+ end
181
+
182
+ private
183
+
184
+ def sanitize_params params
185
+ params.merge(UNSAFE_PARAMS)
186
+ end
187
+
188
+ def default_locals
189
+ {:smart_listing => @smart_listing, :builder => self}
190
+ end
191
+ end
192
+
193
+ def smart_listing_config_profile
194
+ defined?(super) ? super : :default
195
+ end
196
+
197
+ def smart_listing_config
198
+ SmartListing.config(smart_listing_config_profile)
199
+ end
200
+
201
+ # Outputs smart list container
202
+ def smart_listing_for name, *args, &block
203
+ raise ArgumentError, "Missing block" unless block_given?
204
+ name = name.to_sym
205
+ options = args.extract_options!
206
+ bare = options.delete(:bare)
207
+
208
+ builder = Builder.new(name, @smart_listings[name], self, options, block)
209
+
210
+ output = ""
211
+
212
+ data = {}
213
+ data[smart_listing_config.data_attributes(:max_count)] = @smart_listings[name].max_count if @smart_listings[name].max_count && @smart_listings[name].max_count > 0
214
+ data[smart_listing_config.data_attributes(:item_count)] = @smart_listings[name].count
215
+ data[smart_listing_config.data_attributes(:href)] = @smart_listings[name].href if @smart_listings[name].href
216
+ data[smart_listing_config.data_attributes(:callback_href)] = @smart_listings[name].callback_href if @smart_listings[name].callback_href
217
+ data.merge!(options[:data]) if options[:data]
218
+
219
+ if bare
220
+ output = capture(builder, &block)
221
+ else
222
+ output = content_tag(:div, :class => smart_listing_config.classes(:main), :id => name, :data => data) do
223
+ concat(content_tag(:div, "", :class => smart_listing_config.classes(:loading)))
224
+ concat(content_tag(:div, :class => smart_listing_config.classes(:content)) do
225
+ concat(capture(builder, &block))
226
+ end)
227
+ end
228
+ end
229
+
230
+ output
231
+ end
232
+
233
+ def smart_listing_render name = controller_name, *args
234
+ options = args.extract_options!
235
+ smart_listing_for(name, *args) do |smart_listing|
236
+ concat(smart_listing.render_list(options[:locals]))
237
+ end
238
+ end
239
+
240
+ def smart_listing_controls_for name, *args, &block
241
+ smart_listing = @smart_listings.try(:[], name)
242
+
243
+ classes = [smart_listing_config.classes(:controls), args.first.try(:[], :class)]
244
+
245
+ form_tag(smart_listing.try(:href) || {}, :remote => smart_listing.try(:remote?) || true, :method => :get, :class => classes, :data => {smart_listing_config.data_attributes(:main) => name}) do
246
+ concat(content_tag(:div, :style => "margin:0;padding:0;display:inline") do
247
+ concat(hidden_field_tag("#{smart_listing.try(:base_param)}[_]", 1, :id => nil)) # this forces smart_listing_update to refresh the list
248
+ end)
249
+ concat(capture(&block))
250
+ end
251
+ end
252
+
253
+ # Render item action buttons (ie. edit, destroy and custom ones)
254
+ def smart_listing_item_actions actions = []
255
+ content_tag(:span) do
256
+ actions.each do |action|
257
+ next unless action.is_a?(Hash)
258
+
259
+ locals = {
260
+ :action_if => action.has_key?(:if) ? action[:if] : true,
261
+ :url => action.delete(:url),
262
+ :icon => action.delete(:icon),
263
+ :title => action.delete(:title),
264
+ }
265
+
266
+ template = nil
267
+ action_name = action[:name].to_sym
268
+
269
+ case action_name
270
+ when :show
271
+ locals[:icon] ||= smart_listing_config.classes(:icon_show)
272
+ template = 'action_show'
273
+ when :edit
274
+ locals[:icon] ||= smart_listing_config.classes(:icon_edit)
275
+ template = 'action_edit'
276
+ when :destroy
277
+ locals[:icon] ||= smart_listing_config.classes(:icon_trash)
278
+ locals.merge!(
279
+ :confirmation => action.delete(:confirmation),
280
+ )
281
+ template = 'action_delete'
282
+ when :custom
283
+ locals.merge!(
284
+ :html_options => action,
285
+ )
286
+ template = 'action_custom'
287
+ end
288
+
289
+ locals[:icon] = [locals[:icon], smart_listing_config.classes(:muted)] if !locals[:action_if]
290
+
291
+ if template
292
+ concat(render(:partial => "smart_listing/#{template}", :locals => locals))
293
+ else
294
+ concat(render(:partial => "smart_listing/action_#{action_name}", :locals => {:action => action}))
295
+ end
296
+ end
297
+ end
298
+ end
299
+
300
+ def smart_listing_limit_left name
301
+ name = name.to_sym
302
+ smart_listing = @smart_listings[name]
303
+
304
+ smart_listing.max_count - smart_listing.count
305
+ end
306
+
307
+ #################################################################################################
308
+ # JS helpers:
309
+
310
+ # Updates smart listing
311
+ #
312
+ # Posible calls:
313
+ # smart_listing_update name, options = {}
314
+ # smart_listing_update options = {}
315
+ def smart_listing_update *args
316
+ options = args.extract_options!
317
+ name = (args[0] || options[:name] || controller_name).to_sym
318
+ smart_listing = @smart_listings[name]
319
+
320
+ # don't update list if params are missing (prevents interfering with other lists)
321
+ if params.keys.select{|k| k.include?("smart_listing")}.any? && !params[smart_listing.base_param]
322
+ return unless options[:force]
323
+ end
324
+
325
+ builder = Builder.new(name, smart_listing, self, {}, nil)
326
+ render(:partial => 'smart_listing/update_list', :locals => {
327
+ :name => smart_listing.name,
328
+ :part => smart_listing.partial,
329
+ :smart_listing => builder,
330
+ :smart_listing_data => {
331
+ smart_listing_config.data_attributes(:params) => smart_listing.all_params,
332
+ smart_listing_config.data_attributes(:max_count) => smart_listing.max_count,
333
+ smart_listing_config.data_attributes(:item_count) => smart_listing.count,
334
+ },
335
+ :locals => options[:locals] || {}
336
+ })
337
+ end
338
+
339
+ # Renders single item (i.e for create, update actions)
340
+ #
341
+ # Possible calls:
342
+ # smart_listing_item name, item_action, object = nil, partial = nil, options = {}
343
+ # smart_listing_item item_action, object = nil, partial = nil, options = {}
344
+ def smart_listing_item *args
345
+ options = args.extract_options!
346
+ if [:create, :create_continue, :destroy, :edit, :new, :remove, :update].include?(args[1])
347
+ name = args[0]
348
+ item_action = args[1]
349
+ object = args[2]
350
+ partial = args[3]
351
+ else
352
+ name = (options[:name] || controller_name).to_sym
353
+ item_action = args[0]
354
+ object = args[1]
355
+ partial = args[2]
356
+ end
357
+ type = object.class.name.downcase.to_sym if object
358
+ id = options[:id] || object.try(:id)
359
+ valid = options[:valid] if options.has_key?(:valid)
360
+ object_key = options.delete(:object_key) || :object
361
+ new = options.delete(:new)
362
+
363
+ render(:partial => "smart_listing/item/#{item_action.to_s}", :locals => {:name => name, :id => id, :valid => valid, :object_key => object_key, :object => object, :part => partial, :new => new})
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,13 @@
1
+ <%# Link to the "First" page
2
+ - available local variables
3
+ url: url to the first page
4
+ current_page: a page object for the currently displayed page
5
+ total_pages: total number of pages
6
+ per_page: number of items to fetch per page
7
+ remote: data-remote
8
+ -%>
9
+ <% unless current_page.first? %>
10
+ <li class="first">
11
+ <%= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, :remote => remote %>
12
+ </li>
13
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <%# Non-link tag that stands for skipped pages...
2
+ - available local variables
3
+ current_page: a page object for the currently displayed page
4
+ total_pages: total number of pages
5
+ per_page: number of items to fetch per page
6
+ remote: data-remote
7
+ -%>
8
+ <li class="page gap disabled"><a href="#" onclick="return false;"><%= raw(t 'views.pagination.truncate') %></a></li>
@@ -0,0 +1,13 @@
1
+ <%# Link to the "Last" page
2
+ - available local variables
3
+ url: url to the last page
4
+ current_page: a page object for the currently displayed page
5
+ total_pages: total number of pages
6
+ per_page: number of items to fetch per page
7
+ remote: data-remote
8
+ -%>
9
+ <% unless current_page.last? %>
10
+ <li class="last next"><%# "next" class present for border styling in twitter bootstrap %>
11
+ <%= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {:remote => remote} %>
12
+ </li>
13
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <%# Link to the "Next" page
2
+ - available local variables
3
+ url: url to the next page
4
+ current_page: a page object for the currently displayed page
5
+ total_pages: total number of pages
6
+ per_page: number of items to fetch per page
7
+ remote: data-remote
8
+ -%>
9
+ <% unless current_page.last? %>
10
+ <li class="next_page">
11
+ <%= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, :rel => 'next', :remote => remote %>
12
+ </li>
13
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <%# Link showing page number
2
+ - available local variables
3
+ page: a page object for "this" page
4
+ url: url to this page
5
+ current_page: a page object for the currently displayed page
6
+ total_pages: total number of pages
7
+ per_page: number of items to fetch per page
8
+ remote: data-remote
9
+ -%>
10
+ <li class="page<%= ' active' if page.current? %>">
11
+ <%= link_to page, url, opts = {:remote => remote, :rel => page.next? ? 'next' : page.prev? ? 'prev' : nil} %>
12
+ </li>
@@ -0,0 +1,25 @@
1
+ <%# The container tag
2
+ - available local variables
3
+ current_page: a page object for the currently displayed page
4
+ total_pages: total number of pages
5
+ per_page: number of items to fetch per page
6
+ remote: data-remote
7
+ paginator: the paginator that renders the pagination tags inside
8
+ -%>
9
+ <%= paginator.render do -%>
10
+ <div class="<%= smart_listing_config.classes(:pagination_wrapper) %>">
11
+ <ul class="<%= smart_listing_config.classes(:pagination_container) %>">
12
+ <%= first_page_tag unless current_page.first? %>
13
+ <%= prev_page_tag unless current_page.first? %>
14
+ <% each_page do |page| -%>
15
+ <% if page.left_outer? || page.right_outer? || page.inside_window? -%>
16
+ <%= page_tag page %>
17
+ <% elsif !page.was_truncated? -%>
18
+ <%= gap_tag %>
19
+ <% end -%>
20
+ <% end -%>
21
+ <%= next_page_tag unless current_page.last? %>
22
+ <%= last_page_tag unless current_page.last? %>
23
+ </ul>
24
+ </div>
25
+ <% end -%>