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
@@ -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)