effective_datatables 2.2.11 → 2.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 925dbdcde129594000b35c33bdcca9d40a2270a2
4
- data.tar.gz: 45aaec09d5196309e7975c51de52ad97f2d52690
3
+ metadata.gz: 46c6bd508e444a1f4d8bf7c6076bcc3885e5bf99
4
+ data.tar.gz: 3188dca9b7172fe41c80d514a49975ee6d7eee44
5
5
  SHA512:
6
- metadata.gz: 3cd98bc405d0be8aa0da7c8fccbc0657334bdb619ffef1f4f114051ec1ab2ab8d5ace4806da40ce2178855118e351fba9b03d357599e4a04cd080f82b24ee049
7
- data.tar.gz: d743c635c18acb6b64a051e944fcc787e57f753f6b3a0767da299711262542dcca7e5d146f02885e9e1bc91d28c0c2950d5bb0e34c7bde8639d36dd29ceeec07
6
+ metadata.gz: 6619d02959a4c5f48300477daec85d2a39ab0e01240281e52cc26f7d58f451e7a305846d085b82eb6c7722ada044ebd0babbbe77cee5c3330812ec075b52bc14
7
+ data.tar.gz: 3d93e98a7919a064d2556c6f2479a46a5f5c21bd6e8b00ea7a833874f5048d9b8d0248d980f05e81d8425e0632fcbaff3c9e9624bb6e1f40bcc6f24e1de3569d
data/README.md CHANGED
@@ -435,6 +435,59 @@ Optionally uses the authorization method (below) to determine if the `current_us
435
435
 
436
436
  See the `config/initializers/effective_datatable.rb` file for more information.
437
437
 
438
+ ## bulk_actions_column
439
+
440
+ Creates a column of checkboxes to select one, some, or all rows and adds a bulk actions dropdown button.
441
+
442
+ When one or more checkboxes are checked, the bulk actions dropdown is enabled and any defined `bulk_action`s will be available to click.
443
+
444
+ Clicking a bulk action makes an AJAX POST request with the parameters `ids: [1, 2, 3]` as per the selected rows.
445
+
446
+ By default, the method used to determine each row's checkbox value is `to_param`. To call a different method use `bulk_actions_column(resource_method: :slug) do ... end`.
447
+
448
+ This feature has been built with an ActiveRecord collection in mind. To work with an Array backed collection try `resource_method: :first` or similar.
449
+
450
+ After the AJAX request is done, the datatable will be redrawn so any changes made to the collection will be displayed immediately.
451
+
452
+ You can define any number of `bulk_action`s, and separate them with one or more `bulk_action_divider`s.
453
+
454
+ The `bulk_action` method is just an alias for `link_to`, so all the same options will work.
455
+
456
+ ```ruby
457
+ bulk_actions_column do
458
+ bulk_action 'Approve all', bulk_approve_posts_path, data: { confirm: 'Approve all selected posts?' }
459
+ bulk_action_divider
460
+ bulk_action 'Send emails', bulk_email_posts_path, data : { confirm: 'Really send emails?' }
461
+ end
462
+ ```
463
+
464
+ You still need to write your own controller action to process the bulk action. Something like:
465
+
466
+ ```ruby
467
+ class PostsController < ApplicationController
468
+ def bulk_approve
469
+ @posts = Post.where(id: params[:ids])
470
+
471
+ # You should probably write this inside a transaction. This is just an example.
472
+ begin
473
+ @posts.each { |post| post.approve! }
474
+ rescue => e
475
+ render json: { status: 500, message: 'An error occured while approving a post.' }
476
+ end
477
+ end
478
+ end
479
+ ```
480
+
481
+ and in your `routes.rb`:
482
+
483
+ ```ruby
484
+ resources :posts do
485
+ collection do
486
+ post :bulk_approve
487
+ end
488
+ end
489
+ ```
490
+
438
491
  ## table_columns
439
492
 
440
493
  Quickly create multiple table_columns all with default options:
@@ -461,7 +514,6 @@ default_entries :all
461
514
 
462
515
  Valid options are `10, 25, 50, 100, 250, 1000, :all`
463
516
 
464
-
465
517
  ## Additional Functionality
466
518
 
467
519
  There are a few other ways to customize the behaviour of effective_datatables
@@ -15,7 +15,8 @@
15
15
  //= require dataTables/responsive/dataTables.responsive.min
16
16
  //= require dataTables/responsive/responsive.bootstrap.min
17
17
 
18
- //= require_tree ./effective_datatables
18
+ //= require effective_datatables/bulk_actions
19
+ //= require effective_datatables/initialize
19
20
 
20
21
  $.extend( $.fn.dataTable.defaults, {
21
22
  'dom': "<'row'<'col-sm-4'l><'col-sm-8'B>><'row'<'col-sm-12'tr>><'row'<'col-sm-6'i><'col-sm-6'p>>"
@@ -0,0 +1,58 @@
1
+ #### Checkbox toggling and Bulk Actions dropdown disabling
2
+
3
+ $(document).on 'change', "input[data-role='bulk-actions-resource']", (event) ->
4
+ $("input[data-role='bulk-actions-all']").prop('checked', false)
5
+ toggleClosestBulkActionsButton($(event.currentTarget))
6
+
7
+ $(document).on 'change', "input[data-role='bulk-actions-all']", (event) ->
8
+ $checkAll = $(event.currentTarget)
9
+ $resources = $("input[data-role='bulk-actions-resource']")
10
+
11
+ if $checkAll.is(':checked')
12
+ $resources.prop('checked', true)
13
+ else
14
+ $resources.prop('checked', false)
15
+
16
+ toggleClosestBulkActionsButton($checkAll)
17
+
18
+ toggleClosestBulkActionsButton = (element) ->
19
+ $wrapper = element.closest('.dataTables_wrapper')
20
+ $bulkActions = $wrapper.children().first().find('.buttons-bulk-actions').children('button')
21
+
22
+ if $wrapper.find("input[data-role='bulk-actions-resource']:checked").length > 0
23
+ $bulkActions.removeAttr('disabled')
24
+ else
25
+ $bulkActions.attr('disabled', 'disabled')
26
+
27
+
28
+ #### Bulk Action link behaviour
29
+ $(document).on 'click', '.buttons-bulk-actions a', (event) ->
30
+ event.preventDefault() # prevent the click
31
+
32
+ $bulkAction = $(event.currentTarget) # This is a regular <a href=...> tag
33
+ $wrapper = $bulkAction.closest('.dataTables_wrapper')
34
+ $table = $wrapper.find('table.dataTable').first()
35
+ $selected = $table.find("input[data-role='bulk-actions-resource']:checked")
36
+
37
+ url = $bulkAction.attr('href')
38
+ title = $bulkAction.text()
39
+ values = $.map($selected, (input) -> input.getAttribute('value'))
40
+
41
+ return unless url && values
42
+
43
+ # Show Processing... and disable the Bulk Actions dropdown
44
+ $table.siblings('.dataTables_processing').show()
45
+ $wrapper.children().first().find('.buttons-bulk-actions').children('button').attr('disabled', 'disabled')
46
+
47
+ $.post(
48
+ url, { ids: values }
49
+ ).done((response) ->
50
+ success = response['message'] || "Successfully completed #{title} bulk action"
51
+ $table.siblings('.dataTables_processing').html(success)
52
+ ).fail((response) ->
53
+ error = response['message'] || "An error occured while attempting #{title} bulk action: #{response.statusText}"
54
+ alert(error)
55
+ ).always((response) ->
56
+ $table.dataTable().data('bulk-actions-restore-selected-values', values)
57
+ $table.DataTable().draw()
58
+ )
@@ -64,27 +64,53 @@ initializeDataTables = ->
64
64
  serverSide: true
65
65
  scrollCollapse: true
66
66
  pagingType: 'simple_numbers'
67
- headerCallback: ->
68
- table = this.api()
69
- table.columns().flatten().each (index) =>
70
- $th = $(table.column(index).header())
71
- return if $th.hasClass('initialized')
67
+ initComplete: (settings) ->
68
+ initializeBulkActions(this.api())
69
+ initializeFilters(this.api())
70
+ drawCallback: (settings) ->
71
+ $table = $(this.api().table().node())
72
+ selected = $table.data('bulk-actions-restore-selected-values')
73
+ completeBulkAction($table, selected) if selected && selected.length > 0
74
+
75
+ # Copies the bulk actions html, stored in a data attribute on the table, into the buttons area
76
+ initializeBulkActions = (api) ->
77
+ $table = $(api.table().node())
78
+ bulkActions = $table.data('bulk-actions')
79
+
80
+ if bulkActions
81
+ $table.closest('.dataTables_wrapper').children().first()
82
+ .find('.dt-buttons').first().prepend(bulkActions['dropdownHtml'])
83
+
84
+ # After we perform a bulk action, we have to re-select the checkboxes manually and do a bit of house keeping
85
+ completeBulkAction = ($table, selected) ->
86
+ $table.find("input[data-role='bulk-actions-resource']").each (_, input) ->
87
+ $input = $(input)
88
+ $input.prop('checked', selected.indexOf($input.val()) > -1)
72
89
 
73
- settings = table.settings()[0].aoColumns[index] # column specific settings
90
+ $wrapper = $table.closest('.dataTables_wrapper')
91
+ $wrapper.children().first().find('.buttons-bulk-actions').children('button').removeAttr('disabled')
92
+ $table.siblings('.dataTables_processing').html('Processing...')
74
93
 
75
- if settings.filterSelectedValue # Assign preselected filter values
76
- table.settings()[0].aoPreSearchCols[index].sSearch = settings.filterSelectedValue
94
+ # Appends the filter html, stored in the column definitions, into each column header
95
+ initializeFilters = (api) ->
96
+ api.columns().flatten().each (index) =>
97
+ $th = $(api.column(index).header())
98
+ settings = api.settings()[0].aoColumns[index] # column specific settings
77
99
 
78
- if settings.filterHtml # Append the html filter HTML and initialize input events
79
- $th.append('<br>' + settings.filterHtml)
80
- initializeFilterEvents($th)
100
+ if settings.filterSelectedValue # Assign preselected filter values
101
+ api.settings()[0].aoPreSearchCols[index].sSearch = settings.filterSelectedValue
81
102
 
82
- $th.addClass('initialized')
103
+ if settings.filterHtml # Append the html filter HTML and initialize input events
104
+ $th.append('<br>' + settings.filterHtml)
105
+ initializeFilterEvents($th)
83
106
 
84
107
  # Sets up the proper events for each input
85
- initializeFilterEvents = (th) ->
86
- th.find('input,select').each (_, input) ->
108
+ initializeFilterEvents = ($th) ->
109
+ $th.find('input,select').each (_, input) ->
87
110
  $input = $(input)
111
+
112
+ return true if $input.attr('type') == 'hidden' || $input.attr('type') == 'checkbox'
113
+
88
114
  $input.parent().on 'click', (event) -> false # Dont order columns when you click inside the input
89
115
  $input.parent().on 'mousedown', (event) -> event.stopPropagation() # Dont order columns when you click inside the input
90
116
 
@@ -34,6 +34,7 @@ table.dataTable thead tr th {
34
34
  padding: 6px;
35
35
 
36
36
  .form-group { margin-bottom: 0px; font-weight: normal; }
37
+
37
38
  }
38
39
 
39
40
  table.dataTable td ul {
@@ -50,12 +51,15 @@ table.dataTable.sort-hidden thead .sorting_desc { background-image: none; }
50
51
  table.dataTable .form-group { width: 100%; margin-left: 0px; margin-right: 0px; }
51
52
  table.dataTable input,select { width: 100%; }
52
53
  table.dataTable .form-control { width: 100%; }
54
+ table.dataTable .form-group.datatable_filter_bulk_actions { margin-left: 4px; }
53
55
 
54
56
  // Processing popup
55
57
  div.dataTables_wrapper div.dataTables_processing {
56
58
  top: -20px;
57
59
  border: none;
58
60
  box-shadow: none;
61
+ margin-left: -40%;
62
+ width: 80%;
59
63
  }
60
64
 
61
65
  // Show x per page
@@ -73,33 +77,35 @@ div.dt-buttons {
73
77
  width: auto;
74
78
  }
75
79
 
76
- .dt-buttons > a {
77
- border: none;
78
- color: #337ab7;
79
- border-color: transparent;
80
- background: #fff;
81
- font-size: 12px;
82
- padding: 6px;
83
- margin-right: 6px;
84
- float: none;
85
-
86
- &:hover,
87
- &:focus {
80
+ .dt-buttons {
81
+ a, button {
88
82
  border: none;
83
+ color: #337ab7;
89
84
  border-color: transparent;
90
- background: #eee !important;
91
- }
85
+ background: #fff;
86
+ font-size: 12px;
87
+ padding: 6px;
88
+ margin-right: 6px;
89
+ float: none;
90
+
91
+ &:hover,
92
+ &:focus {
93
+ border: none;
94
+ border-color: transparent;
95
+ background: #eee !important;
96
+ }
92
97
 
93
- &:last-child { margin-right: 0px; }
98
+ &:last-child { margin-right: 0px; }
94
99
 
95
- &.active {
96
- color: #fff;
97
- background: #03428d !important;
98
- }
99
- }
100
+ &.active {
101
+ color: #fff;
102
+ background: #03428d !important;
103
+ }
100
104
 
101
- .dt-buttons.btn-group > a {
102
- margin-right: 0;
105
+ .caret {
106
+ margin-bottom: 4px;
107
+ }
108
+ }
103
109
  }
104
110
 
105
111
  // Show / hide columns button
@@ -36,6 +36,21 @@ module EffectiveDatatablesHelper
36
36
  end.to_json()
37
37
  end
38
38
 
39
+ def datatable_bulk_actions(datatable)
40
+ bulk_actions_column = datatable.table_columns.find { |_, options| options[:bulk_actions_column] }.try(:second)
41
+ return false unless bulk_actions_column
42
+
43
+ # This sets content_for(:effective_datatables_bulk_actions) as per the 3 bulk_action methods below
44
+ instance_exec(&bulk_actions_column[:dropdown_block]) if bulk_actions_column[:dropdown_block].respond_to?(:call)
45
+
46
+ {
47
+ dropdownHtml: render(
48
+ partial: bulk_actions_column[:dropdown_partial],
49
+ locals: HashWithIndifferentAccess.new(datatable: datatable).merge(bulk_actions_column[:partial_locals])
50
+ )
51
+ }.to_json()
52
+ end
53
+
39
54
  def datatable_header_filter(form, name, value, opts)
40
55
  return render(partial: opts[:header_partial], locals: {form: form, name: (opts[:label] || name), column: opts}) if opts[:header_partial].present?
41
56
 
@@ -81,6 +96,10 @@ module EffectiveDatatablesHelper
81
96
  group_method: opts[:filter][:group_method] || :last,
82
97
  input_html: { name: nil, value: value, autocomplete: 'off', data: {'column-name' => opts[:name], 'column-index' => opts[:index]} },
83
98
  input_js: { placeholder: (opts[:label] || name.titleize) }
99
+ when :bulk_actions_column
100
+ form.input name, label: false, required: false, value: nil,
101
+ as: :boolean,
102
+ input_html: { name: nil, value: nil, autocomplete: 'off', data: {'column-name' => opts[:name], 'column-index' => opts[:index], 'role' => 'bulk-actions-all'} }
84
103
  end
85
104
  end
86
105
 
@@ -97,4 +116,18 @@ module EffectiveDatatablesHelper
97
116
  attributes[:active_admin_path] rescue false
98
117
  end
99
118
 
119
+
120
+ ### Bulk Actions DSL Methods
121
+ def bulk_action(*args)
122
+ content_for(:effective_datatables_bulk_actions) { content_tag(:li, link_to(*args)) }
123
+ end
124
+
125
+ def bulk_action_divider
126
+ content_for(:effective_datatables_bulk_actions) { content_tag(:li, '', class: 'divider', role: 'separator') }
127
+ end
128
+
129
+ def bulk_action_content(&block)
130
+ content_for(:effective_datatables_bulk_actions) { block.call }
131
+ end
132
+
100
133
  end
@@ -24,7 +24,7 @@ module Effective
24
24
  before = ''; after = ''
25
25
 
26
26
  if postgres?
27
- after = " NULLS #{order_direction == 'DESC' ? 'FIRST' : 'LAST' }"
27
+ after = " NULLS LAST"
28
28
  elsif mysql?
29
29
  before = "ISNULL(#{column}), "
30
30
  end
@@ -54,6 +54,24 @@ module Effective
54
54
  table_column(name, opts, proc, &block)
55
55
  end
56
56
 
57
+ def bulk_actions_column(options = {}, proc = nil, &block)
58
+ name = options.fetch(:name, 'bulk_actions')
59
+ resource_method = options.fetch(:resource_method, :to_param)
60
+
61
+ opts = {
62
+ bulk_actions_column: true,
63
+ label: '',
64
+ partial_local: :resource,
65
+ partial: '/effective/datatables/bulk_actions_column',
66
+ partial_locals: { resource_method: resource_method },
67
+ sortable: false,
68
+ dropdown_partial: '/effective/datatables/bulk_actions_dropdown',
69
+ dropdown_block: block
70
+ }.merge(options)
71
+
72
+ table_column(name, opts, proc)
73
+ end
74
+
57
75
  end
58
76
  end
59
77
  end
@@ -68,6 +68,8 @@ module Effective
68
68
  end
69
69
  elsif has_manys.key?(name)
70
70
  :has_many
71
+ elsif cols[name][:bulk_actions_column]
72
+ :bulk_actions_column
71
73
  elsif name.include?('_address') && (collection_class.new rescue nil).respond_to?(:effective_addresses)
72
74
  :effective_address
73
75
  elsif name == 'id' || name.include?('year') || name.include?('_id')
@@ -195,6 +197,8 @@ module Effective
195
197
  {type: :datetime}
196
198
  when :date
197
199
  {type: :date}
200
+ when :bulk_actions_column
201
+ {type: :bulk_actions_column}
198
202
  else
199
203
  {type: :string}
200
204
  end.merge(filter.symbolize_keys)
@@ -63,7 +63,7 @@ module Effective
63
63
  rendered = {}
64
64
  (display_table_columns || table_columns).each do |name, opts|
65
65
  if opts[:partial] && opts[:visible]
66
- locals = {
66
+ locals = HashWithIndifferentAccess.new(
67
67
  datatable: self,
68
68
  table_column: table_columns[name],
69
69
  controller_namespace: view.controller_path.split('/')[0...-1].map { |path| path.downcase.to_sym if path.present? }.compact,
@@ -71,7 +71,8 @@ module Effective
71
71
  edit_action: (opts[:partial_locals] || {})[:edit_action],
72
72
  destroy_action: (opts[:partial_locals] || {})[:destroy_action],
73
73
  unarchive_action: (opts[:partial_locals] || {})[:unarchive_action]
74
- }
74
+ )
75
+
75
76
  locals.merge!(opts[:partial_locals]) if opts[:partial_locals]
76
77
 
77
78
  if active_record_collection?
@@ -119,6 +120,8 @@ module Effective
119
120
  (obj.send(name) rescue nil)
120
121
  elsif opts[:type] == :has_many
121
122
  (obj.send(name).to_a rescue [])
123
+ elsif opts[:type] == :bulk_actions_column
124
+ BLANK
122
125
  elsif opts[:type] == :obfuscated_id
123
126
  (obj.send(:to_param) rescue nil).to_s
124
127
  elsif opts[:type] == :effective_address
@@ -0,0 +1,2 @@
1
+ - id = (resource.public_send(resource_method) rescue resource.object_id)
2
+ = check_box_tag 'bulk_actions_resources[]', id, false, autocomplete: 'off', id: "bulk_actions_resource_#{id}", data: { role: 'bulk-actions-resource' }, onClick: 'event.stopPropagation();'
@@ -0,0 +1,10 @@
1
+ .btn-group.buttons-bulk-actions
2
+ %button.btn.btn-default.dropdown-toggle{'type' => 'button', 'data-toggle' => 'dropdown', 'aria-haspopup' => true, 'aria-expanded' => false, 'disabled' => 'disabled'}
3
+ Bulk Actions
4
+ %span.caret
5
+ %ul.dropdown-menu
6
+ - if content_for?(:effective_datatables_bulk_actions)
7
+ = yield :effective_datatables_bulk_actions
8
+ - else
9
+ %li
10
+ %a{href: '#'} No bulk actions
@@ -4,6 +4,7 @@
4
4
  class: "#{datatable.table_html_class}",
5
5
  data: {
6
6
  'effective-form-inputs' => defined?(EffectiveFormInputs),
7
+ 'bulk-actions' => datatable_bulk_actions(datatable),
7
8
  'columns' => datatable_columns(datatable),
8
9
  'input-js-options' => local_assigns[:input_js_options],
9
10
  'simple' => datatable.simple?.to_s,
@@ -1,3 +1,3 @@
1
1
  module EffectiveDatatables
2
- VERSION = '2.2.11'.freeze
2
+ VERSION = '2.3.0'.freeze
3
3
  end
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: 2.2.11
4
+ version: 2.3.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: 2016-01-29 00:00:00.000000000 Z
11
+ date: 2016-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -120,7 +120,8 @@ files:
120
120
  - app/assets/javascripts/dataTables/responsive/dataTables.responsive.min.js
121
121
  - app/assets/javascripts/dataTables/responsive/responsive.bootstrap.min.js
122
122
  - app/assets/javascripts/effective_datatables.js
123
- - app/assets/javascripts/effective_datatables/initialize.js.coffee.erb
123
+ - app/assets/javascripts/effective_datatables/bulk_actions.js.coffee
124
+ - app/assets/javascripts/effective_datatables/initialize.js.coffee
124
125
  - app/assets/javascripts/vendor/jquery.debounce.min.js
125
126
  - app/assets/javascripts/vendor/jszip.min.js
126
127
  - app/assets/stylesheets/dataTables/buttons/buttons.bootstrap.min.css
@@ -144,6 +145,8 @@ files:
144
145
  - app/models/effective/effective_datatable/options.rb
145
146
  - app/models/effective/effective_datatable/rendering.rb
146
147
  - app/views/effective/datatables/_actions_column.html.haml
148
+ - app/views/effective/datatables/_bulk_actions_column.html.haml
149
+ - app/views/effective/datatables/_bulk_actions_dropdown.html.haml
147
150
  - app/views/effective/datatables/_datatable.html.haml
148
151
  - app/views/effective/datatables/_spacer_template.html
149
152
  - config/routes.rb