Chris@0: /** Chris@0: * @file Chris@0: * Table select functionality. Chris@0: */ Chris@0: Chris@17: (function($, Drupal) { Chris@0: /** Chris@0: * Initialize tableSelects. Chris@0: * Chris@0: * @type {Drupal~behavior} Chris@0: * Chris@0: * @prop {Drupal~behaviorAttach} attach Chris@0: * Attaches tableSelect functionality. Chris@0: */ Chris@0: Drupal.behaviors.tableSelect = { Chris@0: attach(context, settings) { Chris@0: // Select the inner-most table in case of nested tables. Chris@14: $(context) Chris@14: .find('th.select-all') Chris@14: .closest('table') Chris@14: .once('table-select') Chris@14: .each(Drupal.tableSelect); Chris@0: }, Chris@0: }; Chris@0: Chris@0: /** Chris@0: * Callback used in {@link Drupal.behaviors.tableSelect}. Chris@0: */ Chris@17: Drupal.tableSelect = function() { Chris@0: // Do not add a "Select all" checkbox if there are no rows with checkboxes Chris@0: // in the table. Chris@0: if ($(this).find('td input[type="checkbox"]').length === 0) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // Keep track of the table, which checkbox is checked and alias the Chris@0: // settings. Chris@0: const table = this; Chris@0: let checkboxes; Chris@0: let lastChecked; Chris@0: const $table = $(table); Chris@0: const strings = { Chris@0: selectAll: Drupal.t('Select all rows in this table'), Chris@0: selectNone: Drupal.t('Deselect all rows in this table'), Chris@0: }; Chris@17: const updateSelectAll = function(state) { Chris@0: // Update table's select-all checkbox (and sticky header's if available). Chris@17: $table Chris@17: .prev('table.sticky-header') Chris@17: .addBack() Chris@17: .find('th.select-all input[type="checkbox"]') Chris@17: .each(function() { Chris@17: const $checkbox = $(this); Chris@17: const stateChanged = $checkbox.prop('checked') !== state; Chris@0: Chris@17: $checkbox.attr( Chris@17: 'title', Chris@17: state ? strings.selectNone : strings.selectAll, Chris@17: ); Chris@0: Chris@0: /** Chris@0: * @checkbox {HTMLElement} Chris@0: */ Chris@0: if (stateChanged) { Chris@17: $checkbox.prop('checked', state).trigger('change'); Chris@0: } Chris@17: }); Chris@17: }; Chris@0: Chris@17: // Find all with class select-all, and insert the check all checkbox. Chris@17: $table Chris@17: .find('th.select-all') Chris@17: .prepend( Chris@17: $('').attr( Chris@17: 'title', Chris@17: strings.selectAll, Chris@17: ), Chris@17: ) Chris@17: .on('click', event => { Chris@17: if ($(event.target).is('input[type="checkbox"]')) { Chris@17: // Loop through all checkboxes and set their state to the select all Chris@17: // checkbox' state. Chris@17: checkboxes.each(function() { Chris@17: const $checkbox = $(this); Chris@17: const stateChanged = Chris@17: $checkbox.prop('checked') !== event.target.checked; Chris@17: Chris@17: /** Chris@17: * @checkbox {HTMLElement} Chris@17: */ Chris@17: if (stateChanged) { Chris@17: $checkbox.prop('checked', event.target.checked).trigger('change'); Chris@17: } Chris@17: // Either add or remove the selected class based on the state of the Chris@17: // check all checkbox. Chris@17: Chris@17: /** Chris@17: * @checkbox {HTMLElement} Chris@17: */ Chris@17: $checkbox.closest('tr').toggleClass('selected', this.checked); Chris@17: }); Chris@17: // Update the title and the state of the check all box. Chris@17: updateSelectAll(event.target.checked); Chris@17: } Chris@17: }); Chris@0: Chris@0: // For each of the checkboxes within the table that are not disabled. Chris@17: checkboxes = $table Chris@17: .find('td input[type="checkbox"]:enabled') Chris@17: .on('click', function(e) { Chris@17: // Either add or remove the selected class based on the state of the Chris@17: // check all checkbox. Chris@0: Chris@17: /** Chris@17: * @this {HTMLElement} Chris@17: */ Chris@17: $(this) Chris@17: .closest('tr') Chris@17: .toggleClass('selected', this.checked); Chris@0: Chris@17: // If this is a shift click, we need to highlight everything in the Chris@17: // range. Also make sure that we are actually checking checkboxes Chris@17: // over a range and that a checkbox has been checked or unchecked before. Chris@17: if (e.shiftKey && lastChecked && lastChecked !== e.target) { Chris@17: // We use the checkbox's parent to do our range searching. Chris@17: Drupal.tableSelectRange( Chris@17: $(e.target).closest('tr')[0], Chris@17: $(lastChecked).closest('tr')[0], Chris@17: e.target.checked, Chris@17: ); Chris@17: } Chris@0: Chris@17: // If all checkboxes are checked, make sure the select-all one is checked Chris@17: // too, otherwise keep unchecked. Chris@17: updateSelectAll( Chris@17: checkboxes.length === checkboxes.filter(':checked').length, Chris@17: ); Chris@0: Chris@17: // Keep track of the last checked checkbox. Chris@17: lastChecked = e.target; Chris@17: }); Chris@0: Chris@0: // If all checkboxes are checked on page load, make sure the select-all one Chris@0: // is checked too, otherwise keep unchecked. Chris@17: updateSelectAll(checkboxes.length === checkboxes.filter(':checked').length); Chris@0: }; Chris@0: Chris@0: /** Chris@0: * @param {HTMLElement} from Chris@0: * The HTML element representing the "from" part of the range. Chris@0: * @param {HTMLElement} to Chris@0: * The HTML element representing the "to" part of the range. Chris@0: * @param {bool} state Chris@0: * The state to set on the range. Chris@0: */ Chris@17: Drupal.tableSelectRange = function(from, to, state) { Chris@0: // We determine the looping mode based on the order of from and to. Chris@17: const mode = Chris@17: from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling'; Chris@0: Chris@0: // Traverse through the sibling nodes. Chris@0: for (let i = from[mode]; i; i = i[mode]) { Chris@14: const $i = $(i); Chris@0: // Make sure that we're only dealing with elements. Chris@0: if (i.nodeType !== 1) { Chris@0: continue; Chris@0: } Chris@0: // Either add or remove the selected class based on the state of the Chris@0: // target checkbox. Chris@0: $i.toggleClass('selected', state); Chris@0: $i.find('input[type="checkbox"]').prop('checked', state); Chris@0: Chris@0: if (to.nodeType) { Chris@0: // If we are at the end of the range, stop. Chris@0: if (i === to) { Chris@0: break; Chris@0: } Chris@0: } Chris@0: // A faster alternative to doing $(i).filter(to).length. Chris@0: else if ($.filter(to, [i]).r.length) { Chris@0: break; Chris@0: } Chris@0: } Chris@0: }; Chris@17: })(jQuery, Drupal);