magic_grid 0.11.1 → 0.12.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.
@@ -1,256 +1,12 @@
1
1
  require 'magic_grid/definition'
2
-
3
- if Module.const_defined? :WillPaginate
4
- require 'will_paginate/array'
5
- end
6
-
7
- def MagicGrid::compact_hash(hash)
8
- hash.select {|_,v| v }
9
- end
2
+ require 'magic_grid/html_grid'
10
3
 
11
4
  module MagicGrid
12
5
  module Helpers
13
- def normalize_magic(collection, columns = [], options = {})
14
- args_enum = [collection, columns, options].to_enum
15
- given_grid = args_enum.find {|e| e.is_a? MagicGrid::Definition }
16
- given_grid || MagicGrid::Definition.new(columns, collection, controller, options)
17
- end
18
-
19
- def magic_grid(collection = nil, cols = nil, opts = {}, &block)
20
- grid = normalize_magic(collection, cols, opts)
21
- base_params = grid.base_params
22
- data = {
23
- :searcher => grid.searcher,
24
- :current => controller.request.fullpath,
25
- :live_search => grid.options[:live_search],
26
- :listeners => (grid.options[:listeners] unless grid.options[:listeners].empty?),
27
- :remote => grid.options[:remote],
28
- :default_ajax_handler => grid.options[:default_ajax_handler],
29
- :params => base_params,
30
- }
31
- classes = ['magic_grid'] << grid.options[:class]
32
- content_tag('table',
33
- :class => classes.join(' '),
34
- :id => grid.magic_id,
35
- :data => MagicGrid.compact_hash(data)
36
- ) do
37
- table = content_tag('thead', :data => {:params => base_params}
38
- ) do
39
- thead = ''.html_safe
40
- has_spinner = false
41
- spinner = tag('span',
42
- :id => (grid.magic_id.to_s + "_spinner"),
43
- :class => "magic_grid_spinner"
44
- )
45
- if grid.needs_searcher?
46
- thead << content_tag('tr') do
47
- content_tag('td', :class => 'searcher full-width ui-widget-header',
48
- :colspan => grid.columns.count) do
49
- searcher = search_bar(grid)
50
- unless has_spinner
51
- has_spinner = true
52
- searcher << spinner
53
- end
54
- searcher
55
- end
56
- end
57
- end
58
- if grid.options[:per_page] and grid.options[:top_pager]
59
- thead << magic_pager(grid, base_params) do
60
- unless has_spinner
61
- has_spinner = true
62
- spinner
63
- end
64
- end
65
- end
66
- if thead.empty? and not grid.options[:empty_header]
67
- thead = content_tag 'tr' do
68
- content_tag('td', :class => 'full-width ui-widget-header',
69
- :colspan => grid.columns.count) do
70
- unless has_spinner
71
- has_spnner = true
72
- spinner
73
- end
74
- end
75
- end
76
- end
77
- thead << magic_headers(grid)
78
- end
79
- table << content_tag('tbody', :class => "ui-widget-content") do
80
- magic_rows(grid, &block)
81
- end
82
- table << content_tag('tfoot') do
83
- tfoot = ''.html_safe
84
- if grid.options[:per_page] and grid.options[:bottom_pager]
85
- tfoot << magic_pager(grid, base_params)
86
- end
87
- if tfoot.empty? and not grid.options[:empty_footer]
88
- tfoot = content_tag 'tr' do
89
- content_tag('td', nil, :class => 'full-width ui-widget-header',
90
- :colspan => grid.columns.count)
91
- end
92
- end
93
- tfoot
94
- end
95
- end
96
- end
97
-
98
- def magic_headers(cols, collection = nil, opts = {})
99
- grid = normalize_magic(collection, cols, opts)
100
- content_tag 'tr' do
101
- grid.columns.reduce(''.html_safe) do |acc, col|
102
- classes = ['ui-state-default'] << col[:class]
103
- acc <<
104
- if col.is_a? String
105
- content_tag 'th', col.html_safe, :class => classes.join(' ')
106
- elsif not col.key? :sql
107
- content_tag 'th', col[:label].html_safe, :class => classes.join(' ')
108
- else
109
- sortable_header(grid, col, opts)
110
- end
111
- end
112
- end
113
- end
114
-
115
- def magic_rows(cols, collection = nil, &block)
116
- grid = normalize_magic(collection, cols)
117
- if_empty = grid.options[:if_empty]
118
- rows = grid.collection.map do |row|
119
- if block_given?
120
- "<!-- block: -->" << capture(row, &block)
121
- else
122
- "<!-- magic row: -->" << magic_row(row, grid)
123
- end
124
- end
125
- if rows.empty? and if_empty
126
- content_tag 'tr' do
127
- content_tag('td', :colspan => grid.columns.count,
128
- :class => 'if-empty') do
129
- if if_empty.respond_to? :call
130
- if_empty.call(grid).to_s
131
- else
132
- if_empty
133
- end
134
- end
135
- end
136
- else
137
- rows.join.html_safe
138
- end
139
- end
140
-
141
- def magic_row(record, cols, collection = nil)
142
- grid = normalize_magic(collection, cols)
143
- content_tag 'tr', :class => cycle('odd', 'even') do
144
- grid.columns.reduce(''.html_safe) do |acc, c|
145
- acc << content_tag('td', :class => c[:class].try(:join, ' ')) do
146
- method = c[:to_s] || c[:col]
147
- if method.respond_to? :call
148
- method.call(record)
149
- elsif record.respond_to? method
150
- record.send(method)
151
- end.to_s
152
- end
153
- end
154
- end
155
- end
156
-
157
- def reverse_order(order)
158
- order.to_i == 0 ? 1 : 0
159
- end
160
-
161
- def order_icon(order = -1)
162
- content_tag 'span', '', :class => "ui-icon #{order_icon_class(order)}"
163
- end
164
-
165
- def order_icon_class(order = -1)
166
- case order.to_i
167
- when 0 then 'ui-icon-triangle-1-n'
168
- when 1 then 'ui-icon-triangle-1-s'
169
- else 'ui-icon-carat-2-n-s'
170
- end
171
- end
172
-
173
- def order_class(order = -1)
174
- case order.to_i
175
- when 0 then 'sort-asc'
176
- when 1 then 'sort-desc'
177
- else 'sort-none'
178
- end
179
- end
180
-
181
- def sortable_header(grid, col, opts = {})
182
- id = col[:id]
183
- label = col[:label] || id.titleize
184
- default_sort_order = opts.fetch(:default_order, grid.order(grid.default_order))
185
- my_params = grid.base_params.merge({
186
- grid.param_key(:col) => id,
187
- })
188
- my_params = HashWithIndifferentAccess.new(my_params)
189
- order = nil
190
- classes = ['sorter ui-state-default'] << col[:class]
191
- current = id.to_s == grid.current_sort_col.to_s
192
- if current
193
- order = grid.current_order
194
- classes << "sort-current" << order_class(order)
195
- my_params[grid.param_key(:order)] = reverse_order(order)
196
- label << order_icon(order)
197
- else
198
- my_params.delete grid.param_key(:order) if my_params[grid.param_key(:order)]
199
- label << order_icon()
200
- end
201
- my_params.delete(grid.param_key(:order)) if my_params[grid.param_key(:order)].to_i == default_sort_order.to_i
202
- content_tag 'th', :class => classes.join(' ') do
203
- link_to label.html_safe, my_params, :remote => grid.options[:remote]
204
- end
205
- end
206
-
207
- def search_bar(grid)
208
- searcher_data = {
209
- :min_length => grid.options[:min_search_length],
210
- :current => grid.options[:current_search] || "",
211
- }
212
- searcher = label_tag(grid.searcher.to_sym,
213
- grid.options[:searcher_label])
214
- searcher << search_field_tag(grid.searcher.to_sym,
215
- grid.param(:q),
216
- :placeholder => grid.options[:searcher_tooltip],
217
- :size => grid.options[:searcher_size],
218
- :data => searcher_data,
219
- :form => "a form that doesn't exist")
220
- if grid.options[:search_button]
221
- searcher << button_tag(grid.options[:searcher_button],
222
- :class => 'magic-grid-search-button')
223
- end
224
- searcher
225
- end
226
-
227
- def magic_paginate(collection, opts={})
228
- if respond_to? :will_paginate
229
- # WillPaginate
230
- will_paginate collection.collection, opts
231
- #alias_method :magic_paginate, :will_paginate
232
- elsif respond_to? :paginate
233
- #Kaminari, or something else..
234
- paginate collection.collection, opts
235
- #alias_method :magic_paginate, :paginate
236
- else
237
- ("<!-- page #{collection.current_page} of #{collection.total_pages} -->" +
238
- '<!-- INSTALL WillPaginate or Kaminari for a pager! -->').html_safe
239
- end
240
- end
241
-
242
- def magic_pager(grid, base_params, &block)
243
- content_tag('tr') do
244
- content_tag('td', :class => 'full-width ui-widget-header magic-pager',
245
- :colspan => grid.columns.count) do
246
- pager = magic_paginate(grid.magic_collection,
247
- :param_name => grid.param_key(:page),
248
- :params => base_params
249
- )
250
- pager << capture(&block) if block_given?
251
- pager
252
- end
253
- end
6
+ def magic_grid(collection = nil, columns = nil, opts = {}, &block)
7
+ grid_def = MagicGrid::Definition.new columns, collection, controller, opts
8
+ html_grid = HtmlGrid.new grid_def, self, controller
9
+ html_grid.render &block
254
10
  end
