Omitir recursion en jQuery.find () para un selector?

TL; DR: ¿Cómo obtengo una acción como find (), pero locking transversal (no completo, solo salto) para un cierto selector?

RESPUESTAS: $(Any).find(Selector).not( $(Any).find(Mask).find(Selector) )

Hubo muchas respuestas realmente buenas, desearía poder distribuir más los puntos de recompensa, tal vez debería hacer algunas recompensas de 50 puntos en respuesta a algunos de ellos; p Escogí la de Karl-André Gagnon porque esta respuesta logró hacer que no se requiriera encontrar Excluir una, ligeramente larga, línea. Si bien esto utiliza tres llamadas de búsqueda y un filter pesado no, en la mayoría de los casos jQuery puede usar una implementación muy rápida que omite el recorrido para la mayoría de los find () s.

Las respuestas especialmente buenas se enumeran a continuación:

falsarella : Buena mejora en mi solución , findExclude (), mejor en muchas situaciones

Zbyszek : una solución basada en filters similar a falsarella, también es buena en eficiencia

Justin : una solución completamente diferente, pero manejable y funcional para los problemas subyacentes

Cada uno de estos tiene sus propios méritos únicos y merecen alguna mención.

Necesito descender a un elemento por completo y comparar selectores, devolviendo todos los selectores coincidentes como una matriz, pero omito descender al tree cuando se encuentra otro selector.

Ilustración de ruta de selección Editar: reemplazando la muestra del código original con algunas de mi sitio

Esto es para un foro de posts que puede tener grupos de posts de respuesta nesteds dentro de cualquier post.
Sin embargo, tenga en count que no podemos usar el post o las classs de contenido porque el script también se usa para otros componentes fuera del foro. Solo las classs InterfaceGroup , Interface y controls son potencialmente útiles, y preferiblemente solo interfaces y controles.

Interactúe con el código y véalo en JS Fiddle, gracias Dave A, aquí Haga clic en los botones mientras visualiza una console de JavaScript para ver que la class de controles está vinculada a un time adicional por nivel de anidación entre interfaces.

Visual A, Estructura del foro Struture:

  <li class="InterfaceGroup"> <ul class="Interface Message" data-role="MessagePost" > <li class="instance"> ... condensed ... </li> <li class="InterfaceGroup"> ... condensed ...</li> </ul> <ul class="Interface Message" data-role="MessagePost" > <li class="instance"> ... condensed ... </li> </ul> <ul class="Interface Message" data-role="MessagePost" > <li class="instance"> ... condensed ... </li> <li class="InterfaceGroup"> ... condensed ...</li> </ul> </li> 

Dentro de cada <li class="InterfaceGroup"> puede haber cualquier cantidad de repeticiones de la misma estructura (cada grupo es un hilo de posts) y / o una anidación más profunda, como …

  <li class="InterfaceGroup"> <ul class="Interface Message" data-role="MessagePost" > <li class="instance"> ... condensed ... </li> <li class="InterfaceGroup"> <ul class="Interface Message" data-role="MessagePost" > <li class="instance"> ... condensed ... </li> <li class="InterfaceGroup"> ... condensed ...</li> </ul> </li> </ul> </li> 

Dentro de cada <li class="instance"> ... </li> hay lugares arbitrarios decididos por otro equipo donde class="controls" puede aparecer y un event listener debe estar vinculado. Aunque estos contienen posts, otros componentes estructuran su marcado arbitrariamente, pero siempre tendrán .controls dentro de .Interface , que se recostackn en .InterfaceGroup . Una versión de complejidad networkingucida del contenido interno (para publicaciones en el foro) se encuentra a continuación como reference.

