Найти ближайший следующий элемент в DOM с помощью jQuery - PullRequest
3 голосов
/ 12 июня 2019

В следующем примере.Я хотел бы найти первые .invalid-feedback и .valid-feedback для обоих элементов #main и #secondary.

Очевидно, я заинтересован в общем случае, поэтому я написал расширение прототипа дляjQuery.

$.fn.extend({
  closestNext: function (selector) {
    let found = null    
    let search = (el, selector) => {
      if (!el.length) return
      if (el.nextAll(selector).length) {
        found = el.nextAll(selector).first()
        return
      }
      search(el.parent(), selector)
    }

    search($(this), selector)
    return found
  }
})

// Proof
$('#main').closestNext('.invalid-feedback').text('main-invalid')
$('#secondary').closestNext('.invalid-feedback').text('secondary-invalid')
$('#main').closestNext('.valid-feedback').text('any-valid')
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
    <div>
        <input id="main"/>
    </div>
    <div class="dummy"></div>
    <div class="invalid-feedback"></div>
    <div>
        <input id="secondary"/>
    </div>
    <div class="invalid-feedback"></div>
</div>
<div class="valid-feedback"></div>

То, что я написал, кажется очень сложным, и я ожидаю, что этот вид функции обхода DOM будет частью jQuery из коробки.К сожалению, в руководстве я не нашел связанных функций.

Есть ли более простой способ достижения того же результата, что и closestNext?

РЕДАКТИРОВАТЬ

С более алгоритмической стороны я ищу функцию обхода дерева, которая идет в следующем порядке, но со сложностью лучше, чем я добился в моем примере.

.
├── A1
│   ├── B1
│   │   ├── C1
│   │   ├── C2
│   │   └── C3
│   ├── B2
│   │   ├── C4 <--- Entry point
│   │   ├── C5
│   │   └── C6
│   └── B3
│       ├── C7
│       ├── C8
│       └── C9
└── A2
    ├── B4 
    │   ├── C10
    │   └── C11
    └── B5
        ├── C12
        └── C13

Из C4 Точка входа, заказ на разведку:

>>> traverse(C4)
C5, C6, B3, C7, C8, C9, A2, B4, C10, C11, B5, C12, C13

Ответы [ 4 ]

2 голосов
/ 13 июня 2019

Я не вижу, как ваш первоначальный код может быть проще. В конце концов, он должен повторить разметку.

Однако я увидел, как его можно оптимизировать, используя return в пользу let found = null и вызывая el.nextAll(selector) только один раз.

Я также рассмотрел случаи, когда элемент не найден, как будто нет, в результате вы получите исключение.

Фрагмент стека

$.fn.extend({
  closestNext: function (selector) {
    let search = (el, selector) => {
      if (!el.length) return el
      let f = el.nextAll(selector)
      return f.length ? f.first() : search(el.parent(), selector)
    }
    return search($(this), selector)
  }
})

// Proof
$('#main').closestNext('.invalid-feedback').text('main-invalid')
$('#secondary').closestNext('.invalid-feedback').text('secondary-invalid')
$('#main').closestNext('.valid-feedback').text('any-valid')
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
    <div>
        <input id="main"/>
    </div>
    <div class="dummy"></div>
    <div class="invalid-feedback"></div>
    <div>
        <input id="secondary"/>
    </div>
    <div class="invalid-feedback"></div>
</div>
<div class="valid-feedback"></div>
0 голосов
/ 12 июня 2019

Не уверен, что это полностью работает, но я думаю, что отчасти зависит от того, насколько сложна ваша разметка, но даже тогда, я думаю, это даст вам следующее появление либо действительных, либо недействительных классов обратной связи относительно элементавопрос, так как ваши идентификаторы уникальны, и индекс + 1 в соответствующем наборе должен быть тем, что вы ищете.Возможно, излишний, но он должен получить следующий ближайший в дереве DOM, а не только братьев и сестер, детей и т. Д. Я вставил изменения CSS, чтобы проверить, что он делает.

