mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
move liveui/domutils utilities into DomUtils
This commit is contained in:
@@ -152,4 +152,168 @@ DomUtils = {};
|
||||
return container;
|
||||
};
|
||||
|
||||
})();
|
||||
// Returns true if element a properly contains element b.
|
||||
// Only works on element nodes (e.g. not text nodes).
|
||||
DomUtils.elementContains = function(a, b) {
|
||||
// Note: Some special-casing would be required to implement this method
|
||||
// where a and b aren't necessarily elements, e.g. b is a text node,
|
||||
// because contains() doesn't seem to work reliably on some browsers
|
||||
// including IE.
|
||||
if (a.nodeType !== 1 || b.nodeType !== 1) {
|
||||
return false; // a and b are not both elements
|
||||
}
|
||||
if (a.compareDocumentPosition) {
|
||||
return a.compareDocumentPosition(b) & 0x10;
|
||||
} else {
|
||||
// Should be only old IE and maybe other old browsers here.
|
||||
// Modern Safari has both methods but seems to get contains() wrong.
|
||||
return a !== b && a.contains(b);
|
||||
}
|
||||
};
|
||||
|
||||
// Returns an array containing the children of contextNode that
|
||||
// match `selector`. Unlike querySelectorAll, `selector` is
|
||||
// interpreted as if the document were rooted at `contextNode` --
|
||||
// the only nodes that can be used to match components of the
|
||||
// selector are the descendents of `contextNode`. `contextNode`
|
||||
// itself is not included (it can't be used to match a component of
|
||||
// the selector, and it can never be included in the returned
|
||||
// array.)
|
||||
//
|
||||
// `contextNode` may be either a node, a document, or a DocumentFragment.
|
||||
DomUtils.findElement = function(contextNode, selector) {
|
||||
// Eventually, we will remove the dependency on jQuery ($) and
|
||||
// implement this in terms of querySelectorAll on modern browsers
|
||||
// and Sizzle in old IE. We'll use jQuery's trick for scoped
|
||||
// querySelectorAll which involves temporarily assigning an ID to
|
||||
// contextNode (if it doesn't have one) and prepending the ID to
|
||||
// the selector.
|
||||
if (contextNode.nodeType === 11 /* DocumentFragment */) {
|
||||
// Sizzle doesn't work on a DocumentFragment, but it does work on
|
||||
// a descendent of one.
|
||||
var frag = contextNode;
|
||||
var container = DomUtils.fragmentToContainer(frag);
|
||||
var results = $(container).find(selector);
|
||||
// put nodes back into frag
|
||||
while (container.firstChild)
|
||||
frag.appendChild(container.firstChild);
|
||||
return results;
|
||||
} else {
|
||||
return $(contextNode).find(selector);
|
||||
}
|
||||
};
|
||||
|
||||
// Like `findElement` but searches the nodes from `start` to `end`
|
||||
// inclusive. `start` and `end` must be siblings, and they participate
|
||||
// in the search (they can be used to match selector components, and
|
||||
// they can appear in the returned results). It's as if the parent of
|
||||
// `start` and `end` serves as contextNode, but matches from children
|
||||
// that aren't between `start` and `end` (inclusive) are ignored.
|
||||
//
|
||||
// If `selector` involves sibling selectors, child index selectors, or
|
||||
// the like, the results are undefined.
|
||||
DomUtils.findElementInRange = function(start, end, selector) {
|
||||
end = (end || start);
|
||||
|
||||
var container = start.parentNode;
|
||||
if (! container) {
|
||||
if (start === end && (start.nodeType === 9 /* Document */ ||
|
||||
start.nodeType === 11 /* DocumentFragment */))
|
||||
return DomUtils.findElement(start, selector);
|
||||
throw new Error("Can't find element in range on detached node");
|
||||
}
|
||||
if (end.parentNode !== container)
|
||||
throw new Error("Bad range");
|
||||
|
||||
// narrow the range to exclude top-level non-elements (which can't be
|
||||
// or contain matches) by moving the `start` pointer forward and `end`
|
||||
// backward.
|
||||
while (start !== end && start.nodeType !== 1)
|
||||
start = start.nextSibling;
|
||||
while (start !== end && end.nodeType !== 1)
|
||||
end = end.previousSibling;
|
||||
if (start.nodeType !== 1)
|
||||
return []; // no top-level elements! start === end and it's not an element
|
||||
|
||||
// resultsPlus includes matches that are contained by the range's
|
||||
// parent, but are outside of start..end, i.e. are descended from
|
||||
// (or are) a different sibling.
|
||||
var resultsPlus = DomUtils.findElement(container, selector);
|
||||
|
||||
// Filter the list of nodes to remove nodes that occur before start
|
||||
// or after end.
|
||||
return _.reject(resultsPlus, function(n) {
|
||||
// reject node if (n,start) are in order or (end,n) are in order
|
||||
return (DomUtils.elementOrder(n, start) > 0) ||
|
||||
(DomUtils.elementOrder(end, n) > 0);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Returns 0 if the nodes are the same or either one contains the other;
|
||||
// otherwise, 1 if a comes before b, or else -1 if b comes before a in
|
||||
// document order.
|
||||
// Requires: `a` and `b` are element nodes in the same document tree.
|
||||
DomUtils.elementOrder = function(a, b) {
|
||||
// See http://ejohn.org/blog/comparing-document-position/
|
||||
if (a === b)
|
||||
return 0;
|
||||
if (a.compareDocumentPosition) {
|
||||
var n = a.compareDocumentPosition(b);
|
||||
return ((n & 0x18) ? 0 : ((n & 0x4) ? 1 : -1));
|
||||
} else {
|
||||
// Only old IE is known to not have compareDocumentPosition (though Safari
|
||||
// originally lacked it). Thankfully, IE gives us a way of comparing elements
|
||||
// via the "sourceIndex" property.
|
||||
if (a.contains(b) || b.contains(a))
|
||||
return 0;
|
||||
return (a.sourceIndex < b.sourceIndex ? 1 : -1);
|
||||
}
|
||||
};
|
||||
|
||||
// Wrap `frag` as necessary to prepare it for insertion in
|
||||
// `container`. For example, if `frag` has TR nodes at top level,
|
||||
// and `container` is a TABLE, then it's necessary to wrap `frag` in
|
||||
// a TBODY to avoid IE quirks.
|
||||
//
|
||||
// `frag` is a DocumentFragment and will be modified in
|
||||
// place. `container` is a DOM element.
|
||||
DomUtils.wrapFragmentForContainer = function(frag, container) {
|
||||
if (container && container.nodeName === "TABLE" &&
|
||||
_.any(frag.childNodes,
|
||||
function(n) { return n.nodeName === "TR"; })) {
|
||||
// Avoid putting a TR directly in a TABLE without an
|
||||
// intervening TBODY, because it doesn't work in IE. We do
|
||||
// the same thing on all browsers for ease of testing
|
||||
// and debugging.
|
||||
var tbody = document.createElement("TBODY");
|
||||
tbody.appendChild(frag);
|
||||
frag.appendChild(tbody);
|
||||
}
|
||||
};
|
||||
|
||||
// Return true if `node` is part of the global DOM document. Like
|
||||
// elementContains(document, node), except (1) it works for any node
|
||||
// (eg, text nodes), not just elements; (2) it works around browser
|
||||
// quirks that would otherwise come up when passing 'document' as
|
||||
// the first argument to elementContains.
|
||||
//
|
||||
// Returns true if node === document.
|
||||
DomUtils.isInDocument = function (node) {
|
||||
// Deal with all cases where node is not an element
|
||||
// node descending from the body first...
|
||||
if (node === document)
|
||||
return true;
|
||||
|
||||
if (node.nodeType !== 1 /* Element */)
|
||||
node = node.parentNode;
|
||||
if (! (node && node.nodeType === 1))
|
||||
return false;
|
||||
if (node === document.body)
|
||||
return true;
|
||||
|
||||
return DomUtils.elementContains(document.body, node);
|
||||
};
|
||||
|
||||
|
||||
})();
|
||||
|
||||
@@ -21,7 +21,7 @@ Meteor.ui._elementContains = function(a, b) {
|
||||
// Returns an array of element nodes matching `selector`, where
|
||||
// the selector is interpreted as rooted at `contextNode`.
|
||||
// This means that all nodes that participate in the selector
|
||||
// must be descendents on contextNode.
|
||||
// must be descendents of contextNode.
|
||||
//
|
||||
// jQuery dependency to eventually replace with querySelectorAll
|
||||
// backed up by Sizzle in Old IE. Note that querySelectorAll doesn't
|
||||
@@ -46,9 +46,10 @@ Meteor.ui._findElement = function(contextNode, selector) {
|
||||
}
|
||||
};
|
||||
|
||||
// Requires: `a` and `b` are element nodes in the same document tree.
|
||||
// Returns 0 if the nodes are the same or either one contains the other;
|
||||
// otherwise, 1 if (a,b) are in order and -1 if they are in the opposite order.
|
||||
// otherwise, 1 if a comes before b, or else -1 if b comes before a in
|
||||
// document order.
|
||||
// Requires: `a` and `b` are element nodes in the same document tree.
|
||||
Meteor.ui._elementOrder = function(a, b) {
|
||||
// See http://ejohn.org/blog/comparing-document-position/
|
||||
if (a === b)
|
||||
@@ -66,8 +67,15 @@ Meteor.ui._elementOrder = function(a, b) {
|
||||
}
|
||||
};
|
||||
|
||||
// Like `findElement` but uses a hypothetical LiveRange wrapping start..end
|
||||
// as the context.
|
||||
// Like `findElement` but searches the nodes from `start` to `end`
|
||||
// inclusive. `start` and `end` must be siblings, and they participate
|
||||
// in the search (they can be used to match selector components, and
|
||||
// they can appear in the returned results). It's as if the parent of
|
||||
// `start` and `end` serves as contextNode, but matches from children
|
||||
// that aren't between `start` and `end` (inclusive) are ignored.
|
||||
//
|
||||
// If `selector` involves sibling selectors, child index selectors, or
|
||||
// the like, the results are undefined.
|
||||
Meteor.ui._findElementInRange = function(start, end, selector) {
|
||||
end = (end || start);
|
||||
|
||||
@@ -122,6 +130,11 @@ Meteor.ui._isNodeOnscreen = function (node) {
|
||||
return Meteor.ui._elementContains(document.body, node);
|
||||
};
|
||||
|
||||
// Wraps the contents of `frag`, a DocumentFragment, if necessary
|
||||
// to insert the fragment into `container`, a DOM element.
|
||||
// For example, if `frag` has TR nodes as children and container
|
||||
// is a TABLE, the children of `frag` will be wrapped with a
|
||||
// TBODY in place to work around IE quirks.
|
||||
Meteor.ui._wrapFragmentForContainer = function(frag, container) {
|
||||
if (container && container.nodeName === "TABLE" &&
|
||||
_.any(frag.childNodes,
|
||||
|
||||
@@ -126,7 +126,7 @@ Meteor.ui._doc = Meteor.ui._doc || {};
|
||||
|
||||
var next = comment.nextSibling;
|
||||
|
||||
Meteor.ui._wrapFragmentForContainer(subFrag, comment.parentNode);
|
||||
DomUtils.wrapFragmentForContainer(subFrag, comment.parentNode);
|
||||
comment.parentNode.replaceChild(subFrag, comment);
|
||||
|
||||
return next;
|
||||
@@ -182,7 +182,7 @@ Meteor.ui._doc = Meteor.ui._doc || {};
|
||||
throw new Error("Double-GCed range: "+range.id);
|
||||
|
||||
if (! (node.parentNode &&
|
||||
(Meteor.ui._isNodeOnscreen(node) ||
|
||||
(DomUtils.isInDocument(node) ||
|
||||
Meteor.ui._doc._isNodeHeld(node)))) {
|
||||
// range is offscreen!
|
||||
// kill all ranges in this fragment or detached DOM tree,
|
||||
|
||||
@@ -128,7 +128,7 @@ Meteor.ui._event._loadW3CImpl = function() {
|
||||
// relatedTarget is present and a descendent).
|
||||
(! event.relatedTarget ||
|
||||
(event.currentTarget !== event.relatedTarget &&
|
||||
! Meteor.ui._elementContains(
|
||||
! DomUtils.elementContains(
|
||||
event.currentTarget, event.relatedTarget)))) {
|
||||
if (event.type === 'mouseover'){
|
||||
sendUIEvent('mouseenter', event.currentTarget, false);
|
||||
|
||||
@@ -61,7 +61,7 @@ Meteor.ui = Meteor.ui || {};
|
||||
Meteor.ui._inRender = false;
|
||||
}
|
||||
|
||||
Meteor.ui._wrapFragmentForContainer(frag, range.containerNode());
|
||||
DomUtils.wrapFragmentForContainer(frag, range.containerNode());
|
||||
|
||||
// Perform patching
|
||||
var nodeMatches = matchChunks(range, frag);
|
||||
@@ -540,7 +540,7 @@ Meteor.ui = Meteor.ui || {};
|
||||
var node = range.firstNode();
|
||||
|
||||
if (node.parentNode &&
|
||||
(Meteor.ui._isNodeOnscreen(node) || Sarge.isNodeHeld(node)))
|
||||
(DomUtils.isInDocument(node) || Sarge.isNodeHeld(node)))
|
||||
return false;
|
||||
|
||||
while (node.parentNode)
|
||||
@@ -673,7 +673,7 @@ Meteor.ui = Meteor.ui || {};
|
||||
var selector = h.selector;
|
||||
if (selector) {
|
||||
var contextNode = range.containerNode();
|
||||
var results = Meteor.ui._findElement(contextNode, selector);
|
||||
var results = DomUtils.findElement(contextNode, selector);
|
||||
if (! _.contains(results, curNode))
|
||||
continue;
|
||||
} else {
|
||||
@@ -757,7 +757,7 @@ Meteor.ui = Meteor.ui || {};
|
||||
var collectLabeledNodes = function(range, preserveMap) {
|
||||
var labeledNodes = {};
|
||||
_.each(preserveMap, function(labelFunc, sel) {
|
||||
var matchingNodes = Meteor.ui._findElementInRange(
|
||||
var matchingNodes = DomUtils.findElementInRange(
|
||||
range.firstNode(), range.lastNode(), sel);
|
||||
_.each(matchingNodes, function(n) {
|
||||
// labelFunc can be a function or a constant,
|
||||
@@ -857,7 +857,7 @@ Meteor.ui = Meteor.ui || {};
|
||||
if (pair) {
|
||||
var tgt = pair[0];
|
||||
if (! lastTgtMatch ||
|
||||
Meteor.ui._elementOrder(lastTgtMatch, tgt) > 0) {
|
||||
DomUtils.elementOrder(lastTgtMatch, tgt) > 0) {
|
||||
if (pair.rangeMatch) {
|
||||
// range match! for constant chunk
|
||||
if (patcher.match(pair[0], pair[1], null, true)) {
|
||||
|
||||
@@ -93,7 +93,6 @@ Meteor.ui._Patcher.prototype.match = function(
|
||||
|
||||
var starting = ! lastKeptTgt;
|
||||
var finishing = ! tgt;
|
||||
var elementContains = Meteor.ui._elementContains;
|
||||
|
||||
if (! starting) {
|
||||
// move lastKeptTgt/lastKeptSrc forward and out,
|
||||
@@ -101,7 +100,7 @@ Meteor.ui._Patcher.prototype.match = function(
|
||||
// replacing as we go. If tgt/src is falsy, we make it to the
|
||||
// top level.
|
||||
while (lastKeptTgt.parentNode !== this.tgtParent &&
|
||||
! (tgt && elementContains(lastKeptTgt.parentNode, tgt))) {
|
||||
! (tgt && DomUtils.elementContains(lastKeptTgt.parentNode, tgt))) {
|
||||
// Last-kept nodes are inside parents that are not
|
||||
// parents of the newly matched nodes. Must finish
|
||||
// replacing their contents and back out.
|
||||
|
||||
Reference in New Issue
Block a user