Home Identifier Source Repository

src/modules/dropdown.js

import Dom from '../dom';
import Arr from '../array';
import Str from '../string';
import Sort from '../sort';

export class Dropdown{

    /**
     * Dropdown UI component
     * @param {Object} tf TableFilter instance
     */
    constructor(tf){
        // Configuration object
        var f = tf.config();

        this.enableSlcResetFilter = f.enable_slc_reset_filter===false ?
            false : true;
        //defines empty option text
        this.nonEmptyText = f.non_empty_text || '(Non empty)';
        //sets select filling method: 'innerHTML' or 'createElement'
        this.slcFillingMethod = f.slc_filling_method || 'createElement';
        //IE only, tooltip text appearing on select before it is populated
        this.activateSlcTooltip =  f.activate_slc_tooltip ||
            'Click to activate';
        //tooltip text appearing on multiple select
        this.multipleSlcTooltip = f.multiple_slc_tooltip ||
            'Use Ctrl key for multiple selections';

        this.isCustom = null;
        this.opts = null;
        this.optsTxt = null;
        this.slcInnerHtml = null;

        this.tf = tf;
    }

    /**
     * Build drop-down filter UI asynchronously
     * @param  {Number}  colIndex   Column index
     * @param  {Boolean} isLinked Enable linked refresh behaviour
     * @param  {Boolean} isExternal Render in external container
     * @param  {String}  extSlcId   External container id
     */
    build(colIndex, isLinked, isExternal, extSlcId){
        var tf = this.tf;
        tf.EvtManager(
            tf.Evt.name.dropdown,
            {
                slcIndex: colIndex,
                slcRefreshed: isLinked,
                slcExternal: isExternal,
                slcId: extSlcId
            }
        );
    }

    /**
     * Build drop-down filter UI
     * @param  {Number}  colIndex    Column index
     * @param  {Boolean} isLinked Enable linked refresh behaviour
     * @param  {Boolean} isExternal  Render in external container
     * @param  {String}  extSlcId    External container id
     */
    _build(colIndex, isLinked=false, isExternal=false, extSlcId=null){
        var tf = this.tf;
        colIndex = parseInt(colIndex, 10);

        this.opts = [];
        this.optsTxt = [];
        this.slcInnerHtml = '';

        var slcId = tf.fltIds[colIndex];
        if((!Dom.id(slcId) && !isExternal) ||
            (!Dom.id(extSlcId) && isExternal)){
            return;
        }
        var slc = !isExternal ? Dom.id(slcId) : Dom.id(extSlcId),
            rows = tf.tbl.rows,
            matchCase = tf.matchCase;

        //custom select test
        this.isCustom = tf.isCustomOptions(colIndex);

        //custom selects text
        var activeFlt;
        if(isLinked && tf.activeFilterId){
            activeFlt = tf.activeFilterId.split('_')[0];
            activeFlt = activeFlt.split(tf.prfxFlt)[1];
        }

        /*** remember grid values ***/
        var fltsValues = [], fltArr = [];
        if(tf.rememberGridValues){
            fltsValues =
                tf.feature('store').getFilterValues(tf.fltsValuesCookie);
            if(fltsValues && !Str.isEmpty(fltsValues.toString())){
                if(this.isCustom){
                    fltArr.push(fltsValues[colIndex]);
                } else {
                    fltArr = fltsValues[colIndex].split(' '+tf.orOperator+' ');
                }
            }
        }

        var excludedOpts = null,
            filteredDataCol = null;
        if(isLinked && tf.disableExcludedOptions){
            excludedOpts = [];
            filteredDataCol = [];
        }

        for(var k=tf.refRow; k<tf.nbRows; k++){
            // always visible rows don't need to appear on selects as always
            // valid
            if(tf.hasVisibleRows && tf.visibleRows.indexOf(k) !== -1){
                continue;
            }

            var cell = rows[k].cells,
                nchilds = cell.length;

            // checks if row has exact cell #
            if(nchilds !== tf.nbCells || this.isCustom){
                continue;
            }

            // this loop retrieves cell data
            for(var j=0; j<nchilds; j++){
                // WTF: cyclomatic complexity hell
                if((colIndex===j &&
                    (!isLinked ||
                        (isLinked && tf.disableExcludedOptions))) ||
                    (colIndex==j && isLinked &&
                        ((rows[k].style.display === '' && !tf.paging) ||
                    (tf.paging && (!tf.validRowsIndex ||
                        (tf.validRowsIndex &&
                            Arr.has(tf.validRowsIndex, k))) &&
                        ((activeFlt===undefined || activeFlt==colIndex)  ||
                            (activeFlt!=colIndex &&
                                Arr.has(tf.validRowsIndex, k) ))) ))){
                    var cell_data = tf.getCellData(j, cell[j]),
                        //Vary Peter's patch
                        cell_string = Str.matchCase(cell_data, matchCase);

                    // checks if celldata is already in array
                    if(!Arr.has(this.opts, cell_string, matchCase)){
                        this.opts.push(cell_data);
                    }

                    if(isLinked && tf.disableExcludedOptions){
                        var filteredCol = filteredDataCol[j];
                        if(!filteredCol){
                            filteredCol = tf.getFilteredDataCol(j);
                        }
                        if(!Arr.has(filteredCol, cell_string, matchCase) &&
                            !Arr.has(
                                excludedOpts, cell_string, matchCase) &&
                            !this.isFirstLoad){
                            excludedOpts.push(cell_data);
                        }
                    }
                }//if colIndex==j
            }//for j
        }//for k

        //Retrieves custom values
        if(this.isCustom){
            var customValues = tf.getCustomOptions(colIndex);
            this.opts = customValues[0];
            this.optsTxt = customValues[1];
        }

        if(tf.sortSlc && !this.isCustom){
            if (!matchCase){
                this.opts.sort(Sort.ignoreCase);
                if(excludedOpts){
                    excludedOpts.sort(Sort.ignoreCase);
                }
            } else {
                this.opts.sort();
                if(excludedOpts){ excludedOpts.sort(); }
            }
        }

        //asc sort
        if(tf.sortNumAsc && Arr.has(tf.sortNumAsc, colIndex)){
            try{
                this.opts.sort( numSortAsc );
                if(excludedOpts){
                    excludedOpts.sort(numSortAsc);
                }
                if(this.isCustom){
                    this.optsTxt.sort(numSortAsc);
                }
            } catch(e) {
                this.opts.sort();
                if(excludedOpts){
                    excludedOpts.sort();
                }
                if(this.isCustom){
                    this.optsTxt.sort();
                }
            }//in case there are alphanumeric values
        }
        //desc sort
        if(tf.sortNumDesc && Arr.has(tf.sortNumDesc, colIndex)){
            try{
                this.opts.sort(numSortDesc);
                if(excludedOpts){
                    excludedOpts.sort(numSortDesc);
                }
                if(this.isCustom){
                    this.optsTxt.sort(numSortDesc);
                }
            } catch(e) {
                this.opts.sort();
                if(excludedOpts){
                    excludedOpts.sort();
                }
                if(this.isCustom){
                    this.optsTxt.sort();
                }
            }//in case there are alphanumeric values
        }

        //populates drop-down
        this.addOptions(
            colIndex, slc, isLinked, excludedOpts, fltsValues, fltArr);
    }

