effective_datatables 2.2.11 → 2.3.0

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