Visual B, contenido del post con class de controles:

 <ul class="Interface Message" data-role="MessagePost" > <li class="instance"> <ul class="profile"> ...condensed, nothing clickable...</ul> <ul class="contents"> <li class="heading"><h3>Hi there!</h3></li> <li class="body"><article>TEST Message here</article></li> <li class="vote controls"> <button class="up" data-role="VoteUp" ><i class="fa fa-caret-up"> </i><br/>1</button> <button class="down" data-role="VoteDown" >0<br/><i class="fa fa-caret-down"> </i></button> </li> <li class="social controls"> <button class="reply-btn" data-role="ReplyButton" >Reply</button> </li> </ul> </li> <li class="InterfaceGroup" > <!-- NESTING OCCURRED --> <ul class="Interface Message" data-role="MessagePost" > <li class="instance">... condensed ... </li> <li class="InterfaceGroup" >... condensed ... </li> </ul> </li> </ul> 

Solo podemos enlazar a controles que están dentro de una class de interfaz , la instance puede existir o no, pero Interface lo hará. Los events de burbuja a .controls elementos y tienen una reference a la .Interface que los contiene. .

Así que estoy tratando de $('.Interface').each( bind to any .controls not inside a deeper .Interface )

Esa es la parte difícil, porque

  • .Interface .controls seleccionará el mismo .control varias veces en .each ()
  • .not ('. Interface .Interface .controls') cancela los controles en cualquier anidación más profunda

¿Cómo puedo hacer esto usando jQuery.find () o un método jQuery similar para esto?

He estado considerando que, tal vez, usar niños con un selector no podría funcionar y podría estar haciendo lo mismo que encontrar debajo del capó, pero no estoy tan seguro de que realmente sea o no cause un performance horrible. Aún así, una respuesta recursiva de .children es aceptable.

ACTUALIZACIÓN: Originalmente traté de usar un psuedo-example para abreviar, pero con suerte ver una estructura de foro ayudará a aclarar el problema ya que son estructuras anidadas naturalmente. A continuación, también estoy publicando javascript parcial como reference, la línea dos de la function de inicio es la más importante.

Reducción de JavaScript parcial:

 var Interface=function() { $elf=this; $elf.call= { init:function(Markup) { $elf.Interface = Markup; $elf.Controls = $(Markup).find('.controls').not('.Interface .controls'); $elf.Controls.on('click mouseenter mouseleave', function(event){ $elf.call.events(event); }); return $elf; }, events:function(e) { var classlist = e.target.className.split(/\s+/), c=0, L=0; var role = $(e.target).data('role'); if(e.type == 'click') { CurrentControl=$(e.target).closest('[data-role]')[0]; role = $(CurrentControl).data('role'); switch(role) { case 'ReplyButton':console.log('Reply clicked'); break; case 'VoteUp':console.log('Up vote clicked'); break; case 'VoteDown':console.log('Down vote clicked'); break; default: break; } } } } }; $(document).ready( function() { $('.Interface').each(function(instance, Markup) { Markup.Interface=new Interface().call.init(Markup); }); } ); 

Si desea excluir un elemento en su búsqueda, puede utilizar un filter no. Como por ejemplo, tomé la function que excluye el elemento y lo acorté:

 $.fn.findExclude = function( Selector, Mask,){ return this.find(Selector).not(this.find(Mask).find(Selector)) } 

Ahora, ser honesto contigo, no entendí completamente lo que quieres. Pero, cuando eché un vistazo a tu function, vi lo que estabas tratando de hacer.

De todos modos, eche un vistazo a este violín, el resultado es el mismo que su: http://jsfiddle.net/KX65p/8/

Bueno, realmente no quiero estar respondiendo mi propia pregunta sobre una recompensa, así que si alguien puede proporcionar una implementación mejor o alternativa, por favor …

Sin embargo, al ser presionado para completar el proyecto, terminé trabajando en esto un poco y obtuve un plugin jQuery bastante limpio para hacer una búsqueda de estilo jQuery.find () mientras excluía las twigs secundarias de los resultados sobre la marcha.

Uso para trabajar con sets de elementos dentro de vistas anidadas:

 // Will not look in nested ul's for inputs $('ul').findExclude('input','ul'); // Will look in nested ul's for inputs unless it runs into class="potato" $('ul').findExclude('input','.potato'); 

