/** * Copyright 2013-present, 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. * */ 'use strict'; var _assign = require('object-assign'); var LinkedValueUtils = require('./LinkedValueUtils'); var ReactDOMComponentTree = require('./ReactDOMComponentTree'); var ReactUpdates = require('./ReactUpdates'); var warning = require('fbjs/lib/warning'); var didWarnValueLink = false; var didWarnValueDefaultValue = false; function updateOptionsIfPendingUpdateAndMounted() { if (this._rootNodeID && this._wrapperState.pendingUpdate) { this._wrapperState.pendingUpdate = false; var props = this._currentElement.props; var value = LinkedValueUtils.getValue(props); if (value != null) { updateOptions(this, Boolean(props.multiple), value); } } } function getDeclarationErrorAddendum(owner) { if (owner) { var name = owner.getName(); if (name) { return ' Check the render method of `' + name + '`.'; } } return ''; } var valuePropNames = ['value', 'defaultValue']; /** * Validation function for `value` and `defaultValue`. * @private */ function checkSelectPropTypes(inst, props) { var owner = inst._currentElement._owner; LinkedValueUtils.checkPropTypes('select', props, owner); if (props.valueLink !== undefined && !didWarnValueLink) { process.env.NODE_ENV !== 'production' ? warning(false, '`valueLink` prop on `select` is deprecated; set `value` and `onChange` instead.') : void 0; didWarnValueLink = true; } for (var i = 0; i < valuePropNames.length; i++) { var propName = valuePropNames[i]; if (props[propName] == null) { continue; } var isArray = Array.isArray(props[propName]); if (props.multiple && !isArray) { 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; } else if (!props.multiple && isArray) { 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; } } } /** * @param {ReactDOMComponent} inst * @param {boolean} multiple * @param {*} propValue A stringable (with `multiple`, a list of stringables). * @private */ function updateOptions(inst, multiple, propValue) { var selectedValue, i; var options = ReactDOMComponentTree.getNodeFromInstance(inst).options; if (multiple) { selectedValue = {}; for (i = 0; i < propValue.length; i++) { selectedValue['' + propValue[i]] = true; } for (i = 0; i < options.length; 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; i < options.length; i++) { if (options[i].value === selectedValue) { options[i].selected = true; return; } } if (options.length) { options[0].selected = true; } } } /** * Implements a <select> host 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 = { getHostProps: function (inst, props) { return _assign({}, props, { onChange: inst._wrapperState.onChange, value: undefined }); }, mountWrapper: function (inst, props) { if (process.env.NODE_ENV !== 'production') { checkSelectPropTypes(inst, props); } var value = LinkedValueUtils.getValue(props); inst._wrapperState = { pendingUpdate: false, initialValue: value != null ? value : props.defaultValue, listeners: null, onChange: _handleChange.bind(inst), wasMultiple: Boolean(props.multiple) }; if (props.value !== undefined && props.defaultValue !== undefined && !didWarnValueDefaultValue) { 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; didWarnValueDefaultValue = true; } }, getSelectValueContext: function (inst) { // ReactDOMOption looks at this initial value so the initial generated // markup has correct `selected` attributes return inst._wrapperState.initialValue; }, postUpdateWrapper: function (inst) { var props = inst._currentElement.props; // After the initial mount, we control selected-ness manually so don't pass // this value down inst._wrapperState.initialValue = undefined; var wasMultiple = inst._wrapperState.wasMultiple; inst._wrapperState.wasMultiple = Boolean(props.multiple); var value = LinkedValueUtils.getValue(props); if (value != null) { inst._wrapperState.pendingUpdate = false; updateOptions(inst, Boolean(props.multiple), value); } else if (wasMultiple !== Boolean(props.multiple)) { // For simplicity, reapply `defaultValue` if `multiple` is toggled. if (props.defaultValue != null) { updateOptions(inst, Boolean(props.multiple), props.defaultValue); } else { // Revert the select back to its default unselected state. updateOptions(inst, Boolean(props.multiple), props.multiple ? [] : ''); } } } }; function _handleChange(event) { var props = this._currentElement.props; var returnValue = LinkedValueUtils.executeOnChange(props, event); if (this._rootNodeID) { this._wrapperState.pendingUpdate = true; } ReactUpdates.asap(updateOptionsIfPendingUpdateAndMounted, this); return returnValue; } module.exports = ReactDOMSelect;