255
11
 
256
12
  ::ActionView::Base.send :include, self
@@ -0,0 +1,275 @@
1
+ require 'magic_grid/definition'
2
+
3
+ module MagicGrid
4
+ class HtmlGrid
5
+
6
+ def initialize(grid_definition, view, controller = nil)
7
+ @grid = grid_definition
8
+ @view ||= view
9
+ if controller
10
+ @current_url = controller.request.fullpath
11
+ else
12
+ @current_url = nil
13
+ end
14
+ end
15
+
16
+ def render(&row_renderer)
17
+ @spinner_drawn = false
18
+ grid_data = {
19
+ searcher: @grid.searcher,
20
+ current: @current_url,
21
+ live_search: @grid.options[:live_search],
22
+ listeners: @grid.options[:listeners],
23
+ remote: @grid.options[:remote],
24
+ default_ajax_handler: @grid.options[:default_ajax_handler],
25
+ params: @grid.base_params,
26
+ }
27
+ table_options = {
28
+ class: (['magic_grid'] << @grid.options[:class]).join(' '),
29
+ id: @grid.magic_id,
30
+ data: grid_data.select {|_,v| v }
31
+ }
32
+ @view.content_tag('table', table_options) do
33
+ thead + tbody(&row_renderer) + tfoot
34
+ end
35
+ end
36
+
37
+ def thead
38
+ @view.content_tag('thead', data: {params: @grid.base_params}) do
39
+ magic_grid_head
40
+ end
41
+ end
42
+
43
+ def tbody(&row_renderer)
44
+ @view.content_tag('tbody', class: "ui-widget-content") do
45
+ magic_rows &row_renderer
46
+ end
47
+ end
48
+
49
+ def tfoot
50
+ @view.content_tag('tfoot') do
51
+ magic_grid_foot
52
+ end
53
+ end
54
+
55
+ def spinner_generator
56
+ unless @spinner_drawn
57
+ @spinner_drawn = true
58
+ @view.tag('span',
59
+ id: (@grid.magic_id.to_s + "_spinner"),
60
+ class: "magic_grid_spinner")
61
+ end
62
+ end
63
+
64
+ def magic_grid_head
65
+ thead = [] << searcher_block_if(@grid.needs_searcher?,
66
+ &self.method(:spinner_generator))
67
+ thead << pager_block_if(@grid.options[:per_page] && @grid.options[:top_pager],
68
+ &self.method(:spinner_generator))
69
+ if thead.empty? and not @grid.options[:collapse_emtpy_header]
70
+ thead << filler_block(&self.method(:spinner_generator))
71
+ end
72
+ thead << magic_column_headers
73
+ thead.join.html_safe
74
+ end
75
+
76
+ def magic_grid_foot
77
+ if @grid.options[:per_page] and @grid.options[:bottom_pager]
78
+ magic_pager_block
79
+ elsif not @grid.options[:collapse_emtpy_footer]
80
+ filler_block
81
+ end
82
+ end
83
+
84
+ def filler_block(content = nil, &block)
85
+ @view.content_tag 'tr' do
86
+ @view.content_tag('td', content,
87
+ class: 'full-width ui-widget-header',
88
+ colspan: @grid.columns.count,
89
+ &block)
90
+ end
91
+ end
92
+
93
+ def magic_column_headers
94
+ @view.content_tag 'tr' do
95
+ @grid.columns.reduce(''.html_safe) do |acc, col|
96
+ classes = ['ui-state-default'] << col.html_classes
97
+ acc <<
98
+ if col.sortable?
99
+ sortable_header(col)
100
+ else
101
+ @view.content_tag 'th', col.label.html_safe, class: classes.join(' ')
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ def magic_rows(&row_renderer)
108
+ rows = @grid.collection.map { |row| grid_row(row, &row_renderer) }
109
+ if rows.empty?
110
+ rows << render_empty_collection(@grid.options[:if_empty])
111
+ end
112
+ rows.join.html_safe
113
+ end
114
+
115
+ def render_empty_collection(fallback)
116
+ if fallback
117
+ @view.content_tag 'tr' do
118
+ @view.content_tag('td', colspan: @grid.columns.count,
119
+ class: 'if-empty') do
120
+ if fallback.respond_to? :call
121
+ fallback.call(@grid).to_s
122
+ else
123
+ fallback
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ def grid_row(record, &row_renderer)
131
+ if row_renderer
132
+ @view.capture(record, &row_renderer)
133
+ else
134
+ @view.content_tag 'tr', class: @view.cycle('odd', 'even') do
135
+ @grid.columns.map { |c| grid_cell(c, record) }.join.html_safe
136
+ end
137
+ end
138
+ end
139
+
140
+ def grid_cell(column, record)
141
+ @view.content_tag('td', class: column.html_classes) do
142
+ method = column.reader
143
+ if method.respond_to? :call
144
+ method.call(record).to_s
145
+ elsif record.respond_to? method
146
+ record.send(method).to_s
147
+ else
148
+ ""
149
+ end
150
+ end
151
+ end
152
+
153
+ def unordered
154
+ -1
155
+ end
156
+
157
+ def reverse_order(order)
158
+ order.to_i == 0 ? 1 : 0
159
+ end
160
+
161
+ def order_icon(order = -1)
162
+ @view.content_tag 'span', '', class: "ui-icon #{order_icon_class(order)}"
163
+ end
164
+
165
+ def order_icon_class(order = -1)
166
+ case order.to_i
167
+ when 0 then 'ui-icon-triangle-1-n'
168
+ when 1 then 'ui-icon-triangle-1-s'
169
+ else 'ui-icon-carat-2-n-s'
170
+ end
171
+ end
172
+
173
+ def order_class(order = -1)
174
+ case order.to_i
175
+ when 0 then 'sort-asc'
176
+ when 1 then 'sort-desc'
177
+ else 'sort-none'
178
+ end
179
+ end
180
+
181
+ def sortable_header(col)
182
+ id = col.id
183
+ label = col.label || id.titleize
184
+ default_sort_order = @grid.order(@grid.default_order)
185
+ my_params = @grid.base_params.merge({
186
+ @grid.param_key(:col) => id,
187
+ })
188
+ column_link_params = HashWithIndifferentAccess.new(my_params)
189
+ order = unordered
190
+ classes = ['sorter ui-state-default'] << col.html_classes
191
+ if id.to_s == @grid.current_sort_col.to_s
192
+ order = @grid.current_order
193
+ classes << "sort-current" << order_class(order)
194
+ column_link_params[@grid.param_key(:order)] = reverse_order(order)
195
+ else
196
+ column_link_params.delete @grid.param_key(:order)
197
+ end
198
+ if column_link_params[@grid.param_key(:order)].to_i == default_sort_order.to_i
199
+ column_link_params.delete(@grid.param_key(:order))
200
+ end
201
+ @view.content_tag 'th', class: classes.join(' ') do
202
+ @view.link_to column_link_params, remote: @grid.options[:remote] do
203
+ label.html_safe << order_icon(order)
204
+ end
205
+ end
206
+ end
207
+
208
+ def searcher_block_if(conditional = true, &spinner)
209
+ if conditional
210
+ @view.content_tag('tr') do
211
+ @view.content_tag('td', class: 'searcher full-width ui-widget-header',
212
+ colspan: @grid.columns.count) do
213
+ searcher_input(&spinner)
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ def searcher_input(&spinner)
220
+ searcher_data = {
221
+ min_length: @grid.options[:min_search_length],
222
+ current: @grid.current_search || "",
223
+ }
224
+ searcher = @view.label_tag(@grid.searcher.to_sym,
225
+ @grid.options[:searcher_label])
226
+ searcher << @view.search_field_tag(@grid.searcher.to_sym,
227
+ @grid.param(:q),
228
+ placeholder: @grid.options[:searcher_tooltip],
229
+ size: @grid.options[:searcher_size],
230
+ data: searcher_data,
231
+ form: "a form that doesn't exist")
232
+ if @grid.options[:search_button]
233
+ searcher << @view.button_tag(@grid.options[:searcher_button],
234
+ class: 'magic-grid-search-button')
235
+ end
236
+ searcher << yield if block_given?
237
+ searcher
238
+ end
239
+
240
+ def magic_pager(collection, opts={})
241
+ if @view.respond_to? :will_paginate
242
+ # WillPaginate
243
+ @view.will_paginate collection.collection, opts
244
+ elsif @view.respond_to? :paginate
245
+ #Kaminari, or something else..
246
+ @view.paginate collection.collection, opts
247
+ else
248
+ ("<!-- page #{collection.current_page} of #{collection.total_pages} -->" +
249
+ '<!-- INSTALL WillPaginate or Kaminari for a pager! -->').html_safe
250
+ end
251
+ end
252
+
253
+ def pager_block_if(conditional = true, &spinner)
254
+ if conditional
255
+ magic_pager_block(&spinner)
256
+ end
257
+ end
258
+
259
+ def magic_pager_block(&spinner)
260
+ @view.content_tag('tr') do
261
+ @view.content_tag('td', class: 'full-width ui-widget-header magic-pager',
262
+ colspan: @grid.columns.count) do
263
+ pager = magic_pager(@grid.magic_collection,
264
+ param_name: @grid.param_key(:page),
265
+ params: @grid.base_params
266
+ )
267
+ if spinner
268
+ pager << @view.capture(&spinner)
269
+ end
270
+ pager
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end