Similar cu jQuery .closest() dar care traversează descendenții? (Programare, Jquery)

mamu a intrebat.

Există o funcție similară cu jQuery .closest() dar pentru parcurgerea descendenților și returnarea doar a celor mai apropiați?

Știu că există .find() funcție, dar aceasta returnează toate corespondențele posibile, nu cele mai apropiate.

Editați:

Iată definiția funcției closest (cel puțin pentru mine):

În primul rând, sunt parcurși toți copiii, apoi sunt parcurși copiii fiecărui individ în parte.

În exemplul dat mai jos id='2' este cel mai apropiat .closest descendenți ai id="find-my-closest-descendant"

<div id="find-my-closest-descendant">
    <div>
        <div class="closest" Id='1'></div>
    </div>
    <div class="closest" Id='2'></div>
</div>

Vă rugăm să consultați JSfiddle link.

Comentarii

  • Ce se întâmplă cu .find("filter here").eq(0)? –  > Por Shadow Vrăjitorul Vaccinat.
  • @ShadowWizard Ei bine, dacă se face o traversare în profunzime, atunci elementul zero’th din lista de rezultate ar putea să nu fie cel mai „apropiat”, în funcție de ceea ce înseamnă „cel mai apropiat”. –  > Por Pointy.
  • @Pointy nu este același lucru cu :first filtru? –  > Por Shadow Vrăjitorul Vaccinat.
  • Nu, nu cred – problema este că „:first” și „.eq(0)” se referă la ordinea DOM, dar dacă „cel mai apropiat” înseamnă „cel mai mic număr de noduri DOM”, atunci ar fi returnat un „nepot” al primului copil în loc de un „copil” ulterior care se potrivește cu selectorul. Nu sunt 100% sigur că despre asta este vorba în OP, desigur. –  > Por Pointy.
  • Există un plugin jQuery ce anume face asta pentru tine: plugins.jquery.com/closestDescendant –  > Por TLindig.
16 răspunsuri
Rob W

În conformitate cu definiția dvs. closest, am scris următorul plugin:

(function($) {
    $.fn.closest_descendent = function(filter) {
        var $found = $(),
            $currentSet = this; // Current place
        while ($currentSet.length) {
            $found = $currentSet.filter(filter);
            if ($found.length) break;  // At least one match: break loop
            // Get all children of the current set
            $currentSet = $currentSet.children();
        }
        return $found.first(); // Return first match of the collection
    }    
})(jQuery);

Comentarii

  • Spre deosebire de jQuery .closest() acesta se potrivește și cu elementul pe care a fost apelat. Consultați pagina mea jsfiddle. Schimbând-o în $currentSet = this.children(); // Current place va începe cu copiii săi în schimb jsfiddle –  > Por allicarn.
  • 22

  • JQuery’s closest() poate fi, de asemenea, potrivit cu elementul curent. –  > Por Dávid Horváth.
James

Dacă prin „cel mai apropiat” descendent vă referiți la primul copil, atunci puteți face acest lucru:

$('#foo').find(':first');

Sau:

$('#foo').children().first();

Sau, pentru a căuta prima apariție a unui anumit element, ați putea face:: „cel mai apropiat descendent”:

$('#foo').find('.whatever').first();

Sau:

$('#foo').find('.whatever:first');

Totuși, avem nevoie de o definiție solidă a ceea ce înseamnă „cel mai apropiat descendent”.

De exemplu.

<div id="foo">
    <p>
        <span></span>
    </p>
    <span></span>
</div>

Care <span> ar fi $('#foo').closestDescendent('span') întoarce?

Comentarii

  • Mulțumesc @999 pentru răspunsul detaliat, așteptările mele în ceea ce privește defunctul sunt ca mai întâi să fie parcurși toți copiii, apoi copiii fiecărui individ în parte. În exemplul pe care l-ați dat, ar fi returnat al doilea interval –  > Por mamu.
  • Deci, mai întâi respirația, nu adâncimea.  > Por jessehouwing.
  • Excelent răspuns. Aș fi interesat să știu dacă $('#foo').find('.whatever').first(); este „breadth-first” sau „depth-first” –  > Por Colin.
  • Singura problemă cu această soluție este că jQuery va găsi TOATE criteriile care se potrivesc și apoi îl va returna pe primul. Deși motorul Sizzle este rapid, acest lucru reprezintă o căutare suplimentară inutilă. Nu există nicio modalitate de a scurtcircuita căutarea și de a se opri după ce este găsită prima potrivire. –  > Por icfantv.
  • Aceste exemple selectează toate descendenții în profunzime, nu în amplitudine, deci nu pot fi folosite pentru a rezolva întrebarea inițială. În testele mele, toate au returnat primul interval, nu al doilea. –  > Por JrBaconCheez.
FishBasketGordo

Puteți folosi find cu :first selector:

$('#parent').find('p:first');

Linia de mai sus va găsi primul <p> element în descendenții lui #parent.

