mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-09-28 13:31:06 +00:00
Merge branch 'dev' into json-parse
This commit is contained in:
commit
025b043918
@ -2,9 +2,9 @@
|
||||
|
||||
*high power tools for HTML*
|
||||
|
||||
[](https://gitter.im/intercooler-js/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](https://app.netlify.com/sites/htmx/deploys)
|
||||
[]()
|
||||
[](https://gitter.im/intercooler-js/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](https://app.netlify.com/sites/htmx/deploys)
|
||||
[](https://app.circleci.com/pipelines/github/bigskysoftware/htmx)
|
||||
|
||||
## introduction
|
||||
|
||||
@ -53,4 +53,4 @@ keep the core htmx code tidy
|
||||
|
||||
*javascript fatigue:<br/>
|
||||
longing for a hypertext<br/>
|
||||
already in hand*
|
||||
already in hand*
|
||||
|
7
dist/ext/ajax-header.js
vendored
Normal file
7
dist/ext/ajax-header.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
htmx.defineExtension('ajax-header', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "configRequest.htmx") {
|
||||
evt.detail.headers['X-Requested-With'] = 'XMLHttpRequest';
|
||||
}
|
||||
}
|
||||
});
|
18
dist/ext/class-tools.js
vendored
18
dist/ext/class-tools.js
vendored
@ -58,14 +58,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
function maybeProcessClasses(elt) {
|
||||
if (elt.getAttribute) {
|
||||
var classList = elt.getAttribute("classes") || elt.getAttribute("data-classes");
|
||||
if (classList) {
|
||||
processClassList(elt, classList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
htmx.defineExtension('class-tools', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "processedNode.htmx") {
|
||||
var elt = evt.detail.elt;
|
||||
if (elt.getAttribute) {
|
||||
var classList = elt.getAttribute("classes") || elt.getAttribute("data-classes");
|
||||
if (classList) {
|
||||
processClassList(elt, classList);
|
||||
maybeProcessClasses(elt);
|
||||
if (elt.querySelectorAll) {
|
||||
var children = elt.querySelectorAll("[classes], [data-classes]");
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
maybeProcessClasses(children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
dist/ext/client-side-templates.js
vendored
11
dist/ext/client-side-templates.js
vendored
@ -24,9 +24,14 @@ htmx.defineExtension('client-side-templates', {
|
||||
if (nunjucksTemplate) {
|
||||
var data = JSON.parse(text);
|
||||
var templateName = nunjucksTemplate.getAttribute('nunjucks-template');
|
||||
return nunjucks.render(templateName, data);
|
||||
}
|
||||
var template = htmx.find('#' + templateName);
|
||||
if (template) {
|
||||
return nunjucks.renderString(template.innerHTML, data);
|
||||
} else {
|
||||
return nunjucks.render(templateName, data);
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
7
dist/ext/json-enc.js
vendored
7
dist/ext/json-enc.js
vendored
@ -1,6 +1,11 @@
|
||||
htmx.defineExtension('json-enc', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "configRequest.htmx") {
|
||||
evt.detail.headers['Content-Type'] = "application/json";
|
||||
}
|
||||
},
|
||||
|
||||
encodeParameters : function(xhr, parameters, elt) {
|
||||
xhr.requestHeaders['Content-Type'] = 'application/json';
|
||||
xhr.overrideMimeType('text/json');
|
||||
return (JSON.stringify(parameters));
|
||||
}
|
||||
|
11
dist/ext/method-override.js
vendored
Normal file
11
dist/ext/method-override.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
htmx.defineExtension('method-override', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "configRequest.htmx") {
|
||||
var method = evt.detail.verb;
|
||||
if (method !== "get" || method !== "post") {
|
||||
evt.detail.headers['X-HTTP-Method-Override'] = method.toUpperCase();
|
||||
evt.detail.verb = "post";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
20
dist/ext/remove-me.js
vendored
20
dist/ext/remove-me.js
vendored
@ -1,14 +1,24 @@
|
||||
(function(){
|
||||
function maybeRemoveMe(elt) {
|
||||
var timing = elt.getAttribute("remove-me") || elt.getAttribute("data-remove-me");
|
||||
if (timing) {
|
||||
setTimeout(function () {
|
||||
elt.parentElement.removeChild(elt);
|
||||
}, htmx.parseInterval(timing));
|
||||
}
|
||||
}
|
||||
|
||||
htmx.defineExtension('remove-me', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "processedNode.htmx") {
|
||||
var elt = evt.detail.elt;
|
||||
if (elt.getAttribute) {
|
||||
var timing = elt.getAttribute("remove-me") || elt.getAttribute("data-remove-me");
|
||||
if (timing) {
|
||||
setTimeout(function () {
|
||||
elt.parentElement.removeChild(elt);
|
||||
}, htmx.parseInterval(timing));
|
||||
maybeRemoveMe(elt);
|
||||
if (elt.querySelectorAll) {
|
||||
var children = elt.querySelectorAll("[remove-me], [data-remove-me");
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
maybeRemoveMe(children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
185
dist/htmx.js
vendored
185
dist/htmx.js
vendored
@ -12,12 +12,15 @@ return (function () {
|
||||
'use strict';
|
||||
|
||||
var VERBS = ['get', 'post', 'put', 'delete', 'patch'];
|
||||
var VERB_SELECTOR = VERBS.map(function(verb){
|
||||
return "[hx-" + verb + "], [data-hx-" + verb + "]"
|
||||
}).join(", ");
|
||||
|
||||
//====================================================================
|
||||
// Utilities
|
||||
//====================================================================
|
||||
function parseInterval(str) {
|
||||
if (str === "null" || str === "false" || str === "") {
|
||||
if (str == null || str === "null" || str === "false" || str === "") {
|
||||
return null;
|
||||
} else if (str.lastIndexOf("ms") === str.length - 2) {
|
||||
return parseFloat(str.substr(0, str.length - 2));
|
||||
@ -33,6 +36,11 @@ return (function () {
|
||||
}
|
||||
|
||||
// resolve with both hx and data-hx prefixes
|
||||
function hasAttribute(elt, qualifiedName) {
|
||||
return elt.hasAttribute && (elt.hasAttribute(qualifiedName) ||
|
||||
elt.hasAttribute("data-" + qualifiedName));
|
||||
}
|
||||
|
||||
function getAttributeValue(elt, qualifiedName) {
|
||||
return getRawAttribute(elt, qualifiedName) || getRawAttribute(elt, "data-" + qualifiedName);
|
||||
}
|
||||
@ -374,20 +382,23 @@ return (function () {
|
||||
|
||||
function handleAttributes(parentNode, fragment, settleInfo) {
|
||||
forEach(fragment.querySelectorAll("[id]"), function (newNode) {
|
||||
var oldNode = parentNode.querySelector(newNode.tagName + "[id=" + newNode.id + "]")
|
||||
if (oldNode && oldNode !== parentNode) {
|
||||
var newAttributes = newNode.cloneNode();
|
||||
cloneAttributes(newNode, oldNode);
|
||||
settleInfo.tasks.push(function () {
|
||||
cloneAttributes(newNode, newAttributes);
|
||||
});
|
||||
if (newNode.id && newNode.id.length > 0) {
|
||||
var oldNode = parentNode.querySelector(newNode.tagName + "[id=" + newNode.id + "]");
|
||||
if (oldNode && oldNode !== parentNode) {
|
||||
var newAttributes = newNode.cloneNode();
|
||||
cloneAttributes(newNode, oldNode);
|
||||
settleInfo.tasks.push(function () {
|
||||
cloneAttributes(newNode, newAttributes);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function makeLoadTask(child) {
|
||||
function makeAjaxLoadTask(child) {
|
||||
return function () {
|
||||
processNode(child);
|
||||
processScripts(child);
|
||||
triggerEvent(child, 'load.htmx', {});
|
||||
};
|
||||
}
|
||||
@ -398,7 +409,7 @@ return (function () {
|
||||
var child = fragment.firstChild;
|
||||
parentNode.insertBefore(child, insertBefore);
|
||||
if (child.nodeType !== Node.TEXT_NODE) {
|
||||
settleInfo.tasks.push(makeLoadTask(child));
|
||||
settleInfo.tasks.push(makeAjaxLoadTask(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -477,6 +488,8 @@ return (function () {
|
||||
|
||||
function swap(swapStyle, elt, target, fragment, settleInfo) {
|
||||
switch (swapStyle) {
|
||||
case "none":
|
||||
return;
|
||||
case "outerHTML":
|
||||
swapOuterHTML(target, fragment, settleInfo);
|
||||
return;
|
||||
@ -563,6 +576,9 @@ return (function () {
|
||||
if (token.indexOf("delay:") === 0) {
|
||||
triggerSpec.delay = parseInterval(token.substr(6));
|
||||
}
|
||||
if (token.indexOf("throttle:") === 0) {
|
||||
triggerSpec.throttle = parseInterval(token.substr(9));
|
||||
}
|
||||
}
|
||||
return triggerSpec;
|
||||
}).filter(function(x){ return x !== null });
|
||||
@ -655,13 +671,21 @@ return (function () {
|
||||
if (elementData.delayed) {
|
||||
clearTimeout(elementData.delayed);
|
||||
}
|
||||
var issueRequest = function(){
|
||||
issueAjaxRequest(elt, verb, path, evt.target);
|
||||
if (elementData.throttle) {
|
||||
return;
|
||||
}
|
||||
if (triggerSpec.delay) {
|
||||
elementData.delayed = setTimeout(issueRequest, triggerSpec.delay);
|
||||
|
||||
if (triggerSpec.throttle) {
|
||||
elementData.throttle = setTimeout(function(){
|
||||
issueAjaxRequest(elt, verb, path, evt.target);
|
||||
elementData.throttle = null;
|
||||
}, triggerSpec.throttle);
|
||||
} else if (triggerSpec.delay) {
|
||||
elementData.delayed = setTimeout(function(){
|
||||
issueAjaxRequest(elt, verb, path, evt.target);
|
||||
}, triggerSpec.delay);
|
||||
} else {
|
||||
issueRequest();
|
||||
issueAjaxRequest(elt, verb, path, evt.target);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -825,8 +849,8 @@ return (function () {
|
||||
function processVerbs(elt, nodeData, triggerSpecs) {
|
||||
var explicitAction = false;
|
||||
forEach(VERBS, function (verb) {
|
||||
var path = getAttributeValue(elt, 'hx-' + verb);
|
||||
if (path) {
|
||||
if (hasAttribute(elt,'hx-' + verb)) {
|
||||
var path = getAttributeValue(elt, 'hx-' + verb);
|
||||
explicitAction = true;
|
||||
nodeData.path = path;
|
||||
nodeData.verb = verb;
|
||||
@ -850,17 +874,50 @@ return (function () {
|
||||
return explicitAction;
|
||||
}
|
||||
|
||||
function processScript(elt) {
|
||||
if (elt.tagName === "SCRIPT" && elt.type === "text/javascript") {
|
||||
eval(elt.innerText);
|
||||
function evalScript(script) {
|
||||
if (script.type === "text/javascript") {
|
||||
try {
|
||||
eval(script.innerText);
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processNode(elt) {
|
||||
function processScripts(elt) {
|
||||
if (matches(elt, "script")) {
|
||||
evalScript(elt);
|
||||
}
|
||||
forEach(findAll(elt, "script"), function (script) {
|
||||
evalScript(script);
|
||||
});
|
||||
}
|
||||
|
||||
function isHyperScriptAvailable() {
|
||||
return typeof _hyperscript !== "undefined";
|
||||
}
|
||||
|
||||
function findElementsToProcess(elt) {
|
||||
if (elt.querySelectorAll) {
|
||||
return elt.querySelectorAll(VERB_SELECTOR + ", a, form, [hx-sse], [data-hx-sse], [hx-ws], [data-hx-ws]");
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function processNode(elt, ignoreChildren) {
|
||||
var nodeData = getInternalData(elt);
|
||||
if (!nodeData.processed) {
|
||||
nodeData.processed = true;
|
||||
|
||||
if(isHyperScriptAvailable()){
|
||||
_hyperscript.init(elt);
|
||||
}
|
||||
|
||||
if (elt.value) {
|
||||
nodeData.lastValue = elt.value;
|
||||
}
|
||||
|
||||
var triggerSpecs = getTriggerSpecs(elt);
|
||||
var explicitAction = processVerbs(elt, nodeData, triggerSpecs);
|
||||
|
||||
@ -878,11 +935,9 @@ return (function () {
|
||||
processWebSocketInfo(elt, nodeData, wsInfo);
|
||||
}
|
||||
triggerEvent(elt, "processedNode.htmx");
|
||||
|
||||
processScript(elt);
|
||||
}
|
||||
if (elt.children) { // IE
|
||||
forEach(elt.children, function(child) { processNode(child) });
|
||||
if (!ignoreChildren) {
|
||||
forEach(findElementsToProcess(elt), function(child) { processNode(child, true) });
|
||||
}
|
||||
}
|
||||
|
||||
@ -890,16 +945,6 @@ return (function () {
|
||||
// Event/Log Support
|
||||
//====================================================================
|
||||
|
||||
function sendError(elt, eventName, detail) {
|
||||
var errorURL = getClosestAttributeValue(elt, "hx-error-url");
|
||||
if (errorURL) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", errorURL);
|
||||
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
xhr.send(JSON.stringify({ "elt": elt.id, "event": eventName, "detail" : detail }));
|
||||
}
|
||||
}
|
||||
|
||||
function makeEvent(eventName, detail) {
|
||||
var evt;
|
||||
if (window.CustomEvent && typeof window.CustomEvent === 'function') {
|
||||
@ -948,7 +993,7 @@ return (function () {
|
||||
}
|
||||
if (detail.error) {
|
||||
logError(detail.error);
|
||||
sendError(elt, eventName, detail);
|
||||
triggerEvent(elt, "error.htmx", {errorInfo:detail})
|
||||
}
|
||||
var eventResult = elt.dispatchEvent(event);
|
||||
withExtensions(elt, function (extension) {
|
||||
@ -1191,23 +1236,23 @@ return (function () {
|
||||
|
||||
function getHeaders(elt, target, prompt, eventTarget) {
|
||||
var headers = {
|
||||
"X-HX-Request" : "true",
|
||||
"X-HX-Trigger" : getRawAttribute(elt, "id"),
|
||||
"X-HX-Trigger-Name" : getRawAttribute(elt, "name"),
|
||||
"X-HX-Target" : getAttributeValue(target, "id"),
|
||||
"X-HX-Current-URL" : getDocument().location.href,
|
||||
"HX-Request" : "true",
|
||||
"HX-Trigger" : getRawAttribute(elt, "id"),
|
||||
"HX-Trigger-Name" : getRawAttribute(elt, "name"),
|
||||
"HX-Target" : getAttributeValue(target, "id"),
|
||||
"HX-Current-URL" : getDocument().location.href,
|
||||
}
|
||||
if (prompt !== undefined) {
|
||||
headers["X-HX-Prompt"] = prompt;
|
||||
headers["HX-Prompt"] = prompt;
|
||||
}
|
||||
if (eventTarget) {
|
||||
headers["X-HX-Event-Target"] = getRawAttribute(eventTarget, "id");
|
||||
headers["HX-Event-Target"] = getRawAttribute(eventTarget, "id");
|
||||
}
|
||||
if (getDocument().activeElement) {
|
||||
headers["X-HX-Active-Element"] = getRawAttribute(getDocument().activeElement, "id");
|
||||
headers["X-HX-Active-Element-Name"] = getRawAttribute(getDocument().activeElement, "name");
|
||||
headers["HX-Active-Element"] = getRawAttribute(getDocument().activeElement, "id");
|
||||
headers["HX-Active-Element-Name"] = getRawAttribute(getDocument().activeElement, "name");
|
||||
if (getDocument().activeElement.value) {
|
||||
headers["X-HX-Active-Element-Value"] = getRawAttribute(getDocument().activeElement, "value");
|
||||
headers["HX-Active-Element-Value"] = getRawAttribute(getDocument().activeElement, "value");
|
||||
}
|
||||
}
|
||||
return headers;
|
||||
@ -1319,9 +1364,6 @@ return (function () {
|
||||
|
||||
if (verb !== 'get') {
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||
if (verb !== 'post') {
|
||||
headers['X-HTTP-Method-Override'] = verb.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
// behavior of anchors w/ empty href is to use the current URL
|
||||
@ -1334,9 +1376,15 @@ return (function () {
|
||||
unfilteredParameters:rawParameters,
|
||||
headers:headers,
|
||||
target:target,
|
||||
verb:verb
|
||||
verb:verb,
|
||||
path:path
|
||||
};
|
||||
if(!triggerEvent(elt, 'configRequest.htmx', requestConfig)) return endRequestLock();
|
||||
// copy out in case the object was overwritten
|
||||
path = requestConfig.path;
|
||||
verb = requestConfig.verb;
|
||||
headers = requestConfig.headers;
|
||||
filteredParameters = requestConfig.parameters;
|
||||
|
||||
var splitPath = path.split("#");
|
||||
var pathNoAnchor = splitPath[0];
|
||||
@ -1357,7 +1405,7 @@ return (function () {
|
||||
}
|
||||
xhr.open('GET', finalPathForGet, true);
|
||||
} else {
|
||||
xhr.open('POST', path, true);
|
||||
xhr.open(verb.toUpperCase(), path, true);
|
||||
}
|
||||
|
||||
xhr.overrideMimeType("text/html");
|
||||
@ -1374,8 +1422,8 @@ return (function () {
|
||||
try {
|
||||
if (!triggerEvent(elt, 'beforeOnLoad.htmx', eventDetail)) return;
|
||||
|
||||
handleTrigger(elt, this.getResponseHeader("X-HX-Trigger"));
|
||||
var pushedUrl = this.getResponseHeader("X-HX-Push");
|
||||
handleTrigger(elt, this.getResponseHeader("HX-Trigger"));
|
||||
var pushedUrl = this.getResponseHeader("HX-Push");
|
||||
|
||||
var shouldSaveHistory = shouldPush(elt) || pushedUrl;
|
||||
|
||||
@ -1385,7 +1433,7 @@ return (function () {
|
||||
}
|
||||
// don't process 'No Content' response
|
||||
if (this.status !== 204) {
|
||||
if (!triggerEvent(elt, 'beforeSwap.htmx', eventDetail)) return;
|
||||
if (!triggerEvent(target, 'beforeSwap.htmx', eventDetail)) return;
|
||||
|
||||
var resp = this.response;
|
||||
withExtensions(elt, function(extension){
|
||||
@ -1402,15 +1450,32 @@ return (function () {
|
||||
target.classList.add("htmx-swapping");
|
||||
var doSwap = function () {
|
||||
try {
|
||||
|
||||
var activeElt = document.activeElement;
|
||||
var selectionInfo = {
|
||||
elt: activeElt,
|
||||
start: activeElt.selectionStart,
|
||||
end: activeElt.selectionEnd,
|
||||
};
|
||||
|
||||
var settleInfo = makeSettleInfo(target);
|
||||
selectAndSwap(swapSpec.swapStyle, target, elt, resp, settleInfo);
|
||||
|
||||
if (!bodyContains(selectionInfo.elt) && selectionInfo.elt.id) {
|
||||
var newActiveElt = document.getElementById(selectionInfo.elt.id);
|
||||
if (selectionInfo.start && newActiveElt.setSelectionRange) {
|
||||
newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end);
|
||||
}
|
||||
newActiveElt.focus();
|
||||
}
|
||||
|
||||
target.classList.remove("htmx-swapping");
|
||||
forEach(settleInfo.elts, function (elt) {
|
||||
if (elt.classList) {
|
||||
elt.classList.add("htmx-settling");
|
||||
}
|
||||
triggerEvent(elt, 'afterSwap.htmx', eventDetail);
|
||||
});
|
||||
triggerEvent(elt, 'afterSwap.htmx', eventDetail);
|
||||
if (anchor) {
|
||||
location.hash = anchor;
|
||||
}
|
||||
@ -1422,12 +1487,14 @@ return (function () {
|
||||
if (elt.classList) {
|
||||
elt.classList.remove("htmx-settling");
|
||||
}
|
||||
triggerEvent(elt, 'afterSettle.htmx', eventDetail);
|
||||
});
|
||||
// push URL and save new page
|
||||
if (shouldSaveHistory) {
|
||||
pushUrlIntoHistory(pushedUrl || path);
|
||||
var pathToPush = pushedUrl || finalPathForGet || path;
|
||||
pushUrlIntoHistory(pathToPush);
|
||||
triggerEvent(getDocument().body, 'pushedIntoHistory.htmx', {path:pathToPush});
|
||||
}
|
||||
triggerEvent(elt, 'afterSettle.htmx', eventDetail);
|
||||
}
|
||||
|
||||
if (swapSpec.settleDelay > 0) {
|
||||
@ -1558,7 +1625,7 @@ return (function () {
|
||||
ready(function () {
|
||||
mergeMetaConfig();
|
||||
var body = getDocument().body;
|
||||
processNode(body);
|
||||
processNode(body, true);
|
||||
triggerEvent(body, 'load.htmx', {});
|
||||
window.onpopstate = function () {
|
||||
restoreHistory();
|
||||
@ -1603,4 +1670,4 @@ return (function () {
|
||||
}
|
||||
}
|
||||
)()
|
||||
}));
|
||||
}));
|
||||
|
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.
7
src/ext/ajax-header.js
Normal file
7
src/ext/ajax-header.js
Normal file
@ -0,0 +1,7 @@
|
||||
htmx.defineExtension('ajax-header', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "configRequest.htmx") {
|
||||
evt.detail.headers['X-Requested-With'] = 'XMLHttpRequest';
|
||||
}
|
||||
}
|
||||
});
|
@ -58,14 +58,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
function maybeProcessClasses(elt) {
|
||||
if (elt.getAttribute) {
|
||||
var classList = elt.getAttribute("classes") || elt.getAttribute("data-classes");
|
||||
if (classList) {
|
||||
processClassList(elt, classList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
htmx.defineExtension('class-tools', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "processedNode.htmx") {
|
||||
var elt = evt.detail.elt;
|
||||
if (elt.getAttribute) {
|
||||
var classList = elt.getAttribute("classes") || elt.getAttribute("data-classes");
|
||||
if (classList) {
|
||||
processClassList(elt, classList);
|
||||
maybeProcessClasses(elt);
|
||||
if (elt.querySelectorAll) {
|
||||
var children = elt.querySelectorAll("[classes], [data-classes]");
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
maybeProcessClasses(children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,9 +24,14 @@ htmx.defineExtension('client-side-templates', {
|
||||
if (nunjucksTemplate) {
|
||||
var data = JSON.parse(text);
|
||||
var templateName = nunjucksTemplate.getAttribute('nunjucks-template');
|
||||
return nunjucks.render(templateName, data);
|
||||
}
|
||||
var template = htmx.find('#' + templateName);
|
||||
if (template) {
|
||||
return nunjucks.renderString(template.innerHTML, data);
|
||||
} else {
|
||||
return nunjucks.render(templateName, data);
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,12 @@
|
||||
htmx.defineExtension('json-enc', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "configRequest.htmx") {
|
||||
evt.detail.headers['Content-Type'] = "application/json";
|
||||
}
|
||||
},
|
||||
|
||||
encodeParameters : function(xhr, parameters, elt) {
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.overrideMimeType('text/json');
|
||||
return (JSON.stringify(parameters));
|
||||
}
|
||||
});
|
||||
});
|
11
src/ext/method-override.js
Normal file
11
src/ext/method-override.js
Normal file
@ -0,0 +1,11 @@
|
||||
htmx.defineExtension('method-override', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "configRequest.htmx") {
|
||||
var method = evt.detail.verb;
|
||||
if (method !== "get" || method !== "post") {
|
||||
evt.detail.headers['X-HTTP-Method-Override'] = method.toUpperCase();
|
||||
evt.detail.verb = "post";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
htmx.defineExtension('rails-method', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "configRequest.htmx") {
|
||||
var methodOverride = evt.detail.headers['X-HTTP-Method-Override'];
|
||||
if (methodOverride) {
|
||||
evt.detail.parameters['_method'] = methodOverride;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
@ -1,14 +1,24 @@
|
||||
(function(){
|
||||
function maybeRemoveMe(elt) {
|
||||
var timing = elt.getAttribute("remove-me") || elt.getAttribute("data-remove-me");
|
||||
if (timing) {
|
||||
setTimeout(function () {
|
||||
elt.parentElement.removeChild(elt);
|
||||
}, htmx.parseInterval(timing));
|
||||
}
|
||||
}
|
||||
|
||||
htmx.defineExtension('remove-me', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "processedNode.htmx") {
|
||||
var elt = evt.detail.elt;
|
||||
if (elt.getAttribute) {
|
||||
var timing = elt.getAttribute("remove-me") || elt.getAttribute("data-remove-me");
|
||||
if (timing) {
|
||||
setTimeout(function () {
|
||||
elt.parentElement.removeChild(elt);
|
||||
}, htmx.parseInterval(timing));
|
||||
maybeRemoveMe(elt);
|
||||
if (elt.querySelectorAll) {
|
||||
var children = elt.querySelectorAll("[remove-me], [data-remove-me");
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
maybeRemoveMe(children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
144
src/htmx.js
144
src/htmx.js
@ -12,12 +12,15 @@ return (function () {
|
||||
'use strict';
|
||||
|
||||
var VERBS = ['get', 'post', 'put', 'delete', 'patch'];
|
||||
var VERB_SELECTOR = VERBS.map(function(verb){
|
||||
return "[hx-" + verb + "], [data-hx-" + verb + "]"
|
||||
}).join(", ");
|
||||
|
||||
//====================================================================
|
||||
// Utilities
|
||||
//====================================================================
|
||||
function parseInterval(str) {
|
||||
if (str === "null" || str === "false" || str === "") {
|
||||
if (str == null || str === "null" || str === "false" || str === "") {
|
||||
return null;
|
||||
} else if (str.lastIndexOf("ms") === str.length - 2) {
|
||||
return parseFloat(str.substr(0, str.length - 2));
|
||||
@ -389,20 +392,23 @@ return (function () {
|
||||
|
||||
function handleAttributes(parentNode, fragment, settleInfo) {
|
||||
forEach(fragment.querySelectorAll("[id]"), function (newNode) {
|
||||
var oldNode = parentNode.querySelector(newNode.tagName + "[id=" + newNode.id + "]")
|
||||
if (oldNode && oldNode !== parentNode) {
|
||||
var newAttributes = newNode.cloneNode();
|
||||
cloneAttributes(newNode, oldNode);
|
||||
settleInfo.tasks.push(function () {
|
||||
cloneAttributes(newNode, newAttributes);
|
||||
});
|
||||
if (newNode.id && newNode.id.length > 0) {
|
||||
var oldNode = parentNode.querySelector(newNode.tagName + "[id=" + newNode.id + "]");
|
||||
if (oldNode && oldNode !== parentNode) {
|
||||
var newAttributes = newNode.cloneNode();
|
||||
cloneAttributes(newNode, oldNode);
|
||||
settleInfo.tasks.push(function () {
|
||||
cloneAttributes(newNode, newAttributes);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function makeLoadTask(child) {
|
||||
function makeAjaxLoadTask(child) {
|
||||
return function () {
|
||||
processNode(child);
|
||||
processScripts(child);
|
||||
triggerEvent(child, 'load.htmx', {});
|
||||
};
|
||||
}
|
||||
@ -413,7 +419,7 @@ return (function () {
|
||||
var child = fragment.firstChild;
|
||||
parentNode.insertBefore(child, insertBefore);
|
||||
if (child.nodeType !== Node.TEXT_NODE) {
|
||||
settleInfo.tasks.push(makeLoadTask(child));
|
||||
settleInfo.tasks.push(makeAjaxLoadTask(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -492,6 +498,8 @@ return (function () {
|
||||
|
||||
function swap(swapStyle, elt, target, fragment, settleInfo) {
|
||||
switch (swapStyle) {
|
||||
case "none":
|
||||
return;
|
||||
case "outerHTML":
|
||||
swapOuterHTML(target, fragment, settleInfo);
|
||||
return;
|
||||
@ -578,6 +586,9 @@ return (function () {
|
||||
if (token.indexOf("delay:") === 0) {
|
||||
triggerSpec.delay = parseInterval(token.substr(6));
|
||||
}
|
||||
if (token.indexOf("throttle:") === 0) {
|
||||
triggerSpec.throttle = parseInterval(token.substr(9));
|
||||
}
|
||||
}
|
||||
return triggerSpec;
|
||||
}).filter(function(x){ return x !== null });
|
||||
@ -670,13 +681,21 @@ return (function () {
|
||||
if (elementData.delayed) {
|
||||
clearTimeout(elementData.delayed);
|
||||
}
|
||||
var issueRequest = function(){
|
||||
issueAjaxRequest(elt, verb, path, evt.target);
|
||||
if (elementData.throttle) {
|
||||
return;
|
||||
}
|
||||
if (triggerSpec.delay) {
|
||||
elementData.delayed = setTimeout(issueRequest, triggerSpec.delay);
|
||||
|
||||
if (triggerSpec.throttle) {
|
||||
elementData.throttle = setTimeout(function(){
|
||||
issueAjaxRequest(elt, verb, path, evt.target);
|
||||
elementData.throttle = null;
|
||||
}, triggerSpec.throttle);
|
||||
} else if (triggerSpec.delay) {
|
||||
elementData.delayed = setTimeout(function(){
|
||||
issueAjaxRequest(elt, verb, path, evt.target);
|
||||
}, triggerSpec.delay);
|
||||
} else {
|
||||
issueRequest();
|
||||
issueAjaxRequest(elt, verb, path, evt.target);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -865,21 +884,50 @@ return (function () {
|
||||
return explicitAction;
|
||||
}
|
||||
|
||||
function processScript(elt) {
|
||||
if (elt.tagName === "SCRIPT" && elt.type === "text/javascript") {
|
||||
function evalScript(script) {
|
||||
if (script.type === "text/javascript") {
|
||||
try {
|
||||
eval(elt.innerText);
|
||||
} catch(e) {
|
||||
eval(script.innerText);
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processNode(elt) {
|
||||
function processScripts(elt) {
|
||||
if (matches(elt, "script")) {
|
||||
evalScript(elt);
|
||||
}
|
||||
forEach(findAll(elt, "script"), function (script) {
|
||||
evalScript(script);
|
||||
});
|
||||
}
|
||||
|
||||
function isHyperScriptAvailable() {
|
||||
return typeof _hyperscript !== "undefined";
|
||||
}
|
||||
|
||||
function findElementsToProcess(elt) {
|
||||
if (elt.querySelectorAll) {
|
||||
return elt.querySelectorAll(VERB_SELECTOR + ", a, form, [hx-sse], [data-hx-sse], [hx-ws], [data-hx-ws]");
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function processNode(elt, ignoreChildren) {
|
||||
var nodeData = getInternalData(elt);
|
||||
if (!nodeData.processed) {
|
||||
nodeData.processed = true;
|
||||
|
||||
if(isHyperScriptAvailable()){
|
||||
_hyperscript.init(elt);
|
||||
}
|
||||
|
||||
if (elt.value) {
|
||||
nodeData.lastValue = elt.value;
|
||||
}
|
||||
|
||||
var triggerSpecs = getTriggerSpecs(elt);
|
||||
var explicitAction = processVerbs(elt, nodeData, triggerSpecs);
|
||||
|
||||
@ -897,11 +945,9 @@ return (function () {
|
||||
processWebSocketInfo(elt, nodeData, wsInfo);
|
||||
}
|
||||
triggerEvent(elt, "processedNode.htmx");
|
||||
|
||||
processScript(elt);
|
||||
}
|
||||
if (elt.children) { // IE
|
||||
forEach(elt.children, function(child) { processNode(child) });
|
||||
if (!ignoreChildren) {
|
||||
forEach(findElementsToProcess(elt), function(child) { processNode(child, true) });
|
||||
}
|
||||
}
|
||||
|
||||
@ -909,16 +955,6 @@ return (function () {
|
||||
// Event/Log Support
|
||||
//====================================================================
|
||||
|
||||
function sendError(elt, eventName, detail) {
|
||||
var errorURL = getClosestAttributeValue(elt, "hx-error-url");
|
||||
if (errorURL) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", errorURL);
|
||||
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
xhr.send(JSON.stringify({ "elt": elt.id, "event": eventName, "detail" : detail }));
|
||||
}
|
||||
}
|
||||
|
||||
function makeEvent(eventName, detail) {
|
||||
var evt;
|
||||
if (window.CustomEvent && typeof window.CustomEvent === 'function') {
|
||||
@ -967,7 +1003,7 @@ return (function () {
|
||||
}
|
||||
if (detail.error) {
|
||||
logError(detail.error);
|
||||
sendError(elt, eventName, detail);
|
||||
triggerEvent(elt, "error.htmx", {errorInfo:detail})
|
||||
}
|
||||
var eventResult = elt.dispatchEvent(event);
|
||||
withExtensions(elt, function (extension) {
|
||||
@ -1338,9 +1374,6 @@ return (function () {
|
||||
|
||||
if (verb !== 'get') {
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||
if (verb !== 'post') {
|
||||
headers['X-HTTP-Method-Override'] = verb.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
// behavior of anchors w/ empty href is to use the current URL
|
||||
@ -1357,7 +1390,11 @@ return (function () {
|
||||
path:path
|
||||
};
|
||||
if(!triggerEvent(elt, 'configRequest.htmx', requestConfig)) return endRequestLock();
|
||||
// copy out in case the object was overwritten
|
||||
path = requestConfig.path;
|
||||
verb = requestConfig.verb;
|
||||
headers = requestConfig.headers;
|
||||
filteredParameters = requestConfig.parameters;
|
||||
|
||||
var splitPath = path.split("#");
|
||||
var pathNoAnchor = splitPath[0];
|
||||
@ -1378,7 +1415,7 @@ return (function () {
|
||||
}
|
||||
xhr.open('GET', finalPathForGet, true);
|
||||
} else {
|
||||
xhr.open('POST', path, true);
|
||||
xhr.open(verb.toUpperCase(), path, true);
|
||||
}
|
||||
|
||||
xhr.overrideMimeType("text/html");
|
||||
@ -1406,7 +1443,7 @@ return (function () {
|
||||
}
|
||||
// don't process 'No Content' response
|
||||
if (this.status !== 204) {
|
||||
if (!triggerEvent(elt, 'beforeSwap.htmx', eventDetail)) return;
|
||||
if (!triggerEvent(target, 'beforeSwap.htmx', eventDetail)) return;
|
||||
|
||||
var resp = this.response;
|
||||
withExtensions(elt, function(extension){
|
||||
@ -1423,15 +1460,32 @@ return (function () {
|
||||
target.classList.add("htmx-swapping");
|
||||
var doSwap = function () {
|
||||
try {
|
||||
|
||||
var activeElt = document.activeElement;
|
||||
var selectionInfo = {
|
||||
elt: activeElt,
|
||||
start: activeElt.selectionStart,
|
||||
end: activeElt.selectionEnd,
|
||||
};
|
||||
|
||||
var settleInfo = makeSettleInfo(target);
|
||||
selectAndSwap(swapSpec.swapStyle, target, elt, resp, settleInfo);
|
||||
|
||||
if (!bodyContains(selectionInfo.elt) && selectionInfo.elt.id) {
|
||||
var newActiveElt = document.getElementById(selectionInfo.elt.id);
|
||||
if (selectionInfo.start && newActiveElt.setSelectionRange) {
|
||||
newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end);
|
||||
}
|
||||
newActiveElt.focus();
|
||||
}
|
||||
|
||||
target.classList.remove("htmx-swapping");
|
||||
forEach(settleInfo.elts, function (elt) {
|
||||
if (elt.classList) {
|
||||
elt.classList.add("htmx-settling");
|
||||
}
|
||||
triggerEvent(elt, 'afterSwap.htmx', eventDetail);
|
||||
});
|
||||
triggerEvent(elt, 'afterSwap.htmx', eventDetail);
|
||||
if (anchor) {
|
||||
location.hash = anchor;
|
||||
}
|
||||
@ -1443,12 +1497,14 @@ return (function () {
|
||||
if (elt.classList) {
|
||||
elt.classList.remove("htmx-settling");
|
||||
}
|
||||
triggerEvent(elt, 'afterSettle.htmx', eventDetail);
|
||||
});
|
||||
// push URL and save new page
|
||||
if (shouldSaveHistory) {
|
||||
pushUrlIntoHistory(pushedUrl || path);
|
||||
var pathToPush = pushedUrl || finalPathForGet || path;
|
||||
pushUrlIntoHistory(pathToPush);
|
||||
triggerEvent(getDocument().body, 'pushedIntoHistory.htmx', {path:pathToPush});
|
||||
}
|
||||
triggerEvent(elt, 'afterSettle.htmx', eventDetail);
|
||||
}
|
||||
|
||||
if (swapSpec.settleDelay > 0) {
|
||||
@ -1579,7 +1635,7 @@ return (function () {
|
||||
ready(function () {
|
||||
mergeMetaConfig();
|
||||
var body = getDocument().body;
|
||||
processNode(body);
|
||||
processNode(body, true);
|
||||
triggerEvent(body, 'load.htmx', {});
|
||||
window.onpopstate = function () {
|
||||
restoreHistory();
|
||||
|
@ -8,10 +8,9 @@ describe("hx-delete attribute", function(){
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a DELETE request with proper headers', function()
|
||||
it('issues a DELETE request', function()
|
||||
{
|
||||
this.server.respondWith("DELETE", "/test", function(xhr){
|
||||
xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('DELETE');
|
||||
xhr.respond(200, {}, "Deleted!");
|
||||
});
|
||||
|
||||
@ -21,10 +20,9 @@ describe("hx-delete attribute", function(){
|
||||
btn.innerHTML.should.equal("Deleted!");
|
||||
});
|
||||
|
||||
it('issues a DELETE request with proper headers w/ data-* prefix', function()
|
||||
it('issues a DELETE request w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("DELETE", "/test", function(xhr){
|
||||
xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('DELETE');
|
||||
xhr.respond(200, {}, "Deleted!");
|
||||
});
|
||||
|
||||
|
@ -1,32 +0,0 @@
|
||||
describe("hx-error-url attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('Submits a POST with error content on bad request', function()
|
||||
{
|
||||
this.server.respondWith("POST", "/error", function(xhr){
|
||||
should.equal(JSON.parse(xhr.requestBody).detail.xhr.status, 404);
|
||||
});
|
||||
var btn = make('<button hx-error-url="/error" hx-get="/bad">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
this.server.respond();
|
||||
});
|
||||
|
||||
it('Submits a POST with error content on bad request w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("POST", "/error", function(xhr){
|
||||
should.equal(JSON.parse(xhr.requestBody).detail.xhr.status, 404);
|
||||
});
|
||||
var btn = make('<button data-hx-error-url="/error" hx-get="/bad">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
this.server.respond();
|
||||
});
|
||||
})
|
@ -8,10 +8,9 @@ describe("hx-patch attribute", function(){
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a PATCH request with proper headers', function()
|
||||
it('issues a PATCH request', function()
|
||||
{
|
||||
this.server.respondWith("PATCH", "/test", function(xhr){
|
||||
xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('PATCH');
|
||||
xhr.respond(200, {}, "Patched!");
|
||||
});
|
||||
|
||||
@ -21,10 +20,9 @@ describe("hx-patch attribute", function(){
|
||||
btn.innerHTML.should.equal("Patched!");
|
||||
});
|
||||
|
||||
it('issues a PATCH request with proper headers w/ data-* prefix', function()
|
||||
it('issues a PATCH request w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("PATCH", "/test", function(xhr){
|
||||
xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('PATCH');
|
||||
xhr.respond(200, {}, "Patched!");
|
||||
});
|
||||
|
||||
|
@ -1,15 +1,16 @@
|
||||
describe("hx-push-url attribute", function() {
|
||||
|
||||
var KUTTY_HISTORY_CACHE = "htmx-history-cache";
|
||||
var HTMX_HISTORY_CACHE_NAME = "htmx-history-cache";
|
||||
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
localStorage.removeItem(KUTTY_HISTORY_CACHE);
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME);
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
localStorage.removeItem(KUTTY_HISTORY_CACHE);
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME);
|
||||
});
|
||||
|
||||
it("navigation should push an element into the cache ", function () {
|
||||
@ -19,7 +20,7 @@ describe("hx-push-url attribute", function() {
|
||||
div.click();
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("second")
|
||||
var cache = JSON.parse(localStorage.getItem(KUTTY_HISTORY_CACHE));
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
cache.length.should.equal(1);
|
||||
});
|
||||
|
||||
@ -38,7 +39,7 @@ describe("hx-push-url attribute", function() {
|
||||
this.server.respond();
|
||||
workArea.textContent.should.equal("test2")
|
||||
|
||||
var cache = JSON.parse(localStorage.getItem(KUTTY_HISTORY_CACHE));
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
|
||||
cache.length.should.equal(2);
|
||||
htmx._('restoreHistory')("/test1")
|
||||
@ -58,7 +59,7 @@ describe("hx-push-url attribute", function() {
|
||||
byId("d1").click();
|
||||
this.server.respond();
|
||||
}
|
||||
var cache = JSON.parse(localStorage.getItem(KUTTY_HISTORY_CACHE));
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
cache.length.should.equal(10); // should only be 10 elements
|
||||
});
|
||||
|
||||
@ -77,49 +78,15 @@ describe("hx-push-url attribute", function() {
|
||||
this.server.respond();
|
||||
workArea.textContent.should.equal("test2")
|
||||
|
||||
var cache = JSON.parse(localStorage.getItem(KUTTY_HISTORY_CACHE));
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
|
||||
cache.length.should.equal(2);
|
||||
localStorage.removeItem(KUTTY_HISTORY_CACHE); // clear cache
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME); // clear cache
|
||||
htmx._('restoreHistory')("/test1")
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("test1")
|
||||
});
|
||||
|
||||
function stringRepeat(str, num) {
|
||||
num = Number(num);
|
||||
|
||||
var result = '';
|
||||
while (true) {
|
||||
if (num & 1) { // (1)
|
||||
result += str;
|
||||
}
|
||||
num >>>= 1; // (2)
|
||||
if (num <= 0) break;
|
||||
str += str;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
it("implementation details should be fast", function(){
|
||||
// create an entry with a large content string (256k) and see how fast we can write and read it
|
||||
// to local storage as a single entry
|
||||
var entry = {url: stringRepeat("x", 32), content:stringRepeat("x", 256*1024)}
|
||||
var array = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
array.push(entry);
|
||||
}
|
||||
var start = performance.now();
|
||||
var string = JSON.stringify(array);
|
||||
localStorage.setItem(KUTTY_HISTORY_CACHE, string);
|
||||
var reReadString = localStorage.getItem(KUTTY_HISTORY_CACHE);
|
||||
var finalJson = JSON.parse(reReadString);
|
||||
var end = performance.now();
|
||||
var timeInMs = end - start;
|
||||
chai.assert(timeInMs < 300, "Should take less than 300ms on most platforms");
|
||||
})
|
||||
|
||||
it("navigation should push an element into the cache w/ data-* prefix", function () {
|
||||
this.server.respondWith("GET", "/test", "second");
|
||||
getWorkArea().innerHTML.should.be.equal("");
|
||||
@ -127,21 +94,59 @@ describe("hx-push-url attribute", function() {
|
||||
div.click();
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("second")
|
||||
var cache = JSON.parse(localStorage.getItem(KUTTY_HISTORY_CACHE));
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
cache.length.should.equal(1);
|
||||
});
|
||||
|
||||
it("deals with malformed JSON in history cache when getting", function () {
|
||||
localStorage.setItem(KUTTY_HISTORY_CACHE, "Invalid JSON");
|
||||
localStorage.setItem(HTMX_HISTORY_CACHE_NAME, "Invalid JSON");
|
||||
var history = htmx._('getCachedHistory')('url');
|
||||
should.equal(history, null);
|
||||
});
|
||||
|
||||
it("deals with malformed JSON in history cache when saving", function () {
|
||||
localStorage.setItem(KUTTY_HISTORY_CACHE, "Invalid JSON");
|
||||
localStorage.setItem(HTMX_HISTORY_CACHE_NAME, "Invalid JSON");
|
||||
htmx._('saveToHistoryCache')('url', 'content', 'title', 'scroll');
|
||||
var cache = JSON.parse(localStorage.getItem(KUTTY_HISTORY_CACHE));
|
||||
cache.length.should.equal(1);
|
||||
});
|
||||
|
||||
|
||||
it("afterSettle.htmx is called when replacing outerHTML", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("afterSettle.htmx", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "<button>Bar</button>");
|
||||
});
|
||||
var div = make("<button hx-post='/test' hx-swap='outerHTML'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("afterSettle.htmx", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("should include parameters on a get", function () {
|
||||
var path = "";
|
||||
var handler = htmx.on("pushedIntoHistory.htmx", function (evt) {
|
||||
path = evt.detail.path;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("GET", /test.*/, function (xhr) {
|
||||
xhr.respond(200, {}, "second")
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-push-url="true" hx-get="/test"><input type="hidden" name="foo" value="bar"/>first</form>');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.textContent.should.equal("second")
|
||||
path.should.equal("/test?foo=bar")
|
||||
} finally {
|
||||
htmx.off("pushedIntoHistory.htmx", handler);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -8,10 +8,9 @@ describe("hx-put attribute", function(){
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a PUT request with proper headers', function()
|
||||
it('issues a PUT request', function()
|
||||
{
|
||||
this.server.respondWith("PUT", "/test", function(xhr){
|
||||
xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('PUT');
|
||||
xhr.respond(200, {}, "Putted!");
|
||||
});
|
||||
|
||||
@ -21,10 +20,9 @@ describe("hx-put attribute", function(){
|
||||
btn.innerHTML.should.equal("Putted!");
|
||||
});
|
||||
|
||||
it('issues a PUT request with proper headers', function()
|
||||
it('issues a PUT request w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("PUT", "/test", function(xhr){
|
||||
xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('PUT');
|
||||
xhr.respond(200, {}, "Putted!");
|
||||
});
|
||||
|
||||
|
@ -246,7 +246,6 @@ describe("hx-swap attribute", function(){
|
||||
}, 30);
|
||||
});
|
||||
|
||||
|
||||
it('swap outerHTML properly w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", '<a id="a1" data-hx-get="/test2">Click Me</a>');
|
||||
@ -262,5 +261,14 @@ describe("hx-swap attribute", function(){
|
||||
byId("a1").innerHTML.should.equal('Clicked!');
|
||||
});
|
||||
|
||||
it('swap none works properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", 'Ooops, swapped');
|
||||
|
||||
var div = make('<div hx-swap="none" hx-get="/test">Foo</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal('Foo');
|
||||
});
|
||||
|
||||
})
|
||||
|
@ -30,17 +30,17 @@ describe("hx-trigger attribute", function(){
|
||||
var div = make('<div id="d1"></div>');
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
div.innerHTML.should.equal("");
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
div.innerHTML.should.equal("");
|
||||
input.value = "bar";
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 2");
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 2");
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
});
|
||||
|
||||
it('once modifier works', function()
|
||||
@ -113,32 +113,35 @@ describe("hx-trigger attribute", function(){
|
||||
div.innerHTML.should.equal("Requests: 2");
|
||||
});
|
||||
|
||||
var specExamples = {
|
||||
"": [{trigger: 'click'}],
|
||||
"every 1s": [{trigger: 'every', pollInterval: 1000}],
|
||||
"click": [{trigger: 'click'}],
|
||||
"customEvent": [{trigger: 'customEvent'}],
|
||||
"event changed": [{trigger: 'event', changed: true}],
|
||||
"event once": [{trigger: 'event', once: true}],
|
||||
"event delay:1s": [{trigger: 'event', delay: 1000}],
|
||||
"event changed once delay:1s": [{trigger: 'event', changed: true, once: true, delay: 1000}],
|
||||
"event1,event2": [{trigger: 'event1'}, {trigger: 'event2'}],
|
||||
"event1, event2": [{trigger: 'event1'}, {trigger: 'event2'}],
|
||||
"event1 once, event2 changed": [{trigger: 'event1', once: true}, {trigger: 'event2', changed: true}],
|
||||
"event1,": [{trigger: 'event1'}],
|
||||
",event1": [{trigger: 'event1'}],
|
||||
" ": [{trigger: 'click'}],
|
||||
",": [{trigger: 'click'}]
|
||||
}
|
||||
|
||||
for (var specString in specExamples) {
|
||||
it("parses " + specString, function()
|
||||
{
|
||||
var div = make("<div hx-trigger=" + specString + "></div>");
|
||||
|
||||
it("parses spec strings", function()
|
||||
{
|
||||
var specExamples = {
|
||||
"": [{trigger: 'click'}],
|
||||
"every 1s": [{trigger: 'every', pollInterval: 1000}],
|
||||
"click": [{trigger: 'click'}],
|
||||
"customEvent": [{trigger: 'customEvent'}],
|
||||
"event changed": [{trigger: 'event', changed: true}],
|
||||
"event once": [{trigger: 'event', once: true}],
|
||||
"event delay:1s": [{trigger: 'event', delay: 1000}],
|
||||
"event throttle:1s": [{trigger: 'event', throttle: 1000}],
|
||||
"event changed once delay:1s": [{trigger: 'event', changed: true, once: true, delay: 1000}],
|
||||
"event1,event2": [{trigger: 'event1'}, {trigger: 'event2'}],
|
||||
"event1, event2": [{trigger: 'event1'}, {trigger: 'event2'}],
|
||||
"event1 once, event2 changed": [{trigger: 'event1', once: true}, {trigger: 'event2', changed: true}],
|
||||
"event1,": [{trigger: 'event1'}],
|
||||
",event1": [{trigger: 'event1'}],
|
||||
" ": [{trigger: 'click'}],
|
||||
",": [{trigger: 'click'}]
|
||||
}
|
||||
|
||||
for (var specString in specExamples) {
|
||||
var div = make("<div hx-trigger='" + specString + "'></div>");
|
||||
var spec = htmx._('getTriggerSpecs')(div);
|
||||
spec.should.deep.equal(specExamples[specString]);
|
||||
});
|
||||
}
|
||||
spec.should.deep.equal(specExamples[specString], "Found : " + JSON.stringify(spec) + ", expected : " + JSON.stringify(specExamples[specString]) + " for spec: " + specString);
|
||||
}
|
||||
});
|
||||
|
||||
it('sets default trigger for forms', function()
|
||||
{
|
||||
|
@ -82,5 +82,41 @@ describe("Core htmx Events", function() {
|
||||
}
|
||||
});
|
||||
|
||||
it("afterSwap.htmx is called when replacing outerHTML", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("afterSwap.htmx", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "<button>Bar</button>");
|
||||
});
|
||||
var div = make("<button hx-post='/test' hx-swap='outerHTML'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("afterSwap.htmx", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("afterSettle.htmx is called when replacing outerHTML", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("afterSettle.htmx", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "<button>Bar</button>");
|
||||
});
|
||||
var div = make("<button hx-post='/test' hx-swap='outerHTML'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("afterSettle.htmx", handler);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
70
test/core/perf.js
Normal file
70
test/core/perf.js
Normal file
@ -0,0 +1,70 @@
|
||||
describe("Core htmx perf Tests", function() {
|
||||
|
||||
var HTMX_HISTORY_CACHE_NAME = "htmx-history-cache";
|
||||
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME);
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME);
|
||||
});
|
||||
|
||||
function stringRepeat(str, num) {
|
||||
num = Number(num);
|
||||
|
||||
var result = '';
|
||||
while (true) {
|
||||
if (num & 1) { // (1)
|
||||
result += str;
|
||||
}
|
||||
num >>>= 1; // (2)
|
||||
if (num <= 0) break;
|
||||
str += str;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
it("DOM processing should be fast", function(){
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
// create an entry with a large content string (256k) and see how fast we can write and read it
|
||||
// to local storage as a single entry
|
||||
var str = stringRepeat("<div>", 30) + stringRepeat("<div><div><span><button hx-get='/test'> Test Get Button </button></span></div></div>\n", 1000) + stringRepeat("</div>", 30);
|
||||
var start = performance.now();
|
||||
var stuff = make(str);
|
||||
var end = performance.now();
|
||||
var timeInMs = end - start;
|
||||
|
||||
// make sure the DOM actually processed
|
||||
var firstBtn = stuff.querySelector("button");
|
||||
firstBtn.click();
|
||||
this.server.respond();
|
||||
firstBtn.innerHTML.should.equal("Clicked!");
|
||||
|
||||
chai.assert(timeInMs < 100, "Should take less than 100ms on most platforms, took: " + timeInMs + "ms");
|
||||
})
|
||||
|
||||
it("history implementation should be fast", function(){
|
||||
// create an entry with a large content string (256k) and see how fast we can write and read it
|
||||
// to local storage as a single entry
|
||||
var entry = {url: stringRepeat("x", 32), content:stringRepeat("x", 256*1024)}
|
||||
var array = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
array.push(entry);
|
||||
}
|
||||
var start = performance.now();
|
||||
var string = JSON.stringify(array);
|
||||
localStorage.setItem(HTMX_HISTORY_CACHE_NAME, string);
|
||||
var reReadString = localStorage.getItem(HTMX_HISTORY_CACHE_NAME);
|
||||
var finalJson = JSON.parse(reReadString);
|
||||
var end = performance.now();
|
||||
var timeInMs = end - start;
|
||||
chai.assert(timeInMs < 300, "Should take less than 300ms on most platforms");
|
||||
})
|
||||
|
||||
})
|
@ -49,4 +49,20 @@ describe("Core htmx Regression Tests", function(){
|
||||
form.innerHTML.should.equal("variable=")
|
||||
});
|
||||
|
||||
it ('name=id doesnt cause an error', function(){
|
||||
this.server.respondWith("GET", "/test", "Foo<form><input name=\"id\"/></form>")
|
||||
var div = make('<div hx-get="/test">Get It</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.contain("Foo")
|
||||
});
|
||||
|
||||
it ('empty id doesnt cause an error', function(){
|
||||
this.server.respondWith("GET", "/test", "Foo\n<div id=''></div>")
|
||||
var div = make('<div hx-get="/test">Get It</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.contain("Foo")
|
||||
});
|
||||
|
||||
})
|
||||
|
21
test/ext/ajax-header.js
Normal file
21
test/ext/ajax-header.js
Normal file
@ -0,0 +1,21 @@
|
||||
describe("ajax-header extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('Sends the X-Requested-With header', function () {
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, xhr.requestHeaders['X-Requested-With'])
|
||||
});
|
||||
var btn = make('<button hx-get="/test" hx-ext="ajax-header">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("XMLHttpRequest");
|
||||
});
|
||||
|
||||
});
|
37
test/ext/hyperscript.js
Normal file
37
test/ext/hyperscript.js
Normal file
@ -0,0 +1,37 @@
|
||||
describe("hyperscript integration", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('can trigger with a custom event', function () {
|
||||
this.server.respondWith("GET", "/test", "Custom Event Sent!");
|
||||
var btn = make('<button _="on click send customEvent" hx-trigger="customEvent" hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Custom Event Sent!");
|
||||
});
|
||||
|
||||
it('can handle htmx driven events', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button _="on afterSettle.htmx add .afterSettle" hx-get="/test">Click Me!</button>')
|
||||
btn.classList.contains("afterSettle").should.equal(false);
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.classList.contains("afterSettle").should.equal(true);
|
||||
});
|
||||
|
||||
it('can handle htmx error events', function () {
|
||||
this.server.respondWith("GET", "/test", [404, {}, "Bad request"]);
|
||||
var div = make('<div id="d1"></div>')
|
||||
var btn = make('<button _="on error.htmx(errorInfo) put errorInfo.error into #d1.innerHTML" hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Response Status Error Code 404 from /test");
|
||||
});
|
||||
|
||||
});
|
53
test/ext/method-override.js
Normal file
53
test/ext/method-override.js
Normal file
@ -0,0 +1,53 @@
|
||||
describe("method-override extension", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a DELETE request with proper headers', function()
|
||||
{
|
||||
this.server.respondWith("DELETE", "/test", function(xhr){
|
||||
xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('DELETE');
|
||||
xhr.method.should.equal("POST")
|
||||
xhr.respond(200, {}, "Deleted!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-ext="method-override" hx-delete="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Deleted!");
|
||||
});
|
||||
|
||||
it('issues a PATCH request with proper headers', function()
|
||||
{
|
||||
this.server.respondWith("PATCH", "/test", function(xhr){
|
||||
xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('PATCH');
|
||||
xhr.method.should.equal("POST")
|
||||
xhr.respond(200, {}, "Patched!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-ext="method-override" hx-patch="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Patched!");
|
||||
});
|
||||
|
||||
it('issues a PUT request with proper headers', function()
|
||||
{
|
||||
this.server.respondWith("PUT", "/test", function(xhr){
|
||||
xhr.requestHeaders['X-HTTP-Method-Override'].should.equal('PUT');
|
||||
xhr.method.should.equal("POST")
|
||||
xhr.respond(200, {}, "Putted!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-ext="method-override" hx-put="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Putted!");
|
||||
});
|
||||
|
||||
})
|
@ -1,61 +0,0 @@
|
||||
describe("rails-method extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('Does not affect a GET request', function () {
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, xhr.url)
|
||||
});
|
||||
var btn = make('<button hx-get="/test" hx-ext="rails-method">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("/test");
|
||||
});
|
||||
|
||||
it('Does not affect a POST request', function () {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, getParameters(xhr)['_method']);
|
||||
});
|
||||
var btn = make('<button hx-post="/test" hx-ext="rails-method">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("");
|
||||
});
|
||||
|
||||
it('Adds proper _method param to PUT request', function () {
|
||||
this.server.respondWith("PUT", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, getParameters(xhr)['_method']);
|
||||
});
|
||||
var btn = make('<button hx-put="/test" hx-ext="rails-method">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("PUT");
|
||||
});
|
||||
|
||||
it('Adds proper _method param to PATCH request', function () {
|
||||
this.server.respondWith("PATCH", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, getParameters(xhr)['_method']);
|
||||
});
|
||||
var btn = make('<button hx-patch="/test" hx-ext="rails-method">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("PATCH");
|
||||
});
|
||||
|
||||
it('Adds proper _method param to DELETE request', function () {
|
||||
this.server.respondWith("DELETE", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, getParameters(xhr)['_method']);
|
||||
});
|
||||
var btn = make('<button hx-delete="/test" hx-ext="rails-method">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("DELETE");
|
||||
});
|
||||
|
||||
});
|
@ -18,35 +18,23 @@ describe("remove-me extension", function(){
|
||||
}, 40);
|
||||
});
|
||||
|
||||
it('removes classes properly', function(done)
|
||||
{
|
||||
var div = make('<div class="foo bar" hx-ext="class-tools" classes="remove bar">Click Me!</div>')
|
||||
should.equal(div.classList.contains("foo"), true);
|
||||
should.equal(div.classList.contains("bar"), true);
|
||||
setTimeout(function(){
|
||||
should.equal(div.classList.contains("foo"), true);
|
||||
should.equal(div.classList.contains("bar"), false);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('adds classes properly w/ data-* prefix', function(done)
|
||||
it('removes properly w/ data-* prefix', function(done)
|
||||
{
|
||||
var div = make('<div hx-ext="class-tools" data-classes="add c1">Click Me!</div>')
|
||||
var div = make('<div hx-ext="remove-me" data-remove-me="20ms">Click Me!</div>')
|
||||
should.equal(div.classList.length, 0);
|
||||
setTimeout(function(){
|
||||
should.equal(div.classList.contains("c1"), true);
|
||||
should.equal(div.parentElement, null);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('extension can be on parent', function(done)
|
||||
{
|
||||
var div = make('<div hx-ext="class-tools"><div id="d1" classes="add c1">Click Me!</div></div>')
|
||||
var div = make('<div hx-ext="remove-me"><div id="d1" remove-me="20ms">Click Me!</div></div>')
|
||||
should.equal(div.classList.length, 0);
|
||||
setTimeout(function(){
|
||||
should.equal(div.classList.contains("c1"), false);
|
||||
should.equal(byId("d1").classList.contains("c1"), true);
|
||||
should.equal(byId("d1"), null);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
@ -60,11 +60,11 @@
|
||||
<script src="core/parameters.js"></script>
|
||||
<script src="core/headers.js"></script>
|
||||
<script src="core/regressions.js"></script>
|
||||
<script src="core/perf.js"></script>
|
||||
|
||||
<!-- attribute tests -->
|
||||
<script src="attributes/hx-boost.js"></script>
|
||||
<script src="attributes/hx-delete.js"></script>
|
||||
<script src="attributes/hx-error-url.js"></script>
|
||||
<script src="attributes/hx-ext.js"></script>
|
||||
<script src="attributes/hx-get.js"></script>
|
||||
<script src="attributes/hx-include.js"></script>
|
||||
@ -82,9 +82,16 @@
|
||||
<script src="attributes/hx-trigger.js"></script>
|
||||
<script src="attributes/hx-ws.js"></script>
|
||||
|
||||
<!-- hyperscript integration -->
|
||||
<script src="lib/_hyperscript.js"></script>
|
||||
<script src="ext/hyperscript.js"></script>
|
||||
<script>
|
||||
_hyperscript.start();
|
||||
</script>
|
||||
|
||||
<!-- extension tests -->
|
||||
<script src="../src/ext/rails-method.js"></script>
|
||||
<script src="ext/rails-method.js"></script>
|
||||
<script src="../src/ext/method-override.js"></script>
|
||||
<script src="ext/method-override.js"></script>
|
||||
|
||||
<script src="../src/ext/debug.js"></script>
|
||||
<script src="ext/debug.js"></script>
|
||||
@ -115,6 +122,8 @@
|
||||
<script src="../src/ext/include-vals.js"></script>
|
||||
<script src="ext/include-vals.js"></script>
|
||||
|
||||
<script src="../src/ext/ajax-header.js"></script>
|
||||
<script src="ext/ajax-header.js"></script>
|
||||
|
||||
<!-- events last so they don't screw up other tests -->
|
||||
<script src="core/events.js"></script>
|
||||
|
1603
test/lib/_hyperscript.js
Normal file
1603
test/lib/_hyperscript.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -29,8 +29,13 @@
|
||||
//
|
||||
// make('<div hx-get="/test">dd</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>');
|
||||
htmx.logAll();
|
||||
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
xhr.respond(201, {}, '<form><input hx-trigger="keyup delay:1s changed" hx-swap="outerHTML" hx-get="/test" id="i1" value="blahblah"/></form>')
|
||||
});
|
||||
|
||||
make('<form hx-target="this"><input hx-trigger="keyup delay:1s changed" hx-swap="outerHTML" hx-get="/test" id="i1"/></form>');
|
||||
</script>
|
||||
|
||||
|
||||
|
@ -5,5 +5,6 @@ module.exports = function(config) {
|
||||
config.addPassthroughCopy("css");
|
||||
config.addPassthroughCopy("img");
|
||||
config.addPassthroughCopy("test");
|
||||
config.addPassthroughCopy("_redirects");
|
||||
config.addPlugin(pluginSass, {});
|
||||
}
|
||||
|
1
www/_redirects
Normal file
1
www/_redirects
Normal file
@ -0,0 +1 @@
|
||||
/locality-of-behaviour /essays/locality-of-behaviour
|
@ -1,24 +0,0 @@
|
||||
---
|
||||
layout: layout.njk
|
||||
title: </> htmx - hx-error-url
|
||||
---
|
||||
|
||||
## `hx-error-url`
|
||||
|
||||
The `hx-error-url` attribute allows you to send client-side errors to a specified URL. It is typically put on the
|
||||
body tag, so all errors are caught and send to the server.
|
||||
|
||||
```html
|
||||
<body hx-error-url="/errors">\
|
||||
|
||||
</body>
|
||||
```
|
||||
When a client side error is caught by htmx it will be `POST`-ed to the given URL, with the following JSON format:
|
||||
|
||||
```json
|
||||
{ "elt": elt.id, "event": eventName, "detail" : detail }
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
* `hx-error-url` is inherited and can be placed on a parent element
|
@ -18,7 +18,7 @@ Here is an example:
|
||||
|
||||
```html
|
||||
<div hx-sse="connect /event_stream">
|
||||
<div hx-get="/chatroom" hx-trigger="chatter">
|
||||
<div hx-get="/chatroom" hx-trigger="sse:chatter">
|
||||
...
|
||||
</div>
|
||||
</div>
|
||||
|
98
www/docs.md
98
www/docs.md
@ -27,6 +27,7 @@ title: </> htmx - high power tools for html
|
||||
* [animations](#animations)
|
||||
* [extensions](#extensions)
|
||||
* [events & logging](#events)
|
||||
* [hyperscript](#hyperscript)
|
||||
* [configuring](#config)
|
||||
|
||||
</div>
|
||||
@ -157,8 +158,11 @@ There are few other modifiers you can use for trigger:
|
||||
* `changed` - only issue a request if the value of the element has changed
|
||||
* `delay:<time interval>` - wait the given amount of time (e.g. `1s`) before
|
||||
issuing the request. If the event triggers again, the countdown is reset.
|
||||
* `throttle:<time interval>` - wait the given amount of time (e.g. `1s`) before
|
||||
issuing the request. Unlike `delay` if a new event occurs before the time limit is hit the event will be discarded,
|
||||
so the request will trigger at the end of the time period.
|
||||
|
||||
You can use these two attributes to implement a common UX pattern, [Active Search](/examples/active-search):
|
||||
You can use these attributes to implement many common UX patterns, such as [Active Search](/examples/active-search):
|
||||
|
||||
```html
|
||||
<input type="text" name="q"
|
||||
@ -302,6 +306,7 @@ with any of the following values:
|
||||
| `beforebegin` | prepends the content before the target in the targets parent element
|
||||
| `beforeend` | appends the content after the last child inside the target
|
||||
| `afterend` | appends the content after the target in the targets parent element
|
||||
| `none` | does not append content from respons (out of band items will still be processed)
|
||||
|
||||
#### <a name="oob_swaps"></a>[Out of Band Swaps](#oob_swaps)
|
||||
|
||||
@ -564,8 +569,6 @@ If you set a logger at `htmx.logger`, every event will be logged. This can be v
|
||||
}
|
||||
```
|
||||
|
||||
Htmx can also send errors to a URL that is specified with the [hx-error-url](/attributes/hx-error-url) attributes. This can be useful for debugging client-side issues.
|
||||
|
||||
Htmx includes a helper method:
|
||||
|
||||
```javascript
|
||||
@ -574,6 +577,95 @@ Htmx includes a helper method:
|
||||
|
||||
if you want to log everything while developing.
|
||||
|
||||
## <a name="hyperscript"></a>[hyperscript](#hyperscript)
|
||||
|
||||
**NOTE: hyperscript is in very early alpha**
|
||||
|
||||
Hyperscript is a small scripting language designed to be expressive, making it ideal for embedding directly in HTML,
|
||||
handling custom events, etc. The language is inspired by [HyperTalk](http://hypercard.org/HyperTalk%20Reference%202.4.pdf),
|
||||
javascript, [gosu](https://gosu-lang.github.io/) and others.
|
||||
|
||||
You can explore the language more fully on its main website:
|
||||
|
||||
<http://hyperscript.org>
|
||||
|
||||
### Events & Hyperscript
|
||||
|
||||
Hyperscript was designed to help address features and functionality from intercooler.js that are not implemented in htmx
|
||||
directly, in a more flexible and open manner. One of its prime features is the ability to respond to arbitrary events
|
||||
on a DOM element, using the `on` syntax:
|
||||
|
||||
```html
|
||||
<div _="on afterSettle.htmx log 'Settled!'">
|
||||
...
|
||||
</div>
|
||||
```
|
||||
|
||||
This will log `Settled!` to the console when the `afterSettle.htmx` event is triggered.
|
||||
|
||||
#### intercooler.js features & hyperscript implementations
|
||||
|
||||
Below are some examples of intercooler features and the hyperscript equivalent.
|
||||
|
||||
##### `ic-remove-after`
|
||||
|
||||
Intercooler provided the [`ic-remove-after`](http://intercoolerjs.org/attributes/ic-remove-after.html) attribute
|
||||
for removing an element after a given amount of time.
|
||||
|
||||
In hyperscript this is implemented like so:
|
||||
|
||||
```html
|
||||
<div _="on load wait 5s then remove me">Here is a temporary message!</div>
|
||||
```
|
||||
|
||||
##### `ic-post-errors-to`
|
||||
|
||||
Intercooler provided the [`ic-post-errors-to`](http://intercoolerjs.org/attributes/ic-post-errors-to.html) attribute
|
||||
for posting errors that occured during requests and responses.
|
||||
|
||||
In hyperscript similar functionality is implemented like so:
|
||||
|
||||
```html
|
||||
<body _="on error.htmx(errorInfo) ajax POST errorInfo to /errors">
|
||||
...
|
||||
</body>
|
||||
```
|
||||
|
||||
##### `ic-switch-class`
|
||||
|
||||
Intercooler provided the [`ic-switch-class`](http://intercoolerjs.org/attributes/ic-switch-class.html) attribute, which
|
||||
let you switch a class between siblings.
|
||||
|
||||
In hyperscript you can implement similar functionality like so:
|
||||
|
||||
```html
|
||||
<div hx-target="#content" _="on beforeOnLoad.htmx take .active from .tabs for event.target">
|
||||
<a class="tabs active" hx-get="/tabl1" >Tab 1</a>
|
||||
<a class="tabs" hx-get="/tabl2">Tab 2</a>
|
||||
<a class="tabs" hx-get="/tabl3">Tab 3</a>
|
||||
</div>
|
||||
<div id="content">Tab 1 Content</div>
|
||||
```
|
||||
|
||||
##### `X-IC-Redirect`
|
||||
|
||||
Intercooler provided more response headers than htmx does: `X-IC-Refresh`, `X-IC-Redirect` etc. Htmx omits these
|
||||
headers in favor of the general `HX-Trigger`, combined with some client side code.
|
||||
|
||||
Let's implement the `X-IC-Redirect` header using the `HX-Trigger` response header and some hyperscript.
|
||||
|
||||
First, let's trigger an event with a response header that looks like this:
|
||||
|
||||
`HX-Trigger:{"redirect":{"url":"https://htmx.org"}}`
|
||||
|
||||
Then we would write the following hyperscript:
|
||||
|
||||
```html
|
||||
<body _="on redirect(url) put url into window.location">
|
||||
...
|
||||
</body>
|
||||
```
|
||||
|
||||
## <a name="config"></a>[Configuring htmx](#config)
|
||||
|
||||
Htmx allows you to configure a few defaults:
|
||||
|
@ -53,8 +53,7 @@ This event is triggered after new content has been [swapped into the DOM](/docs
|
||||
|
||||
### <a name="beforeOnLoad.htmx"></a> Event - [`beforeOnLoad.htmx`](#beforeOnLoad.htmx)
|
||||
|
||||
This event is triggered before any new content has been [swapped into the DOM](/docs#swapping). If
|
||||
the event is cancelled, no swap will occur.
|
||||
This event is triggered before any response processing occurs. If the event is cancelled, no swap will occur.
|
||||
|
||||
##### Details
|
||||
|
||||
@ -72,6 +71,39 @@ This event is triggered before an AJAX request is issued. If the event is cance
|
||||
* `detail.xhr` - the `XMLHttpRequest`
|
||||
* `detail.target` - the target of the request
|
||||
|
||||
### <a name="beforeSwap.htmx"></a> Event - [`beforeSwap.htmx`](#beforeSwap.htmx)
|
||||
|
||||
This event is triggered before any new content has been [swapped into the DOM](/docs#swapping). If the event is cancelled, no swap will occur.
|
||||
|
||||
##### Details
|
||||
|
||||
* `detail.elt` - the element that dispatched the request
|
||||
* `detail.xhr` - the `XMLHttpRequest`
|
||||
* `detail.target` - the target of the request
|
||||
|
||||
### <a name="configRequest.htmx"></a> Event - [`configRequest.htmx`](#configRequest.htmx)
|
||||
|
||||
This event is triggered after htmx has collected parameters for inclusion in the request. It can be
|
||||
used to include or update the parameters that htmx will send:
|
||||
|
||||
```javascript
|
||||
document.body.addEventListener('configRequest.htmx', function(evt) {
|
||||
evt.detail.parameters['auth_token'] = getAuthToken(); // add a new parameter into the mix
|
||||
});
|
||||
```
|
||||
|
||||
Note that if an input value appears more than once the value in the `parameters` object will be an array, rather
|
||||
than a single value.
|
||||
|
||||
##### Details
|
||||
|
||||
* `detail.parameters` - the parameters that will be submitted in the request
|
||||
* `detail.unfilteredParameters` - the parameters that were found before filtering by [`hx-select`](/attributes/hx-select)
|
||||
* `detail.headers` - the request headers
|
||||
* `detail.elt` - the element that triggered the request
|
||||
* `detail.target` - the target of the request
|
||||
* `detail.verb` - the HTTP verb in use
|
||||
|
||||
### <a name="historyCacheMiss.htmx"></a> Event - [`historyCacheMiss.htmx`](#historyCacheMiss.htmx)
|
||||
|
||||
This event is triggered when a cache miss occurs when restoring history
|
||||
@ -81,20 +113,20 @@ This event is triggered when a cache miss occurs when restoring history
|
||||
* `detail.xhr` - the `XMLHttpRequest` that will retrieve the remote content for restoration
|
||||
* `detail.path` - the path and query of the page being restored
|
||||
|
||||
### <a name="historyCacheMissLoad.htmx"></a> Event - [`historyCacheMissLoad.htmx`](#historyCacheMissLoad.htmx)
|
||||
### <a name="historyCacheMissError.htmx"></a> Event - [`historyCacheMissError.htmx`](#historyCacheMissError.htmx)
|
||||
|
||||
This event is triggered when a cache miss occurs and a response has been retrieved succesfully from the server
|
||||
for the content to restore
|
||||
This event is triggered when a cache miss occurs and a response has been retrieved from the server
|
||||
for the content to restore, but the response is an error (e.g. `404`)
|
||||
|
||||
##### Details
|
||||
|
||||
* `detail.xhr` - the `XMLHttpRequest`
|
||||
* `detail.path` - the path and query of the page being restored
|
||||
|
||||
### <a name="historyCacheMissError.htmx"></a> Event - [`historyCacheMissError.htmx`](#historyCacheMissError.htmx)
|
||||
### <a name="historyCacheMissLoad.htmx"></a> Event - [`historyCacheMissLoad.htmx`](#historyCacheMissLoad.htmx)
|
||||
|
||||
This event is triggered when a cache miss occurs and a response has been retrieved from the server
|
||||
for the content to restore, but the response is an error (e.g. `404`)
|
||||
This event is triggered when a cache miss occurs and a response has been retrieved succesfully from the server
|
||||
for the content to restore
|
||||
|
||||
##### Details
|
||||
|
||||
@ -209,29 +241,6 @@ This event is triggered when an error occurs during the swap phase
|
||||
* `detail.elt` - the element that triggered the request
|
||||
* `detail.target` - the target of the request
|
||||
|
||||
### <a name="configRequest.htmx"></a> Event - [`configRequest.htmx`](#configRequest.htmx)
|
||||
|
||||
This event is triggered after htmx has collected parameters for inclusion in the request. It can be
|
||||
used to include or update the parameters that htmx will send:
|
||||
|
||||
```javascript
|
||||
document.body.addEventListener('configRequest.htmx', function(evt) {
|
||||
evt.detail.parameters['auth_token'] = getAuthToken(); // add a new parameter into the mix
|
||||
});
|
||||
```
|
||||
|
||||
Note that if an input value appears more than once the value in the `parameters` object will be an array, rather
|
||||
than a single value.
|
||||
|
||||
##### Details
|
||||
|
||||
* `detail.parameters` - the parameters that will be submitted in the request
|
||||
* `detail.unfilteredParameters` - the parameters that were found before filtering by [`hx-select`](/attributes/hx-select)
|
||||
* `detail.headers` - the request headers
|
||||
* `detail.elt` - the element that triggered the request
|
||||
* `detail.target` - the target of the request
|
||||
* `detail.verb` - the HTTP verb in use
|
||||
|
||||
### <a name="targetError.htmx"></a> Event - [`targetError.htmx`](#targetError.htmx)
|
||||
|
||||
This event is triggered when a bad selector is used for a [`hx-target`](/attributes/hx-target) attribute (e.g. an
|
||||
|
@ -5,17 +5,26 @@ title: </> htmx - high power tools for html
|
||||
|
||||
## htmx Extensions
|
||||
|
||||
Htmx has an extension mechanism for defining and using extensions to the default behavior in a simple and obvious manner.
|
||||
Htmx provides an extension mechanism for defining and using extensions within htmx-based applications.
|
||||
|
||||
## <a name="using"></a>[Using Extensions](#using)
|
||||
|
||||
To use an extension you use the [hx-ext](/attributes/hx-ext) attribute:
|
||||
Using an extension involves two steps:
|
||||
|
||||
* include the extension definition, which will add it to the `htmx` extension registry
|
||||
* reference the extension via the [hx-ext](/attributes/hx-ext) attribute
|
||||
|
||||
Here is an example
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/htmx.org@0.0.4/dist/ext/debug.js"></script>
|
||||
<button hx-post="/example" hx-ext="debug">This Button Uses The Debug Extension</button>
|
||||
```
|
||||
|
||||
Note that the `hx-ext` tag may be placed on parent elements if you want a plugin to apply to an entire swath of the dom,
|
||||
This loads the debug extension off of the `unpkg` CDN and then adds the debug extension to the given button. (This
|
||||
will print out extensive logging for the button, for debugging purposes.)
|
||||
|
||||
Note that the `hx-ext` tag may be placed on parent elements if you want a plugin to apply to an entire part of the DOM,
|
||||
and on the `body` tag for it to apply to all htmx requests.
|
||||
|
||||
**Tip:** To use multiple extensions on one element, separate them with a comma:
|
||||
@ -26,26 +35,32 @@ and on the `body` tag for it to apply to all htmx requests.
|
||||
|
||||
## <a name="included"></a> [Included Extensions](#included)
|
||||
|
||||
The following extensions that are tested and distributed with htmx:
|
||||
htmx includes a set of extensions out of the box that address common developer needs. These extensions are tested
|
||||
against `htmx` in each distribution
|
||||
|
||||
### <a name='reference'></a> [Included Extensions List](#reference)
|
||||
|
||||
<div class="info-table">
|
||||
|
||||
| Extension | Description
|
||||
|-----------|-------------
|
||||
| [`json-enc`](/extensions/json-enc) | use JSON encoding in the body of requests, rather than the default `x-www-form-urlencoded`
|
||||
| [`method-override`](/extensions/method-override) | use the `X-HTTP-Method-Override` header for non-`GET` and `POST` requests
|
||||
| [`morphdom-swap`](/extensions/morphdom-swap) | an extension for using the [morphdom](https://github.com/patrick-steele-idem/morphdom) library as the swapping mechanism in htmx.
|
||||
| [`client-side-templates`](/extensions/client-side-templates) | support for client side template processing of JSON responses
|
||||
| [`debug`](/extensions/debug) | an extension for debugging of a particular element using htmx
|
||||
| [`path-deps`](/extensions/path-deps) | an extension for expressing path-based dependencies [similar to intercoolerjs](http://intercoolerjs.org/docs.html#dependencies)
|
||||
| [`class-tools`](/extensions/class-tools) | an extension for manipulating timed addition and removal of classes on HTML elements
|
||||
| [`rails-method`](/extensions/rails-method) | includes the `_method` parameter in requests for rails compatibility
|
||||
| [`remove-me`](/extensions/remove-me) | allows you to remove an element after a given amount of time
|
||||
| [`include-vals`](/extensions/include-vals) | allows you to include additional values in a request
|
||||
| [`ajax-header`](/extensions/ajax-header) | includes the commonly-used `X-Requested-With` header that identifies ajax requests in many backend frameworks
|
||||
|
||||
</div>
|
||||
|
||||
## <a name="defining"></a>[Defining an Extensions](#defining)
|
||||
|
||||
To define an extension you need to call the `htmx.defineExtension()` function:
|
||||
To define an extension you call the `htmx.defineExtension()` function:
|
||||
|
||||
```html
|
||||
<script>
|
||||
@ -57,9 +72,11 @@ To define an extension you need to call the `htmx.defineExtension()` function:
|
||||
</script>
|
||||
```
|
||||
|
||||
Extensions should have names that are dash separated like above and that are reasonably short and descriptive.
|
||||
Typically, this is done in a stand-alone javascript file, rather than in an inline `script` tag.
|
||||
|
||||
Extensions can override the following default extension fields:
|
||||
Extensions should have names that are dash separated and that are reasonably short and descriptive.
|
||||
|
||||
Extensions can override the following default extension points to add or change functionality:
|
||||
|
||||
```javascript
|
||||
{
|
||||
|
22
www/extensions/ajax-header.md
Normal file
22
www/extensions/ajax-header.md
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
layout: layout.njk
|
||||
title: </> htmx - high power tools for html
|
||||
---
|
||||
|
||||
## The `ajax-header` Extension
|
||||
|
||||
This extension adds the `X-Requested-With` header to requests with the value "XMLHttpRequest".
|
||||
|
||||
This header is commonly used by javascript frameworks to differentiate ajax requests from normal http requests.
|
||||
|
||||
### Usage
|
||||
|
||||
```html
|
||||
<body hx-ext="ajax-header">
|
||||
...
|
||||
</body>
|
||||
```
|
||||
|
||||
### Source
|
||||
|
||||
<https://unpkg.com/htmx.org/dist/ext/ajax-header.js>
|
24
www/extensions/method-override.md
Normal file
24
www/extensions/method-override.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
layout: layout.njk
|
||||
title: </> htmx - high power tools for html
|
||||
---
|
||||
|
||||
## The `method-override` Extension
|
||||
|
||||
This extension makes non-`GET` and `POST` requests use a `POST` with the `X-HTTP-Method-Override` header set to the
|
||||
actual HTTP method. This is necessary when dealing with some firewall or proxy situations.
|
||||
|
||||
#### Usage
|
||||
|
||||
```html
|
||||
<body hx-ext="method-override">
|
||||
<button hx-put="/update">
|
||||
This request will be made as a POST w/ the X-HTTP-Method-Override Header Set
|
||||
</button>
|
||||
</body>
|
||||
```
|
||||
|
||||
#### Source
|
||||
|
||||
<https://unpkg.com/htmx.org/dist/ext/method-override.js>
|
||||
|
@ -1,20 +0,0 @@
|
||||
---
|
||||
layout: layout.njk
|
||||
title: </> htmx - high power tools for html
|
||||
---
|
||||
|
||||
## The `rails-method` Extension
|
||||
|
||||
This extension includes the rails `_method` parameter in non-`GET` or `POST` requests.
|
||||
|
||||
### Usage
|
||||
|
||||
```html
|
||||
<body hx-ext="rails-method">
|
||||
...
|
||||
</body>
|
||||
```
|
||||
|
||||
### Source
|
||||
|
||||
<https://unpkg.com/htmx.org/dist/ext/rails-method.js>
|
@ -58,14 +58,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
function maybeProcessClasses(elt) {
|
||||
if (elt.getAttribute) {
|
||||
var classList = elt.getAttribute("classes") || elt.getAttribute("data-classes");
|
||||
if (classList) {
|
||||
processClassList(elt, classList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
htmx.defineExtension('class-tools', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "processedNode.htmx") {
|
||||
var elt = evt.detail.elt;
|
||||
if (elt.getAttribute) {
|
||||
var classList = elt.getAttribute("classes") || elt.getAttribute("data-classes");
|
||||
if (classList) {
|
||||
processClassList(elt, classList);
|
||||
maybeProcessClasses(elt);
|
||||
if (elt.querySelectorAll) {
|
||||
var children = elt.querySelectorAll("[classes], [data-classes]");
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
maybeProcessClasses(children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
161
www/js/htmx.js
161
www/js/htmx.js
@ -12,12 +12,15 @@ return (function () {
|
||||
'use strict';
|
||||
|
||||
var VERBS = ['get', 'post', 'put', 'delete', 'patch'];
|
||||
var VERB_SELECTOR = VERBS.map(function(verb){
|
||||
return "[hx-" + verb + "], [data-hx-" + verb + "]"
|
||||
}).join(", ");
|
||||
|
||||
//====================================================================
|
||||
// Utilities
|
||||
//====================================================================
|
||||
function parseInterval(str) {
|
||||
if (str === "null" || str === "false" || str === "") {
|
||||
if (str == null || str === "null" || str === "false" || str === "") {
|
||||
return null;
|
||||
} else if (str.lastIndexOf("ms") === str.length - 2) {
|
||||
return parseFloat(str.substr(0, str.length - 2));
|
||||
@ -33,6 +36,11 @@ return (function () {
|
||||
}
|
||||
|
||||
// resolve with both hx and data-hx prefixes
|
||||
function hasAttribute(elt, qualifiedName) {
|
||||
return elt.hasAttribute && (elt.hasAttribute(qualifiedName) ||
|
||||
elt.hasAttribute("data-" + qualifiedName));
|
||||
}
|
||||
|
||||
function getAttributeValue(elt, qualifiedName) {
|
||||
return getRawAttribute(elt, qualifiedName) || getRawAttribute(elt, "data-" + qualifiedName);
|
||||
}
|
||||
@ -374,20 +382,23 @@ return (function () {
|
||||
|
||||
function handleAttributes(parentNode, fragment, settleInfo) {
|
||||
forEach(fragment.querySelectorAll("[id]"), function (newNode) {
|
||||
var oldNode = parentNode.querySelector(newNode.tagName + "[id=" + newNode.id + "]")
|
||||
if (oldNode && oldNode !== parentNode) {
|
||||
var newAttributes = newNode.cloneNode();
|
||||
cloneAttributes(newNode, oldNode);
|
||||
settleInfo.tasks.push(function () {
|
||||
cloneAttributes(newNode, newAttributes);
|
||||
});
|
||||
if (newNode.id && newNode.id.length > 0) {
|
||||
var oldNode = parentNode.querySelector(newNode.tagName + "[id=" + newNode.id + "]");
|
||||
if (oldNode && oldNode !== parentNode) {
|
||||
var newAttributes = newNode.cloneNode();
|
||||
cloneAttributes(newNode, oldNode);
|
||||
settleInfo.tasks.push(function () {
|
||||
cloneAttributes(newNode, newAttributes);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function makeLoadTask(child) {
|
||||
function makeAjaxLoadTask(child) {
|
||||
return function () {
|
||||
processNode(child);
|
||||
processScripts(child);
|
||||
triggerEvent(child, 'load.htmx', {});
|
||||
};
|
||||
}
|
||||
@ -398,7 +409,7 @@ return (function () {
|
||||
var child = fragment.firstChild;
|
||||
parentNode.insertBefore(child, insertBefore);
|
||||
if (child.nodeType !== Node.TEXT_NODE) {
|
||||
settleInfo.tasks.push(makeLoadTask(child));
|
||||
settleInfo.tasks.push(makeAjaxLoadTask(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -477,6 +488,8 @@ return (function () {
|
||||
|
||||
function swap(swapStyle, elt, target, fragment, settleInfo) {
|
||||
switch (swapStyle) {
|
||||
case "none":
|
||||
return;
|
||||
case "outerHTML":
|
||||
swapOuterHTML(target, fragment, settleInfo);
|
||||
return;
|
||||
@ -563,6 +576,9 @@ return (function () {
|
||||
if (token.indexOf("delay:") === 0) {
|
||||
triggerSpec.delay = parseInterval(token.substr(6));
|
||||
}
|
||||
if (token.indexOf("throttle:") === 0) {
|
||||
triggerSpec.throttle = parseInterval(token.substr(9));
|
||||
}
|
||||
}
|
||||
return triggerSpec;
|
||||
}).filter(function(x){ return x !== null });
|
||||
@ -655,13 +671,21 @@ return (function () {
|
||||
if (elementData.delayed) {
|
||||
clearTimeout(elementData.delayed);
|
||||
}
|
||||
var issueRequest = function(){
|
||||
issueAjaxRequest(elt, verb, path, evt.target);
|
||||
if (elementData.throttle) {
|
||||
return;
|
||||
}
|
||||
if (triggerSpec.delay) {
|
||||
elementData.delayed = setTimeout(issueRequest, triggerSpec.delay);
|
||||
|
||||
if (triggerSpec.throttle) {
|
||||
elementData.throttle = setTimeout(function(){
|
||||
issueAjaxRequest(elt, verb, path, evt.target);
|
||||
elementData.throttle = null;
|
||||
}, triggerSpec.throttle);
|
||||
} else if (triggerSpec.delay) {
|
||||
elementData.delayed = setTimeout(function(){
|
||||
issueAjaxRequest(elt, verb, path, evt.target);
|
||||
}, triggerSpec.delay);
|
||||
} else {
|
||||
issueRequest();
|
||||
issueAjaxRequest(elt, verb, path, evt.target);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -825,8 +849,8 @@ return (function () {
|
||||
function processVerbs(elt, nodeData, triggerSpecs) {
|
||||
var explicitAction = false;
|
||||
forEach(VERBS, function (verb) {
|
||||
var path = getAttributeValue(elt, 'hx-' + verb);
|
||||
if (path) {
|
||||
if (hasAttribute(elt,'hx-' + verb)) {
|
||||
var path = getAttributeValue(elt, 'hx-' + verb);
|
||||
explicitAction = true;
|
||||
nodeData.path = path;
|
||||
nodeData.verb = verb;
|
||||
@ -850,17 +874,50 @@ return (function () {
|
||||
return explicitAction;
|
||||
}
|
||||
|
||||
function processScript(elt) {
|
||||
if (elt.tagName === "SCRIPT" && elt.type === "text/javascript") {
|
||||
eval(elt.innerText);
|
||||
function evalScript(script) {
|
||||
if (script.type === "text/javascript") {
|
||||
try {
|
||||
eval(script.innerText);
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processNode(elt) {
|
||||
function processScripts(elt) {
|
||||
if (matches(elt, "script")) {
|
||||
evalScript(elt);
|
||||
}
|
||||
forEach(findAll(elt, "script"), function (script) {
|
||||
evalScript(script);
|
||||
});
|
||||
}
|
||||
|
||||
function isHyperScriptAvailable() {
|
||||
return typeof _hyperscript !== "undefined";
|
||||
}
|
||||
|
||||
function findElementsToProcess(elt) {
|
||||
if (elt.querySelectorAll) {
|
||||
return elt.querySelectorAll(VERB_SELECTOR + ", a, form, [hx-sse], [data-hx-sse], [hx-ws], [data-hx-ws]");
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function processNode(elt, ignoreChildren) {
|
||||
var nodeData = getInternalData(elt);
|
||||
if (!nodeData.processed) {
|
||||
nodeData.processed = true;
|
||||
|
||||
if(isHyperScriptAvailable()){
|
||||
_hyperscript.init(elt);
|
||||
}
|
||||
|
||||
if (elt.value) {
|
||||
nodeData.lastValue = elt.value;
|
||||
}
|
||||
|
||||
var triggerSpecs = getTriggerSpecs(elt);
|
||||
var explicitAction = processVerbs(elt, nodeData, triggerSpecs);
|
||||
|
||||
@ -878,11 +935,9 @@ return (function () {
|
||||
processWebSocketInfo(elt, nodeData, wsInfo);
|
||||
}
|
||||
triggerEvent(elt, "processedNode.htmx");
|
||||
|
||||
processScript(elt);
|
||||
}
|
||||
if (elt.children) { // IE
|
||||
forEach(elt.children, function(child) { processNode(child) });
|
||||
if (!ignoreChildren) {
|
||||
forEach(findElementsToProcess(elt), function(child) { processNode(child, true) });
|
||||
}
|
||||
}
|
||||
|
||||
@ -890,16 +945,6 @@ return (function () {
|
||||
// Event/Log Support
|
||||
//====================================================================
|
||||
|
||||
function sendError(elt, eventName, detail) {
|
||||
var errorURL = getClosestAttributeValue(elt, "hx-error-url");
|
||||
if (errorURL) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", errorURL);
|
||||
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
xhr.send(JSON.stringify({ "elt": elt.id, "event": eventName, "detail" : detail }));
|
||||
}
|
||||
}
|
||||
|
||||
function makeEvent(eventName, detail) {
|
||||
var evt;
|
||||
if (window.CustomEvent && typeof window.CustomEvent === 'function') {
|
||||
@ -948,7 +993,7 @@ return (function () {
|
||||
}
|
||||
if (detail.error) {
|
||||
logError(detail.error);
|
||||
sendError(elt, eventName, detail);
|
||||
triggerEvent(elt, "error.htmx", {errorInfo:detail})
|
||||
}
|
||||
var eventResult = elt.dispatchEvent(event);
|
||||
withExtensions(elt, function (extension) {
|
||||
@ -1319,9 +1364,6 @@ return (function () {
|
||||
|
||||
if (verb !== 'get') {
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||
if (verb !== 'post') {
|
||||
headers['X-HTTP-Method-Override'] = verb.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
// behavior of anchors w/ empty href is to use the current URL
|
||||
@ -1334,9 +1376,15 @@ return (function () {
|
||||
unfilteredParameters:rawParameters,
|
||||
headers:headers,
|
||||
target:target,
|
||||
verb:verb
|
||||
verb:verb,
|
||||
path:path
|
||||
};
|
||||
if(!triggerEvent(elt, 'configRequest.htmx', requestConfig)) return endRequestLock();
|
||||
// copy out in case the object was overwritten
|
||||
path = requestConfig.path;
|
||||
verb = requestConfig.verb;
|
||||
headers = requestConfig.headers;
|
||||
filteredParameters = requestConfig.parameters;
|
||||
|
||||
var splitPath = path.split("#");
|
||||
var pathNoAnchor = splitPath[0];
|
||||
@ -1357,7 +1405,7 @@ return (function () {
|
||||
}
|
||||
xhr.open('GET', finalPathForGet, true);
|
||||
} else {
|
||||
xhr.open('POST', path, true);
|
||||
xhr.open(verb.toUpperCase(), path, true);
|
||||
}
|
||||
|
||||
xhr.overrideMimeType("text/html");
|
||||
@ -1385,7 +1433,7 @@ return (function () {
|
||||
}
|
||||
// don't process 'No Content' response
|
||||
if (this.status !== 204) {
|
||||
if (!triggerEvent(elt, 'beforeSwap.htmx', eventDetail)) return;
|
||||
if (!triggerEvent(target, 'beforeSwap.htmx', eventDetail)) return;
|
||||
|
||||
var resp = this.response;
|
||||
withExtensions(elt, function(extension){
|
||||
@ -1402,15 +1450,32 @@ return (function () {
|
||||
target.classList.add("htmx-swapping");
|
||||
var doSwap = function () {
|
||||
try {
|
||||
|
||||
var activeElt = document.activeElement;
|
||||
var selectionInfo = {
|
||||
elt: activeElt,
|
||||
start: activeElt.selectionStart,
|
||||
end: activeElt.selectionEnd,
|
||||
};
|
||||
|
||||
var settleInfo = makeSettleInfo(target);
|
||||
selectAndSwap(swapSpec.swapStyle, target, elt, resp, settleInfo);
|
||||
|
||||
if (!bodyContains(selectionInfo.elt) && selectionInfo.elt.id) {
|
||||
var newActiveElt = document.getElementById(selectionInfo.elt.id);
|
||||
if (selectionInfo.start && newActiveElt.setSelectionRange) {
|
||||
newActiveElt.setSelectionRange(selectionInfo.start, selectionInfo.end);
|
||||
}
|
||||
newActiveElt.focus();
|
||||
}
|
||||
|
||||
target.classList.remove("htmx-swapping");
|
||||
forEach(settleInfo.elts, function (elt) {
|
||||
if (elt.classList) {
|
||||
elt.classList.add("htmx-settling");
|
||||
}
|
||||
triggerEvent(elt, 'afterSwap.htmx', eventDetail);
|
||||
});
|
||||
triggerEvent(elt, 'afterSwap.htmx', eventDetail);
|
||||
if (anchor) {
|
||||
location.hash = anchor;
|
||||
}
|
||||
@ -1422,12 +1487,14 @@ return (function () {
|
||||
if (elt.classList) {
|
||||
elt.classList.remove("htmx-settling");
|
||||
}
|
||||
triggerEvent(elt, 'afterSettle.htmx', eventDetail);
|
||||
});
|
||||
// push URL and save new page
|
||||
if (shouldSaveHistory) {
|
||||
pushUrlIntoHistory(pushedUrl || path);
|
||||
var pathToPush = pushedUrl || finalPathForGet || path;
|
||||
pushUrlIntoHistory(pathToPush);
|
||||
triggerEvent(getDocument().body, 'pushedIntoHistory.htmx', {path:pathToPush});
|
||||
}
|
||||
triggerEvent(elt, 'afterSettle.htmx', eventDetail);
|
||||
}
|
||||
|
||||
if (swapSpec.settleDelay > 0) {
|
||||
@ -1558,7 +1625,7 @@ return (function () {
|
||||
ready(function () {
|
||||
mergeMetaConfig();
|
||||
var body = getDocument().body;
|
||||
processNode(body);
|
||||
processNode(body, true);
|
||||
triggerEvent(body, 'load.htmx', {});
|
||||
window.onpopstate = function () {
|
||||
restoreHistory();
|
||||
@ -1603,4 +1670,4 @@ return (function () {
|
||||
}
|
||||
}
|
||||
)()
|
||||
}));
|
||||
}));
|
||||
|
@ -4,6 +4,15 @@ title: </> htmx - Attributes
|
||||
---
|
||||
|
||||
|
||||
## Contents
|
||||
|
||||
* [Htmx Attribute Reference](#attributes)
|
||||
* [Htmx CSS Class Reference](#classes)
|
||||
* [Htmx Request Headers Reference](#request_headers)
|
||||
* [Htmx Response Headers Reference](#response_headers)
|
||||
* [Htmx Event Reference](#events)
|
||||
* [Htmx Extensions Reference](/extensions#reference)
|
||||
|
||||
## <a name="attributes"></a> [Attribute Reference](#attributes)
|
||||
|
||||
<div class="info-table">
|
||||
@ -13,7 +22,6 @@ title: </> htmx - Attributes
|
||||
| [`hx-boost`](/attributes/hx-boost) | progressively enhances anchors and forms to use AJAX requests
|
||||
| [`hx-confirm`](/attributes/hx-confirm) | shows a confim() dialog before issuing a request
|
||||
| [`hx-delete`](/attributes/hx-delete) | issues a `DELETE` to the specified URL
|
||||
| [`hx-error-url`](/attributes/hx-error-url) | a URL to send client-side errors to
|
||||
| [`hx-ext`](/attributes/hx-ext) | extensions to use for this element
|
||||
| [`hx-get`](/attributes/hx-get) | issues a `GET` to the specified URL
|
||||
| [`hx-history-elt`](/attributes/hx-history-elt) | the element to snapshot and restore during history navigation
|
||||
@ -50,7 +58,7 @@ title: </> htmx - Attributes
|
||||
|
||||
## <a name="headers"></a> [HTTP Header Reference](#headers)
|
||||
|
||||
### <a name="request_headers"></a> [Request Headers](#request_headers)
|
||||
### <a name="request_headers"></a> [Request Headers Reference](#request_headers)
|
||||
|
||||
<div class="info-table">
|
||||
|
||||
@ -70,7 +78,7 @@ title: </> htmx - Attributes
|
||||
|
||||
</div>
|
||||
|
||||
### <a name="response_headers"></a> [Response Headers](#response_headers)
|
||||
### <a name="response_headers"></a> [Response Headers Reference](#response_headers)
|
||||
|
||||
<div class="info-table">
|
||||
|
||||
|
18
www/talk.md
18
www/talk.md
@ -17,14 +17,24 @@ I'll be setting up a forum and chat room at some point.
|
||||
|
||||
[@htmx_org](https://twitter.com/htmx_org)
|
||||
|
||||
## Blog & Announcements
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="1 col">
|
||||
|
||||
## Announcements
|
||||
|
||||
<ul>
|
||||
{%- for post in collections.post reversed -%}
|
||||
<li><a href="{{ post.url }}">{{ post.date | date: "%a, %b %d, %y"}} - {{ post.data.title }}</a>🔥🔥</li>
|
||||
<li><a href="{{ post.url }}">{{ post.date | date: "%Y-%m-%d"}} - {{ post.data.title }} </a>🔥🔥</li>
|
||||
{%- endfor -%}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="1 col">
|
||||
|
||||
## Essays
|
||||
|
||||
* [Locality of Behavior (LoB)](/essays/locality-of-behaviour)
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
10854
www/test/0.0.5/node_modules/chai/chai.js
generated
vendored
Normal file
10854
www/test/0.0.5/node_modules/chai/chai.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
325
www/test/0.0.5/node_modules/mocha/mocha.css
generated
vendored
Normal file
325
www/test/0.0.5/node_modules/mocha/mocha.css
generated
vendored
Normal file
@ -0,0 +1,325 @@
|
||||
@charset "utf-8";
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
}
|
||||
|
||||
#mocha {
|
||||
font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
margin: 60px 50px;
|
||||
}
|
||||
|
||||
#mocha ul,
|
||||
#mocha li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#mocha ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#mocha h1,
|
||||
#mocha h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#mocha h1 {
|
||||
margin-top: 15px;
|
||||
font-size: 1em;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
#mocha h1 a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#mocha h1 a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#mocha .suite .suite h1 {
|
||||
margin-top: 0;
|
||||
font-size: .8em;
|
||||
}
|
||||
|
||||
#mocha .hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha h2 {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#mocha .suite {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
#mocha .test {
|
||||
margin-left: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#mocha .test.pending:hover h2::after {
|
||||
content: '(pending)';
|
||||
font-family: arial, sans-serif;
|
||||
}
|
||||
|
||||
#mocha .test.pass.medium .duration {
|
||||
background: #c09853;
|
||||
}
|
||||
|
||||
#mocha .test.pass.slow .duration {
|
||||
background: #b94a48;
|
||||
}
|
||||
|
||||
#mocha .test.pass::before {
|
||||
content: '✓';
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
color: #00d6b2;
|
||||
}
|
||||
|
||||
#mocha .test.pass .duration {
|
||||
font-size: 9px;
|
||||
margin-left: 5px;
|
||||
padding: 2px 5px;
|
||||
color: #fff;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
|
||||
-webkit-border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-ms-border-radius: 5px;
|
||||
-o-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#mocha .test.pass.fast .duration {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha .test.pending {
|
||||
color: #0b97c4;
|
||||
}
|
||||
|
||||
#mocha .test.pending::before {
|
||||
content: '◦';
|
||||
color: #0b97c4;
|
||||
}
|
||||
|
||||
#mocha .test.fail {
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test.fail pre {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#mocha .test.fail::before {
|
||||
content: '✖';
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
#mocha .test pre.error {
|
||||
color: #c00;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#mocha .test .html-error {
|
||||
overflow: auto;
|
||||
color: black;
|
||||
display: block;
|
||||
float: left;
|
||||
clear: left;
|
||||
font: 12px/1.5 monaco, monospace;
|
||||
margin: 5px;
|
||||
padding: 15px;
|
||||
border: 1px solid #eee;
|
||||
max-width: 85%; /*(1)*/
|
||||
max-width: -webkit-calc(100% - 42px);
|
||||
max-width: -moz-calc(100% - 42px);
|
||||
max-width: calc(100% - 42px); /*(2)*/
|
||||
max-height: 300px;
|
||||
word-wrap: break-word;
|
||||
border-bottom-color: #ddd;
|
||||
-webkit-box-shadow: 0 1px 3px #eee;
|
||||
-moz-box-shadow: 0 1px 3px #eee;
|
||||
box-shadow: 0 1px 3px #eee;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#mocha .test .html-error pre.error {
|
||||
border: none;
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: 0;
|
||||
-moz-box-shadow: 0;
|
||||
box-shadow: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-top: 18px;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* (1): approximate for browsers not supporting calc
|
||||
* (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border)
|
||||
* ^^ seriously
|
||||
*/
|
||||
#mocha .test pre {
|
||||
display: block;
|
||||
float: left;
|
||||
clear: left;
|
||||
font: 12px/1.5 monaco, monospace;
|
||||
margin: 5px;
|
||||
padding: 15px;
|
||||
border: 1px solid #eee;
|
||||
max-width: 85%; /*(1)*/
|
||||
max-width: -webkit-calc(100% - 42px);
|
||||
max-width: -moz-calc(100% - 42px);
|
||||
max-width: calc(100% - 42px); /*(2)*/
|
||||
word-wrap: break-word;
|
||||
border-bottom-color: #ddd;
|
||||
-webkit-box-shadow: 0 1px 3px #eee;
|
||||
-moz-box-shadow: 0 1px 3px #eee;
|
||||
box-shadow: 0 1px 3px #eee;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#mocha .test h2 {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#mocha .test a.replay {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 0;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
line-height: 15px;
|
||||
text-align: center;
|
||||
background: #eee;
|
||||
font-size: 15px;
|
||||
-webkit-border-radius: 15px;
|
||||
-moz-border-radius: 15px;
|
||||
border-radius: 15px;
|
||||
-webkit-transition:opacity 200ms;
|
||||
-moz-transition:opacity 200ms;
|
||||
-o-transition:opacity 200ms;
|
||||
transition: opacity 200ms;
|
||||
opacity: 0.3;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#mocha .test:hover a.replay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#mocha-report.pass .test.fail {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha-report.fail .test.pass {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha-report.pending .test.pass,
|
||||
#mocha-report.pending .test.fail {
|
||||
display: none;
|
||||
}
|
||||
#mocha-report.pending .test.pass.pending {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#mocha-error {
|
||||
color: #c00;
|
||||
font-size: 1.5em;
|
||||
font-weight: 100;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
#mocha-stats {
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
right: 10px;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
color: #888;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#mocha-stats .progress {
|
||||
float: right;
|
||||
padding-top: 0;
|
||||
|
||||
/**
|
||||
* Set safe initial values, so mochas .progress does not inherit these
|
||||
* properties from Bootstrap .progress (which causes .progress height to
|
||||
* equal line height set in Bootstrap).
|
||||
*/
|
||||
height: auto;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
background-color: initial;
|
||||
}
|
||||
|
||||
#mocha-stats em {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#mocha-stats a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#mocha-stats a:hover {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#mocha-stats li {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
list-style: none;
|
||||
padding-top: 11px;
|
||||
}
|
||||
|
||||
#mocha-stats canvas {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#mocha code .comment { color: #ddd; }
|
||||
#mocha code .init { color: #2f6fad; }
|
||||
#mocha code .string { color: #5890ad; }
|
||||
#mocha code .keyword { color: #8a6343; }
|
||||
#mocha code .number { color: #2f6fad; }
|
||||
|
||||
@media screen and (max-device-width: 480px) {
|
||||
#mocha {
|
||||
margin: 60px 0px;
|
||||
}
|
||||
|
||||
#mocha #stats {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
18178
www/test/0.0.5/node_modules/mocha/mocha.js
generated
vendored
Normal file
18178
www/test/0.0.5/node_modules/mocha/mocha.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
16430
www/test/0.0.5/node_modules/sinon/pkg/sinon.js
generated
vendored
Normal file
16430
www/test/0.0.5/node_modules/sinon/pkg/sinon.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
7
www/test/0.0.5/src/ext/ajax-header.js
Normal file
7
www/test/0.0.5/src/ext/ajax-header.js
Normal file
@ -0,0 +1,7 @@
|
||||
htmx.defineExtension('ajax-header', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "configRequest.htmx") {
|
||||
evt.detail.headers['X-Requested-With'] = 'XMLHttpRequest';
|
||||
}
|
||||
}
|
||||
});
|
84
www/test/0.0.5/src/ext/class-tools.js
Normal file
84
www/test/0.0.5/src/ext/class-tools.js
Normal file
@ -0,0 +1,84 @@
|
||||
(function(){
|
||||
|
||||
function splitOnWhitespace(trigger) {
|
||||
return trigger.split(/\s+/);
|
||||
}
|
||||
|
||||
function parseClassOperation(trimmedValue) {
|
||||
var split = splitOnWhitespace(trimmedValue);
|
||||
if (split.length > 1) {
|
||||
var operation = split[0];
|
||||
var classDef = split[1].trim();
|
||||
var cssClass;
|
||||
var delay;
|
||||
if (classDef.indexOf(":") > 0) {
|
||||
var splitCssClass = classDef.split(':');
|
||||
cssClass = splitCssClass[0];
|
||||
delay = htmx.parseInterval(splitCssClass[1]);
|
||||
} else {
|
||||
cssClass = classDef;
|
||||
delay = 100;
|
||||
}
|
||||
return {
|
||||
operation:operation,
|
||||
cssClass:cssClass,
|
||||
delay:delay
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function processClassList(elt, classList) {
|
||||
var runs = classList.split("&");
|
||||
for (var i = 0; i < runs.length; i++) {
|
||||
var run = runs[i];
|
||||
var currentRunTime = 0;
|
||||
var classOperations = run.split(",");
|
||||
for (var j = 0; j < classOperations.length; j++) {
|
||||
var value = classOperations[j];
|
||||
var trimmedValue = value.trim();
|
||||
var classOperation = parseClassOperation(trimmedValue);
|
||||
if (classOperation) {
|
||||
if (classOperation.operation === "toggle") {
|
||||
setTimeout(function () {
|
||||
setInterval(function () {
|
||||
elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass);
|
||||
}, classOperation.delay);
|
||||
}, currentRunTime);
|
||||
currentRunTime = currentRunTime + classOperation.delay;
|
||||
} else {
|
||||
currentRunTime = currentRunTime + classOperation.delay;
|
||||
setTimeout(function () {
|
||||
elt.classList[classOperation.operation].call(elt.classList, classOperation.cssClass);
|
||||
}, currentRunTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function maybeProcessClasses(elt) {
|
||||
if (elt.getAttribute) {
|
||||
var classList = elt.getAttribute("classes") || elt.getAttribute("data-classes");
|
||||
if (classList) {
|
||||
processClassList(elt, classList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
htmx.defineExtension('class-tools', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "processedNode.htmx") {
|
||||
var elt = evt.detail.elt;
|
||||
maybeProcessClasses(elt);
|
||||
if (elt.querySelectorAll) {
|
||||
var children = elt.querySelectorAll("[classes], [data-classes]");
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
maybeProcessClasses(children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
37
www/test/0.0.5/src/ext/client-side-templates.js
Normal file
37
www/test/0.0.5/src/ext/client-side-templates.js
Normal file
@ -0,0 +1,37 @@
|
||||
htmx.defineExtension('client-side-templates', {
|
||||
transformResponse : function(text, xhr, elt) {
|
||||
|
||||
var mustacheTemplate = htmx.closest(elt, "[mustache-template]");
|
||||
if (mustacheTemplate) {
|
||||
var data = JSON.parse(text);
|
||||
var templateId = mustacheTemplate.getAttribute('mustache-template');
|
||||
var template = htmx.find("#" + templateId);
|
||||
if (template) {
|
||||
return Mustache.render(template.innerHTML, data);
|
||||
} else {
|
||||
throw "Unknown mustache template: " + templateId;
|
||||
}
|
||||
}
|
||||
|
||||
var handlebarsTemplate = htmx.closest(elt, "[handlebars-template]");
|
||||
if (handlebarsTemplate) {
|
||||
var data = JSON.parse(text);
|
||||
var templateName = handlebarsTemplate.getAttribute('handlebars-template');
|
||||
return Handlebars.partials[templateName](data);
|
||||
}
|
||||
|
||||
var nunjucksTemplate = htmx.closest(elt, "[nunjucks-template]");
|
||||
if (nunjucksTemplate) {
|
||||
var data = JSON.parse(text);
|
||||
var templateName = nunjucksTemplate.getAttribute('nunjucks-template');
|
||||
var template = htmx.find('#' + templateName);
|
||||
if (template) {
|
||||
return nunjucks.renderString(template.innerHTML, data);
|
||||
} else {
|
||||
return nunjucks.render(templateName, data);
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
});
|
11
www/test/0.0.5/src/ext/debug.js
Normal file
11
www/test/0.0.5/src/ext/debug.js
Normal file
@ -0,0 +1,11 @@
|
||||
htmx.defineExtension('debug', {
|
||||
onEvent: function (name, evt) {
|
||||
if (console.debug) {
|
||||
console.debug(name, evt);
|
||||
} else if (console) {
|
||||
console.log("DEBUG:", name, evt);
|
||||
} else {
|
||||
throw "NO CONSOLE SUPPORTED"
|
||||
}
|
||||
}
|
||||
});
|
24
www/test/0.0.5/src/ext/include-vals.js
Normal file
24
www/test/0.0.5/src/ext/include-vals.js
Normal file
@ -0,0 +1,24 @@
|
||||
(function(){
|
||||
|
||||
function mergeObjects(obj1, obj2) {
|
||||
for (var key in obj2) {
|
||||
if (obj2.hasOwnProperty(key)) {
|
||||
obj1[key] = obj2[key];
|
||||
}
|
||||
}
|
||||
return obj1;
|
||||
}
|
||||
|
||||
htmx.defineExtension('include-vals', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "configRequest.htmx") {
|
||||
var includeValsElt = htmx.closest(evt.detail.elt, "[include-vals],[data-include-vals]");
|
||||
if (includeValsElt) {
|
||||
var includeVals = includeValsElt.getAttribute("include-vals") || includeValsElt.getAttribute("data-include-vals");
|
||||
var valuesToInclude = eval("({" + includeVals + "})");
|
||||
mergeObjects(evt.detail.parameters, valuesToInclude);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
12
www/test/0.0.5/src/ext/json-enc.js
Normal file
12
www/test/0.0.5/src/ext/json-enc.js
Normal file
@ -0,0 +1,12 @@
|
||||
htmx.defineExtension('json-enc', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "configRequest.htmx") {
|
||||
evt.detail.headers['Content-Type'] = "application/json";
|
||||
}
|
||||
},
|
||||
|
||||
encodeParameters : function(xhr, parameters, elt) {
|
||||
xhr.overrideMimeType('text/json');
|
||||
return (JSON.stringify(parameters));
|
||||
}
|
||||
});
|
11
www/test/0.0.5/src/ext/method-override.js
Normal file
11
www/test/0.0.5/src/ext/method-override.js
Normal file
@ -0,0 +1,11 @@
|
||||
htmx.defineExtension('method-override', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "configRequest.htmx") {
|
||||
var method = evt.detail.verb;
|
||||
if (method !== "get" || method !== "post") {
|
||||
evt.detail.headers['X-HTTP-Method-Override'] = method.toUpperCase();
|
||||
evt.detail.verb = "post";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
11
www/test/0.0.5/src/ext/morphdom-swap.js
Normal file
11
www/test/0.0.5/src/ext/morphdom-swap.js
Normal file
@ -0,0 +1,11 @@
|
||||
htmx.defineExtension('morphdom-swap', {
|
||||
isInlineSwap: function(swapStyle) {
|
||||
return swapStyle === 'morphdom';
|
||||
},
|
||||
handleSwap: function (swapStyle, target, fragment) {
|
||||
if (swapStyle === 'morphdom') {
|
||||
morphdom(target, fragment.outerHTML);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
35
www/test/0.0.5/src/ext/path-deps.js
Normal file
35
www/test/0.0.5/src/ext/path-deps.js
Normal file
@ -0,0 +1,35 @@
|
||||
(function(){
|
||||
function dependsOn(pathSpec, url) {
|
||||
var dependencyPath = pathSpec.split("/");
|
||||
var urlPath = url.split("/");
|
||||
for (var i = 0; i < urlPath.length; i++) {
|
||||
var dependencyElement = dependencyPath.shift();
|
||||
var pathElement = urlPath[i];
|
||||
if (dependencyElement !== pathElement && dependencyElement !== "*") {
|
||||
return false;
|
||||
}
|
||||
if (dependencyPath.length === 0 || (dependencyPath.length === 1 && dependencyPath[0] === "")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
htmx.defineExtension('path-deps', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "afterRequest.htmx") {
|
||||
var xhr = evt.detail.xhr;
|
||||
// mutating call
|
||||
if (xhr.method !== "GET") {
|
||||
var eltsWithDeps = htmx.findAll("[path-deps]");
|
||||
for (var i = 0; i < eltsWithDeps.length; i++) {
|
||||
var elt = eltsWithDeps[i];
|
||||
if (dependsOn(elt.getAttribute('path-deps'), xhr.url)) {
|
||||
htmx.trigger(elt, "path-deps");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
27
www/test/0.0.5/src/ext/remove-me.js
Normal file
27
www/test/0.0.5/src/ext/remove-me.js
Normal file
@ -0,0 +1,27 @@
|
||||
(function(){
|
||||
function maybeRemoveMe(elt) {
|
||||
var timing = elt.getAttribute("remove-me") || elt.getAttribute("data-remove-me");
|
||||
if (timing) {
|
||||
setTimeout(function () {
|
||||
elt.parentElement.removeChild(elt);
|
||||
}, htmx.parseInterval(timing));
|
||||
}
|
||||
}
|
||||
|
||||
htmx.defineExtension('remove-me', {
|
||||
onEvent: function (name, evt) {
|
||||
if (name === "processedNode.htmx") {
|
||||
var elt = evt.detail.elt;
|
||||
if (elt.getAttribute) {
|
||||
maybeRemoveMe(elt);
|
||||
if (elt.querySelectorAll) {
|
||||
var children = elt.querySelectorAll("[remove-me], [data-remove-me");
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
maybeRemoveMe(children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
1673
www/test/0.0.5/src/htmx.js
Normal file
1673
www/test/0.0.5/src/htmx.js
Normal file
File diff suppressed because it is too large
Load Diff
61
www/test/0.0.5/test/attributes/hx-boost.js
Normal file
61
www/test/0.0.5/test/attributes/hx-boost.js
Normal file
@ -0,0 +1,61 @@
|
||||
describe("hx-boost attribute", 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");
|
||||
})
|
||||
|
||||
|
||||
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");
|
||||
})
|
||||
|
||||
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");
|
||||
})
|
||||
|
||||
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");
|
||||
})
|
||||
|
||||
it('handles basic anchor properly w/ data-* prefix', function () {
|
||||
this.server.respondWith("GET", "/test", "Boosted");
|
||||
var div = make('<div data-hx-target="this" data-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");
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
34
www/test/0.0.5/test/attributes/hx-delete.js
Normal file
34
www/test/0.0.5/test/attributes/hx-delete.js
Normal file
@ -0,0 +1,34 @@
|
||||
describe("hx-delete attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a DELETE request', function()
|
||||
{
|
||||
this.server.respondWith("DELETE", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "Deleted!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-delete="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Deleted!");
|
||||
});
|
||||
|
||||
it('issues a DELETE request w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("DELETE", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "Deleted!");
|
||||
});
|
||||
|
||||
var btn = make('<button data-hx-delete="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Deleted!");
|
||||
});
|
||||
})
|
98
www/test/0.0.5/test/attributes/hx-ext.js
Normal file
98
www/test/0.0.5/test/attributes/hx-ext.js
Normal file
@ -0,0 +1,98 @@
|
||||
describe("hx-ext attribute", function() {
|
||||
|
||||
var ext1Calls, ext2Calls, ext3Calls;
|
||||
|
||||
beforeEach(function () {
|
||||
ext1Calls = ext2Calls = ext3Calls = 0;
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
htmx.defineExtension("ext-1", {
|
||||
onEvent : function(name, evt) {
|
||||
if(name === "afterRequest.htmx"){
|
||||
ext1Calls++;
|
||||
}
|
||||
}
|
||||
});
|
||||
htmx.defineExtension("ext-2", {
|
||||
onEvent : function(name, evt) {
|
||||
if(name === "afterRequest.htmx"){
|
||||
ext2Calls++;
|
||||
}
|
||||
}
|
||||
});
|
||||
htmx.defineExtension("ext-3", {
|
||||
onEvent : function(name, evt) {
|
||||
if(name === "afterRequest.htmx"){
|
||||
ext3Calls++;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
htmx.removeExtension("ext-1");
|
||||
htmx.removeExtension("ext-2");
|
||||
htmx.removeExtension("ext-3");
|
||||
});
|
||||
|
||||
it('A simple extension is invoked properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var btn = make('<button hx-get="/test" hx-ext="ext-1">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
ext1Calls.should.equal(1);
|
||||
ext2Calls.should.equal(0);
|
||||
ext3Calls.should.equal(0);
|
||||
});
|
||||
|
||||
it('Extensions are merged properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
make('<div hx-ext="ext-1"><button id="btn-1" hx-get="/test" hx-ext="ext-2">Click Me!</button>' +
|
||||
'<button id="btn-2" hx-get="/test" hx-ext="ext-3">Click Me!</button></div>')
|
||||
var btn1 = byId("btn-1");
|
||||
var btn2 = byId("btn-2");
|
||||
|
||||
btn1.click();
|
||||
this.server.respond();
|
||||
ext1Calls.should.equal(1);
|
||||
ext2Calls.should.equal(1);
|
||||
ext3Calls.should.equal(0);
|
||||
|
||||
btn2.click();
|
||||
this.server.respond();
|
||||
ext1Calls.should.equal(2);
|
||||
ext2Calls.should.equal(1);
|
||||
ext3Calls.should.equal(1);
|
||||
});
|
||||
|
||||
it('supports comma separated lists', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
make('<div hx-ext="ext-1"><button id="btn-1" hx-get="/test" hx-ext="ext-2, ext-3 ">Click Me!</button></div>')
|
||||
var btn1 = byId("btn-1");
|
||||
var btn2 = byId("btn-2");
|
||||
|
||||
btn1.click();
|
||||
this.server.respond();
|
||||
ext1Calls.should.equal(1);
|
||||
ext2Calls.should.equal(1);
|
||||
ext3Calls.should.equal(1);
|
||||
});
|
||||
|
||||
it('A simple extension is invoked properly w/ data-* prefix', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var btn = make('<button data-hx-get="/test" data-hx-ext="ext-1">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
ext1Calls.should.equal(1);
|
||||
ext2Calls.should.equal(0);
|
||||
ext3Calls.should.equal(0);
|
||||
});
|
||||
|
||||
|
||||
});
|
76
www/test/0.0.5/test/attributes/hx-get.js
Normal file
76
www/test/0.0.5/test/attributes/hx-get.js
Normal file
@ -0,0 +1,76 @@
|
||||
describe("hx-get attribute", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a GET request on click and swaps content', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var btn = make('<button hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('GET does not include surrounding data by default', function () {
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
should.equal(getParameters(xhr)["i1"], undefined);
|
||||
xhr.respond(200, {}, "Clicked!");
|
||||
});
|
||||
make('<form><input name="i1" value="value"/><button id="b1" hx-get="/test">Click Me!</button></form>')
|
||||
var btn = byId("b1");
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('GET on form includes its own data by default', function () {
|
||||
this.server.respondWith("GET", /\/test.*/, function (xhr) {
|
||||
getParameters(xhr)["i1"].should.equal("value");
|
||||
xhr.respond(200, {}, "Clicked!");
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-get="/test"><input name="i1" value="value"/><button id="b1">Click Me!</button></form>');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('GET on form with existing parameters works properly', function () {
|
||||
this.server.respondWith("GET", /\/test.*/, function (xhr) {
|
||||
getParameters(xhr)["foo"].should.equal("bar");
|
||||
getParameters(xhr)["i1"].should.equal("value");
|
||||
xhr.respond(200, {}, "Clicked!");
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-get="/test?foo=bar"><input name="i1" value="value"/><button id="b1">Click Me!</button></form>');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('GET on form with anchor works properly', function () {
|
||||
this.server.respondWith("GET", /\/test.*/, function (xhr) {
|
||||
getParameters(xhr)["foo"].should.equal("bar");
|
||||
getParameters(xhr)["i1"].should.equal("value");
|
||||
xhr.respond(200, {}, "Clicked!");
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-get="/test?foo=bar#foo"><input name="i1" value="value"/><button id="b1">Click Me!</button></form>');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
|
||||
it('issues a GET request on click and swaps content w/ data-* prefix', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var btn = make('<button data-hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
});
|
153
www/test/0.0.5/test/attributes/hx-include.js
Normal file
153
www/test/0.0.5/test/attributes/hx-include.js
Normal file
@ -0,0 +1,153 @@
|
||||
describe("hx-include attribute", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('By default an input includes itself', 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-target="this"><input hx-post="/include" hx-trigger="click" id="i1" name="i1" value="test"/></div>')
|
||||
var input = byId("i1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('non-GET includes closest 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('<form hx-target="this"><div id="d1" hx-post="/include"></div><input name="i1" value="test"/></form>')
|
||||
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);
|
||||
should.equal(params['i1'], undefined);
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var div = make('<form hx-target="this"><div id="d1" hx-get="/include"></div><input name="i1" value="test"/></form>')
|
||||
var input = byId("d1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('Input not included twice when in 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('<form hx-target="this"><input hx-post="/include" hx-trigger="click" id="i1" name="i1" value="test"/></form>')
|
||||
var input = byId("i1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('Two inputs are included twice when they have the same name', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.deep.equal(["test", "test2"]);
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var div = make('<form hx-target="this">' +
|
||||
'<input hx-post="/include" hx-trigger="click" id="i1" name="i1" value="test"/>' +
|
||||
'<input name="i1" value="test2"/>' +
|
||||
'</form>')
|
||||
var input = byId("i1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('Input not included twice when it explicitly refers to parent 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('<form id="f1" hx-target="this">' +
|
||||
'<input hx-include="#f1" hx-post="/include" hx-trigger="click" id="i1" name="i1" value="test"/>' +
|
||||
'</form>')
|
||||
var input = byId("i1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('Input can be referred to externally', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
make('<input id="i1" name="i1" value="test"/>');
|
||||
var div = make('<div hx-post="/include" hx-include="#i1"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('Two inputs can be referred to externally', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
params['i2'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
make('<input id="i1" name="i1" value="test"/>');
|
||||
make('<input id="i2" name="i2" value="test"/>');
|
||||
var div = make('<div hx-post="/include" hx-include="#i1, #i2"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('A form can be referred to externally', function () {
|
||||
this.server.respondWith("POST", "/include", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
params['i1'].should.equal("test");
|
||||
params['i2'].should.equal("test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
make('<form id="f1">' +
|
||||
'<input name="i1" value="test"/>' +
|
||||
'<input name="i2" value="test"/>' +
|
||||
'</form> ');
|
||||
var div = make('<div hx-post="/include" hx-include="#f1"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('By default an input includes itself w/ data-* prefix', 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 data-hx-target="this"><input data-hx-post="/include" data-hx-trigger="click" id="i1" name="i1" value="test"/></div>')
|
||||
var input = byId("i1")
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
|
||||
});
|
52
www/test/0.0.5/test/attributes/hx-indicator.js
Normal file
52
www/test/0.0.5/test/attributes/hx-indicator.js
Normal file
@ -0,0 +1,52 @@
|
||||
describe("hx-indicator attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = sinon.fakeServer.create();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('Indicator classes are properly put on element with no explicit indicator', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
btn.classList.contains("htmx-request").should.equal(true);
|
||||
this.server.respond();
|
||||
btn.classList.contains("htmx-request").should.equal(false);
|
||||
});
|
||||
|
||||
it('Indicator classes are properly put on element with explicit indicator', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-indicator="#a1, #a2">Click Me!</button>')
|
||||
var a1 = make('<a id="a1"></a>')
|
||||
var a2 = make('<a id="a2"></a>')
|
||||
btn.click();
|
||||
btn.classList.contains("htmx-request").should.equal(false);
|
||||
a1.classList.contains("htmx-request").should.equal(true);
|
||||
a2.classList.contains("htmx-request").should.equal(true);
|
||||
this.server.respond();
|
||||
btn.classList.contains("htmx-request").should.equal(false);
|
||||
a1.classList.contains("htmx-request").should.equal(false);
|
||||
a2.classList.contains("htmx-request").should.equal(false);
|
||||
});
|
||||
|
||||
it('Indicator classes are properly put on element with explicit indicator w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" data-hx-indicator="#a1, #a2">Click Me!</button>')
|
||||
var a1 = make('<a id="a1"></a>')
|
||||
var a2 = make('<a id="a2"></a>')
|
||||
btn.click();
|
||||
btn.classList.contains("htmx-request").should.equal(false);
|
||||
a1.classList.contains("htmx-request").should.equal(true);
|
||||
a2.classList.contains("htmx-request").should.equal(true);
|
||||
this.server.respond();
|
||||
btn.classList.contains("htmx-request").should.equal(false);
|
||||
a1.classList.contains("htmx-request").should.equal(false);
|
||||
a2.classList.contains("htmx-request").should.equal(false);
|
||||
});
|
||||
})
|
101
www/test/0.0.5/test/attributes/hx-params.js
Normal file
101
www/test/0.0.5/test/attributes/hx-params.js
Normal file
@ -0,0 +1,101 @@
|
||||
describe("hx-params attribute", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('none excludes all params', function () {
|
||||
this.server.respondWith("POST", "/params", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
should.equal(params['i1'], undefined);
|
||||
should.equal(params['i2'], undefined);
|
||||
should.equal(params['i3'], undefined);
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-post="/params" hx-params="none">' +
|
||||
'<input name="i1" value="test"/>' +
|
||||
'<input name="i2" value="test"/>' +
|
||||
'<input name="i3" value="test"/>' +
|
||||
'</form> ');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('"*" includes all params', function () {
|
||||
this.server.respondWith("POST", "/params", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
should.equal(params['i1'], "test");
|
||||
should.equal(params['i2'], "test");
|
||||
should.equal(params['i3'], "test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-post="/params" hx-params="*">' +
|
||||
'<input name="i1" value="test"/>' +
|
||||
'<input name="i2" value="test"/>' +
|
||||
'<input name="i3" value="test"/>' +
|
||||
'</form> ');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('named includes works', function () {
|
||||
this.server.respondWith("POST", "/params", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
should.equal(params['i1'], "test");
|
||||
should.equal(params['i2'], undefined);
|
||||
should.equal(params['i3'], "test");
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-post="/params" hx-params="i1, i3">' +
|
||||
'<input name="i1" value="test"/>' +
|
||||
'<input name="i2" value="test"/>' +
|
||||
'<input name="i3" value="test"/>' +
|
||||
'</form> ');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('named exclude works', function () {
|
||||
this.server.respondWith("POST", "/params", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
should.equal(params['i1'], undefined);
|
||||
should.equal(params['i2'], "test");
|
||||
should.equal(params['i3'], undefined);
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-post="/params" hx-params="not i1, i3">' +
|
||||
'<input name="i1" value="test"/>' +
|
||||
'<input name="i2" value="test"/>' +
|
||||
'<input name="i3" value="test"/>' +
|
||||
'</form> ');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('named exclude works w/ data-* prefix', function () {
|
||||
this.server.respondWith("POST", "/params", function (xhr) {
|
||||
var params = getParameters(xhr);
|
||||
should.equal(params['i1'], undefined);
|
||||
should.equal(params['i2'], "test");
|
||||
should.equal(params['i3'], undefined);
|
||||
xhr.respond(200, {}, "Clicked!")
|
||||
});
|
||||
var form = make('<form data-hx-trigger="click" data-hx-post="/params" data-hx-params="not i1, i3">' +
|
||||
'<input name="i1" value="test"/>' +
|
||||
'<input name="i2" value="test"/>' +
|
||||
'<input name="i3" value="test"/>' +
|
||||
'</form> ');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
});
|
34
www/test/0.0.5/test/attributes/hx-patch.js
Normal file
34
www/test/0.0.5/test/attributes/hx-patch.js
Normal file
@ -0,0 +1,34 @@
|
||||
describe("hx-patch attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a PATCH request', function()
|
||||
{
|
||||
this.server.respondWith("PATCH", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "Patched!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-patch="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Patched!");
|
||||
});
|
||||
|
||||
it('issues a PATCH request w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("PATCH", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "Patched!");
|
||||
});
|
||||
|
||||
var btn = make('<button data-hx-patch="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Patched!");
|
||||
});
|
||||
})
|
36
www/test/0.0.5/test/attributes/hx-post.js
Normal file
36
www/test/0.0.5/test/attributes/hx-post.js
Normal file
@ -0,0 +1,36 @@
|
||||
describe("hx-post attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a POST request with proper headers', function()
|
||||
{
|
||||
this.server.respondWith("POST", "/test", function(xhr){
|
||||
should.equal(xhr.requestHeaders['X-HTTP-Method-Override'], undefined);
|
||||
xhr.respond(200, {}, "Posted!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-post="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Posted!");
|
||||
});
|
||||
|
||||
it('issues a POST request with proper headers w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("POST", "/test", function(xhr){
|
||||
should.equal(xhr.requestHeaders['X-HTTP-Method-Override'], undefined);
|
||||
xhr.respond(200, {}, "Posted!");
|
||||
});
|
||||
|
||||
var btn = make('<button data-hx-post="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Posted!");
|
||||
});
|
||||
})
|
138
www/test/0.0.5/test/attributes/hx-push-url.js
Normal file
138
www/test/0.0.5/test/attributes/hx-push-url.js
Normal file
@ -0,0 +1,138 @@
|
||||
describe("hx-push-url attribute", function() {
|
||||
|
||||
var HTMX_HISTORY_CACHE_NAME = "htmx-history-cache";
|
||||
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME);
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME);
|
||||
});
|
||||
|
||||
it("navigation should push an element into the cache ", function () {
|
||||
this.server.respondWith("GET", "/test", "second");
|
||||
getWorkArea().innerHTML.should.be.equal("");
|
||||
var div = make('<div hx-push-url="true" hx-get="/test">first</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("second")
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
cache.length.should.equal(1);
|
||||
});
|
||||
|
||||
it("restore should return old value", function () {
|
||||
this.server.respondWith("GET", "/test1", '<div id="d2" hx-push-url="true" hx-get="/test2" hx-swap="outerHTML settle:0">test1</div>');
|
||||
this.server.respondWith("GET", "/test2", '<div id="d3" hx-push-url="true" hx-get="/test3" hx-swap="outerHTML settle:0">test2</div>');
|
||||
|
||||
make('<div id="d1" hx-push-url="true" hx-get="/test1" hx-swap="outerHTML settle:0">init</div>');
|
||||
|
||||
byId("d1").click();
|
||||
this.server.respond();
|
||||
var workArea = getWorkArea();
|
||||
workArea.textContent.should.equal("test1")
|
||||
|
||||
byId("d2").click();
|
||||
this.server.respond();
|
||||
workArea.textContent.should.equal("test2")
|
||||
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
|
||||
cache.length.should.equal(2);
|
||||
htmx._('restoreHistory')("/test1")
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("test1")
|
||||
});
|
||||
|
||||
it("cache should only store 10 entries", function () {
|
||||
var x = 0;
|
||||
this.server.respondWith("GET", /test.*/, function(xhr){
|
||||
x++;
|
||||
xhr.respond(200, {}, '<div id="d1" hx-push-url="true" hx-get="/test' + x + '" hx-swap="outerHTML settle:0"></div>')
|
||||
});
|
||||
getWorkArea().innerHTML.should.be.equal("");
|
||||
make('<div id="d1" hx-push-url="true" hx-get="/test" hx-swap="outerHTML settle:0"></div>');
|
||||
for (var i = 0; i < 20; i++) { // issue 20 requests
|
||||
byId("d1").click();
|
||||
this.server.respond();
|
||||
}
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
cache.length.should.equal(10); // should only be 10 elements
|
||||
});
|
||||
|
||||
it("cache miss should issue another GET", function () {
|
||||
this.server.respondWith("GET", "/test1", '<div id="d2" hx-push-url="true" hx-get="/test2" hx-swap="outerHTML settle:0">test1</div>');
|
||||
this.server.respondWith("GET", "/test2", '<div id="d3" hx-push-url="true" hx-get="/test3" hx-swap="outerHTML settle:0">test2</div>');
|
||||
|
||||
make('<div id="d1" hx-push-url="true" hx-get="/test1" hx-swap="outerHTML settle:0">init</div>');
|
||||
|
||||
byId("d1").click();
|
||||
this.server.respond();
|
||||
var workArea = getWorkArea();
|
||||
workArea.textContent.should.equal("test1")
|
||||
|
||||
byId("d2").click();
|
||||
this.server.respond();
|
||||
workArea.textContent.should.equal("test2")
|
||||
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
|
||||
cache.length.should.equal(2);
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME); // clear cache
|
||||
htmx._('restoreHistory')("/test1")
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("test1")
|
||||
});
|
||||
|
||||
it("navigation should push an element into the cache w/ data-* prefix", function () {
|
||||
this.server.respondWith("GET", "/test", "second");
|
||||
getWorkArea().innerHTML.should.be.equal("");
|
||||
var div = make('<div data-hx-push-url="true" data-hx-get="/test">first</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
getWorkArea().textContent.should.equal("second")
|
||||
var cache = JSON.parse(localStorage.getItem(HTMX_HISTORY_CACHE_NAME));
|
||||
cache.length.should.equal(1);
|
||||
});
|
||||
|
||||
it("afterSettle.htmx is called when replacing outerHTML", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("afterSettle.htmx", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "<button>Bar</button>");
|
||||
});
|
||||
var div = make("<button hx-post='/test' hx-swap='outerHTML'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("afterSettle.htmx", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("should include parameters on a get", function () {
|
||||
var path = "";
|
||||
var handler = htmx.on("pushedIntoHistory.htmx", function (evt) {
|
||||
path = evt.detail.path;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("GET", /test.*/, function (xhr) {
|
||||
xhr.respond(200, {}, "second")
|
||||
});
|
||||
var form = make('<form hx-trigger="click" hx-push-url="true" hx-get="/test"><input type="hidden" name="foo" value="bar"/>first</form>');
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.textContent.should.equal("second")
|
||||
path.should.equal("/test?foo=bar")
|
||||
} finally {
|
||||
htmx.off("pushedIntoHistory.htmx", handler);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
34
www/test/0.0.5/test/attributes/hx-put.js
Normal file
34
www/test/0.0.5/test/attributes/hx-put.js
Normal file
@ -0,0 +1,34 @@
|
||||
describe("hx-put attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('issues a PUT request', function()
|
||||
{
|
||||
this.server.respondWith("PUT", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "Putted!");
|
||||
});
|
||||
|
||||
var btn = make('<button hx-put="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Putted!");
|
||||
});
|
||||
|
||||
it('issues a PUT request w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("PUT", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "Putted!");
|
||||
});
|
||||
|
||||
var btn = make('<button data-hx-put="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Putted!");
|
||||
});
|
||||
})
|
40
www/test/0.0.5/test/attributes/hx-select.js
Normal file
40
www/test/0.0.5/test/attributes/hx-select.js
Normal file
@ -0,0 +1,40 @@
|
||||
describe("BOOTSTRAP - htmx AJAX Tests", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('properly handles a partial of HTML', function()
|
||||
{
|
||||
var i = 1;
|
||||
this.server.respondWith("GET", "/test", "<div id='d1'>foo</div><div id='d2'>bar</div>");
|
||||
var div = make('<div hx-get="/test" hx-select="#d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("<div id=\"d1\">foo</div>");
|
||||
});
|
||||
|
||||
it('properly handles a full HTML document', function()
|
||||
{
|
||||
var i = 1;
|
||||
this.server.respondWith("GET", "/test", "<html><body><div id='d1'>foo</div><div id='d2'>bar</div></body></html>");
|
||||
var div = make('<div hx-get="/test" hx-select="#d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("<div id=\"d1\">foo</div>");
|
||||
});
|
||||
|
||||
it('properly handles a full HTML document w/ data-* prefix', function()
|
||||
{
|
||||
var i = 1;
|
||||
this.server.respondWith("GET", "/test", "<html><body><div id='d1'>foo</div><div id='d2'>bar</div></body></html>");
|
||||
var div = make('<div hx-get="/test" data-hx-select="#d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("<div id=\"d1\">foo</div>");
|
||||
});
|
||||
})
|
120
www/test/0.0.5/test/attributes/hx-sse.js
Normal file
120
www/test/0.0.5/test/attributes/hx-sse.js
Normal file
@ -0,0 +1,120 @@
|
||||
describe("hx-sse attribute", function() {
|
||||
|
||||
function mockEventSource() {
|
||||
var listeners = {};
|
||||
var wasClosed = false;
|
||||
var mockEventSource = {
|
||||
addEventListener: function (message, l) {
|
||||
listeners[message] = l;
|
||||
},
|
||||
sendEvent: function (event) {
|
||||
var listener = listeners[event];
|
||||
if (listener) {
|
||||
listener();
|
||||
}
|
||||
},
|
||||
close: function () {
|
||||
wasClosed = true;
|
||||
},
|
||||
wasClosed: function () {
|
||||
return wasClosed;
|
||||
}
|
||||
};
|
||||
return mockEventSource;
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
var eventSource = mockEventSource();
|
||||
this.eventSource = eventSource;
|
||||
clearWorkArea();
|
||||
htmx.createEventSource = function(){ return eventSource };
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('handles basic sse triggering', function () {
|
||||
|
||||
this.server.respondWith("GET", "/d1", "div1 updated");
|
||||
this.server.respondWith("GET", "/d2", "div2 updated");
|
||||
|
||||
var div = make('<div hx-sse="connect /foo">' +
|
||||
'<div id="d1" hx-trigger="sse:e1" hx-get="/d1">div1</div>' +
|
||||
'<div id="d2" hx-trigger="sse:e2" hx-get="/d2">div2</div>' +
|
||||
'</div>');
|
||||
|
||||
this.eventSource.sendEvent("e1");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1 updated");
|
||||
byId("d2").innerHTML.should.equal("div2");
|
||||
|
||||
this.eventSource.sendEvent("e2");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1 updated");
|
||||
byId("d2").innerHTML.should.equal("div2 updated");
|
||||
})
|
||||
|
||||
it('does not trigger events that arent named', function () {
|
||||
|
||||
this.server.respondWith("GET", "/d1", "div1 updated");
|
||||
|
||||
var div = make('<div hx-sse="connect /foo">' +
|
||||
'<div id="d1" hx-trigger="sse:e1" hx-get="/d1">div1</div>' +
|
||||
'</div>');
|
||||
|
||||
this.eventSource.sendEvent("foo");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1");
|
||||
|
||||
this.eventSource.sendEvent("e2");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1");
|
||||
|
||||
this.eventSource.sendEvent("e1");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1 updated");
|
||||
})
|
||||
|
||||
it('does not trigger events not on decendents', function () {
|
||||
|
||||
this.server.respondWith("GET", "/d1", "div1 updated");
|
||||
|
||||
var div = make('<div hx-sse="connect /foo"></div>' +
|
||||
'<div id="d1" hx-trigger="sse:e1" hx-get="/d1">div1</div>');
|
||||
|
||||
this.eventSource.sendEvent("foo");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1");
|
||||
|
||||
this.eventSource.sendEvent("e2");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1");
|
||||
|
||||
this.eventSource.sendEvent("e1");
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("div1");
|
||||
})
|
||||
|
||||
it('is closed after removal', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div = make('<div hx-get="/test" hx-swap="outerHTML" hx-sse="connect /foo">' +
|
||||
'<div id="d1" hx-trigger="sse:e1" hx-get="/d1">div1</div>' +
|
||||
'</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
this.eventSource.wasClosed().should.equal(true)
|
||||
})
|
||||
|
||||
it('is closed after removal with no close and activity', function () {
|
||||
var div = make('<div hx-get="/test" hx-swap="outerHTML" hx-sse="connect /foo">' +
|
||||
'<div id="d1" hx-trigger="sse:e1" hx-get="/d1">div1</div>' +
|
||||
'</div>');
|
||||
div.parentElement.removeChild(div);
|
||||
this.eventSource.sendEvent("e1")
|
||||
this.eventSource.wasClosed().should.equal(true)
|
||||
})
|
||||
|
||||
});
|
||||
|
75
www/test/0.0.5/test/attributes/hx-swap-oob.js
Normal file
75
www/test/0.0.5/test/attributes/hx-swap-oob.js
Normal file
@ -0,0 +1,75 @@
|
||||
describe("hx-swap-oob 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", "Clicked<div id='d1' hx-swap-oob='true'>Swapped</div>");
|
||||
var div = make('<div hx-get="/test">click me</div>');
|
||||
make('<div id="d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked");
|
||||
byId("d1").innerHTML.should.equal("Swapped");
|
||||
})
|
||||
|
||||
it('handles more than one oob swap properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked<div id='d1' hx-swap-oob='true'>Swapped1</div><div id='d2' hx-swap-oob='true'>Swapped2</div>");
|
||||
var div = make('<div hx-get="/test">click me</div>');
|
||||
make('<div id="d1"></div>');
|
||||
make('<div id="d2"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked");
|
||||
byId("d1").innerHTML.should.equal("Swapped1");
|
||||
byId("d2").innerHTML.should.equal("Swapped2");
|
||||
})
|
||||
|
||||
it('handles no id match properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked<div id='d1' hx-swap-oob='true'>Swapped</div>");
|
||||
var div = make('<div hx-get="/test">click me</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("Clicked");
|
||||
})
|
||||
|
||||
it('handles basic response properly w/ data-* prefix', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked<div id='d1' data-hx-swap-oob='true'>Swapped</div>");
|
||||
var div = make('<div data-hx-get="/test">click me</div>');
|
||||
make('<div id="d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Clicked");
|
||||
byId("d1").innerHTML.should.equal("Swapped");
|
||||
})
|
||||
|
||||
it('handles outerHTML response properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked<div id='d1' foo='bar' hx-swap-oob='outerHTML'>Swapped</div>");
|
||||
var div = make('<div hx-get="/test">click me</div>');
|
||||
make('<div id="d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
byId("d1").getAttribute("foo").should.equal("bar");
|
||||
div.innerHTML.should.equal("Clicked");
|
||||
byId("d1").innerHTML.should.equal("Swapped");
|
||||
})
|
||||
|
||||
it('handles innerHTML response properly', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked<div id='d1' foo='bar' hx-swap-oob='innerHTML'>Swapped</div>");
|
||||
var div = make('<div hx-get="/test">click me</div>');
|
||||
make('<div id="d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(byId("d1").getAttribute("foo"), null);
|
||||
div.innerHTML.should.equal("Clicked");
|
||||
byId("d1").innerHTML.should.equal("Swapped");
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
274
www/test/0.0.5/test/attributes/hx-swap.js
Normal file
274
www/test/0.0.5/test/attributes/hx-swap.js
Normal file
@ -0,0 +1,274 @@
|
||||
describe("hx-swap attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('swap innerHTML properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", '<a hx-get="/test2">Click Me</a>');
|
||||
this.server.respondWith("GET", "/test2", "Clicked!");
|
||||
|
||||
var div = make('<div hx-get="/test"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal('<a hx-get="/test2">Click Me</a>');
|
||||
var a = div.querySelector('a');
|
||||
a.click();
|
||||
this.server.respond();
|
||||
a.innerHTML.should.equal('Clicked!');
|
||||
});
|
||||
|
||||
it('swap outerHTML properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", '<a id="a1" hx-get="/test2">Click Me</a>');
|
||||
this.server.respondWith("GET", "/test2", "Clicked!");
|
||||
|
||||
var div = make('<div id="d1" hx-get="/test" hx-swap="outerHTML"></div>')
|
||||
div.click();
|
||||
should.equal(byId("d1"), div);
|
||||
this.server.respond();
|
||||
should.equal(byId("d1"), null);
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
byId("a1").innerHTML.should.equal('Clicked!');
|
||||
});
|
||||
|
||||
it('swap beforebegin properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="beforebegin">*</div>')
|
||||
var parent = div.parentElement;
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("1*");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("*2*");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("***");
|
||||
});
|
||||
|
||||
it('swap afterbegin properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="afterbegin">*</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("1*");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("2**");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("***");
|
||||
});
|
||||
|
||||
it('swap afterbegin properly with no initial content', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="afterbegin"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("2*");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
});
|
||||
|
||||
it('swap afterend properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="afterend">*</div>')
|
||||
var parent = div.parentElement;
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("*1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("*2*");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("***");
|
||||
});
|
||||
|
||||
it('swap beforeend properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="beforeend">*</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**2");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("***");
|
||||
});
|
||||
|
||||
it('swap beforeend properly with no initial content', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="beforeend"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*2");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
});
|
||||
|
||||
it('properly parses various swap specifications', function(){
|
||||
var swapSpec = htmx._("getSwapSpecification"); // internal function for swap spec
|
||||
swapSpec(make("<div/>")).swapStyle.should.equal("innerHTML")
|
||||
swapSpec(make("<div hx-swap='innerHTML'/>")).swapStyle.should.equal("innerHTML")
|
||||
swapSpec(make("<div hx-swap='innerHTML'/>")).swapDelay.should.equal(0)
|
||||
swapSpec(make("<div hx-swap='innerHTML'/>")).settleDelay.should.equal(0) // set to 0 in tests
|
||||
swapSpec(make("<div hx-swap='innerHTML swap:10'/>")).swapDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:10'/>")).settleDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='innerHTML swap:10 settle:11'/>")).swapDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='innerHTML swap:10 settle:11'/>")).settleDelay.should.equal(11)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:11 swap:10'/>")).swapDelay.should.equal(10)
|
||||
swapSpec(make("<div hx-swap='innerHTML settle:11 swap:10'/>")).settleDelay.should.equal(11)
|
||||
swapSpec(make("<div hx-swap='innerHTML nonsense settle:11 swap:10'/>")).settleDelay.should.equal(11)
|
||||
swapSpec(make("<div hx-swap='innerHTML nonsense settle:11 swap:10 '/>")).settleDelay.should.equal(11)
|
||||
})
|
||||
|
||||
it('works with a swap delay', function(done) {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div = make("<div hx-get='/test' hx-swap='innerHTML swap:10ms'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("");
|
||||
setTimeout(function () {
|
||||
div.innerText.should.equal("Clicked!");
|
||||
done();
|
||||
}, 30);
|
||||
});
|
||||
|
||||
it('works with a settle delay', function(done) {
|
||||
this.server.respondWith("GET", "/test", "<div id='d1' class='foo' hx-get='/test' hx-swap='outerHTML settle:10ms'></div>");
|
||||
var div = make("<div id='d1' hx-get='/test' hx-swap='outerHTML settle:10ms'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.classList.contains('foo').should.equal(false);
|
||||
setTimeout(function () {
|
||||
byId('d1').classList.contains('foo').should.equal(true);
|
||||
done();
|
||||
}, 30);
|
||||
});
|
||||
|
||||
it('swap outerHTML properly w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", '<a id="a1" data-hx-get="/test2">Click Me</a>');
|
||||
this.server.respondWith("GET", "/test2", "Clicked!");
|
||||
|
||||
var div = make('<div id="d1" data-hx-get="/test" data-hx-swap="outerHTML"></div>')
|
||||
div.click();
|
||||
should.equal(byId("d1"), div);
|
||||
this.server.respond();
|
||||
should.equal(byId("d1"), null);
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
byId("a1").innerHTML.should.equal('Clicked!');
|
||||
});
|
||||
|
||||
it('swap none works properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", 'Ooops, swapped');
|
||||
|
||||
var div = make('<div hx-swap="none" hx-get="/test">Foo</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal('Foo');
|
||||
});
|
||||
|
||||
})
|
83
www/test/0.0.5/test/attributes/hx-target.js
Normal file
83
www/test/0.0.5/test/attributes/hx-target.js
Normal file
@ -0,0 +1,83 @@
|
||||
describe("hx-target attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = sinon.fakeServer.create();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('targets an adjacent element properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-target="#d1" hx-get="/test">Click Me!</button>')
|
||||
var div1 = make('<div id="d1"></div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div1.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('targets a parent element properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div1 = make('<div id="d1"><button id="b1" hx-target="#d1" hx-get="/test">Click Me!</button></div>')
|
||||
var btn = byId("b1")
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div1.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('targets a `this` element properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div1 = make('<div hx-target="this"><button id="b1" hx-get="/test">Click Me!</button></div>')
|
||||
var btn = byId("b1")
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div1.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('targets a `closest` element properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div1 = make('<div><p><i><button id="b1" hx-target="closest div" hx-get="/test">Click Me!</button></i></p></div>')
|
||||
var btn = byId("b1")
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div1.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('targets an inner element properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-target="#d1" hx-get="/test">Click Me!<div id="d1"></div></button>')
|
||||
var div1 = byId("d1")
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div1.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
|
||||
it('handles bad target gracefully', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-target="bad" hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Click Me!");
|
||||
});
|
||||
|
||||
|
||||
it('targets an adjacent element properly w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button data-hx-target="#d1" data-hx-get="/test">Click Me!</button>')
|
||||
var div1 = make('<div id="d1"></div>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div1.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
|
||||
})
|
160
www/test/0.0.5/test/attributes/hx-trigger.js
Normal file
160
www/test/0.0.5/test/attributes/hx-trigger.js
Normal file
@ -0,0 +1,160 @@
|
||||
describe("hx-trigger attribute", function(){
|
||||
beforeEach(function() {
|
||||
this.server = sinon.fakeServer.create();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('non-default value works', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var form = make('<form hx-get="/test" hx-trigger="click">Click Me!</form>');
|
||||
form.click();
|
||||
form.innerHTML.should.equal("Click Me!");
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('changed modifier works', function()
|
||||
{
|
||||
var requests = 0;
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
requests++;
|
||||
xhr.respond(200, {}, "Requests: " + requests);
|
||||
});
|
||||
var input = make('<input hx-trigger="click changed" hx-target="#d1" hx-get="/test" value="foo"/>');
|
||||
var div = make('<div id="d1"></div>');
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("");
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("");
|
||||
input.value = "bar";
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
});
|
||||
|
||||
it('once modifier works', function()
|
||||
{
|
||||
var requests = 0;
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
requests++;
|
||||
xhr.respond(200, {}, "Requests: " + requests);
|
||||
});
|
||||
var input = make('<input hx-trigger="click once" hx-target="#d1" hx-get="/test" value="foo"/>');
|
||||
var div = make('<div id="d1"></div>');
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
input.value = "bar";
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
input.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
});
|
||||
|
||||
it('polling works', function(complete)
|
||||
{
|
||||
var requests = 0;
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
requests++;
|
||||
if (requests > 5) {
|
||||
complete();
|
||||
// cancel polling with a
|
||||
xhr.respond(286, {}, "Requests: " + requests);
|
||||
} else {
|
||||
xhr.respond(200, {}, "Requests: " + requests);
|
||||
}
|
||||
});
|
||||
this.server.autoRespond = true;
|
||||
this.server.autoRespondAfter = 0;
|
||||
make('<div hx-trigger="every 10ms" hx-get="/test"/>');
|
||||
});
|
||||
|
||||
|
||||
it('non-default value works w/ data-* prefix', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var form = make('<form data-hx-get="/test" data-hx-trigger="click">Click Me!</form>');
|
||||
form.click();
|
||||
form.innerHTML.should.equal("Click Me!");
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('works with multiple events', function()
|
||||
{
|
||||
var requests = 0;
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
requests++;
|
||||
xhr.respond(200, {}, "Requests: " + requests);
|
||||
});
|
||||
var div = make('<div hx-trigger="load,click" hx-get="/test">Requests: 0</div>');
|
||||
div.innerHTML.should.equal("Requests: 0");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 1");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Requests: 2");
|
||||
});
|
||||
|
||||
|
||||
|
||||
it("parses spec strings", function()
|
||||
{
|
||||
var specExamples = {
|
||||
"": [{trigger: 'click'}],
|
||||
"every 1s": [{trigger: 'every', pollInterval: 1000}],
|
||||
"click": [{trigger: 'click'}],
|
||||
"customEvent": [{trigger: 'customEvent'}],
|
||||
"event changed": [{trigger: 'event', changed: true}],
|
||||
"event once": [{trigger: 'event', once: true}],
|
||||
"event delay:1s": [{trigger: 'event', delay: 1000}],
|
||||
"event throttle:1s": [{trigger: 'event', throttle: 1000}],
|
||||
"event changed once delay:1s": [{trigger: 'event', changed: true, once: true, delay: 1000}],
|
||||
"event1,event2": [{trigger: 'event1'}, {trigger: 'event2'}],
|
||||
"event1, event2": [{trigger: 'event1'}, {trigger: 'event2'}],
|
||||
"event1 once, event2 changed": [{trigger: 'event1', once: true}, {trigger: 'event2', changed: true}],
|
||||
"event1,": [{trigger: 'event1'}],
|
||||
",event1": [{trigger: 'event1'}],
|
||||
" ": [{trigger: 'click'}],
|
||||
",": [{trigger: 'click'}]
|
||||
}
|
||||
|
||||
for (var specString in specExamples) {
|
||||
var div = make("<div hx-trigger='" + specString + "'></div>");
|
||||
var spec = htmx._('getTriggerSpecs')(div);
|
||||
spec.should.deep.equal(specExamples[specString], "Found : " + JSON.stringify(spec) + ", expected : " + JSON.stringify(specExamples[specString]) + " for spec: " + specString);
|
||||
}
|
||||
});
|
||||
|
||||
it('sets default trigger for forms', function()
|
||||
{
|
||||
var form = make('<form></form>');
|
||||
var spec = htmx._('getTriggerSpecs')(form);
|
||||
spec.should.deep.equal([{trigger: 'submit'}]);
|
||||
})
|
||||
|
||||
it('sets default trigger for form elements', function()
|
||||
{
|
||||
var form = make('<input></input>');
|
||||
var spec = htmx._('getTriggerSpecs')(form);
|
||||
spec.should.deep.equal([{trigger: 'change'}]);
|
||||
})
|
||||
|
||||
})
|
73
www/test/0.0.5/test/attributes/hx-ws.js
Normal file
73
www/test/0.0.5/test/attributes/hx-ws.js
Normal file
@ -0,0 +1,73 @@
|
||||
describe("hx-ws attribute", function() {
|
||||
|
||||
function mockWebsocket() {
|
||||
var listener;
|
||||
var lastSent;
|
||||
var wasClosed = false;
|
||||
var mockSocket = {
|
||||
addEventListener : function(message, l) {
|
||||
listener = l;
|
||||
},
|
||||
write : function(content) {
|
||||
return listener({data:content});
|
||||
},
|
||||
send : function(data) {
|
||||
lastSent = data;
|
||||
},
|
||||
getLastSent : function() {
|
||||
return lastSent;
|
||||
},
|
||||
close : function() {
|
||||
wasClosed = true;
|
||||
},
|
||||
wasClosed : function () {
|
||||
return wasClosed;
|
||||
}
|
||||
};
|
||||
return mockSocket;
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
var socket = mockWebsocket();
|
||||
this.socket = socket;
|
||||
clearWorkArea();
|
||||
htmx.createWebSocket = function(){ return socket };
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('handles a basic call back', function () {
|
||||
var div = make('<div hx-ws="connect wss:/foo"><div id="d1">div1</div><div id="d2">div2</div></div>');
|
||||
this.socket.write("<div id=\"d1\">replaced</div>")
|
||||
byId("d1").innerHTML.should.equal("replaced");
|
||||
byId("d2").innerHTML.should.equal("div2");
|
||||
})
|
||||
|
||||
it('handles a basic send', function () {
|
||||
var div = make('<div hx-ws="connect wss:/foo"><div hx-ws="send" id="d1">div1</div></div>');
|
||||
byId("d1").click();
|
||||
var lastSent = this.socket.getLastSent();
|
||||
var data = JSON.parse(lastSent);
|
||||
data.HEADERS["HX-Request"].should.equal("true");
|
||||
})
|
||||
|
||||
it('is closed after removal', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var div = make('<div hx-get="/test" hx-swap="outerHTML" hx-ws="connect wss:/foo"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
this.socket.wasClosed().should.equal(true)
|
||||
})
|
||||
|
||||
it('is closed after removal with no close and activity', function () {
|
||||
var div = make('<div hx-ws="connect wss:/foo"></div>');
|
||||
div.parentElement.removeChild(div);
|
||||
this.socket.write("<div id=\"d1\">replaced</div>")
|
||||
this.socket.wasClosed().should.equal(true)
|
||||
})
|
||||
|
||||
});
|
||||
|
463
www/test/0.0.5/test/core/ajax.js
Normal file
463
www/test/0.0.5/test/core/ajax.js
Normal file
@ -0,0 +1,463 @@
|
||||
describe("Core htmx AJAX Tests", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
// bootstrap test
|
||||
it('issues a GET request on click and swaps content', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var btn = make('<button hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('processes inner content properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", '<a hx-get="/test2">Click Me</a>');
|
||||
this.server.respondWith("GET", "/test2", "Clicked!");
|
||||
|
||||
var div = make('<div hx-get="/test"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal('<a hx-get="/test2">Click Me</a>');
|
||||
var a = div.querySelector('a');
|
||||
a.click();
|
||||
this.server.respond();
|
||||
a.innerHTML.should.equal('Clicked!');
|
||||
});
|
||||
|
||||
it('handles swap outerHTML properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", '<a id="a1" hx-get="/test2">Click Me</a>');
|
||||
this.server.respondWith("GET", "/test2", "Clicked!");
|
||||
|
||||
var div = make('<div id="d1" hx-get="/test" hx-swap="outerHTML"></div>')
|
||||
div.click();
|
||||
should.equal(byId("d1"), div);
|
||||
this.server.respond();
|
||||
should.equal(byId("d1"), null);
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
byId("a1").innerHTML.should.equal('Clicked!');
|
||||
});
|
||||
|
||||
it('handles beforebegin properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="beforebegin">*</div>')
|
||||
var parent = div.parentElement;
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("1*");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("*2*");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("***");
|
||||
});
|
||||
|
||||
it('handles afterbegin properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="afterbegin">*</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("1*");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("2**");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("***");
|
||||
});
|
||||
|
||||
it('handles afterbegin properly with no initial content', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="afterbegin"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("2*");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
});
|
||||
|
||||
it('handles afterend properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="afterend">*</div>')
|
||||
var parent = div.parentElement;
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("*1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
removeWhiteSpace(parent.innerText).should.equal("*2*");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
removeWhiteSpace(parent.innerText).should.equal("***");
|
||||
});
|
||||
|
||||
it('handles beforeend properly', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="beforeend">*</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**2");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("***");
|
||||
});
|
||||
|
||||
it('handles beforeend properly with no initial content', function()
|
||||
{
|
||||
var i = 0;
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
i++;
|
||||
xhr.respond(200, {}, '<a id="a' + i + '" hx-get="/test2" hx-swap="innerHTML">' + i + '</a>');
|
||||
});
|
||||
this.server.respondWith("GET", "/test2", "*");
|
||||
|
||||
var div = make('<div hx-get="/test" hx-swap="beforeend"></div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("1");
|
||||
|
||||
byId("a1").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*");
|
||||
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("*2");
|
||||
|
||||
byId("a2").click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("**");
|
||||
});
|
||||
|
||||
it('handles hx-target properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var btn = make('<button hx-get="/test" hx-target="#s1">Click Me!</button>');
|
||||
var target = make('<span id="s1">Initial</span>');
|
||||
btn.click();
|
||||
target.innerHTML.should.equal("Initial");
|
||||
this.server.respond();
|
||||
target.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('handles 204 NO CONTENT responses properly', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", [204, {}, "No Content!"]);
|
||||
|
||||
var btn = make('<button hx-get="/test">Click Me!</button>');
|
||||
btn.click();
|
||||
btn.innerHTML.should.equal("Click Me!");
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Click Me!");
|
||||
});
|
||||
|
||||
it('handles hx-trigger with non-default value', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
var form = make('<form hx-get="/test" hx-trigger="click">Click Me!</form>');
|
||||
form.click();
|
||||
form.innerHTML.should.equal("Click Me!");
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
it('handles hx-trigger with load event', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "Loaded!");
|
||||
var div = make('<div hx-get="/test" hx-trigger="load">Load Me!</div>');
|
||||
div.innerHTML.should.equal("Load Me!");
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Loaded!");
|
||||
});
|
||||
|
||||
it('sets the content type of the request properly', function (done) {
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
xhr.respond(200, {}, "done");
|
||||
xhr.overriddenMimeType.should.equal("text/html");
|
||||
done();
|
||||
});
|
||||
var div = make('<div hx-get="/test">Click Me!</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
});
|
||||
|
||||
it('doesnt issue two requests when clicked twice before response', function()
|
||||
{
|
||||
var i = 1;
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "click " + i);
|
||||
i++
|
||||
});
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
div.click();
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("click 1");
|
||||
});
|
||||
|
||||
it('properly handles hx-select for basic situation', function()
|
||||
{
|
||||
var i = 1;
|
||||
this.server.respondWith("GET", "/test", "<div id='d1'>foo</div><div id='d2'>bar</div>");
|
||||
var div = make('<div hx-get="/test" hx-select="#d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("<div id=\"d1\">foo</div>");
|
||||
});
|
||||
|
||||
it('properly handles hx-select for full html document situation', function()
|
||||
{
|
||||
var i = 1;
|
||||
this.server.respondWith("GET", "/test", "<html><body><div id='d1'>foo</div><div id='d2'>bar</div></body></html>");
|
||||
var div = make('<div hx-get="/test" hx-select="#d1"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("<div id=\"d1\">foo</div>");
|
||||
});
|
||||
|
||||
it('properly settles attributes on interior elements', function(done)
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "<div hx-get='/test'><div foo='bar' id='d1'></div></div>");
|
||||
var div = make("<div hx-get='/test' hx-swap='outerHTML settle:10ms'><div id='d1'></div></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(byId("d1").getAttribute("foo"), null);
|
||||
setTimeout(function () {
|
||||
should.equal(byId("d1").getAttribute("foo"), "bar");
|
||||
done();
|
||||
}, 20);
|
||||
});
|
||||
|
||||
it('properly handles checkbox inputs', function()
|
||||
{
|
||||
var values;
|
||||
this.server.respondWith("Post", "/test", function (xhr) {
|
||||
values = getParameters(xhr);
|
||||
xhr.respond(204, {}, "");
|
||||
});
|
||||
|
||||
var form = make('<form hx-post="/test" hx-trigger="click">' +
|
||||
'<input id="cb1" name="c1" value="cb1" type="checkbox">'+
|
||||
'<input id="cb2" name="c1" value="cb2" type="checkbox">'+
|
||||
'<input id="cb3" name="c1" value="cb3" type="checkbox">'+
|
||||
'<input id="cb4" name="c2" value="cb4" type="checkbox">'+
|
||||
'<input id="cb5" name="c2" value="cb5" type="checkbox">'+
|
||||
'<input id="cb6" name="c3" value="cb6" type="checkbox">'+
|
||||
'</form>');
|
||||
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:"cb1"});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
byId("cb2").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:["cb1", "cb2"]});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
byId("cb2").checked = true;
|
||||
byId("cb3").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:["cb1", "cb2", "cb3"]});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
byId("cb2").checked = true;
|
||||
byId("cb3").checked = true;
|
||||
byId("cb4").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:["cb1", "cb2", "cb3"], c2:"cb4"});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
byId("cb2").checked = true;
|
||||
byId("cb3").checked = true;
|
||||
byId("cb4").checked = true;
|
||||
byId("cb5").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:["cb1", "cb2", "cb3"], c2:["cb4", "cb5"]});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
byId("cb2").checked = true;
|
||||
byId("cb3").checked = true;
|
||||
byId("cb4").checked = true;
|
||||
byId("cb5").checked = true;
|
||||
byId("cb6").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:["cb1", "cb2", "cb3"], c2:["cb4", "cb5"], c3:"cb6"});
|
||||
|
||||
byId("cb1").checked = true;
|
||||
byId("cb2").checked = false;
|
||||
byId("cb3").checked = true;
|
||||
byId("cb4").checked = false;
|
||||
byId("cb5").checked = true;
|
||||
byId("cb6").checked = true;
|
||||
form.click();
|
||||
this.server.respond();
|
||||
values.should.deep.equal({c1:["cb1", "cb3"], c2:"cb5", c3:"cb6"});
|
||||
|
||||
});
|
||||
|
||||
it('text nodes dont screw up settling via variable capture', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "<div id='d1' hx-get='/test2'></div>fooo");
|
||||
this.server.respondWith("GET", "/test2", "clicked");
|
||||
var div = make("<div hx-get='/test'/>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
byId("d1").click();
|
||||
this.server.respond();
|
||||
byId("d1").innerHTML.should.equal("clicked");
|
||||
});
|
||||
|
||||
var globalWasCalled = false;
|
||||
window.callGlobal = function() {
|
||||
globalWasCalled = true;
|
||||
}
|
||||
|
||||
it('script nodes evaluate', function()
|
||||
{
|
||||
try {
|
||||
this.server.respondWith("GET", "/test", "<div></div><script type='text/javascript'>callGlobal()</script>");
|
||||
var div = make("<div hx-get='/test'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
globalWasCalled.should.equal(true);
|
||||
} finally {
|
||||
delete window.callGlobal;
|
||||
}
|
||||
});
|
||||
|
||||
it('script node exceptions do not break rendering', function()
|
||||
{
|
||||
this.server.respondWith("GET", "/test", "clicked<script type='text/javascript'>throw 'foo';</script>");
|
||||
var div = make("<div hx-get='/test'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.equal("clicked");
|
||||
});
|
||||
|
||||
it('allows empty verb values', function()
|
||||
{
|
||||
var path = null;
|
||||
var div = make("<div hx-get=''/>");
|
||||
htmx.on(div, "configRequest.htmx", function (evt) {
|
||||
path = evt.detail.path;
|
||||
return false;
|
||||
});
|
||||
div.click();
|
||||
path.should.not.be.null;
|
||||
});
|
||||
|
||||
it('allows blank verb values', function()
|
||||
{
|
||||
var path = null;
|
||||
var div = make("<div hx-get/>");
|
||||
htmx.on(div, "configRequest.htmx", function (evt) {
|
||||
path = evt.detail.path;
|
||||
return false;
|
||||
});
|
||||
div.click();
|
||||
path.should.not.be.null;
|
||||
});
|
||||
|
||||
|
||||
})
|
167
www/test/0.0.5/test/core/api.js
Normal file
167
www/test/0.0.5/test/core/api.js
Normal file
@ -0,0 +1,167 @@
|
||||
describe("Core htmx API test", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('onLoad is called... onLoad', function(){
|
||||
// also tests on/off
|
||||
this.server.respondWith("GET", "/test", "<div id='d1' hx-get='/test'></div>")
|
||||
var helper = htmx.onLoad(function (elt) {
|
||||
elt.setAttribute("foo", "bar");
|
||||
});
|
||||
try {
|
||||
var div = make("<div id='d1' hx-get='/test' hx-swap='outerHTML'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
byId("d1").getAttribute("foo").should.equal("bar");
|
||||
} finally {
|
||||
htmx.off("load.htmx", helper);
|
||||
}
|
||||
});
|
||||
|
||||
it('triggers properly', function () {
|
||||
var div = make("<div/>");
|
||||
var myEventCalled = false;
|
||||
var detailStr = "";
|
||||
htmx.on("myEvent", function(evt){
|
||||
myEventCalled = true;
|
||||
detailStr = evt.detail.str;
|
||||
})
|
||||
htmx.trigger(div, "myEvent", {str:"foo"})
|
||||
|
||||
myEventCalled.should.equal(true);
|
||||
detailStr.should.equal("foo");
|
||||
});
|
||||
|
||||
it('triggers with no details properly', function () {
|
||||
var div = make("<div/>");
|
||||
var myEventCalled = false;
|
||||
htmx.on("myEvent", function(evt){
|
||||
myEventCalled = true;
|
||||
})
|
||||
htmx.trigger(div, "myEvent")
|
||||
myEventCalled.should.equal(true);
|
||||
});
|
||||
|
||||
it('should find properly', function(){
|
||||
var div = make("<div id='d1' class='c1 c2'>");
|
||||
div.should.equal(htmx.find("#d1"));
|
||||
div.should.equal(htmx.find(".c1"));
|
||||
div.should.equal(htmx.find(".c2"));
|
||||
div.should.equal(htmx.find(".c1.c2"));
|
||||
});
|
||||
|
||||
it('should find properly from elt', function(){
|
||||
var div = make("<div><a id='a1'></a><a id='a2'></a></div>");
|
||||
htmx.find(div, "a").id.should.equal('a1');
|
||||
});
|
||||
|
||||
it('should find all properly', function(){
|
||||
var div = make("<div class='c1 c2 c3'><div class='c1 c2'><div class='c1'>");
|
||||
htmx.findAll(".c1").length.should.equal(3);
|
||||
htmx.findAll(".c2").length.should.equal(2);
|
||||
htmx.findAll(".c3").length.should.equal(1);
|
||||
});
|
||||
|
||||
it('should find all properly from elt', function(){
|
||||
var div = make("<div><div class='c1 c2 c3'><div class='c1 c2'><div class='c1'></div>");
|
||||
htmx.findAll(div, ".c1").length.should.equal(3);
|
||||
htmx.findAll(div, ".c2").length.should.equal(2);
|
||||
htmx.findAll(div,".c3").length.should.equal(1);
|
||||
});
|
||||
|
||||
it('should find closest element properly', function () {
|
||||
var div = make("<div><a id='a1'></a><a id='a2'></a></div>");
|
||||
var a = htmx.find(div, "a");
|
||||
htmx.closest(a, "div").should.equal(div);
|
||||
});
|
||||
|
||||
it('should remove element properly', function () {
|
||||
var div = make("<div><a></a></div>");
|
||||
var a = htmx.find(div, "a");
|
||||
htmx.remove(a);
|
||||
div.innerHTML.should.equal("");
|
||||
});
|
||||
|
||||
it('should add class properly', function () {
|
||||
var div = make("<div></div>");
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
htmx.addClass(div, "foo");
|
||||
div.classList.contains("foo").should.equal(true);
|
||||
});
|
||||
|
||||
it('should add class properly after delay', function (done) {
|
||||
var div = make("<div></div>");
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
htmx.addClass(div, "foo", 10);
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
setTimeout(function () {
|
||||
div.classList.contains("foo").should.equal(true);
|
||||
done();
|
||||
}, 20);
|
||||
});
|
||||
|
||||
it('should remove class properly', function () {
|
||||
var div = make("<div></div>");
|
||||
htmx.addClass(div, "foo");
|
||||
div.classList.contains("foo").should.equal(true);
|
||||
htmx.removeClass(div, "foo");
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
});
|
||||
|
||||
it('should add class properly after delay', function (done) {
|
||||
var div = make("<div></div>");
|
||||
htmx.addClass(div, "foo");
|
||||
div.classList.contains("foo").should.equal(true);
|
||||
htmx.removeClass(div, "foo", 10);
|
||||
div.classList.contains("foo").should.equal(true);
|
||||
setTimeout(function () {
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
done();
|
||||
}, 20);
|
||||
});
|
||||
|
||||
it('should toggle class properly', function () {
|
||||
var div = make("<div></div>");
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
htmx.toggleClass(div, "foo");
|
||||
div.classList.contains("foo").should.equal(true);
|
||||
htmx.toggleClass(div, "foo");
|
||||
div.classList.contains("foo").should.equal(false);
|
||||
});
|
||||
|
||||
it('should take class properly', function () {
|
||||
var div1 = make("<div></div>");
|
||||
var div2 = make("<div></div>");
|
||||
var div3 = make("<div></div>");
|
||||
|
||||
div1.classList.contains("foo").should.equal(false);
|
||||
div2.classList.contains("foo").should.equal(false);
|
||||
div3.classList.contains("foo").should.equal(false);
|
||||
|
||||
htmx.takeClass(div1, "foo");
|
||||
|
||||
div1.classList.contains("foo").should.equal(true);
|
||||
div2.classList.contains("foo").should.equal(false);
|
||||
div3.classList.contains("foo").should.equal(false);
|
||||
|
||||
htmx.takeClass(div2, "foo");
|
||||
|
||||
div1.classList.contains("foo").should.equal(false);
|
||||
div2.classList.contains("foo").should.equal(true);
|
||||
div3.classList.contains("foo").should.equal(false);
|
||||
|
||||
htmx.takeClass(div3, "foo");
|
||||
|
||||
div1.classList.contains("foo").should.equal(false);
|
||||
div2.classList.contains("foo").should.equal(false);
|
||||
div3.classList.contains("foo").should.equal(true);
|
||||
});
|
||||
|
||||
|
||||
})
|
122
www/test/0.0.5/test/core/events.js
Normal file
122
www/test/0.0.5/test/core/events.js
Normal file
@ -0,0 +1,122 @@
|
||||
describe("Core htmx Events", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it("load.htmx fires properly", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("load.htmx", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("GET", "/test", "");
|
||||
this.server.respondWith("GET", "/test", "<div></div>");
|
||||
var div = make("<div hx-get='/test'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("load.htmx", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("configRequest.htmx allows attribute addition", function () {
|
||||
var handler = htmx.on("configRequest.htmx", function (evt) {
|
||||
evt.detail.parameters['param'] = "true";
|
||||
});
|
||||
try {
|
||||
var param = null;
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
param = getParameters(xhr)['param'];
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make("<div hx-post='/test'></div>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
param.should.equal("true");
|
||||
} finally {
|
||||
htmx.off("configRequest.htmx", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("configRequest.htmx allows attribute removal", function () {
|
||||
var param = "foo";
|
||||
var handler = htmx.on("configRequest.htmx", function (evt) {
|
||||
delete evt.detail.parameters['param'];
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
param = getParameters(xhr)['param'];
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make("<form hx-trigger='click' hx-post='/test'><input name='param' value='foo'></form>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(param, undefined);
|
||||
} finally {
|
||||
htmx.off("configRequest.htmx", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("configRequest.htmx allows header tweaking", function () {
|
||||
var header = "foo";
|
||||
var handler = htmx.on("configRequest.htmx", function (evt) {
|
||||
evt.detail.headers['X-My-Header'] = "bar";
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
header = xhr.requestHeaders['X-My-Header'];
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make("<form hx-trigger='click' hx-post='/test'><input name='param' value='foo'></form>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(header, "bar");
|
||||
} finally {
|
||||
htmx.off("configRequest.htmx", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("afterSwap.htmx is called when replacing outerHTML", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("afterSwap.htmx", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "<button>Bar</button>");
|
||||
});
|
||||
var div = make("<button hx-post='/test' hx-swap='outerHTML'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("afterSwap.htmx", handler);
|
||||
}
|
||||
});
|
||||
|
||||
it("afterSettle.htmx is called when replacing outerHTML", function () {
|
||||
var called = false;
|
||||
var handler = htmx.on("afterSettle.htmx", function (evt) {
|
||||
called = true;
|
||||
});
|
||||
try {
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, "<button>Bar</button>");
|
||||
});
|
||||
var div = make("<button hx-post='/test' hx-swap='outerHTML'>Foo</button>");
|
||||
div.click();
|
||||
this.server.respond();
|
||||
should.equal(called, true);
|
||||
} finally {
|
||||
htmx.off("afterSettle.htmx", handler);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
110
www/test/0.0.5/test/core/headers.js
Normal file
110
www/test/0.0.5/test/core/headers.js
Normal file
@ -0,0 +1,110 @@
|
||||
describe("Core htmx AJAX headers", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it("should include the HX-Request header", function(){
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
xhr.requestHeaders['HX-Request'].should.be.equal('true');
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
})
|
||||
|
||||
it("should include the HX-Trigger header", function(){
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
xhr.requestHeaders['HX-Trigger'].should.equal('d1');
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make('<div id="d1" hx-get="/test"></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
})
|
||||
|
||||
it("should include the HX-Trigger-Name header", function(){
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
xhr.requestHeaders['HX-Trigger-Name'].should.equal('n1');
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make('<button name="n1" hx-get="/test"></button>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
})
|
||||
|
||||
it("should include the HX-Target header", function(){
|
||||
this.server.respondWith("GET", "/test", function(xhr){
|
||||
xhr.requestHeaders['HX-Target'].should.equal('d1');
|
||||
xhr.respond(200, {}, "");
|
||||
});
|
||||
var div = make('<div hx-target="#d1" hx-get="/test"></div><div id="d1" ></div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
})
|
||||
|
||||
it("should handle simple string HX-Trigger response header properly", function(){
|
||||
this.server.respondWith("GET", "/test", [200, {"HX-Trigger" : "foo"}, ""]);
|
||||
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
var invokedEvent = false;
|
||||
div.addEventListener("foo", function (evt) {
|
||||
invokedEvent = true;
|
||||
});
|
||||
div.click();
|
||||
this.server.respond();
|
||||
invokedEvent.should.equal(true);
|
||||
})
|
||||
|
||||
it("should handle basic JSON HX-Trigger response header properly", function(){
|
||||
this.server.respondWith("GET", "/test", [200, {"HX-Trigger" : "{\"foo\":null}"}, ""]);
|
||||
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
var invokedEvent = false;
|
||||
div.addEventListener("foo", function (evt) {
|
||||
invokedEvent = true;
|
||||
should.equal(null, evt.detail.value);
|
||||
evt.detail.elt.should.equal(div);
|
||||
});
|
||||
div.click();
|
||||
this.server.respond();
|
||||
invokedEvent.should.equal(true);
|
||||
})
|
||||
|
||||
it("should handle JSON with array arg HX-Trigger response header properly", function(){
|
||||
this.server.respondWith("GET", "/test", [200, {"HX-Trigger" : "{\"foo\":[1, 2, 3]}"}, ""]);
|
||||
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
var invokedEvent = false;
|
||||
div.addEventListener("foo", function (evt) {
|
||||
invokedEvent = true;
|
||||
evt.detail.elt.should.equal(div);
|
||||
evt.detail.value.should.deep.equal([1, 2, 3]);
|
||||
});
|
||||
div.click();
|
||||
this.server.respond();
|
||||
invokedEvent.should.equal(true);
|
||||
})
|
||||
|
||||
it("should handle JSON with array arg HX-Trigger response header properly", function(){
|
||||
this.server.respondWith("GET", "/test", [200, {"HX-Trigger" : "{\"foo\":{\"a\":1, \"b\":2}}"}, ""]);
|
||||
|
||||
var div = make('<div hx-get="/test"></div>');
|
||||
var invokedEvent = false;
|
||||
div.addEventListener("foo", function (evt) {
|
||||
invokedEvent = true;
|
||||
evt.detail.elt.should.equal(div);
|
||||
evt.detail.a.should.equal(1);
|
||||
evt.detail.b.should.equal(2);
|
||||
});
|
||||
div.click();
|
||||
this.server.respond();
|
||||
invokedEvent.should.equal(true);
|
||||
})
|
||||
|
||||
});
|
23
www/test/0.0.5/test/core/internals.js
Normal file
23
www/test/0.0.5/test/core/internals.js
Normal file
@ -0,0 +1,23 @@
|
||||
describe("Core htmx internals Tests", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it("makeFragment works with janky stuff", function(){
|
||||
htmx._("makeFragment")("<html></html>").tagName.should.equal("BODY");
|
||||
htmx._("makeFragment")("<html><body></body></html>").tagName.should.equal("BODY");
|
||||
|
||||
//NB - the tag name should be the *parent* element hosting the HTML since we use the fragment children
|
||||
// for the swap
|
||||
htmx._("makeFragment")("<td></td>").tagName.should.equal("TR");
|
||||
htmx._("makeFragment")("<thead></thead>").tagName.should.equal("TABLE");
|
||||
htmx._("makeFragment")("<col></col>").tagName.should.equal("COLGROUP");
|
||||
htmx._("makeFragment")("<tr></tr>").tagName.should.equal("TBODY");
|
||||
})
|
||||
|
||||
});
|
121
www/test/0.0.5/test/core/parameters.js
Normal file
121
www/test/0.0.5/test/core/parameters.js
Normal file
@ -0,0 +1,121 @@
|
||||
describe("Core htmx Parameter Handling", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('Input includes value', function () {
|
||||
var input = make('<input name="foo" value="bar"/>');
|
||||
var vals = htmx._('getInputValues')(input);
|
||||
vals['foo'].should.equal('bar');
|
||||
})
|
||||
|
||||
it('Input includes value on get', function () {
|
||||
var input = make('<input name="foo" value="bar"/>');
|
||||
var vals = htmx._('getInputValues')(input, "get");
|
||||
vals['foo'].should.equal('bar');
|
||||
})
|
||||
|
||||
it('Input includes form', function () {
|
||||
var form = make('<form><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var input = byId('i1');
|
||||
var vals = htmx._('getInputValues')(input);
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.equal('rey');
|
||||
})
|
||||
|
||||
it('Input doesnt include form on get', function () {
|
||||
var form = make('<form><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var input = byId('i1');
|
||||
var vals = htmx._('getInputValues')(input, 'get');
|
||||
vals['foo'].should.equal('bar');
|
||||
should.equal(vals['do'], undefined);
|
||||
})
|
||||
|
||||
it('non-input includes form', function () {
|
||||
var form = make('<form><div id="d1"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var div = byId('d1');
|
||||
var vals = htmx._('getInputValues')(div, "post");
|
||||
vals['do'].should.equal('rey');
|
||||
})
|
||||
|
||||
it('non-input doesnt include form on get', function () {
|
||||
var form = make('<form><div id="d1"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var div = byId('d1');
|
||||
var vals = htmx._('getInputValues')(div, "get");
|
||||
should.equal(vals['do'], undefined);
|
||||
})
|
||||
|
||||
it('Basic form works on get', function () {
|
||||
var form = make('<form><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var vals = htmx._('getInputValues')(form, 'get');
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.equal('rey');
|
||||
})
|
||||
|
||||
it('Basic form works on non-get', function () {
|
||||
var form = make('<form><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var vals = htmx._('getInputValues')(form, 'post');
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.equal('rey');
|
||||
})
|
||||
|
||||
it('Double values are included as array', function () {
|
||||
var form = make('<form><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var vals = htmx._('getInputValues')(form);
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.deep.equal(['rey', 'rey']);
|
||||
})
|
||||
|
||||
it('Double values are included as array in correct order', function () {
|
||||
var form = make('<form><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey1"/><input id="i3" name="do" value="rey2"/></form>');
|
||||
var vals = htmx._('getInputValues')(byId("i3"));
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.deep.equal(['rey1', 'rey2']);
|
||||
})
|
||||
|
||||
it('hx-include works with form', function () {
|
||||
var form = make('<form id="f1"><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var div = make('<div hx-include="#f1"></div>');
|
||||
var vals = htmx._('getInputValues')(div);
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.deep.equal(['rey', 'rey']);
|
||||
})
|
||||
|
||||
it('hx-include works with input', function () {
|
||||
var form = make('<form id="f1"><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var div = make('<div hx-include="#i1"></div>');
|
||||
var vals = htmx._('getInputValues')(div);
|
||||
vals['foo'].should.equal('bar');
|
||||
should.equal(vals['do'], undefined);
|
||||
})
|
||||
|
||||
it('hx-include works with two inputs', function () {
|
||||
var form = make('<form id="f1"><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var div = make('<div hx-include="#i1, #i2"></div>');
|
||||
var vals = htmx._('getInputValues')(div);
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.deep.equal(['rey', 'rey']);
|
||||
})
|
||||
|
||||
it('hx-include works with two inputs, plus form', function () {
|
||||
var form = make('<form id="f1"><input id="i1" name="foo" value="bar"/><input id="i2" name="do" value="rey"/><input id="i2" name="do" value="rey"/></form>');
|
||||
var div = make('<div hx-include="#i1, #i2, #f1"></div>');
|
||||
var vals = htmx._('getInputValues')(div);
|
||||
vals['foo'].should.equal('bar');
|
||||
vals['do'].should.deep.equal(['rey', 'rey']);
|
||||
})
|
||||
|
||||
it('correctly URL escapes values', function () {
|
||||
htmx._("urlEncode")({}).should.equal("");
|
||||
htmx._("urlEncode")({"foo": "bar"}).should.equal("foo=bar");
|
||||
htmx._("urlEncode")({"foo": "bar", "do" : "rey"}).should.equal("foo=bar&do=rey");
|
||||
htmx._("urlEncode")({"foo": "bar", "do" : ["rey", "blah"]}).should.equal("foo=bar&do=rey&do=blah");
|
||||
});
|
||||
|
||||
});
|
||||
|
70
www/test/0.0.5/test/core/perf.js
Normal file
70
www/test/0.0.5/test/core/perf.js
Normal file
@ -0,0 +1,70 @@
|
||||
describe("Core htmx perf Tests", function() {
|
||||
|
||||
var HTMX_HISTORY_CACHE_NAME = "htmx-history-cache";
|
||||
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME);
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
localStorage.removeItem(HTMX_HISTORY_CACHE_NAME);
|
||||
});
|
||||
|
||||
function stringRepeat(str, num) {
|
||||
num = Number(num);
|
||||
|
||||
var result = '';
|
||||
while (true) {
|
||||
if (num & 1) { // (1)
|
||||
result += str;
|
||||
}
|
||||
num >>>= 1; // (2)
|
||||
if (num <= 0) break;
|
||||
str += str;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
it("DOM processing should be fast", function(){
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
|
||||
// create an entry with a large content string (256k) and see how fast we can write and read it
|
||||
// to local storage as a single entry
|
||||
var str = stringRepeat("<div>", 30) + stringRepeat("<div><div><span><button hx-get='/test'> Test Get Button </button></span></div></div>\n", 1000) + stringRepeat("</div>", 30);
|
||||
var start = performance.now();
|
||||
var stuff = make(str);
|
||||
var end = performance.now();
|
||||
var timeInMs = end - start;
|
||||
|
||||
// make sure the DOM actually processed
|
||||
var firstBtn = stuff.querySelector("button");
|
||||
firstBtn.click();
|
||||
this.server.respond();
|
||||
firstBtn.innerHTML.should.equal("Clicked!");
|
||||
|
||||
chai.assert(timeInMs < 100, "Should take less than 100ms on most platforms, took: " + timeInMs + "ms");
|
||||
})
|
||||
|
||||
it("history implementation should be fast", function(){
|
||||
// create an entry with a large content string (256k) and see how fast we can write and read it
|
||||
// to local storage as a single entry
|
||||
var entry = {url: stringRepeat("x", 32), content:stringRepeat("x", 256*1024)}
|
||||
var array = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
array.push(entry);
|
||||
}
|
||||
var start = performance.now();
|
||||
var string = JSON.stringify(array);
|
||||
localStorage.setItem(HTMX_HISTORY_CACHE_NAME, string);
|
||||
var reReadString = localStorage.getItem(HTMX_HISTORY_CACHE_NAME);
|
||||
var finalJson = JSON.parse(reReadString);
|
||||
var end = performance.now();
|
||||
var timeInMs = end - start;
|
||||
chai.assert(timeInMs < 300, "Should take less than 300ms on most platforms");
|
||||
})
|
||||
|
||||
})
|
68
www/test/0.0.5/test/core/regressions.js
Normal file
68
www/test/0.0.5/test/core/regressions.js
Normal file
@ -0,0 +1,68 @@
|
||||
describe("Core htmx Regression Tests", function(){
|
||||
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('SVGs process properly in IE11', function()
|
||||
{
|
||||
var btn = make('<svg onclick="document.getElementById(\'contents\').classList.toggle(\'show\')" class="hamburger" viewBox="0 0 100 80" width="25" height="25" style="margin-bottom:-5px">\n' +
|
||||
'<rect width="100" height="20" style="fill:rgb(52, 101, 164)" rx="10"></rect>\n' +
|
||||
'<rect y="30" width="100" height="20" style="fill:rgb(52, 101, 164)" rx="10"></rect>\n' +
|
||||
'<rect y="60" width="100" height="20" style="fill:rgb(52, 101, 164)" rx="10"></rect>\n' +
|
||||
'</svg>')
|
||||
});
|
||||
|
||||
it ('Handles https://github.com/bigskysoftware/htmx/issues/4 properly', function() {
|
||||
this.server.respondWith("GET", "/index2a.php",
|
||||
"<div id='message' hx-swap-oob='true'>I came from message oob swap I should be second</div>" +
|
||||
"<div id='message2' hx-swap-oob='true'>I came from a message2 oob swap I should be third but I am in the wrong spot</div>" +
|
||||
"I'm page2 content (non-swap) I should be first")
|
||||
|
||||
var h1 = make("<h1 hx-get='/index2a.php' hx-target='#page2' hx-trigger='click'>Kutty CLICK ME</h1>" +
|
||||
"<div id='page2' ></div>" +
|
||||
"<div id='message'></div>" +
|
||||
"<div id='message2'></div>")
|
||||
h1.click();
|
||||
this.server.respond();
|
||||
htmx.find("#page2").innerHTML.should.equal("I'm page2 content (non-swap) I should be first")
|
||||
htmx.find("#message").innerHTML.should.equal("I came from message oob swap I should be second")
|
||||
htmx.find("#message2").innerHTML.should.equal("I came from a message2 oob swap I should be third but I am in the wrong spot")
|
||||
});
|
||||
|
||||
it ('Handles https://github.com/bigskysoftware/htmx/issues/33 "empty values" properly', function() {
|
||||
this.server.respondWith("POST", "/htmx.php", function (xhr) {
|
||||
xhr.respond(200, {}, xhr.requestBody);
|
||||
});
|
||||
|
||||
var form = make('<form hx-trigger="click" hx-post="/htmx.php">\n' +
|
||||
'<input type="text" name="variable" value="">\n' +
|
||||
'<button type="submit">Submit</button>\n' +
|
||||
'</form>')
|
||||
form.click();
|
||||
this.server.respond();
|
||||
form.innerHTML.should.equal("variable=")
|
||||
});
|
||||
|
||||
it ('name=id doesnt cause an error', function(){
|
||||
this.server.respondWith("GET", "/test", "Foo<form><input name=\"id\"/></form>")
|
||||
var div = make('<div hx-get="/test">Get It</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.contain("Foo")
|
||||
});
|
||||
|
||||
it ('empty id doesnt cause an error', function(){
|
||||
this.server.respondWith("GET", "/test", "Foo\n<div id=''></div>")
|
||||
var div = make('<div hx-get="/test">Get It</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerText.should.contain("Foo")
|
||||
});
|
||||
|
||||
})
|
44
www/test/0.0.5/test/core/verbs.js
Normal file
44
www/test/0.0.5/test/core/verbs.js
Normal file
@ -0,0 +1,44 @@
|
||||
describe("Core htmx AJAX Verbs", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('handles basic posts properly', function () {
|
||||
this.server.respondWith("POST", "/test", "post");
|
||||
var div = make('<div hx-post="/test">click me</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("post");
|
||||
})
|
||||
|
||||
it('handles basic put properly', function () {
|
||||
this.server.respondWith("PUT", "/test", "put");
|
||||
var div = make('<div hx-put="/test">click me</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("put");
|
||||
})
|
||||
|
||||
it('handles basic patch properly', function () {
|
||||
this.server.respondWith("PATCH", "/test", "patch");
|
||||
var div = make('<div hx-patch="/test">click me</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("patch");
|
||||
})
|
||||
|
||||
it('handles basic delete properly', function () {
|
||||
this.server.respondWith("DELETE", "/test", "delete");
|
||||
var div = make('<div hx-delete="/test">click me</div>');
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("delete");
|
||||
})
|
||||
|
||||
});
|
||||
|
21
www/test/0.0.5/test/ext/ajax-header.js
Normal file
21
www/test/0.0.5/test/ext/ajax-header.js
Normal file
@ -0,0 +1,21 @@
|
||||
describe("ajax-header extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('Sends the X-Requested-With header', function () {
|
||||
this.server.respondWith("GET", "/test", function (xhr) {
|
||||
xhr.respond(200, {}, xhr.requestHeaders['X-Requested-With'])
|
||||
});
|
||||
var btn = make('<button hx-get="/test" hx-ext="ajax-header">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("XMLHttpRequest");
|
||||
});
|
||||
|
||||
});
|
27
www/test/0.0.5/test/ext/bad-extension.js
Normal file
27
www/test/0.0.5/test/ext/bad-extension.js
Normal file
@ -0,0 +1,27 @@
|
||||
describe("bad extension", function() {
|
||||
htmx.defineExtension("bad-extension", {
|
||||
onEvent : function(name, evt) {throw "onEvent"},
|
||||
transformResponse : function(text, xhr, elt) {throw "transformRequest"},
|
||||
isInlineSwap : function(swapStyle) {throw "isInlineSwap"},
|
||||
handleSwap : function(swapStyle, target, fragment, settleInfo) {throw "handleSwap"},
|
||||
encodeParameters : function(xhr, parameters, elt) {throw "encodeParmeters"}
|
||||
}
|
||||
)
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('does not blow up rendering', function () {
|
||||
this.server.respondWith("GET", "/test", "clicked!");
|
||||
var div = make('<div hx-get="/test" hx-ext="bad-extension">Click Me!</div>')
|
||||
div.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("clicked!");
|
||||
});
|
||||
|
||||
});
|
55
www/test/0.0.5/test/ext/class-tools.js
Normal file
55
www/test/0.0.5/test/ext/class-tools.js
Normal file
@ -0,0 +1,55 @@
|
||||
describe("class-tools extension", function(){
|
||||
beforeEach(function() {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function() {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('adds classes properly', function(done)
|
||||
{
|
||||
var div = make('<div hx-ext="class-tools" classes="add c1">Click Me!</div>')
|
||||
should.equal(div.classList.length, 0);
|
||||
setTimeout(function(){
|
||||
should.equal(div.classList.contains("c1"), true);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('removes classes properly', function(done)
|
||||
{
|
||||
var div = make('<div class="foo bar" hx-ext="class-tools" classes="remove bar">Click Me!</div>')
|
||||
should.equal(div.classList.contains("foo"), true);
|
||||
should.equal(div.classList.contains("bar"), true);
|
||||
setTimeout(function(){
|
||||
should.equal(div.classList.contains("foo"), true);
|
||||
should.equal(div.classList.contains("bar"), false);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('adds classes properly w/ data-* prefix', function(done)
|
||||
{
|
||||
var div = make('<div hx-ext="class-tools" data-classes="add c1">Click Me!</div>')
|
||||
should.equal(div.classList.length, 0);
|
||||
setTimeout(function(){
|
||||
should.equal(div.classList.contains("c1"), true);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('extension can be on parent', function(done)
|
||||
{
|
||||
var div = make('<div hx-ext="class-tools"><div id="d1" classes="add c1">Click Me!</div></div>')
|
||||
should.equal(div.classList.length, 0);
|
||||
setTimeout(function(){
|
||||
should.equal(div.classList.contains("c1"), false);
|
||||
should.equal(byId("d1").classList.contains("c1"), true);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
|
||||
})
|
30
www/test/0.0.5/test/ext/client-side-templates.js
Normal file
30
www/test/0.0.5/test/ext/client-side-templates.js
Normal file
@ -0,0 +1,30 @@
|
||||
describe("client-side-templates extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('works on basic mustache template', function () {
|
||||
this.server.respondWith("GET", "/test", '{"foo":"bar"}');
|
||||
var btn = make('<button hx-get="/test" hx-ext="client-side-templates" mustache-template="mt1">Click Me!</button>')
|
||||
make('<script id="mt1" type="x-tmpl-mustache">*{{foo}}*</script>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("*bar*");
|
||||
});
|
||||
|
||||
it('works on basic handlebars template', function () {
|
||||
this.server.respondWith("GET", "/test", '{"foo":"bar"}');
|
||||
var btn = make('<button hx-get="/test" hx-ext="client-side-templates" handlebars-template="hb1">Click Me!</button>')
|
||||
Handlebars.partials["hb1"] = Handlebars.compile("*{{foo}}*");
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("*bar*");
|
||||
});
|
||||
|
||||
|
||||
});
|
19
www/test/0.0.5/test/ext/debug.js
Normal file
19
www/test/0.0.5/test/ext/debug.js
Normal file
@ -0,0 +1,19 @@
|
||||
describe("debug extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('works on basic request', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button hx-get="/test" hx-ext="debug">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Clicked!");
|
||||
});
|
||||
|
||||
});
|
37
www/test/0.0.5/test/ext/hyperscript.js
Normal file
37
www/test/0.0.5/test/ext/hyperscript.js
Normal file
@ -0,0 +1,37 @@
|
||||
describe("hyperscript integration", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('can trigger with a custom event', function () {
|
||||
this.server.respondWith("GET", "/test", "Custom Event Sent!");
|
||||
var btn = make('<button _="on click send customEvent" hx-trigger="customEvent" hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.innerHTML.should.equal("Custom Event Sent!");
|
||||
});
|
||||
|
||||
it('can handle htmx driven events', function () {
|
||||
this.server.respondWith("GET", "/test", "Clicked!");
|
||||
var btn = make('<button _="on afterSettle.htmx add .afterSettle" hx-get="/test">Click Me!</button>')
|
||||
btn.classList.contains("afterSettle").should.equal(false);
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
btn.classList.contains("afterSettle").should.equal(true);
|
||||
});
|
||||
|
||||
it('can handle htmx error events', function () {
|
||||
this.server.respondWith("GET", "/test", [404, {}, "Bad request"]);
|
||||
var div = make('<div id="d1"></div>')
|
||||
var btn = make('<button _="on error.htmx(errorInfo) put errorInfo.error into #d1.innerHTML" hx-get="/test">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
div.innerHTML.should.equal("Response Status Error Code 404 from /test");
|
||||
});
|
||||
|
||||
});
|
23
www/test/0.0.5/test/ext/include-vals.js
Normal file
23
www/test/0.0.5/test/ext/include-vals.js
Normal file
@ -0,0 +1,23 @@
|
||||
describe("include-vals extension", function() {
|
||||
beforeEach(function () {
|
||||
this.server = makeServer();
|
||||
clearWorkArea();
|
||||
});
|
||||
afterEach(function () {
|
||||
this.server.restore();
|
||||
clearWorkArea();
|
||||
});
|
||||
|
||||
it('Includes values properly', function () {
|
||||
var params = {};
|
||||
this.server.respondWith("POST", "/test", function (xhr) {
|
||||
params = getParameters(xhr);
|
||||
xhr.respond(200, {}, "clicked");
|
||||
});
|
||||
var btn = make('<button hx-post="/test" hx-ext="include-vals" include-vals="foo:\'bar\'">Click Me!</button>')
|
||||
btn.click();
|
||||
this.server.respond();
|
||||
params['foo'].should.equal("bar");
|
||||
});
|
||||
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user