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
@@ -15,6 +15,8 @@ module Effective
15
15
 
16
16
  # The collection itself. Only evaluated once.
17
17
  attr_accessor :_collection
18
+ attr_accessor :_collection_apply_belongs_to
19
+ attr_accessor :_collection_apply_scope
18
20
 
19
21
  # The view
20
22
  attr_reader :view
@@ -31,10 +33,10 @@ module Effective
31
33
  include Effective::EffectiveDatatable::Resource
32
34
  include Effective::EffectiveDatatable::State
33
35
 
34
- def initialize(view = nil, attributes = {})
36
+ def initialize(view = nil, attributes = nil)
35
37
  (attributes = view; view = nil) if view.kind_of?(Hash)
36
38
 
37
- @attributes = initial_attributes(attributes)
39
+ @attributes = (attributes || {})
38
40
  @state = initial_state
39
41
 
40
42
  @_aggregates = {}
@@ -45,6 +47,7 @@ module Effective
45
47
  @_form = {}
46
48
  @_scopes = {}
47
49
 
50
+ raise 'expected a hash of arguments' unless @attributes.kind_of?(Hash)
48
51
  raise 'collection is defined as a method. Please use the collection do ... end syntax.' unless collection.nil?
49
52
  self.view = view if view
50
53
  end
@@ -54,21 +57,23 @@ module Effective
54
57
  @view = (view.respond_to?(:view_context) ? view.view_context : view)
55
58
  raise 'expected view to respond to params' unless @view.respond_to?(:params)
56
59
 
57
- load_cookie!
60
+ assert_attributes!
58
61
  load_attributes!
59
62
 
60
63
  # We need early access to filter and scope, to define defaults from the model first
61
- # This means filters do knows about attributes but not about columns.
64
+ # This means filters do know about attributes but not about columns.
62
65
  initialize_filters if respond_to?(:initialize_filters)
63
66
  load_filters!
64
67
  load_state!
65
68
 
69
+ # Bulk actions called first so it can add the bulk_actions_col first
70
+ initialize_bulk_actions if respond_to?(:initialize_bulk_actions)
71
+
66
72
  # Now we initialize all the columns. columns knows about attributes and filters and scope
67
73
  initialize_datatable if respond_to?(:initialize_datatable)
68
74
  load_columns!
69
75
 
70
76
  # Execute any additional DSL methods
71
- initialize_bulk_actions if respond_to?(:initialize_bulk_actions)
72
77
  initialize_charts if respond_to?(:initialize_charts)
73
78
 
74
79
  # Load the collection. This is the first time def collection is called on the Datatable itself
@@ -78,9 +83,10 @@ module Effective
78
83
  # Figure out the class, and if it's activerecord, do all the resource discovery on it
79
84
  load_resource!
80
85
 
81
- # If attributes match a belongs_to column, scope the collection and remove the column
82
- apply_belongs_to_attributes!
86
+ # Check everything is okay
87
+ validate_datatable!
83
88
 
89
+ # Save for next time
84
90
  save_cookie!
85
91
  end
86
92
 
@@ -117,11 +123,18 @@ module Effective
117
123
  )
118
124
  end
119
125
 
120
- # When simple only a table will be rendered with
121
- # no sorting, no filtering, no export buttons, no pagination, no per page, no colReorder
122
- # default sorting only, default visibility only, all records returned, and responsive enabled
123
- def simple?
124
- attributes[:simple] == true
126
+ # Inline crud
127
+ def inline?
128
+ attributes[:inline] == true
129
+ end
130
+
131
+ # Reordering
132
+ def reorder?
133
+ columns.key?(:_reorder)
134
+ end
135
+
136
+ def sortable?
137
+ !reorder? && attributes[:sortable] != false
125
138
  end
126
139
 
127
140
  # Whether the filters must be rendered as a <form> or we can keep the normal <div> behaviour
@@ -129,12 +142,12 @@ module Effective
129
142
  _form[:verb].present?
