react-addons
Version:
Simple packaging of react addons to avoid fiddly 'react/addons' npm module.
190 lines (175 loc) • 6.53 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 traverseAllChildren
*/
;
var ReactInstanceHandles = require("./ReactInstanceHandles");
var ReactTextComponent = require("./ReactTextComponent");
var invariant = require("./invariant");
var SEPARATOR = ReactInstanceHandles.SEPARATOR;
var SUBSEPARATOR = ':';
/**
* TODO: Test that:
* 1. `mapChildren` transforms strings and numbers into `ReactTextComponent`.
* 2. it('should fail when supplied duplicate key', function() {
* 3. That a single child and an array with one item have the same key pattern.
* });
*/
var userProvidedKeyEscaperLookup = {
'=': '=0',
'.': '=1',
':': '=2'
};
var userProvidedKeyEscapeRegex = /[=.:]/g;
function userProvidedKeyEscaper(match) {
return userProvidedKeyEscaperLookup[match];
}
/**
* Generate a key string that identifies a component within a set.
*
* @param {*} component A component that could contain a manual key.
* @param {number} index Index that is used if a manual key is not provided.
* @return {string}
*/
function getComponentKey(component, index) {
if (component && component.props && component.props.key != null) {
// Explicit key
return wrapUserProvidedKey(component.props.key);
}
// Implicit key determined by the index in the set
return index.toString(36);
}
/**
* Escape a component key so that it is safe to use in a reactid.
*
* @param {*} key Component key to be escaped.
* @return {string} An escaped string.
*/
function escapeUserProvidedKey(text) {
return ('' + text).replace(
userProvidedKeyEscapeRegex,
userProvidedKeyEscaper
);
}
/**
* Wrap a `key` value explicitly provided by the user to distinguish it from
* implicitly-generated keys generated by a component's index in its parent.
*
* @param {string} key Value of a user-provided `key` attribute
* @return {string}
*/
function wrapUserProvidedKey(key) {
return '$' + escapeUserProvidedKey(key);
}
/**
* @param {?*} children Children tree container.
* @param {!string} nameSoFar Name of the key path so far.
* @param {!number} indexSoFar Number of children encountered until this point.
* @param {!function} callback Callback to invoke with each child found.
* @param {?*} traverseContext Used to pass information throughout the traversal
* process.
* @return {!number} The number of children in this subtree.
*/
var traverseAllChildrenImpl =
function(children, nameSoFar, indexSoFar, callback, traverseContext) {
var subtreeCount = 0; // Count of children found in the current subtree.
if (Array.isArray(children)) {
for (var i = 0; i < children.length; i++) {
var child = children[i];
var nextName = (
nameSoFar +
(nameSoFar ? SUBSEPARATOR : SEPARATOR) +
getComponentKey(child, i)
);
var nextIndex = indexSoFar + subtreeCount;
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
nextIndex,
callback,
traverseContext
);
}
} else {
var type = typeof children;
var isOnlyChild = nameSoFar === '';
// If it's the only child, treat the name as if it was wrapped in an array
// so that it's consistent if the number of children grows
var storageName =
isOnlyChild ? SEPARATOR + getComponentKey(children, 0) : nameSoFar;
if (children == null || type === 'boolean') {
// All of the above are perceived as null.
callback(traverseContext, null, storageName, indexSoFar);
subtreeCount = 1;
} else if (children.mountComponentIntoNode) {
callback(traverseContext, children, storageName, indexSoFar);
subtreeCount = 1;
} else {
if (type === 'object') {
("production" !== process.env.NODE_ENV ? invariant(
!children || children.nodeType !== 1,
'traverseAllChildren(...): Encountered an invalid child; DOM ' +
'elements are not valid children of React components.'
) : invariant(!children || children.nodeType !== 1));
for (var key in children) {
if (children.hasOwnProperty(key)) {
subtreeCount += traverseAllChildrenImpl(
children[key],
(
nameSoFar + (nameSoFar ? SUBSEPARATOR : SEPARATOR) +
wrapUserProvidedKey(key) + SUBSEPARATOR +
getComponentKey(children[key], 0)
),
indexSoFar + subtreeCount,
callback,
traverseContext
);
}
}
} else if (type === 'string') {
var normalizedText = new ReactTextComponent(children);
callback(traverseContext, normalizedText, storageName, indexSoFar);
subtreeCount += 1;
} else if (type === 'number') {
var normalizedNumber = new ReactTextComponent('' + children);
callback(traverseContext, normalizedNumber, storageName, indexSoFar);
subtreeCount += 1;
}
}
}
return subtreeCount;
};
/**
* Traverses children that are typically specified as `props.children`, but
* might also be specified through attributes:
*
* - `traverseAllChildren(this.props.children, ...)`
* - `traverseAllChildren(this.props.leftPanelChildren, ...)`
*
* The `traverseContext` is an optional argument that is passed through the
* entire traversal. It can be used to store accumulations or anything else that
* the callback might find relevant.
*
* @param {?*} children Children tree object.
* @param {!function} callback To invoke upon traversing each child.
* @param {?*} traverseContext Context for traversal.
*/
function traverseAllChildren(children, callback, traverseContext) {
if (children !== null && children !== undefined) {
traverseAllChildrenImpl(children, '', 0, callback, traverseContext);
}
}
module.exports = traverseAllChildren;