UNPKG

21 kBJavaScriptView Raw
1import {Feature} from '../../feature';
2import {
3 addClass, removeClass, createCheckItem, createElm, elm, removeElm,
4 getText, tag
5} from '../../dom';
6import {isUndef, EMPTY_FN, isNull} from '../../types';
7import {addEvt, targetEvt, removeEvt} from '../../event';
8import {root} from '../../root';
9import {NONE} from '../../const';
10import {
11 defaultsBool, defaultsStr, defaultsFn, defaultsNb, defaultsArr
12} from '../../settings';
13import {RIGHT} from '../../modules/toolbar';
14
15/**
16 * Columns Visibility extension
17 */
18export default class ColsVisibility extends Feature {
19
20 /**
21 * Creates an instance of ColsVisibility
22 * @param {TableFilter} tf TableFilter instance
23 * @param {Object} Configuration object
24 */
25 constructor(tf, f) {
26 super(tf, f.name);
27
28 // Configuration object
29 let cfg = this.config;
30
31 /**
32 * Module name
33 * @type {String}
34 */
35 this.name = f.name;
36
37 /**
38 * Module description
39 * @type {String}
40 */
41 this.desc = defaultsStr(f.description, 'Columns visibility manager');
42
43 /**
44 * show/hide columns container element
45 * @private
46 */
47 this.spanEl = null;
48
49 /**
50 * show/hide columns button element
51 * @private
52 */
53 this.btnEl = null;
54
55 /**
56 * show/hide columns main container element
57 * @private
58 */
59 this.contEl = null;
60
61 /**
62 * Enable tick to hide a column, defaults to true
63 * @type {Boolean}
64 */
65 this.tickToHide = defaultsBool(f.tick_to_hide, true);
66
67 /**
68 * Enable columns manager UI, defaults to true
69 * @type {Boolean}
70 */
71 this.manager = defaultsBool(f.manager, true);
72
73 /**
74 * Headers HTML table reference only if headers are external
75 * @type {DOMElement}
76 */
77 this.headersTbl = f.headers_table || null;
78
79 /**
80 * Headers row index only if headers are external
81 * @type {Number}
82 */
83 this.headersIndex = defaultsNb(f.headers_index, 1);
84
85 /**
86 * ID of main container element
87 * @type {String}
88 */
89 this.contElTgtId = defaultsStr(f.container_target_id, null);
90
91 /**
92 * Alternative text for column headers in column manager UI
93 * @type {Array}
94 */
95 this.headersText = defaultsArr(f.headers_text, []);
96
97 /**
98 * ID of button's container element
99 * @type {String}
100 */
101 this.btnTgtId = defaultsStr(f.btn_target_id, null);
102
103 /**
104 * Button's text, defaults to Columns▼
105 * @type {String}
106 */
107 this.btnText = defaultsStr(f.btn_text, 'Columns▼');
108
109 /**
110 * Button's inner HTML
111 * @type {String}
112 */
113 this.btnHtml = defaultsStr(f.btn_html, null);
114
115 /**
116 * Css class for button
117 * @type {String}
118 */
119 this.btnCssClass = defaultsStr(f.btn_css_class, 'colVis');
120
121 /**
122 * Columns manager UI close link text, defaults to 'Close'
123 * @type {String}
124 */
125 this.btnCloseText = defaultsStr(f.btn_close_text, 'Close');
126
127 /**
128 * Columns manager UI close link HTML
129 * @type {String}
130 */
131 this.btnCloseHtml = defaultsStr(f.btn_close_html, null);
132
133 /**
134 * Css for columns manager UI close link
135 * @type {String}
136 */
137 this.btnCloseCssClass = defaultsStr(f.btn_close_css_class,
138 this.btnCssClass);
139
140 /**
141 * Extension's stylesheet filename
142 * @type {String}
143 */
144 this.stylesheet = defaultsStr(f.stylesheet, 'colsVisibility.css');
145
146 /**
147 * Css for columns manager UI span
148 * @type {String}
149 */
150 this.spanCssClass = defaultsStr(f.span_css_class, 'colVisSpan');
151
152 /**
153 * Css for columns manager UI main container
154 * @type {String}
155 */
156 this.contCssClass = defaultsStr(f.cont_css_class, 'colVisCont');
157
158 /**
159 * Css for columns manager UI checklist (ul)
160 * @type {String}
161 */
162 this.listCssClass = defaultsStr(cfg.list_css_class, 'cols_checklist');
163
164 /**
165 * Css for columns manager UI checklist item (li)
166 * @type {String}
167 */
168 this.listItemCssClass = defaultsStr(cfg.checklist_item_css_class,
169 'cols_checklist_item');
170
171 /**
172 * Css for columns manager UI checklist item selected state (li)
173 * @type {String}
174 */
175 this.listSlcItemCssClass = defaultsStr(
176 cfg.checklist_selected_item_css_class,
177 'cols_checklist_slc_item'
178 );
179
180 /**
181 * Text preceding the columns list, defaults to 'Hide' or 'Show'
182 * depending on tick mode (tick_to_hide option)
183 * @type {String}
184 */
185 this.text = defaultsStr(f.text, this.tickToHide ? 'Hide: ' : 'Show: ');
186
187 /**
188 * List of columns indexes to be hidden at initialization
189 * @type {Array}
190 */
191 this.atStart = defaultsArr(f.at_start, []);
192
193 /**
194 * Enable hover behaviour on columns manager button/link
195 * @type {Boolean}
196 */
197 this.enableHover = Boolean(f.enable_hover);
198
199 /**
200 * Enable select all option, disabled by default
201 * @type {Boolean}
202 */
203 this.enableTickAll = Boolean(f.enable_tick_all);
204
205 /**
206 * Text for select all option, defaults to 'Select all:'
207 * @type {String}
208 */
209 this.tickAllText = defaultsStr(f.tick_all_text, 'Select all:');
210
211 /**
212 * Default position in toolbar ('left'|'center'|'right')
213 * @type {String}
214 */
215 this.toolbarPosition = defaultsStr(f.toolbar_position, RIGHT);
216
217 /**
218 * List of indexes of hidden columns
219 * @private
220 */
221 this.hiddenCols = [];
222
223 /**
224 * Bound mouseup wrapper
225 * @private
226 */
227 this.boundMouseup = null;
228
229 /**
230 * Callback fired when the extension is initialized
231 * @type {Function}
232 */
233 this.onLoaded = defaultsFn(f.on_loaded, EMPTY_FN);
234
235 /**
236 * Callback fired before the columns manager is opened
237 * @type {Function}
238 */
239 this.onBeforeOpen = defaultsFn(f.on_before_open, EMPTY_FN);
240
241 /**
242 * Callback fired after the columns manager is opened
243 * @type {Function}
244 */
245 this.onAfterOpen = defaultsFn(f.on_after_open, EMPTY_FN);
246
247 /**
248 * Callback fired before the columns manager is closed
249 * @type {Function}
250 */
251 this.onBeforeClose = defaultsFn(f.on_before_close, EMPTY_FN);
252
253 /**
254 * Callback fired after the columns manager is closed
255 * @type {Function}
256 */
257 this.onAfterClose = defaultsFn(f.on_after_close, EMPTY_FN);
258
259 /**
260 * Callback fired before a column is hidden
261 * @type {Function}
262 */
263 this.onBeforeColHidden = defaultsFn(f.on_before_col_hidden, EMPTY_FN);
264
265 /**
266 * Callback fired after a column is hidden
267 * @type {Function}
268 */
269 this.onAfterColHidden = defaultsFn(f.on_after_col_hidden, EMPTY_FN);
270
271 /**
272 * Callback fired before a column is displayed
273 * @type {Function}
274 */
275 this.onBeforeColDisplayed = defaultsFn(f.on_before_col_displayed,
276 EMPTY_FN);
277
278 /**
279 * Callback fired after a column is displayed
280 * @type {Function}
281 */
282 this.onAfterColDisplayed = defaultsFn(f.on_after_col_displayed,
283 EMPTY_FN);
284
285 //Grid layout support
286 if (tf.gridLayout) {
287 this.headersTbl = tf.feature('gridLayout').headTbl; //headers table
288 this.headersIndex = 0; //headers index
289 }
290
291 //Loads extension stylesheet
292 tf.import(f.name + 'Style', tf.getStylePath() + this.stylesheet, null,
293 'link');
294
295 this.enable();
296 }
297
298 /**
299 * Mouse-up event handler handling popup auto-close behaviour
300 * @private
301 */
302 onMouseup(evt) {
303 let targetElm = targetEvt(evt);
304
305 while (targetElm && targetElm !== this.contEl
306 && targetElm !== this.btnEl) {
307 targetElm = targetElm.parentNode;
308 }
309
310 if (targetElm !== this.contEl && targetElm !== this.btnEl) {
311 this.toggle();
312 }
313
314 return;
315 }
316
317 /**
318 * Toggle columns manager UI
319 */
320 toggle() {
321 // ensure mouseup event handler is removed
322 removeEvt(root, 'mouseup', this.boundMouseup);
323
324 let contDisplay = this.contEl.style.display;
325
326 if (contDisplay !== 'inline') {
327 this.onBeforeOpen(this);
328 }
329 if (contDisplay === 'inline') {
330 this.onBeforeClose(this);
331 }
332
333 this.contEl.style.display = contDisplay === 'inline' ?
334 NONE : 'inline';
335
336 if (contDisplay !== 'inline') {
337 this.onAfterOpen(this);
338 addEvt(root, 'mouseup', this.boundMouseup);
339 }
340 if (contDisplay === 'inline') {
341 this.onAfterClose(this);
342 }
343 }
344
345 /**
346 * Check an item in columns manager UI
347 * @private
348 */
349 checkItem(lbl) {
350 let li = lbl.parentNode;
351 if (!li || !lbl) {
352 return;
353 }
354 let isChecked = lbl.firstChild.checked;
355 let colIndex = lbl.firstChild.getAttribute('id').split('_')[1];
356 colIndex = parseInt(colIndex, 10);
357 if (isChecked) {
358 addClass(li, this.listSlcItemCssClass);
359 } else {
360 removeClass(li, this.listSlcItemCssClass);
361 }
362
363 let hide = false;
364 if ((this.tickToHide && isChecked) ||
365 (!this.tickToHide && !isChecked)) {
366 hide = true;
367 }
368 this.setHidden(colIndex, hide);
369 }
370
371 /**
372 * Initializes ColsVisibility instance
373 */
374 init() {
375 if (this.initialized || !this.manager) {
376 return;
377 }
378
379 this.emitter.emit('initializing-extension', this,
380 !isNull(this.btnTgtId));
381
382 this.emitter.on(['hide-column'],
383 (tf, colIndex) => this.hideCol(colIndex));
384
385 this.buildBtn();
386 this.buildManager();
387
388 /** @inherited */
389 this.initialized = true;
390
391 this.boundMouseup = this.onMouseup.bind(this);
392
393 this.emitter.emit('columns-visibility-initialized', this.tf, this);
394 this.emitter.emit('extension-initialized', this);
395
396 // Hide columns at start at very end of initialization, do not move
397 // as order is important
398 this._hideAtStart();
399 }
400
401 /**
402 * Build main button UI
403 */
404 buildBtn() {
405 if (this.btnEl) {
406 return;
407 }
408 let tf = this.tf;
409 let span = createElm('span');
410 span.className = this.spanCssClass;
411
412 // Container element (rdiv or custom element)
413 let targetEl = !this.btnTgtId ?
414 tf.feature('toolbar').container(this.toolbarPosition) :
415 elm(this.btnTgtId);
416
417 if (!this.btnTgtId) {
418 let firstChild = targetEl.firstChild;
419 firstChild.parentNode.insertBefore(span, firstChild);
420 } else {
421 targetEl.appendChild(span);
422 }
423
424 if (!this.btnHtml) {
425 let btn = createElm('a', ['href', 'javascript:;']);
426 btn.className = this.btnCssClass;
427 btn.title = this.desc;
428
429 btn.innerHTML = this.btnText;
430 span.appendChild(btn);
431 if (!this.enableHover) {
432 addEvt(btn, 'click', (evt) => this.toggle(evt));
433 } else {
434 addEvt(btn, 'mouseover', (evt) => this.toggle(evt));
435 }
436 } else { // Custom html
437 span.innerHTML = this.btnHtml;
438 let colVisEl = span.firstChild;
439 if (!this.enableHover) {
440 addEvt(colVisEl, 'click', (evt) => this.toggle(evt));
441 } else {
442 addEvt(colVisEl, 'mouseover', (evt) => this.toggle(evt));
443 }
444 }
445
446 this.spanEl = span;
447 this.btnEl = this.spanEl.firstChild;
448
449 this.onLoaded(this);
450 }
451
452 /**
453 * Build columns manager UI
454 */
455 buildManager() {
456 let tf = this.tf;
457
458 let container = !this.contElTgtId ?
459 createElm('div') :
460 elm(this.contElTgtId);
461 container.className = this.contCssClass;
462
463 //Extension description
464 let extNameLabel = createElm('p');
465 extNameLabel.innerHTML = this.text;
466 container.appendChild(extNameLabel);
467
468 //Headers list
469 let ul = createElm('ul');
470 ul.className = this.listCssClass;
471
472 let tbl = this.headersTbl || tf.dom();
473 let headerIndex = this.headersTbl ?
474 this.headersIndex : tf.getHeadersRowIndex();
475 let headerRow = tbl.rows[headerIndex];
476
477 //Tick all option
478 if (this.enableTickAll) {
479 let li = createCheckItem('col__' + tf.id, this.tickAllText,
480 this.tickAllText);
481 addClass(li, this.listItemCssClass);
482 ul.appendChild(li);
483 li.check.checked = !this.tickToHide;
484
485 addEvt(li.check, 'click', () => {
486 for (let h = 0; h < headerRow.cells.length; h++) {
487 let itm = elm('col_' + h + '_' + tf.id);
488 if (itm && li.check.checked !== itm.checked) {
489 itm.click();
490 itm.checked = li.check.checked;
491 }
492 }
493 });
494 }
495
496 for (let i = 0; i < headerRow.cells.length; i++) {
497 let cell = headerRow.cells[i];
498 let cellText = this.headersText[i] || this._getHeaderText(cell);
499 let liElm = createCheckItem('col_' + i + '_' + tf.id, cellText,
500 cellText);
501 addClass(liElm, this.listItemCssClass);
502 if (!this.tickToHide) {
503 addClass(liElm, this.listSlcItemCssClass);
504 }
505 ul.appendChild(liElm);
506 if (!this.tickToHide) {
507 liElm.check.checked = true;
508 }
509
510 addEvt(liElm.check, 'click', (evt) => {
511 let elm = targetEvt(evt);
512 let lbl = elm.parentNode;
513 this.checkItem(lbl);
514 });
515 }
516
517 //separator
518 let p = createElm('p', ['align', 'center']);
519 let btn;
520 //Close link
521 if (!this.btnCloseHtml) {
522 btn = createElm('a', ['href', 'javascript:;']);
523 btn.className = this.btnCloseCssClass;
524 btn.innerHTML = this.btnCloseText;
525 addEvt(btn, 'click', (evt) => this.toggle(evt));
526 p.appendChild(btn);
527 } else {
528 p.innerHTML = this.btnCloseHtml;
529 btn = p.firstChild;
530 addEvt(btn, 'click', (evt) => this.toggle(evt));
531 }
532
533 container.appendChild(ul);
534 container.appendChild(p);
535
536 this.btnEl.parentNode.insertBefore(container, this.btnEl);
537 this.contEl = container;
538 }
539
540 /**
541 * Hide or show specified columns
542 * @param {Number} colIndex Column index
543 * @param {Boolean} hide Hide column if true or show if false
544 */
545 setHidden(colIndex, hide) {
546 let tf = this.tf;
547 let tbl = tf.dom();
548
549 if (hide) {
550 this.onBeforeColHidden(this, colIndex);
551 } else {
552 this.onBeforeColDisplayed(this, colIndex);
553 }
554
555 this._hideElements(tbl, colIndex, hide);
556 if (this.headersTbl) {
557 this._hideElements(this.headersTbl, colIndex, hide);
558 }
559
560 let hiddenCols = this.hiddenCols;
561 let itemIndex = hiddenCols.indexOf(colIndex);
562 if (hide) {
563 if (itemIndex === -1) {
564 this.hiddenCols.push(colIndex);
565 }
566 } else {
567 if (itemIndex !== -1) {
568 this.hiddenCols.splice(itemIndex, 1);
569 }
570 }
571
572 if (hide) {
573 this.onAfterColHidden(this, colIndex);
574 this.emitter.emit('column-hidden', tf, this, colIndex,
575 this.hiddenCols);
576 } else {
577 this.onAfterColDisplayed(this, colIndex);
578 this.emitter.emit('column-shown', tf, this, colIndex,
579 this.hiddenCols);
580 }
581 }
582
583 /**
584 * Show specified column
585 * @param {Number} colIndex Column index
586 */
587 showCol(colIndex) {
588 if (isUndef(colIndex) || !this.isColHidden(colIndex)) {
589 return;
590 }
591 if (this.manager && this.contEl) {
592 let itm = elm('col_' + colIndex + '_' + this.tf.id);
593 if (itm) {
594 itm.click();
595 }
596 } else {
597 this.setHidden(colIndex, false);
598 }
599 }
600
601 /**
602 * Hide specified column
603 * @param {Number} colIndex Column index
604 */
605 hideCol(colIndex) {
606 if (isUndef(colIndex) || this.isColHidden(colIndex)) {
607 return;
608 }
609 if (this.manager && this.contEl) {
610 let itm = elm('col_' + colIndex + '_' + this.tf.id);
611 if (itm) {
612 itm.click();
613 }
614 } else {
615 this.setHidden(colIndex, true);
616 }
617 }
618
619 /**
620 * Determine if specified column is hidden
621 * @param {Number} colIndex Column index
622 */
623 isColHidden(colIndex) {
624 if (this.hiddenCols.indexOf(colIndex) !== -1) {
625 return true;
626 }
627 return false;
628 }
629
630 /**
631 * Toggle visibility of specified column
632 * @param {Number} colIndex Column index
633 */
634 toggleCol(colIndex) {
635 if (isUndef(colIndex) || this.isColHidden(colIndex)) {
636 this.showCol(colIndex);
637 } else {
638 this.hideCol(colIndex);
639 }
640 }
641
642 /**
643 * Return the indexes of the columns currently hidden
644 * @return {Array} column indexes
645 */
646 getHiddenCols() {
647 return this.hiddenCols;
648 }
649
650 /**
651 * Remove the columns manager
652 */
653 destroy() {
654 if (!this.initialized) {
655 return;
656 }
657 if (elm(this.contElTgtId)) {
658 elm(this.contElTgtId).innerHTML = '';
659 } else {
660 this.contEl.innerHTML = '';
661 removeElm(this.contEl);
662 this.contEl = null;
663 }
664 this.btnEl.innerHTML = '';
665 removeElm(this.btnEl);
666 this.btnEl = null;
667
668 this.emitter.off(['hide-column'],
669 (tf, colIndex) => this.hideCol(colIndex));
670
671 this.boundMouseup = null;
672
673 this.initialized = false;
674 }
675
676 _getHeaderText(cell) {
677 if (!cell.hasChildNodes) {
678 return '';
679 }
680
681 for (let i = 0; i < cell.childNodes.length; i++) {
682 let n = cell.childNodes[i];
683 if (n.nodeType === 3) {
684 return n.nodeValue;
685 } else if (n.nodeType === 1) {
686 if (n.id && n.id.indexOf('popUp') !== -1) {
687 continue;
688 } else {
689 return getText(n);
690 }
691 }
692 continue;
693 }
694 return '';
695 }
696
697 _hideElements(tbl, colIdx, hide) {
698 this._hideCells(tbl, colIdx, hide);
699 this._hideCol(tbl, colIdx, hide);
700 }
701
702 _hideCells(tbl, colIdx, hide) {
703 for (let i = 0; i < tbl.rows.length; i++) {
704 let row = tbl.rows[i];
705 let cell = row.cells[colIdx];
706 if (cell) {
707 cell.style.display = hide ? NONE : '';
708 }
709 }
710 }
711
712 _hideCol(tbl, colIdx, hide) {
713 let colElms = tag(tbl, 'col');
714 if (colElms.length === 0) {
715 return;
716 }
717 colElms[colIdx].style.display = hide ? NONE : '';
718 }
719
720 _hideAtStart() {
721 this.atStart.forEach((colIdx) => {
722 this.hideCol(colIdx);
723 });
724 }
725}