130
143
  end
131
144
 
132
- def table_html_class
133
- attributes[:class] || EffectiveDatatables.html_class
145
+ def html_class
146
+ Array(attributes[:class] || EffectiveDatatables.html_class).join(' ').presence
134
147
  end
135
148
 
136
149
  def to_param
137
- @to_param ||= "#{self.class.name.underscore.parameterize}-#{cookie_param}"
150
+ "#{self.class.name.underscore.parameterize}-#{[self.class, attributes].hash.abs.to_s.last(12)}"
138
151
  end
139
152
 
140
153
  def columns
@@ -150,7 +163,7 @@ module Effective
150
163
  end
151
164
 
152
165
  def resource
153
- @effective_resource
166
+ raise('depecated. Please use .effective_resource instead')
154
167
  end
155
168
 
156
169
  def fallback_effective_resource
@@ -167,5 +180,12 @@ module Effective
167
180
  @value_tool ||= DatatableValueTool.new(self)
168
181
  end
169
182
 
183
+ def validate_datatable!
184
+ if reorder?
185
+ raise 'cannot use reorder with an Array collection' unless active_record_collection?
186
+ raise 'cannot use reorder with a non-Integer column' if effective_resource.sql_type(columns[:_reorder][:reorder]) != :integer
187
+ end
188
+ end
189
+
170
190
  end
171
191
  end
@@ -1,3 +1,4 @@
1
+ # In practice this is just a regular hash with the aggregate, format, search, sort do syntax that saves a block
1
2
  module Effective
2
3
  class DatatableColumn
3
4
  attr_accessor :attributes
@@ -74,13 +74,12 @@ module Effective
74
74
  collection
75
75
  end
76
76
 
77
- def search_column(collection, value, column, index)
78
- Rails.logger.info "VALUE TOOL: search_column #{column.to_s} #{value} #{index}" if EffectiveDatatables.debug
77
+ def search_column(collection, original, column, index)
78
+ Rails.logger.info "VALUE TOOL: search_column #{column.to_s} #{original} #{index}" if EffectiveDatatables.debug
79
79
 
80
- macros = Effective::Resource.new('').macros
81
80
  fuzzy = column[:search][:fuzzy]
82
81
 
83
- term = Effective::Attribute.new(column[:as]).parse(value, name: column[:name])
82
+ term = Effective::Attribute.new(column[:as]).parse(original, name: column[:name])
84
83
  term_downcased = term.to_s.downcase
85
84
 
86
85
  # term == 'nil' rescue false is a Rails 4.1 fix, where you can't compare a TimeWithZone to 'nil'
@@ -90,7 +89,8 @@ module Effective
90
89
 
91
90
  # See effective_resources gem search() method # relation.rb
92
91
  collection.select! do |row|
93
- obj = obj_to_value(row[index], column, row)
92
+ obj = row[index]
93
+ value = obj_to_value(row[index], column, row)
94
94
 
95
95
  case column[:as]
96
96
  when :boolean
@@ -101,7 +101,7 @@ module Effective
101
101
  end
102
102
  when :datetime, :date
103
103
  end_at = (
104
- case (value.to_s.scan(/(\d+)/).flatten).length
104
+ case (original.to_s.scan(/(\d+)/).flatten).length
105
105
  when 1 ; term.end_of_year # Year
106
106
  when 2 ; term.end_of_month # Year-Month
107
107
  when 3 ; term.end_of_day # Year-Month-Day
@@ -111,35 +111,35 @@ module Effective
111
111
  else term
112
112
  end
113
113
  )
114
- obj >= term && obj <= end_at
114
+ value >= term && value <= end_at
115
115
  when :time
116
- (obj.hour == term.hour) && (term.min == 0 ? true : (obj.min == term.min))
116
+ (value.hour == term.hour) && (term.min == 0 ? true : (value.min == term.min))
117
117
  when :decimal, :currency
