mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
move diff/patch into smartpatch
This commit is contained in:
@@ -390,89 +390,42 @@ Meteor.ui = Meteor.ui || {};
|
||||
|
||||
// Performs a replacement by determining which nodes should
|
||||
// be preserved and invoking Meteor.ui._Patcher as appropriate.
|
||||
Meteor.ui._intelligent_replace = function(old_range, new_parent) {
|
||||
Meteor.ui._intelligent_replace = function(tgtRange, srcParent) {
|
||||
|
||||
// Table-body fix: if old_range is in a table and new_parent
|
||||
// Table-body fix: if tgtRange is in a table and srcParent
|
||||
// contains a TR, wrap fragment in a TBODY on all browsers,
|
||||
// so that it will display properly in IE.
|
||||
if (old_range.containerNode().nodeName === "TABLE" &&
|
||||
_.any(new_parent.childNodes,
|
||||
if (tgtRange.containerNode().nodeName === "TABLE" &&
|
||||
_.any(srcParent.childNodes,
|
||||
function(n) { return n.nodeName === "TR"; })) {
|
||||
var tbody = document.createElement("TBODY");
|
||||
while (new_parent.firstChild)
|
||||
tbody.appendChild(new_parent.firstChild);
|
||||
new_parent.appendChild(tbody);
|
||||
while (srcParent.firstChild)
|
||||
tbody.appendChild(srcParent.firstChild);
|
||||
srcParent.appendChild(tbody);
|
||||
}
|
||||
|
||||
var each_labeled_node = function(rangeOrParent, func) {
|
||||
var visit_node = function(is_start, node) {
|
||||
if (is_start && node.nodeType === 1) {
|
||||
if (node.id) {
|
||||
func('#'+node.id, node);
|
||||
} else if (node.getAttribute("name")) {
|
||||
func(node.getAttribute("name"), node);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return false; // skip children of labeled node
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
Meteor.ui._LiveRange.visit_children(rangeOrParent, null, null,
|
||||
visit_node);
|
||||
var copyFunc = function(t, s) {
|
||||
$(t).unbind(); // XXX remove jquery events from node
|
||||
tgtRange.transplant_tag(t, s);
|
||||
};
|
||||
|
||||
var patch = function(targetRangeOrParent, sourceNode) {
|
||||
//tgtRange.replace_contents(srcParent);
|
||||
|
||||
var targetNodes = {};
|
||||
var targetNodeOrder = {};
|
||||
var targetNodeCounter = 0;
|
||||
|
||||
each_labeled_node(targetRangeOrParent, function(label, node) {
|
||||
targetNodes[label] = node;
|
||||
targetNodeOrder[label] = targetNodeCounter++;
|
||||
});
|
||||
|
||||
var patcher = new Meteor.ui._Patcher(
|
||||
targetRangeOrParent, sourceNode);
|
||||
var lastPos = -1;
|
||||
var copyFunc = function(t, s) {
|
||||
$(t).unbind(); // XXX remove jquery events from node
|
||||
old_range.transplant_tag(t, s);
|
||||
};
|
||||
each_labeled_node(sourceNode, function(label, node) {
|
||||
var tgt = targetNodes[label];
|
||||
var src = node;
|
||||
if (tgt && targetNodeOrder[label] > lastPos) {
|
||||
if (patcher.match(tgt, src, copyFunc)) {
|
||||
// match succeeded
|
||||
if (tgt.firstChild || src.firstChild)
|
||||
patch(tgt, src); // recurse
|
||||
}
|
||||
lastPos = targetNodeOrder[label];
|
||||
}
|
||||
});
|
||||
patcher.finish();
|
||||
};
|
||||
|
||||
//old_range.replace_contents(new_parent);
|
||||
|
||||
old_range.replace_contents(function(start, end) {
|
||||
var r;
|
||||
r = new Meteor.ui._LiveUIRange(start, end);
|
||||
r.destroy(true);
|
||||
r = { firstNode: function() { return start; },
|
||||
lastNode: function() { return end; } };
|
||||
tgtRange.replace_contents(function(start, end) {
|
||||
// clear all LiveRanges on target
|
||||
(new Meteor.ui._LiveUIRange(start, end)).destroy(true);
|
||||
|
||||
// remove event handlers on old nodes (which we will be patching)
|
||||
// at top level, where they are attached by $(...).delegate().
|
||||
for(var n = old_range.firstNode();
|
||||
n && n !== old_range.lastNode().nextSibling;
|
||||
for(var n = start;
|
||||
n && n !== end.nextSibling;
|
||||
n = n.nextSibling)
|
||||
$(n).unbind();
|
||||
|
||||
patch(r, new_parent);
|
||||
var patcher = new Meteor.ui._Patcher(
|
||||
start.parentNode, srcParent,
|
||||
start.previousSibling, end.nextSibling);
|
||||
patcher.diffpatch(copyFunc);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
@@ -1,25 +1,76 @@
|
||||
Meteor.ui = Meteor.ui || {};
|
||||
|
||||
|
||||
// tgtParentOrRange can be anything with firstNode() and lastNode()
|
||||
// methods; need not be a functioning LiveRange
|
||||
Meteor.ui._Patcher = function(tgtParentOrRange, srcParent) {
|
||||
if (typeof tgtParentOrRange.firstNode === "function") {
|
||||
this.beforeTgt = tgtParentOrRange.firstNode().previousSibling;
|
||||
this.afterTgt = tgtParentOrRange.lastNode().nextSibling;
|
||||
this.tgtParent = tgtParentOrRange.firstNode().parentNode;
|
||||
if (! this.tgtParent)
|
||||
throw new Error("Can't patch liverange with no parent ndoe");
|
||||
} else {
|
||||
this.tgtParent = tgtParentOrRange;
|
||||
}
|
||||
Meteor.ui._Patcher = function(tgtParent, srcParent, tgtBefore, tgtAfter) {
|
||||
this.tgtParent = tgtParent;
|
||||
this.srcParent = srcParent;
|
||||
|
||||
this.tgtBefore = tgtBefore;
|
||||
this.tgtAfter = tgtAfter;
|
||||
|
||||
this.lastKeptTgtNode = null;
|
||||
this.lastKeptSrcNode = null;
|
||||
};
|
||||
|
||||
Meteor.ui._Patcher.prototype.diffpatch = function(copyCallback) {
|
||||
var self = this;
|
||||
|
||||
var each_labeled_node = function(parent, before, after, func) {
|
||||
for(var n = before ? before.nextSibling : parent.firstChild;
|
||||
n && n !== after;
|
||||
n = n.nextSibling) {
|
||||
|
||||
if (n.nodeType === 1) {
|
||||
if (n.id) {
|
||||
func('#'+n.id, n);
|
||||
continue;
|
||||
} else if (n.getAttribute("name")) {
|
||||
func(n.getAttribute("name"), n);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// not a labeled node; recurse
|
||||
each_labeled_node(n, null, null, func);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var targetNodes = {};
|
||||
var targetNodeOrder = {};
|
||||
var targetNodeCounter = 0;
|
||||
|
||||
each_labeled_node(
|
||||
self.tgtParent, self.tgtBefore, self.tgtAfter,
|
||||
function(label, node) {
|
||||
targetNodes[label] = node;
|
||||
targetNodeOrder[label] = targetNodeCounter++;
|
||||
});
|
||||
|
||||
var lastPos = -1;
|
||||
each_labeled_node(
|
||||
self.srcParent, null, null,
|
||||
function(label, node) {
|
||||
var tgt = targetNodes[label];
|
||||
var src = node;
|
||||
if (tgt && targetNodeOrder[label] > lastPos) {
|
||||
if (self.match(tgt, src, copyCallback)) {
|
||||
// match succeeded
|
||||
if (tgt.firstChild || src.firstChild) {
|
||||
// recurse with a new Patcher!
|
||||
var patcher = new Meteor.ui._Patcher(tgt, src);
|
||||
patcher.diffpatch(copyCallback);
|
||||
}
|
||||
}
|
||||
lastPos = targetNodeOrder[label];
|
||||
}
|
||||
});
|
||||
|
||||
self.finish();
|
||||
|
||||
};
|
||||
|
||||
|
||||
// Advances the patching process up to tgtNode in the target tree,
|
||||
// and srcNode in the source tree. tgtNode will be preserved, with
|
||||
// the attributes of srcNode copied over it, in essence identifying
|
||||
@@ -171,41 +222,41 @@ Meteor.ui._Patcher.prototype.finish = function() {
|
||||
return this.match(null, null);
|
||||
};
|
||||
|
||||
// Replaces the siblings between beforeTgt and afterTgt (exclusive on both
|
||||
// sides) with the siblings between beforeSrc and afterSrc (exclusive on both
|
||||
// Replaces the siblings between tgtBefore and tgtAfter (exclusive on both
|
||||
// sides) with the siblings between srcBefore and srcAfter (exclusive on both
|
||||
// sides). Falsy values indicate start or end of siblings as appropriate.
|
||||
//
|
||||
// Precondition: beforeTgt and afterTgt have same parent; either may be falsy,
|
||||
// but not both, unless optTgtParent is provided. Same with beforeSrc/afterSrc.
|
||||
// Precondition: tgtBefore and tgtAfter have same parent; either may be falsy,
|
||||
// but not both, unless optTgtParent is provided. Same with srcBefore/srcAfter.
|
||||
Meteor.ui._Patcher.prototype._replaceNodes = function(
|
||||
beforeTgt, afterTgt, beforeSrc, afterSrc, optTgtParent, optSrcParent)
|
||||
tgtBefore, tgtAfter, srcBefore, srcAfter, optTgtParent, optSrcParent)
|
||||
{
|
||||
var tgtParent = optTgtParent || (beforeTgt || afterTgt).parentNode;
|
||||
var srcParent = optSrcParent || (beforeSrc || afterSrc).parentNode;
|
||||
var tgtParent = optTgtParent || (tgtBefore || tgtAfter).parentNode;
|
||||
var srcParent = optSrcParent || (srcBefore || srcAfter).parentNode;
|
||||
|
||||
// deal with case where top level is a range
|
||||
if (tgtParent === this.tgtParent) {
|
||||
beforeTgt = beforeTgt || this.beforeTgt;
|
||||
afterTgt = afterTgt || this.afterTgt;
|
||||
tgtBefore = tgtBefore || this.tgtBefore;
|
||||
tgtAfter = tgtAfter || this.tgtAfter;
|
||||
}
|
||||
if (srcParent === this.srcParent) {
|
||||
beforeSrc = beforeSrc || this.beforeSrc;
|
||||
afterSrc = afterSrc || this.afterSrc;
|
||||
srcBefore = srcBefore || this.srcBefore;
|
||||
srcAfter = srcAfter || this.srcAfter;
|
||||
}
|
||||
|
||||
|
||||
// remove old children
|
||||
var n;
|
||||
while ((n = beforeTgt ? beforeTgt.nextSibling : tgtParent.firstChild)
|
||||
&& n !== afterTgt) {
|
||||
while ((n = tgtBefore ? tgtBefore.nextSibling : tgtParent.firstChild)
|
||||
&& n !== tgtAfter) {
|
||||
tgtParent.removeChild(n);
|
||||
}
|
||||
|
||||
// add new children
|
||||
var m;
|
||||
while ((m = beforeSrc ? beforeSrc.nextSibling : srcParent.firstChild)
|
||||
&& m !== afterSrc) {
|
||||
tgtParent.insertBefore(m, afterTgt || null);
|
||||
while ((m = srcBefore ? srcBefore.nextSibling : srcParent.firstChild)
|
||||
&& m !== srcAfter) {
|
||||
tgtParent.insertBefore(m, tgtAfter || null);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -102,7 +102,10 @@ Tinytest.add("smartpatch - basic", function(test) {
|
||||
x = div(aaa+"<b><i>foo</i><u>bar</u></b>"+zzz);
|
||||
y = div("<b><u>bar</u><s>baz</s></b>");
|
||||
var rng = liverange(tag(y, 'u'));
|
||||
p = new Patcher(liverange(tag(x, 'b')), y);
|
||||
var tgt = liverange(tag(x, 'b'));
|
||||
p = new Patcher(tgt.containerNode(), y,
|
||||
tgt.firstNode().previousSibling,
|
||||
tgt.lastNode().nextSibling);
|
||||
var copyCallback = _.bind(rng.transplant_tag, rng);
|
||||
ret = p.match(tag(x, 'u'), tag(y, 'u'), copyCallback);
|
||||
test.isTrue(ret);
|
||||
|
||||
Reference in New Issue
Block a user