In the following example. I would like to find the first .invalid-feedback
and .valid-feedback
for both items #main
and #secondary
.
Obviously I am interested in the generic case, that's the reason why I wrote a prototype extension for 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>
What I wrote seems very complicated and I am expecting this kind of DOM traversal function to be part of jQuery out of the box. Unfortunately, I did not found any related function on the manual.
Is there a simpler way to achieve the same result as what closestNext
does?
EDIT
From a more algorithmic side I am looking for a tree traversing function that goes in the following order, but with a complexity better than what I achieved in my example.
.
├── A1
│ ├── B1
│ │ ├── C1
│ │ ├── C2
│ │ └── C3
│ ├── B2
│ │ ├── C4 <--- Entry point
│ │ ├── C5
│ │ └── C6
│ └── B3
│ ├── C7
│ ├── C8
│ └── C9
└── A2
├── B4
│ ├── C10
│ └── C11
└── B5
├── C12
└── C13
From the C4
Entry point, the exploration order is:
>>> traverse(C4)
C5, C6, B3, C7, C8, C9, A2, B4, C10, C11, B5, C12, C13
I can't see how your initial code can be simpler. After all, it needs to iterate the markup.
What I did see though, was how it could be optimized, using return
in favor of let found = null
and only call el.nextAll(selector)
once.
I also addressed cases where the element isn't found, as if not, you end up with an exception.
Stack snippet
$.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>