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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +632 -512
  3. data/app/assets/javascripts/dataTables/buttons/buttons.html5.js +176 -177
  4. data/app/assets/javascripts/dataTables/buttons/buttons.print.js +2 -0
  5. data/app/assets/javascripts/dataTables/buttons/dataTables.buttons.js +14 -14
  6. data/app/assets/javascripts/dataTables/dataTables.bootstrap.js +1 -1
  7. data/app/assets/javascripts/dataTables/jquery.dataTables.js +246 -217
  8. data/app/assets/javascripts/effective_datatables.js +2 -3
  9. data/app/assets/javascripts/effective_datatables/events.js.coffee +7 -0
  10. data/app/assets/javascripts/effective_datatables/filters.js.coffee +6 -0
  11. data/app/assets/javascripts/effective_datatables/initialize.js.coffee +42 -39
  12. data/app/assets/javascripts/effective_datatables/reset.js.coffee +7 -0
  13. data/app/assets/javascripts/vendor/jquery.delayedChange.js +1 -1
  14. data/app/assets/stylesheets/dataTables/dataTables.bootstrap.css +0 -1
  15. data/app/assets/stylesheets/effective_datatables.scss +1 -2
  16. data/app/assets/stylesheets/effective_datatables/{_scopes.scss → _filters.scss} +1 -1
  17. data/app/assets/stylesheets/effective_datatables/_overrides.scss +1 -1
  18. data/app/controllers/effective/datatables_controller.rb +2 -4
  19. data/app/helpers/effective_datatables_helper.rb +56 -91
  20. data/app/helpers/effective_datatables_private_helper.rb +55 -64
  21. data/app/models/effective/datatable.rb +103 -177
  22. data/app/models/effective/datatable_column.rb +28 -0
  23. data/app/models/effective/datatable_column_tool.rb +110 -0
  24. data/app/models/effective/datatable_dsl_tool.rb +28 -0
  25. data/app/models/effective/datatable_value_tool.rb +142 -0
  26. data/app/models/effective/effective_datatable/attributes.rb +25 -0
  27. data/app/models/effective/effective_datatable/collection.rb +38 -0
  28. data/app/models/effective/effective_datatable/compute.rb +154 -0
  29. data/app/models/effective/effective_datatable/cookie.rb +29 -0
  30. data/app/models/effective/effective_datatable/dsl.rb +14 -8
  31. data/app/models/effective/effective_datatable/dsl/bulk_actions.rb +5 -6
  32. data/app/models/effective/effective_datatable/dsl/charts.rb +7 -9
  33. data/app/models/effective/effective_datatable/dsl/datatable.rb +107 -57
  34. data/app/models/effective/effective_datatable/dsl/filters.rb +50 -0
  35. data/app/models/effective/effective_datatable/format.rb +157 -0
  36. data/app/models/effective/effective_datatable/hooks.rb +0 -18
  37. data/app/models/effective/effective_datatable/params.rb +34 -0
  38. data/app/models/effective/effective_datatable/resource.rb +108 -0
  39. data/app/models/effective/effective_datatable/state.rb +178 -0
  40. data/app/views/effective/datatables/_actions_column.html.haml +9 -42
  41. data/app/views/effective/datatables/_bulk_actions_column.html.haml +1 -1
  42. data/app/views/effective/datatables/_bulk_actions_dropdown.html.haml +2 -3
  43. data/app/views/effective/datatables/_chart.html.haml +1 -1
  44. data/app/views/effective/datatables/_datatable.html.haml +7 -25
  45. data/app/views/effective/datatables/_filters.html.haml +21 -0
  46. data/app/views/effective/datatables/_reset.html.haml +2 -0
  47. data/app/views/effective/datatables/_resource_column.html.haml +8 -0
  48. data/app/views/effective/datatables/index.html.haml +0 -1
  49. data/config/effective_datatables.rb +9 -32
  50. data/lib/effective_datatables.rb +2 -6
  51. data/lib/effective_datatables/engine.rb +1 -1
  52. data/lib/effective_datatables/version.rb +1 -1
  53. data/lib/generators/effective_datatables/install_generator.rb +2 -2
  54. metadata +39 -19
  55. data/app/assets/javascripts/dataTables/colreorder/dataTables.colReorder.js +0 -27
  56. data/app/assets/javascripts/dataTables/jszip/jszip.js +0 -9155
  57. data/app/assets/javascripts/effective_datatables/scopes.js.coffee +0 -9
  58. data/app/models/effective/active_record_datatable_tool.rb +0 -242
  59. data/app/models/effective/array_datatable_tool.rb +0 -97
  60. data/app/models/effective/effective_datatable/ajax.rb +0 -101
  61. data/app/models/effective/effective_datatable/charts.rb +0 -20
  62. data/app/models/effective/effective_datatable/dsl/scopes.rb +0 -23
  63. data/app/models/effective/effective_datatable/helpers.rb +0 -24
  64. data/app/models/effective/effective_datatable/options.rb +0 -309
  65. data/app/models/effective/effective_datatable/rendering.rb +0 -365
  66. data/app/views/effective/datatables/_scopes.html.haml +0 -21
