effective_datatables 1.4.3 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +25 -2
- data/app/assets/images/dataTables/sort_asc.png +0 -0
- data/app/assets/images/dataTables/sort_both.png +0 -0
- data/app/assets/images/dataTables/sort_desc.png +0 -0
- data/app/assets/javascripts/dataTables/bootstrap/2/jquery.dataTables.bootstrap.js +148 -0
- data/app/assets/javascripts/dataTables/bootstrap/3/jquery.dataTables.bootstrap.js +185 -0
- data/app/assets/javascripts/dataTables/dataTables.colReorder.min.js +26 -0
- data/app/assets/javascripts/dataTables/dataTables.colVis.min.js +24 -0
- data/app/assets/javascripts/dataTables/dataTables.fixedColumns.min.js +30 -0
- data/app/assets/javascripts/dataTables/dataTables.tableTools.min.js +70 -0
- data/app/assets/javascripts/dataTables/jquery.dataTables.min.js +160 -0
- data/app/assets/javascripts/effective_datatables.bootstrap2.js +6 -5
- data/app/assets/javascripts/effective_datatables.js +6 -5
- data/app/assets/javascripts/effective_datatables/initialize.js.coffee.erb +52 -39
- data/app/assets/javascripts/vendor/jquery.debounce.min.js +9 -0
- data/app/assets/stylesheets/dataTables/bootstrap/2/jquery.dataTables.bootstrap.scss +207 -0
- data/app/assets/stylesheets/dataTables/bootstrap/3/jquery.dataTables.bootstrap.scss +280 -0
- data/app/assets/stylesheets/dataTables/dataTables.colReorder.min.css +1 -0
- data/app/assets/stylesheets/dataTables/dataTables.colVis.min.css +1 -0
- data/app/assets/stylesheets/dataTables/dataTables.fixedColumns.min.css +1 -0
- data/app/assets/stylesheets/dataTables/dataTables.tableTools.min.css +1 -0
- data/app/assets/stylesheets/dataTables/jquery.dataTables.min.css +1 -0
- data/app/assets/stylesheets/effective_datatables.bootstrap2.scss +4 -3
- data/app/assets/stylesheets/effective_datatables.scss +4 -3
- data/app/assets/stylesheets/effective_datatables/_overrides.scss.erb +71 -63
- data/app/controllers/effective/datatables_controller.rb +4 -4
- data/app/helpers/effective_datatables_helper.rb +24 -30
- data/app/models/effective/active_record_datatable_tool.rb +6 -6
- data/app/models/effective/array_datatable_tool.rb +18 -8
- data/app/models/effective/datatable.rb +98 -44
- data/app/views/effective/datatables/_datatable.html.haml +41 -15
- data/lib/effective_datatables.rb +0 -1
- data/lib/effective_datatables/version.rb +1 -1
- metadata +20 -17
- data/app/assets/javascripts/vendor/jquery.dataTables.columnFilter.js +0 -832
@@ -25,10 +25,10 @@ module Effective
|
|
25
25
|
|
26
26
|
def error_json
|
27
27
|
{
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
28
|
+
:draw => params[:draw].to_i,
|
29
|
+
:data => [],
|
30
|
+
:recordsTotal => 0,
|
31
|
+
:recordsFiltered => 0,
|
32
32
|
}.to_json
|
33
33
|
end
|
34
34
|
|
@@ -1,50 +1,40 @@
|
|
1
1
|
module EffectiveDatatablesHelper
|
2
2
|
def render_datatable(datatable, opts = {}, &block)
|
3
3
|
datatable.view = self
|
4
|
+
locals = {:style => :full, :filterable => true, :sortable => true, :table_class => 'table-bordered table-striped'}.merge(opts)
|
4
5
|
|
5
|
-
|
6
|
-
locals = locals.merge(opts) if opts.kind_of?(Hash)
|
7
|
-
locals[:table_class] = 'sorting-hidden ' + locals[:table_class].to_s if locals[:sortable] == false
|
8
|
-
|
9
|
-
# Do we have to look at empty? behaviour
|
10
|
-
if (block_given? || opts.kind_of?(String) || (opts.kind_of?(Hash) && opts[:empty].present?)) && datatable.empty?
|
11
|
-
if block_given?
|
12
|
-
yield; nil
|
13
|
-
elsif opts.kind_of?(String)
|
14
|
-
opts
|
15
|
-
elsif opts.kind_of?(Hash) && opts[:empty].present?
|
16
|
-
opts[:empty]
|
17
|
-
end
|
18
|
-
else
|
19
|
-
render :partial => 'effective/datatables/datatable', :locals => locals.merge(:datatable => datatable)
|
20
|
-
end
|
6
|
+
render :partial => 'effective/datatables/datatable', :locals => locals.merge(:datatable => datatable)
|
21
7
|
end
|
22
8
|
|
23
9
|
def render_simple_datatable(datatable, opts = {})
|
24
10
|
datatable.view = self
|
25
|
-
|
26
|
-
locals
|
11
|
+
datatable.per_page = :all
|
12
|
+
locals = {:style => :simple, :filterable => false, :sortable => false, :table_class => 'table-bordered table-striped sorting-hidden'}.merge(opts)
|
27
13
|
|
28
14
|
render :partial => 'effective/datatables/datatable', :locals => locals.merge(:datatable => datatable)
|
29
15
|
end
|
30
16
|
|
31
|
-
def
|
32
|
-
return
|
33
|
-
|
34
|
-
filters = datatable.table_columns.values.map { |options, _| options[:filter] || {:type => 'null'} }
|
17
|
+
def render_datatable_header_cell(form, name, opts, filterable = true)
|
18
|
+
return content_tag(:p, opts[:label] || name) if filterable == false
|
35
19
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
20
|
+
case opts[:filter][:type]
|
21
|
+
when :string, :text, :number
|
22
|
+
form.input name, :label => false, :required => false, :as => :string, :placeholder => (opts[:label] || name),
|
23
|
+
:input_html => { :autocomplete => 'off', :data => {'column-name' => opts[:name], 'column-index' => opts[:index]} }
|
24
|
+
when :select, :boolean
|
25
|
+
if opts[:filter][:values].respond_to?(:call)
|
26
|
+
opts[:filter][:values] = opts[:filter][:values].call()
|
40
27
|
|
41
|
-
if filter[:values].kind_of?(ActiveRecord::Relation) || (filter[:values].kind_of?(Array) && filter[:values].first.kind_of?(ActiveRecord::Base))
|
42
|
-
filter[:values] = filter[:values].map { |obj| [obj.
|
28
|
+
if opts[:filter][:values].kind_of?(ActiveRecord::Relation) || (opts[:filter][:values].kind_of?(Array) && opts[:filter][:values].first.kind_of?(ActiveRecord::Base))
|
29
|
+
opts[:filter][:values] = opts[:filter][:values].map { |obj| [obj.to_s, obj.id] }
|
43
30
|
end
|
44
31
|
end
|
45
|
-
end
|
46
32
|
|
47
|
-
|
33
|
+
form.input name, :label => false, :required => false, :as => :select, :collection => opts[:filter][:values], :include_blank => (opts[:label] || name.titleize),
|
34
|
+
:input_html => { :autocomplete => 'off', :data => {'column-name' => opts[:name], 'column-index' => opts[:index]} }
|
35
|
+
else
|
36
|
+
content_tag(:p, opts[:label] || name)
|
37
|
+
end
|
48
38
|
end
|
49
39
|
|
50
40
|
def datatable_non_sortable(datatable, sortable = true)
|
@@ -83,6 +73,10 @@ module EffectiveDatatablesHelper
|
|
83
73
|
end.to_json()
|
84
74
|
end
|
85
75
|
|
76
|
+
def datatable_column_names(datatable)
|
77
|
+
datatable.table_columns.values.map { |options| {:name => options[:name], :targets => options[:index] } }.to_json()
|
78
|
+
end
|
79
|
+
|
86
80
|
def datatables_admin_path?
|
87
81
|
@datatables_admin_path ||= (
|
88
82
|
path = request.path.to_s.downcase.chomp('/') + '/'
|
@@ -2,25 +2,25 @@ module Effective
|
|
2
2
|
class ActiveRecordDatatableTool
|
3
3
|
attr_accessor :table_columns
|
4
4
|
|
5
|
-
delegate :
|
5
|
+
delegate :order_name, :order_direction, :page, :per_page, :search_column, :to => :@datatable
|
6
6
|
|
7
7
|
def initialize(datatable, table_columns)
|
8
8
|
@datatable = datatable
|
9
9
|
@table_columns = table_columns
|
10
10
|
end
|
11
11
|
|
12
|
-
def order_column
|
13
|
-
@order_column ||= table_columns.find { |_, values| values[:index] == order_column_index }.try(:second) # This pulls out the values
|
14
|
-
end
|
15
|
-
|
16
12
|
def search_terms
|
17
13
|
@search_terms ||= @datatable.search_terms.select { |name, search_term| table_columns.key?(name) }
|
18
14
|
end
|
19
15
|
|
16
|
+
def order_column
|
17
|
+
@order_column ||= table_columns[order_name]
|
18
|
+
end
|
19
|
+
|
20
20
|
def order(collection)
|
21
21
|
return collection if order_column.blank?
|
22
22
|
|
23
|
-
if [:string, :text].include?(order_column[:type])
|
23
|
+
if [:string, :text].include?(order_column[:type]) && order_column[:sql_as_column] != true
|
24
24
|
collection.order("COALESCE(#{order_column[:column]}, '') #{order_direction}")
|
25
25
|
else
|
26
26
|
collection.order("#{order_column[:column]} #{order_direction} NULLS #{order_direction == 'ASC' ? 'LAST' : 'FIRST'}")
|
@@ -3,27 +3,29 @@ module Effective
|
|
3
3
|
class ArrayDatatableTool
|
4
4
|
attr_accessor :table_columns
|
5
5
|
|
6
|
-
delegate :
|
6
|
+
delegate :order_name, :order_direction, :page, :per_page, :search_column, :display_table_columns, :to => :@datatable
|
7
7
|
|
8
8
|
def initialize(datatable, table_columns)
|
9
9
|
@datatable = datatable
|
10
10
|
@table_columns = table_columns
|
11
11
|
end
|
12
12
|
|
13
|
-
def order_column
|
14
|
-
@order_column ||= table_columns.find { |_, values| values[:index] == order_column_index }.try(:second) # This pulls out the values
|
15
|
-
end
|
16
|
-
|
17
13
|
def search_terms
|
18
14
|
@search_terms ||= @datatable.search_terms.select { |name, search_term| table_columns.key?(name) }
|
19
15
|
end
|
20
16
|
|
17
|
+
def order_column
|
18
|
+
@order_column ||= table_columns[order_name]
|
19
|
+
end
|
20
|
+
|
21
21
|
def order(collection)
|
22
22
|
if order_column.present?
|
23
|
+
index = display_index(order_column)
|
24
|
+
|
23
25
|
if order_direction == 'ASC'
|
24
|
-
collection.sort! { |x, y| x[
|
26
|
+
collection.sort! { |x, y| x[index] <=> y[index] }
|
25
27
|
else
|
26
|
-
collection.sort! { |x, y| y[
|
28
|
+
collection.sort! { |x, y| y[index] <=> x[index] }
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
@@ -41,9 +43,10 @@ module Effective
|
|
41
43
|
|
42
44
|
def search_column_with_defaults(collection, table_column, search_term)
|
43
45
|
search_term = search_term.downcase
|
46
|
+
index = display_index(table_column)
|
44
47
|
|
45
48
|
collection.select! do |row|
|
46
|
-
value = row[
|
49
|
+
value = row[index].to_s.downcase
|
47
50
|
|
48
51
|
if table_column[:filter][:type] == :select && table_column[:filter][:fuzzy] != true
|
49
52
|
value == search_term
|
@@ -56,6 +59,13 @@ module Effective
|
|
56
59
|
def paginate(collection)
|
57
60
|
Kaminari.paginate_array(collection).page(page).per(per_page)
|
58
61
|
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def display_index(column)
|
66
|
+
(display_table_columns || table_columns).keys.index(column[:name])
|
67
|
+
end
|
68
|
+
|
59
69
|
end
|
60
70
|
end
|
61
71
|
|
@@ -22,6 +22,7 @@ module Effective
|
|
22
22
|
end
|
23
23
|
raise "You cannot use both :partial => '' and proc => ..." if options[:partial] && options[:proc]
|
24
24
|
|
25
|
+
send(:attr_accessor, name)
|
25
26
|
(@table_columns ||= HashWithIndifferentAccess.new())[name] = options
|
26
27
|
end
|
27
28
|
|
@@ -44,17 +45,25 @@ module Effective
|
|
44
45
|
def default_entries(entries)
|
45
46
|
@default_entries = entries
|
46
47
|
end
|
48
|
+
|
49
|
+
def model_name # Searching & Filters
|
50
|
+
@model_name ||= ActiveModel::Name.new(self)
|
51
|
+
end
|
47
52
|
end
|
48
53
|
|
54
|
+
|
49
55
|
def initialize(*args)
|
56
|
+
unless active_record_collection? || (collection.kind_of?(Array) && collection.first.kind_of?(Array))
|
57
|
+
raise "Unsupported collection type. Should be ActiveRecord class, ActiveRecord relation, or an Array of Arrays [[1, 'something'], [2, 'something else']]"
|
58
|
+
end
|
59
|
+
|
50
60
|
if args.present?
|
51
61
|
raise 'Effective::Datatable.new() can only be called with a Hash like arguments' unless args.first.kind_of?(Hash)
|
52
62
|
args.first.each { |k, v| self.attributes[k] = v }
|
53
63
|
end
|
54
64
|
|
55
|
-
|
56
|
-
|
57
|
-
end
|
65
|
+
# Any pre-selected search terms should be assigned now
|
66
|
+
search_terms.each { |column, term| self.send("#{column}=", term) }
|
58
67
|
end
|
59
68
|
|
60
69
|
# Any attributes set on initialize will be echoed back and available to the class
|
@@ -62,6 +71,13 @@ module Effective
|
|
62
71
|
@attributes ||= HashWithIndifferentAccess.new()
|
63
72
|
end
|
64
73
|
|
74
|
+
def to_key; []; end # Searching & Filters
|
75
|
+
|
76
|
+
# Instance method. In Rails 4.2 this needs to be defined on the instance, before it was on the class
|
77
|
+
def model_name # Searching & Filters
|
78
|
+
@model_name ||= ActiveModel::Name.new(self.class)
|
79
|
+
end
|
80
|
+
|
65
81
|
def to_param
|
66
82
|
self.class.name.underscore.parameterize
|
67
83
|
end
|
@@ -85,14 +101,27 @@ module Effective
|
|
85
101
|
end.each_with_index { |(_, col), index| col[:index] = index }
|
86
102
|
end
|
87
103
|
|
104
|
+
# This is for the ColReorder plugin
|
105
|
+
# It sends us a list of columns that are different than our table_columns order
|
106
|
+
# So this method just returns an array of column names, as per ColReorder
|
107
|
+
def display_table_columns
|
108
|
+
if params[:columns].present?
|
109
|
+
HashWithIndifferentAccess.new().tap do |display_columns|
|
110
|
+
params[:columns].each do |_, values|
|
111
|
+
display_columns[values[:name]] = table_columns[values[:name]]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
88
117
|
def to_json
|
89
118
|
raise 'Effective::Datatable to_json called with a nil view. Please call render_datatable(@datatable) or @datatable.view = view before this method' unless view.present?
|
90
119
|
|
91
120
|
@json ||= {
|
92
|
-
:
|
93
|
-
:
|
94
|
-
:
|
95
|
-
:
|
121
|
+
:draw => (params[:draw] || 0),
|
122
|
+
:data => (table_data || []),
|
123
|
+
:recordsTotal => (total_records || 0),
|
124
|
+
:recordsFiltered => (display_records || 0)
|
96
125
|
}
|
97
126
|
end
|
98
127
|
|
@@ -104,20 +133,20 @@ module Effective
|
|
104
133
|
total_records.to_i == 0
|
105
134
|
end
|
106
135
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
136
|
+
def order_name
|
137
|
+
@order_name ||= begin
|
138
|
+
if params[:order] && params[:columns]
|
139
|
+
order_column_index = (params[:order].first[1][:column] rescue '0')
|
140
|
+
(params[:columns][order_column_index] || {})[:name]
|
141
|
+
elsif default_order.present?
|
142
|
+
default_order.keys.first
|
143
|
+
end || table_columns.keys.first
|
115
144
|
end
|
116
145
|
end
|
117
146
|
|
118
147
|
def order_direction
|
119
|
-
if params[:
|
120
|
-
params[:
|
148
|
+
@order_direction ||= if params[:order].present?
|
149
|
+
params[:order].first[1][:dir] == 'desc' ? 'DESC' : 'ASC'
|
121
150
|
elsif default_order.present?
|
122
151
|
default_order.values.first.to_s.downcase == 'desc' ? 'DESC' : 'ASC'
|
123
152
|
else
|
@@ -139,18 +168,15 @@ module Effective
|
|
139
168
|
|
140
169
|
def search_terms
|
141
170
|
@search_terms ||= HashWithIndifferentAccess.new().tap do |terms|
|
142
|
-
if params[:
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
171
|
+
if params[:columns].present? # This is an AJAX request from the DataTable
|
172
|
+
(params[:columns] || {}).each do |_, column|
|
173
|
+
next if table_columns[column[:name]].blank? || (column[:search] || {})[:value].blank?
|
174
|
+
|
175
|
+
terms[column[:name]] = column[:search][:value]
|
147
176
|
end
|
148
|
-
else
|
149
|
-
# We are in the initial render and have to apply default search terms only
|
177
|
+
else # This is the initial render, and we have to apply default search terms only
|
150
178
|
table_columns.each do |name, values|
|
151
|
-
|
152
|
-
terms[name] = values[:filter][:selected]
|
153
|
-
end
|
179
|
+
terms[name] = values[:filter][:selected] if values[:filter][:selected].present?
|
154
180
|
end
|
155
181
|
end
|
156
182
|
end
|
@@ -166,7 +192,7 @@ module Effective
|
|
166
192
|
end
|
167
193
|
|
168
194
|
def per_page
|
169
|
-
length = (params[:
|
195
|
+
length = (params[:length].presence || default_entries).to_i
|
170
196
|
|
171
197
|
if length == -1
|
172
198
|
9999999
|
@@ -177,8 +203,17 @@ module Effective
|
|
177
203
|
end
|
178
204
|
end
|
179
205
|
|
206
|
+
def per_page=(length)
|
207
|
+
case length
|
208
|
+
when Integer
|
209
|
+
params[:length] = length
|
210
|
+
when :all
|
211
|
+
params[:length] = -1
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
180
215
|
def page
|
181
|
-
params[:
|
216
|
+
params[:start].to_i / per_page + 1
|
182
217
|
end
|
183
218
|
|
184
219
|
def total_records
|
@@ -206,8 +241,14 @@ module Effective
|
|
206
241
|
(self.class.instance_methods(false) - [:collection, :search_column]).each do |view_method|
|
207
242
|
@view.class_eval { delegate view_method, :to => :@effective_datatable }
|
208
243
|
end
|
244
|
+
|
245
|
+
# Clear the search_terms memoization
|
246
|
+
@search_terms = nil
|
247
|
+
@order_name = nil
|
248
|
+
@order_direction = nil
|
209
249
|
end
|
210
250
|
|
251
|
+
|
211
252
|
protected
|
212
253
|
|
213
254
|
# So the idea here is that we want to do as much as possible on the database in ActiveRecord
|
@@ -267,7 +308,7 @@ module Effective
|
|
267
308
|
end
|
268
309
|
|
269
310
|
collection.each_with_index.map do |obj, index|
|
270
|
-
table_columns.map do |name, opts|
|
311
|
+
(display_table_columns || table_columns).map do |name, opts|
|
271
312
|
value = if opts[:partial]
|
272
313
|
rendered[name][index]
|
273
314
|
elsif opts[:block]
|
@@ -314,7 +355,11 @@ module Effective
|
|
314
355
|
end
|
315
356
|
|
316
357
|
def active_record_collection?
|
317
|
-
@active_record_collection
|
358
|
+
if @active_record_collection.nil?
|
359
|
+
@active_record_collection = (collection.ancestors.include?(ActiveRecord::Base) rescue false)
|
360
|
+
else
|
361
|
+
@active_record_collection
|
362
|
+
end
|
318
363
|
end
|
319
364
|
|
320
365
|
def table_columns_with_defaults
|
@@ -334,7 +379,7 @@ module Effective
|
|
334
379
|
belong_tos = (collection.ancestors.first.reflect_on_all_associations(:belongs_to) rescue []).inject(HashWithIndifferentAccess.new()) do |retval, bt|
|
335
380
|
unless bt.options[:polymorphic]
|
336
381
|
begin
|
337
|
-
klass = bt.klass || bt.foreign_type.
|
382
|
+
klass = bt.klass || bt.foreign_type.sub('_type', '').classify.constantize
|
338
383
|
rescue => e
|
339
384
|
klass = nil
|
340
385
|
end
|
@@ -370,6 +415,10 @@ module Effective
|
|
370
415
|
cols[name][:type] = :obfuscated_id
|
371
416
|
end
|
372
417
|
|
418
|
+
if sql_table.present? && sql_column.blank? # This is a SELECT AS column
|
419
|
+
cols[name][:sql_as_column] = true
|
420
|
+
end
|
421
|
+
|
373
422
|
cols[name][:filter] = initialize_table_column_filter(cols[name][:filter], cols[name][:type], belong_tos[name])
|
374
423
|
|
375
424
|
if cols[name][:partial]
|
@@ -379,33 +428,38 @@ module Effective
|
|
379
428
|
end
|
380
429
|
|
381
430
|
def initialize_table_column_filter(filter, col_type, belongs_to)
|
382
|
-
return {:type => :null
|
431
|
+
return {:type => :null} if filter == false
|
383
432
|
|
384
433
|
if filter.kind_of?(Symbol)
|
385
|
-
filter =
|
434
|
+
filter = HashWithIndifferentAccess.new(:type => filter)
|
386
435
|
elsif filter.kind_of?(String)
|
387
|
-
filter =
|
436
|
+
filter = HashWithIndifferentAccess.new(:type => filter.to_sym)
|
388
437
|
elsif filter.kind_of?(Hash) == false
|
389
|
-
filter =
|
438
|
+
filter = HashWithIndifferentAccess.new()
|
390
439
|
end
|
391
440
|
|
392
441
|
# This is a fix for passing filter[:selected] == false, it needs to be 'false'
|
393
442
|
filter[:selected] = filter[:selected].to_s unless filter[:selected].nil?
|
394
443
|
|
395
|
-
case col_type
|
444
|
+
filter = case col_type
|
396
445
|
when :belongs_to
|
397
|
-
|
446
|
+
HashWithIndifferentAccess.new(
|
398
447
|
:type => :select,
|
399
|
-
:
|
400
|
-
|
401
|
-
}.merge(filter)
|
448
|
+
:values => Proc.new { belongs_to[:klass].all.map { |obj| [obj.to_s, obj.id] }.sort { |x, y| x[1] <=> y[1] } }
|
449
|
+
).merge(filter)
|
402
450
|
when :integer
|
403
|
-
|
451
|
+
HashWithIndifferentAccess.new(:type => :number).merge(filter)
|
404
452
|
when :boolean
|
405
|
-
|
453
|
+
HashWithIndifferentAccess.new(:type => :boolean, :values => [true, false]).merge(filter)
|
406
454
|
else
|
407
|
-
|
455
|
+
HashWithIndifferentAccess.new(:type => :string).merge(filter)
|
456
|
+
end
|
457
|
+
|
458
|
+
if filter[:type] == :boolean
|
459
|
+
filter = HashWithIndifferentAccess.new(:values => [true, false]).merge(filter)
|
408
460
|
end
|
461
|
+
|
462
|
+
filter
|
409
463
|
end
|
410
464
|
|
411
465
|
end
|