effective_datatables 4.9.2 → 4.10.1
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.
- checksums.yaml +4 -4
- data/README.md +25 -0
- data/app/assets/javascripts/effective_datatables/bulk_actions.js.coffee +16 -4
- data/app/assets/javascripts/effective_datatables/download.js.coffee +10 -0
- data/app/assets/javascripts/effective_datatables/initialize.js.coffee +2 -8
- data/app/controllers/effective/datatables_controller.rb +34 -0
- data/app/helpers/effective_datatables_controller_helper.rb +2 -0
- data/app/helpers/effective_datatables_helper.rb +16 -6
- data/app/helpers/effective_datatables_private_helper.rb +13 -22
- data/app/models/effective/datatable.rb +18 -0
- data/app/models/effective/datatable_column.rb +2 -0
- data/app/models/effective/datatable_column_tool.rb +2 -0
- data/app/models/effective/datatable_dsl_tool.rb +2 -0
- data/app/models/effective/datatable_value_tool.rb +2 -0
- data/app/models/effective/effective_datatable/attributes.rb +2 -0
- data/app/models/effective/effective_datatable/collection.rb +3 -1
- data/app/models/effective/effective_datatable/compute.rb +6 -3
- data/app/models/effective/effective_datatable/cookie.rb +2 -0
- data/app/models/effective/effective_datatable/csv.rb +57 -0
- data/app/models/effective/effective_datatable/dsl/bulk_actions.rb +2 -0
- data/app/models/effective/effective_datatable/dsl/charts.rb +2 -0
- data/app/models/effective/effective_datatable/dsl/datatable.rb +13 -2
- data/app/models/effective/effective_datatable/dsl/filters.rb +2 -0
- data/app/models/effective/effective_datatable/dsl.rb +7 -3
- data/app/models/effective/effective_datatable/format.rb +30 -11
- data/app/models/effective/effective_datatable/hooks.rb +2 -0
- data/app/models/effective/effective_datatable/params.rb +2 -0
- data/app/models/effective/effective_datatable/resource.rb +2 -0
- data/app/models/effective/effective_datatable/state.rb +3 -1
- data/app/views/effective/datatables/_buttons.html.haml +14 -0
- data/config/effective_datatables.rb +3 -0
- data/config/locales/en.yml +1 -0
- data/config/locales/es.yml +1 -0
- data/config/locales/nl.yml +1 -0
- data/config/routes.rb +1 -0
- data/lib/effective_datatables/version.rb +1 -1
- data/lib/effective_datatables.rb +1 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 23fc1a12eb490a341d424c18f5f773081ff37c4b7de69143b0d76d016830f454
|
4
|
+
data.tar.gz: 6d3fff829f467d8dac9abbac7a9023b939d34a0a6eb978270fdcb5ce5edf3d2e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2177238d3bfac03c9020a25b4998bc31d325ad589be8fc8c2c28467a42715cf0d61af9698540db04fad665bb13bc6dc7e8c04c912666f3c4aa85debe98bdfaea
|
7
|
+
data.tar.gz: f48c993d05144a299f366dace3287d4a83d047219bcf30974a23bd3254a81f96a2e6854bafc8dbc2428d34025dff60661057781a89486169f9b25098850bae62
|
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`.
|
@@ -41,13 +41,25 @@ restoreSelected = ($table, selected) ->
|
|
41
41
|
|
42
42
|
if present then $bulkActions.removeAttr('disabled') else $bulkActions.attr('disabled', 'disabled')
|
43
43
|
|
44
|
-
|
44
|
+
# rails_ujs data-confirm requires special attention
|
45
|
+
# https://github.com/rails/rails/blob/main/actionview/app/assets/javascripts/rails-ujs/features/confirm.coffee
|
46
|
+
$(document).on 'confirm:complete', '.dataTables_wrapper .buttons-bulk-actions a', (event) ->
|
47
|
+
if event.originalEvent.detail && event.originalEvent.detail[0] == true
|
48
|
+
doBulkActionPost(event)
|
49
|
+
(window.Rails || $.rails).stopEverything(event)
|
50
|
+
return false
|
51
|
+
|
45
52
|
$(document).on 'click', '.dataTables_wrapper .buttons-bulk-actions a', (event) ->
|
46
|
-
event.
|
47
|
-
|
53
|
+
unless $(event.currentTarget).data('confirm')
|
54
|
+
doBulkActionPost(event)
|
55
|
+
event.preventDefault()
|
56
|
+
|
57
|
+
doBulkActionPost = (event) ->
|
58
|
+
$bulkAction = $(event.currentTarget) # This is the regular <a href=...> tag maybe with data-confirm
|
59
|
+
|
60
|
+
document.cookie = 'ids=; expires=Thu, 01 Jan 1970 00:00:00 GMT'
|
48
61
|
localStorage.removeItem('ids')
|
49
62
|
|
50
|
-
$bulkAction = $(event.currentTarget) # This is a regular <a href=...> tag
|
51
63
|
$wrapper = $bulkAction.closest('.dataTables_wrapper')
|
52
64
|
$table = $wrapper.find('table.dataTable').first()
|
53
65
|
$processing = $table.siblings('.dataTables_processing').first()
|
@@ -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('
|
116
|
-
$buttons.prepend($table.data('
|
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,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
|
1
5
|
module Effective
|
2
6
|
class DatatablesController < ApplicationController
|
3
7
|
skip_log_page_views quiet: true if defined?(EffectiveLogging)
|
@@ -20,6 +24,36 @@ module Effective
|
|
20
24
|
end
|
21
25
|
end
|
22
26
|
|
27
|
+
def download
|
28
|
+
@datatable = EffectiveDatatables.find(params[:id], params[:attributes])
|
29
|
+
@datatable.view = view_context
|
30
|
+
|
31
|
+
EffectiveDatatables.authorize!(self, :index, @datatable.collection_class)
|
32
|
+
|
33
|
+
respond_to do |format|
|
34
|
+
format.csv do
|
35
|
+
headers.delete('Content-Length')
|
36
|
+
|
37
|
+
headers['X-Accel-Buffering'] = 'no'
|
38
|
+
headers['Cache-Control'] = 'no-cache'
|
39
|
+
headers["Content-Type"] = @datatable.csv_content_type
|
40
|
+
headers["Content-Disposition"] = %(attachment; filename="#{@datatable.csv_filename}")
|
41
|
+
headers['Last-Modified'] = Time.zone.now.ctime.to_s
|
42
|
+
|
43
|
+
self.response_body = @datatable.csv_stream
|
44
|
+
response.status = 200
|
45
|
+
end
|
46
|
+
|
47
|
+
# format.csv do
|
48
|
+
# send_data(@datatable.csv_file, filename: @datatable.csv_filename, type: @datatable.csv_content_type, disposition: 'attachment')
|
49
|
+
# end
|
50
|
+
|
51
|
+
format.all do
|
52
|
+
render(status: :unauthorized, body: 'Access Denied')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
23
57
|
def reorder
|
24
58
|
begin
|
25
59
|
@datatable = EffectiveDatatables.find(params[:id], params[:attributes])
|
@@ -1,15 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# These are expected to be called by a developer. They are part of the datatables DSL.
|
2
4
|
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)
|
5
|
+
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
6
|
raise 'expected datatable to be present' unless datatable
|
5
7
|
raise 'expected input_js to be a Hash' unless input_js.kind_of?(Hash)
|
6
8
|
|
9
|
+
if download.nil?
|
10
|
+
download = (buttons && EffectiveDatatables.download)
|
11
|
+
end
|
12
|
+
|
7
13
|
if simple
|
8
|
-
buttons = charts = entries = filters = pagination = search = sort = false
|
14
|
+
buttons = charts = download = entries = filters = pagination = search = sort = false
|
9
15
|
end
|
10
16
|
|
11
17
|
datatable.attributes[:inline] = true if inline
|
12
18
|
datatable.attributes[:sortable] = false unless sort
|
19
|
+
datatable.attributes[:searchable] = false unless search
|
20
|
+
datatable.attributes[:downloadable] = false unless download
|
13
21
|
datatable.attributes[:namespace] = namespace if namespace
|
14
22
|
|
15
23
|
datatable.view ||= self
|
@@ -44,7 +52,7 @@ module EffectiveDatatablesHelper
|
|
44
52
|
'all-label' => I18n.t('effective_datatables.all'),
|
45
53
|
'attributes' => EffectiveDatatables.encrypt(datatable.attributes),
|
46
54
|
'authenticity-token' => form_authenticity_token,
|
47
|
-
'
|
55
|
+
'buttons-html' => datatable_buttons(datatable),
|
48
56
|
'columns' => datatable_columns(datatable),
|
49
57
|
'default-visibility' => datatable.default_visibility.to_json,
|
50
58
|
'display-length' => datatable.display_length,
|
@@ -54,8 +62,6 @@ module EffectiveDatatablesHelper
|
|
54
62
|
'inline' => inline.to_s,
|
55
63
|
'language' => EffectiveDatatables.language(I18n.locale),
|
56
64
|
'options' => input_js.to_json,
|
57
|
-
'reset' => (datatable_reset(datatable) if search),
|
58
|
-
'reorder' => datatable_reorder(datatable),
|
59
65
|
'reorder-index' => (datatable.columns[:_reorder][:index] if datatable.reorder?).to_s,
|
60
66
|
'simple' => simple.to_s,
|
61
67
|
'spinner' => icon('spinner'), # effective_bootstrap
|
@@ -64,7 +70,7 @@ module EffectiveDatatablesHelper
|
|
64
70
|
}
|
65
71
|
}
|
66
72
|
|
67
|
-
if (charts || filters)
|
73
|
+
retval = if (charts || filters)
|
68
74
|
output = ''.html_safe
|
69
75
|
|
70
76
|
if charts
|
@@ -85,6 +91,10 @@ module EffectiveDatatablesHelper
|
|
85
91
|
locals: { datatable: datatable, effective_datatable_params: effective_datatable_params }
|
86
92
|
)
|
87
93
|
end
|
94
|
+
|
95
|
+
Rails.logger.info(" Rendered datatable #{datatable.class} #{datatable.source_location}")
|
96
|
+
|
97
|
+
retval
|
88
98
|
end
|
89
99
|
|
90
100
|
def render_inline_datatable(datatable)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# These aren't expected to be called by a developer. They are internal methods.
|
2
4
|
module EffectiveDatatablesPrivateHelper
|
3
5
|
|
@@ -19,23 +21,12 @@ module EffectiveDatatablesPrivateHelper
|
|
19
21
|
end.to_json.html_safe
|
20
22
|
end
|
21
23
|
|
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
24
|
def datatable_display_order(datatable)
|
29
25
|
((datatable.sortable? && datatable.order_index) ? [datatable.order_index, datatable.order_direction] : false).to_json.html_safe
|
30
26
|
end
|
31
27
|
|
32
|
-
def
|
33
|
-
|
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)
|
28
|
+
def datatable_buttons(datatable, search: true)
|
29
|
+
render('/effective/datatables/buttons', datatable: datatable, search: search).gsub("'", '"').html_safe
|
39
30
|
end
|
40
31
|
|
41
32
|
def datatable_new_resource_button(datatable, name, column)
|
@@ -44,7 +35,7 @@ module EffectiveDatatablesPrivateHelper
|
|
44
35
|
action = { action: :new, class: ['btn', column[:btn_class].presence].compact.join(' '), 'data-remote': true }
|
45
36
|
|
46
37
|
if column[:actions][:new].kind_of?(Hash) # This might be active_record_array_collection?
|
47
|
-
action.merge
|
38
|
+
actions = action.merge(column[:actions][:new])
|
48
39
|
|
49
40
|
effective_resource = (datatable.effective_resource || datatable.fallback_effective_resource)
|
50
41
|
klass = (column[:actions][:new][:klass] || effective_resource&.klass || datatable.collection_class)
|
@@ -78,13 +69,13 @@ module EffectiveDatatablesPrivateHelper
|
|
78
69
|
return if opts[:search] == false
|
79
70
|
|
80
71
|
# Build the search
|
81
|
-
@_effective_datatables_form_builder || effective_form_with(scope:
|
72
|
+
@_effective_datatables_form_builder || effective_form_with(scope: 'datatable_search', url: '#') { |f| @_effective_datatables_form_builder = f }
|
82
73
|
form = @_effective_datatables_form_builder
|
83
74
|
|
84
75
|
collection = opts[:search].delete(:collection)
|
85
76
|
value = datatable.state[:search][name]
|
86
77
|
|
87
|
-
options = opts[:search].merge
|
78
|
+
options = opts[:search].merge(
|
88
79
|
name: nil,
|
89
80
|
feedback: false,
|
90
81
|
label: false,
|
@@ -98,20 +89,20 @@ module EffectiveDatatablesPrivateHelper
|
|
98
89
|
when :string, :text, :number
|
99
90
|
form.text_field name, options
|
100
91
|
when :date, :datetime
|
101
|
-
form.date_field name, options.reverse_merge
|
92
|
+
form.date_field name, options.reverse_merge(
|
102
93
|
date_linked: false, prepend: false, input_js: { useStrict: true, keepInvalid: true }
|
103
94
|
)
|
104
95
|
when :time
|
105
|
-
form.time_field name, options.reverse_merge
|
96
|
+
form.time_field name, options.reverse_merge(
|
106
97
|
date_linked: false, prepend: false, input_js: { useStrict: false, keepInvalid: true }
|
107
98
|
)
|
108
99
|
when :select, :boolean
|
109
|
-
options[:input_js] = (options[:input_js] || {}).reverse_merge
|
100
|
+
options[:input_js] = (options[:input_js] || {}).reverse_merge(placeholder: '')
|
110
101
|
|
111
102
|
form.select name, collection, options
|
112
103
|
when :bulk_actions
|
113
104
|
options[:data]['role'] = 'bulk-actions'
|
114
|
-
form.check_box name, options.merge
|
105
|
+
form.check_box name, options.merge(label: ' ')
|
115
106
|
end
|
116
107
|
end
|
117
108
|
|
@@ -141,7 +132,7 @@ module EffectiveDatatablesPrivateHelper
|
|
141
132
|
placeholder: (opts[:label] || name.to_s.titleize),
|
142
133
|
value: value,
|
143
134
|
wrapper: { class: 'form-group col-auto'}
|
144
|
-
}.merge
|
135
|
+
}.merge(opts.except(:as, :collection, :parse, :value))
|
145
136
|
|
146
137
|
options[:name] = '' unless datatable._filters_form_required?
|
147
138
|
|
@@ -173,7 +164,7 @@ module EffectiveDatatablesPrivateHelper
|
|
173
164
|
label: false,
|
174
165
|
required: false,
|
175
166
|
wrapper: { class: 'form-group col-auto'}
|
176
|
-
}.merge
|
167
|
+
}.merge(opts.except(:checked, :value))
|
177
168
|
|
178
169
|
form.radios :scope, collection, options
|
179
170
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Effective
|
2
4
|
class Datatable
|
3
5
|
attr_reader :attributes # Anything that we initialize our table with. That's it. Can't be changed by state.
|
@@ -21,12 +23,16 @@ module Effective
|
|
21
23
|
# The view
|
22
24
|
attr_reader :view
|
23
25
|
|
26
|
+
# Set by DSL so we can track where this datatable is coming from
|
27
|
+
attr_accessor :source_location
|
28
|
+
|
24
29
|
extend Effective::EffectiveDatatable::Dsl
|
25
30
|
|
26
31
|
include Effective::EffectiveDatatable::Attributes
|
27
32
|
include Effective::EffectiveDatatable::Collection
|
28
33
|
include Effective::EffectiveDatatable::Compute
|
29
34
|
include Effective::EffectiveDatatable::Cookie
|
35
|
+
include Effective::EffectiveDatatable::Csv
|
30
36
|
include Effective::EffectiveDatatable::Format
|
31
37
|
include Effective::EffectiveDatatable::Hooks
|
32
38
|
include Effective::EffectiveDatatable::Params
|
@@ -137,6 +143,10 @@ module Effective
|
|
137
143
|
to_json[:recordsTotal] == 0
|
138
144
|
end
|
139
145
|
|
146
|
+
def to_csv
|
147
|
+
csv_file()
|
148
|
+
end
|
149
|
+
|
140
150
|
def to_json
|
141
151
|
@json ||= (
|
142
152
|
{
|
@@ -164,6 +174,14 @@ module Effective
|
|
164
174
|
!reorder? && attributes[:sortable] != false
|
165
175
|
end
|
166
176
|
|
177
|
+
def searchable?
|
178
|
+
attributes[:searchable] != false
|
179
|
+
end
|
180
|
+
|
181
|
+
def downloadable?
|
182
|
+
attributes[:downloadable] != false
|
183
|
+
end
|
184
|
+
|
167
185
|
# Whether the filters must be rendered as a <form> or we can keep the normal <div> behaviour
|
168
186
|
def _filters_form_required?
|
169
187
|
_form[:verb].present?
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Effective
|
2
4
|
module EffectiveDatatable
|
3
5
|
module Collection
|
@@ -34,7 +36,7 @@ module Effective
|
|
34
36
|
raise 'No collection defined. Please add a collection with collection do ... end' if collection.nil?
|
35
37
|
|
36
38
|
@collection_class = (collection.respond_to?(:klass) ? collection.klass : self.class)
|
37
|
-
|
39
|
+
|
38
40
|
@active_record_collection = (collection.ancestors.include?(ActiveRecord::Base) rescue false)
|
39
41
|
@active_record_array_collection = collection.kind_of?(Array) && collection.present? && collection.first.kind_of?(ActiveRecord::Base)
|
40
42
|
@array_collection = collection.kind_of?(Array) && (collection.blank? || collection.first.kind_of?(Array))
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Effective
|
2
4
|
module EffectiveDatatable
|
3
5
|
module Compute
|
@@ -58,13 +60,14 @@ module Effective
|
|
58
60
|
finalize(col)
|
59
61
|
end
|
60
62
|
|
61
|
-
def arrayize(collection)
|
63
|
+
def arrayize(collection, csv: false)
|
62
64
|
collection.map do |obj|
|
63
65
|
columns.map do |name, opts|
|
64
|
-
if state[:visible][name] == false && (name != order_name) # Sort by invisible array column
|
66
|
+
if state[:visible][name] == false && !csv && (name != order_name) # Sort by invisible array column
|
67
|
+
BLANK
|
68
|
+
elsif csv && !opts[:csv]
|
65
69
|
BLANK
|
66
70
|
elsif opts[:compute]
|
67
|
-
|
68
71
|
if array_collection?
|
69
72
|
dsl_tool.instance_exec(obj, obj[opts[:index]], &opts[:compute])
|
70
73
|
else
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
module Effective
|
6
|
+
module EffectiveDatatable
|
7
|
+
module Csv
|
8
|
+
|
9
|
+
def csv_filename
|
10
|
+
self.class.name.underscore.parameterize + '.csv'
|
11
|
+
end
|
12
|
+
|
13
|
+
def csv_content_type
|
14
|
+
'text/csv; charset=utf-8'
|
15
|
+
end
|
16
|
+
|
17
|
+
def csv_header
|
18
|
+
columns.map { |_, opts| opts[:label] || '' }
|
19
|
+
end
|
20
|
+
|
21
|
+
def csv_file
|
22
|
+
CSV.generate do |csv|
|
23
|
+
csv << csv_header()
|
24
|
+
|
25
|
+
collection.send(csv_collection_method) do |resources|
|
26
|
+
resources = arrayize(resources, csv: true)
|
27
|
+
format(resources, csv: true)
|
28
|
+
finalize(resources)
|
29
|
+
|
30
|
+
resources.each { |resource| csv << resource }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def csv_stream
|
36
|
+
EffectiveResources.with_resource_enumerator do |lines|
|
37
|
+
lines << CSV.generate_line(csv_header)
|
38
|
+
|
39
|
+
collection.public_send(csv_collection_method) do |resources|
|
40
|
+
resources = arrayize(resources, csv: true)
|
41
|
+
format(resources, csv: true)
|
42
|
+
finalize(resources)
|
43
|
+
|
44
|
+
resources.each { |resource| lines << CSV.generate_line(resource) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def csv_collection_method
|
52
|
+
(active_record_collection? ? :find_in_batches : :to_a)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Effective
|
2
4
|
module EffectiveDatatable
|
3
5
|
module Dsl
|
@@ -24,11 +26,15 @@ module Effective
|
|
24
26
|
reorder_col(name)
|
25
27
|
end
|
26
28
|
|
29
|
+
def download(bool)
|
30
|
+
datatable.attributes[:downloadable] = bool
|
31
|
+
end
|
32
|
+
|
27
33
|
# A col has its internal values sorted/searched before the block is run
|
28
34
|
# Anything done in the block, is purely a format on the after sorted/ordered value
|
29
35
|
# the original object == the computed value, which is yielded to the format block
|
30
36
|
# 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)
|
37
|
+
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
38
|
raise 'You cannot use partial: ... with the block syntax' if partial && block_given?
|
33
39
|
|
34
40
|
name = name.to_sym unless name.to_s.include?('.')
|
@@ -38,6 +44,7 @@ module Effective
|
|
38
44
|
as: as,
|
39
45
|
compute: nil,
|
40
46
|
col_class: col_class,
|
47
|
+
csv: csv,
|
41
48
|
format: (format if block_given?),
|
42
49
|
index: nil,
|
43
50
|
label: (label.nil? ? name.to_s.split('.').last.titleize : label),
|
@@ -56,7 +63,7 @@ module Effective
|
|
56
63
|
|
57
64
|
# A val is a computed value that is then sorted/searched after the block is run
|
58
65
|
# 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)
|
66
|
+
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
67
|
raise 'You cannot use partial: ... with the block syntax' if partial && block_given?
|
61
68
|
|
62
69
|
name = name.to_sym unless name.to_s.include?('.')
|
@@ -66,6 +73,7 @@ module Effective
|
|
66
73
|
as: as,
|
67
74
|
compute: (compute if block_given?),
|
68
75
|
col_class: col_class,
|
76
|
+
csv: csv,
|
69
77
|
format: nil,
|
70
78
|
index: nil,
|
71
79
|
label: (label.nil? ? name.to_s.split('.').last.titleize : label),
|
@@ -91,6 +99,7 @@ module Effective
|
|
91
99
|
compute: nil,
|
92
100
|
btn_class: (btn_class || 'btn-sm btn-outline-primary'),
|
93
101
|
col_class: col_class,
|
102
|
+
csv: false,
|
94
103
|
format: (format if block_given?),
|
95
104
|
index: nil,
|
96
105
|
inline: (inline.nil? ? datatable.inline? : inline),
|
@@ -130,6 +139,7 @@ module Effective
|
|
130
139
|
as: :bulk_actions,
|
131
140
|
compute: nil,
|
132
141
|
col_class: col_class,
|
142
|
+
csv: false,
|
133
143
|
format: nil,
|
134
144
|
index: nil,
|
135
145
|
input_name: (input_name || 'bulk_actions_resources'),
|
@@ -157,6 +167,7 @@ module Effective
|
|
157
167
|
as: :reorder,
|
158
168
|
compute: nil,
|
159
169
|
col_class: col_class,
|
170
|
+
csv: false,
|
160
171
|
format: nil,
|
161
172
|
index: nil,
|
162
173
|
label: false,
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Effective
|
2
4
|
module EffectiveDatatable
|
3
5
|
module Dsl
|
@@ -11,11 +13,11 @@ module Effective
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def collection(apply_belongs_to: true, apply_scope: true, &block)
|
14
|
-
define_method('initialize_collection') {
|
16
|
+
define_method('initialize_collection') {
|
15
17
|
self._collection_apply_belongs_to = apply_belongs_to
|
16
18
|
self._collection_apply_scope = apply_scope
|
17
|
-
|
18
|
-
self._collection = dsl_tool.instance_exec(&block)
|
19
|
+
|
20
|
+
self._collection = dsl_tool.instance_exec(&block)
|
19
21
|
}
|
20
22
|
end
|
21
23
|
|
@@ -24,6 +26,8 @@ module Effective
|
|
24
26
|
dsl_tool.in_datatables_do_block = true
|
25
27
|
dsl_tool.instance_exec(&block)
|
26
28
|
dsl_tool.in_datatables_do_block = false
|
29
|
+
|
30
|
+
self.source_location = block.source_location.first if block.respond_to?(:source_location)
|
27
31
|
end
|
28
32
|
end
|
29
33
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Effective
|
2
4
|
module EffectiveDatatable
|
3
5
|
module Format
|
@@ -8,14 +10,16 @@ module Effective
|
|
8
10
|
|
9
11
|
private
|
10
12
|
|
11
|
-
def format(collection)
|
13
|
+
def format(collection, csv: false)
|
12
14
|
# We want to use the render :collection for each column that renders partials
|
13
15
|
rendered = {}
|
14
16
|
|
15
17
|
columns.each do |name, opts|
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
if state[:visible][name] == false && !csv
|
19
|
+
# Nothing to do
|
20
|
+
elsif csv && !opts[:csv]
|
21
|
+
# Nothing to do
|
22
|
+
elsif opts[:partial]
|
19
23
|
locals = { datatable: self, column: opts }.merge!(resource_col_locals(opts))
|
20
24
|
|
21
25
|
rendered[name] = (view.render(
|
@@ -26,6 +30,7 @@ module Effective
|
|
26
30
|
locals: locals,
|
27
31
|
spacer_template: SPACER_TEMPLATE
|
28
32
|
) || '').split(SPACER)
|
33
|
+
|
29
34
|
elsif opts[:as] == :actions # This is actions_col and actions_col do .. end, but not actions_col partial: 'something'
|
30
35
|
locals = { datatable: self, column: opts, spacer_template: SPACER_TEMPLATE }
|
31
36
|
|
@@ -45,7 +50,6 @@ module Effective
|
|
45
50
|
else
|
46
51
|
(view.render_resource_actions(collection.map { |row| row[opts[:index]] }, atts, &opts[:format]) || '').split(SPACER)
|
47
52
|
end
|
48
|
-
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
@@ -54,9 +58,11 @@ module Effective
|
|
54
58
|
index = opts[:index]
|
55
59
|
value = row[index]
|
56
60
|
|
57
|
-
|
58
|
-
if state[:visible][name] == false
|
61
|
+
formatted = (
|
62
|
+
if state[:visible][name] == false && !csv
|
59
63
|
NONVISIBLE
|
64
|
+
elsif csv && !opts[:csv]
|
65
|
+
BLANK
|
60
66
|
elsif opts[:as] == :actions
|
61
67
|
rendered[name][row_index]
|
62
68
|
elsif opts[:format] && rendered.key?(name)
|
@@ -66,14 +72,23 @@ module Effective
|
|
66
72
|
elsif opts[:partial]
|
67
73
|
rendered[name][row_index]
|
68
74
|
else
|
69
|
-
format_column(value, opts)
|
75
|
+
format_column(value, opts, csv: csv)
|
70
76
|
end
|
71
77
|
)
|
78
|
+
|
79
|
+
if csv && (opts[:format] || opts[:partial])
|
80
|
+
formatted = view.strip_tags(formatted)
|
81
|
+
|
82
|
+
formatted.gsub!("\n\n", ' ') unless formatted.frozen?
|
83
|
+
formatted.gsub!("\n", '') unless formatted.frozen?
|
84
|
+
end
|
85
|
+
|
86
|
+
row[index] = formatted
|
72
87
|
end
|
73
88
|
end
|
74
89
|
end
|
75
90
|
|
76
|
-
def format_column(value, column)
|
91
|
+
def format_column(value, column, csv: false)
|
77
92
|
return if value.nil? || (column[:resource] && value.blank?)
|
78
93
|
|
79
94
|
unless column[:as] == :email
|
@@ -90,7 +105,11 @@ module Effective
|
|
90
105
|
when :date
|
91
106
|
value.respond_to?(:strftime) ? value.strftime(EffectiveDatatables.format_date) : BLANK
|
92
107
|
when :datetime
|
93
|
-
|
108
|
+
if csv
|
109
|
+
value
|
110
|
+
else
|
111
|
+
value.respond_to?(:strftime) ? value.strftime(EffectiveDatatables.format_datetime) : BLANK
|
112
|
+
end
|
94
113
|
when :decimal
|
95
114
|
value
|
96
115
|
when :duration
|
@@ -102,7 +121,7 @@ module Effective
|
|
102
121
|
when :effective_roles
|
103
122
|
value.join(', ')
|
104
123
|
when :email
|
105
|
-
view.mail_to(value)
|
124
|
+
csv ? value : view.mail_to(value)
|
106
125
|
when :integer
|
107
126
|
value
|
108
127
|
when :percent
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Effective
|
2
4
|
module EffectiveDatatable
|
3
5
|
module State
|
@@ -98,7 +100,7 @@ module Effective
|
|
98
100
|
load_cookie_state! if cookie.present? && cookie[:params] == cookie_state_params
|
99
101
|
load_filter_params!
|
100
102
|
end
|
101
|
-
|
103
|
+
|
102
104
|
fill_empty_filters!
|
103
105
|
end
|
104
106
|
|
@@ -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')
|
data/config/locales/en.yml
CHANGED
data/config/locales/es.yml
CHANGED
data/config/locales/nl.yml
CHANGED
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
|
|
data/lib/effective_datatables.rb
CHANGED
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.
|
4
|
+
version: 4.10.1
|
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-
|
11
|
+
date: 2021-12-10 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
|