make response handling configurable

This commit is contained in:
Carson Gross
2023-11-14 14:03:14 -07:00
parent 8c5e053377
commit 3287445049
3 changed files with 299 additions and 14 deletions

View File

@@ -75,7 +75,12 @@ return (function () {
globalViewTransitions: false,
methodsThatUseUrlParams: ["get"],
selfRequestsOnly: false,
scrollIntoViewOnBoost: true
scrollIntoViewOnBoost: true,
responseHandling: [
{code:"203", swap: false},
{code:"[23]..", swap: true},
{code:"[45]..", swap: false, error:true},
]
},
parseInterval:parseInterval,
_:internalEval,
@@ -3388,6 +3393,24 @@ return (function () {
}
}
function codeMatches(responseHandlingConfig, status) {
var regExp = new RegExp(responseHandlingConfig.code);
return regExp.test(status);
}
function resolveResponseHandling(xhr) {
for (var i = 0; i < htmx.config.responseHandling.length; i++) {
var responseHandlingElement = htmx.config.responseHandling[i];
if (codeMatches(responseHandlingElement, xhr.status)) {
return responseHandlingElement;
}
}
// no matches, return no swap
return {
swap: false
}
}
function handleAjaxResponse(elt, responseInfo) {
var xhr = responseInfo.xhr;
var target = responseInfo.target;
@@ -3429,27 +3452,45 @@ return (function () {
return;
}
if (hasHeader(xhr,/HX-Retarget:/i)) {
responseInfo.target = getDocument().querySelector(xhr.getResponseHeader("HX-Retarget"));
}
var historyUpdate = determineHistoryUpdates(elt, responseInfo);
// by default htmx only swaps on 200 return codes and does not swap
// on 204 'No Content'
// this can be ovverriden by responding to the htmx:beforeSwap event and
// overriding the detail.shouldSwap property
var shouldSwap = xhr.status >= 200 && xhr.status < 400 && xhr.status !== 204;
var responseHandling = resolveResponseHandling(xhr);
var shouldSwap = responseHandling.swap;
var isError = !!responseHandling.error;
var ignoreTitle = htmx.config.ignoreTitle || responseHandling.ignoreTitle
var selectOverride = responseHandling.select;
if (responseHandling.target) {
responseInfo.target = querySelectorExt(elt, responseHandling.target);
}
var swapOverride = etc.swapOverride;
if (swapOverride == null && responseHandling.swapOverride) {
swapOverride = responseHandling.swapOverride;
}
// response headers override response handling config
if (hasHeader(xhr,/HX-Retarget:/i)) {
responseInfo.target = getDocument().querySelector(xhr.getResponseHeader("HX-Retarget"));
}
if (hasHeader(xhr,/HX-Reswap:/i)) {
swapOverride = xhr.getResponseHeader("HX-Reswap");
}
var serverResponse = xhr.response;
var isError = xhr.status >= 400;
var ignoreTitle = htmx.config.ignoreTitle
var beforeSwapDetails = mergeObjects({shouldSwap: shouldSwap, serverResponse:serverResponse, isError:isError, ignoreTitle:ignoreTitle }, responseInfo);
var beforeSwapDetails = mergeObjects({shouldSwap: shouldSwap, serverResponse:serverResponse, isError:isError, ignoreTitle:ignoreTitle, selectOverride:selectOverride }, responseInfo);
if (responseHandling.event && !triggerEvent(target, responseHandling.event, beforeSwapDetails)) return;
if (!triggerEvent(target, 'htmx:beforeSwap', beforeSwapDetails)) return;
target = beforeSwapDetails.target; // allow re-targeting
serverResponse = beforeSwapDetails.serverResponse; // allow updating content
isError = beforeSwapDetails.isError; // allow updating error
ignoreTitle = beforeSwapDetails.ignoreTitle; // allow updating ignoring title
selectOverride = beforeSwapDetails.selectOverride; //allow updating select override
responseInfo.target = target; // Make updated target available to response events
responseInfo.failed = isError; // Make failed property available to response events
@@ -3469,10 +3510,6 @@ return (function () {
saveCurrentPageToHistory();
}
var swapOverride = etc.swapOverride;
if (hasHeader(xhr,/HX-Reswap:/i)) {
swapOverride = xhr.getResponseHeader("HX-Reswap");
}
var swapSpec = getSwapSpecification(elt, swapOverride);
if (swapSpec.hasOwnProperty('ignoreTitle')) {
@@ -3501,7 +3538,6 @@ return (function () {
// safari issue - see https://github.com/microsoft/playwright/issues/5894
}
var selectOverride;
if (hasHeader(xhr, /HX-Reselect:/i)) {
selectOverride = xhr.getResponseHeader("HX-Reselect");
}

248
test/core/config.js Normal file
View File

@@ -0,0 +1,248 @@
describe("htmx config test", function () {
beforeEach(function () {
this.server = makeServer();
clearWorkArea();
});
afterEach(function () {
this.server.restore();
clearWorkArea();
});
it('swaps normally with no config update', function () {
var responseCode = null;
this.server.respondWith("GET", "/test", function (xhr, id) {
xhr.respond(responseCode, {"Content-Type": "application/json"}, "" + responseCode);
});
responseCode = 200; // 200 should cause a swap by default
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("200");
responseCode = 203; // 203 should not cause a swap by default
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("Click Me!");
responseCode = 300; // 300 should cause a swap by default
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("300");
responseCode = 400; // 400 should not cause a swap by default
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("Click Me!");
responseCode = 500; // 500 should not cause a swap by default
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("Click Me!");
});
it('swap all config should swap everything', function () {
var originalResponseHandling = htmx.config.responseHandling;
try {
htmx.config.responseHandling = [{code: '...', swap: true}];
var responseCode = null;
this.server.respondWith("GET", "/test", function (xhr, id) {
xhr.respond(responseCode, {"Content-Type": "application/json"}, "" + responseCode);
});
responseCode = 200; // 200 should cause a swap by default
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("200");
responseCode = 203; // 203 should not cause a swap by default
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("203");
responseCode = 300; // 300 should cause a swap by default
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("300");
responseCode = 400; // 400 should not cause a swap by default
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("400");
responseCode = 500; // 500 should not cause a swap by default
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("500");
} finally {
htmx.config.responseHandling = originalResponseHandling;
}
});
it('can change the target of a given response code', function () {
var originalResponseHandling = htmx.config.responseHandling;
try {
htmx.config.responseHandling = originalResponseHandling.slice();
htmx.config.responseHandling.unshift({code: "444", swap: true, target: "#a-div"})
var responseCode = null;
this.server.respondWith("GET", "/test", function (xhr, id) {
xhr.respond(responseCode, {"Content-Type": "application/json"}, "" + responseCode);
});
responseCode = 444;
var div = make('<div id="a-div">Another Div</div>')
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("Click Me!");
div.innerHTML.should.equal("444");
} finally {
htmx.config.responseHandling = originalResponseHandling;
}
});
it('can change the swap type of a given response code', function () {
var originalResponseHandling = htmx.config.responseHandling;
try {
htmx.config.responseHandling = originalResponseHandling.slice();
htmx.config.responseHandling.unshift({code: "444", swap: true, target: "#a-div", swapOverride:"outerHTML"})
var responseCode = null;
this.server.respondWith("GET", "/test", function (xhr, id) {
xhr.respond(responseCode, {"Content-Type": "application/json"}, "" + responseCode);
});
responseCode = 444;
var div = make('<div><div id="a-div">Another Div</div></div>')
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("Click Me!");
div.innerHTML.should.equal("444");
} finally {
htmx.config.responseHandling = originalResponseHandling;
}
});
it('can change the select of a given response code', function () {
var originalResponseHandling = htmx.config.responseHandling;
try {
htmx.config.responseHandling = originalResponseHandling.slice();
htmx.config.responseHandling.unshift({code: "444", swap: true, select: ".foo"})
var responseCode = null;
this.server.respondWith("GET", "/test", function (xhr, id) {
xhr.respond(responseCode, {"Content-Type": "application/json"}, "<div><a class='foo'>" + responseCode + "</a></div>");
});
responseCode = 444;
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("<a class=\"foo\">444</a>");
} finally {
htmx.config.responseHandling = originalResponseHandling;
}
});
it('can change if the title is ignored for a given response code', function () {
var originalResponseHandling = htmx.config.responseHandling;
var originalTitle = document.title;
try {
htmx.config.responseHandling = originalResponseHandling.slice();
htmx.config.responseHandling.unshift({code: "444", swap:true, ignoreTitle:true})
var responseCode = null;
this.server.respondWith("GET", "/test", function (xhr, id) {
xhr.respond(responseCode, {"Content-Type": "application/json"}, "<title>Should Not Be Set</title>" + responseCode);
});
responseCode = 444;
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("444");
document.title.should.equal(originalTitle);
} finally {
htmx.config.responseHandling = originalResponseHandling;
}
});
it('can change if error for a given response code', function () {
var originalResponseHandling = htmx.config.responseHandling;
var errorDetected = false;
var handler = htmx.on('htmx:responseError', function() {
errorDetected = true;
});
try {
htmx.config.responseHandling = originalResponseHandling.slice();
htmx.config.responseHandling.unshift({code: "444", swap:true, error:false})
var responseCode = null;
this.server.respondWith("GET", "/test", function (xhr, id) {
xhr.respond(responseCode, {"Content-Type": "application/json"}, "" + responseCode);
});
responseCode = 444;
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("444");
errorDetected.should.equal(false);
} finally {
htmx.off('htmx:responseError', handler);
htmx.config.responseHandling = originalResponseHandling;
}
});
it('can trigger an event for a given response code', function () {
var originalResponseHandling = htmx.config.responseHandling;
var myEventWasTriggered = false;
var handler = htmx.on('myEvent', function() {
myEventWasTriggered = true;
});
try {
htmx.config.responseHandling = originalResponseHandling.slice();
htmx.config.responseHandling.unshift({code: "444", swap:false, error:false, event:'myEvent'})
var responseCode = null;
this.server.respondWith("GET", "/test", function (xhr, id) {
xhr.respond(responseCode, {"Content-Type": "application/json"}, "" + responseCode);
});
responseCode = 444;
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
btn.innerHTML.should.equal("Click Me!");
myEventWasTriggered.should.equal(true);
} finally {
htmx.off('htmx:responseError', handler);
htmx.config.responseHandling = originalResponseHandling;
}
});
});

View File

@@ -58,6 +58,7 @@
<script src="core/internals.js"></script>
<script src="core/api.js"></script>
<script src="core/ajax.js"></script>
<script src="core/config.js"></script>
<script src="core/extensions.js"></script>
<script src="core/verbs.js"></script>
<script src="core/parameters.js"></script>