118
- if fuzzy && (term.round(0) == term) && value.to_s.include?('.') == false
118
+ if fuzzy && (term.round(0) == term) && original.to_s.include?('.') == false
119
119
  if term < 0
120
- obj <= term && obj > (term - 1.0)
120
+ value <= term && value > (term - 1.0)
121
121
  else
122
- obj >= term && obj < (term + 1.0)
122
+ value >= term && value < (term + 1.0)
123
123
  end
124
124
  else
125
- obj == term
125
+ value == term
126
126
  end
127
127
  when :duration
128
- if fuzzy && (term % 60 == 0) && value.to_s.include?('m') == false
128
+ if fuzzy && (term % 60 == 0) && original.to_s.include?('m') == false
129
129
  if term < 0
130
- obj <= term && obj > (term - 60)
130
+ value <= term && value > (term - 60)
131
131
  else
132
- obj >= term && obj < (term + 60)
132
+ value >= term && value < (term + 60)
133
133
  end
134
134
  else
135
- obj == term
135
+ value == term
136
136
  end
137
- when *macros, :resource
137
+ when *datatable.association_macros, :resource
138
138
  Array(obj).any? do |resource|
139
139
  Array(term).any? do |term|
140
140
  matched = false
141
141
 
142
- if term.kind_of?(Integer) && resource.respond_to?(:to_param)
142
+ if term.kind_of?(Integer) && resource.respond_to?(:id) && resource.respond_to?(:to_param)
143
143
  matched = (resource.id == term || resource.to_param == term)
144
144
  end
145
145
 
@@ -148,9 +148,9 @@ module Effective
148
148
  end
149
149
  else # :string, :text, :email
150
150
  if fuzzy
151
- obj.to_s.downcase.include?(term_downcased)
151
+ value.to_s.downcase.include?(term_downcased)
152
152
  else
153
- obj == term || (obj.to_s == term.to_s)
153
+ value == term || (value.to_s == term.to_s)
154
154
  end
155
155
  end
156
156
  end || collection
@@ -4,22 +4,14 @@ module Effective
4
4
 
5
5
  private
6
6
 
7
- def initial_attributes(args)
8
- raise "#{self.class.name}.new() expected Hash like arguments" unless args.kind_of?(Hash)
9
- args
7
+ def assert_attributes!
8
+ if datatables_ajax_request? || datatables_inline_request?
9
+ raise 'expected attributes to be present' unless attributes.present?
10
+ end
10
11
  end
11
12
 
12
13
  def load_attributes!
13
- if datatables_ajax_request?
14
- raise 'expected cookie to be present' unless cookie
15
- raise 'expected attributes cookie to be present' unless cookie[:attributes]
16
-
17
- @attributes = cookie.delete(:attributes)
18
- end
19
-
20
- unless datatables_ajax_request?
21
- @attributes[:_n] ||= view.controller_path.split('/')[0...-1].join('/').presence
22
- end
14
+ @attributes[:namespace] ||= view.controller_path.split('/')[0...-1].join('/')
23
15
  end
24
16
 
25
17
  end
@@ -7,10 +7,23 @@ module Effective
7
7
  @collection_class # Will be either User/Post/etc or Array
8
8
  end
9
9
 
10
+ # User.all
10
11
  def active_record_collection?
11
12
  @active_record_collection == true
12
13
  end
13
14
 
15
+ # [User<1>, User<2>, Post<1>, Page<3>]
16
+ def active_record_array_collection?
17
+ @active_record_array_collection == true
18
+ end
19
+
20
+ def active_record_polymorphic_array_collection?
21
+ return false unless active_record_array_collection?
22
+ return @active_record_polymorphic_array_collection unless @active_record_polymorphic_array_collection.nil?
23
+ @active_record_polymorphic_array_collection = collection.map { |obj| obj.class }.uniq.length > 1
24
+ end
25
+
26
+ # [[1, 'foo'], [2, 'bar']]
14
27
  def array_collection?
15
28
  @array_collection == true
16
29
  end
