Encontrar o elemento mais próximo sem jQuery

88

Estou tentando encontrar o elemento mais próximo com um nome de marca específico sem jquery. Quando clico em <th>, quero ter acesso ao<tbody> para essa mesa. Sugestões? Eu li sobre deslocamento, mas não entendi muito. Devo apenas usar:

Suponha que já esteja configurado para clicar no elemento

th.offsetParent.getElementsByTagName('tbody')[0]
caçador
fonte
1
Se você achar que deve começar a percorrer o DOM, esta é uma instância em que os kbs extras atribuídos a jquery valem a pena.
Kevin Bowersox
2
Acho que essa é uma questão muito importante e válida. Não há motivo para votos negativos.
el.closest('tbody')para navegadores não-ie. Veja resposta + polyfill mais elaborada abaixo.
oriadam

Respostas:

69

Um pouco (muito) atrasado para a festa, mas mesmo assim. Isso deve fazer o truque :

function closest(el, selector) {
    var matchesFn;

    // find vendor prefix
    ['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
        if (typeof document.body[fn] == 'function') {
            matchesFn = fn;
            return true;
        }
        return false;
    })

    var parent;

    // traverse parents
    while (el) {
        parent = el.parentElement;
        if (parent && parent[matchesFn](selector)) {
            return parent;
        }
        el = parent;
    }

    return null;
}
Ales
fonte
2
Ótima resposta que funciona muito bem. MDN também tem um polyfill para element.closest (). O Chrome inclui element.matches () na versão atual, portanto, nenhum prefixo é necessário. Acabei de adicionar isso a uma biblioteca que estou usando em um aplicativo que estou desenvolvendo, o Clibu.
nevf
2
Eu mudei o código para que ele também teste o elemento elque jQuery.closest () e também Element.closest () fazem. for( var parent = el ; parent !== null && !parent[matchesFn](selector) ; parent = el.parentElement ){ el = parent; } return parent;
nevf
1
Este código é bom, mas está faltando um ponto e vírgula e também está definido parentno escopo global (!)
Steven Lu
1
Vale a pena mencionar: developer.mozilla.org/en-US/docs/Web/API/Element/closest o método nativo para o mais próximo, embora com pouco suporte: Chrome41, FF35, IE-nope, Opera28, Safari9
Michiel D
Apenas uma nota sobre isso, você pode querer fazer el.parentNodeou isso irá falhar ao percorrer um SVG no IE.
smcka
122

Muito simples:

el.closest('tbody')

Suportado em todos os navegadores, exceto IE.
ATUALIZAR : o Edge agora também oferece suporte.

Não há necessidade de jQuery. Além disso, substituir o jQuery $(this).closest('tbody')por $(this.closest('tbody'))aumentará o desempenho, significativamente quando o elemento não for encontrado.

Polyfill para IE:

if (!Element.prototype.matches) Element.prototype.matches = Element.prototype.msMatchesSelector;
if (!Element.prototype.closest) Element.prototype.closest = function (selector) {
    var el = this;
    while (el) {
        if (el.matches(selector)) {
            return el;
        }
        el = el.parentElement;
    }
};

Observe que não há nenhum returnquando o elemento não foi encontrado, efetivamente retornando undefinedquando o elemento mais próximo não foi encontrado.

Para obter mais detalhes, consulte: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

Oriadam
fonte
4
Por que essa resposta está no final da página? Deve estar no topo. Muito obrigado
Julien Le Coupanec
mais próximo está em jQuery.But OP diz que não há jQuery.
Steve Moretz
@stevemoretz. mais próximo é o JavaScript nativo agora
Louise Eggleton
@LouiseEggleton Sim, oops
steve moretz
Essa é a melhor resposta!
Md. Tahazzot
22

Veja como obter o elemento mais próximo por nome de tag sem jQuery:

function getClosest(el, tag) {
  // this is necessary since nodeName is always in upper case
  tag = tag.toUpperCase();
  do {
    if (el.nodeName === tag) {
      // tag name is found! let's return it. :)
      return el;
    }
  } while (el = el.parentNode);

  // not found :(
  return null;
}

