summaryrefslogtreecommitdiffstats
path: root/webapp/components/multiselect/multiselect.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'webapp/components/multiselect/multiselect.jsx')
-rw-r--r--webapp/components/multiselect/multiselect.jsx257
1 files changed, 257 insertions, 0 deletions
diff --git a/webapp/components/multiselect/multiselect.jsx b/webapp/components/multiselect/multiselect.jsx
new file mode 100644
index 000000000..a3e32dccf
--- /dev/null
+++ b/webapp/components/multiselect/multiselect.jsx
@@ -0,0 +1,257 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import MultiSelectList from './multiselect_list.jsx';
+
+import {localizeMessage} from 'utils/utils.jsx';
+import Constants from 'utils/constants.jsx';
+const KeyCodes = Constants.KeyCodes;
+
+import React from 'react';
+import ReactSelect from 'react-select';
+import {FormattedMessage} from 'react-intl';
+
+export default class MultiSelect extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.onChange = this.onChange.bind(this);
+ this.onSelect = this.onSelect.bind(this);
+ this.onAdd = this.onAdd.bind(this);
+ this.onInput = this.onInput.bind(this);
+ this.handleEnterPress = this.handleEnterPress.bind(this);
+ this.nextPage = this.nextPage.bind(this);
+ this.prevPage = this.prevPage.bind(this);
+
+ this.selected = null;
+
+ this.state = {
+ page: 0
+ };
+ }
+
+ componentDidMount() {
+ document.addEventListener('keydown', this.handleEnterPress);
+ this.refs.select.focus();
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('keydown', this.handleEnterPress);
+ }
+
+ nextPage() {
+ if (this.props.handlePageChange) {
+ this.props.handlePageChange(this.state.page + 1, this.state.page);
+ }
+ this.refs.list.setSelected(0);
+ this.setState({page: this.state.page + 1});
+ }
+
+ prevPage() {
+ if (this.state.page === 0) {
+ return;
+ }
+
+ if (this.props.handlePageChange) {
+ this.props.handlePageChange(this.state.page - 1, this.state.page);
+ }
+ this.refs.list.setSelected(0);
+ this.setState({page: this.state.page - 1});
+ }
+
+ onSelect(selected) {
+ this.selected = selected;
+ }
+
+ onAdd(value) {
+ if (this.props.maxValues && this.props.values.length >= this.props.maxValues) {
+ return;
+ }
+
+ for (let i = 0; i < this.props.values.length; i++) {
+ if (this.props.values[i].value === value.value) {
+ return;
+ }
+ }
+
+ this.props.handleAdd(value);
+ this.selected = null;
+ this.refs.select.handleInputChange({target: {value: ''}});
+ this.onInput('');
+ this.refs.select.focus();
+ }
+
+ onInput(input) {
+ if (input === '') {
+ this.refs.list.setSelected(-1);
+ } else {
+ this.refs.list.setSelected(0);
+ }
+ this.selected = null;
+
+ this.props.handleInput(input);
+ }
+
+ handleEnterPress(e) {
+ switch (e.keyCode) {
+ case KeyCodes.ENTER:
+ if (this.selected == null) {
+ this.props.handleSubmit();
+ return;
+ }
+ this.onAdd(this.selected);
+ break;
+ }
+ }
+
+ onChange(values) {
+ if (values.length < this.props.values.length) {
+ this.props.handleDelete(values);
+ }
+ }
+
+ render() {
+ const options = this.props.options;
+
+ let numRemainingText;
+ if (this.props.numRemainingText) {
+ numRemainingText = this.props.numRemainingText;
+ } else if (this.props.maxValues != null) {
+ numRemainingText = (
+ <FormattedMessage
+ id='multiselect.numRemaining'
+ defaultMessage='You can add {num, number} more. '
+ values={{
+ num: this.props.maxValues - this.props.values.length
+ }}
+ />
+ );
+ }
+
+ let optionsToDisplay = [];
+ let nextButton;
+ let previousButton;
+ let noteTextContainer;
+
+ if (this.props.noteText) {
+ noteTextContainer = (
+ <div className='multi-select__note'>
+ <div className='note__icon'><span className='fa fa-info'/></div>
+ <div>{this.props.noteText}</div>
+ </div>
+ );
+ }
+
+ if (options && options.length > this.props.perPage) {
+ const pageStart = this.state.page * this.props.perPage;
+ const pageEnd = pageStart + this.props.perPage;
+ optionsToDisplay = options.slice(pageStart, pageEnd);
+
+ if (options.length > pageEnd) {
+ nextButton = (
+ <button
+ className='btn btn-default filter-control filter-control__next'
+ onClick={this.nextPage}
+ >
+ <FormattedMessage
+ id='filtered_user_list.next'
+ defaultMessage='Next'
+ />
+ </button>
+ );
+ }
+
+ if (this.state.page > 0) {
+ previousButton = (
+ <button
+ className='btn btn-default filter-control filter-control__prev'
+ onClick={this.prevPage}
+ >
+ <FormattedMessage
+ id='filtered_user_list.prev'
+ defaultMessage='Previous'
+ />
+ </button>
+ );
+ }
+ } else {
+ optionsToDisplay = options;
+ }
+
+ return (
+ <div className='filtered-user-list'>
+ <div className='filter-row filter-row--full'>
+ <div className='multi-select__container'>
+ <ReactSelect
+ ref='select'
+ multi={true}
+ options={this.props.options}
+ joinValues={true}
+ clearable={false}
+ openOnFocus={true}
+ onInputChange={this.onInput}
+ onBlurResetsInput={false}
+ onCloseResetsInput={false}
+ onChange={this.onChange}
+ value={this.props.values}
+ valueRenderer={this.props.valueRenderer}
+ menuRenderer={() => null}
+ arrowRenderer={() => null}
+ noResultsText={null}
+ placeholder={localizeMessage('multiselect.placeholder', 'Search and add members')}
+ />
+ <button
+ className='btn btn-primary btn-sm'
+ onClick={this.props.handleSubmit}
+ >
+ <FormattedMessage
+ id='multiselect.go'
+ defaultMessage='Go'
+ />
+ </button>
+ </div>
+ <div className='multi-select__help'>
+ <div className='hidden-xs'>
+ <FormattedMessage
+ id='multiselect.instructions'
+ defaultMessage='Use up/down arrows to navigate and enter to select'
+ />
+ </div>
+ {numRemainingText}
+ {noteTextContainer}
+ </div>
+ </div>
+ <MultiSelectList
+ ref='list'
+ options={optionsToDisplay}
+ optionRenderer={this.props.optionRenderer}
+ page={this.state.page}
+ perPage={this.props.perPage}
+ onPageChange={this.props.handlePageChange}
+ onAdd={this.onAdd}
+ onSelect={this.onSelect}
+ />
+ <div className='filter-controls'>
+ {previousButton}
+ {nextButton}
+ </div>
+ </div>
+ );
+ }
+}
+
+MultiSelect.propTypes = {
+ options: React.PropTypes.arrayOf(React.PropTypes.object),
+ optionRenderer: React.PropTypes.func,
+ values: React.PropTypes.arrayOf(React.PropTypes.object),
+ valueRenderer: React.PropTypes.func,
+ handleInput: React.PropTypes.func,
+ handleDelete: React.PropTypes.func,
+ perPage: React.PropTypes.number,
+ handlePageChange: React.PropTypes.func,
+ handleAdd: React.PropTypes.func,
+ handleSubmit: React.PropTypes.func,
+ noteText: React.PropTypes.node,
+ maxValues: React.PropTypes.number,
+ numRemainingText: React.PropTypes.node
+};