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
The solutions involving manually going through the ancestor elements are far more complicated than necessary. You don't need to do the loops manually. Get all the ancestor elements of one element with parents(), reduce it to the ones that contain the second element with has(), then get the first ancestor with first().
var a = $('#a'),
b = $('#b'),
closestCommonAncestor = a.parents().has(b).first();
jsFiddle example
Somewhat late to the party, here's a JavaScript ES6 version that uses Array.prototype.reduce() and Node.contains(), and can take any number of elements as parameters:
function closestCommonAncestor(...elements) {
const reducer = (prev, current) => current.parentElement.contains(prev) ? current.parentElement : prev;
return elements.reduce(reducer, elements[0]);
}
const element1 = document.getElementById('element1');
const element2 = document.getElementById('element2');
const commonAncestor = closestCommonAncestor(element1, element2);
did not liked any of the answers above(want pure javascript and one function). that worked perfectly for me,efficient and also easier to understand:
const findCommonAncestor = (elem, elem2) => {
let parent1 = elem.parentElement,parent2 = elem2.parentElement;
let childrensOfParent1 = [],childrensOfParent2 = [];
while (parent1 !== null && parent2 !== null) {
if (parent1 !== !null) {
childrensOfParent2.push(parent2);
if (childrensOfParent2.includes(parent1)) return parent1;
}
if (parent2 !== !null) {
childrensOfParent1.push(parent1);
if (childrensOfParent1.includes(parent2)) return parent2;
}
parent1 = parent1.parentElement;
parent2 = parent1.parentElement;
}
return null;
};
PureJS
function getFirstCommonAncestor(nodeA, nodeB) {
const parentsOfA = this.getParents(nodeA);
const parentsOfB = this.getParents(nodeB);
return parentsOfA.find((item) => parentsOfB.indexOf(item) !== -1);
}
function getParents(node) {
const result = [];
while (node = node.parentElement) {
result.push(node);
}
return result;
}
Here is a dirtier way of doing this. It's easier to understand but requires dom modification:
function commonAncestor(node1,node2){
var tmp1 = node1,tmp2 = node2;
// find node1's first parent whose nodeType == 1
while(tmp1.nodeType != 1){
tmp1 = tmp1.parentNode;
}
// insert an invisible span contains a strange character that no one
// would use
// if you need to use this function many times,create the span outside
// so you can use it without creating every time
var span = document.createElement('span')
, strange_char = '\uee99';
span.style.display='none';
span.innerHTML = strange_char;
tmp1.appendChild(span);
// find node2's first parent which contains that odd character, that
// would be the node we are looking for
while(tmp2.innerHTML.indexOf(strange_char) == -1){
tmp2 = tmp2.parentNode;
}
// remove that dirty span
tmp1.removeChild(span);
return tmp2;
}
Try this:
function get_common_ancestor(a, b)
{
$parentsa = $(a).parents();
$parentsb = $(b).parents();
var found = null;
$parentsa.each(function() {
var thisa = this;
$parentsb.each(function() {
if (thisa == this)
{
found = this;
return false;
}
});
if (found) return false;
});
return found;
}
Use it like this:
var el = get_common_ancestor("#id_of_one_element", "#id_of_another_element");
That's just rattled out pretty quickly, but it should work. Should be easy to amend if you want something slightly different (e.g. jQuery object returned instead of DOM element, DOM elements as arguments rather than IDs, etc.)