Source: text-filter-widget.js

/**
 * 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.
 *
 * @class textFilterWidget
 * @memberof dc
 * @mixes dc.baseMixin
 * @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();
 * });
 *
 * dc.textFilterWidget('#search')
 *     .dimension(dimension);
 *     // you don't need the group() function
 *
 * @param {String|node|d3.selection|dc.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.
 * @returns {dc.textFilterWidget}
 **/

dc.textFilterWidget = function (parent, chartGroup) {
    var INPUT_CSS_CLASS = 'dc-text-filter-input';

    var _chart = dc.baseMixin({});

    var _normalize = function (s) {
        return s.toLowerCase();
    };

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

    var _placeHolder = 'search';

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

    _chart._doRender = function () {
        _chart.select('input').remove();

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

        _input.on('input', function () {
            _chart.dimension().filterFunction(_filterFunctionFactory(this.value));
            dc.events.trigger(function () {
                dc.redrawAll();
            }, dc.constants.EVENT_DELAY);
        });

        _chart._doRedraw();

        return _chart;
    };

    _chart._doRedraw = function () {
        _chart.root().selectAll('input')
            .attr('placeholder', _placeHolder);

        return _chart;
    };

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

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

    /**
     * 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.
     * @name filterFunctionFactory
     * @memberof dc.textFilterWidget
     * @instance
     * @example
     * // This is the default
     * function (query) {
     *     query = _normalize(query);
     *     return function (d) {
     *         return _normalize(d).indexOf(query) !== -1;
     *     };
     * };
     * @param {function} [filterFunctionFactory]
     * @returns {dc.textFilterWidget|function}
     **/
    _chart.filterFunctionFactory = function (filterFunctionFactory) {
        if (!arguments.length) {
            return _filterFunctionFactory;
        }
        _filterFunctionFactory = filterFunctionFactory;
        return _chart;
    };

    return _chart.anchor(parent, chartGroup);
};