mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-09-28 13:31:06 +00:00
hx-boost attribute
This commit is contained in:
parent
474ea8ed74
commit
d1b37b387b
9
TODO.md
9
TODO.md
@ -12,10 +12,8 @@
|
|||||||
|
|
||||||
## TODOS
|
## TODOS
|
||||||
|
|
||||||
* hx-swap="merge"
|
|
||||||
* hx-swap-direct="merge"
|
|
||||||
* hx-boost
|
|
||||||
* hx-error-url
|
* hx-error-url
|
||||||
|
* hx-select (select from response)
|
||||||
* transition model for content swaps
|
* transition model for content swaps
|
||||||
* history support
|
* history support
|
||||||
* Implement LRU
|
* Implement LRU
|
||||||
@ -23,6 +21,11 @@
|
|||||||
* sse support
|
* sse support
|
||||||
* delay (ic-trigger="keyup" ic-delay="1s")
|
* delay (ic-trigger="keyup" ic-delay="1s")
|
||||||
* change support
|
* change support
|
||||||
|
* Testing
|
||||||
|
* polling
|
||||||
|
* history
|
||||||
|
* merge
|
||||||
|
* hx-boost
|
||||||
* distribute on https://unpkg.com/
|
* distribute on https://unpkg.com/
|
||||||
* build website with 11ty
|
* build website with 11ty
|
||||||
* landing page
|
* landing page
|
||||||
|
182
src/htmx.js
182
src/htmx.js
@ -37,20 +37,28 @@ var HTMx = HTMx || (function () {
|
|||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClosestAttributeValue(elt, attributeName) {
|
function getClosestMatch(elt, condition) {
|
||||||
var attribute = getAttributeValue(elt, attributeName);
|
if (condition(elt)) {
|
||||||
if (attribute) {
|
return elt;
|
||||||
return attribute;
|
|
||||||
} else if (parentElt(elt)) {
|
} else if (parentElt(elt)) {
|
||||||
return getClosestAttributeValue(parentElt(elt, attributeName));
|
return getClosestMatch(parentElt(elt), condition);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getClosestAttributeValue(elt, attributeName) {
|
||||||
|
var closestAttr = null;
|
||||||
|
getClosestMatch(elt, function (e) {
|
||||||
|
return closestAttr = getRawAttribute(e, attributeName);
|
||||||
|
});
|
||||||
|
return closestAttr;
|
||||||
|
}
|
||||||
|
|
||||||
function matches(elt, selector) {
|
function matches(elt, selector) {
|
||||||
// noinspection JSUnresolvedVariable
|
// noinspection JSUnresolvedVariable
|
||||||
return (elt != null) &&(elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector).call(elt, selector);
|
return (elt != null) &&(elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector
|
||||||
|
|| elt.webkitMatchesSelector || elt.oMatchesSelector).call(elt, selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closest (elt, selector) {
|
function closest (elt, selector) {
|
||||||
@ -78,14 +86,13 @@ var HTMx = HTMx || (function () {
|
|||||||
|
|
||||||
function toArray(object) {
|
function toArray(object) {
|
||||||
var arr = [];
|
var arr = [];
|
||||||
for (var i = 0; i < object.length; i++) {
|
forEach(object, function(elt) {
|
||||||
arr.push(object[i])
|
arr.push(elt)
|
||||||
}
|
});
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
function forEach(arr, func) {
|
function forEach(arr, func) {
|
||||||
for (var i = 0; i < arr.length; i++) {
|
for (var i = 0; i < arr.length; i++) {
|
||||||
|
|
||||||
func(arr[i]);
|
func(arr[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,11 +102,22 @@ var HTMx = HTMx || (function () {
|
|||||||
//====================================================================
|
//====================================================================
|
||||||
|
|
||||||
function getTarget(elt) {
|
function getTarget(elt) {
|
||||||
var targetVal = getClosestAttributeValue(elt, "hx-target");
|
var explicitTarget = getClosestMatch(elt, function(e){return getRawAttribute(e,"hx-target") !== null});
|
||||||
if (targetVal) {
|
|
||||||
return getDocument().querySelector(targetVal);
|
if (explicitTarget) {
|
||||||
|
var targetStr = getRawAttribute(explicitTarget, "hx-target");
|
||||||
|
if (targetStr === "this") {
|
||||||
|
return explicitTarget;
|
||||||
|
} else {
|
||||||
|
return getDocument().querySelector(targetStr);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return elt;
|
var data = getInternalData(elt);
|
||||||
|
if (data.boosted) {
|
||||||
|
return getDocument().body;
|
||||||
|
} else {
|
||||||
|
return elt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +147,7 @@ var HTMx = HTMx || (function () {
|
|||||||
}
|
}
|
||||||
if (child.nodeType !== Node.TEXT_NODE) {
|
if (child.nodeType !== Node.TEXT_NODE) {
|
||||||
triggerEvent(child, 'load.hx', {parent:parentElt(child)});
|
triggerEvent(child, 'load.hx', {parent:parentElt(child)});
|
||||||
processElement(child);
|
processNode(child);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if(executeAfter) {
|
if(executeAfter) {
|
||||||
@ -300,46 +318,76 @@ var HTMx = HTMx || (function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processElement(elt) {
|
function isLocalLink(elt) {
|
||||||
var nodeData = getInternalData(elt);
|
return location.hostname === elt.hostname &&
|
||||||
if (nodeData.processed) {
|
getRawAttribute(elt,'href') &&
|
||||||
return;
|
!getRawAttribute(elt,'href').startsWith("#")
|
||||||
} else {
|
}
|
||||||
nodeData.processed = true;
|
|
||||||
}
|
function boostElement(elt, nodeData, trigger) {
|
||||||
forEach(VERBS, function(verb){
|
if ((elt.tagName === "A" && isLocalLink(elt)) || elt.tagName === "FORM") {
|
||||||
var path = getAttributeValue(elt, 'hx-' + verb);
|
nodeData.boosted = true;
|
||||||
if (path) {
|
var verb, path;
|
||||||
var trigger = getTrigger(elt);
|
if (elt.tagName === "A") {
|
||||||
if (trigger === 'load') {
|
verb = "get";
|
||||||
if (!nodeData.loaded) {
|
path = getRawAttribute(elt, 'href');
|
||||||
nodeData.loaded = true;
|
} else {
|
||||||
issueAjaxRequest(elt, verb, path);
|
var rawAttribute = getRawAttribute(elt, "method");
|
||||||
}
|
verb = rawAttribute ? rawAttribute.toLowerCase() : "get";
|
||||||
} else if (trigger.trim().indexOf('every ') === 0) {
|
path = getRawAttribute(elt, 'action');
|
||||||
nodeData.polling = true;
|
|
||||||
processPolling(elt, verb, path);
|
|
||||||
} else {
|
|
||||||
var eventListener = function (evt) {
|
|
||||||
var eventData = getInternalData(evt);
|
|
||||||
if (!eventData.handled) {
|
|
||||||
eventData.handled = true;
|
|
||||||
issueAjaxRequest(elt, verb, path, evt.target);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
nodeData.trigger = trigger;
|
|
||||||
nodeData.eventListener = eventListener;
|
|
||||||
elt.addEventListener(trigger, eventListener);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
addEventListener(elt, verb, path, nodeData, trigger, true);
|
||||||
if (getAttributeValue(elt, 'hx-add-class')) {
|
|
||||||
processClassList(elt, getAttributeValue(elt, 'hx-add-class'), "add");
|
|
||||||
}
|
}
|
||||||
if (getAttributeValue(elt, 'hx-remove-class')) {
|
}
|
||||||
processClassList(elt, getAttributeValue(elt, 'hx-remove-class'), "remove");
|
|
||||||
|
function addEventListener(elt, verb, path, nodeData, trigger, cancel) {
|
||||||
|
var eventListener = function (evt) {
|
||||||
|
if(cancel) evt.preventDefault();
|
||||||
|
var eventData = getInternalData(evt);
|
||||||
|
if (!eventData.handled) {
|
||||||
|
eventData.handled = true;
|
||||||
|
issueAjaxRequest(elt, verb, path, evt.target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
nodeData.trigger = trigger;
|
||||||
|
nodeData.eventListener = eventListener;
|
||||||
|
elt.addEventListener(trigger, eventListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
function processNode(elt) {
|
||||||
|
var nodeData = getInternalData(elt);
|
||||||
|
if (!nodeData.processed) {
|
||||||
|
nodeData.processed = true;
|
||||||
|
var trigger = getTrigger(elt);
|
||||||
|
var explicitAction = false;
|
||||||
|
forEach(VERBS, function(verb){
|
||||||
|
var path = getAttributeValue(elt, 'hx-' + verb);
|
||||||
|
if (path) {
|
||||||
|
explicitAction = true;
|
||||||
|
if (trigger === 'load') {
|
||||||
|
if (!nodeData.loaded) {
|
||||||
|
nodeData.loaded = true;
|
||||||
|
issueAjaxRequest(elt, verb, path);
|
||||||
|
}
|
||||||
|
} else if (trigger.trim().indexOf('every ') === 0) {
|
||||||
|
nodeData.polling = true;
|
||||||
|
processPolling(elt, verb, path);
|
||||||
|
} else {
|
||||||
|
addEventListener(elt, verb, path, nodeData, trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!explicitAction && getClosestAttributeValue(elt, "hx-boost") == "true") {
|
||||||
|
boostElement(elt, nodeData, trigger);
|
||||||
|
}
|
||||||
|
if (getAttributeValue(elt, 'hx-add-class')) {
|
||||||
|
processClassList(elt, getAttributeValue(elt, 'hx-add-class'), "add");
|
||||||
|
}
|
||||||
|
if (getAttributeValue(elt, 'hx-remove-class')) {
|
||||||
|
processClassList(elt, getAttributeValue(elt, 'hx-remove-class'), "remove");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
forEach(elt.children, function(child) { processElement(child) });
|
forEach(elt.children, function(child) { processNode(child) });
|
||||||
}
|
}
|
||||||
|
|
||||||
//====================================================================
|
//====================================================================
|
||||||
@ -404,15 +452,20 @@ var HTMx = HTMx || (function () {
|
|||||||
processResponseNodes(elt, null, content);
|
processResponseNodes(elt, null, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldPush(elt) {
|
||||||
|
return getClosestAttributeValue(elt, "hx-push-url") === "true" ||
|
||||||
|
(elt.tagName === "A" && getInternalData(elt).boosted);
|
||||||
|
}
|
||||||
|
|
||||||
function snapshotForCurrentHistoryEntry(elt) {
|
function snapshotForCurrentHistoryEntry(elt) {
|
||||||
if (getClosestAttributeValue(elt, "hx-push-url") === "true") {
|
if (shouldPush(elt)) {
|
||||||
// TODO event to allow de-initialization of HTML elements in target
|
// TODO event to allow de-initialization of HTML elements in target
|
||||||
updateCurrentHistoryContent();
|
updateCurrentHistoryContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initNewHistoryEntry(elt, url) {
|
function initNewHistoryEntry(elt, url) {
|
||||||
if (getClosestAttributeValue(elt, "hx-push-url") === "true") {
|
if (shouldPush(elt)) {
|
||||||
newHistoryData();
|
newHistoryData();
|
||||||
history.pushState({}, "", url);
|
history.pushState({}, "", url);
|
||||||
updateCurrentHistoryContent();
|
updateCurrentHistoryContent();
|
||||||
@ -475,9 +528,9 @@ var HTMx = HTMx || (function () {
|
|||||||
}
|
}
|
||||||
if (matches(elt, 'form')) {
|
if (matches(elt, 'form')) {
|
||||||
var inputs = elt.elements;
|
var inputs = elt.elements;
|
||||||
for (var i = 0; i < inputs.length; i++) {
|
forEach(inputs, function(input) {
|
||||||
processInputValue(processed, values, inputs[i]);
|
processInputValue(processed, values, input);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -491,10 +544,9 @@ var HTMx = HTMx || (function () {
|
|||||||
var includes = getAttributeValue(elt, "hx-include");
|
var includes = getAttributeValue(elt, "hx-include");
|
||||||
if (includes) {
|
if (includes) {
|
||||||
var nodes = getDocument().querySelectorAll(includes);
|
var nodes = getDocument().querySelectorAll(includes);
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
forEach(nodes, function(node) {
|
||||||
var node = nodes[i];
|
|
||||||
processInputValue(processed, values, node);
|
processInputValue(processed, values, node);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// include the closest form
|
// include the closest form
|
||||||
@ -516,9 +568,9 @@ var HTMx = HTMx || (function () {
|
|||||||
if (values.hasOwnProperty(name)) {
|
if (values.hasOwnProperty(name)) {
|
||||||
var value = values[name];
|
var value = values[name];
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
for (var i = 0; i < value.length; i++) {
|
forEach(value, function(v) {
|
||||||
returnStr = appendParam(returnStr, name, value[i]);
|
returnStr = appendParam(returnStr, name, v);
|
||||||
}
|
});
|
||||||
} else {
|
} else {
|
||||||
returnStr = appendParam(returnStr, name, value);
|
returnStr = appendParam(returnStr, name, value);
|
||||||
}
|
}
|
||||||
@ -647,7 +699,7 @@ var HTMx = HTMx || (function () {
|
|||||||
|
|
||||||
// initialize the document
|
// initialize the document
|
||||||
ready(function () {
|
ready(function () {
|
||||||
processElement(getDocument().body);
|
processNode(getDocument().body);
|
||||||
window.onpopstate = function (event) {
|
window.onpopstate = function (event) {
|
||||||
restoreHistory(event.state);
|
restoreHistory(event.state);
|
||||||
};
|
};
|
||||||
@ -659,7 +711,7 @@ var HTMx = HTMx || (function () {
|
|||||||
|
|
||||||
// Public API
|
// Public API
|
||||||
return {
|
return {
|
||||||
processElement: processElement,
|
processElement: processNode,
|
||||||
version: "0.0.1",
|
version: "0.0.1",
|
||||||
_:internalEval
|
_:internalEval
|
||||||
}
|
}
|
||||||
|
55
test/boost.js
Normal file
55
test/boost.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
describe("HTMx Boost Tests", function() {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.server = makeServer();
|
||||||
|
clearWorkArea();
|
||||||
|
});
|
||||||
|
afterEach(function () {
|
||||||
|
this.server.restore();
|
||||||
|
clearWorkArea();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles basic anchor properly', function () {
|
||||||
|
this.server.respondWith("GET", "/test", "Boosted");
|
||||||
|
var div = make('<div hx-target="this" hx-boost="true"><a id="a1" href="/test">Foo</a></div>');
|
||||||
|
var a = byId('a1');
|
||||||
|
a.click();
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Boosted");
|
||||||
|
history.back();
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
it('handles basic form post properly', function () {
|
||||||
|
this.server.respondWith("POST", "/test", "Boosted");
|
||||||
|
this.server.respondWith("POST", "/test", "Boosted");
|
||||||
|
var div = make('<div hx-target="this" hx-boost="true"><form id="f1" action="/test" method="post"><button id="b1">Submit</button></form></div>');
|
||||||
|
var btn = byId('b1');
|
||||||
|
btn.click();
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Boosted");
|
||||||
|
history.back();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles basic form get properly', function () {
|
||||||
|
this.server.respondWith("GET", "/test", "Boosted");
|
||||||
|
var div = make('<div hx-target="this" hx-boost="true"><form id="f1" action="/test" method="get"><button id="b1">Submit</button></form></div>');
|
||||||
|
var btn = byId('b1');
|
||||||
|
btn.click();
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Boosted");
|
||||||
|
history.back();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles basic form with no explicit method property', function () {
|
||||||
|
this.server.respondWith("GET", "/test", "Boosted");
|
||||||
|
var div = make('<div hx-target="this" hx-boost="true"><form id="f1" action="/test"><button id="b1">Submit</button></form></div>');
|
||||||
|
var btn = byId('b1');
|
||||||
|
btn.click();
|
||||||
|
this.server.respond();
|
||||||
|
div.innerHTML.should.equal("Boosted");
|
||||||
|
history.back();
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
@ -27,6 +27,7 @@
|
|||||||
<script src="values.js"></script>
|
<script src="values.js"></script>
|
||||||
<script src="events.js"></script>
|
<script src="events.js"></script>
|
||||||
<script src="swap_direct.js"></script>
|
<script src="swap_direct.js"></script>
|
||||||
|
<script src="boost.js"></script>
|
||||||
|
|
||||||
<script class="mocha-exec">
|
<script class="mocha-exec">
|
||||||
mocha.run();
|
mocha.run();
|
||||||
|
@ -29,9 +29,9 @@
|
|||||||
<script src="scratch_server.js"></script>
|
<script src="scratch_server.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
this.server.respondWith("GET", "/test", "Clicked<div id='d1' ic-swap-direct='true'>Swapped</div>");
|
this.server.respondWith("POST", "/test", "Boosted");
|
||||||
var div = make('<div hx-get="/test">click me</div>');
|
var div = make('<div hx-target="this" hx-boost="true"><form id="f1" action="/test" method="post"><button id="b1">Submit</button></form></div>');
|
||||||
make('<div id="d1"></div>');
|
var btn = byId('b1');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user