effective_datatables 4.9.4 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 768310a5434c778a3e11512125d661bc4ce443a277bb7e15581e50224b81c48f
4
- data.tar.gz: 6b05137c54ef11ce9801b2e1fc48ee525641d2dd5507b7259e80c3387335404b
3
+ metadata.gz: 173cdec1d403c8203a7f3d3701104a76d7fd8fb04843c4989180b351b5096264
4
+ data.tar.gz: 70da4dcc029e6f7ac2a2f0d7f896734c4036eea227dfd5ecb6354f1efa59f531
5
5
  SHA512:
6
- metadata.gz: edf03e2147e95afd515bc357ac5436dca96c0d3f1aa4fd36ac040bc1cc0885a1a34a0d6ff69897b61bafe8542b46447bc59adc96d42167fd4efc6b98cd65006b
7
- data.tar.gz: 7027f034cee83b584d50290a34674295b3aca4d7b6cb0121ebd1dd052833ffdfaa0be50e26d493d736c8d8e7730aa89dc666b42c0e1219ba33c6ec495f9c01da
6
+ metadata.gz: 474ead17fcde2196a4bdf06cdfa02c9d23e1dc4345126d79bcdf4fa3aa9785a358cce6d54a062862f17ec33c618a908db703a1eaa94e847a87df42dfe738b816
7
+ data.tar.gz: 55570bbe0d91f0bcdb24d818368a3a4dce9be6ffc0d7a99327121d8204df62a56b6eecc61a1bbbb6bb89b1feff5e4ea6d293497bb571ffff640331f14076d8b2
data/README.md CHANGED
@@ -46,6 +46,7 @@ Please check out [Effective Datatables 3.x](https://github.com/code-and-effect/e
46
46
  * [order](#order)
47
47
  * [reorder](#reorder)
48
48
  * [aggregate](#aggregate)
49
+ * [download](#download)
49
50
  * [filters](#filters)
50
51
  * [scope](#scope)
51
52
  * [filter](#filter)
@@ -681,6 +682,30 @@ end.aggregate { |values, column| distance_of_time_in_words(values.min, values.ma
681
682
 
682
683
  In the above example, `values` is an Array containing all row's values for one column at a time.
683
684
 
685
+ ## download
686
+
687
+ Add a Download button which streams a CSV file containing all rows and columns in the table's collection, ignoring any search, sort or filtering.
688
+
689
+ This is an opt-in feature.
690
+
691
+ To enable, please set `config.download = true` in your `config/initializers/effective_datatables.rb` file.
692
+
693
+ Once enabled, you can disable it on an individual table by:
694
+
695
+ ```ruby
696
+ datatable do
697
+ download false
698
+ end
699
+ ```
700
+
701
+ and you can exclude individual columns from being rendered on the CSV export
702
+
703
+ ```ruby
704
+ col :first_name, csv: false
705
+ ```
706
+
707
+ The column will still appear in the export, but the contents will be blank.
708
+
684
709
  ## filters
685
710
 
686
711
  Creates a single form with fields for each `filter` and a single radio input field for all `scopes`.
@@ -0,0 +1,10 @@
1
+ $(document).on 'click', '.dataTables_wrapper a.buttons-download', (event) ->
2
+ $button = $(event.currentTarget)
3
+ $table = $('#' + $button.attr('aria-controls'))
4
+
5
+ url = $table.data('source').replace('.json', '/download.csv')
6
+ attributes = 'attributes=' + encodeURIComponent($table.data('attributes'))
7
+
8
+ $button.attr('href', url + '?' + attributes)
9
+
10
+ setTimeout (=> $button.attr('href', 'download.csv')), 0
@@ -112,14 +112,8 @@ initializeDataTables = (target) ->
112
112
  $table = $(api.table().node())
113
113
  $buttons = $table.closest('.dataTables_wrapper').children().first().find('.dt-buttons')
114
114
 
115
- if $table.data('reset')
116
- $buttons.prepend($table.data('reset'))
117
-
118
- if $table.data('reorder')
119
- $buttons.prepend($table.data('reorder'))
120
-
121
- if $table.data('bulk-actions')
122
- $buttons.prepend($table.data('bulk-actions'))
115
+ if $table.data('buttons-html')
116
+ $buttons.prepend($table.data('buttons-html'))
123
117
 
124
118
  drawAggregates = ($table, aggregates) ->
125
119
  $tfoot = $table.find('tfoot').first()
@@ -1,3 +1,5 @@
1
+ require 'csv'
2
+
1
3
  module Effective
2
4
  class DatatablesController < ApplicationController
3
5
  skip_log_page_views quiet: true if defined?(EffectiveLogging)
@@ -20,6 +22,36 @@ module Effective
20
22
  end
21
23
  end
22
24
 
25
+ def download
26
+ @datatable = EffectiveDatatables.find(params[:id], params[:attributes])
27
+ @datatable.view = view_context
28
+
29
+ EffectiveDatatables.authorize!(self, :index, @datatable.collection_class)
30
+
31
+ respond_to do |format|
32
+ format.csv do
33
+ headers.delete('Content-Length')
34
+
35
+ headers['X-Accel-Buffering'] = 'no'
36
+ headers['Cache-Control'] = 'no-cache'
37
+ headers["Content-Type"] = @datatable.csv_content_type
38
+ headers["Content-Disposition"] = %(attachment; filename="#{@datatable.csv_filename}")
39
+ headers['Last-Modified'] = Time.zone.now.ctime.to_s
40
+
41
+ self.response_body = @datatable.csv_stream
42
+ response.status = 200
43
+ end
44
+
45
+ # format.csv do
46
+ # send_data(@datatable.csv_file, filename: @datatable.csv_filename, type: @datatable.csv_content_type, disposition: 'attachment')
47
+ # end
48
+
49
+ format.all do
50
+ render(status: :unauthorized, body: 'Access Denied')
51
+ end
52
+ end
53
+ end
54
+
23
55
  def reorder
24
56
  begin
25
57
  @datatable = EffectiveDatatables.find(params[:id], params[:attributes])
@@ -1,15 +1,21 @@
1
1
  # These are expected to be called by a developer. They are part of the datatables DSL.
2
2
  module EffectiveDatatablesHelper
3
- def render_datatable(datatable, input_js: {}, buttons: true, charts: true, entries: true, filters: true, inline: false, namespace: nil, pagination: true, search: true, simple: false, sort: true)
3
+ def render_datatable(datatable, input_js: {}, buttons: true, charts: true, download: nil, entries: true, filters: true, inline: false, namespace: nil, pagination: true, search: true, simple: false, sort: true)
4
4
  raise 'expected datatable to be present' unless datatable
5
5
  raise 'expected input_js to be a Hash' unless input_js.kind_of?(Hash)
6
6
 
7
+ if download.nil?
8
+ download = (buttons && EffectiveDatatables.download)
9
+ end
10
+
7
11
  if simple
8
- buttons = charts = entries = filters = pagination = search = sort = false
12
+ buttons = charts = download = entries = filters = pagination = search = sort = false
9
13
  end
10
14
 
11
15
  datatable.attributes[:inline] = true if inline
12
16
  datatable.attributes[:sortable] = false unless sort
17
+ datatable.attributes[:searchable] = false unless search
18
+ datatable.attributes[:downloadable] = false unless download
13
19
  datatable.attributes[:namespace] = namespace if namespace
14
20
 
15
21
  datatable.view ||= self
@@ -44,7 +50,7 @@ module EffectiveDatatablesHelper
44
50
  'all-label' => I18n.t('effective_datatables.all'),
45
51
  'attributes' => EffectiveDatatables.encrypt(datatable.attributes),
46
52
  'authenticity-token' => form_authenticity_token,
47
- 'bulk-actions' => datatable_bulk_actions(datatable),
53
+ 'buttons-html' => datatable_buttons(datatable),
48
54
  'columns' => datatable_columns(datatable),
49
55
  'default-visibility' => datatable.default_visibility.to_json,
50
56
  'display-length' => datatable.display_length,
@@ -54,8 +60,6 @@ module EffectiveDatatablesHelper
54
60
  'inline' => inline.to_s,
55
61
  'language' => EffectiveDatatables.language(I18n.locale),
56
62
  'options' => input_js.to_json,
57
- 'reset' => (datatable_reset(datatable) if search),
58
- 'reorder' => datatable_reorder(datatable),
59
63
  'reorder-index' => (datatable.columns[:_reorder][:index] if datatable.reorder?).to_s,
60
64
  'simple' => simple.to_s,
61
65
  'spinner' => icon('spinner'), # effective_bootstrap
@@ -19,23 +19,12 @@ module EffectiveDatatablesPrivateHelper
19
19
  end.to_json.html_safe
20
20
  end
21
21
 
22
- def datatable_bulk_actions(datatable)
23
- if datatable._bulk_actions.present?
24
- render(partial: '/effective/datatables/bulk_actions_dropdown', locals: { datatable: datatable }).gsub("'", '"').html_safe
25
- end
26
- end
27
-
28
22
  def datatable_display_order(datatable)
29
23
  ((datatable.sortable? && datatable.order_index) ? [datatable.order_index, datatable.order_direction] : false).to_json.html_safe
30
24
  end
31
25
 
32
- def datatable_reset(datatable)
33
- link_to(content_tag(:span, t('effective_datatables.reset')), '#', class: 'btn btn-link btn-sm buttons-reset-search')
34
- end
35
-
36
- def datatable_reorder(datatable)
37
- return unless datatable.reorder? && EffectiveDatatables.authorized?(self, :update, datatable.collection_class)
38
- link_to(content_tag(:span, t('effective_datatables.reorder')), '#', class: 'btn btn-link btn-sm buttons-reorder', disabled: true)
26
+ def datatable_buttons(datatable, search: true)
27
+ render('/effective/datatables/buttons', datatable: datatable, search: search).gsub("'", '"').html_safe
39
28
  end
40
29
 
41
30
  def datatable_new_resource_button(datatable, name, column)
@@ -30,6 +30,7 @@ module Effective
30
30
  include Effective::EffectiveDatatable::Collection
31
31
  include Effective::EffectiveDatatable::Compute
32
32
  include Effective::EffectiveDatatable::Cookie
33
+ include Effective::EffectiveDatatable::Csv
33
34
  include Effective::EffectiveDatatable::Format
34
35
  include Effective::EffectiveDatatable::Hooks
35
36
  include Effective::EffectiveDatatable::Params
@@ -140,6 +141,10 @@ module Effective
140
141
  to_json[:recordsTotal] == 0
141
142
  end
142
143
 
144
+ def to_csv
145
+ csv_file()
146
+ end
147
+
143
148
  def to_json
144
149
  @json ||= (
145
150
  {
@@ -167,6 +172,14 @@ module Effective
167
172
  !reorder? && attributes[:sortable] != false
168
173
  end
169
174
 
175
+ def searchable?
176
+ attributes[:searchable] != false
177
+ end
178
+
179
+ def downloadable?
180
+ attributes[:downloadable] != false
181
+ end
182
+
170
183
  # Whether the filters must be rendered as a <form> or we can keep the normal <div> behaviour
171
184
  def _filters_form_required?
172
185
  _form[:verb].present?
@@ -58,13 +58,14 @@ module Effective
58
58
  finalize(col)
59
59
  end
60
60
 
61
- def arrayize(collection)
61
+ def arrayize(collection, csv: false)
62
62
  collection.map do |obj|
63
63
  columns.map do |name, opts|
64
- if state[:visible][name] == false && (name != order_name) # Sort by invisible array column
64
+ if state[:visible][name] == false && !csv && (name != order_name) # Sort by invisible array column
65
+ BLANK
66
+ elsif csv && !opts[:csv]
65
67
  BLANK
66
68
  elsif opts[:compute]
67
-
68
69
  if array_collection?
69
70
  dsl_tool.instance_exec(obj, obj[opts[:index]], &opts[:compute])
70
71
  else
@@ -0,0 +1,55 @@
1
+ require 'csv'
2
+
3
+ module Effective
4
+ module EffectiveDatatable
5
+ module Csv
6
+
7
+ def csv_filename
8
+ self.class.name.underscore.parameterize + '.csv'
9
+ end
10
+
11
+ def csv_content_type
12
+ 'text/csv; charset=utf-8'
13
+ end
14
+
15
+ def csv_header
16
+ columns.map { |_, opts| opts[:label] || '' }
17
+ end
18
+
19
+ def csv_file
20
+ CSV.generate do |csv|
21
+ csv << csv_header()
22
+
23
+ collection.send(csv_collection_method) do |resources|
24
+ resources = arrayize(resources, csv: true)
25
+ format(resources, csv: true)
26
+ finalize(resources)
27
+
28
+ resources.each { |resource| csv << resource }
29
+ end
30
+ end
31
+ end
32
+
33
+ def csv_stream
34
+ EffectiveResources.with_resource_enumerator do |lines|
35
+ lines << CSV.generate_line(csv_header)
36
+
37
+ collection.public_send(csv_collection_method) do |resources|
38
+ resources = arrayize(resources, csv: true)
39
+ format(resources, csv: true)
40
+ finalize(resources)
41
+
42
+ resources.each { |resource| lines << CSV.generate_line(resource) }
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def csv_collection_method
50
+ (active_record_collection? ? :find_in_batches : :to_a)
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -24,11 +24,15 @@ module Effective
24
24
  reorder_col(name)
25
25
  end
26
26
 
27
+ def download(bool)
28
+ datatable.attributes[:downloadable] = bool
29
+ end
30
+
27
31
  # A col has its internal values sorted/searched before the block is run
28
32
  # Anything done in the block, is purely a format on the after sorted/ordered value
29
33
  # the original object == the computed value, which is yielded to the format block
30
34
  # You can't do compute with .col
31
- def col(name, action: nil, as: nil, col_class: nil, label: nil, partial: nil, partial_as: nil, responsive: 10000, search: {}, sort: true, sql_column: nil, th: nil, th_append: nil, visible: true, &format)
35
+ def col(name, action: nil, as: nil, col_class: nil, csv: true, label: nil, partial: nil, partial_as: nil, responsive: 10000, search: {}, sort: true, sql_column: nil, th: nil, th_append: nil, visible: true, &format)
32
36
  raise 'You cannot use partial: ... with the block syntax' if partial && block_given?
33
37
 
34
38
  name = name.to_sym unless name.to_s.include?('.')
@@ -38,6 +42,7 @@ module Effective
38
42
  as: as,
39
43
  compute: nil,
40
44
  col_class: col_class,
45
+ csv: csv,
41
46
  format: (format if block_given?),
42
47
  index: nil,
43
48
  label: (label.nil? ? name.to_s.split('.').last.titleize : label),
@@ -56,7 +61,7 @@ module Effective
56
61
 
57
62
  # A val is a computed value that is then sorted/searched after the block is run
58
63
  # You can have another block by calling .format afterwards to work on the computed value itself
59
- def val(name, action: nil, as: nil, col_class: nil, label: nil, partial: nil, partial_as: nil, responsive: 10000, search: {}, sort: true, sql_column: false, th: nil, th_append: nil, visible: true, &compute)
64
+ def val(name, action: nil, as: nil, col_class: nil, csv: true, label: nil, partial: nil, partial_as: nil, responsive: 10000, search: {}, sort: true, sql_column: false, th: nil, th_append: nil, visible: true, &compute)
60
65
  raise 'You cannot use partial: ... with the block syntax' if partial && block_given?
61
66
 
62
67
  name = name.to_sym unless name.to_s.include?('.')
@@ -66,6 +71,7 @@ module Effective
66
71
  as: as,
67
72
  compute: (compute if block_given?),
68
73
  col_class: col_class,
74
+ csv: csv,
69
75
  format: nil,
70
76
  index: nil,
71
77
  label: (label.nil? ? name.to_s.split('.').last.titleize : label),
@@ -91,6 +97,7 @@ module Effective
91
97
  compute: nil,
92
98
  btn_class: (btn_class || 'btn-sm btn-outline-primary'),
93
99
  col_class: col_class,
100
+ csv: false,
94
101
  format: (format if block_given?),
95
102
  index: nil,
96
103
  inline: (inline.nil? ? datatable.inline? : inline),
@@ -130,6 +137,7 @@ module Effective
130
137
  as: :bulk_actions,
131
138
  compute: nil,
132
139
  col_class: col_class,
140
+ csv: false,
133
141
  format: nil,
134
142
  index: nil,
135
143
  input_name: (input_name || 'bulk_actions_resources'),
@@ -157,6 +165,7 @@ module Effective
157
165
  as: :reorder,
158
166
  compute: nil,
159
167
  col_class: col_class,
168
+ csv: false,
160
169
  format: nil,
161
170
  index: nil,
162
171
  label: false,
@@ -8,14 +8,16 @@ module Effective
8
8
 
9
9
  private
10
10
 
11
- def format(collection)
11
+ def format(collection, csv: false)
12
12
  # We want to use the render :collection for each column that renders partials
13
13
  rendered = {}
14
14
 
15
15
  columns.each do |name, opts|
16
- next unless state[:visible][name]
17
-
18
- if opts[:partial]
16
+ if state[:visible][name] == false && !csv
17
+ # Nothing to do
18
+ elsif csv && !opts[:csv]
19
+ # Nothing to do
20
+ elsif opts[:partial]
19
21
  locals = { datatable: self, column: opts }.merge!(resource_col_locals(opts))
20
22
 
21
23
  rendered[name] = (view.render(
@@ -26,6 +28,7 @@ module Effective
26
28
  locals: locals,
27
29
  spacer_template: SPACER_TEMPLATE
28
30
  ) || '').split(SPACER)
31
+
29
32
  elsif opts[:as] == :actions # This is actions_col and actions_col do .. end, but not actions_col partial: 'something'
30
33
  locals = { datatable: self, column: opts, spacer_template: SPACER_TEMPLATE }
31
34
 
@@ -45,7 +48,6 @@ module Effective
45
48
  else
46
49
  (view.render_resource_actions(collection.map { |row| row[opts[:index]] }, atts, &opts[:format]) || '').split(SPACER)
47
50
  end
48
-
49
51
  end
50
52
  end
51
53
 
@@ -54,9 +56,11 @@ module Effective
54
56
  index = opts[:index]
55
57
  value = row[index]
56
58
 
57
- row[index] = (
58
- if state[:visible][name] == false
59
+ formatted = (
60
+ if state[:visible][name] == false && !csv
59
61
  NONVISIBLE
62
+ elsif csv && !opts[:csv]
63
+ BLANK
60
64
  elsif opts[:as] == :actions
61
65
  rendered[name][row_index]
62
66
  elsif opts[:format] && rendered.key?(name)
@@ -66,14 +70,23 @@ module Effective
66
70
  elsif opts[:partial]
67
71
  rendered[name][row_index]
68
72
  else
69
- format_column(value, opts)
73
+ format_column(value, opts, csv: csv)
70
74
  end
71
75
  )
76
+
77
+ if csv && (opts[:format] || opts[:partial])
78
+ formatted = view.strip_tags(formatted)
79
+
80
+ formatted.gsub!("\n\n", ' ') unless formatted.frozen?
81
+ formatted.gsub!("\n", '') unless formatted.frozen?
82
+ end
83
+
84
+ row[index] = formatted
72
85
  end
73
86
  end
74
87
  end
75
88
 
76
- def format_column(value, column)
89
+ def format_column(value, column, csv: false)
77
90
  return if value.nil? || (column[:resource] && value.blank?)
78
91
 
79
92
  unless column[:as] == :email
@@ -90,7 +103,11 @@ module Effective
90
103
  when :date
91
104
  value.respond_to?(:strftime) ? value.strftime(EffectiveDatatables.format_date) : BLANK
92
105
  when :datetime
93
- value.respond_to?(:strftime) ? value.strftime(EffectiveDatatables.format_datetime) : BLANK
106
+ if csv
107
+ value
108
+ else
109
+ value.respond_to?(:strftime) ? value.strftime(EffectiveDatatables.format_datetime) : BLANK
110
+ end
94
111
  when :decimal
95
112
  value
96
113
  when :duration
@@ -102,7 +119,7 @@ module Effective
102
119
  when :effective_roles
103
120
  value.join(', ')
104
121
  when :email
105
- view.mail_to(value)
122
+ csv ? value : view.mail_to(value)
106
123
  when :integer
107
124
  value
108
125
  when :percent
@@ -0,0 +1,14 @@
1
+ - if datatable._bulk_actions.present?
2
+ = render('/effective/datatables/bulk_actions_dropdown', datatable: datatable)
3
+
4
+ - if datatable.searchable?
5
+ = link_to '#', class: 'btn btn-link btn-sm buttons-reset-search' do
6
+ %span= t('effective_datatables.reset')
7
+
8
+ - if datatable.reorder? && EffectiveDatatables.authorized?(self, :update, datatable.collection_class)
9
+ = link_to '#', class: 'btn btn-link btn-sm buttons-reorder' do
10
+ %span= t('effective_datatables.reorder')
11
+
12
+ - if datatable.downloadable?
13
+ = link_to 'download.csv', class: 'btn btn-link btn-sm buttons-download', 'aria-controls': datatable.to_param do
14
+ %span= t('effective_datatables.download')
@@ -43,4 +43,7 @@ EffectiveDatatables.setup do |config|
43
43
  config.format_datetime = '%F %H:%M'
44
44
  config.format_date = '%F'
45
45
  config.format_time = '%H:%M'
46
+
47
+ # Enable the Download button which serves a CSV of your collection
48
+ config.download = false
46
49
  end
@@ -2,6 +2,7 @@
2
2
  en:
3
3
  effective_datatables:
4
4
  apply: Apply
5
+ download: Download
5
6
  reset: Reset
6
7
  reorder: Reorder
7
8
  new: New
@@ -2,6 +2,7 @@
2
2
  es:
3
3
  effective_datatables:
4
4
  apply: Filtrar
5
+ download: Download
5
6
  reset: Reiniciar
6
7
  reorder: Reordenar
7
8
  new: Nuevo
@@ -2,6 +2,7 @@
2
2
  nl:
3
3
  effective_datatables:
4
4
  apply: Toepassen
5
+ download: Download
5
6
  reset: Reset
6
7
  reorder: Opnieuw ordenen
7
8
  new: Nieuwe
data/config/routes.rb CHANGED
@@ -2,6 +2,7 @@ EffectiveDatatables::Engine.routes.draw do
2
2
  scope :module => 'effective' do
3
3
  match 'datatables/:id(.:format)', to: 'datatables#show', via: [:get, :post], as: :datatable
4
4
  match 'datatables/:id/reorder(.:format)', to: 'datatables#reorder', via: [:post], as: :reorder_datatable
5
+ match 'datatables/:id/download(.:format)', to: 'datatables#download', via: :get, as: :download_datatable
5
6
  end
6
7
  end
7
8
 
@@ -1,3 +1,3 @@
1
1
  module EffectiveDatatables
2
- VERSION = '4.9.4'.freeze
2
+ VERSION = '4.10.0'.freeze
3
3
  end
@@ -21,6 +21,7 @@ module EffectiveDatatables
21
21
  mattr_accessor :format_time
22
22
 
23
23
  mattr_accessor :debug
24
+ mattr_accessor :download
24
25
 
25
26
  alias_method :max_cookie_size, :cookie_max_size
26
27
  alias_method :max_cookie_size=, :cookie_max_size=
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_datatables
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.9.4
4
+ version: 4.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-23 00:00:00.000000000 Z
11
+ date: 2021-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -111,6 +111,7 @@ files:
111
111
  - app/assets/javascripts/effective_datatables.js
112
112
  - app/assets/javascripts/effective_datatables/bulk_actions.js.coffee
113
113
  - app/assets/javascripts/effective_datatables/charts.js.coffee
114
+ - app/assets/javascripts/effective_datatables/download.js.coffee
114
115
  - app/assets/javascripts/effective_datatables/events.js.coffee
115
116
  - app/assets/javascripts/effective_datatables/filters.js.coffee
116
117
  - app/assets/javascripts/effective_datatables/flash.js.coffee
@@ -141,6 +142,7 @@ files:
141
142
  - app/models/effective/effective_datatable/collection.rb
142
143
  - app/models/effective/effective_datatable/compute.rb
143
144
  - app/models/effective/effective_datatable/cookie.rb
145
+ - app/models/effective/effective_datatable/csv.rb
144
146
  - app/models/effective/effective_datatable/dsl.rb
145
147
  - app/models/effective/effective_datatable/dsl/bulk_actions.rb
146
148
  - app/models/effective/effective_datatable/dsl/charts.rb
@@ -154,6 +156,7 @@ files:
154
156
  - app/views/effective/datatables/_active_storage_column.html.haml
155
157
  - app/views/effective/datatables/_bulk_actions_column.html.haml
156
158
  - app/views/effective/datatables/_bulk_actions_dropdown.html.haml
159
+ - app/views/effective/datatables/_buttons.html.haml
157
160
  - app/views/effective/datatables/_chart.html.haml
158
161
  - app/views/effective/datatables/_datatable.html.haml
159
162
  - app/views/effective/datatables/_filters.html.haml