Home Reference Source

src/modules/help.js

import {Feature} from '../feature';
import {createElm, createText, elm, removeElm} from '../dom';
import {addEvt, targetEvt, removeEvt} from '../event';
import {NONE} from '../const';
import {root} from '../root';
import {isEmpty, isNull} from '../types';
import {defaultsStr, defaultsNb} from '../settings';
import {RIGHT} from './toolbar';

const WIKI_URL = 'https://github.com/koalyptus/TableFilter/wiki/' +
    '4.-Filter-operators';
const WEBSITE_URL = 'https://www.tablefilter.com/';

/**
 * Help UI component
 */
export class Help extends Feature {

    /**
     * Creates an instance of Help
     * @param {TableFilter} tf TableFilter instance
     */
    constructor(tf) {
        super(tf, Help);

        let f = this.config.help_instructions || {};

        /**
         * ID of main custom container element
         * @type {String}
         */
        this.tgtId = defaultsStr(f.target_id, null);

        /**
         * ID of custom container element for instructions
         * @type {String}
         */
        this.contTgtId = defaultsStr(f.container_target_id, null);

        /**
         * Instructions text (accepts HTML)
         * @type {String}
         */
        this.instrText = !isEmpty(f.text) ? f.text :
            'Use the filters above each column to filter and limit table ' +
            'data. Advanced searches can be performed by using the following ' +
            'operators: <br /><b>&lt;</b>, <b>&lt;=</b>, <b>&gt;</b>, ' +
            '<b>&gt;=</b>, <b>=</b>, <b>*</b>, <b>!</b>, <b>{</b>, <b>}</b>, ' +
            '<b>||</b>,<b>&amp;&amp;</b>, <b>[empty]</b>, <b>[nonempty]</b>, ' +
            '<b>rgx:</b><br/><a href="' + WIKI_URL + '" target="_blank">' +
            'Learn more</a><hr/>';

        /**
         * Instructions HTML
         * @type {String}
         */
        this.instrHtml = defaultsStr(f.html, null);

        /**
         * Help button text ('?')
         * @type {String}
         */
        this.btnText = defaultsStr(f.btn_text, '?');

        /**
         * Custom help button HTML
         * @type {String}
         */
        this.btnHtml = defaultsStr(f.btn_html, null);

        /**
         * Css class for help button
         * @type {String}
         */
        this.btnCssClass = defaultsStr(f.btn_css_class, 'helpBtn');

        /**
         * Css class for help container element
         * @type {String}
         */
        this.contCssClass = defaultsStr(f.container_css_class, 'helpCont');

        /**
         * Button DOM element
         * @type {DOMElement}
         */
        this.btn = null;

        /**
         * Help container DOM element
         * @type {DOMElement}
         */
        this.cont = null;

        /**
         * Adjust container left position when table's horizontal scroll is
         * on, typically when `responsive` option is enabled.
         * @type {Number}
         * @defaultValue 25
         */
        this.contAdjustLeftPosition =
            defaultsNb(f.container_adjust_left_position, 25);

        /**
         * Bound mouseup wrapper
         * @private
         */
        this.boundMouseup = null;

        /**
         * Default HTML appended to instructions text
         * @type {String}
         */
        this.defaultHtml = '<div class="helpFooter"><h4>TableFilter ' +
            'v' + tf.version + '</h4>' + '<a href="' + WEBSITE_URL +
            '" target="_blank">' + WEBSITE_URL + '</a>' +
            '<br/><span>&copy;2015-' + tf.year + ' {AUTHOR}</span>' +
            '<div align="center" style="margin-top:8px;">' +
            '<a href="javascript:void(0);" class="close">Close</a></div></div>';

        /**
         * Default position in toolbar ('left'|'center'|'right')
         * @type {String}
         */
        this.toolbarPosition = defaultsStr(f.toolbar_position, RIGHT);

        this.emitter.on(['init-help'], () => this.init());
    }

    /**
     * Mouse-up event handler handling popup auto-close behaviour
     * @private
     */
    onMouseup(evt) {
        let targetElm = targetEvt(evt);

        while (targetElm && targetElm !== this.cont && targetElm !== this.btn) {
            targetElm = targetElm.parentNode;
        }

        if (targetElm !== this.cont && targetElm !== this.btn) {
            this.toggle();
        }

        return;
    }

    /**
     * Initialise Help instance
     */
    init() {
        if (this.initialized) {
            return;
        }

        this.emitter.emit('initializing-feature', this, !isNull(this.tgtId));

        let tf = this.tf;

        let btn = createElm('span');
        let cont = createElm('div');

        this.boundMouseup = this.onMouseup.bind(this);

        //help button is added to defined element
        let targetEl = !this.tgtId ?
            tf.feature('toolbar').container(this.toolbarPosition) :
            elm(this.tgtId);
        targetEl.appendChild(btn);

        let divContainer = !this.contTgtId ? btn : elm(this.contTgtId);

        if (!this.btnHtml) {
            divContainer.appendChild(cont);
            let helplink = createElm('a', ['href', 'javascript:void(0);']);
            helplink.className = this.btnCssClass;
            helplink.appendChild(createText(this.btnText));
            btn.appendChild(helplink);
            addEvt(helplink, 'click', () => this.toggle());
        } else {
            btn.innerHTML = this.btnHtml;
            let helpEl = btn.firstChild;
            addEvt(helpEl, 'click', () => this.toggle());
            divContainer.appendChild(cont);
        }

        if (!this.instrHtml) {
            cont.innerHTML = this.instrText;
            cont.className = this.contCssClass;
        } else {
            if (this.contTgtId) {
                divContainer.appendChild(cont);
            }
            cont.innerHTML = this.instrHtml;
            if (!this.contTgtId) {
                cont.className = this.contCssClass;
            }
        }
        cont.innerHTML += this.defaultHtml;
        addEvt(cont, 'click', () => this.toggle());

        this.cont = cont;
        this.btn = btn;
        /** @inherited */
        this.initialized = true;

        this.emitter.emit('feature-initialized', this);
    }

    /**
     * Toggle help pop-up
     */
    toggle() {
        // check only if explicitily disabled as in this case undefined
        // signifies the help feature is enabled by default
        if (!this.isEnabled()) {
            return;
        }

        // ensure mouseup event handler is removed
        removeEvt(root, 'mouseup', this.boundMouseup);

        let divDisplay = this.cont.style.display;
        if (divDisplay === '' || divDisplay === NONE) {
            this.cont.style.display = 'inline';

            // if table element has an horizontal scrollbar adjust container
            // left position accordingly
            if (this.tf.dom().scrollLeft > 0) {
                this.cont.style.left = `${
                    this.btn.offsetLeft
                    - this.tf.dom().scrollLeft
                    + this.contAdjustLeftPosition
                }px`;
            }

            addEvt(root, 'mouseup', this.boundMouseup);
        } else {
            this.cont.style.display = NONE;
            this.cont.style.left = '';
        }
    }

    /**
     * Remove help UI
     */
    destroy() {
        if (!this.initialized) {
            return;
        }
        removeElm(this.btn);
        this.btn = null;

        removeElm(this.cont);
        this.cont = null;

        this.boundMouseup = null;
        this.initialized = false;
    }

}

// TODO: remove as soon as feature name is fixed
Help.meta = {alwaysInstantiate: true};