Como verificar no Javascript se um elemento está contido em outro

202

Como posso verificar se um elemento DOM é filho de outro elemento DOM? Existem métodos integrados para isso? Por exemplo, algo como:

if (element1.hasDescendant(element2)) 

ou

if (element2.hasParent(element1)) 

Se não, então alguma idéia de como fazer isso? Ele também precisa ser entre navegadores. Devo também mencionar que a criança pode estar aninhada muitos níveis abaixo do pai.

AJ.
fonte
3
Você deve usar stackoverflow.com/a/18234150/1868545
llange
Acredito que, como o JS atualmente é um "padrão dinâmico", a resposta aceita deve ser verificada novamente para indicar o método "conatins" como a resposta aceita.
DavidTaubmann

Respostas:

219

Atualização: Agora existe uma maneira nativa de conseguir isso. Node.contains(). Mencionado no comentário e respostas abaixo também.

Resposta antiga:

Usar a parentNodepropriedade deve funcionar. Também é bastante seguro do ponto de vista entre navegadores. Se o relacionamento é conhecido por ter um nível de profundidade, você pode verificá-lo simplesmente:

if (element2.parentNode == element1) { ... }

Se o filho puder ser aninhado arbitrariamente no fundo do pai, você poderá usar uma função semelhante à seguinte para testar o relacionamento:

function isDescendant(parent, child) {
     var node = child.parentNode;
     while (node != null) {
         if (node == parent) {
             return true;
         }
         node = node.parentNode;
     }
     return false;
}
Asaph
fonte
Obrigado pela sua resposta, o problema é que o filho pode estar aninhado muitos níveis abaixo do pai.
AJ.
@AJ: Atualizei minha resposta para incluir uma solução que deve funcionar para aninhar a criança arbitrariamente no fundo dos pais.
Asafe
228
Se alguém vier agora, talvez seja possível usar o Node.contains ( developer.mozilla.org/en-US/docs/DOM/Node.contains ), que é uma função nativa nos navegadores modernos.
Adam Heath
2
@JohnCarrell Há Node.contains(sem página caniuse embora)
gcampbell
3
@Asaph Você poderia atualizar sua resposta para incluir o novo Node.contains? :)
355

Você deveria usar Node.contains , pois agora é padrão e está disponível em todos os navegadores.

https://developer.mozilla.org/en-US/docs/Web/API/Node.contains

