คล้ายกับ jQuery .closest () แต่ข้ามลูกหลาน?


124

มีฟังก์ชั่นคล้ายกับjQuery .closest()แต่สำหรับการข้ามผ่านลูกหลานและส่งกลับเฉพาะคนที่ใกล้เคียงที่สุดหรือไม่?

ฉันรู้ว่ามี.find()ฟังก์ชั่น แต่ส่งคืนการจับคู่ที่เป็นไปได้ทั้งหมดไม่ใช่ใกล้เคียงที่สุด

แก้ไข:

นี่คือคำจำกัดความของคำที่ใกล้เคียงที่สุด(อย่างน้อยสำหรับฉัน) :

ประการแรกเด็กทุกคนจะถูกส่งผ่านจากนั้นเด็กแต่ละคนจะถูกข้ามผ่าน

ในตัวอย่างที่ระบุด้านล่างid='2'เป็น.closestลูกหลานที่ใกล้เคียงที่สุดของ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>

โปรดดูที่ลิงค์ JSfiddle


4
เกี่ยวกับอะไร.find("filter here").eq(0)?
Shadow Wizard is Ear For You

@ShadowWizard ได้ดีถ้ามันทำการสำรวจเชิงลึกก่อนดังนั้นองค์ประกอบที่เป็นศูนย์ในรายการผลลัพธ์อาจไม่ใช่ "ใกล้เคียงที่สุด" ขึ้นอยู่กับความหมาย "ใกล้เคียงที่สุด"
Pointy

@Pointy ไม่เหมือนกับ:firstfilter เหรอ?
Shadow Wizard is Ear For You

ไม่ฉันไม่คิดอย่างนั้นปัญหาคือ ": first" และ ".eq (0)" อ้างถึงลำดับ DOM แต่ถ้า "ใกล้เคียงที่สุด" หมายถึง "โหนด DOM น้อยที่สุดห่างออกไป" จากนั้นเป็น "หลาน" ของ ลูกคนแรกจะถูกส่งคืนแทนที่จะเป็น "ลูก" ในภายหลังที่ตรงกับตัวเลือก ฉันไม่แน่ใจ 100% ว่าเป็น OP แน่นอน
Pointy

1
มีปลั๊กอิน jQuery ที่ทำสิ่งนั้นให้คุณ: plugins.jquery.com/closestDescendant
TLindig

คำตอบ:


44

ตามคำจำกัดความของclosestคุณฉันได้เขียนปลั๊กอินต่อไปนี้:

