diff --git a/core/misc/tableheader.js b/core/misc/tableheader.js
index 364cad2..c5cb1ac 100644
--- a/core/misc/tableheader.js
+++ b/core/misc/tableheader.js
@@ -174,8 +174,9 @@ $.extend(TableHeader.prototype, {
* Create the duplicate header.
*/
createSticky: function () {
- // Clone the table header so it inherits original jQuery properties.
- var $stickyHeader = this.$originalHeader.clone(true);
+ // Deep clone the table header so it and its children inherit their original
+ // jQuery properties.
+ var $stickyHeader = this.$originalHeader.clone(true, true);
// Hide the table to avoid a flash of the header clone upon page load.
this.$stickyTable = $('
')
.css({
@@ -190,6 +191,9 @@ $.extend(TableHeader.prototype, {
// Initialize all computations.
this.recalculateSticky();
+
+ // Let other scripts know a sticky table header was created.
+ this.$stickyTable.trigger('stickyTableCreated');
},
/**
diff --git a/core/misc/tableselect.js b/core/misc/tableselect.js
index 36af864..af46565 100644
--- a/core/misc/tableselect.js
+++ b/core/misc/tableselect.js
@@ -1,93 +1,191 @@
-(function ($) {
+(function ($, Drupal) {
"use strict";
-Drupal.behaviors.tableSelect = {
- attach: function (context, settings) {
- // Select the inner-most table in case of nested tables.
- $(context).find('th.select-all').closest('table').once('table-select', Drupal.tableSelect);
- }
-};
+Drupal.behaviors.TableSelect = {
+ attach: function (context) {
+ var $selectAllHeaders = $(context).find('th.select-all');
+ tableSelectInitHandler($selectAllHeaders);
+ },
-Drupal.tableSelect = function () {
- // Do not add a "Select all" checkbox if there are no rows with checkboxes in the table
- if ($(this).find('td input:checkbox').length === 0) {
- return;
+ detach: function (context) {
+ // @todo: Remove event handlers and 'select all' checkboxes.
}
+};
- // Keep track of the table, which checkbox is checked and alias the settings.
- var table = this, checkboxes, lastChecked;
- var $table = $(table);
- var strings = { 'selectAll': Drupal.t('Select all rows in this table'), 'selectNone': Drupal.t('Deselect all rows in this table') };
- var updateSelectAll = function (state) {
- // Update table's select-all checkbox (and sticky header's if available).
- $table.prev('table.sticky-header').andSelf().find('th.select-all input:checkbox').each(function() {
- $(this).attr('title', state ? strings.selectNone : strings.selectAll);
- this.checked = state;
- });
- };
-
- // Find all
with class select-all, and insert the check all checkbox.
- $table.find('th.select-all').prepend($('').attr('title', strings.selectAll)).click(function (event) {
- if ($(event.target).is('input:checkbox')) {
- // Loop through all checkboxes and set their state to the select all checkbox' state.
- checkboxes.each(function () {
- this.checked = event.target.checked;
- // Either add or remove the selected class based on the state of the check all checkbox.
- $(this).closest('tr').toggleClass('selected', this.checked);
+function tableSelectInitHandler($selectAllHeaders) {
+ $selectAllHeaders.each(function() {
+ var $header = $(this);
+ var $table = $header.closest('table');
+ // Do nothing if there are no rows with checkboxes in the table.
+ if ($table.find('td input:checkbox').length !== 0) {
+ $table.once('table-select', function() {
+ TableSelect.tables.push(new TableSelect($table, $header));
});
- // Update the title and the state of the check all box.
- updateSelectAll(event.target.checked);
}
});
+}
- // For each of the checkboxes within the table that are not disabled.
- checkboxes = $table.find('td input:checkbox:enabled').click(function (e) {
- // Either add or remove the selected class based on the state of the check all checkbox.
- $(this).closest('tr').toggleClass('selected', this.checked);
+/**
+ * Constructor for the TableSelect object.
+ *
+ * TableSelect will add a 'select all' checkbox to header of the current table.
+ *
+ * @param $table
+ * jQuery object for the parent table to add a 'select all' checkbox to.
+ * @param $header
+ * jQuery object for the header to add a 'select all' checkbox to.
+ *
+ * @constructor
+ */
+function TableSelect($table, $header) {
+ // Cached jQuery selectors.
+ this.$table = $table;
+ this.$header = $header;
+ this.$selectAll = null;
- // If this is a shift click, we need to highlight everything in the range.
- // Also make sure that we are actually checking checkboxes over a range and
- // that a checkbox has been checked or unchecked before.
- if (e.shiftKey && lastChecked && lastChecked !== e.target) {
- // We use the checkbox's parent TR to do our range searching.
- Drupal.tableSelectRange($(e.target).closest('tr')[0], $(lastChecked).closest('tr')[0], e.target.checked);
+ // DOM collections.
+ this.checkboxes = [];
+ this.rows = [];
+
+ // Keep track of checkbox properties.
+ this.checkboxSum = 0;
+ this.checkboxMaxSum = 0;
+ this.lastState = [];
+ this.lastIndex = null;
+
+ // Create 'select all' checkbox and behaviour.
+ this.createSelectAll();
+}
+
+/**
+ * Store the state of TableSelect.
+ */
+$.extend(TableSelect, {
+ /**
+ * This will store the state of all processed tables.
+ *
+ * @type {Array}
+ */
+ tables: []
+});
+
+/**
+ * Extend TableSelect prototype.
+ */
+$.extend(TableSelect.prototype, {
+ titleSelectAll: {
+ all: Drupal.t('Select all rows in this table'),
+ none: Drupal.t('Deselect all rows in this table')
+ },
+
+ createSelectAll: function () {
+ // Store the checkboxes, initial state and their parent rows.
+ var $checkboxes = this.$table.find('td input:checkbox');
+ for (var i = 0, il = $checkboxes.length; i < il; i++) {
+ // Checkbox DOM node.
+ this.checkboxes[i] = $checkboxes[i];
+ // Checkbox initial state.
+ if (!$checkboxes[i].disabled) {
+ this.checkboxSum = $checkboxes[i].checked ? this.checkboxSum+1 : this.checkboxSum;
+ this.checkboxMaxSum++;
+ }
+ this.lastState[i] = $checkboxes[i].checked;
+ // Parent row.
+ var $row = $($checkboxes[i]).closest('tr');
+ this.rows.push($row[0]);
}
+ // Create the 'select all' checkbox.
+ var $selectAll = $('').attr('title', this.titleSelectAll.all);
+ $selectAll.on('click.TableSelect.checkboxAll', {TableSelect: this}, this.toggleSelectAll);
+ this.$selectAll = $selectAll.prependTo(this.$header);
+ // Listen for the creation of sticky tables. Do this on the direct parent of
+ // the table, to minimize the actual execution of the following function.
+ this.$table.parent().on('stickyTableCreated', {TableSelect: this}, this.adaptToSticky);
+ // Keep track of the checkboxes state.
+ this.$table.on('click.TableSelect.toggleCheckbox', 'td input:checkbox', {TableSelect: this}, this.toggleCheckbox);
+ // Let other scripts know a 'select all' checkbox was created.
+ this.$table.trigger('TableSelectCreated');
+ },
- // If all checkboxes are checked, make sure the select-all one is checked too, otherwise keep unchecked.
- updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length));
+ adaptToSticky: function (e) {
+ var self = e.data.TableSelect;
+ var $stickyTable = self.$table.prev('table.sticky-header');
+ // If there is a sticky table header attached to the current table, add the
+ // sticky table 'select all' checkbox to the cached 'select all' selector.
+ if ($stickyTable.length === 1) {
+ var $stickyCheckbox = $stickyTable.find('th.select-all input:checkbox');
+ self.$selectAll = $([self.$selectAll[0], $stickyCheckbox[0]]);
+ }
+ },
- // Keep track of the last checked checkbox.
- lastChecked = e.target;
- });
-};
+ toggleSelectAll: function (e) {
+ var self = e.data.TableSelect;
+ var state = $(e.target).prop('checked');
+ // Update the 'select all' checkbox.
+ self.updateSelectAll(state);
+ // Toggle all checkboxes.
+ self.toggleCheckboxes(0, self.checkboxes.length-1, state);
+ },
-Drupal.tableSelectRange = function (from, to, state) {
- // We determine the looping mode based on the the order of from and to.
- var mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling';
+ updateSelectAll: function (state) {
+ // Set the title and 'checked' state explicitly in the event there are
+ // multiple checkboxes controlling the same table. Otherwise these
+ // checkboxes would not be in sync.
+ var title = state ? this.titleSelectAll.none : this.titleSelectAll.all;
+ this.$selectAll.attr('title', title).prop('checked', state);
+ },
- // Traverse through the sibling nodes.
- for (var i = from[mode], $i; i; i = i[mode]) {
- // Make sure that we're only dealing with elements.
- if (i.nodeType !== 1) {
- continue;
+ toggleCheckbox: function (e) {
+ var self = e.data.TableSelect;
+ var currentCheckbox = e.target;
+ var currentIndex = $(self.checkboxes).index(currentCheckbox);
+ // If this is a shift click, we need to highlight everything in the range.
+ // Also make sure that we are actually checking checkboxes over a range and
+ // that a checkbox has been checked or unchecked before.
+ var from, to;
+ if (e.shiftKey && self.lastIndex && self.lastIndex !== currentIndex) {
+ from = self.lastIndex < currentIndex ? self.lastIndex : currentIndex;
+ to = currentIndex > self.lastIndex ? currentIndex : self.lastIndex;
}
- $i = $(i);
- // Either add or remove the selected class based on the state of the target checkbox.
- $i.toggleClass('selected', state);
- $i.find('input:checkbox').attr('checked', state);
-
- if (to.nodeType) {
- // If we are at the end of the range, stop.
- if (i === to) {
- break;
- }
+ else {
+ from = currentIndex;
+ to = currentIndex;
}
- // A faster alternative to doing $(i).filter(to).length.
- else if ($.filter(to, [i]).r.length) {
- break;
+ // Update the checkboxes and parent rows.
+ self.toggleCheckboxes(from, to, currentCheckbox.checked);
+ // Update the most recent toggled checkbox.
+ self.lastIndex = currentIndex;
+
+ // If all checkboxes are checked, make sure the select-all one is checked
+ // too, otherwise keep unchecked.
+ var state = self.checkboxSum === self.checkboxMaxSum ? true : false;
+ self.updateSelectAll(state);
+ },
+
+ toggleCheckboxes: function (from, to, state) {
+ var checkboxes = this.checkboxes;
+ var rows = this.rows;
+ // Gather all the non-disabled checkboxes that need to be toggled.
+ var applyCheckboxes = [];
+ var applyRows = [];
+ for (var i = from, il = to; i <= il; i++) {
+ if (!checkboxes[i].disabled) {
+ applyCheckboxes.push(checkboxes[i]);
+ applyRows.push(rows[i]);
+ if (this.lastState[i] !== state) {
+ this.checkboxSum = state ? this.checkboxSum+1 : this.checkboxSum-1;
+ }
+ this.lastState[i] = state;
+ }
}
+ // Toggle both the 'checked' property and the 'selected' class.
+ $(applyCheckboxes).prop('checked', state);
+ $(applyRows).toggleClass('selected', state);
}
-};
+});
+
+// Expose constructor in the public space.
+Drupal.TableSelect = TableSelect;
-})(jQuery);
+}(jQuery, Drupal));