@@ -7,24 +7,6 @@ module Effective
7
7
  collection
8
8
  end
9
9
 
10
- # Override this function to perform custom searching on a column
11
- def search_column(collection, table_column, search_term, sql_column_or_index)
12
- if table_column[:array_column]
13
- array_tool.search_column_with_defaults(collection, table_column, search_term, sql_column_or_index)
14
- else
15
- table_tool.search_column_with_defaults(collection, table_column, search_term, sql_column_or_index)
16
- end
17
- end
18
-
19
- # Override this function to perform custom ordering on a column
20
- # direction will be :asc or :desc
21
- def order_column(collection, table_column, direction, sql_column_or_index)
22
- if table_column[:array_column]
23
- array_tool.order_column_with_defaults(collection, table_column, direction, sql_column_or_index)
24
- else
25
- table_tool.order_column_with_defaults(collection, table_column, direction, sql_column_or_index)
26
- end
27
- end
28
10
  end
29
11
  end
30
12
  end
@@ -0,0 +1,34 @@
1
+ module Effective
2
+ module EffectiveDatatable
3
+ module Params
4
+
5
+ private
6
+
7
+ def datatables_ajax_request?
8
+ view && view.params[:draw] && view.params[:columns] && view.params[:id] == to_param
9
+ end
10
+
11
+ def params
12
+ return {} unless view.present?
13
+ @params ||= {}.tap do |params|
14
+ Rack::Utils.parse_query(URI(view.request.referer.presence || '/').query).each { |k, v| params[k.to_sym] = v }
15
+ view.params.each { |k, v| params[k.to_sym] = v }
16
+ end
17
+ end
18
+
19
+ def filter_params
20
+ params.select { |name, value| _filters.key?(name.to_sym) }
21
+ end
22
+
23
+ def scope_param
24
+ params[:scope].to_sym if params.key?(:scope)
25
+ end
26
+
27
+ def search_params
28
+ params.select do |name, value|
29
+ columns.key?(name) && (name != :id) && !value.kind_of?(Hash) && value.class.name != 'ActionController::Parameters'.freeze
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,108 @@
1
+ module Effective
2
+ module EffectiveDatatable
3
+ module Resource
4
+
5
+ def admin_namespace?
6
+ controller_namespace == 'admin'
7
+ end
8
+
9
+ def controller_namespace
10
+ @attributes[:controller_namespace]
11
+ end
12
+
13
+ private
14
+
15
+ # This looks at all the columns and figures out the as:
16
+ def load_resource!
17
+ @resource = Effective::Resource.new(collection_class, namespace: controller_namespace)
18
+
19
+ if active_record_collection?
20
+ columns.each do |name, opts|
21
+ opts[:as] ||= resource.sql_type(name)
22
+ opts[:sql_column] = (resource.sql_column(name) || false) if opts[:sql_column].nil?
23
+
24
+ case opts[:as]
25
+ when *resource.macros
26
+ opts[:resource] = Effective::Resource.new(resource.associated(name), namespace: controller_namespace)
27
+ opts[:sql_column] ||= name
28
+ when Class
29
+ if opts[:as].ancestors.include?(ActiveRecord::Base)
30
+ opts[:resource] = Effective::Resource.new(opts[:as], namespace: controller_namespace)
31
+ opts[:as] = :resource
32
+ opts[:sql_column] ||= name
33
+ end
34
+ when :effective_addresses
35
+ opts[:resource] = Effective::Resource.new(resource.associated(name), namespace: controller_namespace)
36
+ opts[:sql_column] = :effective_addresses
37
+ when :effective_roles
38
+ opts[:sql_column] = :effective_roles
39
+ when :string # This is the fallback
40
+ # Anything that doesn't belong to the model or the sql table, we assume is a SELECT SUM|AVG|RANK() as fancy
41
+ if (resource.table && resource.column(name).blank?)
42
+ opts[:sql_as_column] = true
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ if array_collection?
49
+ row = collection.first
50
+
51
+ columns.each do |name, opts|
52
+ if opts[:as].kind_of?(Class) && opts[:as].ancestors.include?(ActiveRecord::Base)
53
+ opts[:resource] = Effective::Resource.new(opts[:as], namespace: controller_namespace)
54
+ opts[:as] = :resource
55
+ elsif opts[:as] == nil
56
+ if (value = Array(row[opts[:index]]).first).kind_of?(ActiveRecord::Base)
57
+ opts[:resource] = Effective::Resource.new(value, namespace: controller_namespace)
58
+ opts[:as] = :resource
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ columns.each do |name, opts|
65
+ opts[:as] ||= :string
66
+ opts[:as] = :email if (opts[:as] == :string && name == :email)
67
+
68
+ opts[:partial] ||= '/effective/datatables/resource_column' if (opts[:resource] && opts[:as] != :effective_addresses)
69
+
70
+ opts[:col_class] = "col-#{opts[:as]} col-#{name.to_s.parameterize} #{opts[:col_class]}".strip
71
+ end
72
+
73
+ load_resource_search!
74
+ end
75
+
76
+ def load_resource_search!
77
+ columns.each do |name, opts|
78
+
79
+ case opts[:search]
80
+ when false
81
+ opts[:search] = { as: :null }; next
82
+ when Symbol
83
+ opts[:search] = { as: opts[:search] }
84
+ when Array, ActiveRecord::Relation
85
+ opts[:search] = { collection: opts[:search] }
86
+ end
87
+
88
+ search = opts[:search]
89
+
90
+ if search[:collection].kind_of?(ActiveRecord::Relation)
91
+ search[:collection] = search[:collection].map { |obj| [obj.to_s, obj.to_param] }
92
+ elsif search[:collection].kind_of?(Array)
93
+ search[:collection].each { |obj| obj[1] = 'nil' if obj[1] == nil }
94
+ end
95
+
96
+ search[:as] ||= :select if (search.key?(:collection) && opts[:as] != :belongs_to_polymorphic)
97
+ search[:fuzzy] = true unless search.key?(:fuzzy)
98
+
99
+ if array_collection? && opts[:resource].present?
100
+ search.reverse_merge!(resource.search_form_field(name, collection.first[opts[:index]]))
101
+ else
102
+ search.reverse_merge!(resource.search_form_field(name, opts[:as]))
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,178 @@
1
+ module Effective
2
+ module EffectiveDatatable
3
+ module State
4
+
5
+ def scope
6
+ state[:scope]
7
+ end
8
+ alias_method :current_scope, :scope
9
+ alias_method :scopes, :scope
10
+
11
+ def filter
12
+ state[:filter]
13
+ end
14
+ alias_method :filters, :filter
15
+
16
+ def display_length
17
+ state[:length]
18
+ end
19
+
20
+ def display_start
21
+ state[:start]
22
+ end
23
+
24
+ def order_direction
25
+ state[:order_dir]
26
+ end
27
+
28
+ def order_index
29
+ state[:order_index]
30
+ end
31
+
32
+ def order_name
33
+ state[:order_name]
34
+ end
35
+
36
+ def search
37
+ state[:search]
38
+ end
39
+
40
+ def page
41
+ state[:start].to_i / state[:length] + 1
42
+ end
43
+
44
+ def per_page
45
+ state[:length]
46
+ end
47
+
48
+ private
49
+
50
+ # This is called first. Our initial state is set.
51
+ def initial_state
52
+ {
53
+ filter: {},
54
+ length: nil,
55
+ order_name: nil,
56
+ order_dir: nil,
57
+ order_index: nil,
58
+ params: 0,
59
+ scope: nil,
60
+ start: 0,
61
+ search: {},
62
+ visible: {}
63
+ }
64
+ end
65
+
66
+ def load_filters!
67
+ state[:filter] = _filters.inject({}) { |h, (name, opts)| h[name] = opts[:value]; h }
68
+ state[:scope] = _scopes.find { |_, opts| opts[:default] }.try(:first) || _scopes.keys.first
69
+ end
70
+
71
+ def load_filter_params!
72
+ filter_params.each { |name, value| state[:filter][name] = parse_filter_value(_filters[name], value) }
73
+ state[:scope] = scope_param if scope_param
74
+ end
75
+
76
+ def fill_empty_filters!
77
+ state[:filter].each do |name, value|
78
+ next unless (value.nil? && _filters[name][:required])
79
+ state[:filter][name] = _filters[name][:value]
80
+ end
81
+ end
82
+
83
+ def load_state!
84
+ if datatables_ajax_request?
85
+ load_filter_params!
86
+ load_ajax_state!
87
+ elsif cookie.present? && cookie[:state][:params] == params.length
88
+ load_cookie_state!
89
+ else
90
+ # Nothing to do for default state
91
+ end
92
+
93
+ load_filter_params! unless datatables_ajax_request?
94
+ fill_empty_filters!
95
+ end
96
+
97
+ def load_ajax_state!
98
+ state[:length] = params[:length].to_i
99
+
100
+ state[:order_dir] = (params[:order]['0'][:dir] == 'desc' ? :desc : :asc)
101
+ state[:order_index] = params[:order]['0'][:column].to_i
102
+
103
+ state[:scope] = _scopes.keys.find { |name| params[:scope] == name.to_s }
104
+ state[:start] = params[:start].to_i
105
+
106
+ state[:search] = {}
107
+ state[:visible] = {}
108
+
109
+ params[:columns].values.each do |params|
110
+ name = params[:name].to_sym
111
+
112
+ if params[:search][:value].present? && !['null'].include?(params[:search][:value])
113
+ state[:search][name] = params[:search][:value]
114
+ end
115
+
116
+ state[:visible][name] = (params[:visible] == 'true')
117
+ end
118
+
119
+ (params[:filter] || {}).each do |name, value|
120
+ name = name.to_sym
121
+ raise "unexpected filter name: #{name}" unless _filters.key?(name)
122
+
123
+ state[:filter][name] = parse_filter_value(_filters[name], value)
124
+ end
125
+
126
+ state[:params] = cookie[:state][:params]
127
+ end
128
+
129
+ def load_cookie_state!
130
+ @state = cookie[:state]
131
+ end
132
+
133
+ def load_columns!
134
+ state[:length] ||= EffectiveDatatables.default_length
135
+
136
+ if columns.present?
137
+ if order_index.present?
138
+ state[:order_name] = columns.keys[order_index]
139
+ end
140
+
141
+ state[:order_name] ||= columns.find { |name, opts| opts[:sort] }.first
142
+ raise "order column :#{order_name} must exist as a col or val" unless columns[order_name]
143
+
144
+ state[:order_index] = columns[order_name][:index]
145
+ end
146
+
147
+ # Set default order direction
148
+ state[:order_dir] ||= ['_at', '_on', 'date'].any? { |str| order_name.to_s.end_with?(str) } ? :desc : :asc
149
+
150
+ if state[:search].blank?
151
+ columns.each do |name, opts|
152
+ state[:search][name] = opts[:search][:value] if opts[:search].kind_of?(Hash) && opts[:search].key?(:value)
153
+ end
154
+ end
155
+
156
+ columns.each do |name, opts|
157
+ state[:visible][name] = opts[:visible] unless state[:visible].key?(name)
158
+ end
159
+
160
+ unless datatables_ajax_request?
161
+ search_params.each { |name, value| state[:search][name] = value }
162
+ state[:params] = params.length
163
+ end
164
+
165
+ state[:visible].delete_if { |name, _| columns.key?(name) == false }
166
+ state[:search].delete_if { |name, _| columns.key?(name) == false }
167
+ end
168
+
169
+ # The incoming value could be from the passed page params or from the AJAX request.
170
+ # When we parse an incoming filter term for this filter.
171
+ def parse_filter_value(filter, value)
172
+ return filter[:parse].call(value) if filter[:parse]
173
+ Effective::Attribute.new(filter[:value]).parse(value, name: filter[:name])
174
+ end
175
+
176
+ end
177
+ end
178
+ end
@@ -1,44 +1,11 @@
1
- :ruby
2
- if show_action == :authorize_each
3
- show_action = (EffectiveDatatables.authorized?(controller, :show, resource) rescue false)
4
- elsif show_action.respond_to?(:call)
5
- show_action = instance_exec(resource, &show_action)
6
- end
1
+ - if show_path
2
+ - if show_action == true || (EffectiveDatatables.authorized?(controller, :show, resource) rescue false)
3
+ = show_icon_to send(show_path, resource.to_param)
7
4
 
