Home Identifier Source Repository

src/extensions/colsVisibility/colsVisibility.js

import Dom from '../../dom';
import Types from '../../types';
import Event from '../../event';
import Arr from '../../array';

export default class ColsVisibility{

    /**
     * Columns Visibility extension
     * @param {Object} tf TableFilter instance
     * @param {Object} f Config
     */
    constructor(tf, f){

        // Configuration object
        var cfg = tf.config();

        this.initialized = false;
        this.name = f.name;
        this.desc = f.description || 'Columns visibility manager';

        //show/hide cols span element
        this.spanEl = null;
        //show/hide cols button element
        this.btnEl = null;
        //show/hide cols container div element
        this.contEl = null;

        //tick to hide or show column
        this.tickToHide = f.tick_to_hide===false ? false : true;
        //enables/disables cols manager generation
        this.manager = f.manager===false ? false : true;
        //only if external headers
        this.headersTbl = f.headers_table || false;
        //only if external headers
        this.headersIndex = f.headers_index || 1;
        //id of container element
        this.contElTgtId = f.container_target_id || null;
        //alternative headers text
        this.headersText = f.headers_text || null;
        //id of button container element
        this.btnTgtId = f.btn_target_id || null;
        //defines show/hide cols text
        this.btnText = f.btn_text || 'Columns▼';
        //defines show/hide cols button innerHtml
        this.btnHtml = f.btn_html || null;
        //defines css class for show/hide cols button
        this.btnCssClass = f.btn_css_class || 'colVis';
        //defines close link text
        this.btnCloseText = f.btn_close_text || 'Close';
        //defines close button innerHtml
        this.btnCloseHtml = f.btn_close_html || null;
        //defines css class for close button
        this.btnCloseCssClass = f.btn_close_css_class || this.btnCssClass;
        this.stylesheet = f.stylesheet || 'colsVisibility.css';
        //span containing show/hide cols button
        this.prfx = 'colVis_';
        //defines css class span containing show/hide cols
        this.spanCssClass =  f.span_css_class || 'colVisSpan';
        this.prfxCont = this.prfx + 'Cont_';
        //defines css class div containing show/hide cols
        this.contCssClass = f.cont_css_class || 'colVisCont';
        //defines css class for cols list (ul)
        this.listCssClass = cfg.list_css_class ||'cols_checklist';
        //defines css class for list item (li)
        this.listItemCssClass = cfg.checklist_item_css_class ||
            'cols_checklist_item';
        //defines css class for selected list item (li)
        this.listSlcItemCssClass = cfg.checklist_selected_item_css_class ||
            'cols_checklist_slc_item';
        //text preceding columns list
        this.text = f.text || (this.tickToHide ? 'Hide: ' : 'Show: ');
        this.atStart = f.at_start || null;
        this.enableHover = Boolean(f.enable_hover);
        //enables select all option
        this.enableTickAll = Boolean(f.enable_tick_all);
        //text preceding columns list
        this.tickAllText = f.tick_all_text || 'Select all:';

        //array containing hidden columns indexes
        this.hiddenCols = [];
        this.tblHasColTag = (Dom.tag(tf.tbl,'col').length > 0);

        //callback invoked just after cols manager is loaded
        this.onLoaded = Types.isFn(f.on_loaded) ? f.on_loaded : null;
        //calls function before cols manager is opened
        this.onBeforeOpen = Types.isFn(f.on_before_open) ?
            f.on_before_open : null;
        //calls function after cols manager is opened
        this.onAfterOpen = Types.isFn(f.on_after_open) ? f.on_after_open : null;
        //calls function before cols manager is closed
        this.onBeforeClose = Types.isFn(f.on_before_close) ?
            f.on_before_close : null;
        //calls function after cols manager is closed
        this.onAfterClose = Types.isFn(f.on_after_close) ?
            f.on_after_close : null;

        //callback before col is hidden
        this.onBeforeColHidden = Types.isFn(f.on_before_col_hidden) ?
            f.on_before_col_hidden : null;
        //callback after col is hidden
        this.onAfterColHidden = Types.isFn(f.on_after_col_hidden) ?
            f.on_after_col_hidden : null;
        //callback before col is displayed
        this.onBeforeColDisplayed = Types.isFn(f.on_before_col_displayed) ?
            f.on_before_col_displayed : null;
        //callback after col is displayed
        this.onAfterColDisplayed = Types.isFn(f.on_after_col_displayed) ?
            f.on_after_col_displayed : null;

        //Grid layout compatibility
        if(tf.gridLayout){
            this.headersTbl = tf.feature('gridLayout').headTbl; //headers table
            this.headersIndex = 0; //headers index
            this.onAfterColDisplayed = function(){};
            this.onAfterColHidden = function(){};
        }

        //Loads extension stylesheet
        tf.import(f.name+'Style', tf.stylePath + this.stylesheet, null, 'link');

        this.tf = tf;
    }

