effective_datatables 3.6.3 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
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