8
- if edit_action == :authorize_each
9
- edit_action = (EffectiveDatatables.authorized?(controller, :edit, resource) rescue false)
10
- elsif edit_action.respond_to?(:call)
11
- edit_action = instance_exec(resource, &edit_action)
12
- end
5
+ - if edit_path
6
+ - if edit_action == true || (EffectiveDatatables.authorized?(controller, :edit, resource) rescue false)
7
+ = edit_icon_to send(edit_path, resource.to_param)
13
8
 
14
- if destroy_action == :authorize_each
15
- destroy_action = (EffectiveDatatables.authorized?(controller, :destroy, resource) rescue false)
16
- elsif destroy_action.respond_to?(:call)
17
- destroy_action = instance_exec(resource, &destroy_action)
18
- end
19
-
20
- if unarchive_action == :authorize_each
21
- unarchive_action = (EffectiveDatatables.authorized?(controller, :unarchive, resource) rescue false)
22
- elsif unarchive_action.respond_to?(:call)
23
- unarchive_action = instance_exec(resource, &unarchive_action)
24
- end
25
-
26
- - if show_action && defined?(show_path)
27
- = show_icon_to show_path.gsub(':to_param', resource.to_param)
28
-
29
- - if edit_action && defined?(edit_path)
30
- = edit_icon_to edit_path.gsub(':to_param', resource.to_param)
31
-
32
- - if destroy_action && defined?(destroy_path)
33
- - if resource.respond_to?(:archived?) && !resource.archived?
34
- = archive_icon_to destroy_path.gsub(':to_param', resource.to_param)
35
- - elsif resource.respond_to?(:archived?) == false
36
- = destroy_icon_to destroy_path.gsub(':to_param', resource.to_param)
37
-
38
- - if unarchive_action && defined?(unarchive_path)
39
- - if resource.respond_to?(:archived?) && resource.archived?
40
- = unarchive_icon_to unarchive_path.gsub(':to_param', resource.to_param)
41
-
42
- - if actions_block
43
- = capture do
44
- - instance_exec(resource, &actions_block)
9
+ - if destroy_path
10
+ - if destroy_action == true || (EffectiveDatatables.authorized?(controller, :destroy, resource) rescue false)
11
+ = destroy_icon_to send(destroy_path, resource.to_param)
@@ -1,2 +1,2 @@
1
- - id = (resource.public_send(resource_method) rescue resource.object_id)
1
+ - id = (resource.try(:to_param) || resource.try(:id) || resource.object_id)
2
2
  = check_box_tag 'bulk_actions_resources[]', id, false, autocomplete: 'off', id: "bulk_actions_resource_#{id}", data: { role: 'bulk-actions-resource' }, onClick: 'event.stopPropagation();'
