Files
react/React/DOMChildrenOperations.js
T
2013-06-28 14:30:47 -07:00

147 lines
5.4 KiB
JavaScript

/**
* Copyright 2013 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 DOMChildrenOperations
*/
"use strict";
var Danger = require("./Danger");
var insertNodeAt = require("./insertNodeAt");
var keyOf = require("./keyOf");
var throwIf = require("./throwIf");
var NON_INCREASING_OPERATIONS;
if (true) {
NON_INCREASING_OPERATIONS =
'DOM child management operations must be provided in order ' +
'of increasing destination index. This is likely an issue with ' +
'the core framework.';
}
var MOVE_NODE_AT_ORIG_INDEX = keyOf({moveFrom: null});
var INSERT_MARKUP = keyOf({insertMarkup: null});
var REMOVE_AT = keyOf({removeAt: null});
/**
* In order to carry out movement of DOM nodes without knowing their IDs, we
* have to first store knowledge about nodes' original indices before beginning
* to carry out the sequence of operations. Once we begin the sequence, the DOM
* indices in future instructions are no longer valid.
*
* @param {Element} parent Parent DOM node.
* @param {Object} childOperations Description of child operations.
* @returns {Array?} Sparse array containing elements by their current index in
* the DOM.
*/
var _getNodesByOriginalIndex = function(parent, childOperations) {
var nodesByOriginalIndex; // Sparse array.
var childOperation;
var origIndex;
for (var i = 0; i < childOperations.length; i++) {
childOperation = childOperations[i];
if (MOVE_NODE_AT_ORIG_INDEX in childOperation) {
nodesByOriginalIndex = nodesByOriginalIndex || [];
origIndex = childOperation.moveFrom;
nodesByOriginalIndex[origIndex] = parent.childNodes[origIndex];
} else if (REMOVE_AT in childOperation) {
nodesByOriginalIndex = nodesByOriginalIndex || [];
origIndex = childOperation.removeAt;
nodesByOriginalIndex[origIndex] = parent.childNodes[origIndex];
}
}
return nodesByOriginalIndex;
};
/**
* Removes DOM elements from their parent, or moved.
* @param {Element} parent Parent DOM node.
* @param {Array} nodesByOriginalIndex Child nodes by their original index
* (potentially sparse.)
*/
var _removeChildrenByOriginalIndex = function(parent, nodesByOriginalIndex) {
for (var j = 0; j < nodesByOriginalIndex.length; j++) {
var nodeToRemove = nodesByOriginalIndex[j];
if (nodeToRemove) { // We used a sparse array.
parent.removeChild(nodesByOriginalIndex[j]);
}
}
};
/**
* Once all nodes that will be removed or moved - are removed from the parent
* node, we can begin the process of placing nodes into their final locations.
* We must perform all operations in the order of the final DOM index -
* otherwise, we couldn't count on the fact that an insertion at index X, will
* remain at index X. This will iterate through the child operations, adding
* content where needed, skip over removals (they've already been removed) and
* insert "moved" Elements that were previously removed. The "moved" elements
* are only temporarily removed from the parent, so that index calculations can
* be manageable and perform well in the cases that matter.
*/
var _placeNodesAtDestination =
function(parent, childOperations, nodesByOriginalIndex) {
var origNode;
var finalIndex;
var lastFinalIndex = -1;
var childOperation;
for (var k = 0; k < childOperations.length; k++) {
childOperation = childOperations[k];
if (MOVE_NODE_AT_ORIG_INDEX in childOperation) {
origNode = nodesByOriginalIndex[childOperation.moveFrom];
finalIndex = childOperation.finalIndex;
insertNodeAt(parent, origNode, finalIndex);
if (true) {
throwIf(finalIndex <= lastFinalIndex, NON_INCREASING_OPERATIONS);
lastFinalIndex = finalIndex;
}
} else if (REMOVE_AT in childOperation) {
} else if (INSERT_MARKUP in childOperation) {
finalIndex = childOperation.finalIndex;
var markup = childOperation.insertMarkup;
Danger.dangerouslyInsertMarkupAt(parent, markup, finalIndex);
if (true) {
throwIf(finalIndex <= lastFinalIndex, NON_INCREASING_OPERATIONS);
lastFinalIndex = finalIndex;
}
}
}
};
var manageChildren = function(parent, childOperations) {
var nodesByOriginalIndex = _getNodesByOriginalIndex(parent, childOperations);
if (nodesByOriginalIndex) {
_removeChildrenByOriginalIndex(parent, nodesByOriginalIndex);
}
_placeNodesAtDestination(parent, childOperations, nodesByOriginalIndex);
};
var setTextNodeValueAtIndex = function(parent, index, val) {
parent.childNodes[index].nodeValue = val;
};
/**
* Also reexport all of the dangerous functions. It helps to have all dangerous
* functions located in a single module `Danger`.
*/
var DOMChildrenOperations = {
dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup,
manageChildren: manageChildren,
setTextNodeValueAtIndex: setTextNodeValueAtIndex
};
module.exports = DOMChildrenOperations;