JQuery .closest () 'e benzer ama nesiller arasında geçiş mi?


123

jQuery .closest()Torunları geçmek ve yalnızca en yakın olanları geri getirmek için benzer bir işlev var mı ?

.find()İşlevin olduğunu biliyorum , ancak tüm olası eşleşmeleri döndürür, en yakın değil.

Düzenle:

İşte en yakın tanımı (en azından benim için) :

İlk olarak, tüm çocuklar geçilir, ardından her bir birey çocuk üzerinden geçilir.

Aşağıda verilen örnekte id='2'en yakın .closestsoyundan gelenlerid="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>

Lütfen JSfiddle bağlantısına bakın .


4
Peki ya .find("filter here").eq(0)?
Gölge Sihirbazı Sizin İçin Kulak

@ShadowWizard iyi bir derinlik geçişi yaparsa sonuç listesindeki sıfırıncı eleman "en yakın" ne anlama geldiğine bağlı olarak "en yakın" olmayabilir.
Pointy

@Pointy, filtreyle aynı değil :firstmi?
Gölge Sihirbazı Sizin İçin Kulak

Hayır, sanmıyorum - sorun şu ki ": ilk" ve ".eq (0)" DOM ​​sırasına atıfta bulunuyor, ancak "en yakın", "en az DOM düğümü uzakta" anlamına geliyorsa, o zaman bir "torunu" seçici ile eşleşen sonraki bir "alt" yerine ilk çocuk döndürülür. Tabii ki OP'nin bununla ilgili olduğundan% 100 emin değilim.
Pointy

1
Bir jQuery eklentisi var bunu sizin için tam olarak ne yapıyor: plugins.jquery.com/closestDescendant
TLindig

Yanıtlar:


44

Tanımınıza göre closestaşağıdaki eklentiyi yazdım:

(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);

3
JQuery .closest()işlevinin aksine bu, çağrıldığı öğeyle de eşleşir. Benim jsfiddle'ıma bakın . Bunu değiştirerek onun $currentSet = this.children(); // Current placeyerine çocuklarıyla başlayacak jsfiddle
allicarn

21
JQuery'nin en yakın () öğesi de geçerli öğeyle eşleştirilebilir.
Dávid Horváth

120

"En yakın" soyundan bahsederken ilk çocuğu kastediyorsanız, şunları yapabilirsiniz:

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

Veya:

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

Veya belirli bir öğenin ilk oluşumunu aramak için şunları yapabilirsiniz:

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

Veya:

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

Gerçekten de, "en yakın torun" un ne anlama geldiğine dair sağlam bir tanıma ihtiyacımız var.

Örneğin

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

Hangisi <span>geri $('#foo').closestDescendent('span')dönecek?


1
Ayrıntılı cevap için teşekkürler @ 999, merhumdan beklentim önce tüm çocukların, sonra her bireyin çocuklarının geçilmesi. Verdiğiniz örnekte, ikinci aralık döndürülecektir
mamu

8
Öyleyse önce nefes, derinlik değil
jessehouwing

1
Mükemmel cevap. $('#foo').find('.whatever').first();"Önce genişlik" mi yoksa "önce derinlik" mi merak ediyorum
Colin

1
Bu çözümle ilgili tek sorun, jQuery'nin TÜM eşleşen ölçütleri bulması ve ardından ilkini döndürmesidir. Sizzle motoru hızlı olsa da, bu gereksiz ekstra arama anlamına gelir. İlk eşleşme bulunduktan sonra aramaya kısa devre yaptırıp durmanın bir yolu yoktur.
icfantv

Bu örneklerin tümü, önce enine değil, önce derinlik soyundan gelenleri seçer, bu nedenle bunlar orijinal soruyu çözmek için kullanılamaz. Benim testimde hepsi ikinciyi değil, ilk aralığı döndürdüler.
JrBaconCheez

18

Seçici findile kullanabilirsiniz :first:

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

Yukarıdaki satır <p>, soyundan gelen ilk öğeyi bulacaktır #parent.


2
Bence OP'nin istediği buydu, eklentiye gerek yok. Bu, seçilen her öğe için seçilen öğenin soyundan gelen ilk eşleşen öğeyi seçecektir . Yani, orijinal seçiminizle 4 maçınız varsa find('whatever:first'),.
RustyToms

3

Peki ya bu yaklaşım?

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

Bu "doğrudan çocuk" seçici benim için çalışıyor.


