mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-10-03 07:45:21 +00:00
Formalize settle concept
This commit is contained in:
parent
eb9ea0cbca
commit
ab1668d68b
1
TODO.md
1
TODO.md
@ -17,6 +17,7 @@
|
|||||||
* polling cancellation API 205 code
|
* polling cancellation API 205 code
|
||||||
* meta config tag
|
* meta config tag
|
||||||
* simple logging API
|
* simple logging API
|
||||||
|
* hx-toggle-class
|
||||||
|
|
||||||
* Testing
|
* Testing
|
||||||
* polling
|
* polling
|
||||||
|
247
dist/htmx.js
vendored
247
dist/htmx.js
vendored
@ -117,6 +117,10 @@ var HTMx = HTMx || (function () {
|
|||||||
return getDocument().body.contains(elt);
|
return getDocument().body.contains(elt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function concat(arr1, arr2) {
|
||||||
|
return arr1.concat(arr2);
|
||||||
|
}
|
||||||
|
|
||||||
//====================================================================
|
//====================================================================
|
||||||
// Node processing
|
// Node processing
|
||||||
//====================================================================
|
//====================================================================
|
||||||
@ -152,19 +156,21 @@ var HTMx = HTMx || (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleOutOfBandSwaps(fragment) {
|
function handleOutOfBandSwaps(fragment) {
|
||||||
|
var settleTasks = [];
|
||||||
forEach(fragment.children, function(child){
|
forEach(fragment.children, function(child){
|
||||||
if (getAttributeValue(child, "hx-swap-oob") === "true") {
|
if (getAttributeValue(child, "hx-swap-oob") === "true") {
|
||||||
var target = getDocument().getElementById(child.id);
|
var target = getDocument().getElementById(child.id);
|
||||||
if (target) {
|
if (target) {
|
||||||
var fragment = new DocumentFragment()
|
var fragment = new DocumentFragment()
|
||||||
fragment.append(child);
|
fragment.append(child);
|
||||||
swapOuterHTML(target, fragment);
|
settleTasks = settleTasks.concat(swapOuterHTML(target, fragment));
|
||||||
} else {
|
} else {
|
||||||
child.parentNode.removeChild(child);
|
child.parentNode.removeChild(child);
|
||||||
triggerEvent(getDocument().body, "oobErrorNoTarget.hx", {id:child.id, content:child})
|
triggerEvent(getDocument().body, "oobErrorNoTarget.hx", {id:child.id, content:child})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
return settleTasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAttributes(parentNode, fragment) {
|
function handleAttributes(parentNode, fragment) {
|
||||||
@ -179,11 +185,7 @@ var HTMx = HTMx || (function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setTimeout(function () {
|
return attributeSwaps;
|
||||||
forEach(attributeSwaps, function (swap) {
|
|
||||||
swap.call();
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertNodesBefore(parentNode, insertBefore, fragment) {
|
function insertNodesBefore(parentNode, insertBefore, fragment) {
|
||||||
@ -200,32 +202,37 @@ var HTMx = HTMx || (function () {
|
|||||||
|
|
||||||
function swapOuterHTML(target, fragment) {
|
function swapOuterHTML(target, fragment) {
|
||||||
if (target.tagName === "BODY") {
|
if (target.tagName === "BODY") {
|
||||||
swapInnerHTML(target, fragment);
|
return swapInnerHTML(target, fragment);
|
||||||
} else {
|
} else {
|
||||||
insertNodesBefore(parentElt(target), target, fragment);
|
var settleTasks = insertNodesBefore(parentElt(target), target, fragment);
|
||||||
parentElt(target).removeChild(target);
|
parentElt(target).removeChild(target);
|
||||||
|
return settleTasks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapPrepend(target, fragment) {
|
function swapPrepend(target, fragment) {
|
||||||
insertNodesBefore(target, target.firstChild, fragment);
|
return insertNodesBefore(target, target.firstChild, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapPrependBefore(target, fragment) {
|
function swapPrependBefore(target, fragment) {
|
||||||
insertNodesBefore(parentElt(target), target, fragment);
|
return insertNodesBefore(parentElt(target), target, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapAppend(target, fragment) {
|
function swapAppend(target, fragment) {
|
||||||
insertNodesBefore(target, null, fragment);
|
return insertNodesBefore(target, null, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapAppendAfter(target, fragment) {
|
function swapAppendAfter(target, fragment) {
|
||||||
insertNodesBefore(parentElt(target), target.nextSibling, fragment);
|
return insertNodesBefore(parentElt(target), target.nextSibling, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapInnerHTML(target, fragment) {
|
function swapInnerHTML(target, fragment) {
|
||||||
target.innerHTML = "";
|
var firstChild = target.firstChild;
|
||||||
insertNodesBefore(target, null, fragment);
|
return insertNodesBefore(target, firstChild, fragment);
|
||||||
|
while (firstChild.nextSibling) {
|
||||||
|
target.removeChild(firstChild.nextSibling);
|
||||||
|
}
|
||||||
|
target.removeChild(firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeSelectFromResponse(elt, fragment) {
|
function maybeSelectFromResponse(elt, fragment) {
|
||||||
@ -240,30 +247,20 @@ var HTMx = HTMx || (function () {
|
|||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapResponse(target, elt, responseText, callBack) {
|
function swapResponse(target, elt, responseText) {
|
||||||
|
|
||||||
var fragment = makeFragment(responseText);
|
var fragment = makeFragment(responseText);
|
||||||
handleOutOfBandSwaps(fragment);
|
var settleTasks = handleOutOfBandSwaps(fragment);
|
||||||
|
|
||||||
fragment = maybeSelectFromResponse(elt, fragment);
|
fragment = maybeSelectFromResponse(elt, fragment);
|
||||||
|
|
||||||
var swapStyle = getClosestAttributeValue(elt, "hx-swap");
|
var swapStyle = getClosestAttributeValue(elt, "hx-swap");
|
||||||
if (swapStyle === "outerHTML") {
|
switch(swapStyle) {
|
||||||
swapOuterHTML(target, fragment);
|
case "outerHTML": return concat(settleTasks, swapOuterHTML(target, fragment));
|
||||||
} else if (swapStyle === "prepend") {
|
case "prepend": return concat(settleTasks, swapPrepend(target, fragment));
|
||||||
swapPrepend(target, fragment);
|
case "prependBefore": return concat(settleTasks, swapPrependBefore(target, fragment));
|
||||||
} else if (swapStyle === "prependBefore") {
|
case "append": return concat(settleTasks, swapAppend(target, fragment));
|
||||||
swapPrependBefore(target, fragment);
|
case "appendAfter": return concat(settleTasks, swapAppendAfter(target, fragment));
|
||||||
} else if (swapStyle === "append") {
|
default: return concat(settleTasks, swapInnerHTML(target, fragment));
|
||||||
swapAppend(target, fragment);
|
|
||||||
} else if (swapStyle === "appendAfter") {
|
|
||||||
swapAppendAfter(target, fragment);
|
|
||||||
} else {
|
|
||||||
swapInnerHTML(target, fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(callBack) {
|
|
||||||
callBack.call();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,62 +580,78 @@ var HTMx = HTMx || (function () {
|
|||||||
//====================================================================
|
//====================================================================
|
||||||
// History Support
|
// History Support
|
||||||
//====================================================================
|
//====================================================================
|
||||||
|
|
||||||
function makeHistoryId() {
|
|
||||||
return Math.random().toString(36).substr(3, 9);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHistoryElement() {
|
function getHistoryElement() {
|
||||||
var historyElt = getDocument().getElementsByClassName('hx-history-element');
|
var historyElt = getDocument().querySelector('.hx-history-element');
|
||||||
if (historyElt.length > 0) {
|
return historyElt || getDocument().body;
|
||||||
return historyElt[0];
|
}
|
||||||
} else {
|
|
||||||
return getDocument().body;
|
function purgeOldestPaths(paths, historyTimestamps) {
|
||||||
|
var paths = paths.sort(function (path1, path2) {
|
||||||
|
return historyTimestamps[path2] - historyTimestamps[path1]
|
||||||
|
});
|
||||||
|
var slot = 0;
|
||||||
|
forEach(paths, function (path) {
|
||||||
|
slot++;
|
||||||
|
if (slot > 20) {
|
||||||
|
delete historyTimestamps[path];
|
||||||
|
localStorage.removeItem(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bumpHistoryAccessDate(pathAndSearch) {
|
||||||
|
var historyTimestamps = JSON.parse(localStorage.getItem("hx-history-timestamps")) || {};
|
||||||
|
historyTimestamps[pathAndSearch] = Date.now;
|
||||||
|
var paths = Object.keys(historyTimestamps);
|
||||||
|
if (paths.length > 20) {
|
||||||
|
purgeOldestPaths(paths, historyTimestamps);
|
||||||
}
|
}
|
||||||
|
localStorage.setItem("hx-history-timestamps", JSON.stringify(historyTimestamps));
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveLocalHistoryData(historyData) {
|
function saveHistory() {
|
||||||
localStorage.setItem('hx-history', JSON.stringify(historyData));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLocalHistoryData() {
|
|
||||||
var historyEntry = localStorage.getItem('hx-history');
|
|
||||||
var historyData;
|
|
||||||
if (historyEntry) {
|
|
||||||
historyData = JSON.parse(historyEntry);
|
|
||||||
} else {
|
|
||||||
var initialId = makeHistoryId();
|
|
||||||
historyData = {"current": initialId, "slots": [initialId]};
|
|
||||||
saveLocalHistoryData(historyData);
|
|
||||||
}
|
|
||||||
return historyData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function newHistoryData() {
|
|
||||||
var historyData = getLocalHistoryData();
|
|
||||||
var newId = makeHistoryId();
|
|
||||||
var slots = historyData.slots;
|
|
||||||
if (slots.length > 20) {
|
|
||||||
var toEvict = slots.shift();
|
|
||||||
localStorage.removeItem('hx-history-' + toEvict);
|
|
||||||
}
|
|
||||||
slots.push(newId);
|
|
||||||
historyData.current = newId;
|
|
||||||
saveLocalHistoryData(historyData);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCurrentHistoryContent() {
|
|
||||||
var elt = getHistoryElement();
|
var elt = getHistoryElement();
|
||||||
var historyData = getLocalHistoryData();
|
var pathAndSearch = location.pathname+location.search;
|
||||||
history.replaceState({"hx-history-key": historyData.current}, getDocument().title, window.location.href);
|
triggerEvent(getDocument().body, "historyUpdate.hx", {path:pathAndSearch, historyElement:elt});
|
||||||
localStorage.setItem('hx-history-' + historyData.current, elt.innerHTML);
|
history.replaceState({}, getDocument().title, window.location.href);
|
||||||
|
localStorage.setItem('hx-history-content-' + pathAndSearch, elt.innerHTML);
|
||||||
|
bumpHistoryAccessDate(pathAndSearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreHistory(data) {
|
function pushUrlIntoHistory(url) {
|
||||||
var historyKey = data['hx-history-key'];
|
history.pushState({}, "", url );
|
||||||
var content = localStorage.getItem('hx-history-' + historyKey);
|
}
|
||||||
var elt = getHistoryElement();
|
|
||||||
swapInnerHTML(elt, makeFragment(content));
|
function settleImmediately(settleTasks) {
|
||||||
|
forEach(settleTasks, function (task) {
|
||||||
|
task.call();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadHistoryFromServer(pathAndSearch) {
|
||||||
|
triggerEvent(getDocument().body, "historyCacheMiss.hx", {path: pathAndSearch});
|
||||||
|
var request = new XMLHttpRequest();
|
||||||
|
request.open('GET', pathAndSearch, true);
|
||||||
|
request.onload = function () {
|
||||||
|
triggerEvent(getDocument().body, "historyCacheMissLoad.hx", {path: pathAndSearch});
|
||||||
|
if (this.status >= 200 && this.status < 400) {
|
||||||
|
var fragment = makeFragment(this.response);
|
||||||
|
fragment = fragment.querySelector('.hx-history-element') || fragment;
|
||||||
|
settleImmediately(swapInnerHTML(getHistoryElement(), fragment));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreHistory() {
|
||||||
|
var pathAndSearch = location.pathname+location.search;
|
||||||
|
triggerEvent(getDocument().body, "historyUpdate.hx", {path:pathAndSearch});
|
||||||
|
var content = localStorage.getItem('hx-history-content-' + pathAndSearch);
|
||||||
|
if (content) {
|
||||||
|
bumpHistoryAccessDate(pathAndSearch);
|
||||||
|
settleImmediately(swapInnerHTML(getHistoryElement(), makeFragment(content)));
|
||||||
|
} else {
|
||||||
|
loadHistoryFromServer(pathAndSearch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldPush(elt) {
|
function shouldPush(elt) {
|
||||||
@ -646,21 +659,6 @@ var HTMx = HTMx || (function () {
|
|||||||
(elt.tagName === "A" && getInternalData(elt).boosted);
|
(elt.tagName === "A" && getInternalData(elt).boosted);
|
||||||
}
|
}
|
||||||
|
|
||||||
function snapshotForCurrentHistoryEntry(elt) {
|
|
||||||
if (shouldPush(elt)) {
|
|
||||||
// TODO event to allow de-initialization of HTML elements in target
|
|
||||||
updateCurrentHistoryContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initNewHistoryEntry(elt, url) {
|
|
||||||
if (shouldPush(elt)) {
|
|
||||||
newHistoryData();
|
|
||||||
history.pushState({}, "", url);
|
|
||||||
updateCurrentHistoryContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addRequestIndicatorClasses(elt) {
|
function addRequestIndicatorClasses(elt) {
|
||||||
mutateRequestIndicatorClasses(elt, "add");
|
mutateRequestIndicatorClasses(elt, "add");
|
||||||
}
|
}
|
||||||
@ -804,11 +802,14 @@ var HTMx = HTMx || (function () {
|
|||||||
if(!triggerEvent(elt, 'values.hx', {values: inputValues, target:target})) return endRequestLock();
|
if(!triggerEvent(elt, 'values.hx', {values: inputValues, target:target})) return endRequestLock();
|
||||||
|
|
||||||
// request type
|
// request type
|
||||||
|
var requestURL;
|
||||||
if (verb === 'get') {
|
if (verb === 'get') {
|
||||||
var noValues = Object.keys(inputValues).length === 0;
|
var noValues = Object.keys(inputValues).length === 0;
|
||||||
xhr.open('GET', path + (noValues ? "" : "?" + urlEncode(inputValues)), true);
|
requestURL = path + (noValues ? "" : "?" + urlEncode(inputValues));
|
||||||
|
xhr.open('GET', requestURL, true);
|
||||||
} else {
|
} else {
|
||||||
xhr.open('POST', path, true);
|
requestURL = path;
|
||||||
|
xhr.open('POST', requestURL, true);
|
||||||
setHeader(xhr,'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8', true);
|
setHeader(xhr,'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8', true);
|
||||||
if (verb !== 'post') {
|
if (verb !== 'post') {
|
||||||
setHeader(xhr, 'X-HTTP-Method-Override', verb.toUpperCase(), true);
|
setHeader(xhr, 'X-HTTP-Method-Override', verb.toUpperCase(), true);
|
||||||
@ -841,30 +842,56 @@ var HTMx = HTMx || (function () {
|
|||||||
xhr.onload = function () {
|
xhr.onload = function () {
|
||||||
try {
|
try {
|
||||||
if (!triggerEvent(elt, 'beforeOnLoad.hx', {xhr: xhr, target: target})) return;
|
if (!triggerEvent(elt, 'beforeOnLoad.hx', {xhr: xhr, target: target})) return;
|
||||||
snapshotForCurrentHistoryEntry(elt, path);
|
|
||||||
var trigger = this.getResponseHeader("X-HX-Trigger");
|
handleTrigger(elt, this.getResponseHeader("X-HX-Trigger"));
|
||||||
handleTrigger(elt, trigger);
|
var pushedUrl = this.getResponseHeader("X-HX-Push")
|
||||||
initNewHistoryEntry(elt, path);
|
|
||||||
|
var shouldSaveHistory = shouldPush(elt) || pushedUrl;
|
||||||
|
|
||||||
if (this.status >= 200 && this.status < 400) {
|
if (this.status >= 200 && this.status < 400) {
|
||||||
// don't process 'No Content' response
|
// don't process 'No Content' response
|
||||||
if (this.status !== 204) {
|
if (this.status !== 204) {
|
||||||
// Success!
|
// Success!
|
||||||
var resp = this.response;
|
var resp = this.response;
|
||||||
if (!triggerEvent(elt, 'beforeSwap.hx', {xhr: xhr, target: target})) return;
|
if (!triggerEvent(elt, 'beforeSwap.hx', {xhr: xhr, target: target})) return;
|
||||||
|
|
||||||
|
// Save current page
|
||||||
|
if (shouldSaveHistory) {
|
||||||
|
saveHistory();
|
||||||
|
}
|
||||||
|
|
||||||
target.classList.add("hx-swapping");
|
target.classList.add("hx-swapping");
|
||||||
var doSwap = function () {
|
var doSwap = function () {
|
||||||
try {
|
try {
|
||||||
swapResponse(target, elt, resp, function () {
|
var settleTasks = swapResponse(target, elt, resp);
|
||||||
target.classList.remove("hx-swapping");
|
target.classList.remove("hx-swapping");
|
||||||
updateCurrentHistoryContent();
|
target.classList.add("hx-settling");
|
||||||
triggerEvent(elt, 'afterSwap.hx', {xhr: xhr, target: target});
|
triggerEvent(elt, 'afterSwap.hx', {xhr: xhr, target: target});
|
||||||
});
|
|
||||||
|
var doSettle = function(){
|
||||||
|
forEach(settleTasks, function (task) {
|
||||||
|
task.call();
|
||||||
|
});
|
||||||
|
target.classList.remove("hx-settling");
|
||||||
|
// push URL and save new page
|
||||||
|
pushUrlIntoHistory(pushedUrl || requestURL );
|
||||||
|
saveHistory();
|
||||||
|
triggerEvent(elt, 'afterSettle.hx', {xhr: xhr, target: target});
|
||||||
|
}
|
||||||
|
|
||||||
|
var settleDelayStr = getAttributeValue(elt, "hx-settle-delay") || "100ms";
|
||||||
|
if (settleDelayStr) {
|
||||||
|
setTimeout(doSettle, parseInterval(settleDelayStr))
|
||||||
|
} else {
|
||||||
|
doSettle();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
triggerEvent(elt, 'swapError.hx', {xhr: xhr, response: xhr.response, status: xhr.status, target: target});
|
triggerEvent(elt, 'swapError.hx', {xhr: xhr, response: xhr.response, status: xhr.status, target: target});
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var swapDelayStr = getAttributeValue(elt, "hx-swap-delay");
|
|
||||||
|
var swapDelayStr = getAttributeValue(elt, "hx-swap-delay") || "100ms";
|
||||||
if (swapDelayStr) {
|
if (swapDelayStr) {
|
||||||
setTimeout(doSwap, parseInterval(swapDelayStr))
|
setTimeout(doSwap, parseInterval(swapDelayStr))
|
||||||
} else {
|
} else {
|
||||||
@ -908,7 +935,7 @@ var HTMx = HTMx || (function () {
|
|||||||
ready(function () {
|
ready(function () {
|
||||||
processNode(getDocument().body);
|
processNode(getDocument().body);
|
||||||
window.onpopstate = function (event) {
|
window.onpopstate = function (event) {
|
||||||
restoreHistory(event.state);
|
restoreHistory();
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|
||||||
|
2
dist/htmx.min.js
vendored
2
dist/htmx.min.js
vendored
File diff suppressed because one or more lines are too long
BIN
dist/htmx.min.js.gz
vendored
BIN
dist/htmx.min.js.gz
vendored
Binary file not shown.
159
src/htmx.js
159
src/htmx.js
@ -117,6 +117,10 @@ var HTMx = HTMx || (function () {
|
|||||||
return getDocument().body.contains(elt);
|
return getDocument().body.contains(elt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function concat(arr1, arr2) {
|
||||||
|
return arr1.concat(arr2);
|
||||||
|
}
|
||||||
|
|
||||||
//====================================================================
|
//====================================================================
|
||||||
// Node processing
|
// Node processing
|
||||||
//====================================================================
|
//====================================================================
|
||||||
@ -152,19 +156,21 @@ var HTMx = HTMx || (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleOutOfBandSwaps(fragment) {
|
function handleOutOfBandSwaps(fragment) {
|
||||||
|
var settleTasks = [];
|
||||||
forEach(fragment.children, function(child){
|
forEach(fragment.children, function(child){
|
||||||
if (getAttributeValue(child, "hx-swap-oob") === "true") {
|
if (getAttributeValue(child, "hx-swap-oob") === "true") {
|
||||||
var target = getDocument().getElementById(child.id);
|
var target = getDocument().getElementById(child.id);
|
||||||
if (target) {
|
if (target) {
|
||||||
var fragment = new DocumentFragment()
|
var fragment = new DocumentFragment()
|
||||||
fragment.append(child);
|
fragment.append(child);
|
||||||
swapOuterHTML(target, fragment);
|
settleTasks = settleTasks.concat(swapOuterHTML(target, fragment));
|
||||||
} else {
|
} else {
|
||||||
child.parentNode.removeChild(child);
|
child.parentNode.removeChild(child);
|
||||||
triggerEvent(getDocument().body, "oobErrorNoTarget.hx", {id:child.id, content:child})
|
triggerEvent(getDocument().body, "oobErrorNoTarget.hx", {id:child.id, content:child})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
return settleTasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAttributes(parentNode, fragment) {
|
function handleAttributes(parentNode, fragment) {
|
||||||
@ -179,15 +185,11 @@ var HTMx = HTMx || (function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setTimeout(function () {
|
return attributeSwaps;
|
||||||
forEach(attributeSwaps, function (swap) {
|
|
||||||
swap.call();
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertNodesBefore(parentNode, insertBefore, fragment) {
|
function insertNodesBefore(parentNode, insertBefore, fragment) {
|
||||||
handleAttributes(parentNode, fragment);
|
var settleTasks = handleAttributes(parentNode, fragment);
|
||||||
while(fragment.childNodes.length > 0){
|
while(fragment.childNodes.length > 0){
|
||||||
var child = fragment.firstChild;
|
var child = fragment.firstChild;
|
||||||
parentNode.insertBefore(child, insertBefore);
|
parentNode.insertBefore(child, insertBefore);
|
||||||
@ -196,36 +198,45 @@ var HTMx = HTMx || (function () {
|
|||||||
processNode(child);
|
processNode(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return settleTasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapOuterHTML(target, fragment) {
|
function swapOuterHTML(target, fragment) {
|
||||||
if (target.tagName === "BODY") {
|
if (target.tagName === "BODY") {
|
||||||
swapInnerHTML(target, fragment);
|
return swapInnerHTML(target, fragment);
|
||||||
} else {
|
} else {
|
||||||
insertNodesBefore(parentElt(target), target, fragment);
|
var settleTasks = insertNodesBefore(parentElt(target), target, fragment);
|
||||||
parentElt(target).removeChild(target);
|
parentElt(target).removeChild(target);
|
||||||
|
return settleTasks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapPrepend(target, fragment) {
|
function swapPrepend(target, fragment) {
|
||||||
insertNodesBefore(target, target.firstChild, fragment);
|
return insertNodesBefore(target, target.firstChild, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapPrependBefore(target, fragment) {
|
function swapPrependBefore(target, fragment) {
|
||||||
insertNodesBefore(parentElt(target), target, fragment);
|
return insertNodesBefore(parentElt(target), target, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapAppend(target, fragment) {
|
function swapAppend(target, fragment) {
|
||||||
insertNodesBefore(target, null, fragment);
|
return insertNodesBefore(target, null, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapAppendAfter(target, fragment) {
|
function swapAppendAfter(target, fragment) {
|
||||||
insertNodesBefore(parentElt(target), target.nextSibling, fragment);
|
return insertNodesBefore(parentElt(target), target.nextSibling, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapInnerHTML(target, fragment) {
|
function swapInnerHTML(target, fragment) {
|
||||||
target.innerHTML = "";
|
var firstChild = target.firstChild;
|
||||||
insertNodesBefore(target, null, fragment);
|
var settleTasks = insertNodesBefore(target, firstChild, fragment);
|
||||||
|
if (firstChild) {
|
||||||
|
while (firstChild.nextSibling) {
|
||||||
|
target.removeChild(firstChild.nextSibling);
|
||||||
|
}
|
||||||
|
target.removeChild(firstChild);
|
||||||
|
}
|
||||||
|
return settleTasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybeSelectFromResponse(elt, fragment) {
|
function maybeSelectFromResponse(elt, fragment) {
|
||||||
@ -240,30 +251,20 @@ var HTMx = HTMx || (function () {
|
|||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapResponse(target, elt, responseText, callBack) {
|
function swapResponse(target, elt, responseText) {
|
||||||
|
|
||||||
var fragment = makeFragment(responseText);
|
var fragment = makeFragment(responseText);
|
||||||
handleOutOfBandSwaps(fragment);
|
var settleTasks = handleOutOfBandSwaps(fragment);
|
||||||
|
|
||||||
fragment = maybeSelectFromResponse(elt, fragment);
|
fragment = maybeSelectFromResponse(elt, fragment);
|
||||||
|
|
||||||
var swapStyle = getClosestAttributeValue(elt, "hx-swap");
|
var swapStyle = getClosestAttributeValue(elt, "hx-swap");
|
||||||
if (swapStyle === "outerHTML") {
|
switch(swapStyle) {
|
||||||
swapOuterHTML(target, fragment);
|
case "outerHTML": return concat(settleTasks, swapOuterHTML(target, fragment));
|
||||||
} else if (swapStyle === "prepend") {
|
case "prepend": return concat(settleTasks, swapPrepend(target, fragment));
|
||||||
swapPrepend(target, fragment);
|
case "prependBefore": return concat(settleTasks, swapPrependBefore(target, fragment));
|
||||||
} else if (swapStyle === "prependBefore") {
|
case "append": return concat(settleTasks, swapAppend(target, fragment));
|
||||||
swapPrependBefore(target, fragment);
|
case "appendAfter": return concat(settleTasks, swapAppendAfter(target, fragment));
|
||||||
} else if (swapStyle === "append") {
|
default: return concat(settleTasks, swapInnerHTML(target, fragment));
|
||||||
swapAppend(target, fragment);
|
|
||||||
} else if (swapStyle === "appendAfter") {
|
|
||||||
swapAppendAfter(target, fragment);
|
|
||||||
} else {
|
|
||||||
swapInnerHTML(target, fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(callBack) {
|
|
||||||
callBack.call();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -604,7 +605,7 @@ var HTMx = HTMx || (function () {
|
|||||||
|
|
||||||
function bumpHistoryAccessDate(pathAndSearch) {
|
function bumpHistoryAccessDate(pathAndSearch) {
|
||||||
var historyTimestamps = JSON.parse(localStorage.getItem("hx-history-timestamps")) || {};
|
var historyTimestamps = JSON.parse(localStorage.getItem("hx-history-timestamps")) || {};
|
||||||
historyTimestamps[pathAndSearch] = Date.now;
|
historyTimestamps[pathAndSearch] = Date.now();
|
||||||
var paths = Object.keys(historyTimestamps);
|
var paths = Object.keys(historyTimestamps);
|
||||||
if (paths.length > 20) {
|
if (paths.length > 20) {
|
||||||
purgeOldestPaths(paths, historyTimestamps);
|
purgeOldestPaths(paths, historyTimestamps);
|
||||||
@ -612,20 +613,23 @@ var HTMx = HTMx || (function () {
|
|||||||
localStorage.setItem("hx-history-timestamps", JSON.stringify(historyTimestamps));
|
localStorage.setItem("hx-history-timestamps", JSON.stringify(historyTimestamps));
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveForHistory() {
|
function saveHistory() {
|
||||||
var elt = getHistoryElement();
|
var elt = getHistoryElement();
|
||||||
var pathAndSearch = location.pathname+location.search;
|
var pathAndSearch = location.pathname+location.search;
|
||||||
triggerEvent(getDocument().body, "historyUpdate.hx", {path:pathAndSearch});
|
triggerEvent(getDocument().body, "historyUpdate.hx", {path:pathAndSearch, historyElement:elt});
|
||||||
history.replaceState({}, getDocument().title, window.location.href);
|
history.replaceState({}, getDocument().title, window.location.href);
|
||||||
localStorage.setItem('hx-history-content-' + pathAndSearch, elt.innerHTML);
|
localStorage.setItem('hx-history:' + pathAndSearch, elt.innerHTML);
|
||||||
bumpHistoryAccessDate(pathAndSearch);
|
bumpHistoryAccessDate(pathAndSearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initNewHistoryEntry(elt, url) {
|
function pushUrlIntoHistory(url) {
|
||||||
if (shouldPush(elt)) {
|
history.pushState({}, "", url );
|
||||||
history.pushState({}, "", url );
|
}
|
||||||
saveForHistory();
|
|
||||||
}
|
function settleImmediately(settleTasks) {
|
||||||
|
forEach(settleTasks, function (task) {
|
||||||
|
task.call();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadHistoryFromServer(pathAndSearch) {
|
function loadHistoryFromServer(pathAndSearch) {
|
||||||
@ -637,18 +641,18 @@ var HTMx = HTMx || (function () {
|
|||||||
if (this.status >= 200 && this.status < 400) {
|
if (this.status >= 200 && this.status < 400) {
|
||||||
var fragment = makeFragment(this.response);
|
var fragment = makeFragment(this.response);
|
||||||
fragment = fragment.querySelector('.hx-history-element') || fragment;
|
fragment = fragment.querySelector('.hx-history-element') || fragment;
|
||||||
swapInnerHTML(getHistoryElement(), fragment);
|
settleImmediately(swapInnerHTML(getHistoryElement(), fragment));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreHistory() {
|
function restoreHistory() {
|
||||||
var pathAndSearch = location.pathname+location.search;
|
var pathAndSearch = location.pathname+location.search;
|
||||||
triggerEvent(getDocument().body, "historyUpdate.hx", {path:pathAndSearch});
|
triggerEvent(getDocument().body, "historyRestore.hx", {path:pathAndSearch});
|
||||||
var content = localStorage.getItem('hx-history-content-' + pathAndSearch);
|
var content = localStorage.getItem('hx-history:' + pathAndSearch);
|
||||||
if (content) {
|
if (content) {
|
||||||
bumpHistoryAccessDate(pathAndSearch);
|
bumpHistoryAccessDate(pathAndSearch);
|
||||||
swapInnerHTML(getHistoryElement(), makeFragment(content));
|
settleImmediately(swapInnerHTML(getHistoryElement(), makeFragment(content)));
|
||||||
} else {
|
} else {
|
||||||
loadHistoryFromServer(pathAndSearch);
|
loadHistoryFromServer(pathAndSearch);
|
||||||
}
|
}
|
||||||
@ -659,14 +663,6 @@ var HTMx = HTMx || (function () {
|
|||||||
(elt.tagName === "A" && getInternalData(elt).boosted);
|
(elt.tagName === "A" && getInternalData(elt).boosted);
|
||||||
}
|
}
|
||||||
|
|
||||||
function snapshotForCurrentHistoryEntry(elt) {
|
|
||||||
if (shouldPush(elt)) {
|
|
||||||
// TODO event to allow de-initialization of HTML elements in target
|
|
||||||
saveForHistory();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function addRequestIndicatorClasses(elt) {
|
function addRequestIndicatorClasses(elt) {
|
||||||
mutateRequestIndicatorClasses(elt, "add");
|
mutateRequestIndicatorClasses(elt, "add");
|
||||||
}
|
}
|
||||||
@ -810,11 +806,14 @@ var HTMx = HTMx || (function () {
|
|||||||
if(!triggerEvent(elt, 'values.hx', {values: inputValues, target:target})) return endRequestLock();
|
if(!triggerEvent(elt, 'values.hx', {values: inputValues, target:target})) return endRequestLock();
|
||||||
|
|
||||||
// request type
|
// request type
|
||||||
|
var requestURL;
|
||||||
if (verb === 'get') {
|
if (verb === 'get') {
|
||||||
var noValues = Object.keys(inputValues).length === 0;
|
var noValues = Object.keys(inputValues).length === 0;
|
||||||
xhr.open('GET', path + (noValues ? "" : "?" + urlEncode(inputValues)), true);
|
requestURL = path + (noValues ? "" : "?" + urlEncode(inputValues));
|
||||||
|
xhr.open('GET', requestURL, true);
|
||||||
} else {
|
} else {
|
||||||
xhr.open('POST', path, true);
|
requestURL = path;
|
||||||
|
xhr.open('POST', requestURL, true);
|
||||||
setHeader(xhr,'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8', true);
|
setHeader(xhr,'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8', true);
|
||||||
if (verb !== 'post') {
|
if (verb !== 'post') {
|
||||||
setHeader(xhr, 'X-HTTP-Method-Override', verb.toUpperCase(), true);
|
setHeader(xhr, 'X-HTTP-Method-Override', verb.toUpperCase(), true);
|
||||||
@ -847,29 +846,57 @@ var HTMx = HTMx || (function () {
|
|||||||
xhr.onload = function () {
|
xhr.onload = function () {
|
||||||
try {
|
try {
|
||||||
if (!triggerEvent(elt, 'beforeOnLoad.hx', {xhr: xhr, target: target})) return;
|
if (!triggerEvent(elt, 'beforeOnLoad.hx', {xhr: xhr, target: target})) return;
|
||||||
snapshotForCurrentHistoryEntry(elt, path);
|
|
||||||
var trigger = this.getResponseHeader("X-HX-Trigger");
|
handleTrigger(elt, this.getResponseHeader("X-HX-Trigger"));
|
||||||
handleTrigger(elt, trigger);
|
var pushedUrl = this.getResponseHeader("X-HX-Push")
|
||||||
initNewHistoryEntry(elt, path);
|
|
||||||
|
var shouldSaveHistory = shouldPush(elt) || pushedUrl;
|
||||||
|
|
||||||
if (this.status >= 200 && this.status < 400) {
|
if (this.status >= 200 && this.status < 400) {
|
||||||
// don't process 'No Content' response
|
// don't process 'No Content' response
|
||||||
if (this.status !== 204) {
|
if (this.status !== 204) {
|
||||||
// Success!
|
// Success!
|
||||||
var resp = this.response;
|
var resp = this.response;
|
||||||
if (!triggerEvent(elt, 'beforeSwap.hx', {xhr: xhr, target: target})) return;
|
if (!triggerEvent(elt, 'beforeSwap.hx', {xhr: xhr, target: target})) return;
|
||||||
|
|
||||||
|
// Save current page
|
||||||
|
if (shouldSaveHistory) {
|
||||||
|
saveHistory();
|
||||||
|
}
|
||||||
|
|
||||||
target.classList.add("hx-swapping");
|
target.classList.add("hx-swapping");
|
||||||
var doSwap = function () {
|
var doSwap = function () {
|
||||||
try {
|
try {
|
||||||
swapResponse(target, elt, resp, function () {
|
var settleTasks = swapResponse(target, elt, resp);
|
||||||
target.classList.remove("hx-swapping");
|
target.classList.remove("hx-swapping");
|
||||||
setTimeout(saveForHistory, 200);
|
target.classList.add("hx-settling");
|
||||||
triggerEvent(elt, 'afterSwap.hx', {xhr: xhr, target: target});
|
triggerEvent(elt, 'afterSwap.hx', {xhr: xhr, target: target});
|
||||||
});
|
|
||||||
|
var doSettle = function(){
|
||||||
|
forEach(settleTasks, function (settleTask) {
|
||||||
|
settleTask.call();
|
||||||
|
});
|
||||||
|
target.classList.remove("hx-settling");
|
||||||
|
// push URL and save new page
|
||||||
|
if (shouldSaveHistory) {
|
||||||
|
pushUrlIntoHistory(pushedUrl || requestURL );
|
||||||
|
saveHistory();
|
||||||
|
}
|
||||||
|
triggerEvent(elt, 'afterSettle.hx', {xhr: xhr, target: target});
|
||||||
|
}
|
||||||
|
|
||||||
|
var settleDelayStr = getAttributeValue(elt, "hx-settle-delay") || "100ms";
|
||||||
|
if (settleDelayStr) {
|
||||||
|
setTimeout(doSettle, parseInterval(settleDelayStr))
|
||||||
|
} else {
|
||||||
|
doSettle();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
triggerEvent(elt, 'swapError.hx', {xhr: xhr, response: xhr.response, status: xhr.status, target: target});
|
triggerEvent(elt, 'swapError.hx', {xhr: xhr, response: xhr.response, status: xhr.status, target: target});
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var swapDelayStr = getAttributeValue(elt, "hx-swap-delay");
|
var swapDelayStr = getAttributeValue(elt, "hx-swap-delay");
|
||||||
if (swapDelayStr) {
|
if (swapDelayStr) {
|
||||||
setTimeout(doSwap, parseInterval(swapDelayStr))
|
setTimeout(doSwap, parseInterval(swapDelayStr))
|
||||||
|
@ -24,11 +24,13 @@
|
|||||||
<script src="scratch_server.js"></script>
|
<script src="scratch_server.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// this.server.respondWith("GET", "/test", "Clicked!");
|
this.server.respondWith("GET", "/test", '<a hx-get="/test2">Click Me</a>');
|
||||||
// var btn = make('<button hx-get="/test">Click Me!</button>')
|
this.server.respondWith("GET", "/test2", "Clicked!");
|
||||||
|
|
||||||
this.server.respondWith("GET", "/test", '<div id="d1" style="color: red; margin: 100px">Foo</div>');
|
make('<div hx-get="/test">dd</div>')
|
||||||
make('<div hx-swap="outerHTML" hx-get="/test" hx-push-url="true" id="d1">Foo</div>');
|
|
||||||
|
// this.server.respondWith("GET", "/test", '<div id="d1" style="color: red; margin: 100px">Foo</div>');
|
||||||
|
// make('<div hx-swap="outerHTML" hx-get="/test" hx-push-url="true" id="d1">Foo</div>');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user