(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
ซึ่งแตกต่างจาก.closest()ฟังก์ชันjQuery ตรงกับองค์ประกอบที่เรียกใช้เช่นกัน ดูของฉันjsfiddle โดยการเปลี่ยนเป็น $currentSet = this.children(); // Current placeมันจะเริ่มจากเด็ก ๆ แทนjsfiddle
allicarn

21
ใกล้เคียงที่สุด () ของ jQuery ยังสามารถจับคู่กับองค์ประกอบปัจจุบัน
DávidHorváth

121

หากลูกหลานที่ "ใกล้เคียงที่สุด" หมายถึงลูกคนแรกคุณสามารถทำได้:

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

หรือ:

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

หรือหากต้องการค้นหาองค์ประกอบที่เกิดขึ้นเป็นครั้งแรกคุณสามารถทำได้:

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

หรือ:

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

แม้ว่าเราต้องการคำจำกัดความที่ชัดเจนว่า "ลูกหลานที่ใกล้เคียงที่สุด" หมายถึงอะไร

เช่น

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

ซึ่ง<span>จะ$('#foo').closestDescendent('span')กลับมา?


1
ขอบคุณ @ 999 สำหรับคำตอบโดยละเอียดความคาดหวังของฉันที่มีต่อผู้ถือครองคือสิ่งแรกที่เด็ก ๆ ทุกคนจะข้ามผ่านจากนั้นก็จะเป็นเด็กแต่ละคน ในตัวอย่างที่คุณระบุช่วงที่สองจะส่งคืน
mamu

8
ดังนั้นหายใจก่อนไม่ใช่ลึก - แรก
jessehouwing

1
คำตอบที่ยอดเยี่ยม ฉันสนใจที่จะทราบว่า$('#foo').find('.whatever').first();เป็น "ความกว้างก่อน" หรือ "ความลึกก่อน"
Colin

1
ปัญหาหนึ่งในการแก้ปัญหานี้คือ jQuery จะค้นหาเกณฑ์ที่ตรงกันทั้งหมดจากนั้นส่งคืนเกณฑ์แรก แม้ว่าเครื่องยนต์ Sizzle จะเร็ว แต่ก็แสดงถึงการค้นหาเพิ่มเติมที่ไม่จำเป็น ไม่มีทางที่จะทำให้การค้นหาลัดวงจรและหยุดลงได้หลังจากพบนัดแรก
icfantv

ตัวอย่างเหล่านี้ล้วนเลือกลูกหลานที่มีความลึกเป็นอันดับแรกไม่ใช่แบบกว้างก่อนดังนั้นจึงไม่สามารถใช้เพื่อแก้ปัญหาเดิมได้ ในการทดสอบของฉันพวกเขาส่งคืนช่วงแรกทั้งหมดไม่ใช่ช่วงที่สอง
JrBaconCheez

18

คุณสามารถใช้findกับ:firstตัวเลือก:

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

บรรทัดข้างต้นจะได้พบกับครั้งแรกองค์ประกอบในลูกหลานของ<p>#parent


2
ฉันคิดว่านี่คือสิ่งที่ OP ต้องการไม่ต้องใช้ปลั๊กอิน การดำเนินการนี้จะเลือกองค์ประกอบที่ตรงกันแรกในลูกหลานขององค์ประกอบที่เลือกสำหรับแต่ละองค์ประกอบที่เลือก ดังนั้นหากคุณมี 4 แมตช์กับการเลือกเดิมของคุณคุณสามารถจบลงด้วยการแข่งขัน 4 รายการโดยใช้find('whatever:first').
RustyToms

3

แล้วแนวทางนี้ล่ะ?

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

ตัวเลือก "เด็กโดยตรง" นี้เหมาะกับฉัน


OP โดยเฉพาะขอสิ่งที่ข้ามผ่านลูกหลานซึ่งคำตอบนี้ไม่ได้ นี่อาจเป็นคำตอบที่ดี แต่ไม่ใช่สำหรับคำถามนี้โดยเฉพาะ
jumps4fun

@KjetilNordin อาจเป็นเพราะคำถามถูกแก้ไขตั้งแต่คำตอบของฉัน ยิ่งไปกว่านั้นคำจำกัดความของผู้สืบทอดที่ใกล้ชิดที่สุดก็ยังไม่ชัดเจนอย่างน้อยสำหรับฉัน
klawipo

ฉันเห็นด้วยแน่นอน ลูกหลานที่ใกล้เคียงที่สุดอาจหมายถึงหลายสิ่งหลายอย่าง ง่ายกว่าสำหรับผู้ปกครองในกรณีนี้ซึ่งมีผู้ปกครองคนเดียวไปยังองค์ประกอบในแต่ละระดับ และคุณก็ถูกต้องเกี่ยวกับการแก้ไขที่เป็นไปได้เช่นกัน ตอนนี้ฉันเห็นว่าความคิดเห็นของฉันไม่ได้เป็นประโยชน์ แต่อย่างใดโดยเฉพาะอย่างยิ่งเมื่อมีข้อความประเภทเดียวกันอยู่แล้ว
jumps4fun

1

โซลูชัน Pure JS (ใช้ 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;
}

ตัวอย่าง

พิจารณาโครงสร้างต่อไปนี้:

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;


วิธีนี้ใช้งานได้ แต่ฉันได้โพสต์โซลูชัน ES6 ทางเลือกที่นี่ ( stackoverflow.com/a/55144581/2441655 ) เนื่องจากฉันไม่ชอบ: 1) whileนิพจน์ที่มีผลข้างเคียง 2) ใช้.matchesเช็คสองบรรทัดแทนที่จะใช้เพียงบรรทัดเดียว
Venryx

1

ในกรณีที่มีคนกำลังมองหาโซลูชัน JS ที่บริสุทธิ์ (ใช้ ES6 แทน jQuery) นี่คือสิ่งที่ฉันใช้:

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

