Tương tự như jQuery .closest () nhưng truyền tải con cháu?


123

Có một chức năng tương tự jQuery .closest()nhưng để vượt qua con cháu và chỉ trả lại những người thân nhất?

Tôi biết rằng có .find()chức năng, nhưng nó trả về tất cả các kết quả khớp có thể, không phải gần nhất.

Biên tập:

Đây là định nghĩa gần nhất (ít nhất là đối với tôi) :

Ở nơi đầu tiên tất cả trẻ em được đi qua, sau đó mỗi cá nhân trẻ em được đi qua.

Trong ví dụ đưa ra dưới đây id='2'.closesthậu duệ gần nhất củaid="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>

Vui lòng xem liên kết JSfiddle .


4
Thế còn .find("filter here").eq(0)?
Shadow Wizard là Ear For You

@ShadowWizard tốt nếu nó thực hiện một giao dịch theo chiều sâu thì phần tử số 0 trong danh sách kết quả có thể không phải là "gần nhất", tùy thuộc vào "gần nhất" nghĩa là gì.
Mũi nhọn

@Pointy không giống như :firstbộ lọc?
Shadow Wizard là Ear For You

Không, tôi không nghĩ vậy - vấn đề là ": đầu tiên" và ".eq (0)" đề cập đến thứ tự DOM, nhưng nếu "gần nhất" có nghĩa là "ít nút DOM nhất" thì là "cháu" của đứa trẻ đầu tiên sẽ được trả lại thay vì "đứa trẻ" sau đó phù hợp với bộ chọn. Tôi không chắc chắn 100% đó là những gì OP nói về tất nhiên.
Mũi nhọn

1
Có một jQuery plugin chính xác những gì làm điều đó cho bạn: plugins.jquery.com/closestDescendant
TLindig

Câu trả lời:


44

Theo định nghĩa của bạn closest, tôi đã viết plugin sau:

(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
Không giống như .closest()hàm jQuery , nó cũng khớp với phần tử mà nó được gọi. Xem jsfiddle của tôi . Bằng cách thay đổi nó thành $currentSet = this.children(); // Current placenó sẽ bắt đầu với trẻ em thay vì jsfiddle
allicarn

21
Gần nhất () của JQuery cũng có thể được khớp với phần tử hiện tại.
Dávid Horváth

120

Nếu theo hậu duệ "gần nhất", bạn có nghĩa là đứa con đầu lòng thì bạn có thể làm:

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

Hoặc là:

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

Hoặc, để tìm kiếm sự xuất hiện đầu tiên của một yếu tố cụ thể, bạn có thể làm:

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

Hoặc là:

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

Thực sự, chúng ta cần một định nghĩa vững chắc về "hậu duệ gần nhất" nghĩa là gì.

Ví dụ

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

<span>sẽ $('#foo').closestDescendent('span')trở lại?


1
cảm ơn @ 999 đã trả lời chi tiết, kỳ vọng của tôi về người quá cố trước tiên là tất cả trẻ em đều đi qua, sau đó là từng cá nhân trẻ em. Trong ví dụ bạn đã đưa ra, nhịp thứ hai sẽ được trả về
mamu

8
Vì vậy, hơi thở đầu tiên, không phải chiều sâu đầu tiên
jessehouwing

1
Câu trả lời tuyệt vời. Tôi muốn biết liệu $('#foo').find('.whatever').first();là "chiều rộng đầu tiên" hay "chiều sâu đầu tiên"
Colin

1
Một vấn đề với giải pháp này là jQuery sẽ tìm thấy TẤT CẢ các tiêu chí phù hợp và sau đó trả về tiêu chí đầu tiên. Mặc dù công cụ Sizzle hoạt động nhanh, nhưng điều này thể hiện việc tìm kiếm thêm không cần thiết. Không có cách nào để ngắn mạch tìm kiếm và dừng lại sau khi trận đấu đầu tiên được tìm thấy.
icfantv

Tất cả các ví dụ này đều chọn con cháu theo chiều sâu, không phải chiều rộng trước, vì vậy chúng không thể được sử dụng để giải quyết câu hỏi ban đầu. Trong thử nghiệm của tôi, tất cả đều trả về nhịp đầu tiên, không phải lần thứ hai.
JrBaconCheez

18

Bạn có thể sử dụng findvới :firstbộ chọn:

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

Dòng trên sẽ tìm thấy <p>phần tử đầu tiên trong hậu duệ của #parent.


2
Tôi nghĩ rằng đây là những gì OP muốn, không cần plugin. Điều này sẽ chọn phần tử phù hợp đầu tiên trong hậu duệ của phần tử được chọn, cho mỗi phần tử được chọn. Vì vậy, nếu bạn có 4 trận đấu với lựa chọn ban đầu của mình, bạn có thể kết thúc với 4 trận đấu bằng cách sử dụng find('whatever:first').
RustyToms

3

Phương pháp này thì sao?

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

Công cụ chọn "con trực tiếp" này hoạt động với tôi.


OP đặc biệt yêu cầu một cái gì đó đi qua con cháu, mà câu trả lời này không có. Đây có thể là một câu trả lời tốt, nhưng không phải cho câu hỏi cụ thể này.
jumps4fun

@KjetilNordin Có lẽ vì câu hỏi đã được chỉnh sửa kể từ câu trả lời của tôi. Hơn nữa, định nghĩa về hậu duệ gần nhất vẫn chưa quá rõ ràng, ít nhất là với tôi.
klawipo

Tôi chắc chắn đồng ý. Hậu duệ gần nhất có thể có nghĩa là rất nhiều thứ. Dễ dàng hơn với cha mẹ trong trường hợp này, trong đó luôn có một phụ huynh duy nhất cho các phần tử, trên mỗi cấp độ. Và bạn cũng đúng về các chỉnh sửa có thể. Bây giờ tôi thấy rằng nhận xét của tôi không hữu ích theo bất kỳ cách nào, đặc biệt là vì đã có một tuyên bố cùng loại.
jumps4fun

1

Giải pháp thuần túy (sử dụng 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;
}

Thí dụ

Xem xét cấu trúc sau:

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


Giải pháp này hoạt động, nhưng tôi đã đăng một giải pháp ES6 thay thế tại đây ( stackoverflow.com/a/55144581/2441655 ) vì tôi không thích: 1) whileBiểu thức có tác dụng phụ. 2) Sử dụng .matcheskiểm tra trên hai dòng thay vì chỉ một.
Venryx

