javascript - name - jquery selector examples
¿Es posible crear selectores jQuery personalizados que naveguen antepasados? Por ejemplo, a: más cercano o: selector de padres (2)
Escribo muchos complementos de jQuery y tengo selectores de jQuery personalizados que uso todo el tiempo, como :closeto
y :closeto
para proporcionar filtros de uso común.
Ej .: enfocable se ve así
jQuery.extend(jQuery.expr['':''], {
focusable: function (el, index, selector) {
return $(el).is(''a, button, :input[type!=hidden], [tabindex]'');
};
});
y se usa como cualquier otro selector:
$('':focusable'').css(''color'', ''red''); // color all focusable elements red
Noté que ninguno de los selectores de jQuery disponibles puede navegar por los ancestros. Supongo que es porque fueron diseñados para seguir las reglas básicas del selector de CSS que se detallan.
Tome este ejemplo: que encuentra la etiqueta para una entrada que tiene el foco:
$(''input:focus'').closest(''.form-group'').find(''.label'');
Necesito el tipo equivalente de selectores complejos para los complementos, por lo que sería útil proporcionar un selector de este tipo como una única cadena (para que pueda proporcionarse como opciones para el complemento).
por ejemplo, algo como:
$(''input:focus < .form-group .label'');
o
$(''input:focus:closest(.form-group) .label'');
Nota: suponga que se requieren operaciones más complejas y que se requiere la navegación de antepasados (me doy cuenta de que este ejemplo en particular se puede hacer con has
, pero eso no ayuda).
Por ejemplo, también necesita apoyar esto:
options.selector = '':closest(".form-group") .label'';
$(''input'').click(function(){
var label = $(this).find(options.selector);
});
¿Es posible extender los selectores de jQuery para extender el comportamiento de búsqueda (y no solo agregar más filtros booleanos)? ¿Cómo extiendes el comportamiento de búsqueda personalizado?
Actualizar:
Parece que un selector personalizado completo (como <
) no sería tan fácil como agregar un pseudo selector al analizador Sizzle de jQuery. Actualmente estoy viendo esta documentación de Sizzle , pero estoy encontrando inconsistencias con la versión jQuery. (Por ejemplo, no existe ninguna propiedad Sizzle.selectors.order
en el tiempo de ejecución).
Para referencia, jQuery almacena Sizzle
en su propiedad Sizzle.selectors
y Sizzle.selectors
en su propiedad jQuery.expr
.
Hasta ahora he añadido esto:
jQuery.expr.match.closest = /^:(?:closest)$/;
jQuery.expr.find.closest = function (match, context, isXML){
console.log("jQuery.expr.find.closest");
};
y llámelo con una prueba simple: http://jsfiddle.net/z3vwk1ko/2/
pero nunca llega a la declaración de console.log y sigo recibiendo "Syntax error, unrecognized expression: unsupported pseudo: closest"
. Al rastrear dentro de jQuery, está tratando de aplicarlo como un filter
lugar de un find
, por lo que me falta alguna parte clave.
Actualización 2:
El procesamiento para los selectores funciona de derecha a izquierda (consulte el extracto de jQuery 1.11.1 a continuación), por lo que si el último argumento no existe en el contexto, se cancela antes. Esto significa que navegar hacia arriba no se producirá con el código actual de jQuery Sizzle en el caso común en el que deseamos buscar un elemento en otra rama DOM de un antepasado:
// Fetch a seed set for right-to-left matching
i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length;
while (i--) {
token = tokens[i];
// Abort if we hit a combinator
if (Expr.relative[(type = token.type)]) {
break;
}
if ((find = Expr.find[type])) {
// Search, expanding context for leading sibling combinators
if ((seed = find(
token.matches[0].replace(runescape, funescape),
rsibling.test(tokens[0].type) && testContext(context.parentNode) || context
))) {
// If seed is empty or no tokens remain, we can return early
tokens.splice(i, 1);
selector = seed.length && toSelector(tokens);
if (!selector) {
push.apply(results, seed);
return results;
}
break;
}
}
}
Me sorprendió ver esto, pero ahora me doy cuenta de que hace que el motor de reglas sea mucho más fácil de escribir. Sin embargo, significa que debemos asegurarnos de que el extremo derecho de un selector sea lo más específico posible , ya que se evalúa primero. Todo lo que sucede después de eso es solo una poda progresiva del conjunto de resultados (siempre asumí que los primeros elementos del selector tenían que ser más específicos para aumentar la eficiencia).
Basándome en numerosos comentarios y en una explicación detallada de por qué esto es imposible , se me ocurrió que el objetivo que quería podría cumplirse con un $(document).find()
, pero con algún concepto de elementos específicos . Es decir, de alguna manera para apuntar a los elementos de consulta originales, dentro del selector.
Para ello se me ocurrió lo siguiente, un :this
selector, que funciona así (sin intención de juego de palabras):
// Find all labels under .level3 classes that have the .starthere class beneath them
$(''.starthere'').findThis(''.level3:has(:this) .label'')
¡Esto nos permite ahora, efectivamente, buscar el DOM y luego bajar a las ramas adyacentes en una única cadena de selección! es decir, hace el mismo trabajo que este (pero en un solo selector):
$(''.starthere'').parents(''.level3'').find(''.label'')
Pasos:
1 - Añadir un nuevo método jQuery.findThis
2 - Si el selector tiene :this
, sustituya una búsqueda de identificación y busque un document
lugar
3 - Si el selector no contiene un :this
proceso normalmente usa la find
original
4 - Prueba con selector como $(''.target'').find(''.ancestor:has(:this) .label'')
para seleccionar una etiqueta dentro del (de los) ancestro (s) del (los) elemento (s) seleccionado (s)
Esta es la versión revisada, basada en comentarios, que no reemplaza la búsqueda existente y utiliza una identificación única generada.
JSFiddle: http://jsfiddle.net/TrueBlueAussie/z3vwk1ko/36/
// Add findThis method to jQuery (with a custom :this check)
jQuery.fn.findThis = function (selector) {
// If we have a :this selector
if (selector.indexOf('':this'') > 0) {
var ret = $();
for (var i = 0; i < this.length; i++) {
var el = this[i];
var id = el.id;
// If not id already, put in a temp (unique) id
el.id = ''id''+ new Date().getTime();
var selector2 = selector.replace('':this'', ''#'' + el.id);
ret = ret.add(jQuery(selector2, document));
// restore any original id
el.id = id;
}
ret.selector = selector;
return ret;
}
// do a normal find instead
return this.find(selector);
}
// Test case
$(function () {
$(''.starthere'').findThis(''.level3:has(:this) .label'').css({
color: ''red''
});
});
Problemas conocidos:
Esto deja un atributo de
id
blanco en los elementos de destino que no tenían un atributo deid
para comenzar (esto no causa ningún problema, pero no es tan claro como me gustaría)Debido a la forma en que tiene que buscar en el documento, solo puede emular los
parents()
y no elclosest()
, pero tengo la sensación de que puedo usar un enfoque similar y agregar un pseudo selector:closest()
a este código.
Primera versión a continuación:
1 - Guardar el método de find
de jQuery para referencia
2 - Sustituir un nuevo método jQuery.find
3 - Si el selector tiene :this
, sustituya una búsqueda de identificación y busque un document
lugar
4 - Si el selector no contiene un :this
proceso normalmente usa la find
original
5 - Prueba con selector como $(''.target'').find(''.ancestor:has(:this)'')
para seleccionar el (los) ancestro (s) de los elementos seleccionados
JSFiddle: http://jsfiddle.net/TrueBlueAussie/z3vwk1ko/24/
// Save the original jQuery find we are replacing
jQuery.fn.findorig = jQuery.fn.find
// Replace jQuery find with a custom :this hook
jQuery.fn.find = function (selector) {
// If we have a :this selector
if (selector.indexOf('':this'') > 0) {
var self = this;
var ret = $();
for (var i = 0; i < this.length; i++) {
// Save any existing id on the targetted element
var id = self[i].id;
if (!id) {
// If not id already, put in a temp (unique) one
self[i].id = ''findme123'';
}
var selector2 = selector.replace('':this'', ''#findme123'');
ret = ret.add(jQuery(selector2, document));
// restore any original id
self[i].id = id;
}
ret.selector = selector;
return ret;
}
return this.findorig(selector);
}
// Test case
$(function () {
$(''.starthere'').find(''.level3:has(:this)'').css({
color: ''red''
});
});
Esto se basa en 6 horas de esclavo sobre el código fuente de jQuery / Sizzle, así que sé cuidadoso . Siempre me alegra saber cómo mejorar este find
reemplazo, ya que soy nuevo en los aspectos internos de jQuery :)
Ahora significa que puedo resolver el problema inicial de cómo hacer el ejemplo de etiqueta original:
options.selector = ".form-group:has(:this) .label";
$(''input'').click(function(){
var label = $(this).find(options.selector);
});
por ejemplo, http://jsfiddle.net/TrueBlueAussie/z3vwk1ko/25/
No es posible, y les diré por qué.
Para crear un selector personalizado necesitas algo como esto:
jQuery.extend(jQuery.expr['':''], {
focusable: function (el, index, selector) {
return $(el).is(''a, button, :input[type!=hidden], [tabindex]'');
};
})
La lógica del selector le permite seleccionar un subconjunto de elementos de la selección actual, por ejemplo, si $ ("div") selecciona TODOS los divs en la página, los siguientes dos ejemplos seleccionarán todos los divs sin elementos secundarios:
$("div:empty")
$("div").filter(":empty")
De acuerdo con la forma en que jQuery le permite escribir selectores personalizados, no hay forma de devolver elementos fuera del conjunto original, solo puede elegir verdadero o falso para cada elemento del conjunto cuando se le solicite. Por ejemplo, el selector ": contiene" se implementa de esta manera en la versión 2.1.1 de jQuery:
function ( text ) {
return function( elem ) {
return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
};
}
Pero ... (Tal vez quieras ver la última edición primero) Tal vez, encuentres útil un selector de TODOS los ancestros de un elemento dado, por ejemplo, imagina este escenario:
<element1>
<div>
<span id="myelementid"></span>
</div>
<div>
<p>Lorem ipsum sir amet dolor... </p>
</div>
</element1>
<element2>
</element2>
Y, si quieres algo relacionado con el padre div de myelementid, element1 y todos los ancestros, etc ..., puedes usar el selector "has", de esta manera:
$("*:has(''#myelementid'')")
El primer selector devolvería TODOS los elementos, filtrado por "tiene" devolverá todos los elementos que tengan #myelementid como descendiente, ergo, todos los antepasados de #myelementid. Si está preocupado por el rendimiento (y tal vez debería), necesita reemplazar el ''*'' con algo más específico, tal vez esté buscando div
$("div:has(''#myelementid'')")
O para una clase dada
$(".agivenclass:has(''#myelementid'')")
espero que esto ayude
EDITAR
Puede usar el "selector" de los padres () de jquery, ya que devuelve TODOS los ancestros del elemento, incluso si se incluye el elemento raíz del documento (es decir, la etiqueta html), de esta manera:
$(''#myelementid'').parents()
Si desea filtrar ciertos elementos (por ejemplo, divs), puede usar esto:
$(''#myelementid'').parents(null, "div")
O esto:
$(''#myelementid'').parents().filter("div")