factor computePreservations into a class

This commit is contained in:
Geoff Schmidt
2012-08-04 10:06:39 -07:00
parent a40d5b93db
commit c298d031a0

View File

@@ -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