1

Trong trường hợp ai đó đang tìm kiếm một giải pháp JS thuần túy (sử dụng ES6 thay vì jQuery), đây là giải pháp tôi sử dụng:

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, cảm ơn vì đã đưa câu trả lời của bạn đến sự chú ý của tôi! Bạn nói đúng, nhìn lại bản thân mình, cảm giác như nó đang cố tỏ ra quá thông minh và thực sự vụng về (chạy matcheshai lần cũng làm tôi khó chịu một chút). +1 cho bạn :)
ccjmne

0

Tôi nghĩ rằng trước tiên bạn phải xác định "gần nhất" nghĩa là gì. Nếu bạn có nghĩa là nút con cháu phù hợp với tiêu chí của bạn là khoảng cách ngắn nhất về các liên kết của cha mẹ, thì sử dụng ": đầu tiên" hoặc ".eq (0)" sẽ không nhất thiết phải hoạt động:

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

Trong ví dụ đó, <span>phần tử ".target" thứ hai " gần" hơn với "bắt đầu" <div>, bởi vì nó chỉ cách một bước nhảy của cha mẹ. Nếu đó là những gì bạn muốn nói là "gần nhất", bạn cần tìm khoảng cách tối thiểu trong chức năng lọc. Danh sách kết quả từ bộ chọn jQuery luôn theo thứ tự DOM.

Có lẽ:

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

Đó là một loại plugin hack vì cách nó xây dựng đối tượng kết quả, nhưng ý tưởng là bạn phải tìm ra con đường ngắn nhất của cha mẹ trong số tất cả các yếu tố phù hợp bạn tìm thấy. Mã này thiên về các phần tử bên trái trong cây DOM vì <kiểm tra; <=sẽ thiên vị ngay.


0

Tôi đã làm điều này, không có triển khai cho các bộ chọn vị trí (chúng cần nhiều hơn chỉ matchesSelector):

Bản trình diễn: 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);

Có lẽ có một số lỗi mặc dù, đã không kiểm tra nó rất nhiều.


0

Câu trả lời của Rob W không hiệu quả lắm đối với tôi. Tôi đã điều chỉnh nó để làm việc này.

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

Đối với tôi điều này trông giống hệt .find(filter).first(), chỉ chậm hơn;)
Matthias Samsel

Đúng, bạn đúng. Tôi đã viết điều này khi tôi vẫn còn khá mới để viết mã. Tôi nghĩ rằng tôi sẽ xóa câu trả lời này
Daniel Tonon

0

Tôi sẽ sử dụng cách sau để bao gồm chính mục tiêu nếu nó khớp với bộ chọn:

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

Đánh dấu:

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

0

Mặc dù đó là một chủ đề cũ nhưng tôi không thể cưỡng lại việc thực hiện chương trình gần gũi nhất của mình. Cung cấp hậu duệ đầu tiên được tìm thấy với ít đi du lịch nhất (hơi thở đầu tiên). Một là đệ quy (yêu thích cá nhân), khác là sử dụng danh sách việc cần làm để không có đệ quy như các tiện ích mở rộng jQquery.

Hy vọng ai đó có lợi.

Lưu ý: Đệ quy được tràn ngăn xếp và tôi đã cải thiện cái khác, bây giờ giống như câu trả lời trước đó.

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

Các plugin sau trả về hậu duệ gần nhất thứ 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

Tôi đang tìm kiếm một giải pháp tương tự (tôi muốn tất cả các hậu duệ gần nhất, tức là chiều rộng đầu tiên + tất cả các trận đấu bất kể nó tồn tại ở cấp độ nào), đây là những gì tôi đã là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
});

Tôi hi vọng cái này giúp được.


0

Bạn chỉ có thể đặt

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


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.