Comentarii

  • Cred că acest lucru este ceea ce a vrut OP, nu este nevoie de plugin. Aceasta va selecta primul element care se potrivește în descendenții elementului selectat, pentru fiecare element selectat. Deci, dacă aveți 4 potriviri cu selecția inițială, ați putea obține 4 potriviri folosind opțiunea find('whatever:first'). –  > Por RustyToms.
klawipo

Ce zici de această abordare?

$('find-my-closest-descendant').find('> div');

Acest selector „copil direct” funcționează pentru mine.

Comentarii

  • OP cere în mod specific ceva care traversează descendenții, ceea ce acest răspuns nu face. Acesta ar putea fi un răspuns bun, dar nu pentru această întrebare specială. –  > Por jumps4fun.
  • @KjetilNordin Poate pentru că întrebarea a fost modificată de la răspunsul meu. În plus, definiția celui mai apropiat descendent nu este încă atât de clară, cel puțin pentru mine. –  > Por klawipo.
  • Cu siguranță sunt de acord. cel mai apropiat descendent ar putea însemna atât de multe lucruri. Mai ușor cu părintele în acest caz, unde există întotdeauna un singur părinte pentru elemente, pe fiecare nivel. Și ai dreptate și în ceea ce privește posibilele editări. Văd acum că comentariul meu nu a fost de ajutor în niciun fel, mai ales că exista deja o afirmație de același tip. –  > Por jumps4fun.
Venryx

În cazul în care cineva caută o soluție pur JS (folosind ES6 în loc de jQuery), iată care este cea pe care o folosesc eu:

Element.prototype.QuerySelector_BreadthFirst = function(selector) {
    let currentLayerElements = [...this.childNodes];
    while (currentLayerElements.length) {
        let firstMatchInLayer = currentLayerElements.find(a=>a.matches && a.matches(selector));
        if (firstMatchInLayer) return firstMatchInLayer;
        currentLayerElements = currentLayerElements.reduce((acc, item)=>acc.concat([...item.childNodes]), []);
    }
    return null;
};

Comentarii

  • Hei, mulțumesc că mi-ai adus în atenție răspunsul tău! Ai dreptate, uitându-mă înapoi la al meu, se simte ca și cum ar încerca să fie prea inteligent și de fapt este incomod (rulează matches de două ori mă irită destul de mult). +1 pentru tine 🙂 –  > Por ccjmne.
Pointy

Cred că mai întâi trebuie să definești ce înseamnă „cel mai apropiat”. Dacă te referi la nodul descendent care corespunde criteriilor tale și care se află la cea mai mică distanță în ceea ce privește legăturile părintești, atunci folosirea lui „:first” sau „.eq(0)” nu va funcționa neapărat:

<div id='start'>
  <div>
    <div>
      <span class='target'></span>
    </div>
  </div>
  <div>
    <span class='target'></span>
  </div>
</div>

În acest exemplu, al doilea „.target” <span> este „mai aproape” de „start”. <div>, pentru că se află la doar un salt de părinte. Dacă la asta vă referiți prin „cel mai apropiat”, ar trebui să găsiți distanța minimă într-o funcție de filtrare. Lista de rezultate de la un selector jQuery este întotdeauna în ordinea DOM.

Poate:

$.fn.closestDescendant = function(sel) {
  var rv = $();
  this.each(function() {
    var base = this, $base = $(base), $found = $base.find(sel);
    var dist = null, closest = null;
    $found.each(function() {
      var $parents = $(this).parents();
      for (var i = 0; i < $parents.length; ++i)
        if ($parents.get(i) === base) break;
      if (dist === null || i < dist) {
        dist = i;
        closest = this;
      }
    });
    rv.add(closest);
  });
  return rv;
};

Acesta este un fel de plugin hack din cauza modului în care construiește obiectul rezultat, dar ideea este că trebuie să găsiți cea mai scurtă cale parentală dintre toate elementele care se potrivesc pe care le găsiți. Acest cod influențează elementele din stânga în arborele DOM din cauza < verificare; <= ar fi înclinat spre dreapta.

Esailija

Am gătit asta, nu există o implementare pentru selectori poziționali (au nevoie de mai mult decât doar de matchesSelector) încă:

Demo: http://jsfiddle.net/TL4Bq/3/

(function ($) {
    var matchesSelector = jQuery.find.matchesSelector;
    $.fn.closestDescendant = function (selector) {
        var queue, open, cur, ret = [];
        this.each(function () {
            queue = [this];
            open = [];
            while (queue.length) {
                cur = queue.shift();
                if (!cur || cur.nodeType !== 1) {
                    continue;
                }
                if (matchesSelector(cur, selector)) {
                    ret.push(cur);
                    return;
                }
                open.unshift.apply(open, $(cur).children().toArray());
                if (!queue.length) {
                    queue.unshift.apply(queue, open);
                    open = [];
                }
            }
        });
        ret = ret.length > 1 ? jQuery.unique(ret) : ret;
        return this.pushStack(ret, "closestDescendant", selector);
    };
})(jQuery);

Probabil că există totuși unele bug-uri, nu am testat-o foarte mult.