@@ -21,11 +34,13 @@ module Effective
21
34
  raise 'No collection defined. Please add a collection with collection do ... end' if collection.nil?
22
35
 
23
36
  @collection_class = (collection.respond_to?(:klass) ? collection.klass : self.class)
37
+
24
38
  @active_record_collection = (collection.ancestors.include?(ActiveRecord::Base) rescue false)
25
- @array_collection = (collection.kind_of?(Array) && (collection.length == 0 || collection.first.kind_of?(Array)))
39
+ @active_record_array_collection = collection.kind_of?(Array) && collection.present? && collection.first.kind_of?(ActiveRecord::Base)
40
+ @array_collection = collection.kind_of?(Array) && (collection.blank? || collection.first.kind_of?(Array))
26
41
 
27
- unless active_record_collection? || array_collection?
28
- raise "Unsupported collection type. Expecting an ActiveRecord class, ActiveRecord relation, or an Array of Arrays [[1, 'foo'], [2, 'bar']]"
42
+ unless active_record_collection? || active_record_array_collection? || array_collection?
43
+ raise "Unsupported collection. Expecting an ActiveRecord relation, an Array of ActiveRecord objects, or an Array of Arrays [[1, 'foo'], [2, 'bar']]"
29
44
  end
30
45
 
31
46
  _scopes.each do |scope, _|
@@ -14,11 +14,14 @@ module Effective
14
14
  @total_records = (active_record_collection? ? column_tool.size(col) : value_tool.size(col))
15
15
 
16
16
  # Apply scope
17
- col = column_tool.scope(col)
17
+ col = column_tool.scope(col) if @_collection_apply_scope
18
18
 
19
19
  # Apply column searching
20
20
  col = column_tool.search(col)
21
- @display_records = column_tool.size(col) unless value_tool.searched.present?
21
+
22
+ unless value_tool.searched.present? || (column_tool.scoped.blank? && column_tool.searched.blank?)
23
+ @display_records = column_tool.size(col)
24
+ end
22
25
 
23
26
  # Apply column ordering
24
27
  col = column_tool.order(col)
@@ -61,11 +64,15 @@ module Effective
61
64
  if state[:visible][name] == false && (name != order_name) # Sort by invisible array column
62
65
  BLANK
63
66
  elsif opts[:compute]
64
- dsl_tool.instance_exec(obj, (active_record_collection? ? collection : obj[opts[:index]]), &opts[:compute])
67
+ if array_collection?
68
+ dsl_tool.instance_exec(obj, obj[opts[:index]], &opts[:compute])
69
+ else
70
+ dsl_tool.instance_exec(obj, collection, &opts[:compute])
71
+ end
65
72
  elsif (opts[:partial] || opts[:format])
66
- active_record_collection? ? obj : obj[opts[:index]]
73
+ array_collection? ? obj[opts[:index]] : obj
67
74
  elsif opts[:resource]
68
- resource = active_record_collection? ? obj : obj[opts[:index]]
75
+ resource = array_collection? ? obj[opts[:index]] : obj
69
76
 
70
77
  if opts[:resource_field]
71
78
  (associated, field) = name.to_s.split('.').first(2)
@@ -128,9 +135,11 @@ module Effective
128
135
  length = values.length
129
136
  values = values.reject { |value| value.nil? }
130
137
 
138
+ return BLANK if [:id, :year].include?(column[:name])
139
+
131
140
  case aggregate[:name]
132
141
  when :total
133
- if [:percentage].include?(column[:as])
142
+ if [:percent].include?(column[:as])
134
143
  BLANK
135
144
  elsif values.all? { |value| value.kind_of?(Numeric) }
136
145
  values.sum
@@ -5,26 +5,16 @@ module Effective
5
5
  @cookie
6
6
  end
7
7
 
