mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-10-02 15:25:26 +00:00
Merge branch 'dev' into pullrequest-preload
This commit is contained in:
commit
a7e0d80514
378
src/htmx.js
378
src/htmx.js
@ -18,6 +18,7 @@ return (function () {
|
||||
on: addEventListenerImpl,
|
||||
off: removeEventListenerImpl,
|
||||
trigger : triggerEvent,
|
||||
ajax : ajaxHelper,
|
||||
find : find,
|
||||
findAll : findAll,
|
||||
closest : closest,
|
||||
@ -469,6 +470,14 @@ return (function () {
|
||||
});
|
||||
}
|
||||
|
||||
function handlePreservedElements(fragment) {
|
||||
forEach(findAll(fragment, '[hx-preserve], [data-hx-preserve]'), function (preservedElt) {
|
||||
var id = getAttributeValue(preservedElt, "id");
|
||||
var oldElt = getDocument().getElementById(id);
|
||||
preservedElt.parentNode.replaceChild(oldElt, preservedElt);
|
||||
});
|
||||
}
|
||||
|
||||
function handleAttributes(parentNode, fragment, settleInfo) {
|
||||
forEach(fragment.querySelectorAll("[id]"), function (newNode) {
|
||||
if (newNode.id && newNode.id.length > 0) {
|
||||
@ -655,6 +664,7 @@ return (function () {
|
||||
var fragment = makeFragment(responseText);
|
||||
if (fragment) {
|
||||
handleOutOfBandSwaps(fragment, settleInfo);
|
||||
handlePreservedElements(fragment);
|
||||
fragment = maybeSelectFromResponse(elt, fragment);
|
||||
return swap(swapStyle, elt, target, fragment, settleInfo);
|
||||
}
|
||||
@ -838,7 +848,7 @@ return (function () {
|
||||
var nodeData = getInternalData(elt);
|
||||
nodeData.timeout = setTimeout(function () {
|
||||
if (bodyContains(elt) && nodeData.cancelled !== true) {
|
||||
issueAjaxRequest(elt, verb, path);
|
||||
issueAjaxRequest(verb, path, elt);
|
||||
processPolling(elt, verb, getAttributeValue(elt, "hx-" + verb), interval);
|
||||
}
|
||||
}, interval);
|
||||
@ -929,15 +939,15 @@ return (function () {
|
||||
|
||||
if (triggerSpec.throttle) {
|
||||
elementData.throttle = setTimeout(function(){
|
||||
issueAjaxRequest(elt, verb, path, evt.target, evt);
|
||||
issueAjaxRequest(verb, path, elt, evt);
|
||||
elementData.throttle = null;
|
||||
}, triggerSpec.throttle);
|
||||
} else if (triggerSpec.delay) {
|
||||
elementData.delayed = setTimeout(function(){
|
||||
issueAjaxRequest(elt, verb, path, evt.target, evt);
|
||||
issueAjaxRequest(verb, path, elt, evt);
|
||||
}, triggerSpec.delay);
|
||||
} else {
|
||||
issueAjaxRequest(elt, verb, path, evt.target, evt);
|
||||
issueAjaxRequest(verb, path, elt, evt);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -969,7 +979,7 @@ return (function () {
|
||||
var nodeData = getInternalData(elt);
|
||||
if (!nodeData.revealed && isScrolledIntoView(elt)) {
|
||||
nodeData.revealed = true;
|
||||
issueAjaxRequest(elt, nodeData.verb, nodeData.path);
|
||||
issueAjaxRequest(nodeData.verb, nodeData.path, elt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1032,7 +1042,7 @@ return (function () {
|
||||
if (webSocketSourceElt) {
|
||||
var webSocket = getInternalData(webSocketSourceElt).webSocket;
|
||||
elt.addEventListener(getTriggerSpecs(elt)[0].trigger, function (evt) {
|
||||
var headers = getHeaders(elt, webSocketSourceElt, null, elt);
|
||||
var headers = getHeaders(elt, webSocketSourceElt);
|
||||
var results = getInputValues(elt, 'post');
|
||||
var errors = results.errors;
|
||||
var rawParameters = results.values;
|
||||
@ -1121,7 +1131,7 @@ return (function () {
|
||||
var sseListener = function () {
|
||||
if (!maybeCloseSSESource(sseSourceElt)) {
|
||||
if (bodyContains(elt)) {
|
||||
issueAjaxRequest(elt, verb, path);
|
||||
issueAjaxRequest(verb, path, elt);
|
||||
} else {
|
||||
sseEventSource.removeEventListener(sseEventName, sseListener);
|
||||
}
|
||||
@ -1151,7 +1161,7 @@ return (function () {
|
||||
var load = function(){
|
||||
if (!nodeData.loaded) {
|
||||
nodeData.loaded = true;
|
||||
issueAjaxRequest(elt, verb, path);
|
||||
issueAjaxRequest(verb, path, elt);
|
||||
}
|
||||
}
|
||||
if (delay) {
|
||||
@ -1419,7 +1429,7 @@ return (function () {
|
||||
}
|
||||
|
||||
function restoreHistory(path) {
|
||||
saveHistory(currentPathForHistory);
|
||||
saveHistory();
|
||||
path = path || location.pathname+location.search;
|
||||
triggerEvent(getDocument().body, "htmx:historyRestore", {path:path});
|
||||
var cached = getCachedHistory(path);
|
||||
@ -1496,7 +1506,7 @@ return (function () {
|
||||
return true;
|
||||
}
|
||||
|
||||
function processInputValue(processed, values, errors, elt) {
|
||||
function processInputValue(processed, values, errors, elt, validate) {
|
||||
if (elt == null || haveSeenNode(processed, elt)) {
|
||||
return;
|
||||
} else {
|
||||
@ -1534,12 +1544,14 @@ return (function () {
|
||||
values[name] = value;
|
||||
}
|
||||
}
|
||||
validateElement(elt, errors);
|
||||
if (validate) {
|
||||
validateElement(elt, errors);
|
||||
}
|
||||
}
|
||||
if (matches(elt, 'form')) {
|
||||
var inputs = elt.elements;
|
||||
forEach(inputs, function(input) {
|
||||
processInputValue(processed, values, errors, input);
|
||||
processInputValue(processed, values, errors, input, validate);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1556,28 +1568,37 @@ return (function () {
|
||||
|
||||
function getInputValues(elt, verb) {
|
||||
var processed = [];
|
||||
var values = {};
|
||||
var values = {
|
||||
form: {},
|
||||
element: {},
|
||||
includes: {},
|
||||
};
|
||||
var errors = [];
|
||||
|
||||
// only validate when form is directly submitted and novalidate is not set
|
||||
var validate = matches(elt, 'form') && elt.noValidate !== true;
|
||||
|
||||
// for a non-GET include the closest form
|
||||
if (verb !== 'get') {
|
||||
processInputValue(processed, values, errors, closest(elt, 'form'));
|
||||
processInputValue(processed, values.form, errors, closest(elt, 'form'), validate);
|
||||
}
|
||||
|
||||
// include the element itself
|
||||
processInputValue(processed, values, errors, elt);
|
||||
processInputValue(processed, values.element, errors, elt, validate);
|
||||
|
||||
// include any explicit includes
|
||||
var includes = getClosestAttributeValue(elt, "hx-include");
|
||||
if (includes) {
|
||||
var nodes = getDocument().querySelectorAll(includes);
|
||||
forEach(nodes, function(node) {
|
||||
processInputValue(processed, values, errors, node);
|
||||
processInputValue(processed, values.includes, errors, node, validate);
|
||||
});
|
||||
}
|
||||
|
||||
var mergedValues = mergeObjects(values.includes, values.element);
|
||||
mergedValues = mergeObjects(mergedValues, values.form);
|
||||
|
||||
return {errors:errors, values:values};
|
||||
return {errors:errors, values:mergedValues};
|
||||
}
|
||||
|
||||
function appendParam(returnStr, name, realValue) {
|
||||
@ -1626,7 +1647,7 @@ return (function () {
|
||||
// Ajax
|
||||
//====================================================================
|
||||
|
||||
function getHeaders(elt, target, prompt, eventTarget) {
|
||||
function getHeaders(elt, target, prompt) {
|
||||
var headers = {
|
||||
"HX-Request" : "true",
|
||||
"HX-Trigger" : getRawAttribute(elt, "id"),
|
||||
@ -1637,16 +1658,6 @@ return (function () {
|
||||
if (prompt !== undefined) {
|
||||
headers["HX-Prompt"] = prompt;
|
||||
}
|
||||
if (eventTarget) {
|
||||
headers["HX-Event-Target"] = getRawAttribute(eventTarget, "id");
|
||||
}
|
||||
if (getDocument().activeElement) {
|
||||
headers["HX-Active-Element"] = getRawAttribute(getDocument().activeElement, "id");
|
||||
headers["HX-Active-Element-Name"] = getRawAttribute(getDocument().activeElement, "name");
|
||||
if (getDocument().activeElement.value) {
|
||||
headers["HX-Active-Element-Value"] = getRawAttribute(getDocument().activeElement, "value");
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
@ -1817,19 +1828,38 @@ return (function () {
|
||||
return xhr.getAllResponseHeaders().match(regexp);
|
||||
}
|
||||
|
||||
function issueAjaxRequest(elt, verb, path, eventTarget, triggeringEvent) {
|
||||
function ajaxHelper(verb, path, context) {
|
||||
if (context) {
|
||||
if (context instanceof Element || isType(context, 'String')) {
|
||||
issueAjaxRequest(verb, path, null, null, null, resolveTarget(context));
|
||||
} else {
|
||||
issueAjaxRequest(verb, path, resolveTarget(context.source), context.event, context.handler, resolveTarget(context.target));
|
||||
}
|
||||
} else {
|
||||
issueAjaxRequest(verb, path);
|
||||
}
|
||||
}
|
||||
|
||||
function issueAjaxRequest(verb, path, elt, event, responseHandler, targetOverride) {
|
||||
if(elt == null) {
|
||||
elt = getDocument().body;
|
||||
}
|
||||
if (responseHandler == null) {
|
||||
responseHandler = handleAjaxResponse;
|
||||
}
|
||||
if (!bodyContains(elt)) {
|
||||
console.log("Body does not contain", elt);
|
||||
return; // do not issue requests for elements removed from the DOM
|
||||
}
|
||||
var target = getTarget(elt);
|
||||
var target = targetOverride || getTarget(elt);
|
||||
if (target == null) {
|
||||
triggerErrorEvent(elt, 'htmx:targetError', {target: getAttributeValue(elt, "hx-target")});
|
||||
return;
|
||||
}
|
||||
var eltData = getInternalData(elt);
|
||||
if (eltData.requestInFlight) {
|
||||
eltData.queuedRequest = function(){issueAjaxRequest(elt, verb, path, eventTarget, triggeringEvent)};
|
||||
eltData.queuedRequest = function(){
|
||||
issueAjaxRequest(verb, path, elt, event)
|
||||
};
|
||||
return;
|
||||
} else {
|
||||
eltData.requestInFlight = true;
|
||||
@ -1858,7 +1888,7 @@ return (function () {
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
var headers = getHeaders(elt, target, promptResponse, eventTarget);
|
||||
var headers = getHeaders(elt, target, promptResponse);
|
||||
var results = getInputValues(elt, verb);
|
||||
var errors = results.errors;
|
||||
var rawParameters = results.values;
|
||||
@ -1883,7 +1913,7 @@ return (function () {
|
||||
verb:verb,
|
||||
errors:errors,
|
||||
path:path,
|
||||
triggeringEvent:triggeringEvent
|
||||
triggeringEvent:event
|
||||
};
|
||||
|
||||
if(!triggerEvent(elt, 'htmx:configRequest', requestConfig)) return endRequestLock();
|
||||
@ -1931,159 +1961,35 @@ return (function () {
|
||||
}
|
||||
}
|
||||
|
||||
var eventDetail = {xhr: xhr, target: target, requestConfig: requestConfig};
|
||||
var responseInfo = {xhr: xhr, target: target, requestConfig: requestConfig, pathInfo:{
|
||||
path:path, finalPath:finalPathForGet, anchor:anchor
|
||||
}
|
||||
};
|
||||
xhr.onload = function () {
|
||||
try {
|
||||
if (!triggerEvent(elt, 'htmx:beforeOnLoad', eventDetail)) return;
|
||||
|
||||
if (hasHeader(xhr, /HX-Trigger:/i)) {
|
||||
handleTrigger(xhr, "HX-Trigger", elt);
|
||||
}
|
||||
|
||||
if (hasHeader(xhr,/HX-Push:/i)) {
|
||||
var pushedUrl = xhr.getResponseHeader("HX-Push");
|
||||
}
|
||||
|
||||
if (hasHeader(xhr, /HX-Redirect:/i)) {
|
||||
window.location.href = xhr.getResponseHeader("HX-Redirect");
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasHeader(xhr,/HX-Refresh:/i)) {
|
||||
if ("true" === xhr.getResponseHeader("HX-Refresh")) {
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var shouldSaveHistory = shouldPush(elt) || pushedUrl;
|
||||
|
||||
if (this.status >= 200 && this.status < 400) {
|
||||
if (this.status === 286) {
|
||||
cancelPolling(elt);
|
||||
}
|
||||
// don't process 'No Content'
|
||||
if (this.status !== 204) {
|
||||
if (!triggerEvent(target, 'htmx:beforeSwap', eventDetail)) return;
|
||||
|
||||
var serverResponse = this.response;
|
||||
withExtensions(elt, function(extension){
|
||||
serverResponse = extension.transformResponse(serverResponse, xhr, elt);
|
||||
});
|
||||
|
||||
// Save current page
|
||||
if (shouldSaveHistory) {
|
||||
saveHistory();
|
||||
}
|
||||
|
||||
var swapSpec = getSwapSpecification(elt);
|
||||
|
||||
target.classList.add(htmx.config.swappingClass);
|
||||
var doSwap = function () {
|
||||
try {
|
||||
|
||||
var activeElt = document.activeElement;
|
||||
var selectionInfo = {
|
||||
elt: activeElt,
|
||||
start: activeElt ? activeElt.selectionStart : null,
|
||||
end: activeElt ? activeElt.selectionEnd : null
|
||||
};
|
||||
|
||||
var settleInfo = makeSettleInfo(target);
|
||||
selectAndSwap(swapSpec.swapStyle, target, elt, serverResponse, settleInfo);
|
||||
|
||||
if (selectionInfo.elt &&
|
||||
!bodyContains(selectionInfo.elt) &&
|
||||
selectionInfo.elt.id) {
|
||||
var newActiveElt = document.getElementById(selectionInfo.elt.id);
|
||||
if (newActiveElt) {
|
||||
if (selectionInfo.start && newActiveElt.setSelectionRange) {
|
||||
newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end);
|
||||
}
|
||||
newActiveElt.focus();
|
||||
}
|
||||
}
|
||||
|
||||
target.classList.remove(htmx.config.swappingClass);
|
||||
forEach(settleInfo.elts, function (elt) {
|
||||
if (elt.classList) {
|
||||
elt.classList.add(htmx.config.settlingClass);
|
||||
}
|
||||
triggerEvent(elt, 'htmx:afterSwap', eventDetail);
|
||||
});
|
||||
if (anchor) {
|
||||
location.hash = anchor;
|
||||
}
|
||||
|
||||
if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) {
|
||||
handleTrigger(xhr, "HX-Trigger-After-Swap", elt);
|
||||
}
|
||||
|
||||
var doSettle = function(){
|
||||
forEach(settleInfo.tasks, function (task) {
|
||||
task.call();
|
||||
});
|
||||
forEach(settleInfo.elts, function (elt) {
|
||||
if (elt.classList) {
|
||||
elt.classList.remove(htmx.config.settlingClass);
|
||||
}
|
||||
triggerEvent(elt, 'htmx:afterSettle', eventDetail);
|
||||
});
|
||||
// push URL and save new page
|
||||
if (shouldSaveHistory) {
|
||||
var pathToPush = pushedUrl || getPushUrl(elt) || getResponseURL(xhr) || finalPathForGet || path;
|
||||
pushUrlIntoHistory(pathToPush);
|
||||
triggerEvent(getDocument().body, 'htmx:pushedIntoHistory', {path:pathToPush});
|
||||
}
|
||||
updateScrollState(target, settleInfo.elts, swapSpec);
|
||||
|
||||
if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) {
|
||||
handleTrigger(xhr, "HX-Trigger-After-Settle", elt);
|
||||
}
|
||||
}
|
||||
|
||||
if (swapSpec.settleDelay > 0) {
|
||||
setTimeout(doSettle, swapSpec.settleDelay)
|
||||
} else {
|
||||
doSettle();
|
||||
}
|
||||
} catch (e) {
|
||||
triggerErrorEvent(elt, 'htmx:swapError', eventDetail);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
if (swapSpec.swapDelay > 0) {
|
||||
setTimeout(doSwap, swapSpec.swapDelay)
|
||||
} else {
|
||||
doSwap();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
triggerErrorEvent(elt, 'htmx:responseError', mergeObjects({error: "Response Status Error Code " + this.status + " from " + path}, eventDetail));
|
||||
}
|
||||
responseHandler(elt, responseInfo);
|
||||
} catch (e) {
|
||||
triggerErrorEvent(elt, 'htmx:onLoadError', mergeObjects({error:e}, eventDetail));
|
||||
triggerErrorEvent(elt, 'htmx:onLoadError', mergeObjects({error:e}, responseInfo));
|
||||
throw e;
|
||||
} finally {
|
||||
removeRequestIndicatorClasses(elt);
|
||||
var finalElt = getInternalData(elt).replacedWith || elt;
|
||||
triggerEvent(finalElt, 'htmx:afterRequest', eventDetail);
|
||||
triggerEvent(finalElt, 'htmx:afterOnLoad', eventDetail);
|
||||
triggerEvent(finalElt, 'htmx:afterRequest', responseInfo);
|
||||
triggerEvent(finalElt, 'htmx:afterOnLoad', responseInfo);
|
||||
endRequestLock();
|
||||
}
|
||||
}
|
||||
xhr.onerror = function () {
|
||||
removeRequestIndicatorClasses(elt);
|
||||
triggerErrorEvent(elt, 'htmx:afterRequest', eventDetail);
|
||||
triggerErrorEvent(elt, 'htmx:sendError', eventDetail);
|
||||
triggerErrorEvent(elt, 'htmx:afterRequest', responseInfo);
|
||||
triggerErrorEvent(elt, 'htmx:sendError', responseInfo);
|
||||
endRequestLock();
|
||||
}
|
||||
xhr.onabort = function() {
|
||||
removeRequestIndicatorClasses(elt);
|
||||
endRequestLock();
|
||||
}
|
||||
if(!triggerEvent(elt, 'htmx:beforeRequest', eventDetail)) return endRequestLock();
|
||||
if(!triggerEvent(elt, 'htmx:beforeRequest', responseInfo)) return endRequestLock();
|
||||
addRequestIndicatorClasses(elt);
|
||||
|
||||
forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
|
||||
@ -2100,6 +2006,140 @@ return (function () {
|
||||
xhr.send(verb === 'get' ? null : encodeParamsForBody(xhr, elt, filteredParameters));
|
||||
}
|
||||
|
||||
function handleAjaxResponse(elt, responseInfo) {
|
||||
var xhr = responseInfo.xhr;
|
||||
var target = responseInfo.target;
|
||||
|
||||
if (!triggerEvent(elt, 'htmx:beforeOnLoad', responseInfo)) return;
|
||||
|
||||
if (hasHeader(xhr, /HX-Trigger:/i)) {
|
||||
handleTrigger(xhr, "HX-Trigger", elt);
|
||||
}
|
||||
|
||||
if (hasHeader(xhr,/HX-Push:/i)) {
|
||||
var pushedUrl = xhr.getResponseHeader("HX-Push");
|
||||
}
|
||||
|
||||
if (hasHeader(xhr, /HX-Redirect:/i)) {
|
||||
window.location.href = xhr.getResponseHeader("HX-Redirect");
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasHeader(xhr,/HX-Refresh:/i)) {
|
||||
if ("true" === xhr.getResponseHeader("HX-Refresh")) {
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var shouldSaveHistory = shouldPush(elt) || pushedUrl;
|
||||
|
||||
if (xhr.status >= 200 && xhr.status < 400) {
|
||||
if (xhr.status === 286) {
|
||||
cancelPolling(elt);
|
||||
}
|
||||
// don't process 'No Content'
|
||||
if (xhr.status !== 204) {
|
||||
if (!triggerEvent(target, 'htmx:beforeSwap', responseInfo)) return;
|
||||
|
||||
var serverResponse = xhr.response;
|
||||
withExtensions(elt, function(extension){
|
||||
serverResponse = extension.transformResponse(serverResponse, xhr, elt);
|
||||
});
|
||||
|
||||
// Save current page
|
||||
if (shouldSaveHistory) {
|
||||
saveHistory();
|
||||
}
|
||||
|
||||
var swapSpec = getSwapSpecification(elt);
|
||||
|
||||
target.classList.add(htmx.config.swappingClass);
|
||||
var doSwap = function () {
|
||||
try {
|
||||
|
||||
var activeElt = document.activeElement;
|
||||
var selectionInfo = {
|
||||
elt: activeElt,
|
||||
start: activeElt ? activeElt.selectionStart : null,
|
||||
end: activeElt ? activeElt.selectionEnd : null
|
||||
};
|
||||
|
||||
var settleInfo = makeSettleInfo(target);
|
||||
selectAndSwap(swapSpec.swapStyle, target, elt, serverResponse, settleInfo);
|
||||
|
||||
if (selectionInfo.elt &&
|
||||
!bodyContains(selectionInfo.elt) &&
|
||||
selectionInfo.elt.id) {
|
||||
var newActiveElt = document.getElementById(selectionInfo.elt.id);
|
||||
if (newActiveElt) {
|
||||
if (selectionInfo.start && newActiveElt.setSelectionRange) {
|
||||
newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end);
|
||||
}
|
||||
newActiveElt.focus();
|
||||
}
|
||||
}
|
||||
|
||||
target.classList.remove(htmx.config.swappingClass);
|
||||
forEach(settleInfo.elts, function (elt) {
|
||||
if (elt.classList) {
|
||||
elt.classList.add(htmx.config.settlingClass);
|
||||
}
|
||||
triggerEvent(elt, 'htmx:afterSwap', responseInfo);
|
||||
});
|
||||
if (responseInfo.pathInfo.anchor) {
|
||||
location.hash = responseInfo.pathInfo.anchor;
|
||||
}
|
||||
|
||||
if (hasHeader(xhr, /HX-Trigger-After-Swap:/i)) {
|
||||
handleTrigger(xhr, "HX-Trigger-After-Swap", elt);
|
||||
}
|
||||
|
||||
var doSettle = function(){
|
||||
forEach(settleInfo.tasks, function (task) {
|
||||
task.call();
|
||||
});
|
||||
forEach(settleInfo.elts, function (elt) {
|
||||
if (elt.classList) {
|
||||
elt.classList.remove(htmx.config.settlingClass);
|
||||
}
|
||||
triggerEvent(elt, 'htmx:afterSettle', responseInfo);
|
||||
});
|
||||
// push URL and save new page
|
||||
if (shouldSaveHistory) {
|
||||
var pathToPush = pushedUrl || getPushUrl(elt) || getResponseURL(xhr) || responseInfo.pathInfo.finalPath || responseInfo.pathInfo.path;
|
||||
pushUrlIntoHistory(pathToPush);
|
||||
triggerEvent(getDocument().body, 'htmx:pushedIntoHistory', {path:pathToPush});
|
||||
}
|
||||
updateScrollState(target, settleInfo.elts, swapSpec);
|
||||
|
||||
if (hasHeader(xhr, /HX-Trigger-After-Settle:/i)) {
|
||||
handleTrigger(xhr, "HX-Trigger-After-Settle", elt);
|
||||
}
|
||||
}
|
||||
|
||||
if (swapSpec.settleDelay > 0) {
|
||||
setTimeout(doSettle, swapSpec.settleDelay)
|
||||
} else {
|
||||
doSettle();
|
||||
}
|
||||
} catch (e) {
|
||||
triggerErrorEvent(elt, 'htmx:swapError', responseInfo);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
if (swapSpec.swapDelay > 0) {
|
||||
setTimeout(doSwap, swapSpec.swapDelay)
|
||||
} else {
|
||||
doSwap();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
triggerErrorEvent(elt, 'htmx:responseError', mergeObjects({error: "Response Status Error Code " + xhr.status + " from " + responseInfo.pathInfo.path}, responseInfo));
|
||||
}
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
// Extensions API
|
||||
//====================================================================
|
||||
|
@ -34,6 +34,22 @@ describe("hx-include attribute", function() {
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('non-GET includes closest form and overrides values included that exist outside the form', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var div = make('<div hx-include="*" hx-target="this">' +
|
||||
'<input name="i1" value="before"/>' +
|
||||
'<form><div id="d1" hx-post="/include"></div><input name="i1" value="test"/></form>' +
|
||||
'<input name="i1" value="after"/>')
|
||||
var input = byId("d1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('GET does not include closest form by default', function () {
|
||||
this.server.respondWith("GET", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
@ -150,4 +166,4 @@ describe("hx-include attribute", function() {
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
21
test/attributes/hx-preserve.js
Normal file
21
test/attributes/hx-preserve.js
Normal file
@ -0,0 +1,21 @@
|
||||
describe("hx-preserve attribute", function () {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('handles basic response properly', function () {
|
||||
this.server.respondWith("GET", "/test", "<div id='d1' hx-preserve>New Content</div><div id='d2'>New Content</div>");
|
||||
var div = make("<div hx-get='/test'><div id='d1' hx-preserve>Old Content</div><div id='d2'>Old Content</div></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("Old Content");
|
||||
byId("d2").innerHTML.should.equal("New Content");
|
||||
})
|
||||
|
||||
});
|
||||
|
@ -26,6 +26,35 @@ describe("Core htmx client side validation tests", function(){
|
||||
form.textContent.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('Novalidate skips form validation', function()
|
||||
{
|
||||
this.server.respondWith("POST", "/test", "Clicked!");
|
||||
|
||||
var form = make('<form hx-post="/test" hx-trigger="click" novalidate>' +
|
||||
'No Request' +
|
||||
'<input id="i1" name="i1" required>' +
|
||||
'</form>');
|
||||
form.textContent.should.equal("No Request");
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.textContent.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('Validation skipped for indirect form submission', function()
|
||||
{
|
||||
this.server.respondWith("POST", "/test", "Clicked!");
|
||||
|
||||
var form = make('<form hx-post="/test" hx-trigger="click">' +
|
||||
'No Request' +
|
||||
'<input id="i1" name="i1" required>' +
|
||||
'<button id="button" hx-post="/test" hx-target="form"></button>' +
|
||||
'</form>');
|
||||
form.textContent.should.equal("No Request");
|
||||
byId("button").click();
|
||||
this.server.respond();
|
||||
form.textContent.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('HTML5 pattern validation error prevents request', function()
|
||||
{
|
||||
this.server.respondWith("POST", "/test", "Clicked!");
|
||||
|
@ -92,6 +92,7 @@
|
||||
<script src="attributes/hx-params.js"></script>
|
||||
<script src="attributes/hx-patch.js"></script>
|
||||
<script src="attributes/hx-post.js"></script>
|
||||
<script src="attributes/hx-preserve.js"></script>
|
||||
<script src="attributes/hx-push-url.js"></script>
|
||||
<script src="attributes/hx-put.js"></script>
|
||||
<script src="attributes/hx-select.js"></script>
|
||||
|
@ -557,12 +557,7 @@ htmx includes a number of useful headers in requests:
|
||||
| `HX-Trigger` | will be set to the id of the element that triggered the request
|
||||
| `HX-Trigger-Name` | will be set to the name of the element that triggered the request
|
||||
| `HX-Target` | will be set to the id of the target element
|
||||
| `HX-Current-URL` | will be set to the URL of the browser
|
||||
| `HX-Prompt` | will be set to the value entered by the user when prompted via [hx-prompt](/attributes/hx-prompt)
|
||||
| `HX-Event-Target` | the id of the original target of the event that triggered the request
|
||||
| `HX-Active-Element` | the id of the current active element
|
||||
| `HX-Active-Element-Name` | the name of the current active element
|
||||
| `HX-Active-Element-Value` | the value of the current active element
|
||||
|
||||
### <a name="response-header"></a> [Response Headers](#response-headers)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user