-
Notifications
You must be signed in to change notification settings - Fork 296
/
Selector.js
230 lines (204 loc) · 7.5 KB
/
Selector.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
define([
'dojo/_base/declare',
'dojo/_base/lang',
'dojo/_base/sniff',
'dojo/dom-construct',
'dojo/dom-class',
'./Selection'
], function (declare, lang, has, domConstruct, domClass, Selection) {
return declare(Selection, {
// summary:
// Adds an input field (checkbox or radio) to a column that when checked, selects the row
// that contains the input field. To enable, add a "selector" property to a column definition.
//
// description:
// The selector property should contain "checkbox", "radio", or be a function that renders the input.
// If set to "radio", the input field will be a radio button and only one input in the column will be
// checked. If the value of selector is a function, then the function signature is
// renderSelectorInput(column, value, cell, object) where:
// * column - the column definition
// * value - the cell's value
// * cell - the cell's DOM node
// * object - the row's data object
// The custom renderSelectorInput function must return an input field.
postCreate: function () {
this.inherited(arguments);
// Register one listener at the top level that receives events delegated
var handleSelectorClick = lang.hitch(this, '_handleSelectorClick');
// Some mobile browsers do not like the checkbox element to be modified when responding to a "click"
// event. Responding to the "change" event works better.
this.on('.dgrid-selector:change', handleSelectorClick);
this.on('.dgrid-selector:click,.dgrid-selector:keydown', function (event) {
if (event.target.nodeName !== 'INPUT') {
handleSelectorClick(event);
}
});
// Register listeners to the select and deselect events to change the input checked value
this.on('dgrid-select', lang.hitch(this, '_changeSelectorInput', true));
this.on('dgrid-deselect', lang.hitch(this, '_changeSelectorInput', false));
},
_defaultRenderSelectorInput: function (column, selected, cell, object) {
var grid = column.grid;
domClass.add(cell, 'dgrid-selector');
return (cell.input = domConstruct.create('input', {
'aria-checked': selected,
checked: selected,
disabled: !grid.allowSelect(grid.row(object)),
tabIndex: isNaN(column.tabIndex) ? -1 : column.tabIndex,
type: column.selector
}, cell));
},
_configureSelectorColumn: function (column) {
var self = this;
var selector = column.selector;
this._selectorColumns.push(column);
this._selectorSingleRow = this._selectorSingleRow || column.selector === 'radio';
var renderSelectorInput = typeof selector === 'function' ?
selector : this._defaultRenderSelectorInput;
column.sortable = false;
column.renderCell = function (object, value, cell) {
var row = object && self.row(object);
value = row && self.selection[row.id];
renderSelectorInput(column, !!value, cell, object);
};
column.renderHeaderCell = function (th) {
var label = 'label' in column ? column.label : column.field || '';
if (column.selector === 'radio' || !self.allowSelectAll) {
th.appendChild(document.createTextNode(label));
}
else {
column._selectorHeaderCheckbox = renderSelectorInput(column, false, th, {});
self._hasSelectorHeaderCheckbox = true;
}
};
},
_handleSelectorClick: function (event) {
// Avoid double-triggering code below due to space key on input automatically triggering click (#731)
if (event.target.nodeName === 'INPUT' && event.type === 'keydown' && event.keyCode === 32) {
return;
}
var cell = this.cell(event);
var row = cell.row;
// We would really only care about click, since other input sources like spacebar
// trigger a click, but the click event doesn't provide access to the shift key in firefox, so
// listen for keydown as well to get an event in firefox that we can properly retrieve
// the shiftKey property
if (event.type === 'change' || event.type === 'click' || event.keyCode === 32 ||
(!has('opera') && event.keyCode === 13) || event.keyCode === 0) {
this._selectionTriggerEvent = event;
if (row) {
if (this.allowSelect(row)) {
var lastRow = this._lastSelected && this.row(this._lastSelected);
if (this._selectorSingleRow) {
if (!lastRow || lastRow.id !== row.id) {
this.clearSelection();
this.select(row, null, true);
this._lastSelected = row.element;
}
}
else {
if (row) {
if (event.shiftKey) {
// Make sure the last input always ends up checked for shift key
this._changeSelectorInput(true, {rows: [row]});
}
else {
// No shift key, so no range selection
lastRow = null;
}
lastRow = event.shiftKey ? lastRow : null;
this.select(lastRow || row, row, lastRow ? undefined : null);
this._lastSelected = row.element;
}
}
}
}
else {
// No row resolved; must be the select-all checkbox.
this[this.allSelected ? 'clearSelection' : 'selectAll']();
}
this._selectionTriggerEvent = null;
}
},
_changeSelectorInput: function (value, event) {
if (this._selectorColumns.length) {
this._updateRowSelectors(value, event);
}
if (this._hasSelectorHeaderCheckbox) {
this._updateHeaderCheckboxes();
}
},
_updateRowSelectors: function (value, event) {
var rows = event.rows;
var lenRows = rows.length;
var lenCols = this._selectorColumns.length;
for (var iRows = 0; iRows < lenRows; iRows++) {
for (var iCols = 0; iCols < lenCols; iCols++) {
var column = this._selectorColumns[iCols];
var element = this.cell(rows[iRows], column.id).element;
if (!element) {
// Skip if row has been entirely removed
continue;
}
element = (element.contents || element).input;
if (element && !element.disabled) {
// Only change the value if it is not disabled
element.checked = value;
element.setAttribute('aria-checked', value);
}
}
}
},
_updateHeaderCheckboxes: function () {
/* jshint eqeqeq: false */
var lenCols = this._selectorColumns.length;
for (var iCols = 0; iCols < lenCols; iCols++) {
var column = this._selectorColumns[iCols];
var state = 'false';
var selection;
var mixed;
var selectorHeaderCheckbox = column._selectorHeaderCheckbox;
if (selectorHeaderCheckbox) {
selection = this.selection;
mixed = false;
// See if the header checkbox needs to be indeterminate
for (var i in selection) {
// If there is anything in the selection, than it is indeterminate
// (Intentionally coerce since selection[i] can be undefined)
if (selection[i] != this.allSelected) {
mixed = true;
break;
}
}
selectorHeaderCheckbox.indeterminate = mixed;
selectorHeaderCheckbox.checked = this.allSelected;
if (mixed) {
state = 'mixed';
}
else if (this.allSelected) {
state = 'true';
}
selectorHeaderCheckbox.setAttribute('aria-checked', state);
}
}
},
configStructure: function () {
this.inherited(arguments);
var columns = this.columns;
this._selectorColumns = [];
this._hasSelectorHeaderCheckbox = this._selectorSingleRow = false;
for (var k in columns) {
if (columns[k].selector) {
this._configureSelectorColumn(columns[k]);
}
}
},
_handleSelect: function (event) {
// Ignore the default select handler for events that originate from the selector column
var column = this.cell(event).column;
if (!column || !column.selector) {
this.inherited(arguments);
}
}
});
});