Ejemplo más complejo encontrado en http://jsfiddle.net/KX65p/3/ donde uso esto para .each () una class anidada y unir elementos que ocurren en cada vista anidada a una class. Esto me permite hacer que los componentes del lado del server y del lado del cliente reflejen las properties de los demás y tengan un event handling events nesteds más barato.

Implementación:

 // Find-like method which masks any descendant // branches matching the Mask argument. $.fn.findExclude = function( Selector, Mask, result){ // Default result to an empty jQuery object if not provided result = typeof result !== 'undefined' ? result : new jQuery(); // Iterate through all children, except those match Mask this.children().each(function(){ thisObject = jQuery( this ); if( thisObject.is( Selector ) ) result.push( this ); // Recursively seek children without Mask if( !thisObject.is( Mask ) ) thisObject.findExclude( Selector, Mask, result ); }); return result; } 

(Versión condensada) :

 $.fn.findExclude = function( selector, mask, result ) { result = typeof result !== 'undefined' ? result : new jQuery(); this.children().each( function(){ thisObject = jQuery( this ); if( thisObject.is( selector ) ) result.push( this ); if( !thisObject.is( mask ) ) thisObject.findExclude( selector, mask, result ); }); return result; } 

Tal vez algo como esto funcionaría:

 $.fn.findExclude = function (Selector, Mask) { var result = new jQuery(); $(this).each(function () { var $selected = $(this); $selected.find(Selector).filter(function (index) { var $closest = $(this).closest(Mask); return $closest.length == 0 || $closest[0] == $selected[0] || $.contains($closest, $selected); }).each(function () { result.push(this); }); }); return result; } 

http://jsfiddle.net/JCA23/

Elige los elementos que no están en la máscara padre o su máscara más cercana padre es lo mismo que la raíz o su máscara más cercana es padre de la raíz.

Creo que esto es lo más findExclude se puede optimizar findExclude :

 $.fn.findExclude = function (Selector, Mask) { var result = $([]); $(this).each(function (Idx, Elem) { $(Elem).find(Selector).each(function (Idx2, Elem2) { if ($(Elem2).closest(Mask)[0] == Elem) { result = result.add(Elem2); } }); }); return result; } 

Además, vea su violín con loggings agregados con time transcurrido en milisegundos.

Veo que estás preocupado con las actuaciones. Por lo tanto, he realizado algunas testings, y esta implementación no lleva más de 2 milisegundos, mientras que su implementación (como la respuesta que ha publicado) a veces toma alnetworkingedor de 4 ~ 7 milisegundos.

Según tengo entendido, me vincularía a los elementos .controls y permitiría que el evento les .controls encima. A partir de eso, puede get el más cercano. .Interface para get el padre, si es necesario. De esta manera, se le agregan varios manejadores a los mismos elementos a medida que avanza en el agujero del conejo.

Mientras lo vi mencionarlo, nunca lo vi implementado.

 //Attach the event to the controls to minimize amount of binded events $('.controls').on('click mouseenter mouseleave', function (event) { var target = $(event.target), targetInterface = target.closest('.Interface'), role = target.data('role'); if (event.type == 'click') { if (role) { switch (role) { case 'ReplyButton': console.log('Reply clicked'); break; case 'VoteUp': console.log('Up vote clicked'); break; case 'VoteDown': console.log('Down vote clicked'); break; default: break; } } } }); 

Aquí hay un violín que muestra a qué me refiero. Quité tu js a favor de una pantalla simplificada.

Parece que mi solución puede ser una simplificación excesiva, aunque …


Actualización 2

Así que aquí hay un violín que define algunas funciones comunes que ayudarán a lograr lo que estás buscando … Creo. El getInterfaces proporciona una function simplificada para encontrar las interfaces y sus controles, suponiendo que todas las interfaces siempre tienen controles.

Sin embargo, es probable que haya casos marginales que se arrastrarán. ¡También siento que necesito disculparme si ya te has aventurado por este path y simplemente no estoy viendo / entendiendo!


