From 18b153dd037a2b638a06cd9f3736e6a6055c44fc Mon Sep 17 00:00:00 2001 From: Andrea Singh Date: Tue, 30 Dec 2014 19:56:14 -0800 Subject: [PATCH] Added multiple select functionality to new dc.selectMenu --- spec/select-menu-spec.js | 98 ++++++++++++++++++++++++++++++---------- src/select-menu.js | 68 ++++++++++++++++++++++++---- 2 files changed, 133 insertions(+), 33 deletions(-) diff --git a/spec/select-menu-spec.js b/spec/select-menu-spec.js index 317740638..8cfbee8b7 100644 --- a/spec/select-menu-spec.js +++ b/spec/select-menu-spec.js @@ -37,6 +37,13 @@ describe('dc.selectMenu', function() { it('creates select tag', function() { expect(chart.selectAll("select").length).toEqual(1); }); + it('select tag is not a multiple select by default', function() { + expect(chart.selectAll("select").attr("multiple")).toBeNull(); + }); + it('can be made into a multiple', function() { + chart.multiple(true).redraw(); + expect(chart.selectAll("select").attr("multiple")).toBeTruthy(); + }); it('creates prompt option with empty value', function() { var option = chart.selectAll("option")[0][0]; expect(option).toBeTruthy(); @@ -76,36 +83,82 @@ describe('dc.selectMenu', function() { last_option = getOption(chart,last_index); expect(last_option.text).toEqual("Mississippi: 2"); }); - }) + }); + + describe('regular single select', function() { + describe('selecting an option', function () { + it('filters dimension based on selected option\'s value', function(){ + chart.onChange(stateGroup.all()[0].key); + expect(chart.filter()).toEqual("California"); + }); + it('replaces filter on second selection', function(){ + chart.onChange(stateGroup.all()[0].key); + chart.onChange(stateGroup.all()[1].key); + expect(chart.filter()).toEqual("Colorado"); + expect(chart.filters().length).toEqual(1); + }); + it('actually filters dimension', function(){ + chart.onChange(stateGroup.all()[0].key); + expect(regionGroup.all()[0].value).toEqual(0); + expect(regionGroup.all()[3].value).toEqual(2); + }); + it('removes filter when prompt option is selected', function(){ + chart.onChange(null); + expect(chart.hasFilter()).not.toBeTruthy(); + expect(regionGroup.all()[0].value).toEqual(1); + }); + }); + + describe('redraw with existing filter', function () { + it('selects option corresponding to active filter', function(){ + chart.onChange(stateGroup.all()[0].key); + chart.redraw(); + expect(chart.selectAll("select")[0][0].value).toEqual("California"); + }); + }); + + afterEach(function () { + chart.onChange(null); + }); + }); - describe('selecting an option', function () { - it('filters dimension based on selected option\'s value', function(){ - chart.onChange(stateGroup.all()[0].key); - expect(chart.filter()).toEqual("California"); + describe('multiple select', function () { + beforeEach(function () { + chart.multiple(true); + chart.onChange([stateGroup.all()[0].key, stateGroup.all()[1].key]); }); - it('replaces filter on second selection', function(){ - chart.onChange(stateGroup.all()[0].key); - chart.onChange(stateGroup.all()[1].key); - expect(chart.filter()).toEqual("Colorado"); - expect(chart.filters().length).toEqual(1); + it('adds filters based on selections', function(){ + expect(chart.filters()).toEqual(["California", "Colorado"]); + expect(chart.filters().length).toEqual(2); }); it('actually filters dimension', function(){ - chart.onChange(stateGroup.all()[0].key); - expect(regionGroup.all()[0].value).toEqual(0); expect(regionGroup.all()[3].value).toEqual(2); + expect(regionGroup.all()[4].value).toEqual(2); }); - it('removes filter when prompt option is selected', function(){ - chart.onChange(''); + it('removes all filters when prompt option is selected', function(){ + chart.onChange(null); expect(chart.hasFilter()).not.toBeTruthy(); expect(regionGroup.all()[0].value).toEqual(1); }); - }); + it('selects all options corresponding to active filters on redraw', function(){ + var selectedOptions = chart.selectAll("select").selectAll("option")[0].filter(function(d) { + return d.selected; + }); + expect(selectedOptions.length).toEqual(2); + expect(selectedOptions.map(function(d){ return d.value; })).toEqual(["California", "Colorado"]); + }); + it('does not deselect previously filtered options when new option is added', function(){ + chart.onChange([stateGroup.all()[0].key, stateGroup.all()[1].key, stateGroup.all()[5].key]); - describe('redraw with existing filter', function () { - it('selects option corresponding to active filter', function(){ - chart.onChange(stateGroup.all()[0].key); - chart.redraw(); - expect(chart.selectAll("select")[0][0].value).toEqual("California"); + var selectedOptions = chart.selectAll("select").selectAll("option")[0].filter(function(d) { + return d.selected; + }); + expect(selectedOptions.length).toEqual(3); + expect(selectedOptions.map(function(d){ return d.value; })).toEqual(["California", "Colorado", "Ontario"]); + }); + + afterEach(function () { + chart.onChange(null); }); }); @@ -136,7 +189,4 @@ describe('dc.selectMenu', function() { function getOption(chart, i){ return chart.selectAll("option.dc-select-option")[0][i]; } - - -}); - +}); \ No newline at end of file diff --git a/src/select-menu.js b/src/select-menu.js index e39225961..b3ec4d188 100644 --- a/src/select-menu.js +++ b/src/select-menu.js @@ -2,7 +2,8 @@ ## Select Menu Includes: [Base Mixin](#base-mixin) -The select menu is a simple widget that allows filtering a dimension by selecting an option from an HTML menu. The menu can be optionally turned into a multiselect. #### dc.selectMenu(parent[, chartGroup]) Create a select menu instance and attach it to the given parent element. @@ -23,7 +24,7 @@ The select menu is a simple widget that allows filtering a dimension by selectin .dimension(states) .group(stateGroup); - // the option text can be set via the title function + // the option text can be set via the title() function // by default the option text is '`key`: `value`' select.title(function(d){ return 'STATE: ' + d.key; @@ -39,6 +40,7 @@ dc.selectMenu = function (parent, chartGroup) { var _select; var _promptText = 'Select all'; + var _multiple = false; var _order = function (a, b) { return _chart.keyAccessor()(a) > _chart.keyAccessor()(b) ? 1 : _chart.keyAccessor()(b) > _chart.keyAccessor()(a) ? @@ -55,16 +57,27 @@ dc.selectMenu = function (parent, chartGroup) { _chart._doRender = function () { _chart.select('select').remove(); - _select = _chart.root().append('select').classed(SELECT_CSS_CLASS, true); + _select = _chart.root().append('select') + .classed(SELECT_CSS_CLASS, true); + + switchMultipleSelectOption(); + _select.append('option').text(_promptText).attr('value', ''); renderOptions(); return _chart; }; _chart._doRedraw = function () { + switchMultipleSelectOption(); renderOptions(); - // select the option that corresponds to current filter - if (_chart.hasFilter()) { + // select the option(s) corresponding to current filter(s) + if (_chart.hasFilter() && _multiple) { + _select.selectAll('option') + .filter(function (d) { + return d && _chart.filters().indexOf(String(_chart.keyAccessor()(d))) >= 0; + }) + .property('selected', true); + } else if (_chart.hasFilter()) { _select.property('value', _chart.filter()); } else { _select.property('value', ''); @@ -89,12 +102,24 @@ dc.selectMenu = function (parent, chartGroup) { return options; } - function onChange () { - _chart.onChange(this.value); + function onChange (d , i) { + var selectedOptions = Array.prototype.slice.call(d3.event.target.selectedOptions); + var values = selectedOptions.map(function (d) { + return d.value; + }); + // check if only prompt option is selected + if (values.length === 1 && values[0] === '') { + values = null; + } else if (values.length === 1) { + values = values[0]; + } + _chart.onChange(values); } _chart.onChange = function (val) { - if (val) { + if (val && _multiple) { + _chart.replaceFilter([val]); + } else if (val) { _chart.replaceFilter(val); } else { _chart.filterAll(); @@ -104,6 +129,14 @@ dc.selectMenu = function (parent, chartGroup) { }); }; + function switchMultipleSelectOption () { + if (_multiple) { + _select.attr('multiple', true); + } else { + _select.attr('multiple', null); + } + } + /** #### .order([function]) Get or set the function that controls the ordering of option tags in the @@ -159,5 +192,22 @@ dc.selectMenu = function (parent, chartGroup) { return _chart; }; + /** + #### .multiple([bool]) + Controls the type of select menu (single select is default). Setting it to true converts the underlying + HTML tag into a multiple select. + ``` + chart.multiple(true); + ``` + **/ + _chart.multiple = function (_) { + if (!arguments.length) { + return _multiple; + } + _multiple = _; + + return _chart; + }; + return _chart.anchor(parent, chartGroup); -}; +}; \ No newline at end of file