mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
factor computePreservations into a class
This commit is contained in:
@@ -263,6 +263,121 @@ Spark.render = function (htmlFunc) {
|
||||
return frag;
|
||||
};
|
||||
|
||||
|
||||
// Find all of all nodes and regions that should be preserved in
|
||||
// patching. Return a list of objects. There are two kinds of objects
|
||||
// in the list:
|
||||
//
|
||||
// A preserved node:
|
||||
// {type: "node", from: Node, to: Node}
|
||||
//
|
||||
// A preserved (constant) region:
|
||||
// {type: "region", fromStart: Node, fromEnd: Node,
|
||||
// newRange: LiveRange}
|
||||
//
|
||||
// `existingRange` is the range in the document whose contents are to
|
||||
// be replaced. `newRange` holds the new contents and is not part of
|
||||
// the document DOM tree. The implementation will temporarily reparent
|
||||
// the nodes in `newRange` into the document to check for selector matches.
|
||||
var PreservationController = function () {
|
||||
this.roots = []; // keys 'landmark', 'fromRange', 'toRange'
|
||||
this.regionPreservations = [];
|
||||
};
|
||||
|
||||
_.extend(PreservationController.prototype, {
|
||||
addRoot: function (context, preserve, fromRange, toRange) {
|
||||
var self = this;
|
||||
self.roots.push({ context: context, preserve: preserve,
|
||||
fromRange: fromRange, toRange: toRange});
|
||||
},
|
||||
addAllRoots: function (existingRange, newRange) {
|
||||
var self = this;
|
||||
// Consider landmarks that got rerendered and were matched
|
||||
visitMatchingLandmarks(existingRange, newRange, function (from, to) {
|
||||
self.addRoot(from.containerNode(), to.preserve, from, to);
|
||||
});
|
||||
|
||||
// Consider landmarks enclosing the updated region
|
||||
var walk = existingRange;
|
||||
while ((walk = findParentOfType(Spark._ANNOTATION_LANDMARK, walk)))
|
||||
self.addRoot(walk.containerNode(), walk.preserve,
|
||||
existingRange, newRange);
|
||||
},
|
||||
addConstantRegions: function (existingRange, newRange) {
|
||||
var self = this;
|
||||
visitMatchingLandmarks(existingRange, newRange, function (from, to) {
|
||||
if (to.constant)
|
||||
self.regionPreservations.push({
|
||||
type: "region",
|
||||
fromStart: from.firstNode(), fromEnd: from.lastNode(),
|
||||
newRange: to
|
||||
});
|
||||
});
|
||||
},
|
||||
computePreservations: function (existingRange, newRange) {
|
||||
var self = this;
|
||||
var preservations = _.clone(self.regionPreservations);
|
||||
|
||||
var visitLabeledNodes = function (context, clipRange, nodeLabeler, selector, func) {
|
||||
var nodes = DomUtils.findAllClipped(
|
||||
context, selector, clipRange.firstNode(), clipRange.lastNode());
|
||||
|
||||
_.each(nodes, function (n) {
|
||||
var label = nodeLabeler(n);
|
||||
label && func(n, label);
|
||||
});
|
||||
};
|
||||
|
||||
// Find the old incarnation of each of the preserved nodes
|
||||
_.each(self.roots, function (root) {
|
||||
root.fromNodesByLabel = {};
|
||||
_.each(root.preserve, function (nodeLabeler, selector) {
|
||||
root.fromNodesByLabel[selector] = {};
|
||||
visitLabeledNodes(root.context, root.fromRange, nodeLabeler, selector, function (n, label) {
|
||||
root.fromNodesByLabel[selector][label] = n;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Temporarily put newRange into the document so that we can do
|
||||
// properly contextualized selector queries against it.
|
||||
//
|
||||
// Create a temporary range around newRange, and also around any enclosing
|
||||
// ranges that happen to also start and end on those nodes. It is ok
|
||||
// to temporarily put these in the document as well, because CSS selectors
|
||||
// don't care and we will put them back. `tempRange` will hold our place
|
||||
// in the tree `newRange` came from.
|
||||
var tempRange = new LiveRange(Spark._TAG, newRange.firstNode(), newRange.lastNode());
|
||||
var commentFrag = document.createDocumentFragment();
|
||||
commentFrag.appendChild(document.createComment(""));
|
||||
var newRangeFrag = tempRange.replace_contents(commentFrag);
|
||||
// `wrapperRange` will mark where we inserted newRange into the document.
|
||||
var wrapperRange = new LiveRange(Spark._TAG, newRangeFrag);
|
||||
existingRange.insert_before(newRangeFrag);
|
||||
|
||||
_.each(self.roots, function (root) {
|
||||
_.each(root.preserve, function (nodeLabeler, selector) {
|
||||
visitLabeledNodes(root.context, root.toRange, nodeLabeler, selector, function (n, label) {
|
||||
var match = root.fromNodesByLabel[selector][label];
|
||||
if (match) {
|
||||
preservations.push({ type: "node", from: match, to: n });
|
||||
root.fromNodesByLabel[selector][label] = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Extraction is legal because we're just taking the document
|
||||
// back to the state it was in before insertBefore.
|
||||
var extractedFrag = wrapperRange.extract();
|
||||
wrapperRange.destroy();
|
||||
tempRange.replace_contents(extractedFrag);
|
||||
tempRange.destroy();
|
||||
|
||||
return preservations;
|
||||
}
|
||||
});
|
||||
|
||||
// Modify `range` so that it matches the result of
|
||||
// Spark.render(htmlFunc). `range` must be in `document` (that is,
|
||||
// onscreen.) If the old contents had any landmarks that match
|
||||
@@ -295,7 +410,11 @@ Spark.renderToRange = function (range, htmlFunc) {
|
||||
});
|
||||
|
||||
// compute preservations
|
||||
var preservations = computePreservations(range, tempRange);
|
||||
var pc = new PreservationController;
|
||||
pc.addAllRoots(range, tempRange);
|
||||
pc.addConstantRegions(range, tempRange);
|
||||
var preservations = pc.computePreservations(range, tempRange);
|
||||
|
||||
tempRange.destroy();
|
||||
|
||||
// patch (using preservations)
|
||||
@@ -732,107 +851,6 @@ var visitMatchingLandmarks = function (range1, range2, func) {
|
||||
});
|
||||
};
|
||||
|
||||
// Find all of all nodes and regions that should be preserved in
|
||||
// patching. Return a list of objects. There are two kinds of objects
|
||||
// in the list:
|
||||
//
|
||||
// A preserved node:
|
||||
// {type: "node", from: Node, to: Node}
|
||||
//
|
||||
// A preserved (constant) region:
|
||||
// {type: "region", fromStart: Node, fromEnd: Node,
|
||||
// newRange: LiveRange}
|
||||
//
|
||||
// `existingRange` is the range in the document whose contents are to
|
||||
// be replaced. `newRange` holds the new contents and is not part of
|
||||
// the document DOM tree. The implementation will temporarily reparent
|
||||
// the nodes in `newRange` into the document to check for selector matches.
|
||||
var computePreservations = function (existingRange, newRange) {
|
||||
var preservations = [];
|
||||
|
||||
var nodePreserveRoots = []; // keys 'landmark', 'fromRange', 'toRange'
|
||||
|
||||
// Consider landmarks that got rerendered and were matched
|
||||
visitMatchingLandmarks(existingRange, newRange, function (from, to) {
|
||||
if (to.constant)
|
||||
preservations.push({
|
||||
type: "region",
|
||||
fromStart: from.firstNode(), fromEnd: from.lastNode(),
|
||||
newRange: to
|
||||
});
|
||||
|
||||
nodePreserveRoots.push({ context: from.containerNode(), preserve: to.preserve,
|
||||
fromRange: from, toRange: to });
|
||||
});
|
||||
|
||||
// Consider landmarks enclosing the updated region
|
||||
var walk = existingRange;
|
||||
while ((walk = findParentOfType(Spark._ANNOTATION_LANDMARK, walk)))
|
||||
nodePreserveRoots.push({ context: walk.containerNode(), preserve: walk.preserve,
|
||||
fromRange: existingRange, toRange: newRange});
|
||||
|
||||
var visitLabeledNodes = function (context, clipRange, nodeLabeler, selector, func) {
|
||||
var nodes = DomUtils.findAllClipped(
|
||||
context, selector, clipRange.firstNode(), clipRange.lastNode());
|
||||
|
||||
_.each(nodes, function (n) {
|
||||
var label = nodeLabeler(n);
|
||||
label && func(n, label);
|
||||
});
|
||||
};
|
||||
|
||||
// Find the old incarnation of each of the preserved nodes
|
||||
_.each(nodePreserveRoots, function (root) {
|
||||
root.fromNodesByLabel = {};
|
||||
_.each(root.preserve, function (nodeLabeler, selector) {
|
||||
root.fromNodesByLabel[selector] = {};
|
||||
visitLabeledNodes(root.context, root.fromRange, nodeLabeler, selector, function (n, label) {
|
||||
root.fromNodesByLabel[selector][label] = n;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Temporarily put newRange into the document so that we can do
|
||||
// properly contextualized selector queries against it.
|
||||
//
|
||||
// Create a temporary range around newRange, and also around any enclosing
|
||||
// ranges that happen to also start and end on those nodes. It is ok
|
||||
// to temporarily put these in the document as well, because CSS selectors
|
||||
// don't care and we will put them back. `tempRange` will hold our place
|
||||
// in the tree `newRange` came from.
|
||||
var tempRange = new LiveRange(Spark._TAG, newRange.firstNode(), newRange.lastNode());
|
||||
var commentFrag = document.createDocumentFragment();
|
||||
commentFrag.appendChild(document.createComment(""));
|
||||
var newRangeFrag = tempRange.replace_contents(commentFrag);
|
||||
// `wrapperRange` will mark where we inserted newRange into the document.
|
||||
var wrapperRange = new LiveRange(Spark._TAG, newRangeFrag);
|
||||
existingRange.insert_before(newRangeFrag);
|
||||
|
||||
|
||||
_.each(nodePreserveRoots, function (root) {
|
||||
_.each(root.preserve, function (nodeLabeler, selector) {
|
||||
visitLabeledNodes(root.context, root.toRange, nodeLabeler, selector, function (n, label) {
|
||||
var match = root.fromNodesByLabel[selector][label];
|
||||
if (match) {
|
||||
preservations.push({ type: "node", from: match, to: n });
|
||||
root.fromNodesByLabel[selector][label] = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Extraction is legal because we're just taking the document
|
||||
// back to the state it was in before insertBefore.
|
||||
var extractedFrag = wrapperRange.extract();
|
||||
wrapperRange.destroy();
|
||||
tempRange.replace_contents(extractedFrag);
|
||||
tempRange.destroy();
|
||||
|
||||
return preservations;
|
||||
};
|
||||
|
||||
// Find all the landmarks in `range` and let them know that they are
|
||||
// now onscreen. If it's their first time being onscreen, they need to
|
||||
// have their `create` callback called. And they need `render` whether
|
||||
|
||||
Reference in New Issue
Block a user