Source: charts/text-filter-widget.js

import {BaseMixin} from '../base/base-mixin';
import {constants} from '../core/constants';
import {events} from '../core/events';

const INPUT_CSS_CLASS = 'dc-text-filter-input';

/**
 * Text Filter Widget
 *
 * The text filter widget is a simple widget designed to display an input field allowing to filter
 * data that matches the text typed.
 * As opposed to the other charts, this doesn't display any result and doesn't update its display,
 * it's just to input an filter other charts.
 *
 * @mixes BaseMixin
 */
export class TextFilterWidget extends BaseMixin {
    /**
     * Create Text Filter widget
     * @example
     *
     * var data = [{"firstName":"John","lastName":"Coltrane"}{"firstName":"Miles",lastName:"Davis"}]
     * var ndx = crossfilter(data);
     * var dimension = ndx.dimension(function(d) {
     *     return d.lastName.toLowerCase() + ' ' + d.firstName.toLowerCase();
     * });
     *
     * new TextFilterWidget('#search')
     *     .dimension(dimension);
     *     // you don't need the group() function
     *
     * @param {String|node|d3.selection|CompositeChart} parent - Any valid
     * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector}
     * specifying a dom block element such as a div; or a dom element or d3 selection.
     * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
     * Interaction with a chart will only trigger events and redraws within the chart's group.
     */
    constructor (parent, chartGroup) {
        super();

        this._normalize = s => s.toLowerCase();

        this._filterFunctionFactory = query => {
            query = this._normalize(query);
            return d => this._normalize(d).indexOf(query) !== -1;
        };

        this._placeHolder = 'search';

        this.group(() => {
            throw 'the group function on textFilterWidget should never be called, please report the issue';
        });

        this.anchor(parent, chartGroup);
    }

    _doRender () {
        this.select('input').remove();

        this._input = this.root().append('input')
            .classed(INPUT_CSS_CLASS, true);

        const chart = this;
        this._input.on('input', function () {
            chart.dimension().filterFunction(chart._filterFunctionFactory(this.value));
            events.trigger(() => {
                chart.redrawGroup();
            }, constants.EVENT_DELAY);
        });

        this._doRedraw();

        return this;
    }

    _doRedraw () {
        this.root().selectAll('input')
            .attr('placeholder', this._placeHolder);

        return this;
    }

    /**
     * This function will be called on values before calling the filter function.
     * @example
     * // This is the default
     * chart.normalize(function (s) {
     *   return s.toLowerCase();
     * });
     * @param {function} [normalize]
     * @returns {TextFilterWidget|function}
     */
    normalize (normalize) {
        if (!arguments.length) {
            return this._normalize;
        }
        this._normalize = normalize;
        return this;
    }

    /**
     * Placeholder text in the search box.
     * @example
     * // This is the default
     * chart.placeHolder('type to filter');
     * @param {function} [placeHolder='search']
     * @returns {TextFilterWidget|string}
     */
    placeHolder (placeHolder) {
        if (!arguments.length) {
            return this._placeHolder;
        }
        this._placeHolder = placeHolder;
        return this;
    }

    /**
     * This function will be called with the search text, it needs to return a function that will be used to
     * filter the data. The default function checks presence of the search text.
     * @example
     * // This is the default
     * function (query) {
     *     query = _normalize(query);
     *     return function (d) {
     *         return _normalize(d).indexOf(query) !== -1;
     *     };
     * };
     * @param {function} [filterFunctionFactory]
     * @returns {TextFilterWidget|function}
     */
    filterFunctionFactory (filterFunctionFactory) {
        if (!arguments.length) {
            return this._filterFunctionFactory;
        }
        this._filterFunctionFactory = filterFunctionFactory;
        return this;
    }
}

export const textFilterWidget = (parent, chartGroup) => new TextFilterWidget(parent, chartGroup);