Encontrar el elemento más cercano sin jQuery

90

Estoy tratando de encontrar el elemento más cercano con un nombre de etiqueta específico sin jquery. Cuando hago clic en un <th>, quiero tener acceso al <tbody>para esa tabla. Sugerencias Leí sobre la compensación, pero realmente no lo entendí demasiado. Debería usar:

Suponga que th ya está configurado para hacer clic en el elemento

th.offsetParent.getElementsByTagName('tbody')[0]
hunterc
fuente
1
Si encuentra que debe comenzar a atravesar el DOM, esta es una instancia en la que los kbs adicionales atribuidos a jquery valdrían la pena.
Kevin Bowersox
1
intente parentNode: developer.mozilla.org/en-US/docs/Web/API/Node.parentNode
Ivan Chernykh
2
Creo que esta es una pregunta muy importante y válida. No hay motivo para votos negativos.
el.closest('tbody')para navegadores que no son ie. Vea una respuesta más elaborada + polyfill a continuación.
Oriadam

Respuestas:

69

Poco (muy) tarde para la fiesta, pero no obstante. Esto debería hacer el truco :

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
fuente
2
Gran respuesta que funciona de maravilla. MDN también tiene un polyfill para element.closest (). Chrome incluye element.matches () en la versión actual, por lo que no se necesita ningún prefijo. Acabo de agregar esto a una biblioteca que estoy usando en una aplicación que estoy desarrollando, Clibu.
nevf
2
elCambié el código para que también pruebe el elemento que jQuery.closest () y también Element.closest (). for( var parent = el ; parent !== null && !parent[matchesFn](selector) ; parent = el.parentElement ){ el = parent; } return parent;
nevf
1
Este código está bien, pero le falta un punto y coma y también se define parenten el alcance global (!)
Steven Lu
1
Vale la pena mencionar: developer.mozilla.org/en-US/docs/Web/API/Element/closest el método nativo para el soporte más cercano, aunque poco bajo: Chrome41, FF35, IE-nope, Opera28, Safari9
Michiel D
Solo una nota sobre esto, es posible que desee hacer el.parentNodeo esto se romperá al atravesar un SVG en IE.
smcka
124

Muy simple:

el.closest('tbody')

Compatible con todos los navegadores excepto IE.
ACTUALIZACIÓN : Edge ahora también lo admite.

No es necesario jQuery. Además, reemplazar jQuery $(this).closest('tbody')con $(this.closest('tbody'))aumentará el rendimiento, significativamente cuando no se encuentre el elemento.

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;
    }
};

Tenga en cuenta que no hay returncuando no se encontró el elemento, regresando efectivamente undefinedcuando no se encontró el elemento más cercano.

Para obtener más detalles, consulte: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

Oriadam
fuente
4
¿Por qué esta respuesta al final de la página? Debería estar en la cima. Muchas gracias
Julien Le Coupanec
más cercano está en jQuery. Pero OP dice que no jQuery.
steve moretz
@stevemoretz. más cercano es JavaScript nativo ahora
Louise Eggleton
@LouiseEggleton Sí lo es, oops
steve moretz
¡Eso se llama la mejor respuesta!
ChosenUser
22

Así es como se obtiene el elemento más cercano por nombre de etiqueta sin 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
fuente
2
No creo que esto funcione. Solo verifica el árbol DOM. th-> thead-> table nunca considera a los hermanos
hunterc
2
Deberías haber indicado ese caso específico en tu pregunta.
Joon
2
Mira. Esta función atraviesa parentNodes para encontrar el padre más cercano (incluso por extensión) que usa la etiqueta proporcionada. Esto no irá "hacia abajo" en el árbol del documento, solo "hacia arriba". El usuario promedio vería el nodo "más cercano" probablemente como el hermano más cercano. Simplemente no conoce la terminología que necesitaba.
1
Para ser justos con @Jhawins, su definición de más cercano es lo que jQuery términos más cercano. Esta no es una pregunta de jQuery. No puedo dar fe de por qué jQuery decidió que más cercano significaba el ancestro más cercano, pero una definición más razonable sería el elemento más cercano, ya sea un padre, un hermano anterior, un hermano siguiente, etc. Lo que se encuentre "más cercano" al elemento objetivo.
ryandlf
1
@ryandlf ¿Pero qué pasaría si tuvieras un padre y un hermano a la misma "distancia"? La definición de jQuery es clara en que devolverá una coincidencia como máximo.
mtone
9

Existe una función estandarizada para hacer esto: Element.closest . La mayoría de los navegadores, excepto IE11, lo admiten ( detalles por caniuse.com ). Los documentos de MDN también incluyen un polyfill en caso de que tenga que apuntar a navegadores más antiguos.

Para encontrar el tbodypadre más cercano dado thque podría hacer:

th.closest('tbody');

En caso de que desee escribir la función usted mismo, esto es lo que se me ocurrió:

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

Para encontrar el padre más cercano por nombre de etiqueta, puede usarlo así:

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

Esta solución utiliza algunas de las características más recientes de la especificación HTML 5, y su uso en navegadores más antiguos / incompatibles (lea: Internet Explorer) requerirá un 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
fuente
Siempre va a la primera mesa. no es la mesa más cercana .. ver este enlace jsfiddle.net/guuy5kof
Balachandran
¡buena atrapada! arreglado, vea aquí jsfiddle.net/c2pqc2x0, por favor, voten como un jefe :)
Matthew James Davis
ya i enfermedad hace ... ¿Ha comprobado el resultado de su violín, que muestra ..matches Error no definido
Balachandran
funciona muy bien para mí, ¿en qué navegador estás trabajando? esto no es compatible con navegadores más antiguos
Matthew James Davis
hey deja claro que downvote qué dices hermano
Matthew James Davis
3

Para extender la respuesta de @SalmanPK

permitirá usar el nodo como selector, útil cuando se trabaja con eventos como el 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 '
fuente
2

Aquí está la función simple que estoy 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
fuente
2

Resumen:

Para encontrar un antepasado en particular, podemos usar:

Element.closest();

Esta función toma una cadena de selección de CSS como argumento. luego devuelve el ancestro más cercano del elemento actual (o el elemento en sí) que coincide con el selector de CSS que se pasó en los argumentos. Si no hay antepasado, regresará null.

Ejemplo:

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
fuente
1

Obtenga el elemento DOM más cercano en el árbol que contiene una clase, ID, atributo de datos o etiqueta. Incluye el propio elemento. Compatible con 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
fuente
¿Por qué no utilizar un interruptor para las condiciones en firstCharlugar de para todas IF?
Rafael Herscovici
¿Por qué utilizar un forbucle oculto ?
Rafael Herscovici
1

Encuentre los childNodes de Elements más cercanos.

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
fuente
0

Aquí.

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

Solo encuentra hermanos más abajo en el árbol. Use previousSibling para ir al revés O use variables para recorrer ambos caminos y regresar lo que se encuentre primero. Obtiene la idea general, pero si desea atravesar parentNodes o hijos si un hermano no coincide, también puede usar jQuery. En ese punto, fácilmente vale la pena.

RonnyKnoxville
fuente
0

Un poco tarde para la fiesta, pero mientras pasaba y respondía una pregunta muy similar, dejo aquí mi solución: podemos decir que es el closest()enfoque de JQuery , pero en JavaScript.

No necesita pollyfills y son navegadores más antiguos y compatible con IE (:-)): https://stackoverflow.com/a/48726873/2816279

Pedro Ferreira
fuente
-4

Creo que el código más fácil de atrapar con jquery más cercano:

<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>';
}
?>

Muchas gracias.

AL MaMun
fuente