Loading js/frontUi.js +96 −74 Original line number Diff line number Diff line Loading @@ -27,15 +27,18 @@ this.view_id = ''; this.display_id = ''; this.$summary = null; this.totalCount = 0; this.ajaxing = false; } /** * Bind event handlers to an element. * * @param {jQuery} $element * @param {string} element_type * @param {int} index */ bindEventHandlers($element, index) { bindEventHandlers($element, element_type, index = 0) { if ($element.length) { var selectionObject = this; $element.on('keypress', function (event) { Loading @@ -43,22 +46,31 @@ if (event.which === 13) { event.preventDefault(); event.stopPropagation(); selectionObject.update(!this.checked, index, $(this).val()); selectionObject.update(this, element_type, index); $(this).trigger('click'); } if (event.which === 32) { selectionObject.update(!this.checked, index, $(this).val()); selectionObject.update(this, element_type, index); } }); $element.on('click', function (event) { // Act only on left button click. if (event.which === 1) { selectionObject.update(this.checked, index, $(this).val()); selectionObject.update(this, element_type, index); } }); } } bindActionSelect() { if (this.$actionSelect.length) { var selectionObject = this; this.$actionSelect.on('change', function (event) { selectionObject.toggleButtonsState(); }); } } bindCheckboxes() { var selectionObject = this; var checkboxes = $('.form-checkbox', this.vbo_form); Loading @@ -69,10 +81,15 @@ toggleButtonsState() { // If no rows are checked, disable any form submit actions. var checkedCheckboxes = $('.form-checkbox:checked', this.vbo_form); var buttons = $('[id^="edit-actions"] input[type="submit"], [id^="edit-actions"] button[type="submit"]', this.vbo_form); var selectedAjaxItems = $('.vbo-info-list-wrapper li', this.vbo_form); var anyItemsSelected = selectedAjaxItems.length || checkedCheckboxes.length; if (this.view_id.length && this.display_id.length) { var anyItemsSelected = this.totalCount; } else { var anyItemsSelected = $('.form-checkbox:checked', this.vbo_form).length; } if (this.$actionSelect.length) { var has_selection = anyItemsSelected && this.$actionSelect.val() !== ''; buttons.prop('disabled', !has_selection); Loading @@ -82,46 +99,49 @@ } } bindActionSelect() { if (this.$actionSelect.length) { var selectionObject = this; this.$actionSelect.on('change', function (event) { selectionObject.toggleButtonsState(); }); } } /** * Perform an AJAX request to update selection. * * @param {bool} state * @param {mixed} index * @param {string} value * @param {object} element * The checkbox element. * @param {string} element_type * Which type of a checkbox is it? * @param {int} index * Index of the checkbox, used for table select all. */ update(state, index, value) { if (typeof value === 'undefined') { value = null; update(element, element_type, index) { if (!this.view_id.length || !this.display_id.length) { this.toggleButtonsState(); return; } if (this.ajaxing) { return; } if (this.view_id.length && this.display_id.length) { // TODO: prevent form submission when ajaxing. var selectionObject = this; var list = {}; var op = ''; if (index === 'selection_method_change') { op = state ? 'method_exclude' : 'method_include'; if (state) { list = this.list[index]; } } else { if (value && value !== 'on') { list[value] = this.list[index][value]; var selectionObject = this; var op = 'update'; if (element_type === 'selection_method_change') { op = element.checked ? 'method_exclude' : 'method_include'; } else { list = this.list[index]; // Build standard list. $('.form-checkbox', this.vbo_form).each(function () { let dom_value = $(this).val(); // All bulk form keys are quite long, it'd be safe to assume // anything above 10 characters to filter out other values. if (dom_value.length < 10) { return; } list[dom_value] = this.checked; }); // If a table select all was used, update the list according to that. if (element_type === 'table_select_all') { this.list[index].forEach(function (bulk_form_key) { list[bulk_form_key] = element.checked; }); } op = state ? 'add' : 'remove'; } var $summary = this.$summary; Loading @@ -134,6 +154,13 @@ submit: { list: list, op: op }, success: function (data) { selectionObject.totalCount = data.count; $selectionInfo.html(data.selection_info); $summary.text(Drupal.formatPlural(data.count, 'Selected 1 item', 'Selected @count items')); selectionObject.toggleButtonsState(); selectionObject.ajaxing = false; } }; Loading @@ -142,17 +169,8 @@ } var ajaxDrupal = Drupal.ajax(ajax_options); ajaxDrupal.original_success = ajaxDrupal.success; ajaxDrupal.success = function (data, status) { $selectionInfo.html(data.selection_info); $summary.text(Drupal.formatPlural(data.count, 'Selected 1 item', 'Selected @count items')); selectionObject.toggleButtonsState(); this.original_success(data, status); }; this.ajaxing = true; ajaxDrupal.execute(); } } }; Loading @@ -171,7 +189,7 @@ // When grouping is enabled, there can be multiple tables. if ($viewsTables.length) { $viewsTables.each(function (index) { tableSelectAll[index] = $vboForm.find('.select-all input').first(); tableSelectAll[index] = $(this).find('.select-all input').first(); }); } Loading @@ -183,6 +201,7 @@ vboSelection.$summary = $multiSelectElement.find('summary').first(); vboSelection.view_id = $multiSelectElement.attr('data-view-id'); vboSelection.display_id = $multiSelectElement.attr('data-display-id'); vboSelection.totalCount = drupalSettings.vbo_selected_count[vboSelection.view_id][vboSelection.display_id]; // Get the list of all checkbox values and add AJAX callback. vboSelection.list = []; Loading @@ -196,23 +215,27 @@ } $contentWrappers.each(function (index) { var $contentWrapper = $(this); vboSelection.list[index] = {}; vboSelection.list[index] = []; $contentWrapper.find('.views-field-views-bulk-operations-bulk-form input[type="checkbox"]').each(function () { var value = $(this).val(); if (value !== 'on') { vboSelection.list[index][value] = value; vboSelection.bindEventHandlers($(this), index); $(this).find('input[type="checkbox"]').each(function () { let value = $(this).val(); if (this.id !== 'edit-select-all' && value !== 'on') { vboSelection.list[index].push(value); vboSelection.bindEventHandlers($(this), 'vbo_checkbox'); } }); // Bind event handlers to select all checkbox. if ($viewsTables.length && tableSelectAll.length) { vboSelection.bindEventHandlers(tableSelectAll[index], index); vboSelection.bindEventHandlers(tableSelectAll[index], 'table_select_all', index); } }); } // If we don't have multiselect and AJAX calls, we need to toggle button // state on click instead of on AJAX success. else { vboSelection.bindCheckboxes(); } // Initialize all selector if the primary select all and // view table elements exist. Loading Loading @@ -247,7 +270,6 @@ vboSelection.bindEventHandlers($primarySelectAll, 'selection_method_change'); } } vboSelection.bindCheckboxes(); vboSelection.bindActionSelect(); vboSelection.toggleButtonsState(); }; Loading src/Controller/ViewsBulkOperationsController.php +20 −32 Original line number Diff line number Diff line Loading @@ -104,42 +104,30 @@ class ViewsBulkOperationsController extends ControllerBase implements ContainerI $parameters = $request->request->all(); // Reverse operation when in exclude mode. if (!empty($tempstore_data['exclude_mode'])) { if ($parameters['op'] === 'add') { $parameters['op'] = 'remove'; if ($parameters['op'] === 'method_include') { unset($tempstore_data['exclude_mode']); $tempstore_data['list'] = []; } elseif ($parameters['op'] === 'remove') { $parameters['op'] = 'add'; elseif ($parameters['op'] === 'method_exclude') { $tempstore_data['exclude_mode'] = TRUE; $tempstore_data['list'] = []; } elseif ($parameters['op'] === 'update') { $exclude_mode = \array_key_exists('exclude_mode', $tempstore_data) && $tempstore_data['exclude_mode'] === TRUE; foreach ($parameters['list'] as $bulkFormKey => $state) { if ($exclude_mode) { $state = $state === 'true' ? 'false' : 'true'; } switch ($parameters['op']) { case 'add': foreach ($parameters['list'] as $bulkFormKey) { if (!isset($tempstore_data['list'][$bulkFormKey])) { $tempstore_data['list'][$bulkFormKey] = $this->getListItem($bulkFormKey); if ($state === 'true') { $list_item = $this->getListItem($bulkFormKey); if ($list_item !== NULL) { $tempstore_data['list'][$bulkFormKey] = $list_item; } } break; case 'remove': foreach ($parameters['list'] as $bulkFormKey) { if (isset($tempstore_data['list'][$bulkFormKey])) { else { unset($tempstore_data['list'][$bulkFormKey]); } } break; case 'method_include': unset($tempstore_data['exclude_mode']); $tempstore_data['list'] = []; break; case 'method_exclude': $tempstore_data['exclude_mode'] = TRUE; $tempstore_data['list'] = []; break; } $this->setTempstoreData($tempstore_data); Loading src/Form/ViewsBulkOperationsFormTrait.php +9 −2 Original line number Diff line number Diff line Loading @@ -178,8 +178,15 @@ trait ViewsBulkOperationsFormTrait { * @return array * Entity list item. */ protected function getListItem($bulkFormKey): array { $item = \json_decode(\base64_decode($bulkFormKey)); protected function getListItem($bulkFormKey): ?array { $decoded = \base64_decode($bulkFormKey); if ($decoded === FALSE) { return NULL; } $item = \json_decode($decoded); if (!\is_array($item)) { return NULL; } return $item; } Loading src/Plugin/views/field/ViewsBulkOperationsBulkForm.php +1 −0 Original line number Diff line number Diff line Loading @@ -800,6 +800,7 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe 'class' => ['vbo-multipage-selector'], ], ]; $form['#attached']['drupalSettings']['vbo_selected_count'][$this->tempStoreData['view_id']][$this->tempStoreData['display_id']] = $count; // Get selection info elements. $form['header'][$this->options['id']]['multipage']['list'] = $this->getMultipageList($this->tempStoreData); Loading Loading
js/frontUi.js +96 −74 Original line number Diff line number Diff line Loading @@ -27,15 +27,18 @@ this.view_id = ''; this.display_id = ''; this.$summary = null; this.totalCount = 0; this.ajaxing = false; } /** * Bind event handlers to an element. * * @param {jQuery} $element * @param {string} element_type * @param {int} index */ bindEventHandlers($element, index) { bindEventHandlers($element, element_type, index = 0) { if ($element.length) { var selectionObject = this; $element.on('keypress', function (event) { Loading @@ -43,22 +46,31 @@ if (event.which === 13) { event.preventDefault(); event.stopPropagation(); selectionObject.update(!this.checked, index, $(this).val()); selectionObject.update(this, element_type, index); $(this).trigger('click'); } if (event.which === 32) { selectionObject.update(!this.checked, index, $(this).val()); selectionObject.update(this, element_type, index); } }); $element.on('click', function (event) { // Act only on left button click. if (event.which === 1) { selectionObject.update(this.checked, index, $(this).val()); selectionObject.update(this, element_type, index); } }); } } bindActionSelect() { if (this.$actionSelect.length) { var selectionObject = this; this.$actionSelect.on('change', function (event) { selectionObject.toggleButtonsState(); }); } } bindCheckboxes() { var selectionObject = this; var checkboxes = $('.form-checkbox', this.vbo_form); Loading @@ -69,10 +81,15 @@ toggleButtonsState() { // If no rows are checked, disable any form submit actions. var checkedCheckboxes = $('.form-checkbox:checked', this.vbo_form); var buttons = $('[id^="edit-actions"] input[type="submit"], [id^="edit-actions"] button[type="submit"]', this.vbo_form); var selectedAjaxItems = $('.vbo-info-list-wrapper li', this.vbo_form); var anyItemsSelected = selectedAjaxItems.length || checkedCheckboxes.length; if (this.view_id.length && this.display_id.length) { var anyItemsSelected = this.totalCount; } else { var anyItemsSelected = $('.form-checkbox:checked', this.vbo_form).length; } if (this.$actionSelect.length) { var has_selection = anyItemsSelected && this.$actionSelect.val() !== ''; buttons.prop('disabled', !has_selection); Loading @@ -82,46 +99,49 @@ } } bindActionSelect() { if (this.$actionSelect.length) { var selectionObject = this; this.$actionSelect.on('change', function (event) { selectionObject.toggleButtonsState(); }); } } /** * Perform an AJAX request to update selection. * * @param {bool} state * @param {mixed} index * @param {string} value * @param {object} element * The checkbox element. * @param {string} element_type * Which type of a checkbox is it? * @param {int} index * Index of the checkbox, used for table select all. */ update(state, index, value) { if (typeof value === 'undefined') { value = null; update(element, element_type, index) { if (!this.view_id.length || !this.display_id.length) { this.toggleButtonsState(); return; } if (this.ajaxing) { return; } if (this.view_id.length && this.display_id.length) { // TODO: prevent form submission when ajaxing. var selectionObject = this; var list = {}; var op = ''; if (index === 'selection_method_change') { op = state ? 'method_exclude' : 'method_include'; if (state) { list = this.list[index]; } } else { if (value && value !== 'on') { list[value] = this.list[index][value]; var selectionObject = this; var op = 'update'; if (element_type === 'selection_method_change') { op = element.checked ? 'method_exclude' : 'method_include'; } else { list = this.list[index]; // Build standard list. $('.form-checkbox', this.vbo_form).each(function () { let dom_value = $(this).val(); // All bulk form keys are quite long, it'd be safe to assume // anything above 10 characters to filter out other values. if (dom_value.length < 10) { return; } list[dom_value] = this.checked; }); // If a table select all was used, update the list according to that. if (element_type === 'table_select_all') { this.list[index].forEach(function (bulk_form_key) { list[bulk_form_key] = element.checked; }); } op = state ? 'add' : 'remove'; } var $summary = this.$summary; Loading @@ -134,6 +154,13 @@ submit: { list: list, op: op }, success: function (data) { selectionObject.totalCount = data.count; $selectionInfo.html(data.selection_info); $summary.text(Drupal.formatPlural(data.count, 'Selected 1 item', 'Selected @count items')); selectionObject.toggleButtonsState(); selectionObject.ajaxing = false; } }; Loading @@ -142,17 +169,8 @@ } var ajaxDrupal = Drupal.ajax(ajax_options); ajaxDrupal.original_success = ajaxDrupal.success; ajaxDrupal.success = function (data, status) { $selectionInfo.html(data.selection_info); $summary.text(Drupal.formatPlural(data.count, 'Selected 1 item', 'Selected @count items')); selectionObject.toggleButtonsState(); this.original_success(data, status); }; this.ajaxing = true; ajaxDrupal.execute(); } } }; Loading @@ -171,7 +189,7 @@ // When grouping is enabled, there can be multiple tables. if ($viewsTables.length) { $viewsTables.each(function (index) { tableSelectAll[index] = $vboForm.find('.select-all input').first(); tableSelectAll[index] = $(this).find('.select-all input').first(); }); } Loading @@ -183,6 +201,7 @@ vboSelection.$summary = $multiSelectElement.find('summary').first(); vboSelection.view_id = $multiSelectElement.attr('data-view-id'); vboSelection.display_id = $multiSelectElement.attr('data-display-id'); vboSelection.totalCount = drupalSettings.vbo_selected_count[vboSelection.view_id][vboSelection.display_id]; // Get the list of all checkbox values and add AJAX callback. vboSelection.list = []; Loading @@ -196,23 +215,27 @@ } $contentWrappers.each(function (index) { var $contentWrapper = $(this); vboSelection.list[index] = {}; vboSelection.list[index] = []; $contentWrapper.find('.views-field-views-bulk-operations-bulk-form input[type="checkbox"]').each(function () { var value = $(this).val(); if (value !== 'on') { vboSelection.list[index][value] = value; vboSelection.bindEventHandlers($(this), index); $(this).find('input[type="checkbox"]').each(function () { let value = $(this).val(); if (this.id !== 'edit-select-all' && value !== 'on') { vboSelection.list[index].push(value); vboSelection.bindEventHandlers($(this), 'vbo_checkbox'); } }); // Bind event handlers to select all checkbox. if ($viewsTables.length && tableSelectAll.length) { vboSelection.bindEventHandlers(tableSelectAll[index], index); vboSelection.bindEventHandlers(tableSelectAll[index], 'table_select_all', index); } }); } // If we don't have multiselect and AJAX calls, we need to toggle button // state on click instead of on AJAX success. else { vboSelection.bindCheckboxes(); } // Initialize all selector if the primary select all and // view table elements exist. Loading Loading @@ -247,7 +270,6 @@ vboSelection.bindEventHandlers($primarySelectAll, 'selection_method_change'); } } vboSelection.bindCheckboxes(); vboSelection.bindActionSelect(); vboSelection.toggleButtonsState(); }; Loading
src/Controller/ViewsBulkOperationsController.php +20 −32 Original line number Diff line number Diff line Loading @@ -104,42 +104,30 @@ class ViewsBulkOperationsController extends ControllerBase implements ContainerI $parameters = $request->request->all(); // Reverse operation when in exclude mode. if (!empty($tempstore_data['exclude_mode'])) { if ($parameters['op'] === 'add') { $parameters['op'] = 'remove'; if ($parameters['op'] === 'method_include') { unset($tempstore_data['exclude_mode']); $tempstore_data['list'] = []; } elseif ($parameters['op'] === 'remove') { $parameters['op'] = 'add'; elseif ($parameters['op'] === 'method_exclude') { $tempstore_data['exclude_mode'] = TRUE; $tempstore_data['list'] = []; } elseif ($parameters['op'] === 'update') { $exclude_mode = \array_key_exists('exclude_mode', $tempstore_data) && $tempstore_data['exclude_mode'] === TRUE; foreach ($parameters['list'] as $bulkFormKey => $state) { if ($exclude_mode) { $state = $state === 'true' ? 'false' : 'true'; } switch ($parameters['op']) { case 'add': foreach ($parameters['list'] as $bulkFormKey) { if (!isset($tempstore_data['list'][$bulkFormKey])) { $tempstore_data['list'][$bulkFormKey] = $this->getListItem($bulkFormKey); if ($state === 'true') { $list_item = $this->getListItem($bulkFormKey); if ($list_item !== NULL) { $tempstore_data['list'][$bulkFormKey] = $list_item; } } break; case 'remove': foreach ($parameters['list'] as $bulkFormKey) { if (isset($tempstore_data['list'][$bulkFormKey])) { else { unset($tempstore_data['list'][$bulkFormKey]); } } break; case 'method_include': unset($tempstore_data['exclude_mode']); $tempstore_data['list'] = []; break; case 'method_exclude': $tempstore_data['exclude_mode'] = TRUE; $tempstore_data['list'] = []; break; } $this->setTempstoreData($tempstore_data); Loading
src/Form/ViewsBulkOperationsFormTrait.php +9 −2 Original line number Diff line number Diff line Loading @@ -178,8 +178,15 @@ trait ViewsBulkOperationsFormTrait { * @return array * Entity list item. */ protected function getListItem($bulkFormKey): array { $item = \json_decode(\base64_decode($bulkFormKey)); protected function getListItem($bulkFormKey): ?array { $decoded = \base64_decode($bulkFormKey); if ($decoded === FALSE) { return NULL; } $item = \json_decode($decoded); if (!\is_array($item)) { return NULL; } return $item; } Loading
src/Plugin/views/field/ViewsBulkOperationsBulkForm.php +1 −0 Original line number Diff line number Diff line Loading @@ -800,6 +800,7 @@ class ViewsBulkOperationsBulkForm extends FieldPluginBase implements CacheableDe 'class' => ['vbo-multipage-selector'], ], ]; $form['#attached']['drupalSettings']['vbo_selected_count'][$this->tempStoreData['view_id']][$this->tempStoreData['display_id']] = $count; // Get selection info elements. $form['header'][$this->options['id']]['multipage']['list'] = $this->getMultipageList($this->tempStoreData); Loading