@@ -3,9 +3,8 @@
3
3
  Bulk Actions
4
4
  %span.caret
5
5
  %ul.dropdown-menu
6
- - if dropdown_block.respond_to?(:call)
7
- = capture do
8
- - instance_exec(&dropdown_block)
6
+ - if datatable._bulk_actions.present?
7
+ = datatable._bulk_actions.join.html_safe
9
8
  - else
10
9
  %li
11
10
  %a{href: '#'} No bulk actions
@@ -1 +1 @@
1
- .effective-datatables-chart{id: "#{chart[:name].to_s.parameterize}-chart-#{Time.zone.now.nsec}", data: {name: chart[:name], type: chart[:type], options: chart[:options].to_json, data: chart[:data].to_json}}
1
+ .effective-datatables-chart{id: "#{chart[:name].to_s.parameterize}-chart-#{Time.zone.now.nsec}", data: { name: chart[:name], as: chart[:as], options: chart[:options].to_json, data: chart_data.to_json }}
@@ -1,36 +1,18 @@
1
- :ruby
2
- effective_datatable_params = {
3
- id: "#{datatable.to_param}-table-#{Time.zone.now.nsec}",
4
- class: "#{datatable.table_html_class}",
5
- data: {
6
- 'effective-form-inputs' => defined?(EffectiveFormInputs),
7
- 'bulk-actions' => datatable_bulk_actions(datatable),
8
- 'columns' => datatable_columns(datatable),
9
- 'input-js-options' => local_assigns[:input_js_options],
10
- 'simple' => datatable.simple?.to_s,
11
- 'source' => effective_datatables.datatable_path(datatable, {format: 'json'}.merge(attributes: datatable.attributes)).chomp('?'),
12
- 'default-order' => datatable_default_order(datatable),
13
- 'display-entries' => datatable.display_entries,
14
- 'display-records' => (datatable.to_json[:recordsFiltered] || 0),
15
- 'total-records' => (datatable.to_json[:recordsTotal] || 0)
16
- }
17
- }
18
-
19
1
  %table.effective-datatable{effective_datatable_params}
