effective_datatables 4.9.1 → 4.10.0
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 +57 -0
- data/app/assets/javascripts/effective_datatables/bulk_actions.js.coffee +28 -6
- 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 +32 -0
- data/app/helpers/effective_datatables_helper.rb +14 -6
- data/app/helpers/effective_datatables_private_helper.rb +2 -13
- data/app/models/effective/datatable.rb +16 -0
- data/app/models/effective/effective_datatable/compute.rb +4 -3
- data/app/models/effective/effective_datatable/csv.rb +55 -0
- data/app/models/effective/effective_datatable/dsl/datatable.rb +11 -2
- data/app/models/effective/effective_datatable/dsl.rb +2 -0
- data/app/models/effective/effective_datatable/format.rb +28 -11
- 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: 173cdec1d403c8203a7f3d3701104a76d7fd8fb04843c4989180b351b5096264
|
|
4
|
+
data.tar.gz: 70da4dcc029e6f7ac2a2f0d7f896734c4036eea227dfd5ecb6354f1efa59f531
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 474ead17fcde2196a4bdf06cdfa02c9d23e1dc4345126d79bcdf4fa3aa9785a358cce6d54a062862f17ec33c618a908db703a1eaa94e847a87df42dfe738b816
|
|
7
|
+
data.tar.gz: 55570bbe0d91f0bcdb24d818368a3a4dce9be6ffc0d7a99327121d8204df62a56b6eecc61a1bbbb6bb89b1feff5e4ea6d293497bb571ffff640331f14076d8b2
|
data/README.md
CHANGED
|
@@ -46,6 +46,7 @@ Please check out [Effective Datatables 3.x](https://github.com/code-and-effect/e
|
|
|
46
46
|
* [order](#order)
|
|
47
47
|
* [reorder](#reorder)
|
|
48
48
|
* [aggregate](#aggregate)
|
|
49
|
+
* [download](#download)
|
|
49
50
|
* [filters](#filters)
|
|
50
51
|
* [scope](#scope)
|
|
51
52
|
* [filter](#filter)
|
|
@@ -197,6 +198,13 @@ class PostsDatatable < Effective::Datatable
|
|
|
197
198
|
# POSTs to the given url with params[:ids], an Array of ids for all selected rows
|
|
198
199
|
# These actions are assumed to change the underlying collection
|
|
199
200
|
bulk_action 'Approve all', bulk_approve_posts_path, data: { confirm: 'Approve all selected posts?' }
|
|
201
|
+
# GETs to the given url. Pass the ids via cookie, encoded in url, or LocalStorage.
|
|
202
|
+
# These actions are assumed to redirect the user to some other page.
|
|
203
|
+
bulk_action 'Action 1 | ids encoded in params', action_1_posts_path, data: { method: :get }
|
|
204
|
+
|
|
205
|
+
bulk_action 'Action 2 | ids stored in _ids_ field in local storage', action_2_posts_path, data: { 'payload-mode' => 'local-storage', method: :get }
|
|
206
|
+
|
|
207
|
+
bulk_action 'Action 3 | ids stored in _ids_ field in a cookie', action_3_posts_path, data: { 'payload-mode' => 'cookie', method: :get }
|
|
200
208
|
bulk_action_divider
|
|
201
209
|
bulk_action 'Destroy all', bulk_destroy_posts_path, data: { confirm: 'Destroy all selected posts?' }
|
|
202
210
|
end
|
|
@@ -674,6 +682,30 @@ end.aggregate { |values, column| distance_of_time_in_words(values.min, values.ma
|
|
|
674
682
|
|
|
675
683
|
In the above example, `values` is an Array containing all row's values for one column at a time.
|
|
676
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
|
+
|
|
677
709
|
## filters
|
|
678
710
|
|
|
679
711
|
Creates a single form with fields for each `filter` and a single radio input field for all `scopes`.
|
|
@@ -772,6 +804,12 @@ You can also specify `data-method: :get` to instead make a `GET` request with th
|
|
|
772
804
|
```ruby
|
|
773
805
|
bulk_actions do
|
|
774
806
|
bulk_action 'Approve all', bulk_approve_posts_path, data: { confirm: 'Approve all selected posts?' }
|
|
807
|
+
|
|
808
|
+
bulk_action 'Action 1 | ids encoded in params', action_1_posts_path, data: { method: :get }
|
|
809
|
+
|
|
810
|
+
bulk_action 'Action 2 | ids stored in _ids_ field in local storage', action_2_posts_path, data: { 'payload-mode' => 'local-storage', method: :get }
|
|
811
|
+
|
|
812
|
+
bulk_action 'Action 3 | ids stored in _ids_ field in a cookie', action_3_posts_path, data: { 'payload-mode' => 'cookie', method: :get }
|
|
775
813
|
end
|
|
776
814
|
```
|
|
777
815
|
|
|
@@ -781,6 +819,9 @@ In your `routes` file:
|
|
|
781
819
|
resources :posts do
|
|
782
820
|
collection do
|
|
783
821
|
post :bulk_approve
|
|
822
|
+
get :action_1
|
|
823
|
+
get :action_2
|
|
824
|
+
get :action_3
|
|
784
825
|
end
|
|
785
826
|
end
|
|
786
827
|
```
|
|
@@ -799,6 +840,22 @@ def bulk_approve
|
|
|
799
840
|
render json: { status: 500, message: 'An error occured while approving a post.' }
|
|
800
841
|
end
|
|
801
842
|
end
|
|
843
|
+
|
|
844
|
+
def action_1
|
|
845
|
+
@posts = Post.where(id: params[:ids])
|
|
846
|
+
|
|
847
|
+
render :some_partial
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
def action_2
|
|
851
|
+
@posts = Post.where(id: cookies[:ids].split(','))
|
|
852
|
+
|
|
853
|
+
render :some_partial
|
|
854
|
+
end
|
|
855
|
+
|
|
856
|
+
def action_3
|
|
857
|
+
render :some_partial # and get ids via JS: localStorage.getItem('ids');
|
|
858
|
+
end
|
|
802
859
|
```
|
|
803
860
|
|
|
804
861
|
or if using [effective_resources](https://github.com/code-and-effect/effective_resources):
|
|
@@ -41,11 +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.
|
|
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'
|
|
61
|
+
localStorage.removeItem('ids')
|
|
47
62
|
|
|
48
|
-
$bulkAction = $(event.currentTarget) # This is a regular <a href=...> tag
|
|
49
63
|
$wrapper = $bulkAction.closest('.dataTables_wrapper')
|
|
50
64
|
$table = $wrapper.find('table.dataTable').first()
|
|
51
65
|
$processing = $table.siblings('.dataTables_processing').first()
|
|
@@ -54,6 +68,7 @@ $(document).on 'click', '.dataTables_wrapper .buttons-bulk-actions a', (event) -
|
|
|
54
68
|
url = $bulkAction.attr('href')
|
|
55
69
|
title = $bulkAction.text()
|
|
56
70
|
download = $bulkAction.data('bulk-download')
|
|
71
|
+
payload_mode = $bulkAction.data('payload-mode')
|
|
57
72
|
token = $table.data('authenticity-token')
|
|
58
73
|
values = $.map($selected, (input) -> input.getAttribute('value'))
|
|
59
74
|
method = $bulkAction.data('ajax-method')
|
|
@@ -61,10 +76,17 @@ $(document).on 'click', '.dataTables_wrapper .buttons-bulk-actions a', (event) -
|
|
|
61
76
|
return unless url && values
|
|
62
77
|
|
|
63
78
|
if method == 'GET'
|
|
64
|
-
if
|
|
65
|
-
|
|
79
|
+
if payload_mode == 'cookie'
|
|
80
|
+
document.cookie = "ids=#{values}";
|
|
81
|
+
window.location.assign(url)
|
|
82
|
+
else if payload_mode == 'local-storage'
|
|
83
|
+
localStorage.setItem('ids', values);
|
|
84
|
+
window.location.assign(url)
|
|
66
85
|
else
|
|
67
|
-
|
|
86
|
+
if url.includes('?')
|
|
87
|
+
window.location.assign(url + '&' + $.param({ids: values}))
|
|
88
|
+
else
|
|
89
|
+
window.location.assign(url + '?' + $.param({ids: values}))
|
|
68
90
|
|
|
69
91
|
return
|
|
70
92
|
|
|
@@ -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,5 @@
|
|
|
1
|
+
require 'csv'
|
|
2
|
+
|
|
1
3
|
module Effective
|
|
2
4
|
class DatatablesController < ApplicationController
|
|
3
5
|
skip_log_page_views quiet: true if defined?(EffectiveLogging)
|
|
@@ -20,6 +22,36 @@ module Effective
|
|
|
20
22
|
end
|
|
21
23
|
end
|
|
22
24
|
|
|
25
|
+
def download
|
|
26
|
+
@datatable = EffectiveDatatables.find(params[:id], params[:attributes])
|
|
27
|
+
@datatable.view = view_context
|
|
28
|
+
|
|
29
|
+
EffectiveDatatables.authorize!(self, :index, @datatable.collection_class)
|
|
30
|
+
|
|
31
|
+
respond_to do |format|
|
|
32
|
+
format.csv do
|
|
33
|
+
headers.delete('Content-Length')
|
|
34
|
+
|
|
35
|
+
headers['X-Accel-Buffering'] = 'no'
|
|
36
|
+
headers['Cache-Control'] = 'no-cache'
|
|
37
|
+
headers["Content-Type"] = @datatable.csv_content_type
|
|
38
|
+
headers["Content-Disposition"] = %(attachment; filename="#{@datatable.csv_filename}")
|
|
39
|
+
headers['Last-Modified'] = Time.zone.now.ctime.to_s
|
|
40
|
+
|
|
41
|
+
self.response_body = @datatable.csv_stream
|
|
42
|
+
response.status = 200
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# format.csv do
|
|
46
|
+
# send_data(@datatable.csv_file, filename: @datatable.csv_filename, type: @datatable.csv_content_type, disposition: 'attachment')
|
|
47
|
+
# end
|
|
48
|
+
|
|
49
|
+
format.all do
|
|
50
|
+
render(status: :unauthorized, body: 'Access Denied')
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
23
55
|
def reorder
|
|
24
56
|
begin
|
|
25
57
|
@datatable = EffectiveDatatables.find(params[:id], params[:attributes])
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# These are expected to be called by a developer. They are part of the datatables DSL.
|
|
2
2
|
module EffectiveDatatablesHelper
|
|
3
|
-
def render_datatable(datatable, input_js: {}, buttons: true, charts: true, entries: true, filters: true, inline: false, namespace: nil, pagination: true, search: true, simple: false, sort: true)
|
|
3
|
+
def render_datatable(datatable, input_js: {}, buttons: true, charts: true, download: nil, entries: true, filters: true, inline: false, namespace: nil, pagination: true, search: true, simple: false, sort: true)
|
|
4
4
|
raise 'expected datatable to be present' unless datatable
|
|
5
5
|
raise 'expected input_js to be a Hash' unless input_js.kind_of?(Hash)
|
|
6
6
|
|
|
7
|
+
if download.nil?
|
|
8
|
+
download = (buttons && EffectiveDatatables.download)
|
|
9
|
+
end
|
|
10
|
+
|
|
7
11
|
if simple
|
|
8
|
-
buttons = charts = entries = filters = pagination = search = sort = false
|
|
12
|
+
buttons = charts = download = entries = filters = pagination = search = sort = false
|
|
9
13
|
end
|
|
10
14
|
|
|
11
15
|
datatable.attributes[:inline] = true if inline
|
|
12
16
|
datatable.attributes[:sortable] = false unless sort
|
|
17
|
+
datatable.attributes[:searchable] = false unless search
|
|
18
|
+
datatable.attributes[:downloadable] = false unless download
|
|
13
19
|
datatable.attributes[:namespace] = namespace if namespace
|
|
14
20
|
|
|
15
21
|
datatable.view ||= self
|
|
@@ -44,7 +50,7 @@ module EffectiveDatatablesHelper
|
|
|
44
50
|
'all-label' => I18n.t('effective_datatables.all'),
|
|
45
51
|
'attributes' => EffectiveDatatables.encrypt(datatable.attributes),
|
|
46
52
|
'authenticity-token' => form_authenticity_token,
|
|
47
|
-
'
|
|
53
|
+
'buttons-html' => datatable_buttons(datatable),
|
|
48
54
|
'columns' => datatable_columns(datatable),
|
|
49
55
|
'default-visibility' => datatable.default_visibility.to_json,
|
|
50
56
|
'display-length' => datatable.display_length,
|
|
@@ -54,8 +60,6 @@ module EffectiveDatatablesHelper
|
|
|
54
60
|
'inline' => inline.to_s,
|
|
55
61
|
'language' => EffectiveDatatables.language(I18n.locale),
|
|
56
62
|
'options' => input_js.to_json,
|
|
57
|
-
'reset' => (datatable_reset(datatable) if search),
|
|
58
|
-
'reorder' => datatable_reorder(datatable),
|
|
59
63
|
'reorder-index' => (datatable.columns[:_reorder][:index] if datatable.reorder?).to_s,
|
|
60
64
|
'simple' => simple.to_s,
|
|
61
65
|
'spinner' => icon('spinner'), # effective_bootstrap
|
|
@@ -64,7 +68,7 @@ module EffectiveDatatablesHelper
|
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
|
|
67
|
-
if (charts || filters)
|
|
71
|
+
retval = if (charts || filters)
|
|
68
72
|
output = ''.html_safe
|
|
69
73
|
|
|
70
74
|
if charts
|
|
@@ -85,6 +89,10 @@ module EffectiveDatatablesHelper
|
|
|
85
89
|
locals: { datatable: datatable, effective_datatable_params: effective_datatable_params }
|
|
86
90
|
)
|
|
87
91
|
end
|
|
92
|
+
|
|
93
|
+
Rails.logger.info(" Rendered datatable #{datatable.class} #{datatable.source_location}")
|
|
94
|
+
|
|
95
|
+
retval
|
|
88
96
|
end
|
|
89
97
|
|
|
90
98
|
def render_inline_datatable(datatable)
|
|
@@ -19,23 +19,12 @@ module EffectiveDatatablesPrivateHelper
|
|
|
19
19
|
end.to_json.html_safe
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def datatable_bulk_actions(datatable)
|
|
23
|
-
if datatable._bulk_actions.present?
|
|
24
|
-
render(partial: '/effective/datatables/bulk_actions_dropdown', locals: { datatable: datatable }).gsub("'", '"').html_safe
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
22
|
def datatable_display_order(datatable)
|
|
29
23
|
((datatable.sortable? && datatable.order_index) ? [datatable.order_index, datatable.order_direction] : false).to_json.html_safe
|
|
30
24
|
end
|
|
31
25
|
|
|
32
|
-
def
|
|
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)
|
|
26
|
+
def datatable_buttons(datatable, search: true)
|
|
27
|
+
render('/effective/datatables/buttons', datatable: datatable, search: search).gsub("'", '"').html_safe
|
|
39
28
|
end
|
|
40
29
|
|
|
41
30
|
def datatable_new_resource_button(datatable, name, column)
|
|
@@ -21,12 +21,16 @@ module Effective
|
|
|
21
21
|
# The view
|
|
22
22
|
attr_reader :view
|
|
23
23
|
|
|
24
|
+
# Set by DSL so we can track where this datatable is coming from
|
|
25
|
+
attr_accessor :source_location
|
|
26
|
+
|
|
24
27
|
extend Effective::EffectiveDatatable::Dsl
|
|
25
28
|
|
|
26
29
|
include Effective::EffectiveDatatable::Attributes
|
|
27
30
|
include Effective::EffectiveDatatable::Collection
|
|
28
31
|
include Effective::EffectiveDatatable::Compute
|
|
29
32
|
include Effective::EffectiveDatatable::Cookie
|
|
33
|
+
include Effective::EffectiveDatatable::Csv
|
|
30
34
|
include Effective::EffectiveDatatable::Format
|
|
31
35
|
include Effective::EffectiveDatatable::Hooks
|
|
32
36
|
include Effective::EffectiveDatatable::Params
|
|
@@ -137,6 +141,10 @@ module Effective
|
|
|
137
141
|
to_json[:recordsTotal] == 0
|
|
138
142
|
end
|
|
139
143
|
|
|
144
|
+
def to_csv
|
|
145
|
+
csv_file()
|
|
146
|
+
end
|
|
147
|
+
|
|
140
148
|
def to_json
|
|
141
149
|
@json ||= (
|
|
142
150
|
{
|
|
@@ -164,6 +172,14 @@ module Effective
|
|
|
164
172
|
!reorder? && attributes[:sortable] != false
|
|
165
173
|
end
|
|
166
174
|
|
|
175
|
+
def searchable?
|
|
176
|
+
attributes[:searchable] != false
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def downloadable?
|
|
180
|
+
attributes[:downloadable] != false
|
|
181
|
+
end
|
|
182
|
+
|
|
167
183
|
# Whether the filters must be rendered as a <form> or we can keep the normal <div> behaviour
|
|
168
184
|
def _filters_form_required?
|
|
169
185
|
_form[:verb].present?
|
|
@@ -58,13 +58,14 @@ module Effective
|
|
|
58
58
|
finalize(col)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
def arrayize(collection)
|
|
61
|
+
def arrayize(collection, csv: false)
|
|
62
62
|
collection.map do |obj|
|
|
63
63
|
columns.map do |name, opts|
|
|
64
|
-
if state[:visible][name] == false && (name != order_name) # Sort by invisible array column
|
|
64
|
+
if state[:visible][name] == false && !csv && (name != order_name) # Sort by invisible array column
|
|
65
|
+
BLANK
|
|
66
|
+
elsif csv && !opts[:csv]
|
|
65
67
|
BLANK
|
|
66
68
|
elsif opts[:compute]
|
|
67
|
-
|
|
68
69
|
if array_collection?
|
|
69
70
|
dsl_tool.instance_exec(obj, obj[opts[:index]], &opts[:compute])
|
|
70
71
|
else
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'csv'
|
|
2
|
+
|
|
3
|
+
module Effective
|
|
4
|
+
module EffectiveDatatable
|
|
5
|
+
module Csv
|
|
6
|
+
|
|
7
|
+
def csv_filename
|
|
8
|
+
self.class.name.underscore.parameterize + '.csv'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def csv_content_type
|
|
12
|
+
'text/csv; charset=utf-8'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def csv_header
|
|
16
|
+
columns.map { |_, opts| opts[:label] || '' }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def csv_file
|
|
20
|
+
CSV.generate do |csv|
|
|
21
|
+
csv << csv_header()
|
|
22
|
+
|
|
23
|
+
collection.send(csv_collection_method) do |resources|
|
|
24
|
+
resources = arrayize(resources, csv: true)
|
|
25
|
+
format(resources, csv: true)
|
|
26
|
+
finalize(resources)
|
|
27
|
+
|
|
28
|
+
resources.each { |resource| csv << resource }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def csv_stream
|
|
34
|
+
EffectiveResources.with_resource_enumerator do |lines|
|
|
35
|
+
lines << CSV.generate_line(csv_header)
|
|
36
|
+
|
|
37
|
+
collection.public_send(csv_collection_method) do |resources|
|
|
38
|
+
resources = arrayize(resources, csv: true)
|
|
39
|
+
format(resources, csv: true)
|
|
40
|
+
finalize(resources)
|
|
41
|
+
|
|
42
|
+
resources.each { |resource| lines << CSV.generate_line(resource) }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def csv_collection_method
|
|
50
|
+
(active_record_collection? ? :find_in_batches : :to_a)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -24,11 +24,15 @@ module Effective
|
|
|
24
24
|
reorder_col(name)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
def download(bool)
|
|
28
|
+
datatable.attributes[:downloadable] = bool
|
|
29
|
+
end
|
|
30
|
+
|
|
27
31
|
# A col has its internal values sorted/searched before the block is run
|
|
28
32
|
# Anything done in the block, is purely a format on the after sorted/ordered value
|
|
29
33
|
# the original object == the computed value, which is yielded to the format block
|
|
30
34
|
# You can't do compute with .col
|
|
31
|
-
def col(name, action: nil, as: nil, col_class: nil, label: nil, partial: nil, partial_as: nil, responsive: 10000, search: {}, sort: true, sql_column: nil, th: nil, th_append: nil, visible: true, &format)
|
|
35
|
+
def col(name, action: nil, as: nil, col_class: nil, csv: true, label: nil, partial: nil, partial_as: nil, responsive: 10000, search: {}, sort: true, sql_column: nil, th: nil, th_append: nil, visible: true, &format)
|
|
32
36
|
raise 'You cannot use partial: ... with the block syntax' if partial && block_given?
|
|
33
37
|
|
|
34
38
|
name = name.to_sym unless name.to_s.include?('.')
|
|
@@ -38,6 +42,7 @@ module Effective
|
|
|
38
42
|
as: as,
|
|
39
43
|
compute: nil,
|
|
40
44
|
col_class: col_class,
|
|
45
|
+
csv: csv,
|
|
41
46
|
format: (format if block_given?),
|
|
42
47
|
index: nil,
|
|
43
48
|
label: (label.nil? ? name.to_s.split('.').last.titleize : label),
|
|
@@ -56,7 +61,7 @@ module Effective
|
|
|
56
61
|
|
|
57
62
|
# A val is a computed value that is then sorted/searched after the block is run
|
|
58
63
|
# You can have another block by calling .format afterwards to work on the computed value itself
|
|
59
|
-
def val(name, action: nil, as: nil, col_class: nil, label: nil, partial: nil, partial_as: nil, responsive: 10000, search: {}, sort: true, sql_column: false, th: nil, th_append: nil, visible: true, &compute)
|
|
64
|
+
def val(name, action: nil, as: nil, col_class: nil, csv: true, label: nil, partial: nil, partial_as: nil, responsive: 10000, search: {}, sort: true, sql_column: false, th: nil, th_append: nil, visible: true, &compute)
|
|
60
65
|
raise 'You cannot use partial: ... with the block syntax' if partial && block_given?
|
|
61
66
|
|
|
62
67
|
name = name.to_sym unless name.to_s.include?('.')
|
|
@@ -66,6 +71,7 @@ module Effective
|
|
|
66
71
|
as: as,
|
|
67
72
|
compute: (compute if block_given?),
|
|
68
73
|
col_class: col_class,
|
|
74
|
+
csv: csv,
|
|
69
75
|
format: nil,
|
|
70
76
|
index: nil,
|
|
71
77
|
label: (label.nil? ? name.to_s.split('.').last.titleize : label),
|
|
@@ -91,6 +97,7 @@ module Effective
|
|
|
91
97
|
compute: nil,
|
|
92
98
|
btn_class: (btn_class || 'btn-sm btn-outline-primary'),
|
|
93
99
|
col_class: col_class,
|
|
100
|
+
csv: false,
|
|
94
101
|
format: (format if block_given?),
|
|
95
102
|
index: nil,
|
|
96
103
|
inline: (inline.nil? ? datatable.inline? : inline),
|
|
@@ -130,6 +137,7 @@ module Effective
|
|
|
130
137
|
as: :bulk_actions,
|
|
131
138
|
compute: nil,
|
|
132
139
|
col_class: col_class,
|
|
140
|
+
csv: false,
|
|
133
141
|
format: nil,
|
|
134
142
|
index: nil,
|
|
135
143
|
input_name: (input_name || 'bulk_actions_resources'),
|
|
@@ -157,6 +165,7 @@ module Effective
|
|
|
157
165
|
as: :reorder,
|
|
158
166
|
compute: nil,
|
|
159
167
|
col_class: col_class,
|
|
168
|
+
csv: false,
|
|
160
169
|
format: nil,
|
|
161
170
|
index: nil,
|
|
162
171
|
label: false,
|
|
@@ -8,14 +8,16 @@ module Effective
|
|
|
8
8
|
|
|
9
9
|
private
|
|
10
10
|
|
|
11
|
-
def format(collection)
|
|
11
|
+
def format(collection, csv: false)
|
|
12
12
|
# We want to use the render :collection for each column that renders partials
|
|
13
13
|
rendered = {}
|
|
14
14
|
|
|
15
15
|
columns.each do |name, opts|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
if state[:visible][name] == false && !csv
|
|
17
|
+
# Nothing to do
|
|
18
|
+
elsif csv && !opts[:csv]
|
|
19
|
+
# Nothing to do
|
|
20
|
+
elsif opts[:partial]
|
|
19
21
|
locals = { datatable: self, column: opts }.merge!(resource_col_locals(opts))
|
|
20
22
|
|
|
21
23
|
rendered[name] = (view.render(
|
|
@@ -26,6 +28,7 @@ module Effective
|
|
|
26
28
|
locals: locals,
|
|
27
29
|
spacer_template: SPACER_TEMPLATE
|
|
28
30
|
) || '').split(SPACER)
|
|
31
|
+
|
|
29
32
|
elsif opts[:as] == :actions # This is actions_col and actions_col do .. end, but not actions_col partial: 'something'
|
|
30
33
|
locals = { datatable: self, column: opts, spacer_template: SPACER_TEMPLATE }
|
|
31
34
|
|
|
@@ -45,7 +48,6 @@ module Effective
|
|
|
45
48
|
else
|
|
46
49
|
(view.render_resource_actions(collection.map { |row| row[opts[:index]] }, atts, &opts[:format]) || '').split(SPACER)
|
|
47
50
|
end
|
|
48
|
-
|
|
49
51
|
end
|
|
50
52
|
end
|
|
51
53
|
|
|
@@ -54,9 +56,11 @@ module Effective
|
|
|
54
56
|
index = opts[:index]
|
|
55
57
|
value = row[index]
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
if state[:visible][name] == false
|
|
59
|
+
formatted = (
|
|
60
|
+
if state[:visible][name] == false && !csv
|
|
59
61
|
NONVISIBLE
|
|
62
|
+
elsif csv && !opts[:csv]
|
|
63
|
+
BLANK
|
|
60
64
|
elsif opts[:as] == :actions
|
|
61
65
|
rendered[name][row_index]
|
|
62
66
|
elsif opts[:format] && rendered.key?(name)
|
|
@@ -66,14 +70,23 @@ module Effective
|
|
|
66
70
|
elsif opts[:partial]
|
|
67
71
|
rendered[name][row_index]
|
|
68
72
|
else
|
|
69
|
-
format_column(value, opts)
|
|
73
|
+
format_column(value, opts, csv: csv)
|
|
70
74
|
end
|
|
71
75
|
)
|
|
76
|
+
|
|
77
|
+
if csv && (opts[:format] || opts[:partial])
|
|
78
|
+
formatted = view.strip_tags(formatted)
|
|
79
|
+
|
|
80
|
+
formatted.gsub!("\n\n", ' ') unless formatted.frozen?
|
|
81
|
+
formatted.gsub!("\n", '') unless formatted.frozen?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
row[index] = formatted
|
|
72
85
|
end
|
|
73
86
|
end
|
|
74
87
|
end
|
|
75
88
|
|
|
76
|
-
def format_column(value, column)
|
|
89
|
+
def format_column(value, column, csv: false)
|
|
77
90
|
return if value.nil? || (column[:resource] && value.blank?)
|
|
78
91
|
|
|
79
92
|
unless column[:as] == :email
|
|
@@ -90,7 +103,11 @@ module Effective
|
|
|
90
103
|
when :date
|
|
91
104
|
value.respond_to?(:strftime) ? value.strftime(EffectiveDatatables.format_date) : BLANK
|
|
92
105
|
when :datetime
|
|
93
|
-
|
|
106
|
+
if csv
|
|
107
|
+
value
|
|
108
|
+
else
|
|
109
|
+
value.respond_to?(:strftime) ? value.strftime(EffectiveDatatables.format_datetime) : BLANK
|
|
110
|
+
end
|
|
94
111
|
when :decimal
|
|
95
112
|
value
|
|
96
113
|
when :duration
|
|
@@ -102,7 +119,7 @@ module Effective
|
|
|
102
119
|
when :effective_roles
|
|
103
120
|
value.join(', ')
|
|
104
121
|
when :email
|
|
105
|
-
view.mail_to(value)
|
|
122
|
+
csv ? value : view.mail_to(value)
|
|
106
123
|
when :integer
|
|
107
124
|
value
|
|
108
125
|
when :percent
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
- if datatable._bulk_actions.present?
|
|
2
|
+
= render('/effective/datatables/bulk_actions_dropdown', datatable: datatable)
|
|
3
|
+
|
|
4
|
+
- if datatable.searchable?
|
|
5
|
+
= link_to '#', class: 'btn btn-link btn-sm buttons-reset-search' do
|
|
6
|
+
%span= t('effective_datatables.reset')
|
|
7
|
+
|
|
8
|
+
- if datatable.reorder? && EffectiveDatatables.authorized?(self, :update, datatable.collection_class)
|
|
9
|
+
= link_to '#', class: 'btn btn-link btn-sm buttons-reorder' do
|
|
10
|
+
%span= t('effective_datatables.reorder')
|
|
11
|
+
|
|
12
|
+
- if datatable.downloadable?
|
|
13
|
+
= link_to 'download.csv', class: 'btn btn-link btn-sm buttons-download', 'aria-controls': datatable.to_param do
|
|
14
|
+
%span= t('effective_datatables.download')
|
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.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Code and Effect
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-
|
|
11
|
+
date: 2021-12-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -111,6 +111,7 @@ files:
|
|
|
111
111
|
- app/assets/javascripts/effective_datatables.js
|
|
112
112
|
- app/assets/javascripts/effective_datatables/bulk_actions.js.coffee
|
|
113
113
|
- app/assets/javascripts/effective_datatables/charts.js.coffee
|
|
114
|
+
- app/assets/javascripts/effective_datatables/download.js.coffee
|
|
114
115
|
- app/assets/javascripts/effective_datatables/events.js.coffee
|
|
115
116
|
- app/assets/javascripts/effective_datatables/filters.js.coffee
|
|
116
117
|
- app/assets/javascripts/effective_datatables/flash.js.coffee
|
|
@@ -141,6 +142,7 @@ files:
|
|
|
141
142
|
- app/models/effective/effective_datatable/collection.rb
|
|
142
143
|
- app/models/effective/effective_datatable/compute.rb
|
|
143
144
|
- app/models/effective/effective_datatable/cookie.rb
|
|
145
|
+
- app/models/effective/effective_datatable/csv.rb
|
|
144
146
|
- app/models/effective/effective_datatable/dsl.rb
|
|
145
147
|
- app/models/effective/effective_datatable/dsl/bulk_actions.rb
|
|
146
148
|
- app/models/effective/effective_datatable/dsl/charts.rb
|
|
@@ -154,6 +156,7 @@ files:
|
|
|
154
156
|
- app/views/effective/datatables/_active_storage_column.html.haml
|
|
155
157
|
- app/views/effective/datatables/_bulk_actions_column.html.haml
|
|
156
158
|
- app/views/effective/datatables/_bulk_actions_dropdown.html.haml
|
|
159
|
+
- app/views/effective/datatables/_buttons.html.haml
|
|
157
160
|
- app/views/effective/datatables/_chart.html.haml
|
|
158
161
|
- app/views/effective/datatables/_datatable.html.haml
|
|
159
162
|
- app/views/effective/datatables/_filters.html.haml
|