getClosest(th, 'tbody');
Joon
fonte
2
Eu não acredito que isso funcione. Ele apenas verifica a árvore DOM. th-> thead-> table nunca considera irmãos
hunterc
2
Você deveria ter declarado esse caso específico em sua pergunta.
Joon
2
Veja. Esta função percorre parentNodes para encontrar o pai mais próximo (mesmo por extensão) que usa a tag fornecida. Isso não vai "descer" a árvore do documento, apenas "subir". O usuário médio veria o nó "mais próximo" provavelmente como o irmão mais próximo. Ele simplesmente não conhece a terminologia de que precisava.
1
Para ser justo com @Jhawins, sua definição de mais próximo é o que os termos do jQuery mais se aproximam. Esta não é uma questão jQuery. Não posso atestar por que jQuery decidiu que mais próximo significava o ancestral mais próximo, mas uma definição mais razoável seria o elemento mais próximo, seja um pai, um irmão anterior, um irmão seguinte, etc. O que quer que seja encontrado "mais próximo" do elemento alvo.
Ryandlf
1
@ryandlf Mas e se você tivesse um pai e um irmão na mesma "distância"? A definição do jQuery é clara, pois retornará uma correspondência no máximo.
mtone
9

Existe uma função padronizada para fazer isso: Element.closest . A maioria dos navegadores, exceto o IE11, oferece suporte a ele ( detalhes em caniuse.com ). Os documentos MDN também incluem um polyfill no caso de você ter que direcionar os navegadores mais antigos.

Para encontrar o tbodypai / mãe mais próximo dado um, thvocê pode fazer:

th.closest('tbody');

Caso você queira escrever a função sozinho - aqui está o que eu criei:

function findClosestParent (startElement, fn) {
  var parent = startElement.parentElement;
  if (!parent) return undefined;
  return fn(parent) ? parent : findClosestParent(parent, fn);
}

Para encontrar o pai mais próximo pelo nome da tag, você pode usá-lo assim:

findClosestParent(x, element => return element.tagName === "SECTION");
david1995
fonte
6
function closest(el, sel) {
    if (el != null)
        return el.matches(sel) ? el 
            : (el.querySelector(sel) 
                || closest(el.parentNode, sel));
}

Esta solução usa alguns dos recursos mais recentes da especificação HTML 5, e usá-la em navegadores mais antigos / incompatíveis (leia-se: Internet Explorer) exigirá um polyfill.

Element.prototype.matches = (Element.prototype.matches || Element.prototype.mozMatchesSelector 
    || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector 
    || Element.prototype.webkitMatchesSelector || Element.prototype.webkitMatchesSelector);
Matthew James Davis
fonte
Sempre vai para o topo da primeira mesa. não a tabela mais próxima .. veja este link jsfiddle.net/guuy5kof
Balachandran
boa pegada! corrigido, veja aqui jsfiddle.net/c2pqc2x0 por favor me vote como um chefe :)
Matthew James Davis
sim eu vou fazer ... Você verificou o resultado do violino, ele mostra um erro ..matches undefined
Balachandran
funciona muito bem para mim, em qual navegador você está trabalhando? isso não é compatível com navegadores mais antigos
Matthew James Davis
Ei, vamos deixar claro que downvote o que você diria mano
Matthew James Davis
3

Para estender a resposta @SalmanPK

permitirá usar o nó como seletor, útil ao trabalhar com eventos como mouseover.

function closest(el, selector) {
    if (typeof selector === 'string') {
        matches = el.webkitMatchesSelector ? 'webkitMatchesSelector' : (el.msMatchesSelector ? 'msMatchesSelector' : 'matches');
        while (el.parentElement) {
            if (el[matches](selector)) {
                return el
            };
            el = el.parentElement;
        }
    } else {
        while (el.parentElement) {
            if (el === selector) {
                return el
            };
            el = el.parentElement;
        }
    }

    return null;
}
zb '
fonte
2

Esta é a função simples que estou usando: -

function closest(el, selector) {
    var matches = el.webkitMatchesSelector ? 'webkitMatchesSelector' : (el.msMatchesSelector ? 'msMatchesSelector' : 'matches');

    while (el.parentElement) {
        if (el[matches](selector)) return el;

        el = el.parentElement;
    }

    return null;
}
Salman von Abbas
fonte
2

Resumo:

Para encontrar um ancestral específico, podemos usar:

Element.closest();

Esta função usa uma string de seletor CSS como argumento. ele então retorna o ancestral mais próximo do elemento atual (ou o próprio elemento) que corresponde ao seletor CSS que foi passado nos argumentos. Se não houver ancestral, ele retornará null.

Exemplo:

const child = document.querySelector('.child');
// select the child

