Chris@0
|
1 /**
|
Chris@0
|
2 * @file
|
Chris@0
|
3 * Table select functionality.
|
Chris@0
|
4 */
|
Chris@0
|
5
|
Chris@0
|
6 (function ($, Drupal) {
|
Chris@0
|
7 /**
|
Chris@0
|
8 * Initialize tableSelects.
|
Chris@0
|
9 *
|
Chris@0
|
10 * @type {Drupal~behavior}
|
Chris@0
|
11 *
|
Chris@0
|
12 * @prop {Drupal~behaviorAttach} attach
|
Chris@0
|
13 * Attaches tableSelect functionality.
|
Chris@0
|
14 */
|
Chris@0
|
15 Drupal.behaviors.tableSelect = {
|
Chris@0
|
16 attach(context, settings) {
|
Chris@0
|
17 // Select the inner-most table in case of nested tables.
|
Chris@0
|
18 $(context)
|
Chris@0
|
19 .find('th.select-all')
|
Chris@0
|
20 .closest('table')
|
Chris@0
|
21 .once('table-select')
|
Chris@0
|
22 .each(Drupal.tableSelect);
|
Chris@0
|
23 },
|
Chris@0
|
24 };
|
Chris@0
|
25
|
Chris@0
|
26 /**
|
Chris@0
|
27 * Callback used in {@link Drupal.behaviors.tableSelect}.
|
Chris@0
|
28 */
|
Chris@0
|
29 Drupal.tableSelect = function () {
|
Chris@0
|
30 // Do not add a "Select all" checkbox if there are no rows with checkboxes
|
Chris@0
|
31 // in the table.
|
Chris@0
|
32 if ($(this).find('td input[type="checkbox"]').length === 0) {
|
Chris@0
|
33 return;
|
Chris@0
|
34 }
|
Chris@0
|
35
|
Chris@0
|
36 // Keep track of the table, which checkbox is checked and alias the
|
Chris@0
|
37 // settings.
|
Chris@0
|
38 const table = this;
|
Chris@0
|
39 let checkboxes;
|
Chris@0
|
40 let lastChecked;
|
Chris@0
|
41 const $table = $(table);
|
Chris@0
|
42 const strings = {
|
Chris@0
|
43 selectAll: Drupal.t('Select all rows in this table'),
|
Chris@0
|
44 selectNone: Drupal.t('Deselect all rows in this table'),
|
Chris@0
|
45 };
|
Chris@0
|
46 const updateSelectAll = function (state) {
|
Chris@0
|
47 // Update table's select-all checkbox (and sticky header's if available).
|
Chris@0
|
48 $table.prev('table.sticky-header').addBack().find('th.select-all input[type="checkbox"]').each(function () {
|
Chris@0
|
49 const $checkbox = $(this);
|
Chris@0
|
50 const stateChanged = $checkbox.prop('checked') !== state;
|
Chris@0
|
51
|
Chris@0
|
52 $checkbox.attr('title', state ? strings.selectNone : strings.selectAll);
|
Chris@0
|
53
|
Chris@0
|
54 /**
|
Chris@0
|
55 * @checkbox {HTMLElement}
|
Chris@0
|
56 */
|
Chris@0
|
57 if (stateChanged) {
|
Chris@0
|
58 $checkbox.prop('checked', state).trigger('change');
|
Chris@0
|
59 }
|
Chris@0
|
60 });
|
Chris@0
|
61 };
|
Chris@0
|
62
|
Chris@0
|
63 // Find all <th> with class select-all, and insert the check all checkbox.
|
Chris@0
|
64 $table.find('th.select-all').prepend($('<input type="checkbox" class="form-checkbox" />').attr('title', strings.selectAll)).on('click', (event) => {
|
Chris@0
|
65 if ($(event.target).is('input[type="checkbox"]')) {
|
Chris@0
|
66 // Loop through all checkboxes and set their state to the select all
|
Chris@0
|
67 // checkbox' state.
|
Chris@0
|
68 checkboxes.each(function () {
|
Chris@0
|
69 const $checkbox = $(this);
|
Chris@0
|
70 const stateChanged = $checkbox.prop('checked') !== event.target.checked;
|
Chris@0
|
71
|
Chris@0
|
72 /**
|
Chris@0
|
73 * @checkbox {HTMLElement}
|
Chris@0
|
74 */
|
Chris@0
|
75 if (stateChanged) {
|
Chris@0
|
76 $checkbox.prop('checked', event.target.checked).trigger('change');
|
Chris@0
|
77 }
|
Chris@0
|
78 // Either add or remove the selected class based on the state of the
|
Chris@0
|
79 // check all checkbox.
|
Chris@0
|
80
|
Chris@0
|
81 /**
|
Chris@0
|
82 * @checkbox {HTMLElement}
|
Chris@0
|
83 */
|
Chris@0
|
84 $checkbox.closest('tr').toggleClass('selected', this.checked);
|
Chris@0
|
85 });
|
Chris@0
|
86 // Update the title and the state of the check all box.
|
Chris@0
|
87 updateSelectAll(event.target.checked);
|
Chris@0
|
88 }
|
Chris@0
|
89 });
|
Chris@0
|
90
|
Chris@0
|
91 // For each of the checkboxes within the table that are not disabled.
|
Chris@0
|
92 checkboxes = $table.find('td input[type="checkbox"]:enabled').on('click', function (e) {
|
Chris@0
|
93 // Either add or remove the selected class based on the state of the
|
Chris@0
|
94 // check all checkbox.
|
Chris@0
|
95
|
Chris@0
|
96 /**
|
Chris@0
|
97 * @this {HTMLElement}
|
Chris@0
|
98 */
|
Chris@0
|
99 $(this).closest('tr').toggleClass('selected', this.checked);
|
Chris@0
|
100
|
Chris@0
|
101 // If this is a shift click, we need to highlight everything in the
|
Chris@0
|
102 // range. Also make sure that we are actually checking checkboxes
|
Chris@0
|
103 // over a range and that a checkbox has been checked or unchecked before.
|
Chris@0
|
104 if (e.shiftKey && lastChecked && lastChecked !== e.target) {
|
Chris@0
|
105 // We use the checkbox's parent <tr> to do our range searching.
|
Chris@0
|
106 Drupal.tableSelectRange($(e.target).closest('tr')[0], $(lastChecked).closest('tr')[0], e.target.checked);
|
Chris@0
|
107 }
|
Chris@0
|
108
|
Chris@0
|
109 // If all checkboxes are checked, make sure the select-all one is checked
|
Chris@0
|
110 // too, otherwise keep unchecked.
|
Chris@0
|
111 updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length));
|
Chris@0
|
112
|
Chris@0
|
113 // Keep track of the last checked checkbox.
|
Chris@0
|
114 lastChecked = e.target;
|
Chris@0
|
115 });
|
Chris@0
|
116
|
Chris@0
|
117 // If all checkboxes are checked on page load, make sure the select-all one
|
Chris@0
|
118 // is checked too, otherwise keep unchecked.
|
Chris@0
|
119 updateSelectAll((checkboxes.length === checkboxes.filter(':checked').length));
|
Chris@0
|
120 };
|
Chris@0
|
121
|
Chris@0
|
122 /**
|
Chris@0
|
123 * @param {HTMLElement} from
|
Chris@0
|
124 * The HTML element representing the "from" part of the range.
|
Chris@0
|
125 * @param {HTMLElement} to
|
Chris@0
|
126 * The HTML element representing the "to" part of the range.
|
Chris@0
|
127 * @param {bool} state
|
Chris@0
|
128 * The state to set on the range.
|
Chris@0
|
129 */
|
Chris@0
|
130 Drupal.tableSelectRange = function (from, to, state) {
|
Chris@0
|
131 // We determine the looping mode based on the order of from and to.
|
Chris@0
|
132 const mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling';
|
Chris@0
|
133
|
Chris@0
|
134 // Traverse through the sibling nodes.
|
Chris@0
|
135 for (let i = from[mode]; i; i = i[mode]) {
|
Chris@0
|
136 const $i = $(i);
|
Chris@0
|
137 // Make sure that we're only dealing with elements.
|
Chris@0
|
138 if (i.nodeType !== 1) {
|
Chris@0
|
139 continue;
|
Chris@0
|
140 }
|
Chris@0
|
141 // Either add or remove the selected class based on the state of the
|
Chris@0
|
142 // target checkbox.
|
Chris@0
|
143 $i.toggleClass('selected', state);
|
Chris@0
|
144 $i.find('input[type="checkbox"]').prop('checked', state);
|
Chris@0
|
145
|
Chris@0
|
146 if (to.nodeType) {
|
Chris@0
|
147 // If we are at the end of the range, stop.
|
Chris@0
|
148 if (i === to) {
|
Chris@0
|
149 break;
|
Chris@0
|
150 }
|
Chris@0
|
151 }
|
Chris@0
|
152 // A faster alternative to doing $(i).filter(to).length.
|
Chris@0
|
153 else if ($.filter(to, [i]).r.length) {
|
Chris@0
|
154 break;
|
Chris@0
|
155 }
|
Chris@0
|
156 }
|
Chris@0
|
157 };
|
Chris@0
|
158 }(jQuery, Drupal));
|