Похоже на jQuery .closest (), но пересекает потомков? - PullRequest
108 голосов
/ 22 января 2012

Есть ли функция, похожая на jQuery .closest(), но для обхода потомков и возврата только ближайших?

Я знаю, что есть функция .find(), но она возвращает все возможные совпадения, а не самые близкие.

Edit:

Вот определение ближайшего (по крайней мере, для меня) :

В первую очередь пересекаются все дети, затем пересекаются все дети.

В приведенном ниже примере 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 link .

Ответы [ 14 ]

108 голосов
/ 22 января 2012

Если под «ближайшим» потомком вы имеете в виду первого ребенка, то вы можете сделать:

$('#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')?

42 голосов
/ 22 января 2012

Согласно вашему определению 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);
16 голосов
/ 22 января 2012

Вы можете использовать find с селектором :first:

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

В приведенной выше строке будет найден первый элемент <p> в потомках #parent.

2 голосов
/ 15 ноября 2017

А как насчет этого подхода?

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

Этот "прямой дочерний" селектор работает для меня.

1 голос
/ 13 марта 2019

Если кто-то ищет чистое решение 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;
};
1 голос
/ 02 октября 2018

Чистое решение 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;

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

const [$0, $1, $2, $3, $4] = [0, 1, 2, 3, 4].map(x => document.querySelector(`#e${x}`));

console.log(closestDescendant($0, '.findme')); // $2
console.log(closestDescendant($1, '.findme')); // $4
console.log(closestDescendant($2, '.findme')); // $2
console.log(closestDescendant($3, '.findme')); // null
<div id="e0">
    <div id="e1">
        <div></div>
        <div id="e4" class="findme"></div>
        <div></div>
        <div></div>
    </div>
    <div id="e2" class="findme">
        <div></div>
        <div></div>
    </div>
    <div id="e3">
        <div></div>
        <div></div>
        <div></div>
    </div>
</div>
0 голосов
/ 14 марта 2016

Я искал похожее решение (я хотел всех ближайших потомков, то есть сначала ширину + все совпадения независимо от того, на каком уровне он существует), вот что я в итоге сделал:

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

Надеюсь, это поможет.

0 голосов
/ 25 августа 2015

Следующий плагин возвращает 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 голосов
/ 26 апреля 2015

Несмотря на то, что это старая тема, я не удержался от реализации своего ближайшего ребенка.Доставляет первого найденного потомка с наименьшим количеством путешествий (дыхание первым).Один из них рекурсивный (личный фаворит), другой использует список задач, поэтому без рекурсии в качестве расширений 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 голосов
/ 06 февраля 2015

у вас есть много вариантов, однако $("#parent").children(".child"); является самым быстрым. проверьте эту статью для деталей и тестов

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...