effective_datatables 4.9.4 → 4.10.3
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 +31 -0
- 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 +11 -5
- data/app/helpers/effective_datatables_private_helper.rb +13 -22
- data/app/models/effective/datatable.rb +16 -1
- 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 +20 -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 +5 -3
- data/app/models/effective/effective_datatable/format.rb +34 -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: f8fa23825496c27d779457c70827727371d84aeb953b674226d2218d1279555c
|
4
|
+
data.tar.gz: dd26ea781b3b09883ee67d156e9407de85400beb0573edb6c98ee782740762cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5872f805495f9cffcb1e6a41b56671092b0e2a6337d23bc85fb6db5bd90e772dd311c2fb65e21d46fdc42f126906e2b7e7902bef902a1cae6591cb963249e5cc
|
7
|
+
data.tar.gz: ceffdf55902c85e2fc8ab99638add52750f9ae09a3fb6e48a284e6db49592cdedae6dca29544486316e96febb5606bfdfcbca015f2fe887aaf5f09b5f96b3a9c
|
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)
|
@@ -604,6 +605,12 @@ Any `data-remote` actions will be hijacked and performed as inline ajax by datat
|
|
604
605
|
|
605
606
|
If you'd like to opt-out of this behavior, use `actions_col(inline: false)` or add `data-inline: false` to your action link.
|
606
607
|
|
608
|
+
If the automatic actions_col aren't being displayed, try setting the namespace directly when calling the table
|
609
|
+
|
610
|
+
```
|
611
|
+
MyApp::UsersTable.new(namespace: :my_app)
|
612
|
+
```
|
613
|
+
|
607
614
|
## length
|
608
615
|
|
609
616
|
Sets the default number of rows per page. Valid lengths are `5`, `10`, `25`, `50`, `100`, `250`, `500`, `:all`
|
@@ -681,6 +688,30 @@ end.aggregate { |values, column| distance_of_time_in_words(values.min, values.ma
|
|
681
688
|
|
682
689
|
In the above example, `values` is an Array containing all row's values for one column at a time.
|
683
690
|
|
691
|
+
## download
|
692
|
+
|
693
|
+
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.
|
694
|
+
|
695
|
+
This is an opt-in feature.
|
696
|
+
|
697
|
+
To enable, please set `config.download = true` in your `config/initializers/effective_datatables.rb` file.
|
698
|
+
|
699
|
+
Once enabled, you can disable it on an individual table by:
|
700
|
+
|
701
|
+
```ruby
|
702
|
+
datatable do
|
703
|
+
download false
|
704
|
+
end
|
705
|
+
```
|
706
|
+
|
707
|
+
and you can exclude individual columns from being rendered on the CSV export
|
708
|
+
|
709
|
+
```ruby
|
710
|
+
col :first_name, csv: false
|
711
|
+
```
|
712
|
+
|
713
|
+
The column will still appear in the export, but the contents will be blank.
|
714
|
+
|
684
715
|
## filters
|
685
716
|
|
686
717
|
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('
|
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
|
@@ -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.
|
@@ -30,6 +32,7 @@ module Effective
|
|
30
32
|
include Effective::EffectiveDatatable::Collection
|
31
33
|
include Effective::EffectiveDatatable::Compute
|
32
34
|
include Effective::EffectiveDatatable::Cookie
|
35
|
+
include Effective::EffectiveDatatable::Csv
|
33
36
|
include Effective::EffectiveDatatable::Format
|
34
37
|
include Effective::EffectiveDatatable::Hooks
|
35
38
|
include Effective::EffectiveDatatable::Params
|
@@ -39,7 +42,7 @@ module Effective
|
|
39
42
|
def initialize(view = nil, attributes = nil)
|
40
43
|
(attributes = view; view = nil) if view.kind_of?(Hash)
|
41
44
|
|
42
|
-
@attributes = (attributes
|
45
|
+
@attributes = initial_attributes(attributes)
|
43
46
|
@state = initial_state
|
44
47
|
|
45
48
|
@_aggregates = {}
|
@@ -140,6 +143,10 @@ module Effective
|
|
140
143
|
to_json[:recordsTotal] == 0
|
141
144
|
end
|
142
145
|
|
146
|
+
def to_csv
|
147
|
+
csv_file()
|
148
|
+
end
|
149
|
+
|
143
150
|
def to_json
|
144
151
|
@json ||= (
|
145
152
|
{
|
@@ -167,6 +174,14 @@ module Effective
|
|
167
174
|
!reorder? && attributes[:sortable] != false
|
168
175
|
end
|
169
176
|
|
177
|
+
def searchable?
|
178
|
+
attributes[:searchable] != false
|
179
|
+
end
|
180
|
+
|
181
|
+
def downloadable?
|
182
|
+
attributes[:downloadable] != false
|
183
|
+
end
|
184
|
+
|
170
185
|
# Whether the filters must be rendered as a <form> or we can keep the normal <div> behaviour
|
171
186
|
def _filters_form_required?
|
172
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 Attributes
|
@@ -15,6 +17,24 @@ module Effective
|
|
15
17
|
@attributes[:namespace] ||= view.controller_path.split('/')[0...-1].join('/')
|
16
18
|
end
|
17
19
|
|
20
|
+
# Polymorphic shorthand attributes.
|
21
|
+
# If you pass resource: User(1), it sets resource_id: 1, resource_type: 'User'
|
22
|
+
def initial_attributes(attributes)
|
23
|
+
return {} if attributes.blank?
|
24
|
+
|
25
|
+
resources = attributes.select { |k, v| v.kind_of?(ActiveRecord::Base) }
|
26
|
+
return attributes if resources.blank?
|
27
|
+
|
28
|
+
retval = attributes.except(*resources.keys)
|
29
|
+
|
30
|
+
resources.each do |k, resource|
|
31
|
+
retval["#{k}_id".to_sym] = resource.id
|
32
|
+
retval["#{k}_type".to_sym] = resource.class.name
|
33
|
+
end
|
34
|
+
|
35
|
+
retval
|
36
|
+
end
|
37
|
+
|
18
38
|
end
|
19
39
|
end
|
20
40
|
end
|
@@ -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
|
|
@@ -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,20 +72,33 @@ 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
|
80
95
|
return value if value.kind_of?(String)
|
81
96
|
end
|
82
97
|
|
98
|
+
if value.kind_of?(Array) && column[:as] == :string
|
99
|
+
return value.map { |v| view.content_tag(:div, format_column(v, column, csv: csv), class: 'col-resource_item') }.join.html_safe
|
100
|
+
end
|
101
|
+
|
83
102
|
case column[:as]
|
84
103
|
when :actions
|
85
104
|
raise("please use actions_col instead of col(#{name}, as: :actions)")
|
@@ -90,7 +109,11 @@ module Effective
|
|
90
109
|
when :date
|
91
110
|
value.respond_to?(:strftime) ? value.strftime(EffectiveDatatables.format_date) : BLANK
|
92
111
|
when :datetime
|
93
|
-
|
112
|
+
if csv
|
113
|
+
value
|
114
|
+
else
|
115
|
+
value.respond_to?(:strftime) ? value.strftime(EffectiveDatatables.format_datetime) : BLANK
|
116
|
+
end
|
94
117
|
when :decimal
|
95
118
|
value
|
96
119
|
when :duration
|
@@ -102,7 +125,7 @@ module Effective
|
|
102
125
|
when :effective_roles
|
103
126
|
value.join(', ')
|
104
127
|
when :email
|
105
|
-
view.mail_to(value)
|
128
|
+
csv ? value : view.mail_to(value)
|
106
129
|
when :integer
|
107
130
|
value
|
108
131
|
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.3
|
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-28 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
|