使用jQuery查找DOM中最近的下一个元素

3
在下面的示例中,我想找到第一个.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

你事先知道HTML的结构吗? - Scath
预期的响应是什么? - Naga Sai A
@NagaSaiA,对于我提出的示例,期望的响应已在我的问题中。 - nowox
1
鉴于您的回答(以及您对已删除回答的评论),您可能需要澄清标记深度未知。 - Asons
@LGSon 确定,我明白了。只是要注意 closest() 实际上确实会向上移动树形结构。也许不是 OP 想要的方式,但它确实会上升。 - Heretic Monkey
显示剩余4条评论
4个回答

2
我不认为你的初始代码可以更简单。毕竟,它需要迭代标记。
但我发现可以进行优化,使用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>


-1

我还没有机会运行这个程序(编辑:现在已经运行并进行了更正),但这个程序可以解决问题。我们有两个函数-第一个函数查找与我们正在搜索的目标节点匹配的元素的兄弟节点。如果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有任何深入的了解,而是系统地搜索目标(除非找到,否则它会停止运行),并继续上升,直到有效地耗尽其搜索。


嗯,看起来你的解决方案比我的稍微有些繁琐 :) - nowox

-1

不确定这是否完全有效,但我认为是的,有点取决于您的标记有多复杂,但即使如此,我认为它将为您提供与问题元素相关的有效或无效反馈类的下一个出现,因为您的id是唯一的,并且匹配集合中的索引+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");
});

-2

所以,这个方法可以工作,但肯定不够简洁。我必须确保“previous”节点不被包括在内,因此我将它们过滤掉...

我添加了几个测试用例来证明它只对“next”项起作用。

我会选择你现有的解决方案。

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


1
请注意,如果找不到元素(例如第一个.invalid-feedback),您的代码将会得到奇怪的结果。我发布了原始版本的简化版本,解决了这个问题。 - Asons

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接