สวัสดีขอบคุณที่แจ้งคำตอบให้ฉันทราบ! คุณพูดถูกเมื่อมองย้อนกลับไปที่ตัวฉันเองมันรู้สึกว่ามันพยายามจะฉลาดเกินไปและอึดอัดจริงๆ (การวิ่งmatchesสองครั้งยังทำให้ฉันรู้สึกแย่อยู่ไม่น้อย) +1 สำหรับคุณ :)
ccjmne

0

ฉันคิดว่าก่อนอื่นคุณต้องกำหนดความหมายที่ "ใกล้เคียงที่สุด" ก่อน หากคุณหมายถึงโหนดลูกหลานที่ตรงกับเกณฑ์ของคุณซึ่งเป็นระยะทางที่สั้นที่สุดในแง่ของลิงก์ผู้ปกครองการใช้ ": first" หรือ ".eq (0)" จะไม่ได้ผล:

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

ในตัวอย่างนั้น<span>องค์ประกอบ".target" ที่สอง " อยู่ใกล้" กับ "start" <div>มากกว่าเนื่องจากอยู่ห่างจากการกระโดดของผู้ปกครองเพียงครั้งเดียว หากนั่นคือสิ่งที่คุณหมายถึง "ใกล้ที่สุด" คุณจะต้องหาระยะทางต่ำสุดในฟังก์ชันตัวกรอง รายการผลลัพธ์จากตัวเลือก jQuery จะอยู่ในลำดับ DOM เสมอ

อาจจะ:

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

นั่นคือประเภทของปลั๊กอินแฮ็คเนื่องจากวิธีสร้างวัตถุผลลัพธ์ แต่แนวคิดก็คือคุณต้องหาเส้นทางผู้ปกครองที่สั้นที่สุดจากองค์ประกอบที่ตรงกันทั้งหมดที่คุณพบ รหัสนี้เอนเอียงไปทางองค์ประกอบทางซ้ายในโครงสร้าง DOM เนื่องจากการ<ตรวจสอบ <=จะเอนเอียงไปทางขวา


0

ฉันปรุงสิ่งนี้แล้วยังไม่มีการใช้งานสำหรับตัวเลือกตำแหน่ง (พวกเขาต้องการมากกว่าแค่matchesSelector):

การสาธิต: 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);

อาจมีข้อบกพร่องบางอย่างแม้ว่าจะไม่ได้ทดสอบมากนัก


0

คำตอบของ Rob W ไม่ได้ผลสำหรับฉัน ฉันปรับให้เข้ากับสิ่งนี้ซึ่งได้ผล

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

สำหรับฉันดูเหมือนว่า.find(filter).first()ช้ากว่าเท่านั้น)
Matthias Samsel

ใช่คุณพูดถูก ฉันเขียนสิ่งนี้เมื่อฉันยังใหม่กับการเขียนโค้ด ฉันคิดว่าฉันจะลบคำตอบนี้
Daniel Tonon

0

ฉันจะใช้สิ่งต่อไปนี้เพื่อรวมเป้าหมายเองหากตรงกับตัวเลือก:

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

มาร์กอัป:

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

0

แม้ว่าจะเป็นหัวข้อเก่า แต่ฉันก็ไม่สามารถต้านทานการนำเด็กที่ใกล้เคียงที่สุดของฉันไปใช้ ส่งลูกหลานที่พบคนแรกโดยเดินทางน้อยที่สุด (หายใจก่อน) หนึ่งเป็นแบบวนซ้ำ (รายการโปรดส่วนตัว) อื่น ๆ ใช้รายการสิ่งที่ต้องทำโดยไม่ต้องเรียกซ้ำเป็นส่วนขยาย jQquery

หวังว่าจะมีคนได้รับประโยชน์

หมายเหตุ: การเรียกซ้ำได้รับสแต็กล้นและฉันปรับปรุงอีกอันตอนนี้จำลองเป็นคำตอบก่อนหน้านี้

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

ปลั๊กอินต่อไปนี้ส่งคืนลูกหลานที่ใกล้เคียงที่สุดอันดับที่ n

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

ฉันกำลังมองหาวิธีแก้ปัญหาที่คล้ายกัน (ฉันต้องการลูกหลานที่ใกล้เคียงที่สุดทั้งหมดนั่นคือความกว้างก่อน + การแข่งขันทั้งหมดไม่ว่าจะอยู่ในระดับใดก็ตาม) นี่คือสิ่งที่ฉันทำ:

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

ฉันหวังว่านี่จะช่วยได้.




โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.