none - queryselector javascript ejemplos
Prueba si un selector coincide con un elemento dado (7)
¿Hay alguna manera de probar si un selector coincidiría con un elemento DOM determinado? Preferiblemente, sin el uso de una biblioteca externa como Sizzle. Esto es para una biblioteca y me gustaría minimizar la cantidad de complementos de terceros requeridos para la biblioteca "core". Si termina requiriendo Sizzle, lo agregaré como un complemento a la biblioteca para aquellos que quieran la característica que habilitaría.
Por ejemplo, podría hacer algo como:
var element = <input name="el" />
matches("input[name=el]", element) == true
EDITAR : Después de pensarlo más, se me ocurrió una solución, esto técnicamente funciona, pero no parece óptimo en términos de eficiencia:
function matchesSelector(selector, element) {
var nodeList = document.querySelectorAll(selector);
for ( var e in nodeList ) {
return nodeList[e] === element;
}
return false;
}
Básicamente, la función consulta el documento completo con el selector dado y luego itera sobre nodeList. Si el elemento dado está en nodeList, devuelve true, y si no lo está, devolverá false.
Si alguien puede llegar a una respuesta más eficiente, con mucho gusto marcaría su respuesta como la respuesta.
EDITAR : Flavius Stef me señaló una solución específica para navegador para Firefox 3.6+, mozMatchesSelector . También encontré el equivalente para Chrome (la compatibilidad de la versión es desconocida, y puede que funcione o no en Safari u otros navegadores webkit): webkitMatchesSelector
, que es básicamente lo mismo que la implementación de Firefox. Todavía no he encontrado ninguna implementación nativa para los navegadores IE.
Para el ejemplo anterior, el uso sería:
element.(moz|webkit)MatchesSelector("input[name=el]")
Parece que el W3C también ha abordado esto en la especificación de nivel II de la API de selectores (todavía un borrador en este momento). matchesSelector
será un método en los elementos DOM una vez aprobado.
Uso de W3C: element.matchesSelector(selector)
Dado que esa especificación todavía es un borrador y hay un tiempo de retraso antes de que los navegadores populares implementen los métodos una vez que se convierta en el estándar, puede pasar un tiempo hasta que esto realmente se pueda utilizar. La buena noticia es que si utiliza alguno de los marcos populares, es probable que implementen esta funcionalidad sin tener que preocuparse por la compatibilidad con navegadores cruzados. Aunque eso no ayuda a aquellos de nosotros que no podemos incluir bibliotecas de terceros.
Marcos o bibliotecas que implementan esta funcionalidad:
http://www.prototypejs.org/api/element/match
http://developer.yahoo.com/yui/docs/YAHOO.util.Selector.html
http://docs.jquery.com/Traversing/is
http://extjs.com/deploy/dev/docs/output/Ext.DomQuery.html#Ext.DomQuery-methods
http://base2.googlecode.com/svn/doc/base2.html#/doc/!base2.DOM.Element.matchesSelector
En ausencia de xMatchesSelector
, estoy pensando en intentar agregar un estilo con el selector solicitado a un objeto styleSheet
, junto con alguna regla y valor arbitrario que probablemente ya no esté en uso. Luego, compruebe el estilo computed/currentStyle
del elemento para ver si ha heredado la regla CSS añadida. Algo como esto para IE:
function ieMatchesSelector(selector, element) {
var styleSheet = document.styleSheets[document.styleSheets.length-1];
//arbitrary value, probably should first check
//on the off chance that it is already in use
var expected = 91929;
styleSheet.addRule(selector, ''z-index: ''+expected+'' !important;'', -1);
var result = element.currentStyle.zIndex == expected;
styleSheet.removeRule(styleSheet.rules.length-1);
return result;
}
Probablemente haya un bolso lleno de gotcha con este método. Probablemente sea mejor encontrar alguna regla CSS oculta desconocida que tenga menos posibilidades de tener un efecto visual que z-index
, pero dado que se elimina casi inmediatamente después de establecerla, un breve parpadeo debería ser el único efecto secundario. También es menos probable que una regla más oscura sea anulada por un selector más específico, reglas de atributos de estilo u otras reglas importantes (si IE incluso lo admite). De todos modos, vale la pena intentarlo al menos.
Estoy lidiando con este problema ahora. Tengo que admitir IE8 con Javascript nativo, lo que presenta un curioso desafío: IE8 admite tanto querySelector como querySelectorAll, pero no coincide conSelector. Si su situación es similar, aquí hay una opción para que considere:
Cuando le entreguen el nodo DOM y un selector, haga una copia superficial del nodo y de su padre. Esto conservará todos sus atributos pero no hará copias de sus respectivos hijos.
Adjunte el nodo clonado al padre clonado. Use querySelector en el padre clonado: lo único que necesita buscar es el único nodo hijo que tiene, por lo que este proceso es constante. Devolverá el nodo hijo o no lo hará.
Eso se vería así:
function matchesSelector(node, selector)
{
var dummyNode = node.cloneNode(false);
var dummyParent = node.parent.cloneNode(false);
dummyParent.appendChild(dummyNode);
return dummyNode === dummyParent.querySelector(selector);
}
Puede valer la pena crear una cadena completa de padres con copia poco profunda hasta el nodo raíz y consultar la raíz ficticia (en su mayoría vacía) si desea poder probar la relación de su nodo con sus antepasados.
No estoy seguro de para qué parte de los selectores funcionaría esto, pero creo que sería bueno para cualquiera que no se preocupara por los hijos del nodo evaluado. YMMV.
- EDITAR -
Decidí escribir la función para copiar todo desde el nodo que se está probando hasta la raíz. Usando esto, se pueden emplear muchos más selectores. (Nada relacionado con hermanos, sin embargo)
function clonedToRoot(node)
{
dummyNode = node.cloneNode(false);
if(node.parentNode === document)
{
return {''root'' : dummyNode, ''leaf'' : dummyNode};
}
parent = clonedToRoot(node.parentNode).root;
parent.appendChild(dummyNode);
return {''root'' : parent, ''leaf'' : dummyNode};
}
function matchesSelector(node, selector)
{
testTree = clonedToRoot(node)
return testTree.leaf === testTree.root.querySelector(selector)
}
¡Agradecería a un experto que me explicara qué tipos de selectores hay que esto no cubriría!
La API de selectores del W3C ( http://www.w3.org/TR/selectors-api/ ) especifica document.querySelectorAll()
. Esto no es compatible con todos los navegadores, por lo que tendrá que buscar en google los que sí lo admitan: http://www.google.com/search?q=browsers+implementing+selector+api
Los navegadores modernos pueden hacerlo con la función document.querySelectorAll
.
Para beneficio de los que visitan esta página después de estos muchos años, esta funcionalidad ahora se implementa en todos los navegadores modernos como element.matches
sin prefijo de proveedor (excepto para MS para navegadores MS distintos de Edge 15 y webkit
para Android / KitKat). Ver http://caniuse.com/matchesselector .
Para un mejor rendimiento , use las implementaciones del navegador ( (moz|webkit|o|ms)matchesSelector
) cuando sea posible. Cuando no puede hacer eso, aquí hay una implementación manual.
Un caso importante a considerar es probar los selectores para los elementos que no están adjuntos al documento.
Aquí hay un enfoque que maneja esta situación. Si resulta que el element
en cuestión no está adjunto al documento, rastree el árbol para encontrar el antecesor más alto (el último parentNode
no nulo) y parentNode
en un DocumentFragment
. Luego, a partir de ese DocumentFragment
querySelectorAll
y vea si su element
está en NodeList
resultante.
Aquí está el código.
El documento
Aquí hay una estructura de documentos con la que trabajaremos. Tomaremos el .element
y .element
si coincide con los selectores li
y .container *
.
<!DOCTYPE html>
<html>
<body>
<article class="container">
<section>
<h1>Header 1</h1>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</section>
<section>
<h1>Header 2</h1>
<ul>
<li>one</li>
<li>two</li>
<li class="element">three</li>
</ul>
</section>
<footer>Footer</footer>
</article>
</body>
</html>
Búsqueda con document.querySelectorAll
Aquí hay una función matchesSelector
que usa document.querySelectorAll
.
// uses document.querySelectorAll
function matchesSelector(selector, element) {
var all = document.querySelectorAll(selector);
for (var i = 0; i < all.length; i++) {
if (all[i] === element) {
return true;
}
}
return false;
}
Esto funciona siempre que ese elemento esté en el document
.
// this works because the element is in the document
console.log("Part 1");
var element = document.querySelector(".element");
console.log(matchesSelector("li", element)); // true
console.log(matchesSelector(".container *", element)); // true
Sin embargo, falla si el elemento se elimina del document
.
// but they don''t work if we remove the article from the document
console.log("Part 2");
var article = document.querySelector("article");
article.parentNode.removeChild(article);
console.log(matchesSelector("li", element)); // false
console.log(matchesSelector(".container *", element)); // false
Buscando dentro de un DocumentFragment
La solución requiere buscar el subárbol en el que esté el element
. Aquí hay una función actualizada llamada matchesSelector2
.
// uses a DocumentFragment if element is not attached to the document
function matchesSelector2(selector, element) {
if (document.contains(element)) {
return matchesSelector(selector, element);
}
var node = element;
var root = document.createDocumentFragment();
while (node.parentNode) {
node = node.parentNode;
}
root.appendChild(node);
var all = root.querySelectorAll(selector);
for (var i = 0; i < all.length; i++) {
if (all[i] === element) {
root.removeChild(node);
return true;
}
}
root.removeChild(node);
return false;
}
Ahora vemos que matchesSelector2 funciona aunque el elemento se encuentre en un subárbol separado del documento.
// but they will work if we use matchesSelector2
console.log("Part 3");
console.log(matchesSelector2("li", element)); // true
console.log(matchesSelector2(".container *", element)); // true
Puedes ver esto trabajando en jsfiddle .
Poniendolo todo junto
Aquí está la implementación final que surgió:
function is(element, selector) {
var node = element;
var result = false;
var root, frag;
// crawl up the tree
while (node.parentNode) {
node = node.parentNode;
}
// root must be either a Document or a DocumentFragment
if (node instanceof Document || node instanceof DocumentFragment) {
root = node;
} else {
root = frag = document.createDocumentFragment();
frag.appendChild(node);
}
// see if selector matches
var matches = root.querySelectorAll(selector);
for (var i = 0; i < matches.length; i++) {
if (this === matches.item(i)) {
result = true;
break;
}
}
// detach from DocumentFragment and return result
while (frag && frag.firstChild) {
frag.removeChild(frag.firstChild);
}
return result;
}
Una nota importante es que la implementación de jQuery es mucho más rápida. La primera optimización que consideraría es evitar gatear por el árbol si no es necesario. Para hacer esto, podría mirar la parte más a la derecha del selector y probar si esto coincide con el elemento. Sin embargo, tenga en cuenta que si el selector es en realidad múltiples selectores separados por comas, entonces tendrá que probar cada uno. En este momento está construyendo un analizador de selector de CSS, por lo que también podría usar una biblioteca.
Solo usa una identificación para tu elemento? Los ID de HTML deben ser únicos ...