[New feature] selector-less next and previous targets (#1478)

* nextElementSibling and previousElementSibling target selectors

* Renamed nextElementSibling => next, previousElementSibling => previous
This commit is contained in:
Vincent
2023-10-07 06:25:03 +02:00
committed by GitHub
parent 4f63581c55
commit 7274454360
6 changed files with 94 additions and 7 deletions

View File

@@ -596,8 +596,12 @@ return (function () {
return [closest(elt, normalizeSelector(selector.substr(8)))];
} else if (selector.indexOf("find ") === 0) {
return [find(elt, normalizeSelector(selector.substr(5)))];
} else if (selector === "next") {
return [elt.nextElementSibling]
} else if (selector.indexOf("next ") === 0) {
return [scanForwardQuery(elt, normalizeSelector(selector.substr(5)))];
} else if (selector === "previous") {
return [elt.previousElementSibling]
} else if (selector.indexOf("previous ") === 0) {
return [scanBackwardsQuery(elt, normalizeSelector(selector.substr(9)))];
} else if (selector === 'document') {
@@ -1279,12 +1283,14 @@ return (function () {
var from_arg = consumeUntil(tokens, WHITESPACE_OR_COMMA);
if (from_arg === "closest" || from_arg === "find" || from_arg === "next" || from_arg === "previous") {
tokens.shift();
from_arg +=
" " +
consumeUntil(
tokens,
WHITESPACE_OR_COMMA
);
var selector = consumeUntil(
tokens,
WHITESPACE_OR_COMMA
)
// `next` and `previous` allow a selector-less syntax
if (selector.length > 0) {
from_arg += " " + selector;
}
}
triggerSpec.from = from_arg;
} else if (token === "target" && tokens[0] === ":") {

View File

@@ -202,4 +202,43 @@ describe("hx-target attribute", function(){
div3.innerHTML.should.equal("Clicked!");
});
it('targets a `next` element properly without selector', function()
{
this.server.respondWith("GET", "/test", "Clicked!");
make('<div>' +
' <div id="d3"></div>' +
' <button id="b1" hx-target="next" hx-get="/test">Click Me!</button>' +
' <div id="d1"></div>' +
' <div id="d2"></div>' +
'</div>')
var btn = byId("b1")
var div1 = byId("d1")
var div2 = byId("d2")
var div3 = byId("d3")
btn.click();
this.server.respond();
div1.innerHTML.should.equal("Clicked!");
div2.innerHTML.should.equal("");
div3.innerHTML.should.equal("");
});
it('targets a `previous` element properly without selector', function()
{
this.server.respondWith("GET", "/test", "Clicked!");
make('<div>' +
' <div id="d3"></div>' +
' <button id="b1" hx-target="previous" hx-get="/test">Click Me!</button>' +
' <div id="d1"></div>' +
' <div id="d2"></div>' +
'</div>')
var btn = byId("b1")
var div1 = byId("d1")
var div2 = byId("d2")
var div3 = byId("d3")
btn.click();
this.server.respond();
div1.innerHTML.should.equal("");
div2.innerHTML.should.equal("");
div3.innerHTML.should.equal("Clicked!");
});
})

View File

@@ -506,6 +506,36 @@ describe("hx-trigger attribute", function(){
a1.innerHTML.should.equal("Requests: 1");
});
it('from clause works with next', function()
{
var requests = 0;
this.server.respondWith("GET", "/test", function (xhr) {
requests++;
xhr.respond(200, {}, "Requests: " + requests);
});
make('<div hx-trigger="click from:next" hx-target="#a1" hx-get="/test"></div><a id="a1">Requests: 0</a>');
var a1 = byId('a1');
a1.innerHTML.should.equal("Requests: 0");
a1.click();
this.server.respond();
a1.innerHTML.should.equal("Requests: 1");
});
it('from clause works with previous', function()
{
var requests = 0;
this.server.respondWith("GET", "/test", function (xhr) {
requests++;
xhr.respond(200, {}, "Requests: " + requests);
});
make('<a id="a1">Requests: 0</a><div hx-trigger="click from:previous" hx-target="#a1" hx-get="/test"></div>');
var a1 = byId('a1');
a1.innerHTML.should.equal("Requests: 0");
a1.click();
this.server.respond();
a1.innerHTML.should.equal("Requests: 1");
});
it('event listeners on other elements are removed when an element is swapped out', function()
{
var requests = 0;

View File

@@ -12,10 +12,14 @@ function make(htmlStr) {
var wa = getWorkArea();
var child = null;
var children = fragment.children || fragment.childNodes; // IE
var appendedChildren = []
while(children.length > 0) {
child = children[0];
wa.appendChild(child);
htmx.process(child);
appendedChildren.push(child)
}
for (var i = 0; i < appendedChildren.length; i++) {
htmx.process(appendedChildren[i]);
}
return child; // return last added element
};

View File

@@ -11,8 +11,10 @@ request. The value of this attribute can be:
ancestor element or itself, that matches the given CSS selector
(e.g. `closest tr` will target the closest table row to the element).
* `find <CSS selector>` which will find the first child descendant element that matches the given CSS selector.
* `next` which resolves to [element.nextElementSibling](https://developer.mozilla.org/docs/Web/API/Element/nextElementSibling)
* `next <CSS selector>` which will scan the DOM forward for the first element that matches the given CSS selector.
(e.g. `next .error` will target the closest following sibling element with `error` class)
* `previous` which resolves to [element.previousElementSibling](https://developer.mozilla.org/docs/Web/API/Element/previousElementSibling)
* `previous <CSS selector>` which will scan the DOM backwards for the first element that matches the given CSS selector.
(e.g `previous .error` will target the closest previous sibling with `error` class)

View File

@@ -61,6 +61,12 @@ is seen again before the delay completes, it is ignored, the element will trigge
* `window` - listen for events on the window
* `closest <CSS selector>` - finds the [closest](https://developer.mozilla.org/docs/Web/API/Element/closest) ancestor element or itself, matching the given css selector
* `find <CSS selector>` - finds the closest child matching the given css selector
* `next` resolves to [element.nextElementSibling](https://developer.mozilla.org/docs/Web/API/Element/nextElementSibling)
* `next <CSS selector>` scans the DOM forward for the first element that matches the given CSS selector.
(e.g. `next .error` will target the closest following sibling element with `error` class)
* `previous` resolves to [element.previousElementSibling](https://developer.mozilla.org/docs/Web/API/Element/previousElementSibling)
* `previous <CSS selector>` scans the DOM backwards for the first element that matches the given CSS selector.
(e.g `previous .error` will target the closest previous sibling with `error` class)
* `target:<CSS selector>` - allows you to filter via a CSS selector on the target of the event. This can be useful when you want to listen for
triggers from elements that might not be in the DOM at the point of initialization, by, for example, listening on the body,
but with a target filter for a child element