8
- def cookie_key
9
- @cookie_key ||= (datatables_ajax_request? ? view.params[:cookie] : cookie_param)
10
- end
11
-
12
- # All possible dt cookie keys. Used to make sure the datatable has a cookie set for this session.
13
- def cookie_keys
14
- @cookie_keys ||= Array(@dt_cookie).compact.map(&:first)
15
- end
16
-
17
- def cookie_param
18
- [self.class, attributes].hash.abs.to_s.last(12) # Not guaranteed to be 12 long
19
- end
20
-
21
8
  private
22
9
 
23
10
  def load_cookie!
11
+ return unless EffectiveDatatables.save_state
12
+
24
13
  @dt_cookie = view.cookies.signed['_effective_dt']
25
14
 
26
15
  # Load global datatables cookie
27
16
  if @dt_cookie.present?
17
+
28
18
  @dt_cookie = Marshal.load(Base64.decode64(@dt_cookie))
29
19
  raise 'invalid datatables cookie' unless @dt_cookie.kind_of?(Array)
30
20
 
@@ -37,21 +27,33 @@ module Effective
37
27
  if @cookie.kind_of?(Array)
38
28
  @cookie = initial_state.keys.zip(@cookie.second).to_h
39
29
  end
30
+
40
31
  end
41
32
 
42
33
  def save_cookie!
34
+ return unless EffectiveDatatables.save_state
35
+
43
36
  @dt_cookie ||= []
44
37
  @dt_cookie << [cookie_key, cookie_payload]
45
38
 
46
- while @dt_cookie.to_s.size > EffectiveDatatables.max_cookie_size.to_i
39
+ while @dt_cookie.to_s.size > EffectiveDatatables.cookie_max_size.to_i
47
40
  @dt_cookie.shift((@dt_cookie.length / 3) + 1)
48
41
  end
49
42
 
50
- view.cookies.signed['_effective_dt'] = Base64.encode64(Marshal.dump(@dt_cookie))
43
+ # Generate cookie
44
+ domain = EffectiveDatatables.cookie_domain || :all
45
+ tld_length = EffectiveDatatables.cookie_tld_length
46
+ tld_length ||= (view.request.host == 'localhost' ? nil : view.request.host.to_s.split('.').count)
47
+
48
+ view.cookies.signed['_effective_dt'] = { value: Base64.encode64(Marshal.dump(@dt_cookie)), domain: domain, tld_length: tld_length }.compact
49
+ end
50
+
51
+ def cookie_key
52
+ @cookie_key ||= to_param
51
53
  end
52
54
 
53
55
  def cookie_payload
54
- payload = state.except(:attributes, :visible)
56
+ payload = state.except(:visible)
55
57
 
56
58
  # Turn visible into a bitmask. This is undone in load_columns!
57
59
  payload[:vismask] = (
@@ -60,8 +62,7 @@ module Effective
60
62
  end
61
63
  )
62
64
 
63
- # Just store the values
64
- [attributes.delete_if { |k, v| v.nil? }] + payload.values
65
+ payload.values # Just store the values
65
66
  end
66
67
 
67
68
  end
@@ -3,15 +3,20 @@ module Effective
3
3
  module Dsl
4
4
 
5
5
  def bulk_actions(&block)
6
- define_method('initialize_bulk_actions') { dsl_tool.instance_exec(&block) }
6
+ define_method('initialize_bulk_actions') { dsl_tool.instance_exec(&block); dsl_tool.bulk_actions_col }
7
7
  end
8
8
 
9
9
  def charts(&block)
10
10
  define_method('initialize_charts') { dsl_tool.instance_exec(&block) }
11
11
  end
12
12
 
13
- def collection(&block)
14
- define_method('initialize_collection') { self._collection = dsl_tool.instance_exec(&block) }
13
+ def collection(apply_belongs_to: true, apply_scope: true, &block)
14
+ define_method('initialize_collection') {
15
+ self._collection_apply_belongs_to = apply_belongs_to
16
+ self._collection_apply_scope = apply_scope
17
+
18
+ self._collection = dsl_tool.instance_exec(&block)
19
+ }
15
20
  end
16
21
 
17
22
  def datatable(&block)