【问题标题】:Find closest next element in DOM with jQuery使用 jQuery 在 DOM 中查找最近的下一个元素
【发布时间】:2019-10-27 06:34:42
【问题描述】:

在下面的例子中。我想为 #main#secondary 这两个项目找到第一个 .invalid-feedback.valid-feedback

显然我对通用案例感兴趣,这就是我为 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结构吗?
  • 预期响应?
  • @NagaSaiA 给定示例的预期响应在我的问题中
  • 鉴于答案(以及您对已删除答案的评论),您可能需要澄清标记深度是未知的。
  • @HereticMonkey 简单来说,OP 不知道从哪个ascendent 找到next

标签: javascript jquery dom jquery-traversing


【解决方案1】:

我还没有机会运行它(编辑:它已经运行并且已经进行了更正),但这会解决问题。我们有 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 有任何深入了解,而是有条不紊地搜索目标(除非找到而不是停止运行)并继续上升,直到它有效地用尽搜索。

【讨论】:

  • 好吧,看来你的解决方案看起来比我的稍微麻烦 :)
【解决方案2】:

不确定这是否完全有效,但我认为可以,这在某种程度上取决于您的标记有多复杂,但即便如此,我认为它会给您下一次出现相对于元素的有效或无效反馈类有问题,因为您的 id 是唯一的,并且匹配集中的 index + 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");
});

【讨论】:

    【解决方案3】:

    所以,这行得通,但它肯定不干净。我必须确保不包括“以前的”节点,所以我将它们过滤掉......

    我添加了几个测试用例来证明它只作用于“下一个”项目。

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

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

    【讨论】:

    • 请注意,如果是一个元素,例如第一个.invalid-feedback,没有找到,你的代码得到一个奇怪的结果。我发布了原始版本的简化版本,解决了这个问题。
    【解决方案4】:

    我看不出您的初始代码如何更简单。毕竟,它需要迭代标记。

    不过,我确实看到了如何优化它,使用 return 代替 let found = null 并且只调用一次 el.nextAll(selector)

    我还解决了找不到元素的情况,好像没有,你最终会出现异常。

    堆栈sn-p

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

    【讨论】:

      猜你喜欢
      • 2011-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-05
      • 1970-01-01
      相关资源
      最近更新 更多