mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-10-02 23:35:13 +00:00
first pass at hx-sync
attribute
This commit is contained in:
parent
0a0f6b6bab
commit
e6751be2ee
111
src/htmx.js
111
src/htmx.js
@ -1063,7 +1063,7 @@ return (function () {
|
|||||||
triggerSpec.delay = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA));
|
triggerSpec.delay = parseInterval(consumeUntil(tokens, WHITESPACE_OR_COMMA));
|
||||||
} else if (token === "from" && tokens[0] === ":") {
|
} else if (token === "from" && tokens[0] === ":") {
|
||||||
tokens.shift();
|
tokens.shift();
|
||||||
let from_arg = consumeUntil(tokens, WHITESPACE_OR_COMMA);
|
var from_arg = consumeUntil(tokens, WHITESPACE_OR_COMMA);
|
||||||
if (from_arg === "closest" || from_arg === "find") {
|
if (from_arg === "closest" || from_arg === "find") {
|
||||||
tokens.shift();
|
tokens.shift();
|
||||||
from_arg +=
|
from_arg +=
|
||||||
@ -2226,42 +2226,87 @@ return (function () {
|
|||||||
return; // do not issue requests for elements removed from the DOM
|
return; // do not issue requests for elements removed from the DOM
|
||||||
}
|
}
|
||||||
var target = etc.targetOverride || getTarget(elt);
|
var target = etc.targetOverride || getTarget(elt);
|
||||||
if (target == null) {
|
if (target == null || target == DUMMY_ELT) {
|
||||||
triggerErrorEvent(elt, 'htmx:targetError', {target: getAttributeValue(elt, "hx-target")});
|
triggerErrorEvent(elt, 'htmx:targetError', {target: getAttributeValue(elt, "hx-target")});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var syncElt = elt;
|
||||||
var eltData = getInternalData(elt);
|
var eltData = getInternalData(elt);
|
||||||
if (eltData.requestInFlight) {
|
var syncStrategy = getClosestAttributeValue(elt, "hx-sync");
|
||||||
var queueStrategy = 'last';
|
var queueStrategy = null;
|
||||||
if (event) {
|
var abortable = false;
|
||||||
var eventData = getInternalData(event);
|
if (syncStrategy) {
|
||||||
if (eventData && eventData.triggerSpec && eventData.triggerSpec.queue) {
|
var syncStrings = syncStrategy.split(":");
|
||||||
queueStrategy = eventData.triggerSpec.queue;
|
var selector = syncStrings[0].trim();
|
||||||
|
if (selector === "this") {
|
||||||
|
syncElt = getClosestMatch(elt, function (elt) {
|
||||||
|
return getAttributeValue(elt, "hx-sync") != null;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
syncElt = querySelectorExt(elt, selector);
|
||||||
|
}
|
||||||
|
// default to the drop strategy
|
||||||
|
syncStrategy = (syncStrings[1] || 'drop').trim();
|
||||||
|
eltData = getInternalData(syncElt);
|
||||||
|
if (syncStrategy === "drop" && eltData.xhr && eltData.abortable !== true) {
|
||||||
|
return;
|
||||||
|
} else if (syncStrategy === "abort") {
|
||||||
|
if (eltData.xhr) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
abortable = true;
|
||||||
}
|
}
|
||||||
|
} else if (syncStrategy === "replace") {
|
||||||
|
triggerEvent(syncElt, 'htmx:abort'); // abort the current request and continue
|
||||||
|
} else if (syncStrategy.indexOf("queue") === 0) {
|
||||||
|
var queueStrArray = syncStrategy.split(" ");
|
||||||
|
queueStrategy = (queueStrArray[1] || "last").trim();
|
||||||
}
|
}
|
||||||
if (eltData.queuedRequests == null) {
|
|
||||||
eltData.queuedRequests = [];
|
|
||||||
}
|
|
||||||
if (queueStrategy === "first" && eltData.queuedRequests.length === 0) {
|
|
||||||
eltData.queuedRequests.push(function () {
|
|
||||||
issueAjaxRequest(verb, path, elt, event, etc)
|
|
||||||
});
|
|
||||||
} else if (queueStrategy === "all") {
|
|
||||||
eltData.queuedRequests.push(function () {
|
|
||||||
issueAjaxRequest(verb, path, elt, event, etc)
|
|
||||||
});
|
|
||||||
} else if (queueStrategy === "last") {
|
|
||||||
eltData.queuedRequests = []; // dump existing queue
|
|
||||||
eltData.queuedRequests.push(function () {
|
|
||||||
issueAjaxRequest(verb, path, elt, event, etc)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
eltData.requestInFlight = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eltData.xhr) {
|
||||||
|
if (eltData.abortable) {
|
||||||
|
triggerEvent(syncElt, 'htmx:abort'); // abort the current request and continue
|
||||||
|
} else {
|
||||||
|
if(queueStrategy == null){
|
||||||
|
if (event) {
|
||||||
|
var eventData = getInternalData(event);
|
||||||
|
if (eventData && eventData.triggerSpec && eventData.triggerSpec.queue) {
|
||||||
|
queueStrategy = eventData.triggerSpec.queue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (queueStrategy == null) {
|
||||||
|
queueStrategy = "last";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (eltData.queuedRequests == null) {
|
||||||
|
eltData.queuedRequests = [];
|
||||||
|
}
|
||||||
|
if (queueStrategy === "first" && eltData.queuedRequests.length === 0) {
|
||||||
|
eltData.queuedRequests.push(function () {
|
||||||
|
issueAjaxRequest(verb, path, elt, event, etc)
|
||||||
|
});
|
||||||
|
} else if (queueStrategy === "all") {
|
||||||
|
eltData.queuedRequests.push(function () {
|
||||||
|
issueAjaxRequest(verb, path, elt, event, etc)
|
||||||
|
});
|
||||||
|
} else if (queueStrategy === "last") {
|
||||||
|
eltData.queuedRequests = []; // dump existing queue
|
||||||
|
eltData.queuedRequests.push(function () {
|
||||||
|
issueAjaxRequest(verb, path, elt, event, etc)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
eltData.xhr = xhr;
|
||||||
|
eltData.abortable = abortable;
|
||||||
var endRequestLock = function(){
|
var endRequestLock = function(){
|
||||||
eltData.requestInFlight = false
|
eltData.xhr = null;
|
||||||
|
eltData.abortable = false;
|
||||||
if (eltData.queuedRequests != null &&
|
if (eltData.queuedRequests != null &&
|
||||||
eltData.queuedRequests.length > 0) {
|
eltData.queuedRequests.length > 0) {
|
||||||
var queuedRequest = eltData.queuedRequests.shift();
|
var queuedRequest = eltData.queuedRequests.shift();
|
||||||
@ -2289,7 +2334,6 @@ return (function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
|
|
||||||
var headers = getHeaders(elt, target, promptResponse);
|
var headers = getHeaders(elt, target, promptResponse);
|
||||||
if (etc.headers) {
|
if (etc.headers) {
|
||||||
@ -2760,6 +2804,13 @@ return (function () {
|
|||||||
insertIndicatorStyles();
|
insertIndicatorStyles();
|
||||||
var body = getDocument().body;
|
var body = getDocument().body;
|
||||||
processNode(body);
|
processNode(body);
|
||||||
|
body.addEventListener("htmx:abort", function (evt) {
|
||||||
|
var target = evt.target;
|
||||||
|
var internalData = getInternalData(target);
|
||||||
|
if (internalData && internalData.xhr) {
|
||||||
|
internalData.xhr.abort();
|
||||||
|
}
|
||||||
|
});
|
||||||
window.onpopstate = function (event) {
|
window.onpopstate = function (event) {
|
||||||
if (event.state && event.state.htmx) {
|
if (event.state && event.state.htmx) {
|
||||||
restoreHistory();
|
restoreHistory();
|
||||||
|
224
test/attributes/hx-sync.js
Normal file
224
test/attributes/hx-sync.js
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
describe("hx-sync attribute", function(){
|
||||||
|
beforeEach(function() {
|
||||||
|
this.server = makeServer();
|
||||||
|
clearWorkArea();
|
||||||
|
});
|
||||||
|
afterEach(function() {
|
||||||
|
this.server.restore();
|
||||||
|
clearWorkArea();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can use drop strategy', function()
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
this.server.respondWith("GET", "/test", function(xhr){
|
||||||
|
xhr.respond(200, {}, "Click " + count++);
|
||||||
|
});
|
||||||
|
make('<div hx-sync="this:drop"><button id="b1" hx-get="/test">Initial</button>' +
|
||||||
|
' <button id="b2" hx-get="/test">Initial</button></div>')
|
||||||
|
var b1 = byId("b1");
|
||||||
|
var b2 = byId("b2");
|
||||||
|
b1.click();
|
||||||
|
b2.click();
|
||||||
|
this.server.respond();
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Click 0');
|
||||||
|
b2.innerHTML.should.equal('Initial');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults to the drop strategy', function()
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
this.server.respondWith("GET", "/test", function(xhr){
|
||||||
|
xhr.respond(200, {}, "Click " + count++);
|
||||||
|
});
|
||||||
|
make('<div hx-sync="this"><button id="b1" hx-get="/test">Initial</button>' +
|
||||||
|
' <button id="b2" hx-get="/test">Initial</button></div>')
|
||||||
|
var b1 = byId("b1");
|
||||||
|
var b2 = byId("b2");
|
||||||
|
b1.click();
|
||||||
|
b2.click();
|
||||||
|
this.server.respond();
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Click 0');
|
||||||
|
b2.innerHTML.should.equal('Initial');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can use replace strategy', function()
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
this.server.respondWith("GET", "/test", function(xhr){
|
||||||
|
xhr.respond(200, {}, "Click " + count++);
|
||||||
|
});
|
||||||
|
make('<div hx-sync="this:replace"><button id="b1" hx-get="/test">Initial</button>' +
|
||||||
|
' <button id="b2" hx-get="/test">Initial</button></div>')
|
||||||
|
var b1 = byId("b1");
|
||||||
|
var b2 = byId("b2");
|
||||||
|
b1.click();
|
||||||
|
b2.click();
|
||||||
|
this.server.respond();
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Initial');
|
||||||
|
b2.innerHTML.should.equal('Click 0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can use queue all strategy', function()
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
this.server.respondWith("GET", "/test", function(xhr){
|
||||||
|
xhr.respond(200, {}, "Click " + count++);
|
||||||
|
});
|
||||||
|
make('<div hx-sync="this:queue all"><button id="b1" hx-get="/test">Initial</button>' +
|
||||||
|
' <button id="b2" hx-get="/test">Initial</button>' +
|
||||||
|
' <button id="b3" hx-get="/test">Initial</button></div>')
|
||||||
|
var b1 = byId("b1");
|
||||||
|
b1.click();
|
||||||
|
|
||||||
|
var b2 = byId("b2");
|
||||||
|
b2.click();
|
||||||
|
|
||||||
|
var b3 = byId("b3");
|
||||||
|
b3.click();
|
||||||
|
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Click 0');
|
||||||
|
b2.innerHTML.should.equal('Initial');
|
||||||
|
b3.innerHTML.should.equal('Initial');
|
||||||
|
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Click 0');
|
||||||
|
b2.innerHTML.should.equal('Click 1');
|
||||||
|
b3.innerHTML.should.equal('Initial');
|
||||||
|
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Click 0');
|
||||||
|
b2.innerHTML.should.equal('Click 1');
|
||||||
|
b3.innerHTML.should.equal('Click 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can use queue last strategy', function()
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
this.server.respondWith("GET", "/test", function(xhr){
|
||||||
|
xhr.respond(200, {}, "Click " + count++);
|
||||||
|
});
|
||||||
|
make('<div hx-sync="this:queue last"><button id="b1" hx-get="/test">Initial</button>' +
|
||||||
|
' <button id="b2" hx-get="/test">Initial</button>' +
|
||||||
|
' <button id="b3" hx-get="/test">Initial</button></div>')
|
||||||
|
var b1 = byId("b1");
|
||||||
|
b1.click();
|
||||||
|
|
||||||
|
var b2 = byId("b2");
|
||||||
|
b2.click();
|
||||||
|
|
||||||
|
var b3 = byId("b3");
|
||||||
|
b3.click();
|
||||||
|
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Click 0');
|
||||||
|
b2.innerHTML.should.equal('Initial');
|
||||||
|
b3.innerHTML.should.equal('Initial');
|
||||||
|
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Click 0');
|
||||||
|
b2.innerHTML.should.equal('Initial');
|
||||||
|
b3.innerHTML.should.equal('Click 1');
|
||||||
|
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Click 0');
|
||||||
|
b2.innerHTML.should.equal('Initial');
|
||||||
|
b3.innerHTML.should.equal('Click 1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can use queue first strategy', function()
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
this.server.respondWith("GET", "/test", function(xhr){
|
||||||
|
xhr.respond(200, {}, "Click " + count++);
|
||||||
|
});
|
||||||
|
make('<div hx-sync="this:queue first"><button id="b1" hx-get="/test">Initial</button>' +
|
||||||
|
' <button id="b2" hx-get="/test">Initial</button>' +
|
||||||
|
' <button id="b3" hx-get="/test">Initial</button></div>')
|
||||||
|
var b1 = byId("b1");
|
||||||
|
b1.click();
|
||||||
|
|
||||||
|
var b2 = byId("b2");
|
||||||
|
b2.click();
|
||||||
|
|
||||||
|
var b3 = byId("b3");
|
||||||
|
b3.click();
|
||||||
|
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Click 0');
|
||||||
|
b2.innerHTML.should.equal('Initial');
|
||||||
|
b3.innerHTML.should.equal('Initial');
|
||||||
|
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Click 0');
|
||||||
|
b2.innerHTML.should.equal('Click 1');
|
||||||
|
b3.innerHTML.should.equal('Initial');
|
||||||
|
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Click 0');
|
||||||
|
b2.innerHTML.should.equal('Click 1');
|
||||||
|
b3.innerHTML.should.equal('Initial');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can use abort strategy to end existing abortable request', function()
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
this.server.respondWith("GET", "/test", function(xhr){
|
||||||
|
xhr.respond(200, {}, "Click " + count++);
|
||||||
|
});
|
||||||
|
make('<div hx-sync="this"><button hx-sync="closest div:abort" id="b1" hx-get="/test">Initial</button>' +
|
||||||
|
' <button id="b2" hx-get="/test">Initial</button></div>')
|
||||||
|
var b1 = byId("b1");
|
||||||
|
var b2 = byId("b2");
|
||||||
|
b1.click();
|
||||||
|
b2.click();
|
||||||
|
this.server.respond();
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Initial');
|
||||||
|
b2.innerHTML.should.equal('Click 0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can use abort strategy to drop abortable request when one is in flight', function()
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
this.server.respondWith("GET", "/test", function(xhr){
|
||||||
|
xhr.respond(200, {}, "Click " + count++);
|
||||||
|
});
|
||||||
|
make('<div hx-sync="this"><button hx-sync="closest div:abort" id="b1" hx-get="/test">Initial</button>' +
|
||||||
|
' <button id="b2" hx-get="/test">Initial</button></div>')
|
||||||
|
var b1 = byId("b1");
|
||||||
|
var b2 = byId("b2");
|
||||||
|
b2.click();
|
||||||
|
b1.click();
|
||||||
|
this.server.respond();
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Initial');
|
||||||
|
b2.innerHTML.should.equal('Click 0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can abort a request programmatically', function()
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
this.server.respondWith("GET", "/test", function(xhr){
|
||||||
|
xhr.respond(200, {}, "Click " + count++);
|
||||||
|
});
|
||||||
|
make('<div><button id="b1" hx-get="/test">Initial</button>' +
|
||||||
|
' <button id="b2" hx-get="/test">Initial</button></div>')
|
||||||
|
var b1 = byId("b1");
|
||||||
|
var b2 = byId("b2");
|
||||||
|
b1.click();
|
||||||
|
b2.click();
|
||||||
|
|
||||||
|
htmx.trigger(b1, "htmx:abort");
|
||||||
|
|
||||||
|
this.server.respond();
|
||||||
|
this.server.respond();
|
||||||
|
b1.innerHTML.should.equal('Initial');
|
||||||
|
b2.innerHTML.should.equal('Click 0');
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
@ -73,6 +73,7 @@
|
|||||||
<script src="attributes/hx-sse.js"></script>
|
<script src="attributes/hx-sse.js"></script>
|
||||||
<script src="attributes/hx-swap-oob.js"></script>
|
<script src="attributes/hx-swap-oob.js"></script>
|
||||||
<script src="attributes/hx-swap.js"></script>
|
<script src="attributes/hx-swap.js"></script>
|
||||||
|
<script src="attributes/hx-sync.js"></script>
|
||||||
<script src="attributes/hx-target.js"></script>
|
<script src="attributes/hx-target.js"></script>
|
||||||
<script src="attributes/hx-trigger.js"></script>
|
<script src="attributes/hx-trigger.js"></script>
|
||||||
<script src="attributes/hx-vals.js"></script>
|
<script src="attributes/hx-vals.js"></script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user