mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-09-29 22:11:22 +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));
|
||||
} else if (token === "from" && tokens[0] === ":") {
|
||||
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") {
|
||||
tokens.shift();
|
||||
from_arg +=
|
||||
@ -2226,42 +2226,87 @@ return (function () {
|
||||
return; // do not issue requests for elements removed from the DOM
|
||||
}
|
||||
var target = etc.targetOverride || getTarget(elt);
|
||||
if (target == null) {
|
||||
if (target == null || target == DUMMY_ELT) {
|
||||
triggerErrorEvent(elt, 'htmx:targetError', {target: getAttributeValue(elt, "hx-target")});
|
||||
return;
|
||||
}
|
||||
|
||||
var syncElt = elt;
|
||||
var eltData = getInternalData(elt);
|
||||
if (eltData.requestInFlight) {
|
||||
var queueStrategy = 'last';
|
||||
if (event) {
|
||||
var eventData = getInternalData(event);
|
||||
if (eventData && eventData.triggerSpec && eventData.triggerSpec.queue) {
|
||||
queueStrategy = eventData.triggerSpec.queue;
|
||||
var syncStrategy = getClosestAttributeValue(elt, "hx-sync");
|
||||
var queueStrategy = null;
|
||||
var abortable = false;
|
||||
if (syncStrategy) {
|
||||
var syncStrings = syncStrategy.split(":");
|
||||
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(){
|
||||
eltData.requestInFlight = false
|
||||
eltData.xhr = null;
|
||||
eltData.abortable = false;
|
||||
if (eltData.queuedRequests != null &&
|
||||
eltData.queuedRequests.length > 0) {
|
||||
var queuedRequest = eltData.queuedRequests.shift();
|
||||
@ -2289,7 +2334,6 @@ return (function () {
|
||||
}
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
var headers = getHeaders(elt, target, promptResponse);
|
||||
if (etc.headers) {
|
||||
@ -2760,6 +2804,13 @@ return (function () {
|
||||
insertIndicatorStyles();
|
||||
var body = getDocument().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) {
|
||||
if (event.state && event.state.htmx) {
|
||||
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-swap-oob.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-trigger.js"></script>
|
||||
<script src="attributes/hx-vals.js"></script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user