Meteor.ui._LiveRange -> LiveRange

This commit is contained in:
David Greenspan
2012-07-27 13:40:33 -07:00
parent 492bf11fcf
commit 2f4e1d0586
7 changed files with 57 additions and 63 deletions

View File

@@ -1,12 +1,6 @@
// Stand back, I'm going to try SCIENCE.
Meteor.ui = Meteor.ui || {};
(function () {
// XXX we should eventually move LiveRange off into its own
// package. but it would be also be nice to keep it as a single,
// self-contained file to make it easier to use outside of Meteor.
// Possible optimization: get rid of start_idx/end_idx and just search
// the list. Not clear which strategy will be faster.
@@ -25,7 +19,7 @@ Meteor.ui = Meteor.ui || {};
return (test_elt.test === 123);
})();
Meteor.ui._wrap_endpoints = function (start, end) {
var wrapEndpoints = function (start, end) {
if (canSetTextProps) {
return [start, end];
} else {
@@ -47,7 +41,7 @@ Meteor.ui = Meteor.ui || {};
};
// This is a constructor (invoke it as 'new Meteor.ui._LiveRange').
// This is a constructor (invoke it as 'new LiveRange').
//
// Create a range, tagged 'tag', that includes start, end, and all
// the nodes between them, and the children of all of those nodes,
@@ -61,7 +55,7 @@ Meteor.ui = Meteor.ui || {};
// other ranges is uniquely determined.
//
// To track the range as it's relocated, some of the DOM nodes that
// are part of the range will have an expando attribute set on
// are part of the range will have an expando attribute 0Aset on
// them. The name of the expando attribute will be 'tag', so pick
// something that won't collide.
//
@@ -81,7 +75,7 @@ Meteor.ui = Meteor.ui || {};
// and end are the first and last child of their parent respectively
// or when caller is building up the range tree from the inside
// out. Let's wait for the profiler to tell us to add this.
Meteor.ui._LiveRange = function (tag, start, end, inner) {
LiveRange = function (tag, start, end, inner) {
if (start.nodeType === 11 /* DocumentFragment */) {
end = start.lastChild;
start = start.firstChild;
@@ -93,7 +87,7 @@ Meteor.ui = Meteor.ui || {};
this.tag = tag; // must be set before calling _ensure_tag
var endpoints = Meteor.ui._wrap_endpoints(start, end);
var endpoints = wrapEndpoints(start, end);
start = this._ensure_tag(endpoints[0]);
end = this._ensure_tag(endpoints[1]);
@@ -179,7 +173,7 @@ Meteor.ui = Meteor.ui || {};
return index;
};
Meteor.ui._LiveRange.prototype._ensure_tag = function (node) {
LiveRange.prototype._ensure_tag = function (node) {
if (!(this.tag in node))
node[this.tag] = [[], []];
return node;
@@ -199,7 +193,7 @@ Meteor.ui = Meteor.ui || {};
return result;
})();
Meteor.ui._LiveRange._clean_node = function (tag, node, force) {
LiveRange._clean_node = function (tag, node, force) {
var data = node[tag];
if (data && (!(data[0].length + data[1].length) || force)) {
if (can_delete_expandos)
@@ -220,7 +214,7 @@ Meteor.ui = Meteor.ui || {};
// through the DOM.
//
// Pass true for `recursive` to also destroy all descendent ranges.
Meteor.ui._LiveRange.prototype.destroy = function (recursive) {
LiveRange.prototype.destroy = function (recursive) {
var self = this;
if (recursive) {
@@ -236,7 +230,7 @@ Meteor.ui = Meteor.ui || {};
if (! is_start) {
// when leaving a node, force-clean its children
for(var n = node.firstChild; n; n = n.nextSibling) {
Meteor.ui._LiveRange._clean_node(self.tag, n, true);
LiveRange._clean_node(self.tag, n, true);
}
}
});
@@ -249,7 +243,7 @@ Meteor.ui = Meteor.ui || {};
for(var n = this._start.nextSibling;
n !== this._end;
n = n.nextSibling) {
Meteor.ui._LiveRange._clean_node(self.tag, n, true);
LiveRange._clean_node(self.tag, n, true);
}
// clean ends on this._start and starts on this._end
@@ -269,18 +263,18 @@ Meteor.ui = Meteor.ui || {};
};
// Return the first node in the range (in preorder traversal)
Meteor.ui._LiveRange.prototype.firstNode = function () {
LiveRange.prototype.firstNode = function () {
return this._start;
};
// Return the last node in the range (in postorder traversal)
Meteor.ui._LiveRange.prototype.lastNode = function () {
LiveRange.prototype.lastNode = function () {
return this._end;
};
// Return the node that immediately contains this LiveRange, that is,
// the parentNode of firstNode and lastNode.
Meteor.ui._LiveRange.prototype.containerNode = function() {
LiveRange.prototype.containerNode = function() {
return this._start.parentNode;
};
@@ -301,7 +295,7 @@ Meteor.ui = Meteor.ui || {};
//
// If you create or destroy ranges with this tag from a visitation
// function, results are undefined!
Meteor.ui._LiveRange.prototype.visit = function(visit_range, visit_node) {
LiveRange.prototype.visit = function(visit_range, visit_node) {
visit_range = visit_range || function() {};
visit_node = visit_node || function() {};
@@ -338,7 +332,7 @@ Meteor.ui = Meteor.ui || {};
};
// startEnd === 0 for starts, 1 for ends
Meteor.ui._LiveRange.prototype._remove_entries =
LiveRange.prototype._remove_entries =
function(node, startEnd, i, j)
{
var entries = node[this.tag][startEnd];
@@ -353,13 +347,13 @@ Meteor.ui = Meteor.ui || {};
// potentially remove empty liverange data
if (! entries.length) {
Meteor.ui._LiveRange._clean_node(this.tag, node);
LiveRange._clean_node(this.tag, node);
}
return removed;
};
Meteor.ui._LiveRange.prototype._insert_entries =
LiveRange.prototype._insert_entries =
function(node, startEnd, i, newRanges)
{
// insert the new ranges and "adopt" them by setting node pointers
@@ -387,7 +381,7 @@ Meteor.ui = Meteor.ui || {};
// our children.
//
// It is illegal for new_frag to be empty.
Meteor.ui._LiveRange.prototype.replace_contents = function (new_frag) {
LiveRange.prototype.replace_contents = function (new_frag) {
if (! new_frag.firstChild)
throw new Error("replace_contents requires non-empty fragment");
@@ -429,7 +423,7 @@ Meteor.ui = Meteor.ui || {};
// This method is a generalization of replace_contents that works by
// temporarily removing this LiveRange from the DOM and restoring it after
// `func` has been called.
Meteor.ui._LiveRange.prototype.operate = function (func) {
LiveRange.prototype.operate = function (func) {
// boundary nodes of departing fragment
var old_start = this._start;
var old_end = this._end;
@@ -461,7 +455,7 @@ Meteor.ui = Meteor.ui || {};
}
// wrap endpoints if necessary
var new_endpoints = Meteor.ui._wrap_endpoints(new_start, new_end);
var new_endpoints = wrapEndpoints(new_start, new_end);
new_start = this._ensure_tag(new_endpoints[0]);
new_end = this._ensure_tag(new_endpoints[1]);
@@ -481,7 +475,7 @@ Meteor.ui = Meteor.ui || {};
// This is a low-level operation suitable for moving liveranges en masse
// from one DOM tree to another, where transplant_tag is called on every
// pair of nodes such that targetNode takes the place of sourceNode.
Meteor.ui._LiveRange.transplant_tag = function(tag, targetNode, sourceNode) {
LiveRange.transplant_tag = function(tag, targetNode, sourceNode) {
if (! sourceNode[tag])
return;
@@ -509,7 +503,7 @@ Meteor.ui = Meteor.ui || {};
//
// This is a low-level operation suitable for moving liveranges en masse
// from one DOM tree to another.
Meteor.ui._LiveRange.transplant_range = function(tgtStart, tgtEnd, srcRange) {
LiveRange.transplant_range = function(tgtStart, tgtEnd, srcRange) {
srcRange._ensure_tag(tgtStart);
if (tgtEnd !== tgtStart)
srcRange._ensure_tag(tgtEnd);
@@ -525,7 +519,7 @@ Meteor.ui = Meteor.ui || {};
// Inserts a DocumentFragment immediately before this range.
// The new nodes are outside this range but inside all
// enclosing ranges.
Meteor.ui._LiveRange.prototype.insert_before = function(frag) {
LiveRange.prototype.insert_before = function(frag) {
var frag_start = frag.firstChild;
if (! frag_start) // empty frag
@@ -545,7 +539,7 @@ Meteor.ui = Meteor.ui || {};
// Inserts a DocumentFragment immediately after this range.
// The new nodes are outside this range but inside all
// enclosing ranges.
Meteor.ui._LiveRange.prototype.insert_after = function(frag) {
LiveRange.prototype.insert_after = function(frag) {
var frag_end = frag.lastChild;
if (! frag_end) // empty frag
@@ -571,7 +565,7 @@ Meteor.ui = Meteor.ui || {};
// it is illegal to perform `extract` if the immediately
// enclosing range would become empty. If this precondition
// is violated, no action is taken and null is returned.
Meteor.ui._LiveRange.prototype.extract = function() {
LiveRange.prototype.extract = function() {
if (this._start_idx > 0 &&
this._start[this.tag][0][this._start_idx - 1]._end === this._end) {
// immediately enclosing range wraps same nodes, so can't extract because
@@ -618,30 +612,30 @@ Meteor.ui = Meteor.ui || {};
// this range's container node (the parent of its endpoints) and
// only return liveranges whose first and last nodes are siblings
// of this one's.
Meteor.ui._LiveRange.prototype.findParent = function(withSameContainer) {
var result = _enclosing_range_search(this.tag, this._end, this._end_idx);
LiveRange.prototype.findParent = function(withSameContainer) {
var result = enclosingRangeSearch(this.tag, this._end, this._end_idx);
if (result)
return result;
if (withSameContainer)
return null;
return Meteor.ui._LiveRange.findRange(this.tag, this.containerNode());
return LiveRange.findRange(this.tag, this.containerNode());
};
// Find the nearest enclosing range containing `node`, if any.
Meteor.ui._LiveRange.findRange = function(tag, node) {
var result = _enclosing_range_search(tag, node);
LiveRange.findRange = function(tag, node) {
var result = enclosingRangeSearch(tag, node);
if (result)
return result;
if (! node.parentNode)
return null;
return Meteor.ui._LiveRange.findRange(tag, node.parentNode);
return LiveRange.findRange(tag, node.parentNode);
};
var _enclosing_range_search = function(tag, end, end_idx) {
var enclosingRangeSearch = function(tag, end, end_idx) {
// Search for an enclosing range, at the same level,
// starting at node `end` or after the range whose
// position in the end array of `end` is `end_idx`.

View File

@@ -4,7 +4,7 @@
/******************************************************************************/
var create = function (id, start, end, inner, tag) {
var ret = new Meteor.ui._LiveRange(tag || 'a', start, end, inner);
var ret = new LiveRange(tag || 'a', start, end, inner);
ret.id = id;
return ret;
};
@@ -27,7 +27,7 @@ var dump = function (what, tag) {
if (typeof what === 'object' && what.nodeType === 11 /* DocumentFragment */) {
if (what.firstChild) {
var range = new Meteor.ui._LiveRange(tag || 'a', what);
var range = new LiveRange(tag || 'a', what);
range.visit(emit, emit);
range.destroy();
}
@@ -43,11 +43,11 @@ var dump = function (what, tag) {
// actual can be a range or a fragment
var assert_dump = function (test, expected, actual, tag) {
test.equal(dump(actual), expected, "Tree doesn't match");
if (actual instanceof Meteor.ui._LiveRange)
if (actual instanceof LiveRange)
check_liverange_integrity(actual);
else {
if (actual.firstChild) {
var range = new Meteor.ui._LiveRange(tag || 'a', actual);
var range = new LiveRange(tag || 'a', actual);
check_liverange_integrity(range);
range.destroy();
}
@@ -92,7 +92,7 @@ var assert_contained = function (r, expected) {
Tinytest.add("liverange - single node", function (test) {
var f = frag("<div id=1></div>");
var r_a = create("a", f);
test.instanceOf(r_a, Meteor.ui._LiveRange);
test.instanceOf(r_a, LiveRange);
assert_dump(test, "<a><1></1></a>", r_a);
assert_dump(test, "<a><1></1></a>", f);
assert_contained(r_a, {range: r_a, children: []});
@@ -481,7 +481,7 @@ var makeTestPattern = function(codedStr) {
// close range
var start = starts.pop();
var range =
new Meteor.ui._LiveRange(
new LiveRange(
self.tag, start[0].childNodes[start[1]],
start[0].lastChild);
range.letter = c.toUpperCase();
@@ -506,12 +506,12 @@ var makeTestPattern = function(codedStr) {
};
self.findRange = function(node) {
return Meteor.ui._LiveRange.findRange(self.tag, node);
return LiveRange.findRange(self.tag, node);
};
self.currentString = function() {
var buf = [];
var tempRange = new Meteor.ui._LiveRange(self.tag, self.frag);
var tempRange = new LiveRange(self.tag, self.frag);
tempRange.visit(function(is_start, range) {
buf.push(is_start ?
range.letter.toUpperCase() :
@@ -603,11 +603,11 @@ Tinytest.add("liverange - destroy", function(test) {
var frag = document.createDocumentFragment();
var txt = document.createComment("pudding");
frag.appendChild(txt);
var rng5 = new Meteor.ui._LiveRange('_pudding', txt);
var rng4 = new Meteor.ui._LiveRange('_pudding', txt);
var rng3 = new Meteor.ui._LiveRange('_pudding', txt);
var rng2 = new Meteor.ui._LiveRange('_pudding', txt);
var rng1 = new Meteor.ui._LiveRange('_pudding', txt);
var rng5 = new LiveRange('_pudding', txt);
var rng4 = new LiveRange('_pudding', txt);
var rng3 = new LiveRange('_pudding', txt);
var rng2 = new LiveRange('_pudding', txt);
var rng1 = new LiveRange('_pudding', txt);
rng1.num = 1;
rng2.num = 2;
rng3.num = 3;

View File

@@ -118,7 +118,7 @@ Meteor.ui._doc = Meteor.ui._doc || {};
Meteor.ui._doc._newAnnotations[id] = null;
var subFrag = makeFrag(html);
var range = new Meteor.ui._LiveRange(Meteor.ui._TAG, subFrag);
var range = new LiveRange(Meteor.ui._TAG, subFrag);
// assign options to the LiveRange, including `id`
_.extend(range, options);
// enqueue the new range for callback processing
@@ -200,7 +200,7 @@ Meteor.ui._doc = Meteor.ui._doc || {};
// properly killing them and cleaning the
// nodes. May be called as cleanNodes(fragment) or cleanNodes(node) as well.
Meteor.ui._doc.cleanNodes = function(start, end) {
var wrapper = new Meteor.ui._LiveRange(Meteor.ui._TAG, start, end);
var wrapper = new LiveRange(Meteor.ui._TAG, start, end);
wrapper.visit(function (isStart, range) {
if (isStart && ! range.dead) {
range.dead = true;

View File

@@ -23,7 +23,7 @@ Tinytest.add("livedocument - assembly", function(test) {
test.equal(f.html(), html);
var actualGroups = [];
var tempRange = new Meteor.ui._LiveRange(Meteor.ui._TAG, frag);
var tempRange = new LiveRange(Meteor.ui._TAG, frag);
tempRange.visit(function(isStart, rng) {
if (! isStart)
actualGroups.push(Meteor.ui._rangeToHtml(rng));

View File

@@ -324,7 +324,7 @@ Meteor.ui = Meteor.ui || {};
};
if (! range)
range = new Meteor.ui._LiveRange(Meteor.ui._tag, frag);
range = new LiveRange(Meteor.ui._tag, frag);
// Table-body fix: if container is a table and frag
// contains a TR, wrap fragment in a TBODY on all browsers,
@@ -655,7 +655,7 @@ Meteor.ui = Meteor.ui || {};
if (! curNode)
return;
var innerRange = Meteor.ui._LiveRange.findRange(Meteor.ui._tag, curNode);
var innerRange = LiveRange.findRange(Meteor.ui._tag, curNode);
var type = event.type;
@@ -702,7 +702,7 @@ Meteor.ui = Meteor.ui || {};
// find the innermost enclosing liverange that has event data
var findEventData = function(node) {
var innerRange = Meteor.ui._LiveRange.findRange(Meteor.ui._tag, node);
var innerRange = LiveRange.findRange(Meteor.ui._tag, node);
for(var range = innerRange; range; range = range.findParent())
if (range.data)
@@ -781,7 +781,7 @@ Meteor.ui = Meteor.ui || {};
// create a temporary range around newFrag in order
// to visit it.
var tempRange = new Meteor.ui._LiveRange(Meteor.ui._tag, newFrag);
var tempRange = new LiveRange(Meteor.ui._tag, newFrag);
// visit new frag
eachKeyedChunk(tempRange, function(r, path) {
var oldRange = oldChunks[path];
@@ -828,7 +828,7 @@ Meteor.ui = Meteor.ui || {};
var patch = function(tgtParent, srcParent, tgtBefore, tgtAfter, nodeMatches) {
var copyFunc = function(t, s) {
Meteor.ui._LiveRange.transplant_tag(Meteor.ui._tag, t, s);
LiveRange.transplant_tag(Meteor.ui._tag, t, s);
};
var patcher = new Meteor.ui._Patcher(
@@ -862,7 +862,7 @@ Meteor.ui = Meteor.ui || {};
// range match! for constant chunk
if (patcher.match(pair[0], pair[1], null, true)) {
patcher.skipToSiblings(pair[2], pair[3]);
Meteor.ui._LiveRange.transplant_range(
LiveRange.transplant_range(
pair[0], pair[2], pair[4]);
}
} else {

View File

@@ -91,7 +91,7 @@ Tinytest.add("patcher - basic", function(test) {
test.isTrue(ret);
assert_html(x, "<i><u>bar</u><s>baz</s></i>");
var LiveRange = Meteor.ui._LiveRange;
var LiveRange = LiveRange;
var t = "_foo";
var liverange = function(start, end, inner) {
return new LiveRange(t, start, end, inner);

View File

@@ -28,7 +28,7 @@ _.extend(Spark._Renderer.prototype, {
annotate: function (html, tag, what) {
var id = tag + "-" + this.createId();
this.annotations[id] = function (start, end) {
var range = new Meteor.ui._LiveRange(tag, start, end);
var range = new LiveRange(tag, start, end);
if (what instanceof Function)
what(range);
else
@@ -67,7 +67,7 @@ Spark.setContext = function (html, context) {
};
Spark.getContext = function (node) {
var range = Meteor.ui._LiveRange.findRange("_context", node);
var range = LiveRange.findRange("_context", node);
return range && range.context;
}