/** * The cboxMenu is a simple widget designed to filter a dimension by * selecting option(s) from a set of HTML `<input />` elements. The menu can be * made into a set of radio buttons (single select) or checkboxes (multiple). * @class cboxMenu * @memberof dc * @mixes dc.baseMixin * @example * // create a cboxMenu under #cbox-container using the default global chart group * var cbox = dc.cboxMenu('#cbox-container') * .dimension(states) * .group(stateGroup); * // the option text can be set via the title() function * // by default the option text is '`key`: `value`' * cbox.title(function (d){ * return 'STATE: ' + d.key; * }) * @param {String|node|d3.selection|dc.compositeChart} parent - Any valid * [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) 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 widget should be placed in. * Interaction with the widget will only trigger events and redraws within its group. * @returns {cboxMenu} **/ dc.cboxMenu = function (parent, chartGroup) { var GROUP_CSS_CLASS = 'dc-cbox-group'; var ITEM_CSS_CLASS = 'dc-cbox-item'; var _chart = dc.baseMixin({}); var _cbox; var _promptText = 'Select all'; var _multiple = false; var _inputType = 'radio'; var _promptValue = null; // generate a random number to use as an ID var _randVal = Math.floor(Math.random() * (100000)) + 1; var _order = function (a, b) { return _chart.keyAccessor()(a) > _chart.keyAccessor()(b) ? 1 : _chart.keyAccessor()(b) > _chart.keyAccessor()(a) ? -1 : 0; }; var _filterDisplayed = function (d) { return _chart.valueAccessor()(d) > 0; }; _chart.data(function (group) { return group.all().filter(_filterDisplayed); }); _chart._doRender = function () { return _chart._doRedraw(); }; /* // IS THIS NEEDED? // Fixing IE 11 crash when redrawing the chart // see here for list of IE user Agents : // http://www.useragentstring.com/pages/useragentstring.php?name=Internet+Explorer var ua = window.navigator.userAgent; // test for IE 11 but not a lower version (which contains MSIE in UA) if (ua.indexOf('Trident/') > 0 && ua.indexOf('MSIE') === -1) { _chart.redraw = _chart.render; } */ _chart._doRedraw = function () { _chart.select('ul').remove(); _cbox = _chart.root() .append('ul') .classed(GROUP_CSS_CLASS, true); renderOptions(); if (_chart.hasFilter() && _multiple) { _cbox.selectAll('input') .property('checked', function (d) { // adding `false` avoids failing test cases in phantomjs return d && _chart.filters().indexOf(String(_chart.keyAccessor()(d))) >= 0 || false; }); } else if (_chart.hasFilter()) { _cbox.selectAll('input') .property('checked', function (d) { if (!d) { return false; } return _chart.keyAccessor()(d) === _chart.filter(); }); } return _chart; }; function renderOptions () { var options = _cbox .selectAll('li.' + ITEM_CSS_CLASS) .data(_chart.data(), function (d) { return _chart.keyAccessor()(d); }); options.exit().remove(); options = options.enter() .append('li') .classed(ITEM_CSS_CLASS, true) .merge(options); options .append('input') .attr('type', _inputType) .attr('value', function (d) { return _chart.keyAccessor()(d); }) .attr('name', 'domain_' + _randVal) .attr('id', function (d, i) { return 'input_' + _randVal + '_' + i; }); options .append('label') .attr('for', function (d, i) { return 'input_' + _randVal + '_' + i; }) .text(_chart.title()); // 'all' option if (_multiple) { _cbox .append('li') .append('input') .attr('type', 'reset') .text(_promptText) .on('click', onChange); } else { var li = _cbox.append('li'); li.append('input') .attr('type', _inputType) .attr('value', _promptValue) .attr('name', 'domain_' + _randVal) .attr('id', function (d, i) { return 'input_' + _randVal + '_all'; }) .property('checked', true); li.append('label') .attr('for', function (d, i) { return 'input_' + _randVal + '_all'; }) .text(_promptText); } _cbox .selectAll('li.' + ITEM_CSS_CLASS) .sort(_order); _cbox.on('change', onChange); return options; } function onChange (d, i) { var values, target = d3.select(d3.event.target), options; if (!target.datum()) { values = _promptValue || null; } else { options = d3.select(this).selectAll('input') .filter(function (o) { if (o) { return this.checked; } }); values = options.nodes().map(function (option) { return option.value; }); // check if only prompt option is selected if (!_multiple && values.length === 1) { values = values[0]; } } _chart.onChange(values); } _chart.onChange = function (val) { if (val && _multiple) { _chart.replaceFilter([val]); } else if (val) { _chart.replaceFilter(val); } else { _chart.filterAll(); } dc.events.trigger(function () { _chart.redrawGroup(); }); }; /** * Get or set the function that controls the ordering of option tags in the * cbox menu. By default options are ordered by the group key in ascending * order. * @method order * @memberof dc.cboxMenu * @instance * @param {Function} [order] * @returns {Function|dc.cboxMenu} * @example * // order by the group's value * chart.order(function (a,b) { * return a.value > b.value ? 1 : b.value > a.value ? -1 : 0; * }); **/ _chart.order = function (order) { if (!arguments.length) { return _order; } _order = order; return _chart; }; /** * Get or set the text displayed in the options used to prompt selection. * @method promptText * @memberof dc.cboxMenu * @instance * @param {String} [promptText='Select all'] * @returns {String|dc.cboxMenu} * @example * chart.promptText('All states'); **/ _chart.promptText = function (promptText) { if (!arguments.length) { return _promptText; } _promptText = promptText; return _chart; }; /** * Get or set the function that filters options prior to display. By default options * with a value of < 1 are not displayed. * @method filterDisplayed * @memberof dc.cboxMenu * @instance * @param {function} [filterDisplayed] * @returns {Function|dc.cboxMenu} * @example * // display all options override the `filterDisplayed` function: * chart.filterDisplayed(function () { * return true; * }); **/ _chart.filterDisplayed = function (filterDisplayed) { if (!arguments.length) { return _filterDisplayed; } _filterDisplayed = filterDisplayed; return _chart; }; /** * Controls the type of input element. Setting it to true converts * the HTML `input` tags from radio buttons to checkboxes. * @method multiple * @memberof dc.cboxMenu * @instance * @param {boolean} [multiple=false] * @returns {Boolean|dc.cboxMenu} * @example * chart.multiple(true); **/ _chart.multiple = function (multiple) { if (!arguments.length) { return _multiple; } _multiple = multiple; if (_multiple) { _inputType = 'checkbox'; } else { _inputType = 'radio'; } return _chart; }; /** * Controls the default value to be used for * [dimension.filter](https://github.com/crossfilter/crossfilter/wiki/API-Reference#dimension_filter) * when only the prompt value is selected. If `null` (the default), no filtering will occur when * just the prompt is selected. * @method promptValue * @memberof dc.cboxMenu * @instance * @param {?*} [promptValue=null] * @returns {*|dc.cboxMenu} **/ _chart.promptValue = function (promptValue) { if (!arguments.length) { return _promptValue; } _promptValue = promptValue; return _chart; }; return _chart.anchor(parent, chartGroup); };