20
2
  %thead
21
- - if datatable.table_columns.any? { |_, opts| opts[:th].present? } == false
3
+ - if datatable.columns.any? { |_, opts| opts[:th].present? } == false
22
4
  %tr
23
- - datatable.table_columns.each do |name, opts|
5
+ - datatable.columns.each do |name, opts|
24
6
  %th= opts[:label] || name
25
7
  - else
26
- - max_depth = datatable.table_columns.map { |_, opts| opts[:th].try(:[], :depth) || 0 }.max
8
+ - max_depth = datatable.columns.map { |_, opts| opts[:th].try(:[], :depth) || 0 }.max
27
9
  - [*0..max_depth].each do |depth|
28
10
  %tr
29
- - table_columns = datatable.table_columns.select { |_, opts| (opts[:th].try(:[], :depth) || 0) == depth }
30
- - table_columns.each do |name, opts|
11
+ - columns = datatable.columns.select { |_, opts| (opts[:th].try(:[], :depth) || 0) == depth }
12
+ - columns.each do |name, opts|
31
13
  %th{(opts[:th] || {}).merge(title: (opts[:label] || name))}
32
14
  = opts[:label] || name
33
- - (opts[:append_th] || []).each do |faux_col|
15
+ - (opts[:th_append] || []).each do |faux_col|
34
16
  %th{(faux_col[:th] || {}).merge(title: faux_col[:label])}
35
17
  = faux_col[:label]
36
18
 
@@ -40,7 +22,7 @@
40
22
  - row.each do |col|
41
23
  %td= col.to_s.html_safe
42
24
 
43
- - if datatable.aggregates.present?
25
+ - if datatable.to_json[:aggregates].present?
44
26
  %tfoot
45
27
  - datatable.to_json[:aggregates].each do |row|
46
28
  %tr