Update htmx to latest hyperscript, fix tests

This commit is contained in:
carson 2020-09-11 08:00:45 -06:00
parent 9c592d4e2f
commit 45f3909b9c
4 changed files with 1104 additions and 1012 deletions

View File

@ -457,7 +457,7 @@ return (function () {
processNode(child);
processScripts(child);
processFocus(child)
triggerEvent(child, 'htmx:load', {});
triggerEvent(child, 'htmx:load');
};
}
@ -978,14 +978,10 @@ return (function () {
});
}
function isHyperScriptAvailable() {
return typeof _hyperscript !== "undefined";
}
function findElementsToProcess(elt) {
if (elt.querySelectorAll) {
var results = elt.querySelectorAll(VERB_SELECTOR + ", a, form, [hx-sse], [data-hx-sse], [hx-ws]," +
" [data-hx-ws], [_], [script], [data-script]");
" [data-hx-ws]");
return results;
} else {
return [];
@ -997,10 +993,6 @@ return (function () {
if (!nodeData.initialized) {
nodeData.initialized = true;
if (isHyperScriptAvailable()) {
_hyperscript.init(elt);
}
if (elt.value) {
nodeData.lastValue = elt.value;
}

View File

@ -11,6 +11,7 @@ describe("hyperscript integration", function() {
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>')
htmx.trigger(btn, "htmx:load"); // have to manually trigger the load event for non-AJAX dynamic content
btn.click();
this.server.respond();
btn.innerHTML.should.equal("Custom Event Sent!");
@ -19,6 +20,7 @@ describe("hyperscript integration", function() {
it('can handle htmx driven events', function () {
this.server.respondWith("GET", "/test", "Clicked!");
var btn = make('<button _="on htmx:afterSettle add .afterSettle" hx-get="/test">Click Me!</button>')
htmx.trigger(btn, "htmx:load");
btn.classList.contains("afterSettle").should.equal(false);
btn.click();
this.server.respond();
@ -29,9 +31,20 @@ describe("hyperscript integration", function() {
this.server.respondWith("GET", "/test", [404, {}, "Bad request"]);
var div = make('<div id="d1"></div>')
var btn = make('<button _="on htmx:error(errorInfo) put errorInfo.error into #d1.innerHTML" hx-get="/test">Click Me!</button>')
htmx.trigger(btn, "htmx:load");
btn.click();
this.server.respond();
div.innerHTML.should.equal("Response Status Error Code 404 from /test");
});
it('hyperscript in non-htmx annotated nodes is evaluated', function () {
this.server.respondWith("GET", "/test", "<div><div><div id='d1' _='on click put \"Clicked...\" into my.innerHTML'></div></div></div>");
var btn = make('<button hx-get="/test">Click Me!</button>')
btn.click();
this.server.respond();
var newDiv = byId("d1");
newDiv.click();
newDiv.innerText.should.equal("Clicked...");
});
});

View File

@ -92,9 +92,6 @@
<!-- hyperscript integration -->
<script src="lib/_hyperscript.js"></script>
<script src="ext/hyperscript.js"></script>
<script>
_hyperscript.start();
</script>
<!-- extension tests -->
<script src="ext/extension-swap.js"></script>

View File

@ -11,9 +11,39 @@
return (function () {
'use strict';
//-----------------------------------------------
//====================================================================
// Utilities
//====================================================================
function mergeObjects(obj1, obj2) {
for (var key in obj2) {
if (obj2.hasOwnProperty(key)) {
obj1[key] = obj2[key];
}
}
return obj1;
}
function parseJSON(jString) {
try {
return JSON.parse(jString);
} catch(error) {
logError(error);
return null;
}
}
function logError(msg) {
if(console.error) {
console.error(msg);
} else if (console.log) {
console.log("ERROR: ", msg);
}
}
//====================================================================
// Lexer
//-----------------------------------------------
//====================================================================
var _lexer = function () {
var OP_TABLE = {
'+': 'PLUS',
@ -77,6 +107,10 @@
(c >= 'A' && c <= 'Z');
}
function isIdentifierChar(c) {
return (c === "_" || c === "$");
}
function makeTokensObject(tokens, consumed, source) {
@ -205,7 +239,7 @@
tokens.push(consumeClassReference());
} else if (!possiblePrecedingSymbol() && currentChar() === "#" && isAlpha(nextChar())) {
tokens.push(consumeIdReference());
} else if (isAlpha(currentChar())) {
} else if (isAlpha(currentChar()) || isIdentifierChar(currentChar())) {
tokens.push(consumeIdentifier());
} else if (isNumeric(currentChar())) {
tokens.push(consumeNumber());
@ -273,7 +307,7 @@
function consumeIdentifier() {
var identifier = makeToken("IDENTIFIER");
var value = consumeChar();
while (isAlpha(currentChar())) {
while (isAlpha(currentChar()) || isIdentifierChar(currentChar())) {
value += consumeChar();
}
identifier.value = value;
@ -369,9 +403,9 @@
}
}();
//-----------------------------------------------
//====================================================================
// Parser
//-----------------------------------------------
//====================================================================
var _parser = function () {
var GRAMMAR = {}
@ -440,11 +474,10 @@
}
}();
//-----------------------------------------------
//====================================================================
// Runtime
//-----------------------------------------------
//====================================================================
var _runtime = function () {
var SCRIPT_ATTRIBUTES = ["_", "script", "data-script"];
function matchesSelector(elt, selector) {
// noinspection JSUnresolvedVariable
@ -509,9 +542,17 @@
return last;
}
var _scriptAttrs = null;
function getScriptAttributes() {
if (_scriptAttrs == null) {
_scriptAttrs = _hyperscript.config.attributes.replace(/ /g,'').split(",")
}
return _scriptAttrs;
}
function getScript(elt) {
for (var i = 0; i < SCRIPT_ATTRIBUTES.length; i++) {
var scriptAttribute = SCRIPT_ATTRIBUTES[i];
for (var i = 0; i < getScriptAttributes().length; i++) {
var scriptAttribute = getScriptAttributes()[i];
if (elt.hasAttribute && elt.hasAttribute(scriptAttribute)) {
return elt.getAttribute(scriptAttribute)
}
@ -525,12 +566,8 @@
});
}
function setScriptAttrs(values) {
SCRIPT_ATTRIBUTES = values;
}
function getScriptSelector() {
return SCRIPT_ATTRIBUTES.map(function (attribute) {
return getScriptAttributes().map(function (attribute) {
return "[" + attribute + "]";
}).join(", ");
}
@ -562,9 +599,23 @@
return eval(evalString).apply(null, args);
}
function processNode(elt) {
var selector = _runtime.getScriptSelector();
if (matchesSelector(elt, selector)) {
initElement(elt);
}
forEach(elt.querySelectorAll(selector), function (elt) {
initElement(elt);
})
}
function initElement(elt) {
var internalData = getInternalData(elt);
if (!internalData.initialized) {
var src = getScript(elt);
if (src) {
internalData.initialized = true;
internalData.script = src;
var tokens = _lexer.tokenize(src);
var hyperScript = _parser.parseHyperScript(tokens);
var transpiled = _parser.transpile(hyperScript);
@ -575,6 +626,17 @@
hyperscriptObj.applyEventListenersTo(elt);
}
}
}
function getInternalData(elt) {
var dataProp = 'hyperscript-internal-data';
var data = elt[dataProp];
if (!data) {
data = elt[dataProp] = {};
}
return data;
}
function ajax(method, url, callback, data) {
var xhr = new XMLHttpRequest();
@ -606,18 +668,17 @@
matchesSelector: matchesSelector,
getScript: getScript,
applyEventListeners: applyEventListeners,
setScriptAttrs: setScriptAttrs,
initElement: initElement,
processNode: processNode,
evaluate: evaluate,
getScriptSelector: getScriptSelector,
ajax: ajax,
}
}();
//-----------------------------------------------
// Expressions
//-----------------------------------------------
//====================================================================
// Grammar
//====================================================================
{
_parser.addGrammarElement("parenthesized", function (parser, tokens) {
if (tokens.matchOpToken('(')) {
var expr = parser.parseElement("expression", tokens);
@ -657,7 +718,9 @@
type: "nakedString",
tokens: tokenArr,
transpile: function () {
return "'" + tokenArr.map(function(t){return t.value}).join("") + "'";
return "'" + tokenArr.map(function (t) {
return t.value
}).join("") + "'";
}
}
}
@ -872,7 +935,9 @@
type: "arrayLiteral",
values: values,
transpile: function () {
return "[" + values.map(function(v){ return parser.transpile(v) }).join(", ") + "]";
return "[" + values.map(function (v) {
return parser.transpile(v)
}).join(", ") + "]";
}
}
}
@ -931,10 +996,12 @@
_parser.addGrammarElement("functionCall", function (parser, tokens, root) {
if (tokens.matchOpToken("(")) {
var args = [];
if (!tokens.matchOpToken(')')) {
do {
args.push(parser.parseElement("expression", tokens));
} while (tokens.matchOpToken(","))
tokens.requireOpToken(")");
}
var functionCall = {
type: "functionCall",
root: root,
@ -1169,7 +1236,6 @@
};
})
_parser.addGrammarElement("eventListener", function (parser, tokens) {
tokens.requireToken("on");
var on = parser.parseElement("dotOrColonPath", tokens);
@ -1204,7 +1270,9 @@
"var my = me;\n" +
"_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function(target){\n" +
" target.addEventListener('" + parser.transpile(on) + "', function(event){\n" +
args.map(function(arg){return "var " + arg.value + " = event.detail." + arg.value + ";"}).join("\n") + "\n" +
args.map(function (arg) {
return "var " + arg.value + " = event.detail." + arg.value + ";"
}).join("\n") + "\n" +
parser.transpile(start) +
" })\n" +
"})\n" +
@ -1645,45 +1713,67 @@
};
}
})
}
//-----------------------------------------------
//====================================================================
// API
//-----------------------------------------------
//====================================================================
function start(scriptAttrs) {
if (scriptAttrs) {
_runtime.setScriptAttrs(scriptAttrs);
}
var fn = function () {
var elements = document.querySelectorAll(_runtime.getScriptSelector());
_runtime.forEach(elements, function (elt) {
init(elt);
})
};
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
return true;
}
function init(elt) {
_runtime.initElement(elt);
function processNode(elt) {
_runtime.processNode(elt);
}
function evaluate(str) {
return _runtime.evaluate(str);
}
return {
//====================================================================
// Initialization
//====================================================================
function ready(fn) {
if (document.readyState !== 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
function getMetaConfig() {
var element = document.querySelector('meta[name="htmx-config"]');
if (element) {
return parseJSON(element.content);
} else {
return null;
}
}
function mergeMetaConfig() {
var metaConfig = getMetaConfig();
if (metaConfig) {
_hyperscript.config = mergeObjects(_hyperscript.config , metaConfig)
}
}
var _hyperscript = {
lexer: _lexer,
parser: _parser,
runtime: _runtime,
evaluate: evaluate,
init: init,
start: start
processNode: processNode,
config: {
attributes : "_, script, data-script"
}
};
ready(function () {
mergeMetaConfig();
processNode(document.body);
document.addEventListener("htmx:load", function(evt){
processNode(evt.detail.elt);
})
})
return _hyperscript;
}
)()
}));