effective_datatables 4.7.16 → 4.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +278 -24
- data/app/assets/javascripts/effective_datatables/bulk_actions.js.coffee +32 -9
- data/app/assets/javascripts/effective_datatables/download.js.coffee +10 -0
- data/app/assets/javascripts/effective_datatables/flash.js.coffee +1 -1
- data/app/assets/javascripts/effective_datatables/initialize.js.coffee +22 -13
- data/app/assets/javascripts/effective_datatables/inline_crud.js.coffee +42 -13
- data/app/assets/javascripts/effective_datatables/reorder.js.coffee +8 -2
- data/app/assets/javascripts/effective_datatables/reset.js.coffee +23 -2
- data/app/assets/javascripts/vendor/jquery.delayedChange.js +1 -2
- data/app/assets/stylesheets/dataTables/dataTables.bootstrap4.scss +1 -3
- 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 +22 -6
- data/app/helpers/effective_datatables_private_helper.rb +30 -20
- data/app/models/effective/datatable.rb +57 -4
- data/app/models/effective/datatable_column.rb +2 -0
- data/app/models/effective/datatable_column_tool.rb +5 -3
- data/app/models/effective/datatable_dsl_tool.rb +7 -5
- data/app/models/effective/datatable_value_tool.rb +9 -8
- data/app/models/effective/effective_datatable/attributes.rb +21 -0
- data/app/models/effective/effective_datatable/collection.rb +3 -1
- data/app/models/effective/effective_datatable/compute.rb +11 -7
- data/app/models/effective/effective_datatable/cookie.rb +6 -0
- data/app/models/effective/effective_datatable/csv.rb +71 -0
- data/app/models/effective/effective_datatable/dsl/bulk_actions.rb +3 -1
- data/app/models/effective/effective_datatable/dsl/charts.rb +2 -0
- data/app/models/effective/effective_datatable/dsl/datatable.rb +20 -6
- data/app/models/effective/effective_datatable/dsl/filters.rb +3 -1
- data/app/models/effective/effective_datatable/dsl.rb +7 -3
- data/app/models/effective/effective_datatable/format.rb +52 -24
- data/app/models/effective/effective_datatable/hooks.rb +2 -0
- data/app/models/effective/effective_datatable/params.rb +9 -2
- data/app/models/effective/effective_datatable/resource.rb +26 -13
- data/app/models/effective/effective_datatable/state.rb +4 -2
- data/app/views/effective/datatables/_active_storage_column.html.haml +4 -0
- data/app/views/effective/datatables/_bulk_actions_dropdown.html.haml +3 -2
- data/app/views/effective/datatables/_buttons.html.haml +14 -0
- data/config/effective_datatables.rb +8 -1
- data/config/locales/en.yml +4 -1
- data/config/locales/es.yml +4 -1
- data/config/locales/nl.yml +4 -1
- data/config/routes.rb +1 -0
- data/lib/effective_datatables/engine.rb +6 -4
- data/lib/effective_datatables/version.rb +1 -1
- data/lib/effective_datatables.rb +5 -0
- metadata +11 -10
- data/app/datatables/effective_style_guide_datatable.rb +0 -47
- data/app/models/effective/access_denied.rb +0 -17
- data/app/views/effective/style_guide/_effective_datatables.html.haml +0 -1
@@ -5,6 +5,9 @@ initializeDataTables = (target) ->
|
|
5
5
|
buttons_export_columns = options['buttons_export_columns'] || ':not(.col-actions)'
|
6
6
|
reorder = datatable.data('reorder')
|
7
7
|
|
8
|
+
if datatable.data('inline') && datatable.closest('form').length > 0
|
9
|
+
console.error('inline datatable cannot work inside a form')
|
10
|
+
|
8
11
|
if options['buttons'] == false
|
9
12
|
options['buttons'] = []
|
10
13
|
|
@@ -49,7 +52,7 @@ initializeDataTables = (target) ->
|
|
49
52
|
displayStart: datatable.data('display-start')
|
50
53
|
iDisplayLength: datatable.data('display-length')
|
51
54
|
language: datatable.data('language')
|
52
|
-
lengthMenu: [[5, 10, 25, 50, 100, 250, 500, 9999999], ['5', '10', '25', '50', '100', '250', '500', '
|
55
|
+
lengthMenu: [[5, 10, 25, 50, 100, 250, 500, 9999999], ['5', '10', '25', '50', '100', '250', '500', datatable.data('all-label')]]
|
53
56
|
order: datatable.data('display-order')
|
54
57
|
processing: true
|
55
58
|
responsive: true
|
@@ -77,7 +80,7 @@ initializeDataTables = (target) ->
|
|
77
80
|
filter_name = name.replace('filters[', '').substring(0, name.length-9)
|
78
81
|
|
79
82
|
params['filter'][filter_name] = $form.find("input[name='#{name}']:checked").val()
|
80
|
-
|
83
|
+
|
81
84
|
else if $input.attr('id')
|
82
85
|
filter_name = $input.attr('id').replace('filters_', '')
|
83
86
|
params['filter'][filter_name] = $input.val()
|
@@ -109,14 +112,8 @@ initializeDataTables = (target) ->
|
|
109
112
|
$table = $(api.table().node())
|
110
113
|
$buttons = $table.closest('.dataTables_wrapper').children().first().find('.dt-buttons')
|
111
114
|
|
112
|
-
if $table.data('
|
113
|
-
$buttons.prepend($table.data('
|
114
|
-
|
115
|
-
if $table.data('reorder')
|
116
|
-
$buttons.prepend($table.data('reorder'))
|
117
|
-
|
118
|
-
if $table.data('bulk-actions')
|
119
|
-
$buttons.prepend($table.data('bulk-actions'))
|
115
|
+
if $table.data('buttons-html')
|
116
|
+
$buttons.prepend($table.data('buttons-html'))
|
120
117
|
|
121
118
|
drawAggregates = ($table, aggregates) ->
|
122
119
|
$tfoot = $table.find('tfoot').first()
|
@@ -131,7 +128,7 @@ initializeDataTables = (target) ->
|
|
131
128
|
if typeof(google) != 'undefined' && typeof(google.visualization) != 'undefined'
|
132
129
|
$.each charts, (name, data) =>
|
133
130
|
$(".effective-datatables-chart[data-name='#{name}']").each (_, obj) =>
|
134
|
-
chart = new google.visualization[data['
|
131
|
+
chart = new google.visualization[data['as']](obj)
|
135
132
|
chart.draw(google.visualization.arrayToDataTable(data['data']), data['options'])
|
136
133
|
|
137
134
|
# Appends the search html, stored in the column definitions, into each column header
|
@@ -162,14 +159,23 @@ initializeDataTables = (target) ->
|
|
162
159
|
$input.on 'change', (event) -> dataTableSearch($(event.currentTarget))
|
163
160
|
else if $input.is('input')
|
164
161
|
$input.delayedChange ($input) -> dataTableSearch($input)
|
165
|
-
$input.on('paste', -> dataTableSearch($input))
|
166
162
|
|
167
163
|
# Do the actual search
|
168
164
|
dataTableSearch = ($input) -> # This is the function called by a select or input to run the search
|
169
165
|
return if $input.is(':invalid')
|
170
166
|
|
171
167
|
table = $input.closest('table.dataTable')
|
172
|
-
|
168
|
+
|
169
|
+
value = $input.val()
|
170
|
+
|
171
|
+
if Array.isArray(value)
|
172
|
+
# Nothing
|
173
|
+
else if value.startsWith('"') && value.endsWith('"')
|
174
|
+
value = value.substring(1, value.length-1)
|
175
|
+
else
|
176
|
+
value = $.trim(value)
|
177
|
+
|
178
|
+
table.DataTable().column("#{$input.data('column-name')}:name").search(value).draw()
|
173
179
|
|
174
180
|
if reorder
|
175
181
|
init_options['rowReorder'] = { selector: 'td.col-_reorder', snapX: true, dataSrc: datatable.data('reorder-index') }
|
@@ -188,6 +194,7 @@ initializeDataTables = (target) ->
|
|
188
194
|
|
189
195
|
table.addClass('initialized')
|
190
196
|
table.children('thead').trigger('effective-bootstrap:initialize')
|
197
|
+
table.children('thead').find('input[autofocus]').first().focus()
|
191
198
|
true
|
192
199
|
|
193
200
|
destroyDataTables = ->
|
@@ -201,3 +208,5 @@ $(document).on 'page:change', -> initializeDataTables()
|
|
201
208
|
$(document).on 'turbolinks:load', -> initializeDataTables()
|
202
209
|
$(document).on 'turbolinks:render', -> initializeDataTables()
|
203
210
|
$(document).on 'turbolinks:before-cache', -> destroyDataTables()
|
211
|
+
$(document).on 'turbo:load', -> initializeDataTables()
|
212
|
+
$(document).on 'turbo:before-cache', -> destroyDataTables()
|
@@ -1,15 +1,34 @@
|
|
1
1
|
# To achieve inline crud, we use rails' data-remote links, and override their behaviour when inside a datatable
|
2
2
|
# This works with EffectiveForm.remote_form which is part of the effective_bootstrap gem.
|
3
3
|
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
# https://github.com/rails/jquery-ujs/wiki/ajax
|
5
|
+
# https://edgeguides.rubyonrails.org/working_with_javascript_in_rails.html#rails-ujs-event-handlers
|
6
|
+
|
7
|
+
$(document).on 'ajax:before', '.dataTables_wrapper .col-actions', (event) ->
|
8
|
+
$action = $(event.target)
|
9
|
+
$table = $(event.target).closest('table')
|
8
10
|
|
9
11
|
return true if ('' + $action.data('inline')) == 'false'
|
10
12
|
|
11
|
-
$params = $.param(
|
12
|
-
|
13
|
+
$params = $.param(
|
14
|
+
{
|
15
|
+
_datatable_id: $table.attr('id'),
|
16
|
+
_datatable_attributes: $table.data('attributes'),
|
17
|
+
_datatable_action: true
|
18
|
+
}
|
19
|
+
)
|
20
|
+
|
21
|
+
$action.attr('data-params', $params)
|
22
|
+
true
|
23
|
+
|
24
|
+
# We click the New/Edit/Action button from the col-actions
|
25
|
+
$(document).on 'ajax:beforeSend', '.dataTables_wrapper .col-actions', (event, xhr, settings) ->
|
26
|
+
[xhr, settings] = event.detail if event.detail # rails/ujs
|
27
|
+
|
28
|
+
$action = $(event.target)
|
29
|
+
$table = $(event.target).closest('table')
|
30
|
+
|
31
|
+
return true if ('' + $action.data('inline')) == 'false'
|
13
32
|
|
14
33
|
if $action.closest('.effective-datatables-inline-row,table.dataTable').hasClass('effective-datatables-inline-row')
|
15
34
|
# Nothing.
|
@@ -22,6 +41,8 @@ $(document).on 'ajax:beforeSend', '.dataTables_wrapper .col-actions', (e, xhr, s
|
|
22
41
|
|
23
42
|
# We have either completed the resource action, or fetched the inline form to load.
|
24
43
|
$(document).on 'ajax:success', '.dataTables_wrapper .col-actions', (event, data) ->
|
44
|
+
[data, status, xhr] = event.detail if event.detail # rails/ujs
|
45
|
+
|
25
46
|
$action = $(event.target)
|
26
47
|
|
27
48
|
return true if ('' + $action.data('inline')) == 'false'
|
@@ -54,12 +75,22 @@ $(document).on 'ajax:error', '.dataTables_wrapper', (event) ->
|
|
54
75
|
EffectiveForm.remote_form_flash = ''
|
55
76
|
true
|
56
77
|
|
57
|
-
|
58
|
-
|
59
|
-
|
78
|
+
## Now for the fetched form. We add the datatables params attributes
|
79
|
+
|
80
|
+
$(document).on 'ajax:before', '.dataTables_wrapper .col-inline-form', (event) ->
|
81
|
+
$action = $(event.target)
|
82
|
+
$form = $action.closest('form')
|
83
|
+
$table = $action.closest('table')
|
84
|
+
|
85
|
+
if $form.find('input[name=_datatable_id]').length == 0
|
86
|
+
$('<input>').attr(
|
87
|
+
{type: 'hidden', name: '_datatable_id', value: $table.attr('id')}
|
88
|
+
).appendTo($form)
|
60
89
|
|
61
|
-
|
62
|
-
|
90
|
+
if $form.find('input[name=_datatable_attributes]').length == 0
|
91
|
+
$('<input>').attr(
|
92
|
+
{type: 'hidden', name: '_datatable_attributes', value: $table.data('attributes')}
|
93
|
+
).appendTo($form)
|
63
94
|
|
64
95
|
true
|
65
96
|
|
@@ -92,7 +123,6 @@ beforeNew = ($action) ->
|
|
92
123
|
|
93
124
|
# Append spinner and show Processing
|
94
125
|
$th.append($table.data('spinner'))
|
95
|
-
$table.DataTable().flash()
|
96
126
|
$table.one 'draw.dt', (event) ->
|
97
127
|
$th.find('a').show().siblings('svg').remove() if event.target == event.currentTarget
|
98
128
|
|
@@ -121,7 +151,6 @@ beforeEdit = ($action) ->
|
|
121
151
|
|
122
152
|
# Append spinner and show Processing
|
123
153
|
$td.append($table.data('spinner'))
|
124
|
-
$table.DataTable().flash()
|
125
154
|
|
126
155
|
afterEdit = ($action) ->
|
127
156
|
$tr = $action.closest('tr')
|
@@ -8,7 +8,14 @@ reorder = (event, diff, edit) ->
|
|
8
8
|
return unless oldNode? && newNode?
|
9
9
|
|
10
10
|
url = @context[0].ajax.url.replace('.json', '/reorder.json')
|
11
|
-
|
11
|
+
|
12
|
+
data = {
|
13
|
+
'authenticity_token': $('head').find("meta[name='csrf-token']").attr('content'),
|
14
|
+
'reorder[id]': oldNode.data('reorder-resource'),
|
15
|
+
'reorder[old]': oldNode.val(),
|
16
|
+
'reorder[new]': newNode.val(),
|
17
|
+
'attributes': $table.data('attributes')
|
18
|
+
}
|
12
19
|
|
13
20
|
@context[0].rowreorder.c.enable = false
|
14
21
|
|
@@ -40,4 +47,3 @@ $(document).on 'click', '.dataTables_wrapper a.buttons-reorder', (event) ->
|
|
40
47
|
$table.addClass('reordering')
|
41
48
|
|
42
49
|
column.visible(!column.visible())
|
43
|
-
|
@@ -1,11 +1,32 @@
|
|
1
1
|
$(document).on 'click', '.dataTables_wrapper a.buttons-reset-search', (event) ->
|
2
2
|
event.preventDefault() # prevent the click
|
3
3
|
|
4
|
+
# Reset the HTML
|
4
5
|
$table = $(event.currentTarget).closest('.dataTables_wrapper').find('table.dataTable').first()
|
5
6
|
$thead = $table.children('thead').first()
|
6
7
|
|
7
|
-
|
8
|
+
# Reset all inputs
|
8
9
|
$thead.find('select').val('').trigger('change.select2')
|
9
10
|
|
10
|
-
$
|
11
|
+
$inputs = $thead.find('input')
|
12
|
+
$inputs.val('').removeAttr('checked').removeAttr('selected')
|
13
|
+
|
14
|
+
# Reset delayedChange
|
15
|
+
$.each $inputs, (input) =>
|
16
|
+
$input = $(input)
|
17
|
+
if ($input.delayedChange.oldVal)
|
18
|
+
$input.delayedChange.oldVal = undefined
|
19
|
+
|
20
|
+
# Reset the datatable
|
21
|
+
datatable = $table.DataTable()
|
22
|
+
|
23
|
+
# Reset search
|
24
|
+
datatable.search('').columns().search('')
|
25
|
+
|
26
|
+
# Reset to default visibility
|
27
|
+
$.each $table.data('default-visibility'), (index, visible) =>
|
28
|
+
datatable.column(index).visible(visible, false)
|
29
|
+
|
30
|
+
# Don't pass up the click
|
31
|
+
false
|
11
32
|
|
@@ -14,11 +14,10 @@
|
|
14
14
|
|
15
15
|
return this.each(function() {
|
16
16
|
var element = $(this);
|
17
|
-
element.keyup
|
17
|
+
element.on('keyup paste', function() {
|
18
18
|
clearTimeout(timer);
|
19
19
|
timer = setTimeout(function() {
|
20
20
|
var newVal = element.val();
|
21
|
-
newVal = $.trim(newVal);
|
22
21
|
if (element.delayedChange.oldVal != newVal) {
|
23
22
|
element.delayedChange.oldVal = newVal;
|
24
23
|
o.onChange.call(this, element);
|
@@ -162,9 +162,7 @@ div.dataTables_scrollFoot > .dataTables_scrollFootInner > table {
|
|
162
162
|
text-align: center;
|
163
163
|
}
|
164
164
|
}
|
165
|
-
|
166
|
-
padding-right: 20px;
|
167
|
-
}
|
165
|
+
|
168
166
|
table.dataTable.table-sm .sorting:before,
|
169
167
|
table.dataTable.table-sm .sorting_asc:before,
|
170
168
|
table.dataTable.table-sm .sorting_desc:before {
|
@@ -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,18 +1,29 @@
|
|
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, 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
|
21
|
+
datatable.attributes[:namespace] = namespace if namespace
|
13
22
|
|
14
23
|
datatable.view ||= self
|
15
24
|
|
25
|
+
datatable.state[:length] = 9999999 if simple
|
26
|
+
|
16
27
|
unless EffectiveDatatables.authorized?(controller, :index, datatable.collection_class)
|
17
28
|
return content_tag(:p, "You are not authorized to view this datatable. (cannot :index, #{datatable.collection_class})")
|
18
29
|
end
|
@@ -40,10 +51,12 @@ module EffectiveDatatablesHelper
|
|
40
51
|
id: datatable.to_param,
|
41
52
|
class: html_class,
|
42
53
|
data: {
|
54
|
+
'all-label' => I18n.t('effective_datatables.all'),
|
43
55
|
'attributes' => EffectiveDatatables.encrypt(datatable.attributes),
|
44
56
|
'authenticity-token' => form_authenticity_token,
|
45
|
-
'
|
57
|
+
'buttons-html' => datatable_buttons(datatable),
|
46
58
|
'columns' => datatable_columns(datatable),
|
59
|
+
'default-visibility' => datatable.default_visibility.to_json,
|
47
60
|
'display-length' => datatable.display_length,
|
48
61
|
'display-order' => datatable_display_order(datatable),
|
49
62
|
'display-records' => datatable.to_json[:recordsFiltered],
|
@@ -51,8 +64,7 @@ module EffectiveDatatablesHelper
|
|
51
64
|
'inline' => inline.to_s,
|
52
65
|
'language' => EffectiveDatatables.language(I18n.locale),
|
53
66
|
'options' => input_js.to_json,
|
54
|
-
'
|
55
|
-
'reorder' => datatable_reorder(datatable),
|
67
|
+
'reorder' => datatable.reorder?.to_s,
|
56
68
|
'reorder-index' => (datatable.columns[:_reorder][:index] if datatable.reorder?).to_s,
|
57
69
|
'simple' => simple.to_s,
|
58
70
|
'spinner' => icon('spinner'), # effective_bootstrap
|
@@ -61,7 +73,7 @@ module EffectiveDatatablesHelper
|
|
61
73
|
}
|
62
74
|
}
|
63
75
|
|
64
|
-
if (charts || filters)
|
76
|
+
retval = if (charts || filters)
|
65
77
|
output = ''.html_safe
|
66
78
|
|
67
79
|
if charts
|
@@ -82,6 +94,10 @@ module EffectiveDatatablesHelper
|
|
82
94
|
locals: { datatable: datatable, effective_datatable_params: effective_datatable_params }
|
83
95
|
)
|
84
96
|
end
|
97
|
+
|
98
|
+
Rails.logger.info(" Rendered datatable #{datatable.class} #{datatable.source_location}")
|
99
|
+
|
100
|
+
retval
|
85
101
|
end
|
86
102
|
|
87
103
|
def render_inline_datatable(datatable)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# These aren't expected to be called by a developer. They are internal methods.
|
2
4
|
module EffectiveDatatablesPrivateHelper
|
3
5
|
|
@@ -19,32 +21,21 @@ 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)
|
42
|
-
return unless
|
33
|
+
return unless datatable.inline? && (column[:actions][:new] != false)
|
43
34
|
|
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
|
-
|
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)
|
@@ -68,8 +59,23 @@ module EffectiveDatatablesPrivateHelper
|
|
68
59
|
when :reorder
|
69
60
|
content_tag(:span, t('effective_datatables.reorder'), style: 'display: none;')
|
70
61
|
else
|
71
|
-
|
62
|
+
label = opts[:label].presence || datatable_human_attribute_name(datatable, name, opts)
|
63
|
+
content_tag(:span, label)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def datatable_human_attribute_name(datatable, name, opts)
|
68
|
+
return (name.to_s.split('.').last || '').titleize unless datatable.active_record_collection?
|
69
|
+
|
70
|
+
case opts[:as]
|
71
|
+
when :belongs_to
|
72
|
+
opts[:resource].human_name
|
73
|
+
when :has_many
|
74
|
+
opts[:resource].human_plural_name
|
75
|
+
else
|
76
|
+
datatable.collection_class.human_attribute_name(name)
|
72
77
|
end
|
78
|
+
|
73
79
|
end
|
74
80
|
|
75
81
|
def datatable_search_tag(datatable, name, opts)
|
@@ -78,13 +84,13 @@ module EffectiveDatatablesPrivateHelper
|
|
78
84
|
return if opts[:search] == false
|
79
85
|
|
80
86
|
# Build the search
|
81
|
-
@_effective_datatables_form_builder || effective_form_with(scope:
|
87
|
+
@_effective_datatables_form_builder || effective_form_with(scope: 'datatable_search', url: '#') { |f| @_effective_datatables_form_builder = f }
|
82
88
|
form = @_effective_datatables_form_builder
|
83
89
|
|
84
90
|
collection = opts[:search].delete(:collection)
|
85
91
|
value = datatable.state[:search][name]
|
86
92
|
|
87
|
-
options = opts[:search].
|
93
|
+
options = opts[:search].merge(
|
88
94
|
name: nil,
|
89
95
|
feedback: false,
|
90
96
|
label: false,
|
@@ -92,6 +98,8 @@ module EffectiveDatatablesPrivateHelper
|
|
92
98
|
data: { 'column-name': name, 'column-index': opts[:index] }
|
93
99
|
)
|
94
100
|
|
101
|
+
options.delete(:fuzzy)
|
102
|
+
|
95
103
|
case options.delete(:as)
|
96
104
|
when :string, :text, :number
|
97
105
|
form.text_field name, options
|
@@ -139,7 +147,7 @@ module EffectiveDatatablesPrivateHelper
|
|
139
147
|
placeholder: (opts[:label] || name.to_s.titleize),
|
140
148
|
value: value,
|
141
149
|
wrapper: { class: 'form-group col-auto'}
|
142
|
-
}.merge(opts.except(:as, :collection, :parse))
|
150
|
+
}.merge(opts.except(:as, :collection, :parse, :value))
|
143
151
|
|
144
152
|
options[:name] = '' unless datatable._filters_form_required?
|
145
153
|
|
@@ -149,6 +157,8 @@ module EffectiveDatatablesPrivateHelper
|
|
149
157
|
elsif as == :boolean
|
150
158
|
collection ||= [true, false].map { |value| [t("effective_datatables.boolean_#{value}"), value] }
|
151
159
|
form.public_send(:select, name, collection, options) # boolean
|
160
|
+
elsif as == :string
|
161
|
+
form.public_send(:text_field, name, options)
|
152
162
|
elsif form.respond_to?(as)
|
153
163
|
form.public_send(as, name, options) # check_box, text_area
|
154
164
|
else
|
@@ -169,7 +179,7 @@ module EffectiveDatatablesPrivateHelper
|
|
169
179
|
label: false,
|
170
180
|
required: false,
|
171
181
|
wrapper: { class: 'form-group col-auto'}
|
172
|
-
}.merge(opts)
|
182
|
+
}.merge(opts.except(:checked, :value))
|
173
183
|
|
174
184
|
form.radios :scope, collection, options
|
175
185
|
end
|
@@ -1,8 +1,10 @@
|
|
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.
|
4
|
-
attr_reader :effective_resource
|
5
6
|
attr_reader :state
|
7
|
+
attr_accessor :effective_resource
|
6
8
|
|
7
9
|
# Hashes of DSL options
|
8
10
|
attr_reader :_aggregates
|
@@ -21,22 +23,26 @@ module Effective
|
|
21
23
|
# The view
|
22
24
|
attr_reader :view
|
23
25
|
|
26
|
+
# Set by DSL so we can track where this datatable is coming from
|
27
|
+
attr_accessor :source_location
|
28
|
+
|
24
29
|
extend Effective::EffectiveDatatable::Dsl
|
25
30
|
|
26
31
|
include Effective::EffectiveDatatable::Attributes
|
27
32
|
include Effective::EffectiveDatatable::Collection
|
28
33
|
include Effective::EffectiveDatatable::Compute
|
29
34
|
include Effective::EffectiveDatatable::Cookie
|
35
|
+
include Effective::EffectiveDatatable::Csv
|
30
36
|
include Effective::EffectiveDatatable::Format
|
31
37
|
include Effective::EffectiveDatatable::Hooks
|
32
38
|
include Effective::EffectiveDatatable::Params
|
33
39
|
include Effective::EffectiveDatatable::Resource
|
34
40
|
include Effective::EffectiveDatatable::State
|
35
41
|
|
36
|
-
def initialize(view = nil, attributes = nil)
|
42
|
+
def initialize(view = nil, attributes = nil)
|
37
43
|
(attributes = view; view = nil) if view.kind_of?(Hash)
|
38
44
|
|
39
|
-
@attributes = (attributes
|
45
|
+
@attributes = initial_attributes(attributes)
|
40
46
|
@state = initial_state
|
41
47
|
|
42
48
|
@_aggregates = {}
|
@@ -49,13 +55,40 @@ module Effective
|
|
49
55
|
|
50
56
|
raise 'expected a hash of arguments' unless @attributes.kind_of?(Hash)
|
51
57
|
raise 'collection is defined as a method. Please use the collection do ... end syntax.' unless collection.nil?
|
58
|
+
|
52
59
|
self.view = view if view
|
53
60
|
end
|
54
61
|
|
62
|
+
def rendered(params = {})
|
63
|
+
raise('expected a hash of params') unless params.kind_of?(Hash)
|
64
|
+
|
65
|
+
view = ApplicationController.renderer.controller.helpers
|
66
|
+
|
67
|
+
view.class_eval do
|
68
|
+
attr_accessor :rendered_params
|
69
|
+
|
70
|
+
def current_user
|
71
|
+
rendered_params[:current_user]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
if params[:current_user_id]
|
76
|
+
params[:current_user] = User.find(params[:current_user_id])
|
77
|
+
end
|
78
|
+
|
79
|
+
view.rendered_params = params
|
80
|
+
|
81
|
+
self.view = view
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
55
85
|
# Once the view is assigned, we initialize everything
|
56
86
|
def view=(view)
|
57
87
|
@view = (view.respond_to?(:view_context) ? view.view_context : view)
|
58
|
-
|
88
|
+
|
89
|
+
unless @view.respond_to?(:params) || @view.respond_to?(:rendered_params)
|
90
|
+
raise 'expected view to respond to params'
|
91
|
+
end
|
59
92
|
|
60
93
|
assert_attributes!
|
61
94
|
load_attributes!
|
@@ -110,6 +143,10 @@ module Effective
|
|
110
143
|
to_json[:recordsTotal] == 0
|
111
144
|
end
|
112
145
|
|
146
|
+
def to_csv
|
147
|
+
csv_file()
|
148
|
+
end
|
149
|
+
|
113
150
|
def to_json
|
114
151
|
@json ||= (
|
115
152
|
{
|
@@ -137,6 +174,18 @@ module Effective
|
|
137
174
|
!reorder? && attributes[:sortable] != false
|
138
175
|
end
|
139
176
|
|
177
|
+
def searchable?
|
178
|
+
attributes[:searchable] != false
|
179
|
+
end
|
180
|
+
|
181
|
+
def downloadable?
|
182
|
+
attributes[:downloadable] != false
|
183
|
+
end
|
184
|
+
|
185
|
+
def skip_save_state?
|
186
|
+
attributes[:skip_save_state] == true
|
187
|
+
end
|
188
|
+
|
140
189
|
# Whether the filters must be rendered as a <form> or we can keep the normal <div> behaviour
|
141
190
|
def _filters_form_required?
|
142
191
|
_form[:verb].present?
|
@@ -170,6 +219,10 @@ module Effective
|
|
170
219
|
@fallback_effective_resource ||= Effective::Resource.new('', namespace: controller_namespace)
|
171
220
|
end
|
172
221
|
|
222
|
+
def default_visibility
|
223
|
+
columns.values.inject({}) { |h, col| h[col[:index]] = col[:visible]; h }
|
224
|
+
end
|
225
|
+
|
173
226
|
private
|
174
227
|
|
175
228
|
def column_tool
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Effective
|
2
4
|
class DatatableColumnTool
|
3
5
|
attr_reader :datatable
|
@@ -43,7 +45,7 @@ module Effective
|
|
43
45
|
Rails.logger.info "COLUMN TOOL: order_column #{column.to_s} #{direction} #{sql_column}" if EffectiveDatatables.debug
|
44
46
|
|
45
47
|
if column[:sql_as_column]
|
46
|
-
collection.order("#{sql_column} #{datatable.effective_resource.sql_direction(direction)}")
|
48
|
+
collection.order(Arel.sql("#{sql_column} #{datatable.effective_resource.sql_direction(direction)}"))
|
47
49
|
else
|
48
50
|
Effective::Resource.new(collection)
|
49
51
|
.order(column[:name], direction, as: column[:as], sort: column[:sort], sql_column: column[:sql_column], limit: datatable.limit)
|
@@ -73,10 +75,10 @@ module Effective
|
|
73
75
|
end
|
74
76
|
|
75
77
|
def search_column(collection, value, column, sql_column)
|
76
|
-
Rails.logger.info "COLUMN TOOL: search_column #{column.to_s}
|
78
|
+
Rails.logger.info "COLUMN TOOL: search_column #{column.to_s} value=#{value} operation=#{column[:search][:operation]} column=#{sql_column}" if EffectiveDatatables.debug
|
77
79
|
|
78
80
|
Effective::Resource.new(collection)
|
79
|
-
.search(column[:name], value, as: column[:as],
|
81
|
+
.search(column[:name], value, as: column[:as], operation: column[:search][:operation], column: sql_column)
|
80
82
|
end
|
81
83
|
|
82
84
|
def paginate(collection)
|