effective_datatables 3.6.3 → 3.7.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/app/assets/javascripts/dataTables/locales/en.lang +33 -0
  4. data/app/assets/javascripts/dataTables/locales/es.lang +36 -0
  5. data/app/assets/javascripts/dataTables/locales/nl.lang +30 -0
  6. data/app/assets/javascripts/effective_datatables/filters.js.coffee +1 -0
  7. data/app/assets/javascripts/effective_datatables/flash.js.coffee +31 -0
  8. data/app/assets/javascripts/effective_datatables/initialize.js.coffee +41 -53
  9. data/app/assets/javascripts/effective_datatables/inline_crud.js.coffee +217 -0
  10. data/app/assets/javascripts/effective_datatables/overrides.js.coffee +7 -0
  11. data/app/assets/javascripts/effective_datatables/reorder.js.coffee +43 -0
  12. data/app/assets/javascripts/effective_datatables/reset.js.coffee +1 -1
  13. data/app/assets/stylesheets/effective_datatables/_overrides.scss +28 -0
  14. data/app/controllers/effective/datatables_controller.rb +39 -6
  15. data/app/datatables/effective_style_guide_datatable.rb +47 -0
  16. data/app/helpers/effective_datatables_helper.rb +49 -56
  17. data/app/helpers/effective_datatables_private_helper.rb +137 -11
  18. data/app/models/effective/datatable.rb +36 -16
  19. data/app/models/effective/datatable_column.rb +1 -0
  20. data/app/models/effective/datatable_value_tool.rb +20 -20
  21. data/app/models/effective/effective_datatable/attributes.rb +5 -13
  22. data/app/models/effective/effective_datatable/collection.rb +18 -3
  23. data/app/models/effective/effective_datatable/compute.rb +15 -6
  24. data/app/models/effective/effective_datatable/cookie.rb +19 -18
  25. data/app/models/effective/effective_datatable/dsl.rb +8 -3
  26. data/app/models/effective/effective_datatable/dsl/bulk_actions.rb +16 -23
  27. data/app/models/effective/effective_datatable/dsl/datatable.rb +70 -28
  28. data/app/models/effective/effective_datatable/dsl/filters.rb +12 -4
  29. data/app/models/effective/effective_datatable/format.rb +1 -4
  30. data/app/models/effective/effective_datatable/params.rb +9 -4
  31. data/app/models/effective/effective_datatable/resource.rb +129 -74
  32. data/app/models/effective/effective_datatable/state.rb +30 -15
  33. data/app/views/effective/datatables/_bulk_actions_dropdown.html.haml +3 -5
  34. data/app/views/effective/datatables/_datatable.html.haml +3 -5
  35. data/app/views/effective/datatables/_filters.html.haml +4 -24
  36. data/app/views/effective/datatables/_reorder_column.html.haml +5 -0
  37. data/app/views/effective/style_guide/_effective_datatables.html.haml +1 -0
  38. data/config/effective_datatables.rb +8 -21
  39. data/config/locales/en.yml +12 -0
  40. data/config/locales/es.yml +12 -0
  41. data/config/locales/nl.yml +12 -0
  42. data/config/routes.rb +5 -4
  43. data/lib/effective_datatables.rb +49 -2
  44. data/lib/effective_datatables/engine.rb +4 -2
  45. data/lib/effective_datatables/version.rb +1 -1
  46. metadata +17 -5
  47. data/app/views/effective/datatables/_reset.html.haml +0 -2
@@ -3,16 +3,16 @@ module Effective
3
3
  module Dsl
4
4
  module BulkActions
5
5
 
6
- def bulk_action(*args)
7
- datatable._bulk_actions.push(content_tag(:li, link_to_bulk_action(*args)))
6
+ def bulk_action(title, url, opts = {})
7
+ datatable._bulk_actions.push(link_to_bulk_action(title, url, opts))
8
8
  end
9
9
 
10
- def bulk_download(*args)
11
- datatable._bulk_actions.push(content_tag(:li, link_to_bulk_action(*args), 'data-authenticity-token' => form_authenticity_token))
10
+ def bulk_download(title, url, opts = {})
11
+ datatable._bulk_actions.push(link_to_bulk_action(title, url, opts.merge('data-bulk-download': true)))
12
12
  end
13
13
 
14
14
  def bulk_action_divider