    /**
     * Add drop-down options
     * @param {Number} colIndex     Column index
     * @param {Object} slc          Select Dom element
     * @param {Boolean} isLinked    Enable linked refresh behaviour
     * @param {Array} excludedOpts  Array of excluded options
     * @param {Array} fltsValues    Collection of persisted filter values
     * @param {Array} fltArr        Collection of persisted filter values
     */
    addOptions(colIndex, slc, isLinked, excludedOpts, fltsValues, fltArr){
        var tf = this.tf,
            fillMethod = Str.lower(this.slcFillingMethod),
            slcValue = slc.value;

        slc.innerHTML = '';
        slc = this.addFirstOption(slc);

        for(var y=0; y<this.opts.length; y++){
            if(this.opts[y]===''){
                continue;
            }
            var val = this.opts[y]; //option value
            var lbl = this.isCustom ? this.optsTxt[y] : val; //option text
            var isDisabled = false;
            if(isLinked && tf.disableExcludedOptions &&
                Arr.has(
                    excludedOpts,
                    Str.matchCase(val, tf.matchCase),
                    tf.matchCase
                )){
                isDisabled = true;
            }

            if(fillMethod === 'innerhtml'){
                var slcAttr = '';
                if(tf.fillSlcOnDemand && slcValue===this.opts[y]){
                    slcAttr = 'selected="selected"';
                }
                this.slcInnerHtml += '<option value="'+val+'" ' + slcAttr +
                    (isDisabled ? 'disabled="disabled"' : '')+ '>' +
                    lbl+'</option>';
            } else {
                var opt;
                //fill select on demand
                if(tf.fillSlcOnDemand && slcValue===this.opts[y] &&
                    tf['col'+colIndex]===tf.fltTypeSlc){
                    opt = Dom.createOpt(lbl, val, true);
                } else {
                    if(tf['col'+colIndex]!==tf.fltTypeMulti){
                        opt = Dom.createOpt(
                            lbl,
                            val,
                            (fltsValues[colIndex]!==' ' &&
                                val===fltsValues[colIndex]) ? true : false
                        );
                    } else {
                        opt = Dom.createOpt(
                            lbl,
                            val,
                            (Arr.has(fltArr,
                                Str.matchCase(this.opts[y], tf.matchCase),
                                tf.matchCase) ||
                              fltArr.toString().indexOf(val)!== -1) ?
                                true : false
                        );
                    }
                }
                if(isDisabled){
                    opt.disabled = true;
                }
                slc.appendChild(opt);
            }
        }// for y

        if(fillMethod === 'innerhtml'){
            slc.innerHTML += this.slcInnerHtml;
        }
        slc.setAttribute('filled', '1');
    }

    /**
     * Add drop-down header option
     * @param {Object} slc Select DOM element
     */
    addFirstOption(slc){
        var tf = this.tf,
            fillMethod = Str.lower(this.slcFillingMethod);

        if(fillMethod === 'innerhtml'){
            this.slcInnerHtml += '<option value="">'+ tf.displayAllText +
                '</option>';
        }
        else {
            var opt0 = Dom.createOpt(
                (!this.enableSlcResetFilter ? '' : tf.displayAllText),'');
            if(!this.enableSlcResetFilter){
                opt0.style.display = 'none';
            }
            slc.appendChild(opt0);
            if(tf.enableEmptyOption){
                var opt1 = Dom.createOpt(tf.emptyText, tf.emOperator);
                slc.appendChild(opt1);
            }
            if(tf.enableNonEmptyOption){
                var opt2 = Dom.createOpt(tf.nonEmptyText, tf.nmOperator);
                slc.appendChild(opt2);
            }
        }
        return slc;
    }

}