UNPKG

6.69 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2013-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 */
8
9'use strict';
10
11var _assign = require('object-assign');
12
13var LinkedValueUtils = require('./LinkedValueUtils');
14var ReactDOMComponentTree = require('./ReactDOMComponentTree');
15var ReactUpdates = require('./ReactUpdates');
16
17var warning = require('fbjs/lib/warning');
18
19var didWarnValueLink = false;
20var didWarnValueDefaultValue = false;
21
22function updateOptionsIfPendingUpdateAndMounted() {
23 if (this._rootNodeID && this._wrapperState.pendingUpdate) {
24 this._wrapperState.pendingUpdate = false;
25
26 var props = this._currentElement.props;
27 var value = LinkedValueUtils.getValue(props);
28
29 if (value != null) {
30 updateOptions(this, Boolean(props.multiple), value);
31 }
32 }
33}
34
35function getDeclarationErrorAddendum(owner) {
36 if (owner) {
37 var name = owner.getName();
38 if (name) {
39 return ' Check the render method of `' + name + '`.';
40 }
41 }
42 return '';
43}
44
45var valuePropNames = ['value', 'defaultValue'];
46
47/**
48 * Validation function for `value` and `defaultValue`.
49 * @private
50 */
51function checkSelectPropTypes(inst, props) {
52 var owner = inst._currentElement._owner;
53 LinkedValueUtils.checkPropTypes('select', props, owner);
54
55 if (props.valueLink !== undefined && !didWarnValueLink) {
56 process.env.NODE_ENV !== 'production' ? warning(false, '`valueLink` prop on `select` is deprecated; set `value` and `onChange` instead.') : void 0;
57 didWarnValueLink = true;
58 }
59
60 for (var i = 0; i < valuePropNames.length; i++) {
61 var propName = valuePropNames[i];
62 if (props[propName] == null) {
63 continue;
64 }
65 var isArray = Array.isArray(props[propName]);
66 if (props.multiple && !isArray) {
67 process.env.NODE_ENV !== 'production' ? warning(false, 'The `%s` prop supplied to <select> must be an array if ' + '`multiple` is true.%s', propName, getDeclarationErrorAddendum(owner)) : void 0;
68 } else if (!props.multiple && isArray) {
69 process.env.NODE_ENV !== 'production' ? warning(false, 'The `%s` prop supplied to <select> must be a scalar ' + 'value if `multiple` is false.%s', propName, getDeclarationErrorAddendum(owner)) : void 0;
70 }
71 }
72}
73
74/**
75 * @param {ReactDOMComponent} inst
76 * @param {boolean} multiple
77 * @param {*} propValue A stringable (with `multiple`, a list of stringables).
78 * @private
79 */
80function updateOptions(inst, multiple, propValue) {
81 var selectedValue, i;
82 var options = ReactDOMComponentTree.getNodeFromInstance(inst).options;
83
84 if (multiple) {
85 selectedValue = {};
86 for (i = 0; i < propValue.length; i++) {
87 selectedValue['' + propValue[i]] = true;
88 }
89 for (i = 0; i < options.length; i++) {
90 var selected = selectedValue.hasOwnProperty(options[i].value);
91 if (options[i].selected !== selected) {
92 options[i].selected = selected;
93 }
94 }
95 } else {
96 // Do not set `select.value` as exact behavior isn't consistent across all
97 // browsers for all cases.
98 selectedValue = '' + propValue;
99 for (i = 0; i < options.length; i++) {
100 if (options[i].value === selectedValue) {
101 options[i].selected = true;
102 return;
103 }
104 }
105 if (options.length) {
106 options[0].selected = true;
107 }
108 }
109}
110
111/**
112 * Implements a <select> host component that allows optionally setting the
113 * props `value` and `defaultValue`. If `multiple` is false, the prop must be a
114 * stringable. If `multiple` is true, the prop must be an array of stringables.
115 *
116 * If `value` is not supplied (or null/undefined), user actions that change the
117 * selected option will trigger updates to the rendered options.
118 *
119 * If it is supplied (and not null/undefined), the rendered options will not
120 * update in response to user actions. Instead, the `value` prop must change in
121 * order for the rendered options to update.
122 *
123 * If `defaultValue` is provided, any options with the supplied values will be
124 * selected.
125 */
126var ReactDOMSelect = {
127 getHostProps: function (inst, props) {
128 return _assign({}, props, {
129 onChange: inst._wrapperState.onChange,
130 value: undefined
131 });
132 },
133
134 mountWrapper: function (inst, props) {
135 if (process.env.NODE_ENV !== 'production') {
136 checkSelectPropTypes(inst, props);
137 }
138
139 var value = LinkedValueUtils.getValue(props);
140 inst._wrapperState = {
141 pendingUpdate: false,
142 initialValue: value != null ? value : props.defaultValue,
143 listeners: null,
144 onChange: _handleChange.bind(inst),
145 wasMultiple: Boolean(props.multiple)
146 };
147
148 if (props.value !== undefined && props.defaultValue !== undefined && !didWarnValueDefaultValue) {
149 process.env.NODE_ENV !== 'production' ? warning(false, 'Select elements must be either controlled or uncontrolled ' + '(specify either the value prop, or the defaultValue prop, but not ' + 'both). Decide between using a controlled or uncontrolled select ' + 'element and remove one of these props. More info: ' + 'https://fb.me/react-controlled-components') : void 0;
150 didWarnValueDefaultValue = true;
151 }
152 },
153
154 getSelectValueContext: function (inst) {
155 // ReactDOMOption looks at this initial value so the initial generated
156 // markup has correct `selected` attributes
157 return inst._wrapperState.initialValue;
158 },
159
160 postUpdateWrapper: function (inst) {
161 var props = inst._currentElement.props;
162
163 // After the initial mount, we control selected-ness manually so don't pass
164 // this value down
165 inst._wrapperState.initialValue = undefined;
166
167 var wasMultiple = inst._wrapperState.wasMultiple;
168 inst._wrapperState.wasMultiple = Boolean(props.multiple);
169
170 var value = LinkedValueUtils.getValue(props);
171 if (value != null) {
172 inst._wrapperState.pendingUpdate = false;
173 updateOptions(inst, Boolean(props.multiple), value);
174 } else if (wasMultiple !== Boolean(props.multiple)) {
175 // For simplicity, reapply `defaultValue` if `multiple` is toggled.
176 if (props.defaultValue != null) {
177 updateOptions(inst, Boolean(props.multiple), props.defaultValue);
178 } else {
179 // Revert the select back to its default unselected state.
180 updateOptions(inst, Boolean(props.multiple), props.multiple ? [] : '');
181 }
182 }
183 }
184};
185
186function _handleChange(event) {
187 var props = this._currentElement.props;
188 var returnValue = LinkedValueUtils.executeOnChange(props, event);
189
190 if (this._rootNodeID) {
191 this._wrapperState.pendingUpdate = true;
192 }
193 ReactUpdates.asap(updateOptionsIfPendingUpdateAndMounted, this);
194 return returnValue;
195}
196
197module.exports = ReactDOMSelect;
\No newline at end of file