Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 <input /> tag
noResultsText | string | placeholder displayed when there are no matching search results
onBlur | func | onBlur handler: function(event) {}
Expand Down Expand Up @@ -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.
4 changes: 3 additions & 1 deletion examples/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down Expand Up @@ -40,6 +41,7 @@ React.render(
<CustomRenderField label="Custom render options/values" />
<CustomRenderField label="Custom render options/values (multi)" multi delimiter="," />
<RemoteSelectField label="Remote Options" hint='Type anything in the remote example to asynchronously load options. Valid alternative results are "A", "AA", and "AB"' />
<MultiSelectSummaryField label="Summary Multiselect" />
</div>,
document.getElementById('example')
);
);
41 changes: 41 additions & 0 deletions examples/src/components/MultiSelectSummaryField.js
Original file line number Diff line number Diff line change
@@ -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 (
<div className="section">
<h3 className="section-heading">{this.props.label}</h3>
<Select multi={true} multiSum={true} value={this.state.value} placeholder="Select your favourite(s)" options={ops} onChange={this.handleSelectChange} />
</div>
);
}
});

module.exports = MultiSelectField;
26 changes: 19 additions & 7 deletions less/multi.less
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//
//
// Multi-Select
// ------------------------------

Expand All @@ -9,21 +9,33 @@

// 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;
border: 1px solid transparent;
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
Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -93,4 +105,4 @@
background-color: @select-item-disabled-bg;
}
}
}
}
4 changes: 2 additions & 2 deletions src/Option.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
<span>{ obj.create ? this.props.addLabelText.replace('{label}', obj.label) : renderedLabel }</span>
</div>
);
}
});

module.exports = Option;
module.exports = Option;
87 changes: 79 additions & 8 deletions src/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <input /> 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
Expand Down Expand Up @@ -74,6 +76,7 @@ var Select = React.createClass({
inputProps: {},
matchPos: 'any',
matchProp: 'any',
multiSumLimit: 3,
name: undefined,
newOptionCreator: undefined,
noResultsText: 'No results found',
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand All @@ -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,
Expand All @@ -724,6 +761,8 @@ var Select = React.createClass({
return optionResult;
}, this);



if (ops.length) {
return ops;
} else {
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -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)) {
Expand All @@ -799,6 +867,7 @@ var Select = React.createClass({
}
}


var loading = this.state.isLoading ? <span className="Select-loading" aria-hidden="true" /> : null;
var clear = this.props.clearable && this.state.value && !this.props.disabled ? <span className="Select-clear" title={this.props.multi ? this.props.clearAllText : this.props.clearValueText} aria-label={this.props.multi ? this.props.clearAllText : this.props.clearValueText} onMouseDown={this.clearValue} onTouchEnd={this.clearValue} onClick={this.clearValue} dangerouslySetInnerHTML={{ __html: '&times;' }} /> : null;

Expand All @@ -812,7 +881,9 @@ var Select = React.createClass({
};
menu = (
<div ref="selectMenuContainer" className="Select-menu-outer">
<div {...menuProps}>{this.buildMenu()}</div>
<div {...menuProps}>
{this.buildMenu()}
</div>
</div>
);
}
Expand Down