Users selects two or more elements in a HTML page. What i want to accomplish is to find those elements\' common ancestors (so body node would be the common ancestor if none
Here's another pure method that uses element.compareDocumentPosition() and element.contains(), the former being a standards method and the latter being a method supported by most major browsers excluding Firefox:
function getCommonAncestor(node1, node2) {
var method = "contains" in node1 ? "contains" : "compareDocumentPosition",
test = method === "contains" ? 1 : 0x10;
while (node1 = node1.parentNode) {
if ((node1[method](node2) & test) === test)
return node1;
}
return null;
}
Working demo: http://jsfiddle.net/3FaRr/ (using lonesomeday's test case)
This should be, more or less, as efficient as possible since it is pure DOM and has only one loop.
Taking another look at the question, I noticed the "or more" part of the "two or more" requirement had gone ignored by the answers. So I decided to tweak mine slightly to allow any number of nodes to be specified:
function getCommonAncestor(node1 /*, node2, node3, ... nodeN */) {
if (arguments.length < 2)
throw new Error("getCommonAncestor: not enough parameters");
var i,
method = "contains" in node1 ? "contains" : "compareDocumentPosition",
test = method === "contains" ? 1 : 0x0010,
nodes = [].slice.call(arguments, 1);
rocking:
while (node1 = node1.parentNode) {
i = nodes.length;
while (i--) {
if ((node1[method](nodes[i]) & test) !== test)
continue rocking;
}
return node1;
}
return null;
}
Working demo: http://jsfiddle.net/AndyE/3FaRr/1
This is a generalized take on lonesomeday's answer. Instead of only two elements it will take a full JQuery object.
function CommonAncestor(jq) {
var prnt = $(jq[0]);
jq.each(function () {
prnt = prnt.parents().add(prnt).has(this).last();
});
return prnt;
}
Somewhat late to the party, but here's an elegant jQuery solution (since the question is tagged jQuery) -
/**
* Get all parents of an element including itself
* @returns {jQuerySelector}
*/
$.fn.family = function() {
var i, el, nodes = $();
for (i = 0; i < this.length; i++) {
for (el = this[i]; el !== document; el = el.parentNode) {
nodes.push(el);
}
}
return nodes;
};
/**
* Find the common ancestors in or against a selector
* @param selector {?(String|jQuerySelector|Element)}
* @returns {jQuerySelector}
*/
$.fn.common = function(selector) {
if (selector && this.is(selector)) {
return this;
}
var i,
$parents = (selector ? this : this.eq(0)).family(),
$targets = selector ? $(selector) : this.slice(1);
for (i = 0; i < $targets.length; i++) {
$parents = $parents.has($targets.eq(i).family());
}
return $parents;
};
/**
* Find the first common ancestor in or against a selector
* @param selector {?(String|jQuerySelector|Element)}
* @returns {jQuerySelector}
*/
$.fn.commonFirst = function(selector) {
return this.common(selector).first();
};
You should be able to use the jQuery .parents() function and then walk through the results looking for the first match. (Or I guess you could start from the end and go backwards until you see the first difference; that's probably better.)
Here's a pure JavaScript version that is a little more efficient.
function parents(node) {
var nodes = [node]
for (; node; node = node.parentNode) {
nodes.unshift(node)
}
return nodes
}
function commonAncestor(node1, node2) {
var parents1 = parents(node1)
var parents2 = parents(node2)
if (parents1[0] != parents2[0]) throw "No common ancestor!"
for (var i = 0; i < parents1.length; i++) {
if (parents1[i] != parents2[i]) return parents1[i - 1]
}
}
This doesn't require much code anymore to solve:
steps:
code:
function getLowestCommonParent(node_a, node_b) {
while (node_a = node_a.parentElement) {
if (node_a.contains(node_b)) {
return node_a;
}
}
return null;
}