diff --git a/README.md b/README.md
index 27f19880ef..ef236c0c79 100644
--- a/README.md
+++ b/README.md
@@ -87,6 +87,7 @@ You can enable multi-value selection by setting `multi={true}`. In this mode:
* The `onChange` event provides an array of the selected options as the second argument
* The first argument to `onChange` is always a string, regardless of whether the values of the selected options are numbers or strings
* By default, only options in the `options` array can be selected. Setting `allowCreate` to true allows new options to be created if they do not already exist.
+* You can set a `multiSum={true}` if you would like to have the list of selected options summarized. There is a default option that can also be sent as a prop `multiSumLimit={number}` (default is set to 3. If the select items is less than the limit then it just prints out an ordered list, if it is more it will say x of y selected for example: "7 of 20 selected". If all items are selected it will say "All", and if all but one or two it will say "All except, {valueLabel}".
### Async options
@@ -168,6 +169,8 @@ For multi-select inputs, when providing a custom `filterOptions` method, remembe
matchPos | string | (any, start) match the start or entire string when filtering
matchProp | string | (any, label, value) which option property to filter on
multi | bool | multi-value input
+ multiSum | bool | multi-value text summary option
+ multiSumLimit | bool | number of values until it stops listing them out one by one
name | string | field name, for hidden tag
noResultsText | string | placeholder displayed when there are no matching search results
onBlur | func | onBlur handler: function(event) {}
@@ -197,4 +200,4 @@ See our [CONTRIBUTING.md](https://github.com/JedWatson/react-select/blob/master/
# License
-MIT Licensed. Copyright (c) Jed Watson 2015.
+MIT Licensed. Copyright (c) Jed Watson 2015.
\ No newline at end of file
diff --git a/examples/src/app.js b/examples/src/app.js
index cb06945ba6..dd382d42a3 100644
--- a/examples/src/app.js
+++ b/examples/src/app.js
@@ -11,6 +11,7 @@ import StatesField from './components/StatesField';
import UsersField from './components/UsersField';
import ValuesAsNumbersField from './components/ValuesAsNumbersField';
import DisabledUpsellOptions from './components/DisabledUpsellOptions';
+import MultiSelectSummaryField from './components/MultiSelectSummaryField';
var FLAVOURS = [
{ label: 'Chocolate', value: 'chocolate' },
@@ -40,6 +41,7 @@ React.render(
+
,
document.getElementById('example')
-);
+);
\ No newline at end of file
diff --git a/examples/src/components/MultiSelectSummaryField.js b/examples/src/components/MultiSelectSummaryField.js
new file mode 100644
index 0000000000..36d4ccc222
--- /dev/null
+++ b/examples/src/components/MultiSelectSummaryField.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import Select from 'react-select';
+
+function logChange() {
+ console.log.apply(console, [].concat(['Select value changed:'], Array.prototype.slice.apply(arguments)));
+}
+
+var MultiSelectField = React.createClass({
+ displayName: 'MultiSelectField',
+ propTypes: {
+ label: React.PropTypes.string,
+ },
+ getInitialState () {
+ return {
+ value: []
+ };
+ },
+ handleSelectChange (value, values) {
+ logChange('New value:', value, 'Values:', values);
+ this.setState({ value: value });
+ },
+
+ render () {
+ var ops = [
+ { label: 'Chocolate', value: 'chocolate' },
+ { label: 'Vanilla', value: 'vanilla' },
+ { label: 'Strawberry', value: 'strawberry' },
+ { label: 'Caramel', value: 'caramel' },
+ { label: 'Cookies and Cream', value: 'cookiescream' },
+ { label: 'Peppermint', value: 'peppermint' }
+ ];
+ return (
+
+
{this.props.label}
+
+
+ );
+ }
+});
+
+module.exports = MultiSelectField;
\ No newline at end of file
diff --git a/less/multi.less b/less/multi.less
index 25022b06d7..2887555ead 100644
--- a/less/multi.less
+++ b/less/multi.less
@@ -1,4 +1,4 @@
-//
+//
// Multi-Select
// ------------------------------
@@ -9,13 +9,13 @@
// control: reduce padding to allow for items
.Select-control {
- padding:
+ padding:
@select-padding-vertical - @select-item-gutter - @select-item-padding-vertical - 1
@select-padding-horizontal * 3 + 22
@select-padding-vertical - @select-item-gutter - @select-item-padding-vertical - 1
@select-padding-horizontal - @select-item-gutter - @select-item-padding-horizontal
}
-
+
// add margin to the input element
.Select-input {
vertical-align: middle;
@@ -23,7 +23,19 @@
margin: @select-item-gutter;
padding: @select-item-padding-vertical 0;
}
-
+
+ .Select-control > span:first-child {
+ padding-left: 7px;
+ vertical-align: middle;
+ }
+
+ .Select-option.is-multiSum span{
+ padding: 4px;
+ background: @select-item-bg;
+ color: @select-item-color;
+ border: 1px solid @select-item-border-color;
+ border-radius: @select-item-border-radius;
+ }
}
// Items
@@ -50,7 +62,7 @@
cursor: default;
.border-right-radius( @select-item-border-radius );
padding: @select-item-padding-vertical @select-item-padding-horizontal;
-
+
.Select-item-label__a {
color: @select-item-color;
cursor: pointer;
@@ -62,7 +74,7 @@
cursor: pointer;
.border-left-radius( @select-item-border-radius );
border-right: 1px solid @select-item-border-color;
-
+
// move the baseline up by 1px
padding: @select-item-padding-vertical - 1 @select-item-padding-horizontal @select-item-padding-vertical + 1;
@@ -93,4 +105,4 @@
background-color: @select-item-disabled-bg;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Option.js b/src/Option.js
index b786a9a1d9..15a3e55d7d 100644
--- a/src/Option.js
+++ b/src/Option.js
@@ -44,10 +44,10 @@ var Option = React.createClass({
onMouseDown={this.props.mouseDown}
onClick={this.props.mouseDown}
title={obj.title}>
- { obj.create ? this.props.addLabelText.replace('{label}', obj.label) : renderedLabel }
+ { obj.create ? this.props.addLabelText.replace('{label}', obj.label) : renderedLabel }
);
}
});
-module.exports = Option;
+module.exports = Option;
\ No newline at end of file
diff --git a/src/Select.js b/src/Select.js
index e1124f7c03..cbe782b925 100644
--- a/src/Select.js
+++ b/src/Select.js
@@ -35,6 +35,8 @@ var Select = React.createClass({
matchPos: React.PropTypes.string, // (any|start) match the start or entire string when filtering
matchProp: React.PropTypes.string, // (any|label|value) which option property to filter on
multi: React.PropTypes.bool, // multi-value input
+ multiSum: React.PropTypes.bool, // multiSum option enabler
+ multiSumLimit: React.PropTypes.number, // limit for the number of options before it summarizes to x of y
name: React.PropTypes.string, // field name, for hidden tag
newOptionCreator: React.PropTypes.func, // factory to create new options when allowCreate set
noResultsText: React.PropTypes.string, // placeholder displayed when there are no matching search results
@@ -74,6 +76,7 @@ var Select = React.createClass({
inputProps: {},
matchPos: 'any',
matchProp: 'any',
+ multiSumLimit: 3,
name: undefined,
newOptionCreator: undefined,
noResultsText: 'No results found',
@@ -269,7 +272,7 @@ var Select = React.createClass({
if (typeof values === 'string') {
values = values === ''
? []
- : this.props.multi
+ : this.props.multi
? values.split(this.props.delimiter)
: [ values ];
} else {
@@ -631,9 +634,11 @@ var Select = React.createClass({
focusAdjacentOption: function(dir) {
this._focusedOptionReveal = true;
+
var ops = this.state.filteredOptions.filter(function(op) {
return !op.disabled;
});
+
if (!this.state.isOpen) {
this.setState({
isOpen: true,
@@ -675,6 +680,7 @@ var Select = React.createClass({
}
},
+
buildMenu: function() {
var focusedValue = this.state.focusedOption ? this.state.focusedOption.value : null;
var renderLabel = this.props.optionRenderer || function(op) {
@@ -695,24 +701,55 @@ var Select = React.createClass({
};
options.unshift(newOption);
}
- var ops = Object.keys(options).map(function(key) {
- var op = options[key];
+
+ if (this.props.multi && this.props.multiSum){
+ options = options.map(function(opt){
+ opt.type = 'opt';
+ opt.isMulti = false;
+ opt.renderLabel = undefined;
+ opt.selectValue = undefined;
+ return opt;
+ });
+
+ if (this.state.values.length > 0){
+ var multiValues = this.state.values.map(function(val){
+ val.type = 'multiSum';
+ val.isMulti = true;
+ var optionRenderer = this.props.optionRenderer;
+ val.renderLabel = function(op){
+ var label = op.label;
+ if (optionRenderer){
+ label = optionRenderer(op);
+ }
+ return 'x ' + label;
+ };
+
+ val.selectValue = this.removeValue.bind(this, val);
+ return val;
+ }, this);
+
+ options = multiValues.concat(options);
+ }
+ }
+
+ var ops = options.map(function(op) {
var isSelected = this.state.value === op.value;
var isFocused = focusedValue === op.value;
var optionClass = classes({
'Select-option': true,
'is-selected': isSelected,
'is-focused': isFocused,
- 'is-disabled': op.disabled
+ 'is-disabled': op.disabled,
+ 'is-multiSum': op.isMulti
});
var ref = isFocused ? 'focused' : null;
var mouseEnter = this.focusOption.bind(this, op);
var mouseLeave = this.unfocusOption.bind(this, op);
- var mouseDown = this.selectValue.bind(this, op);
+ var mouseDown = op.selectValue || this.selectValue.bind(this, op);
var optionResult = React.createElement(this.props.optionComponent, {
- key: 'option-' + op.value,
+ key: 'option-' + op.value + '-' + op.type,
className: optionClass,
- renderFunc: renderLabel,
+ renderFunc: ( op.renderLabel || renderLabel),
mouseEnter: mouseEnter,
mouseLeave: mouseLeave,
mouseDown: mouseDown,
@@ -724,6 +761,8 @@ var Select = React.createClass({
return optionResult;
}, this);
+
+
if (ops.length) {
return ops;
} else {
@@ -753,6 +792,30 @@ var Select = React.createClass({
}
},
+ summarizeValues: function(values){
+ var summary = '';
+
+ if (values.length < this.props.multiSumLimit){
+ this.state.values.forEach( function(opt, i){
+ summary = summary + opt.label;
+ if (i < (values.length - 1) ){
+ summary = summary + ', ';
+ }
+ });
+ return summary;
+
+ } else if (values.length === this.props.options.length){
+ return 'All';
+ } else if (values.length >= (this.props.options.length - 2) ){
+ this.state.filteredOptions.forEach( function(opt){
+ summary = summary + ', ' + opt.label;
+ });
+ return 'All except' + summary;
+ }
+
+ return summary = values.length + ' of ' + this.props.options.length + ' selected';
+ },
+
render: function() {
var selectClass = classes('Select', this.props.className, {
'is-multi': this.props.multi,
@@ -765,6 +828,7 @@ var Select = React.createClass({
});
var value = [];
if (this.props.multi) {
+
this.state.values.forEach(function(val) {
var onOptionLabelClick = this.handleOptionLabelClick.bind(this, val);
var onRemove = this.removeValue.bind(this, val);
@@ -779,6 +843,10 @@ var Select = React.createClass({
});
value.push(valueComponent);
}, this);
+
+ if (this.props.multiSum && value.length > 0){
+ value = this.summarizeValues(value);
+ }
}
if (!this.state.inputValue && (!this.props.multi || !value.length)) {
@@ -799,6 +867,7 @@ var Select = React.createClass({
}
}
+
var loading = this.state.isLoading ? : null;
var clear = this.props.clearable && this.state.value && !this.props.disabled ? : null;
@@ -812,7 +881,9 @@ var Select = React.createClass({
};
menu = (