Actualización 3

Bien bien. Creo que entiendo lo que quieres. Desea get las interfaces únicas y tener una colección de controles que le pertenecen, que tienen sentido ahora.

Usando este violín como ejemplo, seleccionamos tanto el .Interface como el .Interface .controls .

 var interfacesAndControls = $('.Interface, .Interface .controls'); 

De esta manera, tenemos una colección orderada de las interfaces y los controles que les pertenecen para que aparezcan en el DOM. Con esto podemos recorrer la colección y verificar si el elemento actual tiene la .Interface asociada. También podemos mantener una reference al object de interfaz actual que creamos para que podamos agregar los controles más adelante.

 if (el.hasClass('Interface')){ currentInterface = new app.Interface(el, [], eventCallback); interfaces.push(currentInterface); //We don't need to do anything further with the interface return; }; 

Ahora, cuando no tenemos .Interface class .Interface con el elemento, tenemos controles. Así que primero modifiquemos nuestro object de Interface para admitir la adición de controles y events bindings a los controles a medida que se agregan a la colección.

 //The init function was removed and the call to it self.addControls = function(el){ //Use the mouseover and mouseout events so event bubbling occurs el.on('click mouseover mouseout', self.eventCallback) self.controls.push(el); } 

Ahora todo lo que tenemos que hacer es agregar el control a los controles de interfaz actuales.

 currentInterface.addControls(el); 

Después de todo eso, debería get una matriz de 3 objects (interfaces), que tienen una matriz de 2 controles cada uno.

¡Con suerte, ESO tiene todo lo que estás buscando!

Si te entiendo:

entendiendo mejor sus necesidades y aplicando las classs específicas que necesita, creo que esta es la syntax que funcionará:

 var targetsOfTopGroups = $('.InterfaceGroup .Interface:not(.Interface .Interface):not(.Interface .InterfaceGroup)') 

Este violín es un bash de reproducir tu escenario. Siéntase libre de jugar con eso.


Creo que encontré el problema. No incluiste los botones en tu not selector

Cambié el enlace para ser

  var Controls = $('.InterfaceGroup .Interface :button:not(.Interface .Interface :button):not(.Interface .InterfaceGroup :button)'); 

Violín

¿Por qué no tomar el problema al revés?

Seleccione todos los elementos $ (. Target) y luego deséchelos de un tratamiento posterior si sus. $ Parents (.group) están vacíos, lo que daría una sensación como:

 $('.target').each(function(){ if (! $(this).parents('.group').length){ //the jqueryElem is empy, do or do not } else { //not empty do what you wanted to do } }); 

Tenga en count que no responde el título, pero literalmente le da "Selector B, dentro de un resultado del Selector A"

Si sus classs .interface tienen algún tipo de identificador, esto parece ser bastante fácil. Perhabs ya tiene un identificador de este tipo por otras razones o elige include uno.

http://jsfiddle.net/Dc4dz/

 <div class="interface" name="a"> <div class="control">control</div> <div class="branch"> <div class="control">control</div> <div class="interface"> <div class="branch"> <div class="control">control</div> </div> </div> </div> <div class="interface" name="c"> <div class="branch"> <div class="control">control</div> </div> </div> </div> $( ".interface[name=c] .control:not(.interface[name=c] .interface .control)" ).css( "background-color", "networking" ); $( ".interface[name=a] .control:not(.interface[name=a] .interface .control)" ).css( "background-color", "green" ); 

Editar: Y ahora me pregunto si estás abordando este problema desde el ángulo equivocado.

Así que estoy tratando de $ ('. Interface'). Cada uno (se unen a cualquier control. No dentro de un .Interface más profundo)

http://jsfiddle.net/Dc4dz/1/

 $(".interface").on("click", ".control", function (event) { alert($(this).text()); event.stopPropagation(); }); 

El evento se desencadenaría en un .control; luego burbujearía hasta su .closest (".interface") donde se procesaría y se detendría la propagación adicional. ¿No es eso lo que describes?