console.dir(child.closest('.parent').className);
// check if there is any ancestor called parent
<div class="parent">
  <div></div>
  <div>
    <div></div>
    <div class="child"></div>
  </div>
</div>

Willem van der Veen
fonte
1

Obtenha o elemento DOM mais próximo na árvore que contém uma classe, ID, atributo de dados ou tag. Inclui o próprio elemento. Compatível com o IE6.

var getClosest = function (elem, selector) {

    var firstChar = selector.charAt(0);

    // Get closest match
    for ( ; elem && elem !== document; elem = elem.parentNode ) {

        // If selector is a class
        if ( firstChar === '.' ) {
            if ( elem.classList.contains( selector.substr(1) ) ) {
                return elem;
            }
        }

        // If selector is an ID
        if ( firstChar === '#' ) {
            if ( elem.id === selector.substr(1) ) {
                return elem;
            }
        } 

        // If selector is a data attribute
        if ( firstChar === '[' ) {
            if ( elem.hasAttribute( selector.substr(1, selector.length - 2) ) ) {
                return elem;
            }
        }

        // If selector is a tag
        if ( elem.tagName.toLowerCase() === selector ) {
            return elem;
        }

    }

    return false;

};

var elem = document.querySelector('#some-element');
var closest = getClosest(elem, '.some-class');
var closestLink = getClosest(elem, 'a');
var closestExcludingElement = getClosest(elem.parentNode, '.some-class');
Chris Ferdinandi
fonte
Por que não usar um switch para as condições em firstCharvez de todas IF?
Rafael Herscovici
Por que usar um forloop obscuro ?
Rafael Herscovici
1

Encontre os elementos childNodes mais próximos.

closest:function(el, selector,userMatchFn) {
var matchesFn;

// find vendor prefix
['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
    if (typeof document.body[fn] == 'function') {
        matchesFn = fn;
        return true;
    }
    return false;
});
function findInChilds(el){
    if(!el) return false;
    if(el && el[matchesFn] && el[matchesFn](selector)

    && userMatchFn(el) ) return [el];
    var resultAsArr=[];
    if(el.childNodes && el.childNodes.length){
        for(var i=0;i< el.childNodes.length;i++)
        {
             var child=el.childNodes[i];
             var resultForChild=findInChilds(child);
            if(resultForChild instanceof Array){
                for(var j=0;j<resultForChild.length;j++)
                {
                    resultAsArr.push(resultForChild[j]);
                }
            } 
        }

    }
    return resultAsArr.length?resultAsArr: false;
}

var parent;
if(!userMatchFn || arguments.length==2) userMatchFn=function(){return true;}
while (el) {
    parent = el.parentElement;
    result=findInChilds(parent);
    if (result)     return result;

    el = parent;
}

return null;

}

MajidTaheri
fonte
0

Aqui.

function findNearest(el, tag) {
    while( el && el.tagName && el.tagName !== tag.toUpperCase()) {
        el = el.nextSibling;     
    } return el;
} 

Encontra apenas irmãos mais abaixo na árvore. Use previousSibling para ir para o outro lado Ou use variáveis ​​para percorrer os dois caminhos e retornar o que for encontrado primeiro. Você entendeu a idéia geral, mas se quiser atravessar parentNodes ou children se um irmão não corresponder, você também pode usar jQuery. Nesse ponto, vale facilmente a pena.

RonnyKnoxville
fonte
0

Um pouco tarde para a festa, mas como eu estava passando e respondendo a uma pergunta muito semelhante, deixo aqui minha solução - podemos dizer que é a closest()abordagem JQuery , mas em bom e velho JavaScript.

Não precisa de pollyfills e é navegadores mais antigos e amigável com o IE (:-)): https://stackoverflow.com/a/48726873/2816279

Pedro Ferreira
fonte
-4

Eu acho que o código mais fácil de pegar com jquery mais próximo:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
    $(document).ready(function () {
        $(".add").on("click", function () {
            var v = $(this).closest(".division").find("input[name='roll']").val();
            alert(v);
        });
    });
</script>
<?php

for ($i = 1; $i <= 5; $i++) {
    echo'<div class = "division">'
        . '<form method="POST" action="">'
        . '<p><input type="number" name="roll" placeholder="Enter Roll"></p>'
        . '<p><input type="button" class="add" name = "submit" value = "Click"></p>'
        . '</form></div>';
}
?>

Muito obrigado.

AL MaMun
fonte