15
- datatable._bulk_actions.push(content_tag(:li, '', class: 'divider', role: 'separator'))
15
+ datatable._bulk_actions.push(content_tag(:div, '', class: 'dropdown-divider'))
16
16
  end
17
17
 
18
18
  def bulk_action_content(&block)
@@ -22,26 +22,19 @@ module Effective
22
22
  private
23
23
 
24
24
  # We can't let any data-method be applied to the link, or jquery_ujs does the wrong thing with it
25
- def link_to_bulk_action(*args)
26
- args.map! do |arg|
27
- if arg.kind_of?(Hash)
28
- data_method = (
29
- arg.delete(:'data-method') ||
30
- arg.delete('data-method') ||
31
- (arg[:data] || {}).delete('method') ||
32
- (arg[:data] || {}).delete(:method)
33
- )
34
-
35
- # But if the data-method was :get, we add bulk-actions-get-link = true
36
- if data_method.to_s == 'get'
37
- arg[:data].present? ? arg[:data]['bulk-actions-get'] = true : arg['data-bulk-actions-get'] = true
38
- end
39
- end
40
-
41
- arg
25
+ def link_to_bulk_action(title, url, opts = {})
26
+
27
+ # Transform data: { ... } hash into 'data-' keys
28
+ if (data = opts.delete(:data))
29
+ data.each { |k, v| opts["data-#{k}"] ||= v }
42
30
  end
43
31
 
44
- link_to(*args)
32
+ verbs = {'DELETE' => 'DELETE', 'GET' => 'GET'}
33
+ opts['data-ajax-method'] = verbs[opts.delete('data-method').to_s.upcase] || 'POST'
34
+
35
+ opts[:class] = [opts[:class], 'dropdown-item'].compact.join(' ')
36
+
37
+ content_tag(:li, link_to(title, url, opts))
45
38
  end
46
39
 
47
40
  end
@@ -3,6 +3,11 @@ module Effective
3
3
  module Dsl
4
4
  module Datatable
5
5
  # Instance Methods inside the datatable do .. end block
6
+ def length(length)
7
+ raise 'length must be 5, 10, 25, 50, 100, 250, 500, :all' unless [5, 10, 25, 50, 100, 250, 500, :all].include?(length)
8
+ datatable.state[:length] ||= (length == :all ? 9999999 : length)
9
+ end
10
+
6
11
  def order(name, dir = nil)
7
12
  raise 'order direction must be :asc or :desc' unless [nil, :asc, :desc].include?(dir)
8
13
 
@@ -10,9 +15,13 @@ module Effective
10
15
  datatable.state[:order_dir] ||= dir
11
16
  end
12
17
 
13
- def length(length)
14
- raise 'length must be 5, 10, 25, 50, 100, 250, 500, :all' unless [5, 10, 25, 50, 100, 250, 500, :all].include?(length)
15
- datatable.state[:length] ||= (length == :all ? 9999999 : length)
18
+ def reorder(name, dir = nil)
19
+ raise 'order direction must be :asc or :desc' unless [nil, :asc, :desc].include?(dir)
20
+
21
+ datatable.state[:order_name] = :_reorder
22
+ datatable.state[:order_dir] = dir
23
+
24
+ reorder_col(name)
16
25
  end
17
26
 
18
27
  # A col has its internal values sorted/searched before the block is run
@@ -25,7 +34,7 @@ module Effective
25
34
  name = name.to_sym unless name.to_s.include?('.')
26
35
 