    toggle(){
        var contDisplay = this.contEl.style.display;
        var onBeforeOpen = this.onBeforeOpen;
        var onBeforeClose = this.onBeforeClose;
        var onAfterOpen = this.onAfterOpen;
        var onAfterClose = this.onAfterClose;

        if(onBeforeOpen && contDisplay !== 'inline'){
            onBeforeOpen.call(null, this);
        }
        if(onBeforeClose && contDisplay === 'inline'){
            onBeforeClose.call(null, this);
        }

        this.contEl.style.display = contDisplay === 'inline' ?
            'none' : 'inline';

        if(onAfterOpen && contDisplay !== 'inline'){
            onAfterOpen.call(null, this);
        }
        if(onAfterClose && contDisplay === 'inline'){
            onAfterClose.call(null, this);
        }
    }

    checkItem(lbl){
        var li = lbl.parentNode;
        if(!li || !lbl){
            return;
        }
        var isChecked = lbl.firstChild.checked;
        var colIndex = lbl.firstChild.getAttribute('id').split('_')[1];
        colIndex = parseInt(colIndex, 10);
        if(isChecked){
            Dom.addClass(li, this.listSlcItemCssClass);
        } else {
            Dom.removeClass(li, this.listSlcItemCssClass);
        }

        var hide = false;
        if((this.tickToHide && isChecked) || (!this.tickToHide && !isChecked)){
            hide = true;
        }
        this.setHidden(colIndex, hide);
    }

    init(){
        if(!this.manager){
            return;
        }
        this.buildBtn();
        this.buildManager();

        this.initialized = true;
    }

    /**
     * Build main button UI
     */
    buildBtn(){
        if(this.btnEl){
            return;
        }
        var tf = this.tf;
        var span = Dom.create('span', ['id', this.prfx+tf.id]);
        span.className = this.spanCssClass;

        //Container element (rdiv or custom element)
        if(!this.btnTgtId){
            tf.setToolbar();
        }
        var targetEl = !this.btnTgtId ? tf.rDiv : Dom.id(this.btnTgtId);

        if(!this.btnTgtId){
            var firstChild = targetEl.firstChild;
            firstChild.parentNode.insertBefore(span, firstChild);
        } else {
            targetEl.appendChild(span);
        }

        if(!this.btnHtml){
            var btn = Dom.create('a', ['href','javascript:;']);
            btn.className = this.btnCssClass;
            btn.title = this.desc;

            btn.innerHTML = this.btnText;
            span.appendChild(btn);
            if(!this.enableHover){
                Event.add(btn, 'click', (evt)=> { this.toggle(evt); });
            } else {
                Event.add(btn, 'mouseover', (evt)=> { this.toggle(evt); });
            }
        } else { //Custom html
            span.innerHTML = this.btnHtml;
            var colVisEl = span.firstChild;
            if(!this.enableHover){
                Event.add(colVisEl, 'click', (evt)=> { this.toggle(evt); });
            } else {
                Event.add(colVisEl, 'mouseover', (evt)=> { this.toggle(evt); });
            }
        }

        this.spanEl = span;
        this.btnEl = this.spanEl.firstChild;

        if(this.onLoaded){
            this.onLoaded.call(null, this);
        }
    }

