From 45f3909b9c6c76fe567e583d883f9589fd3693ba Mon Sep 17 00:00:00 2001 From: carson Date: Fri, 11 Sep 2020 08:00:45 -0600 Subject: [PATCH] Update htmx to latest hyperscript, fix tests --- src/htmx.js | 12 +- test/ext/hyperscript.js | 13 + test/index.html | 3 - test/lib/_hyperscript.js | 2088 ++++++++++++++++++++------------------ 4 files changed, 1104 insertions(+), 1012 deletions(-) diff --git a/src/htmx.js b/src/htmx.js index 01d808f4..dc415cff 100644 --- a/src/htmx.js +++ b/src/htmx.js @@ -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; } diff --git a/test/ext/hyperscript.js b/test/ext/hyperscript.js index 07b93bfd..55bfc252 100644 --- a/test/ext/hyperscript.js +++ b/test/ext/hyperscript.js @@ -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('') + 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('') + 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('
') var btn = make('') + 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", "
"); + var btn = make('') + btn.click(); + this.server.respond(); + var newDiv = byId("d1"); + newDiv.click(); + newDiv.innerText.should.equal("Clicked..."); + }); + }); \ No newline at end of file diff --git a/test/index.html b/test/index.html index dfca4a26..a9e4d720 100644 --- a/test/index.html +++ b/test/index.html @@ -92,9 +92,6 @@ - diff --git a/test/lib/_hyperscript.js b/test/lib/_hyperscript.js index 0638762f..4e6e40f4 100644 --- a/test/lib/_hyperscript.js +++ b/test/lib/_hyperscript.js @@ -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,20 +599,45 @@ 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 src = getScript(elt); - if (src) { - var tokens = _lexer.tokenize(src); - var hyperScript = _parser.parseHyperScript(tokens); - var transpiled = _parser.transpile(hyperScript); - if (elt.getAttribute('debug') === "true") { - console.log(transpiled); + 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); + if (elt.getAttribute('debug') === "true") { + console.log(transpiled); + } + var hyperscriptObj = eval(transpiled); + hyperscriptObj.applyEventListenersTo(elt); } - var hyperscriptObj = eval(transpiled); - 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(); xhr.onload = function() { @@ -606,1084 +668,1112 @@ matchesSelector: matchesSelector, getScript: getScript, applyEventListeners: applyEventListeners, - setScriptAttrs: setScriptAttrs, - initElement: initElement, + processNode: processNode, evaluate: evaluate, getScriptSelector: getScriptSelector, ajax: ajax, } }(); - //----------------------------------------------- - // Expressions - //----------------------------------------------- - - _parser.addGrammarElement("parenthesized", function (parser, tokens) { - if (tokens.matchOpToken('(')) { - var expr = parser.parseElement("expression", tokens); - tokens.requireOpToken(")"); - return { - type: "parenthesized", - expr: expr, - transpile: function () { - return "(" + parser.transpile(expr) + ")"; - } - } - } - }) - - _parser.addGrammarElement("string", function (parser, tokens) { - var stringToken = tokens.matchTokenType('STRING'); - if (stringToken) { - return { - type: "string", - token: stringToken, - transpile: function () { - if (stringToken.value.indexOf("'") === 0) { - return "'" + stringToken.value + "'"; - } else { - return '"' + stringToken.value + '"'; - } - } - } - } - }) - - _parser.addGrammarElement("nakedString", function (parser, tokens) { - if (tokens.hasMore()) { - var tokenArr = tokens.consumeUntilWhitespace(); - tokens.matchTokenType("WHITESPACE"); - return { - type: "nakedString", - tokens: tokenArr, - transpile: function () { - return "'" + tokenArr.map(function(t){return t.value}).join("") + "'"; - } - } - } - }) - - _parser.addGrammarElement("number", function (parser, tokens) { - var number = tokens.matchTokenType('NUMBER'); - if (number) { - var numberToken = number; - var value = parseFloat(number.value) - return { - type: "number", - value: value, - numberToken: numberToken, - transpile: function () { - return numberToken.value; - } - } - } - }) - - _parser.addGrammarElement("idRef", function (parser, tokens) { - var elementId = tokens.matchTokenType('ID_REF'); - if (elementId) { - return { - type: "idRef", - value: elementId.value.substr(1), - transpile: function () { - return "document.getElementById('" + this.value + "')" - } - }; - } - }) - - _parser.addGrammarElement("classRef", function (parser, tokens) { - var classRef = tokens.matchTokenType('CLASS_REF'); - if (classRef) { - return { - type: "classRef", - value: classRef.value, - className: function () { - return this.value.substr(1); - }, - transpile: function () { - return "document.querySelectorAll('" + this.value + "')" - } - }; - } - }) - - _parser.addGrammarElement("attributeRef", function (parser, tokens) { - if (tokens.matchOpToken("[")) { - var name = tokens.matchTokenType("IDENTIFIER"); - var value = null; - if (tokens.matchOpToken("=")) { - value = parser.parseElement("expression", tokens); - } - tokens.requireOpToken("]"); - return { - type: "attribute_expression", - name: name.value, - value: value, - transpile: function () { - if (this.value) { - return "({name: '" + this.name + "', value: " + parser.transpile(this.value) + "})"; - } else { - return "({name: '" + this.name + "'})"; - } - } - } - } - }) - - _parser.addGrammarElement("objectLiteral", function (parser, tokens) { - if (tokens.matchOpToken("{")) { - var fields = [] - if (!tokens.matchOpToken("}")) { - do { - var name = tokens.requireTokenType("IDENTIFIER"); - tokens.requireOpToken(":"); - var value = parser.parseElement("expression", tokens); - fields.push({name: name, value: value}); - } while (tokens.matchOpToken(",")) - tokens.requireOpToken("}"); - } - return { - type: "objectLiteral", - fields: fields, - transpile: function () { - return "({" + fields.map(function (field) { - return field.name.value + ":" + parser.transpile(field.value) - }).join(", ") + "})"; - } - } - } - - - }) - - _parser.addGrammarElement("namedArgumentList", function (parser, tokens) { - if (tokens.matchOpToken("(")) { - var fields = [] - if (!tokens.matchOpToken(")")) { - do { - var name = tokens.requireTokenType("IDENTIFIER"); - tokens.requireOpToken(":"); - var value = parser.parseElement("expression", tokens); - fields.push({name: name, value: value}); - } while (tokens.matchOpToken(",")) + //==================================================================== + // Grammar + //==================================================================== + { + _parser.addGrammarElement("parenthesized", function (parser, tokens) { + if (tokens.matchOpToken('(')) { + var expr = parser.parseElement("expression", tokens); tokens.requireOpToken(")"); - } - return { - type: "namedArgumentList", - fields: fields, - transpile: function () { - return "({_namedArgList_:true, " + fields.map(function (field) { - return field.name.value + ":" + parser.transpile(field.value) - }).join(", ") + "})"; - } - } - } - - - }) - - _parser.addGrammarElement("symbol", function (parser, tokens) { - var identifier = tokens.matchTokenType('IDENTIFIER'); - if (identifier) { - return { - type: "symbol", - name: identifier.value, - transpile: function () { - return identifier.value; - } - }; - } - }); - - _parser.addGrammarElement("implicitMeTarget", function (parser, tokens) { - return { - type: "implicitMeTarget", - transpile: function () { - return "[me]" - } - }; - }); - - _parser.addGrammarElement("implicitAllTarget", function (parser, tokens) { - return { - type: "implicitAllTarget", - transpile: function () { - return 'document.querySelectorAll("*")'; - } - }; - }); - - _parser.addGrammarElement("millisecondLiteral", function (parser, tokens) { - var number = tokens.requireTokenType(tokens, "NUMBER"); - var factor = 1; - if (tokens.matchToken("s")) { - factor = 1000; - } else if (tokens.matchToken("ms")) { - // do nothing - } - return { - type: "millisecondLiteral", - number: number, - factor: factor, - transpile: function () { - return factor * parseFloat(this.number.value); - } - }; - }); - - _parser.addGrammarElement("boolean", function (parser, tokens) { - var booleanLiteral = tokens.matchToken("true") || tokens.matchToken("false"); - if (booleanLiteral) { - return { - type: "boolean", - transpile: function () { - return booleanLiteral.value; - } - } - } - }); - - _parser.addGrammarElement("null", function (parser, tokens) { - if (tokens.matchToken('null')) { - return { - type: "null", - transpile: function () { - return "null"; - } - } - } - }); - - _parser.addGrammarElement("arrayLiteral", function (parser, tokens) { - if (tokens.matchOpToken('[')) { - var values = []; - if (!tokens.matchOpToken(']')) { - do { - var expr = parser.parseElement("expression", tokens); - if (expr == null) { - parser.raiseParseError(tokens, "Expected an expression"); + return { + type: "parenthesized", + expr: expr, + transpile: function () { + return "(" + parser.transpile(expr) + ")"; } - values.push(expr); - } while(tokens.matchOpToken(",")) + } + } + }) + + _parser.addGrammarElement("string", function (parser, tokens) { + var stringToken = tokens.matchTokenType('STRING'); + if (stringToken) { + return { + type: "string", + token: stringToken, + transpile: function () { + if (stringToken.value.indexOf("'") === 0) { + return "'" + stringToken.value + "'"; + } else { + return '"' + stringToken.value + '"'; + } + } + } + } + }) + + _parser.addGrammarElement("nakedString", function (parser, tokens) { + if (tokens.hasMore()) { + var tokenArr = tokens.consumeUntilWhitespace(); + tokens.matchTokenType("WHITESPACE"); + return { + type: "nakedString", + tokens: tokenArr, + transpile: function () { + return "'" + tokenArr.map(function (t) { + return t.value + }).join("") + "'"; + } + } + } + }) + + _parser.addGrammarElement("number", function (parser, tokens) { + var number = tokens.matchTokenType('NUMBER'); + if (number) { + var numberToken = number; + var value = parseFloat(number.value) + return { + type: "number", + value: value, + numberToken: numberToken, + transpile: function () { + return numberToken.value; + } + } + } + }) + + _parser.addGrammarElement("idRef", function (parser, tokens) { + var elementId = tokens.matchTokenType('ID_REF'); + if (elementId) { + return { + type: "idRef", + value: elementId.value.substr(1), + transpile: function () { + return "document.getElementById('" + this.value + "')" + } + }; + } + }) + + _parser.addGrammarElement("classRef", function (parser, tokens) { + var classRef = tokens.matchTokenType('CLASS_REF'); + if (classRef) { + return { + type: "classRef", + value: classRef.value, + className: function () { + return this.value.substr(1); + }, + transpile: function () { + return "document.querySelectorAll('" + this.value + "')" + } + }; + } + }) + + _parser.addGrammarElement("attributeRef", function (parser, tokens) { + if (tokens.matchOpToken("[")) { + var name = tokens.matchTokenType("IDENTIFIER"); + var value = null; + if (tokens.matchOpToken("=")) { + value = parser.parseElement("expression", tokens); + } tokens.requireOpToken("]"); + return { + type: "attribute_expression", + name: name.value, + value: value, + transpile: function () { + if (this.value) { + return "({name: '" + this.name + "', value: " + parser.transpile(this.value) + "})"; + } else { + return "({name: '" + this.name + "'})"; + } + } + } } + }) + + _parser.addGrammarElement("objectLiteral", function (parser, tokens) { + if (tokens.matchOpToken("{")) { + var fields = [] + if (!tokens.matchOpToken("}")) { + do { + var name = tokens.requireTokenType("IDENTIFIER"); + tokens.requireOpToken(":"); + var value = parser.parseElement("expression", tokens); + fields.push({name: name, value: value}); + } while (tokens.matchOpToken(",")) + tokens.requireOpToken("}"); + } + return { + type: "objectLiteral", + fields: fields, + transpile: function () { + return "({" + fields.map(function (field) { + return field.name.value + ":" + parser.transpile(field.value) + }).join(", ") + "})"; + } + } + } + + + }) + + _parser.addGrammarElement("namedArgumentList", function (parser, tokens) { + if (tokens.matchOpToken("(")) { + var fields = [] + if (!tokens.matchOpToken(")")) { + do { + var name = tokens.requireTokenType("IDENTIFIER"); + tokens.requireOpToken(":"); + var value = parser.parseElement("expression", tokens); + fields.push({name: name, value: value}); + } while (tokens.matchOpToken(",")) + tokens.requireOpToken(")"); + } + return { + type: "namedArgumentList", + fields: fields, + transpile: function () { + return "({_namedArgList_:true, " + fields.map(function (field) { + return field.name.value + ":" + parser.transpile(field.value) + }).join(", ") + "})"; + } + } + } + + + }) + + _parser.addGrammarElement("symbol", function (parser, tokens) { + var identifier = tokens.matchTokenType('IDENTIFIER'); + if (identifier) { + return { + type: "symbol", + name: identifier.value, + transpile: function () { + return identifier.value; + } + }; + } + }); + + _parser.addGrammarElement("implicitMeTarget", function (parser, tokens) { return { - type: "arrayLiteral", - values:values, + type: "implicitMeTarget", transpile: function () { - return "[" + values.map(function(v){ return parser.transpile(v) }).join(", ") + "]"; - } - } - } - }); - - _parser.addGrammarElement("blockLiteral", function (parser, tokens) { - if (tokens.matchOpToken('\\')) { - var args = [] - var arg1 = tokens.matchTokenType("IDENTIFIER"); - if (arg1) { - args.push(arg1); - while (tokens.matchOpToken(",")) { - args.push(tokens.requireTokenType("IDENTIFIER")); - } - } - // TODO compound op token - tokens.requireOpToken("-"); - tokens.requireOpToken(">"); - var expr = parser.parseElement("expression", tokens); - if (expr == null) { - parser.raiseParseError(tokens, "Expected an expression"); - } - return { - type: "blockLiteral", - args: args, - expr: expr, - transpile: function () { - return "function(" + args.map(function (arg) { - return arg.value - }).join(", ") + "){ return " + - parser.transpile(expr) + " }"; - } - } - } - }); - - _parser.addGrammarElement("leaf", function (parser, tokens) { - return parser.parseAnyOf(["parenthesized", "boolean", "null", "string", "number", "idRef", "classRef", "symbol", "propertyRef", "objectLiteral", "arrayLiteral", "blockLiteral"], tokens) - }); - - _parser.addGrammarElement("propertyAccess", function (parser, tokens, root) { - if (tokens.matchOpToken(".")) { - var prop = tokens.requireTokenType("IDENTIFIER"); - var propertyAccess = { - type: "propertyAccess", - root: root, - prop: prop, - transpile: function () { - return parser.transpile(root) + "." + prop.value; + return "[me]" } }; - return _parser.parseElement("indirectExpression", tokens, propertyAccess); - } - }); + }); - _parser.addGrammarElement("functionCall", function (parser, tokens, root) { - if (tokens.matchOpToken("(")) { - var args = []; - do { - args.push(parser.parseElement("expression", tokens)); - } while (tokens.matchOpToken(",")) - tokens.requireOpToken(")"); - var functionCall = { - type: "functionCall", - root: root, - args: args, + _parser.addGrammarElement("implicitAllTarget", function (parser, tokens) { + return { + type: "implicitAllTarget", transpile: function () { - return parser.transpile(root) + "(" + args.map(function (arg) { - return parser.transpile(arg) - }).join(",") + ")" + return 'document.querySelectorAll("*")'; } }; - return _parser.parseElement("indirectExpression", tokens, functionCall); - } - }); + }); - _parser.addGrammarElement("indirectExpression", function (parser, tokens, root) { - var propAccess = parser.parseElement("propertyAccess", tokens, root); - if (propAccess) { - return propAccess; - } - - var functionCall = parser.parseElement("functionCall", tokens, root); - if (functionCall) { - return functionCall; - } - - return root; - }); - - _parser.addGrammarElement("primaryExpression", function (parser, tokens) { - var leaf = parser.parseElement("leaf", tokens); - if (leaf) { - return parser.parseElement("indirectExpression", tokens, leaf); - } - parser.raiseParseError(tokens, "Unexpected value: " + tokens.currentToken().value); - }); - - _parser.addGrammarElement("postfixExpression", function (parser, tokens) { - var root = parser.parseElement("primaryExpression", tokens); - if (tokens.matchOpToken(":")) { - var typeName = tokens.requireTokenType("IDENTIFIER"); - var nullOk = !tokens.matchOpToken("!"); + _parser.addGrammarElement("millisecondLiteral", function (parser, tokens) { + var number = tokens.requireTokenType(tokens, "NUMBER"); + var factor = 1; + if (tokens.matchToken("s")) { + factor = 1000; + } else if (tokens.matchToken("ms")) { + // do nothing + } return { - type: "typeCheck", - typeName: typeName, - root: root, - nullOk: nullOk, + type: "millisecondLiteral", + number: number, + factor: factor, transpile: function () { - return "_hyperscript.runtime.typeCheck(" + parser.transpile(root) + ", '" + typeName.value + "', " + nullOk + ")"; + return factor * parseFloat(this.number.value); + } + }; + }); + + _parser.addGrammarElement("boolean", function (parser, tokens) { + var booleanLiteral = tokens.matchToken("true") || tokens.matchToken("false"); + if (booleanLiteral) { + return { + type: "boolean", + transpile: function () { + return booleanLiteral.value; + } } } - } else { + }); + + _parser.addGrammarElement("null", function (parser, tokens) { + if (tokens.matchToken('null')) { + return { + type: "null", + transpile: function () { + return "null"; + } + } + } + }); + + _parser.addGrammarElement("arrayLiteral", function (parser, tokens) { + if (tokens.matchOpToken('[')) { + var values = []; + if (!tokens.matchOpToken(']')) { + do { + var expr = parser.parseElement("expression", tokens); + if (expr == null) { + parser.raiseParseError(tokens, "Expected an expression"); + } + values.push(expr); + } while (tokens.matchOpToken(",")) + tokens.requireOpToken("]"); + } + return { + type: "arrayLiteral", + values: values, + transpile: function () { + return "[" + values.map(function (v) { + return parser.transpile(v) + }).join(", ") + "]"; + } + } + } + }); + + _parser.addGrammarElement("blockLiteral", function (parser, tokens) { + if (tokens.matchOpToken('\\')) { + var args = [] + var arg1 = tokens.matchTokenType("IDENTIFIER"); + if (arg1) { + args.push(arg1); + while (tokens.matchOpToken(",")) { + args.push(tokens.requireTokenType("IDENTIFIER")); + } + } + // TODO compound op token + tokens.requireOpToken("-"); + tokens.requireOpToken(">"); + var expr = parser.parseElement("expression", tokens); + if (expr == null) { + parser.raiseParseError(tokens, "Expected an expression"); + } + return { + type: "blockLiteral", + args: args, + expr: expr, + transpile: function () { + return "function(" + args.map(function (arg) { + return arg.value + }).join(", ") + "){ return " + + parser.transpile(expr) + " }"; + } + } + } + }); + + _parser.addGrammarElement("leaf", function (parser, tokens) { + return parser.parseAnyOf(["parenthesized", "boolean", "null", "string", "number", "idRef", "classRef", "symbol", "propertyRef", "objectLiteral", "arrayLiteral", "blockLiteral"], tokens) + }); + + _parser.addGrammarElement("propertyAccess", function (parser, tokens, root) { + if (tokens.matchOpToken(".")) { + var prop = tokens.requireTokenType("IDENTIFIER"); + var propertyAccess = { + type: "propertyAccess", + root: root, + prop: prop, + transpile: function () { + return parser.transpile(root) + "." + prop.value; + } + }; + return _parser.parseElement("indirectExpression", tokens, propertyAccess); + } + }); + + _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, + args: args, + transpile: function () { + return parser.transpile(root) + "(" + args.map(function (arg) { + return parser.transpile(arg) + }).join(",") + ")" + } + }; + return _parser.parseElement("indirectExpression", tokens, functionCall); + } + }); + + _parser.addGrammarElement("indirectExpression", function (parser, tokens, root) { + var propAccess = parser.parseElement("propertyAccess", tokens, root); + if (propAccess) { + return propAccess; + } + + var functionCall = parser.parseElement("functionCall", tokens, root); + if (functionCall) { + return functionCall; + } + return root; - } - }); + }); - _parser.addGrammarElement("logicalNot", function (parser, tokens) { - if (tokens.matchToken("not")) { - var root = parser.parseElement("unaryExpression", tokens); - return { - type: "logicalNot", - root: root, - transpile: function () { - return "!" + parser.transpile(root); + _parser.addGrammarElement("primaryExpression", function (parser, tokens) { + var leaf = parser.parseElement("leaf", tokens); + if (leaf) { + return parser.parseElement("indirectExpression", tokens, leaf); + } + parser.raiseParseError(tokens, "Unexpected value: " + tokens.currentToken().value); + }); + + _parser.addGrammarElement("postfixExpression", function (parser, tokens) { + var root = parser.parseElement("primaryExpression", tokens); + if (tokens.matchOpToken(":")) { + var typeName = tokens.requireTokenType("IDENTIFIER"); + var nullOk = !tokens.matchOpToken("!"); + return { + type: "typeCheck", + typeName: typeName, + root: root, + nullOk: nullOk, + transpile: function () { + return "_hyperscript.runtime.typeCheck(" + parser.transpile(root) + ", '" + typeName.value + "', " + nullOk + ")"; + } } - }; - } - }); - - _parser.addGrammarElement("negativeNumber", function (parser, tokens) { - if (tokens.matchOpToken("-")) { - var root = parser.parseElement("unaryExpression", tokens); - return { - type: "negativeNumber", - root: root, - transpile: function () { - return "-" + parser.transpile(root); - } - }; - } - }); - - _parser.addGrammarElement("unaryExpression", function (parser, tokens) { - return parser.parseAnyOf(["logicalNot", "negativeNumber", "postfixExpression"], tokens); - }); - - _parser.addGrammarElement("mathOperator", function (parser, tokens) { - var expr = parser.parseElement("unaryExpression", tokens); - var mathOp, initialMathOp = null; - mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%") - while (mathOp) { - initialMathOp = initialMathOp || mathOp; - if(initialMathOp.value !== mathOp.value) { - parser.raiseParseError(tokens, "You must parenthesize math operations with different operators") - } - var rhs = parser.parseElement("unaryExpression", tokens); - expr = { - type: "mathOperator", - operator: mathOp.value, - lhs: expr, - rhs: rhs, - transpile: function () { - return parser.transpile(this.lhs) + " " + this.operator + " " + parser.transpile(this.rhs); - } - } - mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%") - } - return expr; - }); - - _parser.addGrammarElement("mathExpression", function (parser, tokens) { - return parser.parseAnyOf(["mathOperator", "unaryExpression"], tokens); - }); - - _parser.addGrammarElement("comparisonOperator", function (parser, tokens) { - var expr = parser.parseElement("mathExpression", tokens); - var comparisonOp, initialComparisonOp = null; - comparisonOp = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!==") - while (comparisonOp) { - initialComparisonOp = initialComparisonOp || comparisonOp; - if(initialComparisonOp.value !== comparisonOp.value) { - parser.raiseParseError(tokens, "You must parenthesize comparison operations with different operators") - } - var rhs = parser.parseElement("mathExpression", tokens); - expr = { - type: "comparisonOperator", - operator: comparisonOp.value, - lhs: expr, - rhs: rhs, - transpile: function () { - return parser.transpile(this.lhs) + " " + this.operator + " " + parser.transpile(this.rhs); - } - } - comparisonOp = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!==") - } - return expr; - }); - - _parser.addGrammarElement("comparisonExpression", function (parser, tokens) { - return parser.parseAnyOf(["comparisonOperator", "mathExpression"], tokens); - }); - - _parser.addGrammarElement("logicalOperator", function (parser, tokens) { - var expr = parser.parseElement("comparisonExpression", tokens); - var logicalOp, initialLogicalOp = null; - logicalOp = tokens.matchToken("and") || tokens.matchToken("or"); - while (logicalOp) { - initialLogicalOp = initialLogicalOp || logicalOp; - if(initialLogicalOp.value !== logicalOp.value) { - parser.raiseParseError(tokens, "You must parenthesize logical operations with different operators") - } - var rhs = parser.parseElement("comparisonExpression", tokens); - expr = { - type: "logicalOperator", - operator: logicalOp.value, - lhs: expr, - rhs: rhs, - transpile: function () { - return parser.transpile(this.lhs) + " " + (this.operator === "and" ? " && " : " || ") + " " + parser.transpile(this.rhs); - } - } - logicalOp = tokens.matchToken("and") || tokens.matchToken("or"); - } - return expr; - }); - - _parser.addGrammarElement("logicalExpression", function (parser, tokens) { - return parser.parseAnyOf(["logicalOperator", "mathExpression"], tokens); - }); - - _parser.addGrammarElement("expression", function (parser, tokens) { - return parser.parseElement("logicalExpression", tokens); - }); - - _parser.addGrammarElement("target", function (parser, tokens) { - var root = parser.parseAnyOf(["symbol", "classRef", "idRef"], tokens); - if (root == null) { - parser.raiseParseError(tokens, "Expected a valid target expression"); - } - - var propPath = [] - while (tokens.matchOpToken(".")) { - propPath.push(tokens.requireTokenType("IDENTIFIER").value) - } - - return { - type: "target", - propPath: propPath, - root: root, - transpile: function () { - return "_hyperscript.runtime.evalTarget(" + parser.transpile(root) + ", [" + propPath.map(function (prop) { - return "\"" + prop + "\"" - }).join(", ") + "])"; - } - }; - }); - - _parser.addGrammarElement("command", function (parser, tokens) { - return parser.parseAnyOf(["onCmd", "addCmd", "removeCmd", "toggleCmd", "waitCmd", "sendCmd", "triggerCmd", - "takeCmd", "logCmd", "callCmd", "putCmd", "setCmd", "ifCmd", "ajaxCmd"], tokens); - }) - - _parser.addGrammarElement("commandList", function (parser, tokens) { - var cmd = parser.parseElement("command", tokens); - if (cmd) { - tokens.matchToken("then"); - cmd.next = parser.parseElement("commandList", tokens); - return cmd; - } - }) - - _parser.addGrammarElement("hyperscript", function (parser, tokens) { - var eventListeners = [] - do { - eventListeners.push(parser.parseElement("eventListener", tokens)); - } while (tokens.matchToken("end") && tokens.hasMore()) - if (tokens.hasMore()) { - parser.raiseParseError(tokens); - } - return { - type: "hyperscript", - eventListeners: eventListeners, - transpile: function () { - return "(function(){\n" + - "var eventListeners = []\n" + - eventListeners.map(function (el) { - return " eventListeners.push(" + parser.transpile(el) + ");\n" - }).join("") + - " function applyEventListenersTo(elt) { _hyperscript.runtime.applyEventListeners(this, elt) }\n" + - " return {eventListeners:eventListeners, applyEventListenersTo:applyEventListenersTo}\n" + - "})()" - } - }; - }) - - - _parser.addGrammarElement("eventListener", function (parser, tokens) { - tokens.requireToken("on"); - var on = parser.parseElement("dotOrColonPath", tokens); - if (on == null) { - parser.raiseParseError(tokens, "Expected event name") - } - if (tokens.matchToken("from")) { - var from = parser.parseElement("target", tokens); - if (from == null) { - parser.raiseParseError(tokens, "Expected target value") - } - } else { - var from = parser.parseElement("implicitMeTarget", tokens); - } - - var args = []; - if (tokens.matchOpToken("(")) { - do { - args.push(tokens.requireTokenType('IDENTIFIER')); - } while (tokens.matchOpToken(",")) - tokens.requireOpToken(')') - } - - var start = parser.parseElement("commandList", tokens); - var eventListener = { - type: "eventListener", - on: on, - from: from, - start: start, - transpile: function () { - return "(function(me){" + - "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" + - parser.transpile(start) + - " })\n" + - "})\n" + - "})" - } - }; - return eventListener; - }); - - _parser.addGrammarElement("addCmd", function (parser, tokens) { - if (tokens.matchToken("add")) { - var classRef = parser.parseElement("classRef", tokens); - var attributeRef = null; - if (classRef == null) { - attributeRef = parser.parseElement("attributeRef", tokens); - if (attributeRef == null) { - parser.raiseParseError(tokens, "Expected either a class reference or attribute expression") - } - } - - if (tokens.matchToken("to")) { - var to = parser.parseElement("target", tokens); } else { - var to = parser.parseElement("implicitMeTarget"); + return root; + } + }); + + _parser.addGrammarElement("logicalNot", function (parser, tokens) { + if (tokens.matchToken("not")) { + var root = parser.parseElement("unaryExpression", tokens); + return { + type: "logicalNot", + root: root, + transpile: function () { + return "!" + parser.transpile(root); + } + }; + } + }); + + _parser.addGrammarElement("negativeNumber", function (parser, tokens) { + if (tokens.matchOpToken("-")) { + var root = parser.parseElement("unaryExpression", tokens); + return { + type: "negativeNumber", + root: root, + transpile: function () { + return "-" + parser.transpile(root); + } + }; + } + }); + + _parser.addGrammarElement("unaryExpression", function (parser, tokens) { + return parser.parseAnyOf(["logicalNot", "negativeNumber", "postfixExpression"], tokens); + }); + + _parser.addGrammarElement("mathOperator", function (parser, tokens) { + var expr = parser.parseElement("unaryExpression", tokens); + var mathOp, initialMathOp = null; + mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%") + while (mathOp) { + initialMathOp = initialMathOp || mathOp; + if (initialMathOp.value !== mathOp.value) { + parser.raiseParseError(tokens, "You must parenthesize math operations with different operators") + } + var rhs = parser.parseElement("unaryExpression", tokens); + expr = { + type: "mathOperator", + operator: mathOp.value, + lhs: expr, + rhs: rhs, + transpile: function () { + return parser.transpile(this.lhs) + " " + this.operator + " " + parser.transpile(this.rhs); + } + } + mathOp = tokens.matchAnyOpToken("+", "-", "*", "/", "%") + } + return expr; + }); + + _parser.addGrammarElement("mathExpression", function (parser, tokens) { + return parser.parseAnyOf(["mathOperator", "unaryExpression"], tokens); + }); + + _parser.addGrammarElement("comparisonOperator", function (parser, tokens) { + var expr = parser.parseElement("mathExpression", tokens); + var comparisonOp, initialComparisonOp = null; + comparisonOp = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!==") + while (comparisonOp) { + initialComparisonOp = initialComparisonOp || comparisonOp; + if (initialComparisonOp.value !== comparisonOp.value) { + parser.raiseParseError(tokens, "You must parenthesize comparison operations with different operators") + } + var rhs = parser.parseElement("mathExpression", tokens); + expr = { + type: "comparisonOperator", + operator: comparisonOp.value, + lhs: expr, + rhs: rhs, + transpile: function () { + return parser.transpile(this.lhs) + " " + this.operator + " " + parser.transpile(this.rhs); + } + } + comparisonOp = tokens.matchAnyOpToken("<", ">", "<=", ">=", "==", "===", "!=", "!==") + } + return expr; + }); + + _parser.addGrammarElement("comparisonExpression", function (parser, tokens) { + return parser.parseAnyOf(["comparisonOperator", "mathExpression"], tokens); + }); + + _parser.addGrammarElement("logicalOperator", function (parser, tokens) { + var expr = parser.parseElement("comparisonExpression", tokens); + var logicalOp, initialLogicalOp = null; + logicalOp = tokens.matchToken("and") || tokens.matchToken("or"); + while (logicalOp) { + initialLogicalOp = initialLogicalOp || logicalOp; + if (initialLogicalOp.value !== logicalOp.value) { + parser.raiseParseError(tokens, "You must parenthesize logical operations with different operators") + } + var rhs = parser.parseElement("comparisonExpression", tokens); + expr = { + type: "logicalOperator", + operator: logicalOp.value, + lhs: expr, + rhs: rhs, + transpile: function () { + return parser.transpile(this.lhs) + " " + (this.operator === "and" ? " && " : " || ") + " " + parser.transpile(this.rhs); + } + } + logicalOp = tokens.matchToken("and") || tokens.matchToken("or"); + } + return expr; + }); + + _parser.addGrammarElement("logicalExpression", function (parser, tokens) { + return parser.parseAnyOf(["logicalOperator", "mathExpression"], tokens); + }); + + _parser.addGrammarElement("expression", function (parser, tokens) { + return parser.parseElement("logicalExpression", tokens); + }); + + _parser.addGrammarElement("target", function (parser, tokens) { + var root = parser.parseAnyOf(["symbol", "classRef", "idRef"], tokens); + if (root == null) { + parser.raiseParseError(tokens, "Expected a valid target expression"); + } + + var propPath = [] + while (tokens.matchOpToken(".")) { + propPath.push(tokens.requireTokenType("IDENTIFIER").value) } return { - type: "addCmd", - classRef: classRef, - attributeRef: attributeRef, - to: to, + type: "target", + propPath: propPath, + root: root, transpile: function () { - if (this.classRef) { - return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" + - " target.classList.add('" + classRef.className() + "')" + - "})"; - } else { - return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" + - " target.setAttribute('" + attributeRef.name + "', " + parser.transpile(attributeRef) + ".value)" + - "})"; - } + return "_hyperscript.runtime.evalTarget(" + parser.transpile(root) + ", [" + propPath.map(function (prop) { + return "\"" + prop + "\"" + }).join(", ") + "])"; } - } - } - }); + }; + }); - _parser.addGrammarElement("removeCmd", function (parser, tokens) { - if (tokens.matchToken("remove")) { - var classRef = parser.parseElement("classRef", tokens); - var attributeRef = null; - var elementExpr = null; - if (classRef == null) { - attributeRef = parser.parseElement("attributeRef", tokens); - if (attributeRef == null) { - elementExpr = parser.parseElement("expression", tokens) - if (elementExpr == null) { - parser.raiseParseError(tokens, "Expected either a class reference, attribute expression or value expression"); - } + _parser.addGrammarElement("command", function (parser, tokens) { + return parser.parseAnyOf(["onCmd", "addCmd", "removeCmd", "toggleCmd", "waitCmd", "sendCmd", "triggerCmd", + "takeCmd", "logCmd", "callCmd", "putCmd", "setCmd", "ifCmd", "ajaxCmd"], tokens); + }) + + _parser.addGrammarElement("commandList", function (parser, tokens) { + var cmd = parser.parseElement("command", tokens); + if (cmd) { + tokens.matchToken("then"); + cmd.next = parser.parseElement("commandList", tokens); + return cmd; + } + }) + + _parser.addGrammarElement("hyperscript", function (parser, tokens) { + var eventListeners = [] + do { + eventListeners.push(parser.parseElement("eventListener", tokens)); + } while (tokens.matchToken("end") && tokens.hasMore()) + if (tokens.hasMore()) { + parser.raiseParseError(tokens); + } + return { + type: "hyperscript", + eventListeners: eventListeners, + transpile: function () { + return "(function(){\n" + + "var eventListeners = []\n" + + eventListeners.map(function (el) { + return " eventListeners.push(" + parser.transpile(el) + ");\n" + }).join("") + + " function applyEventListenersTo(elt) { _hyperscript.runtime.applyEventListeners(this, elt) }\n" + + " return {eventListeners:eventListeners, applyEventListenersTo:applyEventListenersTo}\n" + + "})()" } + }; + }) + + _parser.addGrammarElement("eventListener", function (parser, tokens) { + tokens.requireToken("on"); + var on = parser.parseElement("dotOrColonPath", tokens); + if (on == null) { + parser.raiseParseError(tokens, "Expected event name") } if (tokens.matchToken("from")) { var from = parser.parseElement("target", tokens); + if (from == null) { + parser.raiseParseError(tokens, "Expected target value") + } } else { - var from = parser.parseElement("implicitMeTarget"); + var from = parser.parseElement("implicitMeTarget", tokens); } - return { - type: "removeCmd", - classRef: classRef, - attributeRef: attributeRef, - elementExpr: elementExpr, + var args = []; + if (tokens.matchOpToken("(")) { + do { + args.push(tokens.requireTokenType('IDENTIFIER')); + } while (tokens.matchOpToken(",")) + tokens.requireOpToken(')') + } + + var start = parser.parseElement("commandList", tokens); + var eventListener = { + type: "eventListener", + on: on, from: from, + start: start, transpile: function () { - if (this.elementExpr) { - return "_hyperscript.runtime.forEach( " + parser.transpile(elementExpr) + ", function (target) {" + - " target.parentElement.removeChild(target)" + - "})"; - } else { + return "(function(me){" + + "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" + + parser.transpile(start) + + " })\n" + + "})\n" + + "})" + } + }; + return eventListener; + }); + + _parser.addGrammarElement("addCmd", function (parser, tokens) { + if (tokens.matchToken("add")) { + var classRef = parser.parseElement("classRef", tokens); + var attributeRef = null; + if (classRef == null) { + attributeRef = parser.parseElement("attributeRef", tokens); + if (attributeRef == null) { + parser.raiseParseError(tokens, "Expected either a class reference or attribute expression") + } + } + + if (tokens.matchToken("to")) { + var to = parser.parseElement("target", tokens); + } else { + var to = parser.parseElement("implicitMeTarget"); + } + + return { + type: "addCmd", + classRef: classRef, + attributeRef: attributeRef, + to: to, + transpile: function () { if (this.classRef) { - return "_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function (target) {" + - " target.classList.remove('" + classRef.className() + "')" + + return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" + + " target.classList.add('" + classRef.className() + "')" + "})"; } else { - return "_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function (target) {" + - " target.removeAttribute('" + attributeRef.name + "')" + + return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" + + " target.setAttribute('" + attributeRef.name + "', " + parser.transpile(attributeRef) + ".value)" + "})"; } } } } - } - }); + }); - _parser.addGrammarElement("toggleCmd", function (parser, tokens) { - if (tokens.matchToken("toggle")) { - var classRef = parser.parseElement("classRef", tokens); - var attributeRef = null; - if (classRef == null) { - attributeRef = parser.parseElement("attributeRef", tokens); - if (attributeRef == null) { - parser.raiseParseError(tokens, "Expected either a class reference or attribute expression") + _parser.addGrammarElement("removeCmd", function (parser, tokens) { + if (tokens.matchToken("remove")) { + var classRef = parser.parseElement("classRef", tokens); + var attributeRef = null; + var elementExpr = null; + if (classRef == null) { + attributeRef = parser.parseElement("attributeRef", tokens); + if (attributeRef == null) { + elementExpr = parser.parseElement("expression", tokens) + if (elementExpr == null) { + parser.raiseParseError(tokens, "Expected either a class reference, attribute expression or value expression"); + } + } + } + if (tokens.matchToken("from")) { + var from = parser.parseElement("target", tokens); + } else { + var from = parser.parseElement("implicitMeTarget"); + } + + return { + type: "removeCmd", + classRef: classRef, + attributeRef: attributeRef, + elementExpr: elementExpr, + from: from, + transpile: function () { + if (this.elementExpr) { + return "_hyperscript.runtime.forEach( " + parser.transpile(elementExpr) + ", function (target) {" + + " target.parentElement.removeChild(target)" + + "})"; + } else { + if (this.classRef) { + return "_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function (target) {" + + " target.classList.remove('" + classRef.className() + "')" + + "})"; + } else { + return "_hyperscript.runtime.forEach( " + parser.transpile(from) + ", function (target) {" + + " target.removeAttribute('" + attributeRef.name + "')" + + "})"; + } + } + } } } - if (tokens.matchToken("on")) { - var on = parser.parseElement("target", tokens); - } else { - var on = parser.parseElement("implicitMeTarget"); + }); + + _parser.addGrammarElement("toggleCmd", function (parser, tokens) { + if (tokens.matchToken("toggle")) { + var classRef = parser.parseElement("classRef", tokens); + var attributeRef = null; + if (classRef == null) { + attributeRef = parser.parseElement("attributeRef", tokens); + if (attributeRef == null) { + parser.raiseParseError(tokens, "Expected either a class reference or attribute expression") + } + } + if (tokens.matchToken("on")) { + var on = parser.parseElement("target", tokens); + } else { + var on = parser.parseElement("implicitMeTarget"); + } + return { + type: "toggleCmd", + classRef: classRef, + attributeRef: attributeRef, + on: on, + transpile: function () { + if (this.classRef) { + return "_hyperscript.runtime.forEach( " + parser.transpile(on) + ", function (target) {" + + " target.classList.toggle('" + classRef.className() + "')" + + "})"; + } else { + return "_hyperscript.runtime.forEach( " + parser.transpile(on) + ", function (target) {" + + " if(target.hasAttribute('" + attributeRef.name + "')) {\n" + + " target.removeAttribute('" + attributeRef.name + "');\n" + + " } else { \n" + + " target.setAttribute('" + attributeRef.name + "', " + parser.transpile(attributeRef) + ".value)" + + " }" + + "})"; + } + } + } } - return { - type: "toggleCmd", - classRef: classRef, - attributeRef: attributeRef, - on: on, - transpile: function () { - if (this.classRef) { - return "_hyperscript.runtime.forEach( " + parser.transpile(on) + ", function (target) {" + - " target.classList.toggle('" + classRef.className() + "')" + - "})"; - } else { - return "_hyperscript.runtime.forEach( " + parser.transpile(on) + ", function (target) {" + - " if(target.hasAttribute('" + attributeRef.name + "')) {\n" + - " target.removeAttribute('" + attributeRef.name + "');\n" + - " } else { \n" + - " target.setAttribute('" + attributeRef.name + "', " + parser.transpile(attributeRef) + ".value)" + - " }" + + }) + + _parser.addGrammarElement("waitCmd", function (parser, tokens) { + if (tokens.matchToken("wait")) { + var time = parser.parseElement('millisecondLiteral', tokens); + return { + type: "waitCmd", + time: time, + transpile: function () { + var capturedNext = this.next; + delete this.next; + return "setTimeout(function () { " + parser.transpile(capturedNext) + " }, " + parser.transpile(this.time) + ")"; + } + } + } + }) + + // TODO - colon path needs to eventually become part of ruby-style symbols + _parser.addGrammarElement("dotOrColonPath", function (parser, tokens) { + var root = tokens.matchTokenType("IDENTIFIER"); + if (root) { + var path = [root.value]; + + var separator = tokens.matchOpToken(".") || tokens.matchOpToken(":"); + if (separator) { + do { + path.push(tokens.requireTokenType("IDENTIFIER").value); + } while (tokens.matchOpToken(separator.value)) + } + + return { + type: "dotOrColonPath", + path: path, + transpile: function () { + return path.join(separator ? separator.value : ""); + } + } + } + }); + + _parser.addGrammarElement("sendCmd", function (parser, tokens) { + if (tokens.matchToken("send")) { + + var eventName = parser.parseElement("dotOrColonPath", tokens); + + var details = parser.parseElement("namedArgumentList", tokens); + if (tokens.matchToken("to")) { + var to = parser.parseElement("target", tokens); + } else { + var to = parser.parseElement("implicitMeTarget"); + } + + return { + type: "sendCmd", + eventName: eventName, + details: details, + to: to, + transpile: function () { + return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" + + " _hyperscript.runtime.triggerEvent(target, '" + parser.transpile(eventName) + "'," + parser.transpile(details, "{}") + ")" + "})"; } } } - } - }) + }) - _parser.addGrammarElement("waitCmd", function (parser, tokens) { - if (tokens.matchToken("wait")) { - var time = parser.parseElement('millisecondLiteral', tokens); - return { - type: "waitCmd", - time: time, - transpile: function () { - var capturedNext = this.next; - delete this.next; - return "setTimeout(function () { " + parser.transpile(capturedNext) + " }, " + parser.transpile(this.time) + ")"; - } - } - } - }) + _parser.addGrammarElement("triggerCmd", function (parser, tokens) { + if (tokens.matchToken("trigger")) { - // TODO - colon path needs to eventually become part of ruby-style symbols - _parser.addGrammarElement("dotOrColonPath", function (parser, tokens) { - var root = tokens.matchTokenType("IDENTIFIER"); - if (root) { - var path = [root.value]; + var eventName = parser.parseElement("dotOrColonPath", tokens); + var details = parser.parseElement("namedArgumentList", tokens); - var separator = tokens.matchOpToken(".") || tokens.matchOpToken(":"); - if (separator) { - do { - path.push(tokens.requireTokenType("IDENTIFIER").value); - } while (tokens.matchOpToken(separator.value)) - } - - return { - type: "dotOrColonPath", - path: path, - transpile: function () { - return path.join(separator ? separator.value : ""); - } - } - } - }); - - _parser.addGrammarElement("sendCmd", function (parser, tokens) { - if (tokens.matchToken("send")) { - - var eventName = parser.parseElement("dotOrColonPath", tokens); - - var details = parser.parseElement("namedArgumentList", tokens); - if (tokens.matchToken("to")) { - var to = parser.parseElement("target", tokens); - } else { - var to = parser.parseElement("implicitMeTarget"); - } - - return { - type: "sendCmd", - eventName: eventName, - details: details, - to: to, - transpile: function () { - return "_hyperscript.runtime.forEach( " + parser.transpile(to) + ", function (target) {" + - " _hyperscript.runtime.triggerEvent(target, '" + parser.transpile(eventName) + "'," + parser.transpile(details, "{}") + ")" + - "})"; - } - } - } - }) - - _parser.addGrammarElement("triggerCmd", function (parser, tokens) { - if (tokens.matchToken("trigger")) { - - var eventName = parser.parseElement("dotOrColonPath", tokens); - var details = parser.parseElement("namedArgumentList", tokens); - - return { - type: "triggerCmd", - eventName: eventName, - details: details, - transpile: function () { - return "_hyperscript.runtime.triggerEvent(me, '" + parser.transpile(eventName) + "'," + parser.transpile(details, "{}") + ");"; - } - } - } - }) - - _parser.addGrammarElement("takeCmd", function (parser, tokens) { - if (tokens.matchToken("take")) { - var classRef = tokens.requireTokenType(tokens, "CLASS_REF"); - - if (tokens.matchToken("from")) { - var from = parser.parseElement("target", tokens); - } else { - var from = parser.parseElement("implicitAllTarget") - } - - if (tokens.matchToken("for")) { - var forElt = parser.parseElement("target", tokens); - } else { - var forElt = parser.parseElement("implicitMeTarget") - } - - return { - type: "takeCmd", - classRef: classRef, - from: from, - forElt: forElt, - transpile: function () { - var clazz = this.classRef.value.substr(1); - return " _hyperscript.runtime.forEach(" + parser.transpile(from) + ", function (target) { target.classList.remove('" + clazz + "') }); " + - "_hyperscript.runtime.forEach( " + parser.transpile(forElt) + ", function (target) {" + - " target.classList.add('" + clazz + "')" + - "})"; - } - } - } - }) - - _parser.addGrammarElement("logCmd", function (parser, tokens) { - if (tokens.matchToken("log")) { - var exprs = [parser.parseElement("expression", tokens)]; - while (tokens.matchOpToken(",")) { - exprs.push(parser.parseElement("expression", tokens)); - } - if (tokens.matchToken("with")) { - var withExpr = parser.parseElement("expression", tokens); - } - return { - type: "logCmd", - exprs: exprs, - withExpr: withExpr, - transpile: function () { - if (withExpr) { - return parser.transpile(withExpr) + "(" + exprs.map(function (expr) { - return parser.transpile(expr) - }).join(", ") + ")"; - } else { - return "console.log(" + exprs.map(function (expr) { - return parser.transpile(expr) - }).join(", ") + ")"; + return { + type: "triggerCmd", + eventName: eventName, + details: details, + transpile: function () { + return "_hyperscript.runtime.triggerEvent(me, '" + parser.transpile(eventName) + "'," + parser.transpile(details, "{}") + ");"; } } - }; - } - }) + } + }) - _parser.addGrammarElement("callCmd", function (parser, tokens) { - if (tokens.matchToken("call") || tokens.matchToken("get")) { - return { - type: "callCmd", - expr: parser.parseElement("expression", tokens), - transpile: function () { - return "var it = " + parser.transpile(this.expr); + _parser.addGrammarElement("takeCmd", function (parser, tokens) { + if (tokens.matchToken("take")) { + var classRef = tokens.requireTokenType(tokens, "CLASS_REF"); + + if (tokens.matchToken("from")) { + var from = parser.parseElement("target", tokens); + } else { + var from = parser.parseElement("implicitAllTarget") + } + + if (tokens.matchToken("for")) { + var forElt = parser.parseElement("target", tokens); + } else { + var forElt = parser.parseElement("implicitMeTarget") + } + + return { + type: "takeCmd", + classRef: classRef, + from: from, + forElt: forElt, + transpile: function () { + var clazz = this.classRef.value.substr(1); + return " _hyperscript.runtime.forEach(" + parser.transpile(from) + ", function (target) { target.classList.remove('" + clazz + "') }); " + + "_hyperscript.runtime.forEach( " + parser.transpile(forElt) + ", function (target) {" + + " target.classList.add('" + clazz + "')" + + "})"; + } } } - } - }) + }) - _parser.addGrammarElement("putCmd", function (parser, tokens) { - if (tokens.matchToken("put")) { - - var value = parser.parseElement("expression", tokens); - - var operation = tokens.matchToken("into") || - tokens.matchToken("before") || - tokens.matchToken("after"); - - if (operation == null && tokens.matchToken("at")) { - operation = tokens.matchToken("start") || - tokens.matchToken("end"); - tokens.requireToken("of"); + _parser.addGrammarElement("logCmd", function (parser, tokens) { + if (tokens.matchToken("log")) { + var exprs = [parser.parseElement("expression", tokens)]; + while (tokens.matchOpToken(",")) { + exprs.push(parser.parseElement("expression", tokens)); + } + if (tokens.matchToken("with")) { + var withExpr = parser.parseElement("expression", tokens); + } + return { + type: "logCmd", + exprs: exprs, + withExpr: withExpr, + transpile: function () { + if (withExpr) { + return parser.transpile(withExpr) + "(" + exprs.map(function (expr) { + return parser.transpile(expr) + }).join(", ") + ")"; + } else { + return "console.log(" + exprs.map(function (expr) { + return parser.transpile(expr) + }).join(", ") + ")"; + } + } + }; } + }) - if (operation == null) { - parser.raiseParseError(tokens, "Expected one of 'into', 'before', 'at start of', 'at end of', 'after'"); + _parser.addGrammarElement("callCmd", function (parser, tokens) { + if (tokens.matchToken("call") || tokens.matchToken("get")) { + return { + type: "callCmd", + expr: parser.parseElement("expression", tokens), + transpile: function () { + return "var it = " + parser.transpile(this.expr); + } + } } - var target = parser.parseElement("target", tokens); + }) - var directWrite = target.propPath.length === 0 && operation.value === "into"; - var symbolWrite = directWrite && target.root.type === "symbol"; - if (directWrite && !symbolWrite) { - parser.raiseParseError(tokens, "Can only put directly into symbols, not references") + _parser.addGrammarElement("putCmd", function (parser, tokens) { + if (tokens.matchToken("put")) { + + var value = parser.parseElement("expression", tokens); + + var operation = tokens.matchToken("into") || + tokens.matchToken("before") || + tokens.matchToken("after"); + + if (operation == null && tokens.matchToken("at")) { + operation = tokens.matchToken("start") || + tokens.matchToken("end"); + tokens.requireToken("of"); + } + + if (operation == null) { + parser.raiseParseError(tokens, "Expected one of 'into', 'before', 'at start of', 'at end of', 'after'"); + } + var target = parser.parseElement("target", tokens); + + var directWrite = target.propPath.length === 0 && operation.value === "into"; + var symbolWrite = directWrite && target.root.type === "symbol"; + if (directWrite && !symbolWrite) { + parser.raiseParseError(tokens, "Can only put directly into symbols, not references") + } + + return { + type: "putCmd", + target: target, + op: operation.value, + symbolWrite: symbolWrite, + value: value, + transpile: function () { + if (this.symbolWrite) { + return "var " + target.root.name + " = " + parser.transpile(value); + } else { + if (this.op === "into") { + var lastProperty = target.propPath.pop(); // steal last property for assignment + return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + + " target." + lastProperty + "=" + parser.transpile(value) + + "})"; + } else if (this.op === "before") { + return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + + " target.insertAdjacentHTML('beforebegin', " + parser.transpile(value) + ")" + + "})"; + } else if (this.op === "start") { + return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + + " target.insertAdjacentHTML('afterbegin', " + parser.transpile(value) + ")" + + "})"; + } else if (this.op === "end") { + return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + + " target.insertAdjacentHTML('beforeend', " + parser.transpile(value) + ")" + + "})"; + } else if (this.op === "after") { + return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + + " target.insertAdjacentHTML('afterend', " + parser.transpile(value) + ")" + + "})"; + } + } + } + } } + }) - return { - type: "putCmd", - target: target, - op: operation.value, - symbolWrite: symbolWrite, - value: value, - transpile: function () { - if (this.symbolWrite) { - return "var " + target.root.name + " = " + parser.transpile(value); - } else { - if (this.op === "into") { + _parser.addGrammarElement("setCmd", function (parser, tokens) { + if (tokens.matchToken("set")) { + + var target = parser.parseElement("target", tokens); + + tokens.requireToken("to"); + + var value = parser.parseElement("expression", tokens); + + var directWrite = target.propPath.length === 0; + var symbolWrite = directWrite && target.root.type === "symbol"; + if (directWrite && !symbolWrite) { + parser.raiseParseError(tokens, "Can only put directly into symbols, not references") + } + + return { + type: "setCmd", + target: target, + symbolWrite: symbolWrite, + value: value, + transpile: function () { + if (this.symbolWrite) { + return "var " + target.root.name + " = " + parser.transpile(value); + } else { var lastProperty = target.propPath.pop(); // steal last property for assignment return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + " target." + lastProperty + "=" + parser.transpile(value) + "})"; - } else if (this.op === "before") { - return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + - " target.insertAdjacentHTML('beforebegin', " + parser.transpile(value) + ")" + - "})"; - } else if (this.op === "start") { - return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + - " target.insertAdjacentHTML('afterbegin', " + parser.transpile(value) + ")" + - "})"; - } else if (this.op === "end") { - return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + - " target.insertAdjacentHTML('beforeend', " + parser.transpile(value) + ")" + - "})"; - } else if (this.op === "after") { - return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + - " target.insertAdjacentHTML('afterend', " + parser.transpile(value) + ")" + - "})"; } } } } - } - }) + }) - _parser.addGrammarElement("setCmd", function (parser, tokens) { - if (tokens.matchToken("set")) { + _parser.addGrammarElement("ifCmd", function (parser, tokens) { + if (tokens.matchToken("if")) { + var expr = parser.parseElement("expression", tokens); + tokens.matchToken("then"); // optional 'then' + var trueBranch = parser.parseElement("commandList", tokens); + if (tokens.matchToken("else")) { + var falseBranch = parser.parseElement("commandList", tokens); + } + if (tokens.hasMore()) { + tokens.requireToken("end"); + } + return { + type: "ifCmd", + expr: expr, + trueBranch: trueBranch, + falseBranch: falseBranch, + transpile: function () { + return "if(" + parser.transpile(expr) + "){" + "" + parser.transpile(trueBranch) + "}" + + " else {" + parser.transpile(falseBranch, "") + "}" - var target = parser.parseElement("target", tokens); - - tokens.requireToken("to"); - - var value = parser.parseElement("expression", tokens); - - var directWrite = target.propPath.length === 0; - var symbolWrite = directWrite && target.root.type === "symbol"; - if (directWrite && !symbolWrite) { - parser.raiseParseError(tokens, "Can only put directly into symbols, not references") - } - - return { - type: "setCmd", - target: target, - symbolWrite: symbolWrite, - value: value, - transpile: function () { - if (this.symbolWrite) { - return "var " + target.root.name + " = " + parser.transpile(value); - } else { - var lastProperty = target.propPath.pop(); // steal last property for assignment - return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" + - " target." + lastProperty + "=" + parser.transpile(value) + - "})"; } } } - } - }) - - _parser.addGrammarElement("ifCmd", function (parser, tokens) { - if (tokens.matchToken("if")) { - var expr = parser.parseElement("expression", tokens); - tokens.matchToken("then"); // optional 'then' - var trueBranch = parser.parseElement("commandList", tokens); - if (tokens.matchToken("else")) { - var falseBranch = parser.parseElement("commandList", tokens); - } - if (tokens.hasMore()) { - tokens.requireToken("end"); - } - return { - type: "ifCmd", - expr: expr, - trueBranch: trueBranch, - falseBranch: falseBranch, - transpile: function () { - return "if(" + parser.transpile(expr) + "){" + "" + parser.transpile(trueBranch) + "}" + - " else {" + parser.transpile(falseBranch, "") + "}" + }) + _parser.addGrammarElement("ajaxCmd", function (parser, tokens) { + if (tokens.matchToken("ajax")) { + var method = tokens.matchToken("GET") || tokens.matchToken("POST"); + if (method == null) { + parser.raiseParseError(tokens, "Requires either GET or POST"); } - } - } - }) - - _parser.addGrammarElement("ajaxCmd", function (parser, tokens) { - if (tokens.matchToken("ajax")) { - var method = tokens.matchToken("GET") || tokens.matchToken("POST"); - if (method == null) { - parser.raiseParseError(tokens, "Requires either GET or POST"); - } - if (method.value !== "GET") { - if (!tokens.matchToken("to")) { - var data = parser.parseElement("expression", tokens); - tokens.requireToken("to"); + if (method.value !== "GET") { + if (!tokens.matchToken("to")) { + var data = parser.parseElement("expression", tokens); + tokens.requireToken("to"); + } } - } - var url = parser.parseElement("string", tokens); - if (url == null) { - var url = parser.parseElement("nakedString", tokens); - } - - return { - type: "requestCommand", - method: method, - transpile: function () { - var capturedNext = this.next; - delete this.next; - return "_hyperscript.runtime.ajax('" + method.value + "', " + - parser.transpile(url) + ", " + - "function(response, xhr){ " + parser.transpile(capturedNext) + " }," + - parser.transpile(data, "null") + ")"; + var url = parser.parseElement("string", tokens); + if (url == null) { + var url = parser.parseElement("nakedString", tokens); } - }; - } - }) - //----------------------------------------------- - // 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; + return { + type: "requestCommand", + method: method, + transpile: function () { + var capturedNext = this.next; + delete this.next; + return "_hyperscript.runtime.ajax('" + method.value + "', " + + parser.transpile(url) + ", " + + "function(response, xhr){ " + parser.transpile(capturedNext) + " }," + + parser.transpile(data, "null") + ")"; + } + }; + } + }) } - function init(elt) { - _runtime.initElement(elt); + //==================================================================== + // API + //==================================================================== + + 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; } )() })); \ No newline at end of file