使用 jQuery 在 DOM 中查找最接近的下一个元素
Find closest next element in DOM with jQuery
在下面的例子中。我想为 #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
我还没有机会 运行 这个(编辑:它已经 运行 并且已经进行了更正)但这会成功。我们有 2 个函数 - 第一个函数寻找与我们正在搜索的目标节点匹配的元素的兄弟姐妹。如果 getSiblings 没有 return 匹配目标元素,则第二个函数开始滚动并向上攀登节点树。
我希望这会有所帮助..享受
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 有任何深入了解,而是有条不紊地搜索目标(除非找到它停止 运行ning)并继续上升直到它已经有效地用尽了它的搜索。
不确定这是否完全有效,但我认为是这样,这在某种程度上取决于您的标记的复杂程度,但即便如此,我认为它会给您下一次有效或无效的反馈 类 相对于所讨论的元素,因为您的 ID 是唯一的,并且匹配集中的索引 + 1 应该是您要查找的内容。可能矫枉过正,但它应该在 DOM 树中获得下一个最接近的树,而不仅仅是兄弟姐妹,children 等。我放入 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");
});
所以,这可行,但绝对不干净。我必须确保不包括 "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>
我看不出你的初始代码如何能更简单。毕竟,它需要迭代标记。
虽然我看到的是如何优化它,使用 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>
在下面的例子中。我想为 #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
我还没有机会 运行 这个(编辑:它已经 运行 并且已经进行了更正)但这会成功。我们有 2 个函数 - 第一个函数寻找与我们正在搜索的目标节点匹配的元素的兄弟姐妹。如果 getSiblings 没有 return 匹配目标元素,则第二个函数开始滚动并向上攀登节点树。 我希望这会有所帮助..享受
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 有任何深入了解,而是有条不紊地搜索目标(除非找到它停止 运行ning)并继续上升直到它已经有效地用尽了它的搜索。
不确定这是否完全有效,但我认为是这样,这在某种程度上取决于您的标记的复杂程度,但即便如此,我认为它会给您下一次有效或无效的反馈 类 相对于所讨论的元素,因为您的 ID 是唯一的,并且匹配集中的索引 + 1 应该是您要查找的内容。可能矫枉过正,但它应该在 DOM 树中获得下一个最接近的树,而不仅仅是兄弟姐妹,children 等。我放入 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");
});
所以,这可行,但绝对不干净。我必须确保不包括 "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>
我看不出你的初始代码如何能更简单。毕竟,它需要迭代标记。
虽然我看到的是如何优化它,使用 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>