$("#main, #secondary").on("click", function(e) {
  var invalid = $(this).add('.invalid-feedback');
  var valid = $(this).add('.valid-feedback');
  $(invalid.get(invalid.index( $(this)) +1)).css("background", "black");
  $(valid.get(valid.index( $(this)) +1)).css("background", "blue");
});
0 голосов
/ 13 июня 2019

Итак, это работает, но определенно не чисто. Я должен убедиться, что «предыдущие» узлы не включены, поэтому я отфильтрую их ...

Я добавил еще несколько тестовых примеров, чтобы продемонстрировать, что он действует только на «следующие» элементы.

Я бы пошел с вашим существующим решением.

$.fn.extend({
  closestNext: function(selector, originalMe) {
    const me = $(this);
    // try next first
    let nextSibling = me.next(selector);
    if (nextSibling.length) return nextSibling;
    // try descendents
    let descendents = me.find(selector).not((i, e) => (originalMe || me).prevAll().is(e));
    if (descendents.length) return descendents.first();
    // try next parent
    const parent = me.parent().not((i, e) => (originalMe || me).parent().prevAll().is(e));
    return parent.closestNext(selector, me);
  }
})

// Proof
$('#main').closestNext('.invalid-feedback').text('main-invalid')
$('#secondary').closestNext('.invalid-feedback').text('secondary-invalid')
$('#main').closestNext('.valid-feedback').text('any-valid')
.dummy {
  color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="valid-feedback dummy">should not change</div>
<div>
  <div class="invalid-feedback dummy">should not change</div>
  <div>
    <input id="main" />
  </div>
  <div class="dummy">should not change</div>
  <div class="invalid-feedback">should change</div>
  <div>
    <input id="secondary" />
  </div>
  <div class="invalid-feedback">should change</div>
</div>
<div class="valid-feedback">should change</div>
0 голосов
/ 12 июня 2019

У меня не было возможности запустить это (РЕДАКТИРОВАТЬ: с тех пор оно было выполнено и исправление было сделано), но это поможет.у нас есть 2 функции - первая функция ищет элементы одного элемента, которые соответствуют целевому узлу, который мы ищем.Вторая функция заставляет шарик катиться и подниматься вверх по дереву узлов, если getSiblings не возвращает совпадение для целевого элемента.Я надеюсь, что это поможет .. наслаждайтесь



var getSiblings = function (elem,selector) {

    // Setup siblings array and start from the parent element of our input
    //this is under the assumption that you're only looking for an element that FOLLOWS #main/#secondary


    var sibling = elem,
    target = selector;

    // Loop through each sibling and return the target element if its found
    while (sibling) {

            if (sibling.hasClass(target) ) { //return the target element if its been located
                return sibling;
            }
        //since the element has not been located, move onto the next sibling
        if (sibling.next().length == 0) {
            return 0;
        } else {
            sibling = sibling.next();
        }

    }

    return 0; //failed to locate target
};

function firstOfClass(el,selector) {
   //our variables where startingPoint is #main/#secondary and target is what we're looking for
   var startingPoint = el,
   target = selector;
   while (startingPoint.parent().length) { 
//if it has a parent lets take a look using our getSiblings function
       var targetCheck = getSiblings(startingPoint, target);
       if ( targetCheck != 0 ) { //returns 0 if no siblings are found i.e we found a match
           return targetCheck;
       } else {
//lets keep ascending
           startingPoint = startingPoint.parent();
       }
   }
   return getSiblings(startingPoint, target);

}
//returns node if found, otherwise returns 0

firstOfClass( $('#main'),'valid-feedback' );

firstOfClass( $('#secondary'),'invalid-feedback' );

Как вы видите, решение не требует глубоких знаний о вашей DOM, а вместо этого методично ищет цель (если не найдено, чем останавливается) и продолжает подниматьсяпока он фактически не исчерпал свой поиск.

...