    /**
     * Build columns manager UI
     */
    buildManager(){
        var tf = this.tf;

        var container = !this.contElTgtId ?
            Dom.create('div', ['id', this.prfxCont+tf.id]) :
            Dom.id(this.contElTgtId);
        container.className = this.contCssClass;

        //Extension description
        var extNameLabel = Dom.create('p');
        extNameLabel.innerHTML = this.text;
        container.appendChild(extNameLabel);

        //Headers list
        var ul = Dom.create('ul' ,['id', 'ul'+this.name+'_'+tf.id]);
        ul.className = this.listCssClass;

        var tbl = this.headersTbl ? this.headersTbl : tf.tbl;
        var headerIndex = this.headersTbl ?
            this.headersIndex : tf.getHeadersRowIndex();
        var headerRow = tbl.rows[headerIndex];

        //Tick all option
        if(this.enableTickAll){
            var li = Dom.createCheckItem(
                'col__'+tf.id, this.tickAllText, this.tickAllText);
            Dom.addClass(li, this.listItemCssClass);
            ul.appendChild(li);
            li.check.checked = !this.tickToHide;

            Event.add(li.check, 'click', ()=> {
                for(var h = 0; h < headerRow.cells.length; h++){
                    var itm = Dom.id('col_'+h+'_'+tf.id);
                    if(itm && li.check.checked !== itm.checked){
                        itm.click();
                        itm.checked = li.check.checked;
                    }
                }
            });
        }

        for(var i = 0; i < headerRow.cells.length; i++){
            var cell = headerRow.cells[i];
            var cellText = this.headersText && this.headersText[i] ?
                this.headersText[i] : this._getHeaderText(cell);
            var liElm = Dom.createCheckItem(
                'col_'+i+'_'+tf.id, cellText, cellText);
            Dom.addClass(liElm, this.listItemCssClass);
            if(!this.tickToHide){
                Dom.addClass(liElm, this.listSlcItemCssClass);
            }
            ul.appendChild(liElm);
            if(!this.tickToHide){
                liElm.check.checked = true;
            }

            Event.add(liElm.check, 'click', (evt)=> {
                var elm = Event.target(evt);
                var lbl = elm.parentNode;
                this.checkItem(lbl);
            });
        }

        //separator
        var p = Dom.create('p', ['align','center']);
        var btn;
        //Close link
        if(!this.btnCloseHtml){
            btn = Dom.create('a', ['href','javascript:;']);
            btn.className = this.btnCloseCssClass;
            btn.innerHTML = this.btnCloseText;
            Event.add(btn, 'click', (evt)=> { this.toggle(evt); });
            p.appendChild(btn);
        } else {
            p.innerHTML = this.btnCloseHtml;
            btn = p.firstChild;
            Event.add(btn, 'click', (evt)=> { this.toggle(evt); });
        }

        container.appendChild(ul);
        container.appendChild(p);

        this.btnEl.parentNode.insertBefore(container, this.btnEl);
        this.contEl = container;

        if(this.atStart){
            var a = this.atStart;
            for(var k=0; k<a.length; k++){
                var itm = Dom.id('col_'+a[k]+'_'+tf.id);
                if(itm){
                    itm.click();
                }
            }
        }
    }