Daniel Tonon

Răspunsul lui Rob W nu prea a funcționat pentru mine. L-am adaptat la acesta, care a funcționat.

//closest_descendent plugin
$.fn.closest_descendent = function(filter) {
    var found = [];

    //go through every matched element that is a child of the target element
    $(this).find(filter).each(function(){
        //when a match is found, add it to the list
        found.push($(this));
    });

    return found[0]; // Return first match in the list
}

Comentarii

  • Pentru mine arată exact ca .find(filter).first(), doar că mai lent 😉 –  > Por Matthias Samsel.
  • Da, ai dreptate. Am scris acest lucru când eram încă destul de nou în domeniul codării. Cred că voi șterge acest răspuns –  > Por Daniel Tonon.
ling

Aș folosi următoarele pentru a include ținta însăși dacă se potrivește cu selectorul:

    var jTarget = $("#doo");
    var sel = '.pou';
    var jDom = jTarget.find(sel).addBack(sel).first();

Markup:

<div id="doo" class="pou">
    poo
    <div class="foo">foo</div>
    <div class="pou">pooo</div>
</div>

cole

Următorul plugin returnează al n-lea cel mai apropiat descendent.

$.fn.getNthClosestDescendants = function(n, type) {
  var closestMatches = [];
  var children = this.children();

  recursiveMatch(children);

  function recursiveMatch(children) {
    var matches = children.filter(type);

    if (
      matches.length &&
      closestMatches.length < n
    ) {
      var neededMatches = n - closestMatches.length;
      var matchesToAdd = matches.slice(0, neededMatches);
      matchesToAdd.each(function() {
        closestMatches.push(this);
      });
    }

    if (closestMatches.length < n) {
      var newChildren = children.children();
      recursiveMatch(newChildren);
    }
  }

  return closestMatches;
};

Sharique Abdullah

Căutam o soluție similară (voiam toți descendenții cei mai apropiați, adică breadth first + toate potrivirile indiferent de nivelul la care există), iată ce am ajuns să fac:

var item = $('#find-my-closest-descendant');
item.find(".matching-descendant").filter(function () {
    var $this = $(this);
    return $this.parent().closest("#find-my-closest-descendant").is(item);
}).each(function () {
    // Do what you want here
});

Sper că vă ajută.

ccjmne

Soluție pur JS (folosind ES6).

export function closestDescendant(root, selector) {
  const elements = [root];
  let e;
  do { e = elements.shift(); } while (!e.matches(selector) && elements.push(...e.children));
  return e.matches(selector) ? e : null;
}

Exemplu

Având în vedere următoarea structură:

div                 == $0
├── div             == $1
│   ├── div
│   ├── div.findme  == $4
│   ├── div
│   └── div
├── div.findme      == $2
│   ├── div
│   └── div
└── div             == $3
    ├── div
    ├── div
    └── div
closestDescendant($0, '.findme') === $2;
closestDescendant($1, '.findme') === $4;
closestDescendant($2, '.findme') === $2;
closestDescendant($3, '.findme') === null;

Comentarii

  • Această soluție funcționează, dar am postat o soluție ES6 alternativă aici (stackoverflow.com/a/55144581/2441655), deoarece nu-mi place: 1) while expresia să aibă efecte secundare. 2) Utilizarea expresiei .matches check pe două linii în loc de una singură. –  > Por Venryx.
Jyoti watpade

Puteți pune pur și simplu,

$("#find-my-closest-descendant").siblings('.closest:first');

Jean Paul A.K.A el_vete

Există un articol excelent care subliniază faptul că ceea ce solicită OP poate fi realizat cu ușurință cu cel mai apropiat [Geeks for Geeks]
1https://www.geeksforgeeks.org/jquery-closest-with-examples/

Eelco

Chiar dacă este un subiect vechi, nu am putut rezista să nu-mi implementez closestChild. livrează primul descendent găsit cu cea mai mică deplasare ( breath first ). Unul este recursiv (preferatul personal ), celălalt folosind o listă de tot așa fără recursivitate ca extensii jQquery.

Sper că cineva beneficiază.

Notă : Recursivul primește stack overflow, iar pe celălalt l-am îmbunătățit, acum simular cu răspunsul anterior dat.

jQuery.fn.extend( {

    closestChild_err : function( selector ) { // recursive, stack overflow when not found
        var found = this.children( selector ).first();
        if ( found.length == 0 ) {
            found = this.children().closestChild( selector ).first(); // check all children
        }
        return found;
    },

    closestChild : function( selector ) {
        var todo = this.children(); // start whith children, excluding this
        while ( todo.length > 0 ) {
            var found = todo.filter( selector );
            if ( found.length > 0 ) { // found closest: happy
                return found.first();
            } else {
                todo = todo.children();
            }
        }
        return $();
    },

});  

Abou-Emish

aveți o mulțime de opțiuni, totuși $("#parent").children(".child"); este cea mai rapidă.verificați acest articol pentru detalii și benchmark

Tags: