Commit 92754f07 authored by Marcin Grabias's avatar Marcin Grabias Committed by Marcin Grabias
Browse files

Issue #3019720 by Renrhaf, Graber, jaapjan: Selection not properly updated on...

Issue #3019720 by Renrhaf, Graber, jaapjan: Selection not properly updated on multi-page view when using Ctrl+Shift
parent 098ef29c
Loading
Loading
Loading
Loading
+96 −74
Original line number Diff line number Diff line
@@ -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) {
@@ -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);
@@ -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);
@@ -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;
@@ -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;
        }
      };

@@ -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();

      }
    }
  };

@@ -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();
      });
    }

@@ -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 = [];
@@ -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.
@@ -247,7 +270,6 @@
        vboSelection.bindEventHandlers($primarySelectAll, 'selection_method_change');
      }
    }
    vboSelection.bindCheckboxes();
    vboSelection.bindActionSelect();
    vboSelection.toggleButtonsState();
  };
+20 −32
Original line number Diff line number Diff line
@@ -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);
+9 −2
Original line number Diff line number Diff line
@@ -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;
  }

+1 −0
Original line number Diff line number Diff line
@@ -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);