/** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ReactDOMSelect */ 'use strict'; var AutoFocusMixin = require("./AutoFocusMixin"); var LinkedValueUtils = require("./LinkedValueUtils"); var ReactBrowserComponentMixin = require("./ReactBrowserComponentMixin"); var ReactClass = require("./ReactClass"); var ReactElement = require("./ReactElement"); var ReactUpdates = require("./ReactUpdates"); var assign = require("./Object.assign"); var select = ReactElement.createFactory('select'); function updateOptionsIfPendingUpdateAndMounted() { /*jshint validthis:true */ if (this._pendingUpdate) { this._pendingUpdate = false; var value = LinkedValueUtils.getValue(this); if (value != null && this.isMounted()) { updateOptions(this, value); } } } /** * Validation function for `value` and `defaultValue`. * @private */ function selectValueType(props, propName, componentName) { if (props[propName] == null) { return null; } if (props.multiple) { if (!Array.isArray(props[propName])) { return new Error( ("The `" + propName + "` prop supplied to <select> must be an array if ") + ("`multiple` is true.") ); } } else { if (Array.isArray(props[propName])) { return new Error( ("The `" + propName + "` prop supplied to <select> must be a scalar ") + ("value if `multiple` is false.") ); } } } /** * @param {ReactComponent} component Instance of ReactDOMSelect * @param {*} propValue A stringable (with `multiple`, a list of stringables). * @private */ function updateOptions(component, propValue) { var selectedValue, i, l; var options = component.getDOMNode().options; if (component.props.multiple) { selectedValue = {}; for (i = 0, l = propValue.length; i < l; i++) { selectedValue['' + propValue[i]] = true; } for (i = 0, l = options.length; i < l; i++) { var selected = selectedValue.hasOwnProperty(options[i].value); if (options[i].selected !== selected) { options[i].selected = selected; } } } else { // Do not set `select.value` as exact behavior isn't consistent across all // browsers for all cases. selectedValue = '' + propValue; for (i = 0, l = options.length; i < l; i++) { if (options[i].value === selectedValue) { options[i].selected = true; return; } } if (options.length) { options[0].selected = true; } } } /** * Implements a <select> native component that allows optionally setting the * props `value` and `defaultValue`. If `multiple` is false, the prop must be a * stringable. If `multiple` is true, the prop must be an array of stringables. * * If `value` is not supplied (or null/undefined), user actions that change the * selected option will trigger updates to the rendered options. * * If it is supplied (and not null/undefined), the rendered options will not * update in response to user actions. Instead, the `value` prop must change in * order for the rendered options to update. * * If `defaultValue` is provided, any options with the supplied values will be * selected. */ var ReactDOMSelect = ReactClass.createClass({ displayName: 'ReactDOMSelect', tagName: 'SELECT', mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin], propTypes: { defaultValue: selectValueType, value: selectValueType }, render: function() { // Clone `this.props` so we don't mutate the input. var props = assign({}, this.props); props.onChange = this._handleChange; props.value = null; return select(props, this.props.children); }, componentWillMount: function() { this._pendingUpdate = false; }, componentDidMount: function() { var value = LinkedValueUtils.getValue(this); if (value != null) { updateOptions(this, value); } else if (this.props.defaultValue != null) { updateOptions(this, this.props.defaultValue); } }, componentDidUpdate: function(prevProps) { var value = LinkedValueUtils.getValue(this); if (value != null) { this._pendingUpdate = false; updateOptions(this, value); } else if (!prevProps.multiple !== !this.props.multiple) { // For simplicity, reapply `defaultValue` if `multiple` is toggled. if (this.props.defaultValue != null) { updateOptions(this, this.props.defaultValue); } else { // Revert the select back to its default unselected state. updateOptions(this, this.props.multiple ? [] : ''); } } }, _handleChange: function(event) { var returnValue; var onChange = LinkedValueUtils.getOnChange(this); if (onChange) { returnValue = onChange.call(this, event); } this._pendingUpdate = true; ReactUpdates.asap(updateOptionsIfPendingUpdateAndMounted, this); return returnValue; } }); module.exports = ReactDOMSelect;