effective_datatables 2.12.2 → 3.0.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.
- checksums.yaml +4 -4
- data/README.md +632 -512
- data/app/assets/javascripts/dataTables/buttons/buttons.html5.js +176 -177
- data/app/assets/javascripts/dataTables/buttons/buttons.print.js +2 -0
- data/app/assets/javascripts/dataTables/buttons/dataTables.buttons.js +14 -14
- data/app/assets/javascripts/dataTables/dataTables.bootstrap.js +1 -1
- data/app/assets/javascripts/dataTables/jquery.dataTables.js +246 -217
- data/app/assets/javascripts/effective_datatables.js +2 -3
- data/app/assets/javascripts/effective_datatables/events.js.coffee +7 -0
- data/app/assets/javascripts/effective_datatables/filters.js.coffee +6 -0
- data/app/assets/javascripts/effective_datatables/initialize.js.coffee +42 -39
- data/app/assets/javascripts/effective_datatables/reset.js.coffee +7 -0
- data/app/assets/javascripts/vendor/jquery.delayedChange.js +1 -1
- data/app/assets/stylesheets/dataTables/dataTables.bootstrap.css +0 -1
- data/app/assets/stylesheets/effective_datatables.scss +1 -2
- data/app/assets/stylesheets/effective_datatables/{_scopes.scss → _filters.scss} +1 -1
- data/app/assets/stylesheets/effective_datatables/_overrides.scss +1 -1
- data/app/controllers/effective/datatables_controller.rb +2 -4
- data/app/helpers/effective_datatables_helper.rb +56 -91
- data/app/helpers/effective_datatables_private_helper.rb +55 -64
- data/app/models/effective/datatable.rb +103 -177
- data/app/models/effective/datatable_column.rb +28 -0
- data/app/models/effective/datatable_column_tool.rb +110 -0
- data/app/models/effective/datatable_dsl_tool.rb +28 -0
- data/app/models/effective/datatable_value_tool.rb +142 -0
- data/app/models/effective/effective_datatable/attributes.rb +25 -0
- data/app/models/effective/effective_datatable/collection.rb +38 -0
- data/app/models/effective/effective_datatable/compute.rb +154 -0
- data/app/models/effective/effective_datatable/cookie.rb +29 -0
- data/app/models/effective/effective_datatable/dsl.rb +14 -8
- data/app/models/effective/effective_datatable/dsl/bulk_actions.rb +5 -6
- data/app/models/effective/effective_datatable/dsl/charts.rb +7 -9
- data/app/models/effective/effective_datatable/dsl/datatable.rb +107 -57
- data/app/models/effective/effective_datatable/dsl/filters.rb +50 -0
- data/app/models/effective/effective_datatable/format.rb +157 -0
- data/app/models/effective/effective_datatable/hooks.rb +0 -18
- data/app/models/effective/effective_datatable/params.rb +34 -0
- data/app/models/effective/effective_datatable/resource.rb +108 -0
- data/app/models/effective/effective_datatable/state.rb +178 -0
- data/app/views/effective/datatables/_actions_column.html.haml +9 -42
- data/app/views/effective/datatables/_bulk_actions_column.html.haml +1 -1
- data/app/views/effective/datatables/_bulk_actions_dropdown.html.haml +2 -3
- data/app/views/effective/datatables/_chart.html.haml +1 -1
- data/app/views/effective/datatables/_datatable.html.haml +7 -25
- data/app/views/effective/datatables/_filters.html.haml +21 -0
- data/app/views/effective/datatables/_reset.html.haml +2 -0
- data/app/views/effective/datatables/_resource_column.html.haml +8 -0
- data/app/views/effective/datatables/index.html.haml +0 -1
- data/config/effective_datatables.rb +9 -32
- data/lib/effective_datatables.rb +2 -6
- data/lib/effective_datatables/engine.rb +1 -1
- data/lib/effective_datatables/version.rb +1 -1
- data/lib/generators/effective_datatables/install_generator.rb +2 -2
- metadata +39 -19
- data/app/assets/javascripts/dataTables/colreorder/dataTables.colReorder.js +0 -27
- data/app/assets/javascripts/dataTables/jszip/jszip.js +0 -9155
- data/app/assets/javascripts/effective_datatables/scopes.js.coffee +0 -9
- data/app/models/effective/active_record_datatable_tool.rb +0 -242
- data/app/models/effective/array_datatable_tool.rb +0 -97
- data/app/models/effective/effective_datatable/ajax.rb +0 -101
- data/app/models/effective/effective_datatable/charts.rb +0 -20
- data/app/models/effective/effective_datatable/dsl/scopes.rb +0 -23
- data/app/models/effective/effective_datatable/helpers.rb +0 -24
- data/app/models/effective/effective_datatable/options.rb +0 -309
- data/app/models/effective/effective_datatable/rendering.rb +0 -365
- data/app/views/effective/datatables/_scopes.html.haml +0 -21
@@ -1,20 +0,0 @@
|
|
1
|
-
module Effective
|
2
|
-
module EffectiveDatatable
|
3
|
-
module Charts
|
4
|
-
|
5
|
-
def charts_data
|
6
|
-
HashWithIndifferentAccess.new().tap do |retval|
|
7
|
-
(charts || {}).each do |name, chart|
|
8
|
-
retval[name] = {
|
9
|
-
name: chart[:name],
|
10
|
-
type: chart[:type],
|
11
|
-
options: chart[:options],
|
12
|
-
data: (instance_exec(&chart[:block]) if chart[:block])
|
13
|
-
}
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module Effective
|
2
|
-
module EffectiveDatatable
|
3
|
-
module Dsl
|
4
|
-
module Scopes
|
5
|
-
# Instance Methods inside the scopes do .. end block
|
6
|
-
def scope(name, default = :klass_scope, options = {}, &block)
|
7
|
-
if block_given?
|
8
|
-
raise "You cannot use partial: ... with the block syntax" if options[:partial]
|
9
|
-
options[:block] = block
|
10
|
-
end
|
11
|
-
|
12
|
-
if default == :klass_scope || default == { default: true }
|
13
|
-
options[:klass_scope] = true
|
14
|
-
default = (default == :klass_scope ? nil : true)
|
15
|
-
end
|
16
|
-
|
17
|
-
# This needs to be a {} not WithIndifferentAccess or rendering _scopes won't work correctly
|
18
|
-
(@scopes ||= {})[name] = options.merge(default: default)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
module Effective
|
2
|
-
module EffectiveDatatable
|
3
|
-
module Helpers
|
4
|
-
|
5
|
-
# When we order by Array, it's already a string.
|
6
|
-
# This gives us a mechanism to sort numbers as numbers
|
7
|
-
def convert_to_column_type(table_column, value)
|
8
|
-
if value.html_safe? && value.kind_of?(String) && value.start_with?('<')
|
9
|
-
value = ActionView::Base.full_sanitizer.sanitize(value)
|
10
|
-
end
|
11
|
-
|
12
|
-
case table_column[:type]
|
13
|
-
when :number, :price, :decimal, :float, :percentage
|
14
|
-
(value.to_s.gsub(/[^0-9|\.]/, '').to_f rescue 0.00) unless value.kind_of?(Numeric)
|
15
|
-
when :integer
|
16
|
-
(value.to_s.gsub(/\D/, '').to_i rescue 0) unless value.kind_of?(Integer)
|
17
|
-
else
|
18
|
-
; # Do nothing
|
19
|
-
end || value
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,309 +0,0 @@
|
|
1
|
-
# This is extended as class level into Datatable
|
2
|
-
|
3
|
-
module Effective
|
4
|
-
module EffectiveDatatable
|
5
|
-
module Options
|
6
|
-
|
7
|
-
def initialize_datatable_options
|
8
|
-
@table_columns = _initialize_datatable_options(@table_columns, the_collection)
|
9
|
-
end
|
10
|
-
|
11
|
-
def initialize_attributes(args)
|
12
|
-
_initialize_attributes(args)
|
13
|
-
end
|
14
|
-
|
15
|
-
def initialize_scope_options
|
16
|
-
@scopes = _initialize_scope_options(@scopes)
|
17
|
-
_initialize_current_scope_attribute
|
18
|
-
end
|
19
|
-
|
20
|
-
def initialize_chart_options
|
21
|
-
@charts = _initialize_chart_options(@charts)
|
22
|
-
end
|
23
|
-
|
24
|
-
def quote_sql(name)
|
25
|
-
collection_class.connection.quote_column_name(name) rescue name
|
26
|
-
end
|
27
|
-
|
28
|
-
protected
|
29
|
-
|
30
|
-
def _initialize_attributes(args)
|
31
|
-
args.compact.each do |arg|
|
32
|
-
# ActionController::Parameters / Rails 5 hack. TODO.
|
33
|
-
if arg.respond_to?(:permit)
|
34
|
-
arg = (arg.respond_to?(:to_unsafe_h) ? arg.to_unsafe_h : arg.to_h)
|
35
|
-
end
|
36
|
-
|
37
|
-
raise "#{self.class.name}.new() can only be initialized with a Hash like arguments" unless arg.kind_of?(Hash)
|
38
|
-
|
39
|
-
arg.each { |k, v| self.attributes[k] = v }
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# The scope DSL is
|
44
|
-
# scope :start_date, default_value, options: {}
|
45
|
-
#
|
46
|
-
# The value might already be assigned, but if not, we have to assign the default to attributes
|
47
|
-
|
48
|
-
# A scope comes to us like {:start_date => {default: Time.zone.now, filter: {as: :select, collection: ... input_html :}}}
|
49
|
-
# We want to make sure an input_html: { value: default } exists
|
50
|
-
def _initialize_scope_options(scopes)
|
51
|
-
(scopes || []).each do |name, options|
|
52
|
-
value = attributes.key?(name) ? attributes[name].presence : options[:default]
|
53
|
-
|
54
|
-
if attributes.key?(name) == false
|
55
|
-
self.attributes[name] = options[:default]
|
56
|
-
value = options[:default]
|
57
|
-
end
|
58
|
-
|
59
|
-
if (options[:fallback] || options[:presence]) && attributes[name].blank?
|
60
|
-
self.attributes[name] = options[:default]
|
61
|
-
value = options[:default]
|
62
|
-
end
|
63
|
-
|
64
|
-
options[:filter] ||= {}
|
65
|
-
options[:filter][:input_html] ||= {}
|
66
|
-
options[:filter][:input_html][:value] = value
|
67
|
-
options[:filter][:selected] = value
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def _initialize_current_scope_attribute
|
72
|
-
attributes[:current_scope] ||= klass_scopes.find { |name, options| options[:klass_scope] && options[:default] }.try(:first)
|
73
|
-
|
74
|
-
if attributes[:current_scope].present?
|
75
|
-
attributes[:current_scope] = attributes[:current_scope].to_sym
|
76
|
-
attributes[:current_scope] = nil unless klass_scopes.keys.include?(attributes[:current_scope])
|
77
|
-
end
|
78
|
-
|
79
|
-
if attributes[:current_scope].present?
|
80
|
-
klass_scopes.each { |name, _| attributes[name] = (name == attributes[:current_scope]) }
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def _initialize_chart_options(charts)
|
85
|
-
charts
|
86
|
-
end
|
87
|
-
|
88
|
-
def _initialize_datatable_options(cols, collection)
|
89
|
-
# We set some memoized helper values
|
90
|
-
@collection_class = (collection.respond_to?(:klass) ? collection.klass : self.class)
|
91
|
-
@active_record_collection = (collection.ancestors.include?(ActiveRecord::Base) rescue false)
|
92
|
-
@array_collection = (collection.kind_of?(Array) && (collection.length == 0 || collection.first.kind_of?(Array)))
|
93
|
-
|
94
|
-
# And then parse all the colums
|
95
|
-
sql_table = (collection.table rescue nil)
|
96
|
-
|
97
|
-
# Here we identify all belongs_to associations and build up a Hash like:
|
98
|
-
# {user: {foreign_key: 'user_id', klass: User}, order: {foreign_key: 'order_id', klass: Effective::Order}}
|
99
|
-
belong_tos = (collection.klass.reflect_on_all_associations(:belongs_to) rescue []).inject({}) do |retval, bt|
|
100
|
-
if bt.options[:polymorphic]
|
101
|
-
retval[bt.name.to_s] = {foreign_key: bt.foreign_key, klass: bt.name, polymorphic: true}
|
102
|
-
else
|
103
|
-
klass = bt.klass || (bt.foreign_type.sub('_type', '').classify.constantize rescue nil)
|
104
|
-
retval[bt.name.to_s] = {foreign_key: bt.foreign_key, klass: klass} if bt.foreign_key.present? && klass.present?
|
105
|
-
end
|
106
|
-
|
107
|
-
retval
|
108
|
-
end
|
109
|
-
|
110
|
-
# Figure out has_manys and has_many_belongs_to_many's
|
111
|
-
has_manys = {}
|
112
|
-
has_and_belongs_to_manys = {}
|
113
|
-
|
114
|
-
(collection.klass.reflect_on_all_associations() rescue []).each do |reflect|
|
115
|
-
if reflect.macro == :has_many
|
116
|
-
klass = reflect.klass || (reflect.build_association({}).class)
|
117
|
-
has_manys[reflect.name.to_s] = { klass: klass }
|
118
|
-
elsif reflect.macro == :has_and_belongs_to_many
|
119
|
-
klass = reflect.klass || (reflect.build_association({}).class)
|
120
|
-
has_and_belongs_to_manys[reflect.name.to_s] = { klass: klass }
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
table_columns = (cols || {}).each_with_index do |(name, _), index|
|
125
|
-
sql_column = (collection.columns rescue []).find do |column|
|
126
|
-
column.name == name.to_s || (belong_tos.key?(name) && column.name == belong_tos[name][:foreign_key])
|
127
|
-
end
|
128
|
-
|
129
|
-
cols[name][:array_column] ||= false
|
130
|
-
cols[name][:index] = index # The index of this column in the collection, regardless of hidden table_columns
|
131
|
-
cols[name][:name] ||= name
|
132
|
-
cols[name][:label] ||= name.titleize
|
133
|
-
cols[name][:column] ||= (sql_table && sql_column) ? "#{quote_sql(sql_table.name)}.#{quote_sql(sql_column.name)}" : name
|
134
|
-
cols[name][:width] ||= nil
|
135
|
-
cols[name][:sortable] = true if cols[name][:sortable].nil?
|
136
|
-
cols[name][:visible] = true if cols[name][:visible].nil?
|
137
|
-
|
138
|
-
# Type
|
139
|
-
cols[name][:type] ||= cols[name][:as] # Use as: or type: interchangeably
|
140
|
-
|
141
|
-
cols[name][:type] ||= (
|
142
|
-
if belong_tos.key?(name)
|
143
|
-
if belong_tos[name][:polymorphic]
|
144
|
-
:belongs_to_polymorphic
|
145
|
-
else
|
146
|
-
:belongs_to
|
147
|
-
end
|
148
|
-
elsif has_manys.key?(name)
|
149
|
-
:has_many
|
150
|
-
elsif has_and_belongs_to_manys.key?(name)
|
151
|
-
:has_and_belongs_to_many
|
152
|
-
elsif cols[name][:bulk_actions_column]
|
153
|
-
:bulk_actions_column
|
154
|
-
elsif name.include?('_address') && defined?(EffectiveAddresses) && (collection_class.new rescue nil).respond_to?(:effective_addresses)
|
155
|
-
:effective_address
|
156
|
-
elsif name == 'id' && defined?(EffectiveObfuscation) && collection.respond_to?(:deobfuscate)
|
157
|
-
:obfuscated_id
|
158
|
-
elsif name == 'roles' && defined?(EffectiveRoles) && collection.respond_to?(:with_role)
|
159
|
-
:effective_roles
|
160
|
-
elsif sql_column.try(:type).present?
|
161
|
-
sql_column.type
|
162
|
-
elsif name.end_with?('_id')
|
163
|
-
:integer
|
164
|
-
else
|
165
|
-
:string # When in doubt
|
166
|
-
end
|
167
|
-
)
|
168
|
-
|
169
|
-
cols[name][:class] = "col-#{cols[name][:type]} col-#{name.parameterize} #{cols[name][:class]}".strip
|
170
|
-
|
171
|
-
# Formats
|
172
|
-
if name == 'id' || name.include?('year') || name.end_with?('_id')
|
173
|
-
cols[name][:format] = :non_formatted_integer
|
174
|
-
end
|
175
|
-
|
176
|
-
# Sortable - Disable sorting on these types
|
177
|
-
if [:has_many, :effective_address, :obfuscated_id].include?(cols[name][:type])
|
178
|
-
cols[name][:sortable] = false
|
179
|
-
end
|
180
|
-
|
181
|
-
# EffectiveRoles, if you do table_column :roles, everything just works
|
182
|
-
if cols[name][:type] == :effective_roles
|
183
|
-
cols[name][:column] = sql_table.present? ? "#{quote_sql(sql_table.name)}.#{quote_sql('roles_mask')}" : name
|
184
|
-
end
|
185
|
-
|
186
|
-
# This is a SELECT AS column, or a JOIN column or a delegated object
|
187
|
-
if sql_table.present? && sql_column.blank? && !cols[name][:array_column]
|
188
|
-
cols[name][:sql_as_column] = true
|
189
|
-
end
|
190
|
-
|
191
|
-
cols[name][:filter] = initialize_table_column_filter(cols[name], belong_tos[name], has_manys[name], has_and_belongs_to_manys[name])
|
192
|
-
|
193
|
-
if cols[name][:partial]
|
194
|
-
cols[name][:partial_local] ||= (sql_table.try(:name) || cols[name][:partial].split('/').last(2).first.presence || 'obj').singularize.to_sym
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
end
|
199
|
-
|
200
|
-
def initialize_table_column_filter(column, belongs_to, has_many, has_and_belongs_to_manys)
|
201
|
-
filter = column[:filter]
|
202
|
-
col_type = column[:type]
|
203
|
-
sql_column = column[:column].to_s.upcase
|
204
|
-
|
205
|
-
return {as: :null} if filter == false
|
206
|
-
|
207
|
-
filter = {as: filter.to_sym} if filter.kind_of?(String)
|
208
|
-
filter = {} unless filter.kind_of?(Hash)
|
209
|
-
|
210
|
-
# This is a fix for passing filter[:selected] == false, it needs to be 'false'
|
211
|
-
filter[:selected] = filter[:selected].to_s unless filter[:selected].nil?
|
212
|
-
|
213
|
-
# Allow :values or :collection to be used interchangeably
|
214
|
-
if filter.key?(:values)
|
215
|
-
filter[:collection] ||= filter[:values]
|
216
|
-
end
|
217
|
-
|
218
|
-
# Allow :as or :type to be used interchangeably
|
219
|
-
if filter.key?(:type)
|
220
|
-
filter[:as] ||= filter[:type]
|
221
|
-
end
|
222
|
-
|
223
|
-
# If you pass a collection, just assume it's a select
|
224
|
-
if filter.key?(:collection) && col_type != :belongs_to_polymorphic
|
225
|
-
filter[:as] ||= :select
|
226
|
-
end
|
227
|
-
|
228
|
-
# Fuzzy by default throughout
|
229
|
-
unless filter.key?(:fuzzy)
|
230
|
-
filter[:fuzzy] = true
|
231
|
-
end
|
232
|
-
|
233
|
-
# Check if this is an aggregate column
|
234
|
-
if ['SUM(', 'COUNT(', 'MAX(', 'MIN(', 'AVG('].any? { |str| sql_column.include?(str) }
|
235
|
-
filter[:sql_operation] = :having
|
236
|
-
end
|
237
|
-
|
238
|
-
case col_type
|
239
|
-
when :belongs_to
|
240
|
-
{
|
241
|
-
as: :select,
|
242
|
-
collection: (
|
243
|
-
if belongs_to[:klass].respond_to?(:datatables_filter)
|
244
|
-
Proc.new { belongs_to[:klass].datatables_filter }
|
245
|
-
elsif belongs_to[:klass].respond_to?(:sorted)
|
246
|
-
Proc.new { belongs_to[:klass].sorted }
|
247
|
-
else
|
248
|
-
Proc.new { belongs_to[:klass].all.map { |obj| [obj.to_s, obj.id] }.sort { |x, y| x[0] <=> y[0] } }
|
249
|
-
end
|
250
|
-
)
|
251
|
-
}
|
252
|
-
when :belongs_to_polymorphic
|
253
|
-
{as: :grouped_select, polymorphic: true, collection: {}}
|
254
|
-
when :has_many
|
255
|
-
{
|
256
|
-
as: :select,
|
257
|
-
multiple: true,
|
258
|
-
collection: (
|
259
|
-
if has_many[:klass].respond_to?(:datatables_filter)
|
260
|
-
Proc.new { has_many[:klass].datatables_filter }
|
261
|
-
elsif has_many[:klass].respond_to?(:sorted)
|
262
|
-
Proc.new { has_many[:klass].sorted }
|
263
|
-
else
|
264
|
-
Proc.new { has_many[:klass].all.map { |obj| [obj.to_s, obj.id] }.sort { |x, y| x[0] <=> y[0] } }
|
265
|
-
end
|
266
|
-
)
|
267
|
-
}
|
268
|
-
when :has_and_belongs_to_many
|
269
|
-
{
|
270
|
-
as: :select,
|
271
|
-
multiple: true,
|
272
|
-
collection: (
|
273
|
-
if has_and_belongs_to_manys[:klass].respond_to?(:datatables_filter)
|
274
|
-
Proc.new { has_and_belongs_to_manys[:klass].datatables_filter }
|
275
|
-
elsif has_and_belongs_to_manys[:klass].respond_to?(:sorted)
|
276
|
-
Proc.new { has_and_belongs_to_manys[:klass].sorted }
|
277
|
-
else
|
278
|
-
Proc.new { has_and_belongs_to_manys[:klass].all.map { |obj| [obj.to_s, obj.id] }.sort { |x, y| x[0] <=> y[0] } }
|
279
|
-
end
|
280
|
-
)
|
281
|
-
}
|
282
|
-
when :effective_address
|
283
|
-
{as: :string}
|
284
|
-
when :effective_roles
|
285
|
-
{as: :select, collection: EffectiveRoles.roles}
|
286
|
-
when :obfuscated_id
|
287
|
-
{as: :obfuscated_id}
|
288
|
-
when :integer
|
289
|
-
{as: :number}
|
290
|
-
when :boolean
|
291
|
-
if EffectiveDatatables.boolean_format == :yes_no
|
292
|
-
{as: :boolean, collection: [['Yes', true], ['No', false]] }
|
293
|
-
else
|
294
|
-
{as: :boolean, collection: [['true', true], ['false', false]] }
|
295
|
-
end
|
296
|
-
when :datetime
|
297
|
-
{as: :datetime}
|
298
|
-
when :date
|
299
|
-
{as: :date}
|
300
|
-
when :bulk_actions_column
|
301
|
-
{as: :bulk_actions_column}
|
302
|
-
else
|
303
|
-
{as: :string}
|
304
|
-
end.merge(filter.symbolize_keys)
|
305
|
-
end
|
306
|
-
|
307
|
-
end
|
308
|
-
end
|
309
|
-
end
|
@@ -1,365 +0,0 @@
|
|
1
|
-
# This is extended as class level into Datatable
|
2
|
-
|
3
|
-
module Effective
|
4
|
-
module EffectiveDatatable
|
5
|
-
module Rendering
|
6
|
-
BLANK = ''.freeze
|
7
|
-
|
8
|
-
protected
|
9
|
-
|
10
|
-
# So the idea here is that we want to do as much as possible on the database in ActiveRecord
|
11
|
-
# And then run any array_columns through in post-processed results
|
12
|
-
def table_data
|
13
|
-
col = the_collection
|
14
|
-
|
15
|
-
if active_record_collection?
|
16
|
-
col = table_tool.order(col)
|
17
|
-
col = table_tool.search(col)
|
18
|
-
|
19
|
-
if table_tool.search_terms.present? && array_tool.search_terms.blank?
|
20
|
-
self.display_records = active_record_collection_size(col)
|
21
|
-
end
|
22
|
-
|
23
|
-
if array_tool.search_terms.present?
|
24
|
-
col = self.arrayize(col)
|
25
|
-
col = array_tool.search(col)
|
26
|
-
self.display_records = col.size
|
27
|
-
end
|
28
|
-
|
29
|
-
if array_tool.order_by_column.present?
|
30
|
-
col = self.arrayize(col)
|
31
|
-
col = array_tool.order(col)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
if col.kind_of?(Array)
|
36
|
-
col = array_tool.order(col)
|
37
|
-
col = array_tool.search(col)
|
38
|
-
end
|
39
|
-
|
40
|
-
self.display_records ||= total_records
|
41
|
-
|
42
|
-
if col.kind_of?(Array)
|
43
|
-
col = array_tool.paginate(col)
|
44
|
-
else
|
45
|
-
col = table_tool.paginate(col)
|
46
|
-
end
|
47
|
-
|
48
|
-
col = self.arrayize(col)
|
49
|
-
|
50
|
-
self.format(col)
|
51
|
-
col = self.finalize(col)
|
52
|
-
end
|
53
|
-
|
54
|
-
def arrayize(collection)
|
55
|
-
return collection if @arrayized # Prevent the collection from being arrayized more than once
|
56
|
-
@arrayized = true
|
57
|
-
|
58
|
-
# We want to use the render :collection for each column that renders partials
|
59
|
-
rendered = {}
|
60
|
-
(display_table_columns || table_columns).each do |name, opts|
|
61
|
-
if opts[:partial] && opts[:visible]
|
62
|
-
locals = HashWithIndifferentAccess.new(
|
63
|
-
datatable: self,
|
64
|
-
table_column: table_columns[name],
|
65
|
-
controller_namespace: controller_namespace,
|
66
|
-
show_action: (opts[:partial_locals] || {})[:show_action],
|
67
|
-
edit_action: (opts[:partial_locals] || {})[:edit_action],
|
68
|
-
destroy_action: (opts[:partial_locals] || {})[:destroy_action],
|
69
|
-
unarchive_action: (opts[:partial_locals] || {})[:unarchive_action]
|
70
|
-
)
|
71
|
-
|
72
|
-
locals.merge!(opts[:partial_locals]) if opts[:partial_locals]
|
73
|
-
|
74
|
-
if opts[:type] == :actions
|
75
|
-
add_actions_column_locals(locals)
|
76
|
-
locals[:actions_block] = opts[:actions_block]
|
77
|
-
end
|
78
|
-
|
79
|
-
rendered[name] = (render(
|
80
|
-
:partial => opts[:partial],
|
81
|
-
:as => opts[:partial_local],
|
82
|
-
:collection => collection,
|
83
|
-
:formats => :html,
|
84
|
-
:locals => locals,
|
85
|
-
:spacer_template => '/effective/datatables/spacer_template',
|
86
|
-
) || '').split('EFFECTIVEDATATABLESSPACER')
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
collection.each_with_index.map do |obj, index|
|
91
|
-
(display_table_columns || table_columns).map do |name, opts|
|
92
|
-
begin
|
93
|
-
if opts[:visible] == false && (name != order_name.to_s) # Sort by invisible array column
|
94
|
-
BLANK
|
95
|
-
elsif opts[:block]
|
96
|
-
begin
|
97
|
-
if active_record_collection?
|
98
|
-
view.instance_exec(obj, collection, self, &opts[:block])
|
99
|
-
else
|
100
|
-
view.instance_exec(obj, obj[opts[:index]], collection, self, &opts[:block])
|
101
|
-
end
|
102
|
-
rescue NoMethodError => e
|
103
|
-
if opts[:type] == :actions && e.message == 'super called outside of method'
|
104
|
-
rendered[name][index]
|
105
|
-
else
|
106
|
-
raise(e)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
elsif opts[:proc]
|
110
|
-
if active_record_collection?
|
111
|
-
view.instance_exec(obj, collection, self, &opts[:proc])
|
112
|
-
else
|
113
|
-
view.instance_exec(obj, obj[opts[:index]], collection, self, &opts[:proc])
|
114
|
-
end
|
115
|
-
elsif opts[:partial]
|
116
|
-
rendered[name][index]
|
117
|
-
elsif opts[:type] == :belongs_to
|
118
|
-
(obj.send(name) rescue nil).to_s
|
119
|
-
elsif opts[:type] == :belongs_to_polymorphic
|
120
|
-
(obj.send(name) rescue nil).to_s
|
121
|
-
elsif opts[:type] == :has_many
|
122
|
-
(obj.send(name).map { |obj| obj.to_s }.join('<br>') rescue BLANK)
|
123
|
-
elsif opts[:type] == :has_and_belongs_to_many
|
124
|
-
(obj.send(name).map { |obj| obj.to_s }.join('<br>') rescue BLANK)
|
125
|
-
elsif opts[:type] == :bulk_actions_column
|
126
|
-
BLANK
|
127
|
-
elsif opts[:type] == :year
|
128
|
-
obj.send(name).try(:year)
|
129
|
-
elsif opts[:type] == :obfuscated_id
|
130
|
-
(obj.send(:to_param) rescue nil).to_s
|
131
|
-
elsif opts[:type] == :effective_address
|
132
|
-
(Array(obj.send(name)) rescue [])
|
133
|
-
elsif opts[:type] == :effective_roles
|
134
|
-
(obj.send(:roles) rescue [])
|
135
|
-
elsif obj.kind_of?(Array) # Array backed collection
|
136
|
-
obj[opts[:index]]
|
137
|
-
elsif opts[:sql_as_column]
|
138
|
-
obj[name] || obj.send(name)
|
139
|
-
else
|
140
|
-
obj.send(name)
|
141
|
-
end
|
142
|
-
rescue => e
|
143
|
-
Rails.env.production? ? obj.try(:[], name) : raise(e)
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def format(collection)
|
150
|
-
collection.each do |row|
|
151
|
-
(display_table_columns || table_columns).each_with_index do |(name, opts), index|
|
152
|
-
value = row[index]
|
153
|
-
next if value == nil || value == BLANK || opts[:visible] == false
|
154
|
-
next if opts[:block] || opts[:partial] || opts[:proc]
|
155
|
-
|
156
|
-
if opts[:sql_as_column]
|
157
|
-
row[index] = value.to_s
|
158
|
-
end
|
159
|
-
|
160
|
-
case (opts[:format] || opts[:type])
|
161
|
-
when :belongs_to, :belongs_to_polymorphic
|
162
|
-
row[index] = value.to_s
|
163
|
-
when :has_many
|
164
|
-
if value.kind_of?(Array)
|
165
|
-
if value.length == 0
|
166
|
-
row[index] = BLANK
|
167
|
-
elsif value.length == 1
|
168
|
-
row[index] = value.first.to_s
|
169
|
-
elsif opts[:sentence]
|
170
|
-
row[index] = value.map { |v| v.to_s }.to_sentence
|
171
|
-
else
|
172
|
-
row[index] = value.map { |v| v.to_s }.join('<br>')
|
173
|
-
end
|
174
|
-
end
|
175
|
-
when :effective_address
|
176
|
-
row[index] = value.map { |addr| addr.to_html }.join('<br>')
|
177
|
-
when :effective_roles
|
178
|
-
row[index] = value.join(', ')
|
179
|
-
when :datetime
|
180
|
-
row[index] = value.strftime(EffectiveDatatables.datetime_format) rescue BLANK
|
181
|
-
when :date
|
182
|
-
row[index] = value.strftime(EffectiveDatatables.date_format) rescue BLANK
|
183
|
-
when :price
|
184
|
-
# This is an integer value, "number of cents"
|
185
|
-
raise 'column type: price expects an Integer representing the number of cents' unless value.kind_of?(Integer)
|
186
|
-
row[index] = number_to_currency(value / 100.0)
|
187
|
-
when :currency
|
188
|
-
row[index] = number_to_currency(value || 0)
|
189
|
-
when :percentage
|
190
|
-
row[index] = number_to_percentage(value || 0)
|
191
|
-
when :integer
|
192
|
-
if EffectiveDatatables.integer_format.kind_of?(Symbol)
|
193
|
-
row[index] = view.instance_exec { public_send(EffectiveDatatables.integer_format, value) }
|
194
|
-
elsif EffectiveDatatables.integer_format.respond_to?(:call)
|
195
|
-
row[index] = view.instance_exec { EffectiveDatatables.integer_format.call(value) }
|
196
|
-
end
|
197
|
-
when :boolean
|
198
|
-
if EffectiveDatatables.boolean_format == :yes_no && value == true
|
199
|
-
row[index] = 'Yes'
|
200
|
-
elsif EffectiveDatatables.boolean_format == :yes_no && value == false
|
201
|
-
row[index] = 'No'
|
202
|
-
end
|
203
|
-
when :string
|
204
|
-
row[index] = mail_to(value) if name == 'email'
|
205
|
-
else
|
206
|
-
; # Nothing
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
collection
|
212
|
-
end
|
213
|
-
|
214
|
-
# This should return an Array of values the same length as table_data
|
215
|
-
def aggregate_data(table_data)
|
216
|
-
return false unless aggregates.present?
|
217
|
-
|
218
|
-
values = table_data.transpose
|
219
|
-
|
220
|
-
aggregates.map do |name, options|
|
221
|
-
(display_table_columns || table_columns).map.with_index do |(name, column), index|
|
222
|
-
|
223
|
-
if column[:visible] != true
|
224
|
-
''
|
225
|
-
elsif (options[:block] || options[:proc]).respond_to?(:call)
|
226
|
-
view.instance_exec(column, (values[index] || []), values, &(options[:block] || options[:proc]))
|
227
|
-
else
|
228
|
-
''
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
protected
|
235
|
-
|
236
|
-
def add_actions_column_locals(locals)
|
237
|
-
return unless active_record_collection?
|
238
|
-
|
239
|
-
if locals[:show_action] == :authorize
|
240
|
-
locals[:show_action] = (EffectiveDatatables.authorized?(controller, :show, collection_class) rescue false)
|
241
|
-
end
|
242
|
-
|
243
|
-
if locals[:edit_action] == :authorize
|
244
|
-
locals[:edit_action] = (EffectiveDatatables.authorized?(controller, :edit, collection_class) rescue false)
|
245
|
-
end
|
246
|
-
|
247
|
-
if locals[:destroy_action] == :authorize
|
248
|
-
locals[:destroy_action] = (EffectiveDatatables.authorized?(controller, :destroy, collection_class) rescue false)
|
249
|
-
end
|
250
|
-
|
251
|
-
if locals[:unarchive_action] == :authorize
|
252
|
-
locals[:unarchive_action] = (EffectiveDatatables.authorized?(controller, :unarchive, collection_class) rescue false)
|
253
|
-
end
|
254
|
-
|
255
|
-
# Then we look at the routes to see if these actions _actually_ exist
|
256
|
-
|
257
|
-
routes = Rails.application.routes
|
258
|
-
|
259
|
-
begin
|
260
|
-
resource = collection_class.new(id: 123)
|
261
|
-
resource.define_singleton_method(:persisted?) { true } # We override persisted? to get the correct action urls
|
262
|
-
raise 'no to param' unless resource.to_param.present?
|
263
|
-
rescue => e
|
264
|
-
return
|
265
|
-
end
|
266
|
-
|
267
|
-
if (locals[:show_action] == true || locals[:show_action] == :authorize_each) && !locals[:show_path]
|
268
|
-
# Try our namespace
|
269
|
-
url = (view.polymorphic_path([*controller_namespace, resource]) rescue false)
|
270
|
-
url = false if url && !(routes.recognize_path(url) rescue false)
|
271
|
-
|
272
|
-
# Try no namespace
|
273
|
-
unless url
|
274
|
-
url = (view.polymorphic_path(resource) rescue false)
|
275
|
-
url = false if url && !(routes.recognize_path(url) rescue false)
|
276
|
-
end
|
277
|
-
|
278
|
-
# So if we have a URL, this is an action we can link to
|
279
|
-
if url
|
280
|
-
locals[:show_path] = url.gsub("/#{resource.to_param}", '/:to_param')
|
281
|
-
else
|
282
|
-
locals[:show_action] = false
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
if (locals[:edit_action] == true || locals[:edit_action] == :authorize_each) && !locals[:edit_path]
|
287
|
-
# Try our namespace
|
288
|
-
url = (view.edit_polymorphic_path([*controller_namespace, resource]) rescue false)
|
289
|
-
url = false if url && !(routes.recognize_path(url) rescue false)
|
290
|
-
|
291
|
-
# Try no namespace
|
292
|
-
unless url
|
293
|
-
url = (view.edit_polymorphic_path(resource) rescue false)
|
294
|
-
url = false if url && !(routes.recognize_path(url) rescue false)
|
295
|
-
end
|
296
|
-
|
297
|
-
# So if we have a URL, this is an action we can link to
|
298
|
-
if url
|
299
|
-
locals[:edit_path] = url.gsub("/#{resource.to_param}", '/:to_param')
|
300
|
-
else
|
301
|
-
locals[:edit_action] = false
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
if (locals[:destroy_action] == true || locals[:destroy_action] == :authorize_each) && !locals[:destroy_path]
|
306
|
-
# Try our namespace
|
307
|
-
url = (view.polymorphic_path([*controller_namespace, resource]) rescue false)
|
308
|
-
url = false if url && !(routes.recognize_path(url, method: :delete) rescue false)
|
309
|
-
|
310
|
-
# Try no namespace
|
311
|
-
unless url
|
312
|
-
url = (view.polymorphic_path(resource) rescue false)
|
313
|
-
url = false if url && !(routes.recognize_path(url, method: :delete) rescue false)
|
314
|
-
end
|
315
|
-
|
316
|
-
# So if we have a URL, this is an action we can link to
|
317
|
-
if url
|
318
|
-
locals[:destroy_path] = url.gsub("/#{resource.to_param}", '/:to_param')
|
319
|
-
else
|
320
|
-
locals[:destroy_action] = false
|
321
|
-
end
|
322
|
-
end
|
323
|
-
|
324
|
-
if resource.respond_to?(:archived?)
|
325
|
-
if (locals[:unarchive_action] == true || locals[:unarchive_action] == :authorize_each) && !locals[:unarchive_path]
|
326
|
-
# Try our namespace
|
327
|
-
url = (view.polymorphic_path([*controller_namespace, resource], action: :unarchive) rescue false)
|
328
|
-
url = false if url && !(routes.recognize_path(url) rescue false)
|
329
|
-
|
330
|
-
# Try no namespace
|
331
|
-
unless url
|
332
|
-
url = (view.polymorphic_path(resource, action: :unarchive) rescue false)
|
333
|
-
url = false if url && !(routes.recognize_path(url) rescue false)
|
334
|
-
end
|
335
|
-
|
336
|
-
# So if we have a URL, this is an action we can link to
|
337
|
-
if url
|
338
|
-
locals[:unarchive_path] = url.gsub("/#{resource.to_param}", '/:to_param')
|
339
|
-
else
|
340
|
-
locals[:unarchive_action] = false
|
341
|
-
end
|
342
|
-
end
|
343
|
-
else
|
344
|
-
locals[:unarchive_action] = false
|
345
|
-
end
|
346
|
-
|
347
|
-
end
|
348
|
-
|
349
|
-
private
|
350
|
-
|
351
|
-
def controller_namespace
|
352
|
-
@controller_namespace ||= (
|
353
|
-
path = if attributes[:referer].present?
|
354
|
-
URI(attributes[:referer]).path
|
355
|
-
else
|
356
|
-
view.controller_path
|
357
|
-
end
|
358
|
-
|
359
|
-
path.split('/')[0...-1].map { |path| path.downcase.to_sym if path.present? }.compact
|
360
|
-
)
|
361
|
-
end
|
362
|
-
|
363
|
-
end # / Rendering
|
364
|
-
end
|
365
|
-
end
|