effective_datatables 4.9.4 → 4.10.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|