mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
DomRange.contains, tests
This commit is contained in:
@@ -12,6 +12,18 @@ if (Meteor.isClient) {
|
||||
var $ = Package.jquery.jQuery;
|
||||
|
||||
var DomBackend = {
|
||||
// Must use jQuery semantics for `context`, not
|
||||
// querySelectorAll's. In other words, all the parts
|
||||
// of `selector` must be found under `context`.
|
||||
findBySelector: function (selector, context) {
|
||||
return jQuery.find(selector, context);
|
||||
},
|
||||
newFragment: function (nodeArray) {
|
||||
// jQuery fragments are built specially in
|
||||
// IE<9 so that they can safely hold HTML5
|
||||
// elements.
|
||||
return $.buildFragment(nodeArray, document);
|
||||
},
|
||||
parseHTML: function (html) {
|
||||
// Return an array of nodes.
|
||||
//
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
var DomBackend = UI.DomBackend;
|
||||
|
||||
var removeNode = function (n) {
|
||||
// if (n.nodeType === 1 &&
|
||||
@@ -25,13 +26,6 @@ var moveNode = function (n, parent, next) {
|
||||
parent.insertBefore(n, next || null);
|
||||
};
|
||||
|
||||
var newFragment = function (nodeArray) {
|
||||
// jQuery fragments are built specially in
|
||||
// IE<9 so that they can safely hold HTML5
|
||||
// elements.
|
||||
return $.buildFragment(nodeArray, document);
|
||||
};
|
||||
|
||||
// A very basic operation like Underscore's `_.extend` that
|
||||
// copies `src`'s own, enumerable properties onto `tgt` and
|
||||
// returns `tgt`.
|
||||
@@ -74,7 +68,7 @@ var DomRange = function (component) {
|
||||
// detection too.
|
||||
var start = document.createTextNode("");
|
||||
var end = document.createTextNode("");
|
||||
var fragment = newFragment([start, end]);
|
||||
var fragment = DomBackend.newFragment([start, end]);
|
||||
|
||||
if (component) {
|
||||
this.component = component;
|
||||
@@ -212,6 +206,8 @@ _extend(DomRange.prototype, {
|
||||
if (typeof newMember.nodeType !== 'number')
|
||||
throw new Error("Expected Component or Node");
|
||||
var node = newMember;
|
||||
// can't attach `$ui` to a TextNode in IE 8, so
|
||||
// don't bother on any browser.
|
||||
if (node.nodeType !== 3)
|
||||
node.$ui = this.component;
|
||||
|
||||
@@ -455,6 +451,8 @@ _extend(DomRange.prototype, {
|
||||
n && ! n.$ui;
|
||||
n = n.previousSibling) {
|
||||
this.members[this.nextMemberId++] = n;
|
||||
// can't attach `$ui` to a TextNode in IE 8, so
|
||||
// don't bother on any browser.
|
||||
if (n.nodeType !== 3)
|
||||
n.$ui = this.component;
|
||||
}
|
||||
@@ -652,6 +650,52 @@ var moveWithOwnersIntoTbody = function (range) {
|
||||
|
||||
///// FIND BY SELECTOR
|
||||
|
||||
DomRange.prototype.contains = function (compOrNode) {
|
||||
if (! compOrNode)
|
||||
throw new Error("Expected Component or Node");
|
||||
|
||||
var parentNode = this.parentNode();
|
||||
if (! parentNode)
|
||||
return false;
|
||||
|
||||
var range;
|
||||
if (! compOrNode)
|
||||
debugger;
|
||||
if ('dom' in compOrNode) {
|
||||
// Component
|
||||
range = compOrNode.dom;
|
||||
var pn = range.parentNode();
|
||||
if (! pn)
|
||||
return false;
|
||||
// If parentNode is different, it must be a node
|
||||
// we contain.
|
||||
if (pn !== parentNode)
|
||||
return this.contains(pn);
|
||||
if (range === this)
|
||||
return false; // don't contain self
|
||||
// Ok, `range` is a same-parent range to see if we
|
||||
// contain.
|
||||
} else {
|
||||
// Node
|
||||
var node = compOrNode;
|
||||
if (! elementContains(parentNode, node))
|
||||
return false;
|
||||
|
||||
while (node.parentNode !== parentNode)
|
||||
node = node.parentNode;
|
||||
|
||||
range = node.$ui && node.$ui.dom;
|
||||
}
|
||||
|
||||
// Now see if `range` is truthy and either `this`
|
||||
// or an immediate subrange
|
||||
|
||||
while (range && range !== this)
|
||||
range = range.owner && range.owner.dom;
|
||||
|
||||
return range === this;
|
||||
};
|
||||
|
||||
DomRange.prototype.$ = function (selector) {
|
||||
var self = this;
|
||||
|
||||
@@ -667,10 +711,18 @@ DomRange.prototype.$ = function (selector) {
|
||||
// so if performance is an issue, the selector should be
|
||||
// run on a child element.
|
||||
|
||||
// Since jQuery can't run selectors on a DocumentFragment,
|
||||
// we don't expect findBySelector to work.
|
||||
if (parentNode.nodeType === 11) // DocumentFragment
|
||||
throw new Error("Can't use $ on a detached component");
|
||||
|
||||
var results = DomBackend.findBySelector(selector, parentNode);
|
||||
|
||||
// We don't assume `results` has jQuery API; a plain array
|
||||
// should do just as well. However, if we do have a jQuery
|
||||
// array, we want to end up with one also.
|
||||
var results = $(selector, parentNode);
|
||||
// array, we want to end up with one also, so we use
|
||||
// `.filter`.
|
||||
|
||||
|
||||
// Function that selects only elements that are actually
|
||||
// in this DomRange, rather than simply descending from
|
||||
@@ -681,14 +733,7 @@ DomRange.prototype.$ = function (selector) {
|
||||
if (typeof elem === 'number')
|
||||
elem = this;
|
||||
|
||||
while (elem.parentNode !== parentNode)
|
||||
elem = elem.parentNode;
|
||||
|
||||
var range = elem.$ui && elem.$ui.dom;
|
||||
while (range && range !== self)
|
||||
range = range.owner && range.owner.dom;
|
||||
|
||||
return range === self;
|
||||
return self.contains(elem);
|
||||
};
|
||||
|
||||
if (! results.filter) {
|
||||
@@ -764,6 +809,8 @@ DomRange.prototype.on = function (events, selector, handler) {
|
||||
if ((! selector) && evt.currentTarget !== evt.target)
|
||||
// no selector means only fire on target
|
||||
return;
|
||||
if (! h.$ui.dom.contains(evt.currentTarget))
|
||||
return;
|
||||
return h.handler.call(h.$ui, evt);
|
||||
};
|
||||
})(handlerRecord);
|
||||
@@ -775,4 +822,29 @@ DomRange.prototype.on = function (events, selector, handler) {
|
||||
}
|
||||
};
|
||||
|
||||
// Returns true if element a contains node b and is not node b.
|
||||
var elementContains = function (a, b) {
|
||||
if (a.nodeType !== 1) // ELEMENT
|
||||
return false;
|
||||
if (a === b)
|
||||
return false;
|
||||
|
||||
if (a.compareDocumentPosition) {
|
||||
return a.compareDocumentPosition(b) & 0x10;
|
||||
} else {
|
||||
// Should be only old IE and maybe other old browsers here.
|
||||
// Modern Safari has both functions but seems to get contains() wrong.
|
||||
// IE can't handle b being a text node. We work around this
|
||||
// by doing a direct parent test now.
|
||||
b = b.parentNode;
|
||||
if (! (b && b.nodeType === 1)) // ELEMENT
|
||||
return false;
|
||||
if (a === b)
|
||||
return true;
|
||||
|
||||
return a.contains(b);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
UI.DomRange = DomRange;
|
||||
@@ -29,6 +29,14 @@ var inDocument = function (range, func) {
|
||||
}
|
||||
};
|
||||
|
||||
var htmlRange = function (html) {
|
||||
var r = new DomRange;
|
||||
_.each(parseHTML(html), function (node) {
|
||||
r.add(node);
|
||||
});
|
||||
return r;
|
||||
};
|
||||
|
||||
Tinytest.add("ui - DomRange - basic", function (test) {
|
||||
var r = new DomRange;
|
||||
r.which = 'R';
|
||||
@@ -622,14 +630,6 @@ Tinytest.add("ui - DomRange - basic events", function (test) {
|
||||
}
|
||||
};
|
||||
|
||||
var htmlRange = function (html) {
|
||||
var r = new DomRange;
|
||||
_.each(parseHTML(html), function (node) {
|
||||
r.add(node);
|
||||
});
|
||||
return r;
|
||||
};
|
||||
|
||||
inDocument(
|
||||
htmlRange("<span>Foo</span>"),
|
||||
function (r) {
|
||||
@@ -662,6 +662,78 @@ Tinytest.add("ui - DomRange - basic events", function (test) {
|
||||
arrayEqual(buf, [['click', span, span]]);
|
||||
});
|
||||
|
||||
inDocument(
|
||||
htmlRange('<div id="yeah"><span>Foo</span></div>' +
|
||||
'<div id="no">Bar</div>'),
|
||||
function (r) {
|
||||
var buf = [];
|
||||
|
||||
// test click on particular div, which is
|
||||
// not the target or the bound element
|
||||
r.on('click', '#yeah', function (evt) {
|
||||
buf.push([evt.type, evt.target, evt.currentTarget]);
|
||||
});
|
||||
|
||||
arrayEqual(buf, []);
|
||||
r.$('#no')[0].click();
|
||||
arrayEqual(buf, []);
|
||||
var yeah = r.$('#yeah')[0];
|
||||
yeah.click();
|
||||
arrayEqual(buf, [['click', yeah, yeah]]);
|
||||
});
|
||||
|
||||
inDocument(
|
||||
new DomRange,
|
||||
function (r) {
|
||||
var s;
|
||||
r.add(s = htmlRange('<div id="one"></div>'));
|
||||
r.add(htmlRange('<div id="two"></div>'));
|
||||
var one = r.$('#one')[0];
|
||||
var two = r.$('#two')[0];
|
||||
|
||||
var buf = [];
|
||||
|
||||
// test that click must be in range to fire
|
||||
// event handler
|
||||
s.on('click', 'div', function (evt) {
|
||||
buf.push([evt.type, evt.target, evt.currentTarget]);
|
||||
});
|
||||
|
||||
arrayEqual(buf, []);
|
||||
two.click();
|
||||
arrayEqual(buf, []);
|
||||
one.click();
|
||||
arrayEqual(buf, [['click', one, one]]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Tinytest.add("ui - DomRange - contains", function (test) {
|
||||
inDocument(new DomRange, function (r) {
|
||||
var s = htmlRange('<div id="one"><span>Foo</span></div>');
|
||||
var t = new DomRange;
|
||||
t.add(s);
|
||||
r.add(t);
|
||||
r.add(htmlRange('<div id="two"></div>'));
|
||||
var one = r.$('#one')[0];
|
||||
var two = r.$('#two')[0];
|
||||
var span = r.$('span')[0];
|
||||
|
||||
test.isFalse(r.contains(r));
|
||||
test.isTrue(r.contains(s));
|
||||
test.isTrue(r.contains(t));
|
||||
test.isTrue(r.contains(one));
|
||||
test.isTrue(s.contains(one));
|
||||
test.isTrue(t.contains(one));
|
||||
test.isTrue(r.contains(two));
|
||||
test.isFalse(s.contains(two));
|
||||
test.isFalse(t.contains(two));
|
||||
test.isTrue(r.contains(span));
|
||||
test.isTrue(s.contains(span));
|
||||
test.isTrue(t.contains(span));
|
||||
test.isFalse(r.contains(r.parentNode));
|
||||
test.isFalse(r.contains(document.createElement("DIV")));
|
||||
});
|
||||
});
|
||||
|
||||
// TO TEST STILL:
|
||||
|
||||
Reference in New Issue
Block a user