    /**
     * Hide or show specified columns
     * @param {Numner} colIndex Column index
     * @param {Boolean} hide    hide column if true or show if false
     */
    setHidden(colIndex, hide){
        var tf = this.tf;
        var tbl = tf.tbl;

        if(this.onBeforeColHidden && hide){
            this.onBeforeColHidden.call(null, this, colIndex);
        }
        if(this.onBeforeColDisplayed && !hide){
            this.onBeforeColDisplayed.call(null, this, colIndex);
        }

        this._hideCells(tbl, colIndex, hide);
        if(this.headersTbl){
            this._hideCells(this.headersTbl, colIndex, hide);
        }

        var hiddenCols = this.hiddenCols;
        if(hide){
            if(hiddenCols.indexOf(colIndex) === -1){
                this.hiddenCols.push(colIndex);
            }
        } else {
            var itemIndex = Arr.indexByValue(hiddenCols, colIndex, true);
            if(hiddenCols.indexOf(colIndex) !== -1){
                this.hiddenCols.splice(itemIndex, 1);
            }
        }

        var gridLayout;
        var headTbl;
        var gridColElms;
        if(this.onAfterColHidden && hide){
            //This event is fired just after a column is displayed for
            //grid_layout support
            //TODO: grid layout module should be responsible for those
            //calculations
            if(tf.gridLayout){
                gridLayout = tf.feature('gridLayout');
                headTbl = gridLayout.headTbl;
                gridColElms = gridLayout.gridColElms;
                var hiddenWidth = parseInt(
                    gridColElms[colIndex].style.width, 10);

                var headTblW = parseInt(headTbl.style.width, 10);
                headTbl.style.width = headTblW - hiddenWidth + 'px';
                tbl.style.width = headTbl.style.width;
            }
            this.onAfterColHidden.call(null, this, colIndex);
        }

        if(this.onAfterColDisplayed && !hide){
            //This event is fired just after a column is displayed for
            //grid_layout support
            //TODO: grid layout module should be responsible for those
            //calculations
            if(tf.gridLayout){
                gridLayout = tf.feature('gridLayout');
                headTbl = gridLayout.headTbl;
                gridColElms = gridLayout.gridColElms;
                var width = parseInt(gridColElms[colIndex].style.width, 10);
                headTbl.style.width =
                    (parseInt(headTbl.style.width, 10) + width) + 'px';
                tf.tbl.style.width = headTbl.style.width;
            }
            this.onAfterColDisplayed.call(null, this, colIndex);
        }
    }

    /**
     * Show specified column
     * @param  {Number} colIndex Column index
     */
    showCol(colIndex){
        if(colIndex === undefined || !this.isColHidden(colIndex)){
            return;
        }
        if(this.manager && this.contEl){
            var itm = Dom.id('col_'+colIndex+'_'+this.tf.id);
            if(itm){ itm.click(); }
        } else {
            this.setHidden(colIndex, false);
        }
    }

    /**
     * Hide specified column
     * @param  {Number} colIndex Column index
     */
    hideCol(colIndex){
        if(colIndex === undefined || this.isColHidden(colIndex)){
            return;
        }
        if(this.manager && this.contEl){
            var itm = Dom.id('col_'+colIndex+'_'+this.tf.id);
            if(itm){ itm.click(); }
        } else {
            this.setHidden(colIndex, true);
        }
    }

    /**
     * Determine if specified column is hidden
     * @param  {Number} colIndex Column index
     */
    isColHidden(colIndex){
        if(this.hiddenCols.indexOf(colIndex) !== -1){
            return true;
        }
        return false;
    }

    /**
     * Toggle visibility of specified column
     * @param  {Number} colIndex Column index
     */
    toggleCol(colIndex){
        if(colIndex === undefined || this.isColHidden(colIndex)){
            this.showCol(colIndex);
        } else {
            this.hideCol(colIndex);
        }
    }

    /**
     * Returns the indexes of the columns currently hidden
     * @return {Array} column indexes
     */
    getHiddenCols(){
        return this.hiddenCols;
    }

    /**
     * Remove the columns manager
     */
    destroy(){
        if(!this.btnEl && !this.contEl){
            return;
        }
        if(Dom.id(this.contElTgtId)){
            Dom.id(this.contElTgtId).innerHTML = '';
        } else {
            this.contEl.innerHTML = '';
            this.contEl.parentNode.removeChild(this.contEl);
            this.contEl = null;
        }
        this.btnEl.innerHTML = '';
        this.btnEl.parentNode.removeChild(this.btnEl);
        this.btnEl = null;
        this.initialized = false;
    }

    _getHeaderText(cell){
        if(!cell.hasChildNodes){
            return '';
        }

        for(var i=0; i<cell.childNodes.length; i++){
            var n = cell.childNodes[i];
            if(n.nodeType === 3){
                return n.nodeValue;
            } else if(n.nodeType === 1){
                if(n.id && n.id.indexOf('popUp') !== -1){
                    continue;
                } else {
                    return Dom.getText(n);
                }
            }
            continue;
        }
        return '';
    }

    _hideCells(tbl, colIndex, hide){
        for(var i=0; i<tbl.rows.length; i++){
            var row = tbl.rows[i];
            var cell = row.cells[colIndex];
            if(cell){
                cell.style.display = hide ? 'none' : '';
            }
        }
    }

}