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);