27
36
  datatable._columns[name] = Effective::DatatableColumn.new(
28
- action: action, # resource columns only
37
+ action: action,
29
38
  as: as,
30
39
  compute: nil,
31
40
  col_class: col_class,
@@ -53,7 +62,7 @@ module Effective
53
62
  name = name.to_sym unless name.to_s.include?('.')
54
63
 
55
64
  datatable._columns[name] = Effective::DatatableColumn.new(
56
- action: action, # Resource columns only
65
+ action: action,
57
66
  as: as,
58
67
  compute: (compute if block_given?),
59
68
  col_class: col_class,
@@ -73,8 +82,48 @@ module Effective
73
82
  )
74
83
  end
75
84
 
76
- def bulk_actions_col(col_class: nil, partial: nil, partial_as: nil, responsive: 5000)
77
- raise 'You can only have one bulk actions column' if datatable.columns[:_bulk_actions].present?
85
+ def actions_col(btn_class: nil, col_class: nil, inline: nil, partial: nil, partial_as: nil, actions_partial: nil, responsive: 5000, visible: true, **actions, &format)
86
+ raise 'You can only have one actions column' if datatable.columns[:_actions].present?
87
+
88
+ datatable._columns[:_actions] = Effective::DatatableColumn.new(
89
+ action: false,
90
+ as: :actions,
91
+ compute: nil,
92
+ btn_class: (btn_class || 'btn-sm btn-outline-primary'),
93
+ col_class: col_class,
94
+ format: (format if block_given?),
95
+ index: nil,
96
+ inline: (inline.nil? ? datatable.inline? : inline),
97
+ label: false,
98
+ name: :actions,
99
+ partial: partial,
100
+ partial_as: partial_as,
101
+ actions_partial: (actions_partial || :glyphicons),
102
+ responsive: responsive,
103
+ search: false,
104
+ sort: false,
105
+ sql_column: nil,
106
+ th: nil,
107
+ th_append: nil,
108
+ visible: visible,
109
+
110
+ # { approve: false }. These args are passed to effective_resources render_resource_actions
111
+ actions: actions
112
+ )
113
+ end
114
+
115
+ def aggregate(name, label: nil, &compute)
116
+ datatable._aggregates[name.to_sym] = {
117
+ compute: (compute if block_given?),
118
+ label: label || name.to_s.titleize,
119
+ name: name.to_sym,
120
+ }
121
+ end
122
+
123
+ # Called automatically after bulk_actions do ... end
124
+ # Call again if you want to change the position of the bulk_actions_col
125
+ def bulk_actions_col(col_class: nil, input_name: nil, partial: nil, partial_as: nil, responsive: 5000)
126
+ datatable._columns.delete(:_bulk_actions) if datatable.columns[:_bulk_actions]
78
127
 
79
128
  datatable._columns[:_bulk_actions] = Effective::DatatableColumn.new(
80
129
  action: false,
@@ -83,6 +132,7 @@ module Effective
83
132
  col_class: col_class,
84
133
  format: nil,
85
134
  index: nil,
135
+ input_name: (input_name || 'bulk_actions_resources'),
86
136
  label: false,
87
137
  name: :bulk_actions,
88
138
  partial: partial || '/effective/datatables/bulk_actions_column',
@@ -97,41 +147,33 @@ module Effective
97
147
  )
98
148
  end
99
149
 
100
- def actions_col(col_class: nil, partial: nil, partial_as: nil, actions_partial: nil, responsive: 5000, visible: true, **actions, &format)
101
- raise 'You can only have one actions column' if datatable.columns[:_actions].present?
150
+ # Called automatically after reorder
151
+ # Call again if you want to change the position of the reorder_col
152
+ def reorder_col(name, col_class: nil, partial: nil, partial_as: nil, sql_column: nil, responsive: 5000)
153
+ datatable._columns.delete(:_reorder) if datatable.columns[:_reorder]
102
154
 
103
- datatable._columns[:_actions] = Effective::DatatableColumn.new(
155
+ datatable._columns[:_reorder] = Effective::DatatableColumn.new(
104
156
  action: false,
105
- as: :actions,
157
+ as: :reorder,
106
158
  compute: nil,
107
159
  col_class: col_class,
108
- format: (format if block_given?),
160
+ format: nil,
109
161
  index: nil,
110
162
  label: false,
111
- name: :actions,
112
- partial: partial,
163
+ name: :reorder,
164
+ partial: partial || '/effective/datatables/reorder_column',
113
165
  partial_as: partial_as,
114
- actions_partial: (actions_partial || :glyphicons),
166
+ reorder: name,
115
167
  responsive: responsive,
116
168
  search: false,
117
- sort: false,
118
- sql_column: nil,
169
+ sort: true,
170
+ sql_column: (sql_column || name),
119
171
  th: nil,
120
172
  th_append: nil,
121
- visible: visible,
122
-
123
- # { approve: false }. These args are passed to effective_resources render_resource_actions
124
- actions: actions
173
+ visible: false
125
174
  )
126
175
  end
127
176
 
128
- def aggregate(name, label: nil, &compute)
129
- datatable._aggregates[name.to_sym] = {
130
- compute: (compute if block_given?),
131
- label: label || name.to_s.titleize,
132
- name: name.to_sym,
133
- }
134
- end
135
177
  end
136
178
  end
137
179
  end
@@ -5,7 +5,7 @@ module Effective
5
5
  def filter(name = nil, value = :_no_value, as: nil, label: nil, parse: nil, required: false, **input_html)
6
6
  return datatable.filter if (name == nil && value == :_no_value) # This lets block methods call 'filter' and get the values
7
7
 
8
- raise 'expected second argument to be a value' if value == :_no_value
8
+ raise 'expected second argument to be a value. the default value for this filter.' if value == :_no_value
9
9
  raise 'parse must be a Proc' if parse.present? && !parse.kind_of?(Proc)
10
10
 
11
11
  # Merge search
@@ -13,15 +13,23 @@ module Effective
13
13
  input_html = input_html.merge(input_html[:search])
14
14
  end
15
15
 
16
+ # Try to guess as value
17
+ as ||= (
18
+ if input_html.key?(:collection)
19
+ :select
20
+ elsif value != nil
21
+ Effective::Attribute.new(value).type
22
+ end
23
+ ) || :text
24
+
16
25
  datatable._filters[name.to_sym] = {
17
26
  value: value,
18
27
  as: as,
19
- label: label || (label == false ? false : name.to_s.titleize),
28
+ label: label,
20
29
  name: name.to_sym,
21
30
  parse: parse,
22
31
  required: required,
23
- input_html: input_html.merge({ value: value })
24
- }
32
+ }.compact.reverse_merge(input_html)
25
33
  end
26
34
 
27
35
  def scope(name = nil, *args, default: nil, label: nil)
@@ -84,10 +84,7 @@ module Effective
84
84
  when :actions
85
85
  raise("please use actions_col instead of col(#{name}, as: :actions)")
86
86
  when :boolean
87
- case value
88
- when true ; 'Yes'
89
- when false ; 'No'
90
- end
87
+ view.t("effective_datatables.boolean_#{value}")
91
88
  when :currency
92
89
  view.number_to_currency(value)
93
90
  when :date
@@ -7,8 +7,13 @@ module Effective
7
7
  def datatables_ajax_request?
8
8
  return @_datatables_ajax_request unless @_datatables_ajax_request.nil?
9
9
 
10
- @_datatables_ajax_request =
11
- (view && view.params[:draw] && view.params[:columns] && cookie_keys.include?(view.params[:cookie])) == true
10
+ @_datatables_ajax_request = (view.present? && view.params.key?(:draw) && view.params.key?(:columns))
11
+ end
12
+
13
+ def datatables_inline_request?
14
+ return @_datatables_inline_request unless @_datatables_inline_request.nil?
15
+
16
+ @_datatables_inline_request = (view.present? && view.params[:_datatable_id].to_s.split('-')[0...-1] == to_param.split('-')[0...-1])
12
17
  end
13
18
 
14
19
  def params
@@ -20,7 +25,7 @@ module Effective
20
25
  end
21
26
 
22
27
  def filter_params
23
- params.select { |name, value| _filters.key?(name.to_sym) }
28
+ params.select { |name, value| _filters.key?(name.to_sym) && name != 'id' }
24
29
  end
25
30
 
26
31
  def scope_param
@@ -29,7 +34,7 @@ module Effective
29
34
 
30
35
  def search_params
31
36
  params.select do |name, value|
32
- columns.key?(name) && (name != :id) && !value.kind_of?(Hash) && value.class.name != 'ActionController::Parameters'.freeze
37
+ columns.key?(name) && ![:id, :action].include?(name) && !value.kind_of?(Hash) && value.class.name != 'ActionController::Parameters'.freeze
33
38
  end
34
39
  end
35
40
  end
@@ -8,7 +8,11 @@ module Effective
8
8
  end
9
9
 
10
10
  def controller_namespace
11
- @attributes[:_n]
11
+ @attributes[:namespace]
12
+ end
13
+
14
+ def association_macros
15
+ [:belongs_to, :belongs_to_polymorphic, :has_many, :has_and_belongs_to_many, :has_one]
12
16
  end
13
17
 
14
18
  private
@@ -23,99 +27,129 @@ module Effective
23
27
  def load_resource!
24
28
  load_effective_resource!
25
29
 
26
- if active_record_collection?
27
- columns.each do |name, opts|
30
+ load_active_record_collection!
31
+ load_active_record_array_collection!
32
+ load_array_collection!
28
33
 
29
- # col 'comments.title'
30
- if name.kind_of?(String) && name.include?('.')
31
- raise "invalid datatables column '#{name}'. the joined syntax only supports one dot." if name.scan(/\./).count > 1
34
+ load_resource_columns!
35
+ load_resource_belongs_tos!
36
+ load_resource_search!
37
+ end
32
38
 
33
- (associated, field) = name.split('.').first(2)
39
+ def load_effective_resource!
40
+ @effective_resource = if active_record_collection?
41
+ Effective::Resource.new(collection_class, namespace: controller_namespace)
42
+ end
43
+ end
34
44
 
35
- unless resource.macros.include?(resource.sql_type(associated))
36
- raise "invalid datatables column '#{name}'. unable to find '#{name.split('.').first}' association on '#{resource}'."
37
- end
45
+ def load_active_record_collection!
46
+ return unless active_record_collection?
38
47
 
39
- joins_values = (collection.joins_values + collection.left_outer_joins_values)
48
+ columns.each do |name, opts|
49
+ # col 'comments.title'
50
+ if name.kind_of?(String) && name.include?('.')
51
+ raise "invalid datatables column '#{name}'. the joined syntax only supports one dot." if name.scan(/\./).count > 1
40
52
 
41
- unless joins_values.include?(associated.to_sym)
42
- raise "your datatables collection must .joins(:#{associated}) or .left_outer_joins(:#{associated}) to work with the joined syntax"
43
- end
53
+ (associated, field) = name.split('.').first(2)
44
54
 
45
- opts[:resource] = Effective::Resource.new(resource.associated(associated), namespace: controller_namespace)
55
+ unless association_macros.include?(effective_resource.sql_type(associated))
56
+ raise "invalid datatables column '#{name}'. unable to find '#{name.split('.').first}' association on '#{effective_resource}'."
57
+ end
46
58
 
47
- if opts[:resource].column(field)
48
- opts[:as] ||= opts[:resource].sql_type(field)
49
- opts[:as] = :integer if opts[:resource].sql_type(field) == :belongs_to && field.end_with?('_id')
50
- opts[:sql_column] = opts[:resource].sql_column(field) if opts[:sql_column].nil?
59
+ joins_values = (collection.joins_values + collection.left_outer_joins_values)
51
60
 
52
- opts[:resource].sort_column = field
53
- opts[:resource].search_columns = field
54
- end
61
+ unless joins_values.include?(associated.to_sym)
62
+ raise "your datatables collection must .joins(:#{associated}) or .left_outer_joins(:#{associated}) to work with the joined syntax"
63
+ end
55
64
 
56
- opts[:resource_field] = field
65
+ opts[:resource] = Effective::Resource.new(effective_resource.associated(associated), namespace: controller_namespace)
57
66
 
58
- next
67
+ if opts[:resource].column(field)
68
+ opts[:as] ||= opts[:resource].sql_type(field)
69
+ opts[:as] = :integer if opts[:resource].sql_type(field) == :belongs_to && field.end_with?('_id')
70
+ opts[:sql_column] = opts[:resource].sql_column(field) if opts[:sql_column].nil?
71
+
72
+ opts[:resource].sort_column = field
73
+ opts[:resource].search_columns = field
59
74
  end
60
75
 
61
- # Regular fields
62
- opts[:as] ||= resource.sql_type(name)
63
- opts[:sql_column] = resource.sql_column(name) if opts[:sql_column].nil?
76
+ opts[:resource_field] = field
77
+
78
+ next
79
+ end
80
+
81
+ # Regular fields
82
+ opts[:as] ||= effective_resource.sql_type(name)
83
+ opts[:sql_column] = effective_resource.sql_column(name) if opts[:sql_column].nil?
64
84
 
65
- case opts[:as]
66
- when *resource.macros
67
- opts[:resource] ||= Effective::Resource.new(resource.associated(name), namespace: controller_namespace)
85
+ case opts[:as]
86
+ when *association_macros
87
+ opts[:resource] ||= Effective::Resource.new(effective_resource.associated(name), namespace: controller_namespace)
88
+ opts[:sql_column] = name if opts[:sql_column].nil?
89
+ when Class
90
+ if opts[:as].ancestors.include?(ActiveRecord::Base)
91
+ opts[:resource] = Effective::Resource.new(opts[:as], namespace: controller_namespace)
92
+ opts[:as] = :resource
68
93
  opts[:sql_column] = name if opts[:sql_column].nil?
69
- when Class
70
- if opts[:as].ancestors.include?(ActiveRecord::Base)
71
- opts[:resource] = Effective::Resource.new(opts[:as], namespace: controller_namespace)
72
- opts[:as] = :resource
73
- opts[:sql_column] = name if opts[:sql_column].nil?
74
- end
75
- when :effective_addresses
76
- opts[:resource] = Effective::Resource.new(resource.associated(name), namespace: controller_namespace)
77
- opts[:sql_column] = :effective_addresses
78
- when :effective_roles
79
- opts[:sql_column] = :effective_roles
80
- when :string # This is the fallback
81
- # Anything that doesn't belong to the model or the sql table, we assume is a SELECT SUM|AVG|RANK() as fancy
82
- opts[:sql_as_column] = true if (resource.table && resource.column(name).blank?)
83
94
  end
95
+ when :effective_addresses
96
+ opts[:resource] = Effective::Resource.new(effective_resource.associated(name), namespace: controller_namespace)
97
+ opts[:sql_column] = :effective_addresses
98
+ when :effective_roles
99
+ opts[:sql_column] = :effective_roles
100
+ when :string # This is the fallback
101
+ # Anything that doesn't belong to the model or the sql table, we assume is a SELECT SUM|AVG|RANK() as fancy
102
+ opts[:sql_as_column] = true if (effective_resource.table && effective_resource.column(name).blank?)
103
+ end
84
104
 
85
- if opts[:sql_column].present? && AGGREGATE_SQL_FUNCTIONS.any? { |str| opts[:sql_column].to_s.start_with?(str) }
86
- opts[:sql_as_column] = true
87
- end
105
+ if opts[:sql_column].present? && AGGREGATE_SQL_FUNCTIONS.any? { |str| opts[:sql_column].to_s.start_with?(str) }
106
+ opts[:sql_as_column] = true
88
107
  end
89
108
  end
109
+ end
90
110
 
91
- if array_collection?
92
- row = collection.first
111
+ def load_active_record_array_collection!
112
+ return unless active_record_array_collection?
113
+ end
93
114
 
94
- columns.each do |name, opts|
95
- if opts[:as].kind_of?(Class) && opts[:as].ancestors.include?(ActiveRecord::Base)
96
- opts[:resource] = Effective::Resource.new(opts[:as], namespace: controller_namespace)
115
+ def load_array_collection!
116
+ return unless array_collection?
117
+
118
+ row = collection.first
119
+
120
+ columns.each do |name, opts|
121
+ if opts[:as].kind_of?(Class) && opts[:as].ancestors.include?(ActiveRecord::Base)
122
+ opts[:resource] = Effective::Resource.new(opts[:as], namespace: controller_namespace)
123
+ opts[:as] = :resource
124
+ elsif opts[:as] == nil && row.present?
125
+ if (value = Array(row[opts[:index]]).first).kind_of?(ActiveRecord::Base)
126
+ opts[:resource] = Effective::Resource.new(value, namespace: controller_namespace)
97
127
  opts[:as] = :resource
98
- elsif opts[:as] == nil
99
- if (value = Array(row[opts[:index]]).first).kind_of?(ActiveRecord::Base)
100
- opts[:resource] = Effective::Resource.new(value, namespace: controller_namespace)
101
- opts[:as] = :resource
102
- end
103
128
  end
104
129
  end
105
130
  end
131
+ end
106
132
 
133
+ def load_resource_columns!
107
134
  columns.each do |name, opts|
108
135
  opts[:as] ||= :string
109
136
  opts[:as] = :email if (opts[:as] == :string && name.to_s.end_with?('email'))
110
137
 
138
+ if opts[:action]
139
+ opts[:resource] ||= effective_resource
140
+ end
141
+
111
142
  if opts[:resource] && !opts[:resource_field] && opts[:as] != :effective_addresses
112
143
  opts[:partial] ||= '/effective/datatables/resource_column'
113
144
  end
114
145
 
115
- opts[:col_class] = "col-#{opts[:as]} col-#{name.to_s.parameterize} #{opts[:col_class]}".strip
146
+ opts[:col_class] = [
147
+ "col-#{opts[:as]}",
148
+ "col-#{name.to_s.parameterize}",
149
+ ('colvis-default' if opts[:visible]),
150
+ opts[:col_class].presence
151
+ ].compact.join(' ')
116
152
  end
117
-
118
- load_resource_search!
119
153
  end
120
154
 
121
155
  def load_resource_search!
@@ -127,7 +161,7 @@ module Effective
127
161
  when Symbol
128
162
  opts[:search] = { as: opts[:search] }
129
163
  when Array, ActiveRecord::Relation
130
- opts[:search] = { collection: opts[:search] }
164
+ opts[:search] = { as: :select, collection: opts[:search] }
131
165
  when Hash
132
166
  # Nothing
133
167
  else
@@ -136,31 +170,35 @@ module Effective
136
170
 
137
171
  search = opts[:search]
138
172
 
173
+ # Parameterize collection
139
174
  if search[:collection].kind_of?(ActiveRecord::Relation)
140
175
  search[:collection] = search[:collection].map { |obj| [obj.to_s, obj.to_param] }
141
176
  elsif search[:collection].kind_of?(Array) && search[:collection].first.kind_of?(ActiveRecord::Base)
142
177
  search[:collection] = search[:collection].map { |obj| [obj.to_s, obj.to_param] }
143
- elsif search[:collection].kind_of?(Array)
144
- search[:collection].each { |obj| obj[1] = 'nil' if obj[1] == nil }
145
- elsif search[:collection].kind_of?(Hash)
146
- search[:collection].each { |k, v| search[:collection][k] = 'nil' if v == nil }
147
178
  end
148
179
 
180
+ search[:as] ||= :select if search.key?(:collection)
181
+ search[:fuzzy] ||= true unless search.key?(:fuzzy)
149
182
  search[:value] ||= search.delete(:selected) if search.key?(:selected)
150
183
 
151
- search[:as] ||= :select if (search.key?(:collection) && opts[:as] != :belongs_to_polymorphic)
152
-
153
- search[:fuzzy] = true unless search.key?(:fuzzy)
184
+ # Merge with defaults
185
+ search_resource = [opts[:resource], effective_resource, fallback_effective_resource].compact
186
+ search_resource = search_resource.find { |res| res.klass.present? } || search_resource.first
154
187
 
155
188
  if array_collection? && opts[:resource].present?
156
- search.reverse_merge!(resource.search_form_field(name, collection.first[opts[:index]]))
189
+ search.reverse_merge!(search_resource.search_form_field(name, collection.first[opts[:index]]))
157
190
  elsif search[:as] != :string
158
- search.reverse_merge!(resource.search_form_field(name, opts[:as]))
191
+ search.reverse_merge!(search_resource.search_form_field(name, opts[:as]))
192
+ end
193
+
194
+ # Assign default include_null
195
+ if search[:as] == :select && !search.key?(:include_null)
196
+ search[:include_null] = true
159
197
  end
160
198
  end
161
199
  end
162
200
 
163
- def apply_belongs_to_attributes!
201
+ def load_resource_belongs_tos!
164
202
  return unless active_record_collection?
165
203
 
166
204
  changed = attributes.select do |attribute, value|
@@ -168,10 +206,27 @@ module Effective
168
206
  next unless attribute.ends_with?('_id')
169
207
 
170
208
  associated = attribute.gsub(/_id\z/, '').to_sym # Replace last _id
171
- next unless columns[associated] && columns[associated][:as] == :belongs_to
172
209
 
173
- @_collection = @_collection.where(attribute => value)
174
- columns.delete(associated)
210
+ next unless columns[associated]
211
+
212
+ if columns[associated][:as] == :belongs_to
213
+ if @_collection_apply_belongs_to && !@_collection.where_values_hash.include?(attribute)
214
+ @_collection = @_collection.where(attribute => value)
215
+ end
216
+
217
+ columns.delete(associated)
218
+ elsif columns[associated][:as] == :belongs_to_polymorphic
219
+ associated_type = attributes["#{associated}_type".to_sym] || raise("Expected #{associated}_type attribute to be present when #{associated}_id is present on a polymorphic belongs to")
220
+
221
+ if @_collection_apply_belongs_to
222
+ if !@_collection.where_values_hash.include?(attribute) && !@_collection.where_values_hash.include?("#{associated}_type")
223
+ @_collection = @_collection.where(attribute => value).where("#{associated}_type" => associated_type)
224
+ end
225
+ end
226
+
227
+ columns.delete(associated)
228
+ end
229
+
175
230
  end.present?
176
231
 
177
232
  load_columns! if changed