react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / src / core / ReactInstanceHandles.js
81152 views/**1* Copyright 2013-2014, Facebook, Inc.2* All rights reserved.3*4* This source code is licensed under the BSD-style license found in the5* LICENSE file in the root directory of this source tree. An additional grant6* of patent rights can be found in the PATENTS file in the same directory.7*8* @providesModule ReactInstanceHandles9* @typechecks static-only10*/1112"use strict";1314var ReactRootIndex = require('ReactRootIndex');1516var invariant = require('invariant');1718var SEPARATOR = '.';19var SEPARATOR_LENGTH = SEPARATOR.length;2021/**22* Maximum depth of traversals before we consider the possibility of a bad ID.23*/24var MAX_TREE_DEPTH = 100;2526/**27* Creates a DOM ID prefix to use when mounting React components.28*29* @param {number} index A unique integer30* @return {string} React root ID.31* @internal32*/33function getReactRootIDString(index) {34return SEPARATOR + index.toString(36);35}3637/**38* Checks if a character in the supplied ID is a separator or the end.39*40* @param {string} id A React DOM ID.41* @param {number} index Index of the character to check.42* @return {boolean} True if the character is a separator or end of the ID.43* @private44*/45function isBoundary(id, index) {46return id.charAt(index) === SEPARATOR || index === id.length;47}4849/**50* Checks if the supplied string is a valid React DOM ID.51*52* @param {string} id A React DOM ID, maybe.53* @return {boolean} True if the string is a valid React DOM ID.54* @private55*/56function isValidID(id) {57return id === '' || (58id.charAt(0) === SEPARATOR && id.charAt(id.length - 1) !== SEPARATOR59);60}6162/**63* Checks if the first ID is an ancestor of or equal to the second ID.64*65* @param {string} ancestorID66* @param {string} descendantID67* @return {boolean} True if `ancestorID` is an ancestor of `descendantID`.68* @internal69*/70function isAncestorIDOf(ancestorID, descendantID) {71return (72descendantID.indexOf(ancestorID) === 0 &&73isBoundary(descendantID, ancestorID.length)74);75}7677/**78* Gets the parent ID of the supplied React DOM ID, `id`.79*80* @param {string} id ID of a component.81* @return {string} ID of the parent, or an empty string.82* @private83*/84function getParentID(id) {85return id ? id.substr(0, id.lastIndexOf(SEPARATOR)) : '';86}8788/**89* Gets the next DOM ID on the tree path from the supplied `ancestorID` to the90* supplied `destinationID`. If they are equal, the ID is returned.91*92* @param {string} ancestorID ID of an ancestor node of `destinationID`.93* @param {string} destinationID ID of the destination node.94* @return {string} Next ID on the path from `ancestorID` to `destinationID`.95* @private96*/97function getNextDescendantID(ancestorID, destinationID) {98invariant(99isValidID(ancestorID) && isValidID(destinationID),100'getNextDescendantID(%s, %s): Received an invalid React DOM ID.',101ancestorID,102destinationID103);104invariant(105isAncestorIDOf(ancestorID, destinationID),106'getNextDescendantID(...): React has made an invalid assumption about ' +107'the DOM hierarchy. Expected `%s` to be an ancestor of `%s`.',108ancestorID,109destinationID110);111if (ancestorID === destinationID) {112return ancestorID;113}114// Skip over the ancestor and the immediate separator. Traverse until we hit115// another separator or we reach the end of `destinationID`.116var start = ancestorID.length + SEPARATOR_LENGTH;117for (var i = start; i < destinationID.length; i++) {118if (isBoundary(destinationID, i)) {119break;120}121}122return destinationID.substr(0, i);123}124125/**126* Gets the nearest common ancestor ID of two IDs.127*128* Using this ID scheme, the nearest common ancestor ID is the longest common129* prefix of the two IDs that immediately preceded a "marker" in both strings.130*131* @param {string} oneID132* @param {string} twoID133* @return {string} Nearest common ancestor ID, or the empty string if none.134* @private135*/136function getFirstCommonAncestorID(oneID, twoID) {137var minLength = Math.min(oneID.length, twoID.length);138if (minLength === 0) {139return '';140}141var lastCommonMarkerIndex = 0;142// Use `<=` to traverse until the "EOL" of the shorter string.143for (var i = 0; i <= minLength; i++) {144if (isBoundary(oneID, i) && isBoundary(twoID, i)) {145lastCommonMarkerIndex = i;146} else if (oneID.charAt(i) !== twoID.charAt(i)) {147break;148}149}150var longestCommonID = oneID.substr(0, lastCommonMarkerIndex);151invariant(152isValidID(longestCommonID),153'getFirstCommonAncestorID(%s, %s): Expected a valid React DOM ID: %s',154oneID,155twoID,156longestCommonID157);158return longestCommonID;159}160161/**162* Traverses the parent path between two IDs (either up or down). The IDs must163* not be the same, and there must exist a parent path between them. If the164* callback returns `false`, traversal is stopped.165*166* @param {?string} start ID at which to start traversal.167* @param {?string} stop ID at which to end traversal.168* @param {function} cb Callback to invoke each ID with.169* @param {?boolean} skipFirst Whether or not to skip the first node.170* @param {?boolean} skipLast Whether or not to skip the last node.171* @private172*/173function traverseParentPath(start, stop, cb, arg, skipFirst, skipLast) {174start = start || '';175stop = stop || '';176invariant(177start !== stop,178'traverseParentPath(...): Cannot traverse from and to the same ID, `%s`.',179start180);181var traverseUp = isAncestorIDOf(stop, start);182invariant(183traverseUp || isAncestorIDOf(start, stop),184'traverseParentPath(%s, %s, ...): Cannot traverse from two IDs that do ' +185'not have a parent path.',186start,187stop188);189// Traverse from `start` to `stop` one depth at a time.190var depth = 0;191var traverse = traverseUp ? getParentID : getNextDescendantID;192for (var id = start; /* until break */; id = traverse(id, stop)) {193var ret;194if ((!skipFirst || id !== start) && (!skipLast || id !== stop)) {195ret = cb(id, traverseUp, arg);196}197if (ret === false || id === stop) {198// Only break //after// visiting `stop`.199break;200}201invariant(202depth++ < MAX_TREE_DEPTH,203'traverseParentPath(%s, %s, ...): Detected an infinite loop while ' +204'traversing the React DOM ID tree. This may be due to malformed IDs: %s',205start, stop206);207}208}209210/**211* Manages the IDs assigned to DOM representations of React components. This212* uses a specific scheme in order to traverse the DOM efficiently (e.g. in213* order to simulate events).214*215* @internal216*/217var ReactInstanceHandles = {218219/**220* Constructs a React root ID221* @return {string} A React root ID.222*/223createReactRootID: function() {224return getReactRootIDString(ReactRootIndex.createReactRootIndex());225},226227/**228* Constructs a React ID by joining a root ID with a name.229*230* @param {string} rootID Root ID of a parent component.231* @param {string} name A component's name (as flattened children).232* @return {string} A React ID.233* @internal234*/235createReactID: function(rootID, name) {236return rootID + name;237},238239/**240* Gets the DOM ID of the React component that is the root of the tree that241* contains the React component with the supplied DOM ID.242*243* @param {string} id DOM ID of a React component.244* @return {?string} DOM ID of the React component that is the root.245* @internal246*/247getReactRootIDFromNodeID: function(id) {248if (id && id.charAt(0) === SEPARATOR && id.length > 1) {249var index = id.indexOf(SEPARATOR, 1);250return index > -1 ? id.substr(0, index) : id;251}252return null;253},254255/**256* Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that257* should would receive a `mouseEnter` or `mouseLeave` event.258*259* NOTE: Does not invoke the callback on the nearest common ancestor because260* nothing "entered" or "left" that element.261*262* @param {string} leaveID ID being left.263* @param {string} enterID ID being entered.264* @param {function} cb Callback to invoke on each entered/left ID.265* @param {*} upArg Argument to invoke the callback with on left IDs.266* @param {*} downArg Argument to invoke the callback with on entered IDs.267* @internal268*/269traverseEnterLeave: function(leaveID, enterID, cb, upArg, downArg) {270var ancestorID = getFirstCommonAncestorID(leaveID, enterID);271if (ancestorID !== leaveID) {272traverseParentPath(leaveID, ancestorID, cb, upArg, false, true);273}274if (ancestorID !== enterID) {275traverseParentPath(ancestorID, enterID, cb, downArg, true, false);276}277},278279/**280* Simulates the traversal of a two-phase, capture/bubble event dispatch.281*282* NOTE: This traversal happens on IDs without touching the DOM.283*284* @param {string} targetID ID of the target node.285* @param {function} cb Callback to invoke.286* @param {*} arg Argument to invoke the callback with.287* @internal288*/289traverseTwoPhase: function(targetID, cb, arg) {290if (targetID) {291traverseParentPath('', targetID, cb, arg, true, false);292traverseParentPath(targetID, '', cb, arg, false, true);293}294},295296/**297* Traverse a node ID, calling the supplied `cb` for each ancestor ID. For298* example, passing `.0.$row-0.1` would result in `cb` getting called299* with `.0`, `.0.$row-0`, and `.0.$row-0.1`.300*301* NOTE: This traversal happens on IDs without touching the DOM.302*303* @param {string} targetID ID of the target node.304* @param {function} cb Callback to invoke.305* @param {*} arg Argument to invoke the callback with.306* @internal307*/308traverseAncestors: function(targetID, cb, arg) {309traverseParentPath('', targetID, cb, arg, true, false);310},311312/**313* Exposed for unit testing.314* @private315*/316_getFirstCommonAncestorID: getFirstCommonAncestorID,317318/**319* Exposed for unit testing.320* @private321*/322_getNextDescendantID: getNextDescendantID,323324isAncestorIDOf: isAncestorIDOf,325326SEPARATOR: SEPARATOR327328};329330module.exports = ReactInstanceHandles;331332333