OP özellikle torunlardan geçen bir şey ister, ancak bu yanıt bunu yapmaz. Bu iyi bir cevap olabilir, ancak bu özel soru için değil.
jumps4fun

@KjetilNordin Belki de sorunun cevabımdan beri düzenlenmiş olmasından kaynaklanıyor. Dahası, en yakın torun tanımı, en azından benim için hala o kadar net değil.
klawipo

Kesinlikle katılıyorum en yakın torun pek çok anlama gelebilir. Her seviyede tek bir ebeveynin olduğu bu durumda ebeveyn ile daha kolaydır. Ve olası düzenlemeler konusunda da haklısın. Şimdi görüyorum ki, yorumum hiçbir şekilde yardımcı olmadı, özellikle de zaten aynı türde bir ifade olduğu için.
jumps4fun

1

Saf JS çözümü (ES6 kullanarak).

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

Misal

Aşağıdaki yapıyı göz önünde bulundurarak:

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


Bu çözüm işe yarıyor, ancak burada alternatif bir ES6 çözümü yayınladım ( stackoverflow.com/a/55144581/2441655 ) çünkü beğenmediğim: 1) whileYan etkileri olan ifade. 2) .matchesÇekin tek satır yerine iki satırda kullanılması.
Venryx

1

Birinin saf bir JS çözümü araması durumunda (jQuery yerine ES6 kullanarak), işte benim kullandığım:

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

Hey, cevabını dikkatime sunduğun için teşekkürler! Haklısın, kendi başıma dönüp baktığımda, çok zeki ve aslında garip olmaya çalışıyormuş gibi geliyor ( matchesiki kez koşmak da beni biraz rahatsız ediyor). +1 sizin için :)
ccjmne

0

Bence önce "en yakın" ın ne anlama geldiğini tanımlamalısın. Ebeveyn bağlantıları açısından en kısa mesafe olan ölçütlerinize uyan alt düğümü kastediyorsanız, ": ilk" veya ".eq (0)" kullanmak mutlaka işe yaramayacaktır:

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

Bu örnekte, ikinci ".target" <span>öğesi "başlangıca" "daha yakındır" <div>, çünkü yalnızca bir ebeveyn atlama uzaklığındadır. "En yakın" derken kastettiğin buysa, bir filtre işlevinde minimum mesafeyi bulman gerekir. Bir jQuery seçicinin sonuç listesi her zaman DOM sırasındadır.

Olabilir:

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

Bu, sonuç nesnesini oluşturma biçiminden dolayı bir tür hack eklentisi, ancak fikir, bulduğunuz tüm eşleşen öğeler arasından en kısa ebeveyn yolunu bulmanız gerektiğidir. Bu kod, <kontrol nedeniyle DOM ağacında sola doğru olan öğelere eğilim gösterir ; <=sağa doğru önyargılı olur.


0

Bunu pişirdim, konumsal seçiciler için uygulama yok (sadece daha fazlasına ihtiyaçları var matchesSelector):

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

Muhtemelen bazı hatalar olsa da, çok fazla test etmedi.


0

Rob W'nun cevabı benim için pek işe yaramadı. İşe yarayan buna uyarladım.

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

Benim için bu tam olarak .find(filter).first()daha yavaş görünüyor ;)
Matthias Samsel

Evet haklısın. Bunu kodlamada hala oldukça yeniyken yazdım. Sanırım bu yanıtı sileceğim
Daniel Tonon

0

Seçiciyle eşleşirse hedefin kendisini dahil etmek için aşağıdakileri kullanırdım:

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

İşaretleme:

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

0

Bu eski bir konu olmasına rağmen en yakın çocuğumu uygulamaya karşı koyamadım. En az seyahat ile ilk bulunan soyundan gelir (önce nefes alır). Biri özyinelemeli (kişisel favori), diğeri ise jQquery uzantıları olarak özyineleme olmadan yapılacaklar listesi kullanıyor.

Umarım birisi fayda sağlar.

Not: Özyinelemeli yığın taşması alır ve diğerini geliştirdim, şimdi verilen önceki cevaba benzer.

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 $();
    },

});  

0

Aşağıdaki eklenti n'inci en yakın torunları döndürür.

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

0

Benzer bir çözüm arıyordum (en yakın torunların hepsini istedim, yani önce genişlik + hangi seviyede olursa olsun tüm eşleşmeler), işte sonunda şunu yaptım:

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

Umarım bu yardımcı olur.


0

Sadece basitçe koyabilirsin

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


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.