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