Brian Di Palma
fonte
O método Node.hasParent (pai) é desnecessário, mas seria Node.prototype.hasParent = function (elemento) {return element.contains (this);};
11121 llange
6
Também é suportado em navegadores móveis? Os documentos mdn têm pontos de interrogação para Android, Safari, IE e Opera.
Thetallweeks
1
@thetallweeks - Funciona no meu Android 7 pelo menos.
Sphinxxx
5
você poderia pelo menos ter adicionado um exemplo
Nino Škopac 05/04
2
Esta é uma boa answear mas exemplo seria muito legal:var parent = document.getElementById('menu'); var allElements = document.getElementsByTagName('a'); if (parent.contains(allElements[i]) { alert('Link is inside meni'); }
baron_bartek
45

Eu apenas tive que compartilhar 'meu'.

Embora conceitualmente seja o mesmo que a resposta do Asaph (beneficiando da mesma compatibilidade entre navegadores, até o IE6), é muito menor e é útil quando o tamanho é alto e / ou quando não é necessário com tanta frequência.

function childOf(/*child node*/c, /*parent node*/p){ //returns boolean
  while((c=c.parentNode)&&c!==p); 
  return !!c; 
}

..ou como one-liner ( apenas 64 caracteres !):

function childOf(c,p){while((c=c.parentNode)&&c!==p);return !!c}

e jsfiddle aqui .


Uso:
childOf(child, parent) retorna booleanotrue| false.

Explicação:
while avalia enquanto a condição while for avaliadatrue.
O&&operador (AND) retorna esse booleano verdadeiro / falso após avaliar o lado esquerdo e o lado direito, mas apenas se o lado esquerdo for verdadeiro ( left-hand && right-hand) .

O lado esquerdo (de &&) é: (c=c.parentNode).
Isso primeiro atribuirá o parentNodede ae, cem cseguida, o operador AND avaliará o resultado ccomo um booleano.
Como parentNoderetorna nullse não houver nenhum pai sobrando e nullfor convertido para false, o loop while será interrompido corretamente quando não houver mais pais.

O lado direito (de &&) é: c!==p.
O !==operador de comparação é ' não exatamente igual a'. Portanto, se o pai da criança não é o pai (você especificou), ele avalia true, mas se o pai da criança é o pai, ele avalia false.
Portanto, se for c!==p avaliado como falso, o &&operador retornará falsequando a condição while e o loop while forem interrompidos. (Observe que não há necessidade de um corpo while e o ;ponto e vírgula de fechamento é necessário.)

Portanto, quando o loop while termina, cé um nó (não null) quando ele encontra um pai OU null(quando o loop é executado até o final sem encontrar uma correspondência).

Portanto, simplesmente returnesse fato (convertido como valor booleano, em vez de nó) com return !!c;:: the !( NOToperator) inverte um valor booleano ( truetorna false- se e vice-versa).
!cconverte c(nó ou nulo) em um booleano antes de poder inverter esse valor. Portanto, adicionar um segundo !( !!c) converte esse falso de volta em verdadeiro (e é por isso que um duplo !!é frequentemente usado para 'converter qualquer coisa em booleano').


Extra:
O corpo / carga útil da função é tão pequeno que, dependendo do caso (como quando não é usado frequentemente e aparece apenas uma vez no código), pode-se até omitir a função (quebra automática) e usar o loop while:

var a=document.getElementById('child'),
    b=document.getElementById('parent'),
    c;

c=a; while((c=c.parentNode)&&c!==b); //c=!!c;

if(!!c){ //`if(c)` if `c=!!c;` was used after while-loop above
    //do stuff
}

ao invés de:

var a=document.getElementById('child'),
    b=document.getElementById('parent'),
    c;

function childOf(c,p){while((c=c.parentNode)&&c!==p);return !!c}

c=childOf(a, b);    

if(c){ 
    //do stuff
}
GitaarLAB
fonte
4
@SolomonUcko: A comparação estrita da igualdade ( !==) pode melhorar a velocidade, deixando claro para o compilador que pode pular as etapas de verificação de tipo e conversão implícita opcionais que ocorreriam em uma comparação de igualdade frouxa, melhorando assim a velocidade (descrevendo com mais precisão o que quer que aconteça, o que também é benéfico para o programador). Também me lembro de quando escrevi isso, que apenas a comparação frouxa ( !=) era muito suscetível a erros ou não funcionava em alguns navegadores mais antigos (suspeito que fosse no IE6, mas esqueci )
GitaarLAB
29

Outra solução que não foi mencionada:

Exemplo Aqui

var parent = document.querySelector('.parent');

if (parent.querySelector('.child') !== null) {
    // .. it's a child
}

Não importa se o elemento é um filho direto, ele funcionará a qualquer profundidade.


Como alternativa, usando o .contains()método :

Exemplo Aqui

var parent = document.querySelector('.parent'),
    child = document.querySelector('.child');

if (parent.contains(child)) {
    // .. it's a child
}
Josh Crozier
fonte
19

Dê uma olhada no Node # compareDocumentPosition .

function isDescendant(ancestor,descendant){
    return ancestor.compareDocumentPosition(descendant) & 
        Node.DOCUMENT_POSITION_CONTAINS;
}

function isAncestor(descendant,ancestor){
    return descendant.compareDocumentPosition(ancestor) & 
        Node.DOCUMENT_POSITION_CONTAINED_BY;
}

Outros relacionamentos incluem DOCUMENT_POSITION_DISCONNECTED, DOCUMENT_POSITION_PRECEDINGeDOCUMENT_POSITION_FOLLOWING .

Não suportado no IE <= 8.


fonte
interessante! Isso me lembra uma função JS que eu desenvolvi no ano de 2003 ... Levei alguns dias e recebi ajuda de alguns desenvolvedores de JS ... Incrível (e triste) que hoje em dia ainda não haja uma execução completa de arrastar e soltar nem de navegador adequado ao objeto .
DavidTaubmann
17

Você pode usar o método contains

var result = parent.contains(child);

ou você pode tentar usar compareDocumentPosition ()

var result = nodeA.compareDocumentPosition(nodeB);

O último é mais poderoso: retorna uma máscara de bit como resultado.

Iegor Kozakov
fonte
2

Me deparei com um maravilhoso pedaço de código para verificar se um elemento é ou não filho de outro elemento. Eu tenho que usar isso porque o IE não suporta o.contains método elemento. Espero que isso ajude outras pessoas também.

Abaixo está a função:

function isChildOf(childObject, containerObject) {
  var returnValue = false;
  var currentObject;

  if (typeof containerObject === 'string') {
    containerObject = document.getElementById(containerObject);
  }
  if (typeof childObject === 'string') {
    childObject = document.getElementById(childObject);
  }

  currentObject = childObject.parentNode;

  while (currentObject !== undefined) {
    if (currentObject === document.body) {
      break;
    }

    if (currentObject.id == containerObject.id) {
      returnValue = true;
      break;
    }

    // Move up the hierarchy
    currentObject = currentObject.parentNode;
  }

  return returnValue;
}
RashFlash
fonte
Eu literalmente tentei todos os outros polyfill nesta página, incluindo .contains, e este é o único que funcionou. Obrigado!
ProtoEvangelion
1

tente este:

x = document.getElementById("td35");
if (x.childElementCount > 0) {
    x = document.getElementById("LastRow");
    x.style.display = "block";
}
else {
    x = document.getElementById("LastRow");
    x.style.display = "none";
}
user3675072
fonte
-2

Recentemente, deparei-me com esta função que pode fazer:

Node.compareDocumentPosition ()

ZioCain
fonte
2
Voto negativo porque a mesma resposta já existia várias vezes com mais informações do que apenas um link externo.
Stefan Fabian