Skip to content

Commit 000865d

Browse files
committed
fix: final fix for ColumnPicker and column hidden property
1 parent 29572a6 commit 000865d

File tree

3 files changed

+398
-4
lines changed

3 files changed

+398
-4
lines changed

controls/slick.columnmenu.js

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
/***
2+
* A control to add a Column Picker (right+click on any column header to reveal the column picker)
3+
* NOTE: this a simplified and updated version of slick.columnpicker.js
4+
*
5+
* USAGE:
6+
*
7+
* Add the slick.columnpicker.(js|css) files and register it with the grid.
8+
*
9+
* Available options, by defining a columnPicker object:
10+
*
11+
* var options = {
12+
* enableCellNavigation: true,
13+
* columnPicker: {
14+
* columnTitle: "Columns", // default to empty string
15+
*
16+
* // the last 2 checkboxes titles
17+
* hideForceFitButton: false, // show/hide checkbox near the end "Force Fit Columns" (default:false)
18+
* hideSyncResizeButton: false, // show/hide checkbox near the end "Synchronous Resize" (default:false)
19+
* forceFitTitle: "Force fit columns", // default to "Force fit columns"
20+
* headerColumnValueExtractor: "Extract the column label" // default to column.name
21+
* syncResizeTitle: "Synchronous resize", // default to "Synchronous resize"
22+
* }
23+
* };
24+
*
25+
* @class Slick.Controls.ColumnPicker
26+
* @constructor
27+
*/
28+
29+
(function (window) {
30+
'use strict';
31+
function SlickColumnPicker(columns, grid, options) {
32+
var _grid = grid;
33+
var _options = options;
34+
var _gridUid = (grid && grid.getUID) ? grid.getUID() : '';
35+
var _columnTitleElm;
36+
var _listElm;
37+
var _menuElm;
38+
var columnCheckboxes;
39+
var onColumnsChanged = new Slick.Event();
40+
var _bindingEventService = new Slick.BindingEventService();
41+
42+
var defaults = {
43+
fadeSpeed: 250,
44+
45+
// the last 2 checkboxes titles
46+
hideForceFitButton: false,
47+
hideSyncResizeButton: false,
48+
forceFitTitle: "Force fit columns",
49+
syncResizeTitle: "Synchronous resize",
50+
headerColumnValueExtractor:
51+
function (columnDef) {
52+
return columnDef.name;
53+
}
54+
};
55+
56+
function init(grid) {
57+
grid.onHeaderContextMenu.subscribe(handleHeaderContextMenu);
58+
grid.onColumnsReordered.subscribe(updateColumnOrder);
59+
_options = Slick.Utils.extend({}, defaults, options);
60+
61+
_menuElm = document.createElement('div');
62+
_menuElm.className = `slick-columnpicker ${_gridUid}`;
63+
_menuElm.style.display = 'none';
64+
document.body.appendChild(_menuElm);
65+
66+
const buttonElm = document.createElement('button');
67+
buttonElm.type = 'button';
68+
buttonElm.className = 'close';
69+
buttonElm.dataset.dismiss = 'slick-columnpicker';
70+
buttonElm.ariaLabel = 'Close';
71+
72+
const spanCloseElm = document.createElement('span');
73+
spanCloseElm.className = 'close';
74+
spanCloseElm.ariaHidden = 'true';
75+
spanCloseElm.innerHTML = '×';
76+
buttonElm.appendChild(spanCloseElm);
77+
_menuElm.appendChild(buttonElm);
78+
79+
// user could pass a title on top of the columns list
80+
if (_options.columnPickerTitle || (_options.columnPicker && _options.columnPicker.columnTitle)) {
81+
var columnTitle = _options.columnPickerTitle || _options.columnPicker.columnTitle;
82+
_columnTitleElm = document.createElement('div');
83+
_columnTitleElm.className = 'slick-gridmenu-custom';
84+
_columnTitleElm.textContent = columnTitle;
85+
_menuElm.appendChild(_columnTitleElm);
86+
}
87+
88+
_bindingEventService.bind(_menuElm, 'click', updateColumn);
89+
90+
_listElm = document.createElement('span');
91+
_listElm.className = 'slick-columnpicker-list';
92+
93+
// Hide the menu on outside click.
94+
_bindingEventService.bind(document.body, 'mousedown', handleBodyMouseDown);
95+
96+
// destroy the picker if user leaves the page
97+
_bindingEventService.bind(document.body, 'beforeunload', destroy);
98+
}
99+
100+
function destroy() {
101+
_grid.onHeaderContextMenu.unsubscribe(handleHeaderContextMenu);
102+
_grid.onColumnsReordered.unsubscribe(updateColumnOrder);
103+
_bindingEventService.unbindAll();
104+
105+
if (_listElm) {
106+
_listElm.remove();
107+
}
108+
if (_menuElm) {
109+
_menuElm.remove();
110+
}
111+
}
112+
113+
function handleBodyMouseDown(e) {
114+
if ((_menuElm !== e.target && !(_menuElm && _menuElm.contains(e.target))) || e.target.className === 'close') {
115+
_menuElm.setAttribute('aria-expanded', 'false');
116+
_menuElm.style.display = 'none';
117+
}
118+
}
119+
120+
function handleHeaderContextMenu(e) {
121+
e.preventDefault();
122+
Slick.Utils.emptyElement(_listElm);
123+
updateColumnOrder();
124+
columnCheckboxes = [];
125+
126+
let columnId, columnLabel, excludeCssClass;
127+
for (var i = 0; i < columns.length; i++) {
128+
columnId = columns[i].id;
129+
excludeCssClass = columns[i].excludeFromColumnPicker ? "hidden" : "";
130+
131+
const liElm = document.createElement('li');
132+
liElm.className = excludeCssClass;
133+
liElm.ariaLabel = columns[i] && columns[i].name;
134+
135+
const checkboxElm = document.createElement('input');
136+
checkboxElm.type = 'checkbox';
137+
checkboxElm.id = `${_gridUid}colpicker-${columnId}`;
138+
checkboxElm.dataset.columnid = columns[i].id;
139+
liElm.appendChild(checkboxElm);
140+
141+
columnCheckboxes.push(checkboxElm);
142+
143+
if (_grid.getColumnIndex(columnId) != null && !columns[i].hidden) {
144+
checkboxElm.checked = true;
145+
}
146+
147+
if (_options && _options.columnPicker && _options.columnPicker.headerColumnValueExtractor) {
148+
columnLabel = _options.columnPicker.headerColumnValueExtractor(columns[i], _options);
149+
} else {
150+
columnLabel = defaults.headerColumnValueExtractor(columns[i], _options);
151+
}
152+
153+
const labelElm = document.createElement('label');
154+
labelElm.htmlFor = `${_gridUid}colpicker-${columnId}`;
155+
labelElm.innerHTML = columnLabel;
156+
liElm.appendChild(labelElm);
157+
_listElm.appendChild(liElm);
158+
}
159+
160+
if (_options.columnPicker && (!_options.columnPicker.hideForceFitButton || !_options.columnPicker.hideSyncResizeButton)) {
161+
_listElm.appendChild(document.createElement('hr'));
162+
}
163+
164+
if (!(_options.columnPicker && _options.columnPicker.hideForceFitButton)) {
165+
let forceFitTitle = (_options.columnPicker && _options.columnPicker.forceFitTitle) || _options.forceFitTitle;
166+
167+
const liElm = document.createElement('li');
168+
liElm.ariaLabel = forceFitTitle;
169+
_listElm.appendChild(liElm);
170+
171+
const forceFitCheckboxElm = document.createElement('input');
172+
forceFitCheckboxElm.type = 'checkbox';
173+
forceFitCheckboxElm.id = `${_gridUid}colpicker-forcefit`;
174+
forceFitCheckboxElm.dataset.option = 'autoresize';
175+
liElm.appendChild(forceFitCheckboxElm);
176+
177+
const labelElm = document.createElement('label');
178+
labelElm.htmlFor = `${_gridUid}colpicker-forcefit`;
179+
labelElm.textContent = forceFitTitle;
180+
liElm.appendChild(labelElm);
181+
182+
if (_grid.getOptions().forceFitColumns) {
183+
forceFitCheckboxElm.checked = true;
184+
}
185+
}
186+
187+
if (!(_options.columnPicker && _options.columnPicker.hideSyncResizeButton)) {
188+
let syncResizeTitle = (_options.columnPicker && _options.columnPicker.syncResizeTitle) || _options.syncResizeTitle;
189+
190+
const liElm = document.createElement('li');
191+
liElm.ariaLabel = syncResizeTitle;
192+
_listElm.appendChild(liElm);
193+
194+
const syncResizeCheckboxElm = document.createElement('input');
195+
syncResizeCheckboxElm.type = 'checkbox';
196+
syncResizeCheckboxElm.id = `${_gridUid}colpicker-syncresize`;
197+
syncResizeCheckboxElm.dataset.option = 'syncresize';
198+
liElm.appendChild(syncResizeCheckboxElm);
199+
200+
const labelElm = document.createElement('label');
201+
labelElm.htmlFor = `${_gridUid}colpicker-syncresize`;
202+
labelElm.textContent = syncResizeTitle;
203+
liElm.appendChild(labelElm);
204+
205+
if (_grid.getOptions().syncColumnCellResize) {
206+
syncResizeCheckboxElm.checked = true;
207+
}
208+
}
209+
210+
repositionMenu(e);
211+
}
212+
213+
function repositionMenu(event) {
214+
const targetEvent = event && event.touches && event.touches[0] || event;
215+
_menuElm.style.top = `${targetEvent.pageY - 10}px`;
216+
_menuElm.style.left = `${targetEvent.pageX - 10}px`;
217+
_menuElm.style.maxHeight = `${window.innerHeight - targetEvent.clientY}px`;
218+
_menuElm.style.display = 'block';
219+
_menuElm.setAttribute('aria-expanded', 'true');
220+
_menuElm.appendChild(_listElm);
221+
}
222+
223+
function updateColumnOrder() {
224+
// Because columns can be reordered, we have to update the `columns`
225+
// to reflect the new order, however we can't just take `grid.getColumns()`,
226+
// as it does not include columns currently hidden by the picker.
227+
// We create a new `columns` structure by leaving currently-hidden
228+
// columns in their original ordinal position and interleaving the results
229+
// of the current column sort.
230+
let current = _grid.getColumns().slice(0);
231+
let ordered = new Array(columns.length);
232+
for (let i = 0; i < ordered.length; i++) {
233+
if (_grid.getColumnIndex(columns[i].id) === undefined) {
234+
// If the column doesn't return a value from getColumnIndex,
235+
// it is hidden. Leave it in this position.
236+
ordered[i] = columns[i];
237+
} else {
238+
// Otherwise, grab the next visible column.
239+
ordered[i] = current.shift();
240+
}
241+
}
242+
columns = ordered;
243+
}
244+
245+
/** Update the Titles of each sections (command, customTitle, ...) */
246+
function updateAllTitles(gridMenuOptions) {
247+
if (_columnTitleElm && _columnTitleElm.innerHTML) {
248+
_columnTitleElm.innerHTML = gridMenuOptions.columnTitle;
249+
}
250+
}
251+
252+
function updateColumn(e) {
253+
if (e.target.dataset.option === 'autoresize') {
254+
// when calling setOptions, it will resize with ALL Columns (even the hidden ones)
255+
// we can avoid this problem by keeping a reference to the visibleColumns before setOptions and then setColumns after
256+
var previousVisibleColumns = getVisibleColumns();
257+
var isChecked = e.target.checked;
258+
_grid.setOptions({ forceFitColumns: isChecked });
259+
_grid.setColumns(previousVisibleColumns);
260+
return;
261+
}
262+
263+
if (e.target.dataset.option === 'syncresize') {
264+
if (e.target.checked) {
265+
_grid.setOptions({ syncColumnCellResize: true });
266+
} else {
267+
_grid.setOptions({ syncColumnCellResize: false });
268+
}
269+
return;
270+
}
271+
272+
if (e.target.type === 'checkbox') {
273+
const isChecked = e.target.checked;
274+
const columnId = e.target.dataset.columnid || '';
275+
let visibleColumns = [];
276+
columnCheckboxes.forEach((columnCheckbox, idx) => {
277+
if (columns[idx].hidden !== undefined) { columns[idx].hidden = !columnCheckbox.checked; }
278+
if (columnCheckbox.checked) {
279+
visibleColumns.push(columns[idx]);
280+
}
281+
});
282+
283+
if (!visibleColumns.length) {
284+
e.target.checked = true;
285+
return;
286+
}
287+
288+
_grid.setColumns(visibleColumns);
289+
onColumnsChanged.notify({ columnId: columnId, showing: isChecked, allColumns: columns, columns: visibleColumns, grid: _grid });
290+
}
291+
}
292+
293+
function setColumnVisibiliy(idxOrId, show) {
294+
var idx = typeof idxOrId === 'number' ? idxOrId : getColumnIndexbyId(idxOrId);
295+
296+
var sVisible = !!_grid.getColumnIndex(columns[idx].id);
297+
var visibleColumns = getVisibleColumns();
298+
var col = columns[idx];
299+
if (show) {
300+
col.hidden = false;
301+
visibleColumns.splice(idx, 0, col);
302+
} else {
303+
let newVisibleColumns = [];
304+
for (let i = 0; i < visibleColumns.length; i++) {
305+
if (visibleColumns[i].id !== col.id) { newVisibleColumns.push(visibleColumns[i]); }
306+
}
307+
visibleColumns = newVisibleColumns;
308+
}
309+
310+
_grid.setColumns(visibleColumns);
311+
onColumnsChanged.notify({ columnId: col.id, showing: show, allColumns: columns, columns: visibleColumns, grid: _grid });
312+
}
313+
314+
function getAllColumns() {
315+
return columns;
316+
}
317+
318+
function getColumnbyId(id) {
319+
for (let i = 0; i < columns.length; i++) {
320+
if (columns[i].id === id) { return columns[i]; }
321+
}
322+
return null;
323+
}
324+
325+
function getColumnIndexbyId(id) {
326+
for (let i = 0; i < columns.length; i++) {
327+
if (columns[i].id === id) { return i; }
328+
}
329+
return -1;
330+
}
331+
332+
/** visible columns, we can simply get them directly from the grid */
333+
function getVisibleColumns() {
334+
return _grid.getColumns();
335+
}
336+
337+
init(_grid);
338+
339+
return {
340+
"init": init,
341+
"getAllColumns": getAllColumns,
342+
"getColumnbyId": getColumnbyId,
343+
"getColumnIndexbyId": getColumnIndexbyId,
344+
"getVisibleColumns": getVisibleColumns,
345+
"destroy": destroy,
346+
"updateAllTitles": updateAllTitles,
347+
"onColumnsChanged": onColumnsChanged,
348+
"setColumnVisibiliy": setColumnVisibiliy
349+
};
350+
}
351+
352+
// Slick.Controls.ColumnPicker
353+
Slick.Utils.extend(true, window, { Slick: { Controls: { ColumnPicker: SlickColumnPicker } } });
354+
})(window);

0 commit comments

Comments
 (0)