react-addons
Version:
Simple packaging of react addons to avoid fiddly 'react/addons' npm module.
269 lines (236 loc) • 9.11 kB
JavaScript
/**
* Copyright 2013-2014 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule DOMProperty
* @typechecks static-only
*/
/*jslint bitwise: true */
"use strict";
var invariant = require("./invariant");
var DOMPropertyInjection = {
/**
* Mapping from normalized, camelcased property names to a configuration that
* specifies how the associated DOM property should be accessed or rendered.
*/
MUST_USE_ATTRIBUTE: 0x1,
MUST_USE_PROPERTY: 0x2,
HAS_SIDE_EFFECTS: 0x4,
HAS_BOOLEAN_VALUE: 0x8,
HAS_POSITIVE_NUMERIC_VALUE: 0x10,
/**
* Inject some specialized knowledge about the DOM. This takes a config object
* with the following properties:
*
* isCustomAttribute: function that given an attribute name will return true
* if it can be inserted into the DOM verbatim. Useful for data-* or aria-*
* attributes where it's impossible to enumerate all of the possible
* attribute names,
*
* Properties: object mapping DOM property name to one of the
* DOMPropertyInjection constants or null. If your attribute isn't in here,
* it won't get written to the DOM.
*
* DOMAttributeNames: object mapping React attribute name to the DOM
* attribute name. Attribute names not specified use the **lowercase**
* normalized name.
*
* DOMPropertyNames: similar to DOMAttributeNames but for DOM properties.
* Property names not specified use the normalized name.
*
* DOMMutationMethods: Properties that require special mutation methods. If
* `value` is undefined, the mutation method should unset the property.
*
* @param {object} domPropertyConfig the config as described above.
*/
injectDOMPropertyConfig: function(domPropertyConfig) {
var Properties = domPropertyConfig.Properties || {};
var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {};
var DOMPropertyNames = domPropertyConfig.DOMPropertyNames || {};
var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {};
if (domPropertyConfig.isCustomAttribute) {
DOMProperty._isCustomAttributeFunctions.push(
domPropertyConfig.isCustomAttribute
);
}
for (var propName in Properties) {
("production" !== process.env.NODE_ENV ? invariant(
!DOMProperty.isStandardName[propName],
'injectDOMPropertyConfig(...): You\'re trying to inject DOM property ' +
'\'%s\' which has already been injected. You may be accidentally ' +
'injecting the same DOM property config twice, or you may be ' +
'injecting two configs that have conflicting property names.',
propName
) : invariant(!DOMProperty.isStandardName[propName]));
DOMProperty.isStandardName[propName] = true;
var lowerCased = propName.toLowerCase();
DOMProperty.getPossibleStandardName[lowerCased] = propName;
var attributeName = DOMAttributeNames[propName];
if (attributeName) {
DOMProperty.getPossibleStandardName[attributeName] = propName;
}
DOMProperty.getAttributeName[propName] = attributeName || lowerCased;
DOMProperty.getPropertyName[propName] =
DOMPropertyNames[propName] || propName;
var mutationMethod = DOMMutationMethods[propName];
if (mutationMethod) {
DOMProperty.getMutationMethod[propName] = mutationMethod;
}
var propConfig = Properties[propName];
DOMProperty.mustUseAttribute[propName] =
propConfig & DOMPropertyInjection.MUST_USE_ATTRIBUTE;
DOMProperty.mustUseProperty[propName] =
propConfig & DOMPropertyInjection.MUST_USE_PROPERTY;
DOMProperty.hasSideEffects[propName] =
propConfig & DOMPropertyInjection.HAS_SIDE_EFFECTS;
DOMProperty.hasBooleanValue[propName] =
propConfig & DOMPropertyInjection.HAS_BOOLEAN_VALUE;
DOMProperty.hasPositiveNumericValue[propName] =
propConfig & DOMPropertyInjection.HAS_POSITIVE_NUMERIC_VALUE;
("production" !== process.env.NODE_ENV ? invariant(
!DOMProperty.mustUseAttribute[propName] ||
!DOMProperty.mustUseProperty[propName],
'DOMProperty: Cannot require using both attribute and property: %s',
propName
) : invariant(!DOMProperty.mustUseAttribute[propName] ||
!DOMProperty.mustUseProperty[propName]));
("production" !== process.env.NODE_ENV ? invariant(
DOMProperty.mustUseProperty[propName] ||
!DOMProperty.hasSideEffects[propName],
'DOMProperty: Properties that have side effects must use property: %s',
propName
) : invariant(DOMProperty.mustUseProperty[propName] ||
!DOMProperty.hasSideEffects[propName]));
("production" !== process.env.NODE_ENV ? invariant(
!DOMProperty.hasBooleanValue[propName] ||
!DOMProperty.hasPositiveNumericValue[propName],
'DOMProperty: Cannot have both boolean and positive numeric value: %s',
propName
) : invariant(!DOMProperty.hasBooleanValue[propName] ||
!DOMProperty.hasPositiveNumericValue[propName]));
}
}
};
var defaultValueCache = {};
/**
* DOMProperty exports lookup objects that can be used like functions:
*
* > DOMProperty.isValid['id']
* true
* > DOMProperty.isValid['foobar']
* undefined
*
* Although this may be confusing, it performs better in general.
*
* @see http://jsperf.com/key-exists
* @see http://jsperf.com/key-missing
*/
var DOMProperty = {
ID_ATTRIBUTE_NAME: 'data-reactid',
/**
* Checks whether a property name is a standard property.
* @type {Object}
*/
isStandardName: {},
/**
* Mapping from lowercase property names to the properly cased version, used
* to warn in the case of missing properties.
* @type {Object}
*/
getPossibleStandardName: {},
/**
* Mapping from normalized names to attribute names that differ. Attribute
* names are used when rendering markup or with `*Attribute()`.
* @type {Object}
*/
getAttributeName: {},
/**
* Mapping from normalized names to properties on DOM node instances.
* (This includes properties that mutate due to external factors.)
* @type {Object}
*/
getPropertyName: {},
/**
* Mapping from normalized names to mutation methods. This will only exist if
* mutation cannot be set simply by the property or `setAttribute()`.
* @type {Object}
*/
getMutationMethod: {},
/**
* Whether the property must be accessed and mutated as an object property.
* @type {Object}
*/
mustUseAttribute: {},
/**
* Whether the property must be accessed and mutated using `*Attribute()`.
* (This includes anything that fails `<propName> in <element>`.)
* @type {Object}
*/
mustUseProperty: {},
/**
* Whether or not setting a value causes side effects such as triggering
* resources to be loaded or text selection changes. We must ensure that
* the value is only set if it has changed.
* @type {Object}
*/
hasSideEffects: {},
/**
* Whether the property should be removed when set to a falsey value.
* @type {Object}
*/
hasBooleanValue: {},
/**
* Whether the property must be positive numeric or parse as a positive
* numeric and should be removed when set to a falsey value.
* @type {Object}
*/
hasPositiveNumericValue: {},
/**
* All of the isCustomAttribute() functions that have been injected.
*/
_isCustomAttributeFunctions: [],
/**
* Checks whether a property name is a custom attribute.
* @method
*/
isCustomAttribute: function(attributeName) {
return DOMProperty._isCustomAttributeFunctions.some(
function(isCustomAttributeFn) {
return isCustomAttributeFn.call(null, attributeName);
}
);
},
/**
* Returns the default property value for a DOM property (i.e., not an
* attribute). Most default values are '' or false, but not all. Worse yet,
* some (in particular, `type`) vary depending on the type of element.
*
* TODO: Is it better to grab all the possible properties when creating an
* element to avoid having to create the same element twice?
*/
getDefaultValueForProperty: function(nodeName, prop) {
var nodeDefaults = defaultValueCache[nodeName];
var testElement;
if (!nodeDefaults) {
defaultValueCache[nodeName] = nodeDefaults = {};
}
if (!(prop in nodeDefaults)) {
testElement = document.createElement(nodeName);
nodeDefaults[prop] = testElement[prop];
}
return nodeDefaults[prop];
},
injection: DOMPropertyInjection
};
module.exports = DOMProperty;