Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
81165 views
1
/**
2
* Copyright 2013-2014, Facebook, Inc.
3
* All rights reserved.
4
*
5
* This source code is licensed under the BSD-style license found in the
6
* LICENSE file in the root directory of this source tree. An additional grant
7
* of patent rights can be found in the PATENTS file in the same directory.
8
*
9
* @providesModule ReactDOMSelect
10
*/
11
12
"use strict";
13
14
var AutoFocusMixin = require('AutoFocusMixin');
15
var LinkedValueUtils = require('LinkedValueUtils');
16
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
17
var ReactCompositeComponent = require('ReactCompositeComponent');
18
var ReactElement = require('ReactElement');
19
var ReactDOM = require('ReactDOM');
20
var ReactUpdates = require('ReactUpdates');
21
22
var assign = require('Object.assign');
23
24
// Store a reference to the <select> `ReactDOMComponent`. TODO: use string
25
var select = ReactElement.createFactory(ReactDOM.select.type);
26
27
function updateWithPendingValueIfMounted() {
28
/*jshint validthis:true */
29
if (this.isMounted()) {
30
this.setState({value: this._pendingValue});
31
this._pendingValue = 0;
32
}
33
}
34
35
/**
36
* Validation function for `value` and `defaultValue`.
37
* @private
38
*/
39
function selectValueType(props, propName, componentName) {
40
if (props[propName] == null) {
41
return;
42
}
43
if (props.multiple) {
44
if (!Array.isArray(props[propName])) {
45
return new Error(
46
`The \`${propName}\` prop supplied to <select> must be an array if ` +
47
`\`multiple\` is true.`
48
);
49
}
50
} else {
51
if (Array.isArray(props[propName])) {
52
return new Error(
53
`The \`${propName}\` prop supplied to <select> must be a scalar ` +
54
`value if \`multiple\` is false.`
55
);
56
}
57
}
58
}
59
60
/**
61
* If `value` is supplied, updates <option> elements on mount and update.
62
* @param {ReactComponent} component Instance of ReactDOMSelect
63
* @param {?*} propValue For uncontrolled components, null/undefined. For
64
* controlled components, a string (or with `multiple`, a list of strings).
65
* @private
66
*/
67
function updateOptions(component, propValue) {
68
var multiple = component.props.multiple;
69
var value = propValue != null ? propValue : component.state.value;
70
var options = component.getDOMNode().options;
71
var selectedValue, i, l;
72
if (multiple) {
73
selectedValue = {};
74
for (i = 0, l = value.length; i < l; ++i) {
75
selectedValue['' + value[i]] = true;
76
}
77
} else {
78
selectedValue = '' + value;
79
}
80
for (i = 0, l = options.length; i < l; i++) {
81
var selected = multiple ?
82
selectedValue.hasOwnProperty(options[i].value) :
83
options[i].value === selectedValue;
84
85
if (selected !== options[i].selected) {
86
options[i].selected = selected;
87
}
88
}
89
}
90
91
/**
92
* Implements a <select> native component that allows optionally setting the
93
* props `value` and `defaultValue`. If `multiple` is false, the prop must be a
94
* string. If `multiple` is true, the prop must be an array of strings.
95
*
96
* If `value` is not supplied (or null/undefined), user actions that change the
97
* selected option will trigger updates to the rendered options.
98
*
99
* If it is supplied (and not null/undefined), the rendered options will not
100
* update in response to user actions. Instead, the `value` prop must change in
101
* order for the rendered options to update.
102
*
103
* If `defaultValue` is provided, any options with the supplied values will be
104
* selected.
105
*/
106
var ReactDOMSelect = ReactCompositeComponent.createClass({
107
displayName: 'ReactDOMSelect',
108
109
mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin],
110
111
propTypes: {
112
defaultValue: selectValueType,
113
value: selectValueType
114
},
115
116
getInitialState: function() {
117
return {value: this.props.defaultValue || (this.props.multiple ? [] : '')};
118
},
119
120
componentWillMount: function() {
121
this._pendingValue = null;
122
},
123
124
componentWillReceiveProps: function(nextProps) {
125
if (!this.props.multiple && nextProps.multiple) {
126
this.setState({value: [this.state.value]});
127
} else if (this.props.multiple && !nextProps.multiple) {
128
this.setState({value: this.state.value[0]});
129
}
130
},
131
132
render: function() {
133
// Clone `this.props` so we don't mutate the input.
134
var props = assign({}, this.props);
135
136
props.onChange = this._handleChange;
137
props.value = null;
138
139
return select(props, this.props.children);
140
},
141
142
componentDidMount: function() {
143
updateOptions(this, LinkedValueUtils.getValue(this));
144
},
145
146
componentDidUpdate: function(prevProps) {
147
var value = LinkedValueUtils.getValue(this);
148
var prevMultiple = !!prevProps.multiple;
149
var multiple = !!this.props.multiple;
150
if (value != null || prevMultiple !== multiple) {
151
updateOptions(this, value);
152
}
153
},
154
155
_handleChange: function(event) {
156
var returnValue;
157
var onChange = LinkedValueUtils.getOnChange(this);
158
if (onChange) {
159
returnValue = onChange.call(this, event);
160
}
161
162
var selectedValue;
163
if (this.props.multiple) {
164
selectedValue = [];
165
var options = event.target.options;
166
for (var i = 0, l = options.length; i < l; i++) {
167
if (options[i].selected) {
168
selectedValue.push(options[i].value);
169
}
170
}
171
} else {
172
selectedValue = event.target.value;
173
}
174
175
this._pendingValue = selectedValue;
176
ReactUpdates.asap(updateWithPendingValueIfMounted, this);
177
return returnValue;
178
}
179
180
});
181
182
module.exports = ReactDOMSelect;
183
184