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,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 -%>