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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378 | 1
1
1
1
4
4
4
5
9
9
4
4
9
9
2
2
2
9
1
8
8
8
8
8
8
8
6
4
1
4
4
4
4
4
4
4
4
4
4
4
1
1
4
4
4
4
4
4
4
4
4
4
1
1
1
1
1
1
1
1
| define([
'dojo/_base/declare',
'dojo/dom-construct',
'dojo/has',
'dojo/on',
'../util/misc',
'dojo/i18n!./nls/columnHider'
], function (declare, domConstruct, has, listen, miscUtil, i18n) {
/*
* Column Hider plugin for dgrid
* Originally contributed by TRT 2011-09-28
*
* A dGrid plugin that attaches a menu to a dgrid, along with a way of opening it,
* that will allow you to show and hide columns. A few caveats:
*
* 1. Menu placement is entirely based on CSS definitions.
* 2. If you want columns initially hidden, you must add "hidden: true" to your
* column definition.
* 3. This implementation does NOT support ColumnSet, and has not been tested
* with multi-subrow records.
* 4. Column show/hide is controlled via straight up HTML checkboxes. If you
* are looking for something more fancy, you'll probably need to use this
* definition as a template to write your own plugin.
*
*/
var activeGrid, // references grid for which the menu is currently open
bodyListener; // references pausable event handler for body mousedown
function getColumnIdFromCheckbox(cb, grid) {
// Given one of the checkboxes from the hider menu,
// return the id of the corresponding column.
// (e.g. gridIDhere-hider-menu-check-colIDhere -> colIDhere)
return cb.id.substr(grid.id.length + 18);
}
return declare(null, {
// hiderMenuNode: DOMNode
// The node for the menu to show/hide columns.
hiderMenuNode: null,
// hiderToggleNode: DOMNode
// The node for the toggler to open the menu.
hiderToggleNode: null,
// i18nColumnHider: Object
// This object contains all of the internationalized strings for
// the ColumnHider extension as key/value pairs.
i18nColumnHider: i18n,
// _hiderMenuOpened: Boolean
// Records the current open/closed state of the menu.
_hiderMenuOpened: false,
// _columnHiderRules: Object
// Hash containing handles returned from addCssRule.
_columnHiderRules: null,
// _columnHiderCheckboxes: Object
// Hash containing checkboxes generated for menu items.
_columnHiderCheckboxes: null,
_renderHiderMenuEntries: function () {
// summary:
// Iterates over subRows for the sake of adding items to the
// column hider menu.
var subRows = this.subRows,
first = true,
srLength, cLength, sr, c;
delete this._columnHiderFirstCheckbox;
for (sr = 0, srLength = subRows.length; sr < srLength; sr++) {
for (c = 0, cLength = subRows[sr].length; c < cLength; c++) {
this._renderHiderMenuEntry(subRows[sr][c]);
if (first) {
first = false;
this._columnHiderFirstCheckbox =
this._columnHiderCheckboxes[subRows[sr][c].id];
}
}
}
},
_renderHiderMenuEntry: function (col) {
var id = col.id,
replacedId = miscUtil.escapeCssIdentifier(id, '-'),
div,
checkId,
checkbox,
label;
if (col.hidden) {
// Hide the column (reset first to avoid short-circuiting logic)
col.hidden = false;
this._hideColumn(id);
col.hidden = true;
}
// Allow cols to opt out of the hider (e.g. for selector column).
if (col.unhidable) {
return;
}
// Create the checkbox and label for each column selector.
div = domConstruct.create('div', { className: 'dgrid-hider-menu-row' });
checkId = this.domNode.id + '-hider-menu-check-' + replacedId;
checkbox = this._columnHiderCheckboxes[id] = domConstruct.create('input', {
className: 'dgrid-hider-menu-check hider-menu-check-' + replacedId,
id: checkId,
type: 'checkbox'
}, div);
label = domConstruct.create('label', {
className: 'dgrid-hider-menu-label hider-menu-label-' + replacedId,
'for': checkId
}, div);
label.appendChild(document.createTextNode(col.label || col.field || ''));
this.hiderMenuNode.appendChild(div);
if (!col.hidden) {
// Hidden state is false; checkbox should be initially checked.
// (Need to do this after adding to DOM to avoid IE6 clobbering it.)
checkbox.checked = true;
}
},
renderHeader: function () {
var grid = this,
hiderMenuNode = this.hiderMenuNode,
hiderToggleNode = this.hiderToggleNode,
id;
function stopPropagation(event) {
event.stopPropagation();
}
this.inherited(arguments);
Eif (!hiderMenuNode) {
// First run
// Assume that if this plugin is used, then columns are hidable.
// Create the toggle node.
hiderToggleNode = this.hiderToggleNode = domConstruct.create('button', {
'aria-label': this.i18nColumnHider.popupTriggerLabel,
className: 'ui-icon dgrid-hider-toggle',
type: 'button'
}, this.domNode);
this._listeners.push(listen(hiderToggleNode, 'click', function (e) {
grid._toggleColumnHiderMenu(e);
}));
// Create the column list, with checkboxes.
hiderMenuNode = this.hiderMenuNode = domConstruct.create('div', {
'aria-label': this.i18nColumnHider.popupLabel,
className: 'dgrid-hider-menu',
id: this.id + '-hider-menu',
role: 'dialog'
});
this._listeners.push(listen(hiderMenuNode, 'keyup', function (e) {
var charOrCode = e.charCode || e.keyCode;
if (charOrCode === /*ESCAPE*/ 27) {
grid._toggleColumnHiderMenu(e);
hiderToggleNode.focus();
}
}));
// Make sure our menu is initially hidden, then attach to the document.
hiderMenuNode.style.display = 'none';
this.domNode.appendChild(hiderMenuNode);
// Hook up delegated listener for modifications to checkboxes.
this._listeners.push(listen(hiderMenuNode,
'.dgrid-hider-menu-check:' + (has('ie') < 9 ? 'click' : 'change'),
function (e) {
grid._updateColumnHiddenState(
getColumnIdFromCheckbox(e.target, grid), !e.target.checked);
}
));
// Stop click events from propagating from menu or trigger nodes,
// so that we can simply track body clicks for hide without
// having to drill-up to check.
this._listeners.push(
listen(hiderMenuNode, 'mousedown', stopPropagation),
listen(hiderToggleNode, 'mousedown', stopPropagation)
);
// Hook up top-level mousedown listener if it hasn't been yet.
if (!bodyListener) {
bodyListener = listen.pausable(document, 'mousedown', function (e) {
// If an event reaches this listener, the menu is open,
// but a click occurred outside, so close the dropdown.
activeGrid && activeGrid._toggleColumnHiderMenu(e);
});
bodyListener.pause(); // pause initially; will resume when menu opens
}
}
else { // subsequent run
// Remove active rules, and clear out the menu (to be repopulated).
for (id in this._columnHiderRules) {
this._columnHiderRules[id].remove();
}
hiderMenuNode.innerHTML = '';
}
this._columnHiderCheckboxes = {};
this._columnHiderRules = {};
// Populate menu with checkboxes/labels based on current columns.
this._renderHiderMenuEntries();
},
destroy: function () {
this.inherited(arguments);
// Remove any remaining rules applied to hidden columns.
for (var id in this._columnHiderRules) {
this._columnHiderRules[id].remove();
}
},
left: function (cell, steps) {
return this.right(cell, -steps);
},
right: function (cell, steps) {
if (!cell.element) {
cell = this.cell(cell);
}
var nextCell = this.inherited(arguments),
prevCell = cell;
// Skip over hidden cells
while (nextCell.column.hidden) {
nextCell = this.inherited(arguments, [nextCell, steps > 0 ? 1 : -1]);
if (prevCell.element === nextCell.element) {
// No further visible cell found - return original
return cell;
}
prevCell = nextCell;
}
return nextCell;
},
isColumnHidden: function (id) {
// summary:
// Convenience method to determine current hidden state of a column
return !!this._columnHiderRules[id];
},
_toggleColumnHiderMenu: function () {
var hidden = this._hiderMenuOpened, // reflects hidden state after toggle
hiderMenuNode = this.hiderMenuNode,
domNode = this.domNode,
firstCheckbox;
// Show or hide the hider menu
hiderMenuNode.style.display = (hidden ? 'none' : '');
// Adjust height of menu
if (hidden) {
// Clear the set size
hiderMenuNode.style.height = '';
}
else {
// Adjust height of the menu if necessary
// Why 12? Based on menu default paddings and border, we need
// to adjust to be 12 pixels shorter. Given the infrequency of
// this style changing, we're assuming it will remain this
// static value of 12 for now, to avoid pulling in any sort of
// computed styles.
if (hiderMenuNode.offsetHeight > domNode.offsetHeight - 12) {
hiderMenuNode.style.height = (domNode.offsetHeight - 12) + 'px';
}
// focus on the first checkbox
(firstCheckbox = this._columnHiderFirstCheckbox) && firstCheckbox.focus();
}
// Pause or resume the listener for clicks outside the menu
bodyListener[hidden ? 'pause' : 'resume']();
// Update activeGrid appropriately
activeGrid = hidden ? null : this;
// Toggle the instance property
this._hiderMenuOpened = !hidden;
},
_hideColumn: function (id) {
// summary:
// Hides the column indicated by the given id.
// Use miscUtil function directly, since we clean these up ourselves anyway
var grid = this,
selectorPrefix = '#' + miscUtil.escapeCssIdentifier(this.domNode.id) + ' .dgrid-column-',
tableRule; // used in IE8 code path
Iif (this._columnHiderRules[id]) {
return;
}
this._columnHiderRules[id] =
miscUtil.addCssRule(selectorPrefix + miscUtil.escapeCssIdentifier(id, '-'),
'display: none;');
Iif (has('ie') === 8 || has('ie') === 10) {
// Work around IE8 display issue and IE10 issue where
// header/body cells get out of sync when ColumnResizer is also used
tableRule = miscUtil.addCssRule('.dgrid-row-table', 'display: inline-table;');
window.setTimeout(function () {
tableRule.remove();
grid.resize();
}, 0);
}
},
_showColumn: function (id) {
// summary:
// Shows the column indicated by the given id
// (by removing the rule responsible for hiding it).
if (this._columnHiderRules[id]) {
this._columnHiderRules[id].remove();
delete this._columnHiderRules[id];
}
},
_updateColumnHiddenState: function (id, hidden) {
// summary:
// Performs internal work for toggleColumnHiddenState; see the public
// method for more information.
this[hidden ? '_hideColumn' : '_showColumn'](id);
// Update hidden state in actual column definition,
// in case columns are re-rendered.
this.columns[id].hidden = hidden;
// Emit event to notify of column state change.
listen.emit(this.domNode, 'dgrid-columnstatechange', {
grid: this,
column: this.columns[id],
hidden: hidden,
bubbles: true
});
// Adjust the size of the header.
this.resize();
},
toggleColumnHiddenState: function (id, hidden) {
// summary:
// Shows or hides the column with the given id.
// id: String
// ID of column to show/hide.
// hide: Boolean?
// If specified, explicitly sets the hidden state of the specified
// column. If unspecified, toggles the column from the current state.
Eif (typeof hidden === 'undefined') {
hidden = !this._columnHiderRules[id];
}
this._updateColumnHiddenState(id, hidden);
// Since this can be called directly, re-sync the appropriate checkbox.
Iif (this._columnHiderCheckboxes[id]) {
this._columnHiderCheckboxes[id].checked = !hidden;
}
}
});
});
|