summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorcarson <carson@leaddyno.com>2020-06-12 13:42:55 -0700
committercarson <carson@leaddyno.com>2020-06-12 13:42:55 -0700
commit0ac641b63fc7b2d313cad03f37742c8922eef0a2 (patch)
tree16d902b83c17a611a8e8e94509c32bbe4ef052e8
parent7a0010f43cb84b251d0d184d8c4a54480e795570 (diff)
downloadhtmx-0ac641b63fc7b2d313cad03f37742c8922eef0a2.tar.gz
htmx-0ac641b63fc7b2d313cad03f37742c8922eef0a2.zip
remove the hx-error-url attribute in favor of hyperscript
add basic hyperscript compatibility testing
-rw-r--r--src/htmx.js21
-rw-r--r--test/attributes/hx-error-url.js32
-rw-r--r--test/ext/hyperscript.js37
-rw-r--r--test/index.html8
-rw-r--r--test/lib/_hyperscript.js1524
-rw-r--r--www/attributes/hx-error-url.md24
-rw-r--r--www/docs.md2
-rw-r--r--www/reference.md1
8 files changed, 1577 insertions, 72 deletions
diff --git a/src/htmx.js b/src/htmx.js
index fc53fd54..54761675 100644
--- a/src/htmx.js
+++ b/src/htmx.js
@@ -888,11 +888,19 @@ return (function () {
});
}
+ function isHyperScriptAvailable() {
+ return typeof _hyperscript !== "undefined";
+ }
+
function processNode(elt) {
var nodeData = getInternalData(elt);
if (!nodeData.processed) {
nodeData.processed = true;
+ if(isHyperScriptAvailable()){
+ _hyperscript.init(elt);
+ }
+
if (elt.value) {
nodeData.lastValue = elt.value;
}
@@ -914,7 +922,6 @@ return (function () {
processWebSocketInfo(elt, nodeData, wsInfo);
}
triggerEvent(elt, "processedNode.htmx");
-
}
if (elt.children) { // IE
forEach(elt.children, function(child) { processNode(child) });
@@ -925,16 +932,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') {
@@ -983,7 +980,7 @@ return (function () {
}
if (detail.error) {
logError(detail.error);
- sendError(elt, eventName, detail);
+ triggerEvent(elt, "error.htmx", {errorDetail:detail})
}
var eventResult = elt.dispatchEvent(event);
withExtensions(elt, function (extension) {
diff --git a/test/attributes/hx-error-url.js b/test/attributes/hx-error-url.js
deleted file mode 100644
index 3ee46aa7..00000000
--- a/test/attributes/hx-error-url.js
+++ /dev/null
@@ -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();
- });
-})
diff --git a/test/ext/hyperscript.js b/test/ext/hyperscript.js
new file mode 100644
index 00000000..37602c6e
--- /dev/null
+++ b/test/ext/hyperscript.js
@@ -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 put event.detail.errorDetail.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");
+ });
+
+}); \ No newline at end of file
diff --git a/test/index.html b/test/index.html
index 13fc2bd0..1b47a4dd 100644
--- a/test/index.html
+++ b/test/index.html
@@ -64,7 +64,6 @@
<!-- 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,6 +81,13 @@
<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/method-override.js"></script>
<script src="ext/method-override.js"></script>
diff --git a/test/lib/_hyperscript.js b/test/lib/_hyperscript.js
new file mode 100644
index 00000000..97697430
--- /dev/null
+++ b/test/lib/_hyperscript.js
@@ -0,0 +1,1524 @@
+//AMD insanity
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define([], factory);
+ } else {
+ // Browser globals
+ root._hyperscript = factory();
+ }
+}(typeof self !== 'undefined' ? self : this, function () {
+ return (function () {
+ 'use strict';
+
+ //-----------------------------------------------
+ // Lexer
+ //-----------------------------------------------
+ var _lexer = function () {
+ var OP_TABLE = {
+ '+': 'PLUS',
+ '-': 'MINUS',
+ '*': 'MULTIPLY',
+ '/': 'DIVIDE',
+ '.': 'PERIOD',
+ '\\': 'BACKSLASH',
+ ':': 'COLON',
+ '%': 'PERCENT',
+ '|': 'PIPE',
+ '!': 'EXCLAMATION',
+ '?': 'QUESTION',
+ '#': 'POUND',
+ '&': 'AMPERSAND',
+ ';': 'SEMI',
+ ',': 'COMMA',
+ '(': 'L_PAREN',
+ ')': 'R_PAREN',
+ '<': 'L_ANG',
+ '>': 'R_ANG',
+ '<=': 'LTE_ANG',
+ '>=': 'GTE_ANG',
+ '==': 'EQ',
+ '===': 'EQQ',
+ '{': 'L_BRACE',
+ '}': 'R_BRACE',
+ '[': 'L_BRACKET',
+ ']': 'R_BRACKET',
+ '=': 'EQUALS'
+ };
+
+ function isValidCSSClassChar(c) {
+ return isAlpha(c) || isNumeric(c) || c === "-" || c === "_";
+ }
+
+ function isValidCSSIDChar(c) {
+ return isAlpha(c) || isNumeric(c) || c === "-" || c === "_" || c === ":";
+ }
+
+ function isWhitespace(c) {
+ return c === " " || c === "\t" || isNewline(c);
+ }
+
+ function positionString(token) {
+ return "[Line: " + token.line + ", Column: " + token.col + "]"
+ }
+
+ function isNewline(c) {
+ return c === '\r' || c === '\n';
+ }
+
+ function isNumeric(c) {
+ return c >= '0' && c <= '9';
+ }
+
+ function isAlpha(c) {
+ return (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z');
+ }
+
+
+ function makeTokensObject(tokens, consumed, source) {
+
+ function raiseError(tokens, error) {
+ _parser.raiseParseError(tokens, error);
+ }
+
+ function requireOpToken(value) {
+ var token = matchOpToken(value);
+ if (token) {
+ return token;
+ } else {
+ raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'");
+ }
+ }
+
+ function matchAnyOpToken(op1, op2, op3) {
+ for (var i = 0; i < arguments.length; i++) {
+ var opToken = arguments[i];
+ var match = matchOpToken(opToken);
+ if (match) {
+ return match;
+ }
+ }
+ }
+
+ function matchOpToken(value) {
+ if (currentToken() && currentToken().op && currentToken().value === value) {
+ return consumeToken();
+ }
+ }
+
+ function requireTokenType(type1, type2, type3, type4) {
+ var token = matchTokenType(type1, type2, type3, type4);
+ if (token) {
+ return token;
+ } else {
+ raiseError(this, "Expected one of " + JSON.stringify([type1, type2, type3]));
+ }
+ }
+
+ function matchTokenType(type1, type2, type3, type4) {
+ if (currentToken() && currentToken().type && [type1, type2, type3, type4].indexOf(currentToken().type) >= 0) {
+ return consumeToken();
+ }
+ }
+
+ function requireToken(value, type) {
+ var token = matchToken(value, type);
+ if (token) {
+ return token;
+ } else {
+ raiseError(this, "Expected '" + value + "' but found '" + currentToken().value + "'");
+ }
+ }
+
+ function matchToken(value, type) {
+ var type = type || "IDENTIFIER";
+ if (currentToken() && currentToken().value === value && currentToken().type === type) {
+ return consumeToken();
+ }
+ }
+
+ function consumeToken() {
+ var match = tokens.shift();
+ consumed.push(match);
+ return match;
+ }
+
+ function hasMore() {
+ return tokens.length > 0;
+ }
+
+ function currentToken() {
+ return tokens[0];
+ }
+
+ return {
+ matchAnyOpToken: matchAnyOpToken,
+ matchOpToken: matchOpToken,
+ requireOpToken: requireOpToken,
+ matchTokenType: matchTokenType,
+ requireTokenType: requireTokenType,
+ consumeToken: consumeToken,
+ matchToken: matchToken,
+ requireToken: requireToken,
+ list: tokens,
+ source: source,
+ hasMore: hasMore,
+ currentToken: currentToken
+ }
+ }
+
+ function tokenize(string) {
+ var source = string;
+ var tokens = [];
+ var position = 0;
+ var column = 0;
+ var line = 1;
+ var lastToken = "<START>";
+
+ while (position < source.length) {
+ consumeWhitespace();
+ if (currentChar() === "-" && nextChar() === "-") {
+ consumeComment();
+ } else {
+ if (!possiblePrecedingSymbol() && currentChar() === "." && isAlpha(nextChar())) {
+ tokens.push(consumeClassReference());
+ } else if (!possiblePrecedingSymbol() && currentChar() === "#" && isAlpha(nextChar())) {
+ tokens.push(consumeIdReference());
+ } else if (isAlpha(currentChar())) {
+ tokens.push(consumeIdentifier());
+ } else if (isNumeric(currentChar())) {
+ tokens.push(consumeNumber());
+ } else if (currentChar() === '"' || currentChar() === "'") {
+ tokens.push(consumeString());
+ } else if (OP_TABLE[currentChar()]) {
+ tokens.push(consumeOp());
+ } else {
+ if (position < source.length) {
+ throw Error("Unknown token: " + currentChar() + " ");
+ }
+ }
+ }
+ }
+
+ return makeTokensObject(tokens, [], source);
+
+ function makeOpToken(type, value) {
+ var token = makeToken(type, value);
+ token.op = true;
+ return token;
+ }
+
+ function makeToken(type, value) {
+ return {
+ type: type,
+ value: value,
+ start: position,
+ end: position + 1,
+ column: column,
+ line: line
+ };
+ }
+
+ function consumeComment() {
+ while (currentChar() && !isNewline(currentChar())) {
+ consumeChar();
+ }
+ consumeChar();
+ }
+
+ function consumeClassReference() {
+ var classRef = makeToken("CLASS_REF");
+ var value = consumeChar();
+ while (isValidCSSClassChar(currentChar())) {
+ value += consumeChar();
+ }
+ classRef.value = value;
+ classRef.end = position;
+ return classRef;
+ }
+
+
+ function consumeIdReference() {
+ var idRef = makeToken("ID_REF");
+ var value = consumeChar();
+ while (isValidCSSIDChar(currentChar())) {
+ value += consumeChar();
+ }
+ idRef.value = value;
+ idRef.end = position;
+ return idRef;
+ }
+
+ function consumeIdentifier() {
+ var identifier = makeToken("IDENTIFIER");
+ var value = consumeChar();
+ while (isAlpha(currentChar())) {
+ value += consumeChar();
+ }
+ identifier.value = value;
+ identifier.end = position;
+ return identifier;
+ }
+
+ function consumeNumber() {
+ var number = makeToken("NUMBER");
+ var value = consumeChar();
+ while (isNumeric(currentChar())) {
+ value += consumeChar();
+ }
+ if (currentChar() === ".") {
+ value += consumeChar();
+ }
+ while (isNumeric(currentChar())) {
+ value += consumeChar();
+ }
+ number.value = value;
+ number.end = position;
+ return number;
+ }
+
+ function consumeOp() {
+ var value = consumeChar(); // consume leading char
+ while (currentChar() && OP_TABLE[value + currentChar()]) {
+ value += consumeChar();
+ }
+ var op = makeOpToken(OP_TABLE[value], value);
+ op.value = value;
+ op.end = position;
+ return op;
+ }
+
+ function consumeString() {
+ var string = makeToken("STRING");
+ var startChar = consumeChar(); // consume leading quote
+ var value = "";
+ while (currentChar() && currentChar() !== startChar) {
+ if (currentChar() === "\\") {
+ consumeChar(); // consume escape char and move on
+ }
+ value += consumeChar();
+ }
+ if (currentChar() !== startChar) {
+ throw Error("Unterminated string at " + positionString(string));
+ } else {
+ consumeChar(); // consume final quote
+ }
+ string.value = value;
+ string.end = position;
+ return string;
+ }
+
+ function currentChar() {
+ return source.charAt(position);
+ }
+
+ function nextChar() {
+ return source.charAt(position + 1);
+ }
+
+ function consumeChar() {
+ lastToken = currentChar();
+ position++;
+ column++;
+ return lastToken;
+ }
+
+ function possiblePrecedingSymbol() {
+ return isAlpha(lastToken) || isNumeric(lastToken) || lastToken === ")" || lastToken === "}" || lastToken === "]"
+ }
+
+ function consumeWhitespace() {
+ while (currentChar() && isWhitespace(currentChar())) {
+ if (isNewline(currentChar())) {
+ column = 0;
+ line++;
+ }
+ consumeChar();
+ }
+ }
+ }
+
+ return {
+ tokenize: tokenize
+ }
+ }();
+
+ //-----------------------------------------------
+ // Parser
+ //-----------------------------------------------
+ var _parser = function () {
+
+ var GRAMMAR = {}
+
+ function addGrammarElement(name, definition) {
+ GRAMMAR[name] = definition;
+ }
+
+ function createParserContext(tokens) {
+ var currentToken = tokens.currentToken();
+ var source = tokens.source;
+ var lines = source.split("\n");
+ var line = currentToken ? currentToken.line - 1 : lines.length - 1;
+ var contextLine = lines[line];
+ var offset = currentToken ? currentToken.column : contextLine.length - 1;
+ return contextLine + "\n" + " ".repeat(offset) + "^^\n\n";
+ }
+
+ function raiseParseError(tokens, message) {
+ message = (message || "Unexpected Token : " + tokens.currentToken().value) + "\n\n" +
+ createParserContext(tokens);
+ var error = new Error(message);
+ error.tokens = tokens;
+ throw error
+ }
+
+ function parseElement(type, tokens, root) {
+ var expressionDef = GRAMMAR[type];
+ if (expressionDef) return expressionDef(_parser, tokens, root);
+ }
+
+ function parseAnyOf(types, tokens) {
+ for (var i = 0; i < types.length; i++) {
+ var type = types[i];
+ var expression = parseElement(type, tokens);
+ if (expression) {
+ return expression;
+ }
+ }
+ }
+
+ function parseHyperScript(tokens) {
+ return parseElement("hyperscript", tokens)
+ }
+
+ function transpile(node, defaultVal) {
+ if (node == null) {
+ return defaultVal;
+ }
+ var src = node.transpile();
+ if (node.next) {
+ return src + "\n" + transpile(node.next)
+ } else {
+ return src;
+ }
+ }
+
+ return {
+ // parser API
+ parseElement: parseElement,
+ parseAnyOf: parseAnyOf,
+ parseHyperScript: parseHyperScript,
+ raiseParseError: raiseParseError,
+ addGrammarElement: addGrammarElement,
+ transpile: transpile
+ }
+ }();
+
+ //-----------------------------------------------
+ // Runtime
+ //-----------------------------------------------
+ var _runtime = function () {
+ var SCRIPT_ATTRIBUTES = ["_", "script", "data-script"];
+
+ function matchesSelector(elt, selector) {
+ // noinspection JSUnresolvedVariable
+ var matchesFunction = elt.matches ||
+ elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector
+ || elt.webkitMatchesSelector || elt.oMatchesSelector;
+ return matchesFunction && matchesFunction.call(elt, selector);
+ }
+
+ function makeEvent(eventName, detail) {
+ var evt;
+ if (window.CustomEvent && typeof window.CustomEvent === 'function') {
+ evt = new CustomEvent(eventName, {bubbles: true, cancelable: true, detail: detail});
+ } else {
+ evt = document.createEvent('CustomEvent');
+ evt.initCustomEvent(eventName, true, true, detail);
+ }
+ return evt;
+ }
+
+ function triggerEvent(elt, eventName, detail) {
+ var detail = detail || {};
+ detail["sentBy"] = elt;
+ var event = makeEvent(eventName, detail);
+ var eventResult = elt.dispatchEvent(event);
+ return eventResult;
+ }
+
+ function forEach(arr, func) {
+ if (arr.length) {
+ for (var i = 0; i < arr.length; i++) {
+ func(arr[i]);
+ }
+ } else {
+ func(arr);
+ }
+ }
+
+ function getScript(elt) {
+ for (var i = 0; i < SCRIPT_ATTRIBUTES.length; i++) {
+ var scriptAttribute = SCRIPT_ATTRIBUTES[i];
+ if (elt.hasAttribute && elt.hasAttribute(scriptAttribute)) {
+ return elt.getAttribute(scriptAttribute)
+ }
+ }
+ return null;
+ }
+
+ function applyEventListeners(hypeScript, elt) {
+ forEach(hypeScript.eventListeners, function (eventListener) {
+ eventListener(elt);
+ });
+ }
+
+ function setScriptAttrs(values) {
+ SCRIPT_ATTRIBUTES = values;
+ }
+
+ function getScriptSelector() {
+ return SCRIPT_ATTRIBUTES.map(function (attribute) {
+ return "[" + attribute + "]";
+ }).join(", ");
+ }
+
+ function isType(o, type) {
+ return Object.prototype.toString.call(o) === "[object " + type + "]";
+ }
+
+ function evaluate(typeOrSrc, srcOrCtx, ctxArg) {
+ if (isType(srcOrCtx, "Object")) {
+ var src = typeOrSrc;
+ var ctx = srcOrCtx;
+ var type = "expression"
+ } else if (isType(srcOrCtx, "String")) {
+ var src = srcOrCtx;
+ var type = typeOrSrc
+ var ctx = ctxArg;
+ } else {
+ var src = typeOrSrc;
+ var ctx = {};
+ var type = "expression";
+ }
+ ctx = ctx || {};
+ var compiled = _parser.parseElement(type, _lexer.tokenize(src) ).transpile();
+ var evalString = "(function(" + Object.keys(ctx).join(",") + "){return " + compiled + "})";
+ // TODO parser debugging
+ if(false) console.log("transpile: " + compiled);
+ if(false) console.log("evalString: " + evalString);
+ var args = Object.keys(ctx).map(function (key) {
+ return ctx[key]
+ });
+ if(false) console.log("args", args);
+ return eval(evalString).apply(null, args);
+ }
+
+ 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(true) console.log(transpiled);
+ var hyperscriptObj = eval(transpiled);
+ hyperscriptObj.applyEventListenersTo(elt);
+ }
+ }
+
+ function ajax(method, url, callback, data) {
+ var xhr = new XMLHttpRequest();
+ xhr.onload = function() {
+ callback(this.response)
+ };
+ xhr.open(method, url);
+ xhr.send(JSON.stringify(data));
+ }
+
+ function typeCheck(value, typeString, nullOk) {
+ if (value == null && nullOk) {
+ return value;
+ }
+ var typeName = Object.prototype.toString.call(value).slice(8, -1);
+ var typeCheckValue = value && typeName === typeString;
+ if (typeCheckValue) {
+ return value;
+ } else {
+ throw new Error("Typecheck failed! Expected: " + typeString + ", Found: " + typeName);
+ }
+ }
+
+ return {
+ typeCheck: typeCheck,
+ forEach: forEach,
+ triggerEvent: triggerEvent,
+ matchesSelector: matchesSelector,
+ getScript: getScript,
+ applyEventListeners: applyEventListeners,
+ setScriptAttrs: setScriptAttrs,
+ initElement: initElement,
+ 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("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("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");
+ }
+ 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 = [];
+ 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("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 + ")";
+ }
+ }
+ } else {
+ 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 value = parser.parseAnyOf(["symbol", "classRef", "idRef"], tokens);
+ if (value == null) {
+ parser.raiseParseError(tokens, "Expected a valid target expression");
+ }
+ return {
+ type: "target",
+ value: value,
+ transpile: function (context) {
+ if (value.type === "classRef") {
+ return parser.transpile(value);
+ } else if (value.type === "idRef") {
+ return "[" + parser.transpile(value) + "]";
+ } else {
+ return "[" + parser.transpile(value) + "]"; //TODO, check if array?
+ }
+ }
+ };
+ });
+
+ _parser.addGrammarElement("command", function (parser, tokens) {
+ return parser.parseAnyOf(["onCmd", "addCmd", "removeCmd", "toggleCmd", "waitCmd", "sendCmd",
+ "takeCmd", "logCmd", "callCmd", "putCmd", "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"
+ }) +
+ " function applyEventListenersTo(elt) { _hyperscript.runtime.applyEventListeners(this, elt) }" +
+ " return {eventListeners:eventListeners, applyEventListenersTo:applyEventListenersTo}\n" +
+ "})()"
+ }
+ };
+ })
+
+
+ _parser.addGrammarElement("eventListener", function (parser, tokens) {
+ tokens.requireToken("on");
+ var on = parser.parseElement("dotPath", 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 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" +
+ 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(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)" +
+ "})";
+ }
+ }
+ }
+ }
+ });
+
+ _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 + "')" +
+ "})";
+ }
+ }
+ }
+ }
+ }
+ });
+
+ _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)" +
+ " }" +
+ "})";
+ }
+ }
+ }
+ }
+ })
+
+ _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("dotPath", function (parser, tokens) {
+ var root = tokens.matchTokenType("IDENTIFIER");
+ if (root) {
+ var path = [root.value];
+ while (tokens.matchOpToken(".")) {
+ path.push(tokens.requireTokenType("IDENTIFIER").value);
+ }
+ return {
+ type: "dotPath",
+ path: path,
+ transpile: function () {
+ return path.join(".");
+ }
+ }
+ }
+ });
+
+ _parser.addGrammarElement("sendCmd", function (parser, tokens) {
+ if (tokens.matchToken("send")) {
+
+ var eventName = parser.parseElement("dotPath", tokens);
+
+ var details = parser.parseElement("objectLiteral", 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("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")
+ }
+ return {
+ type: "takeCmd",
+ classRef: classRef,
+ from: from,
+ transpile: function () {
+ var clazz = this.classRef.value.substr(1);
+ return " _hyperscript.runtime.forEach(" + parser.transpile(from) + ", function (target) { target.classList.remove('" + clazz + "') }); " +
+ "me.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(", ") + ")";
+ }
+ }
+ };
+ }
+ })
+
+ _parser.addGrammarElement("callCmd", function (parser, tokens) {
+ if (tokens.matchToken("call")) {
+ return {
+ type: "callCmd",
+ expr: parser.parseElement("expression", tokens),
+ transpile: function () {
+ return "var it = " + parser.transpile(this.expr);
+ }
+ }
+ }
+ })
+
+ _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("afterbegin") ||
+ tokens.matchToken("beforeend") ||
+ tokens.matchToken("after");
+
+ if (operation == null) {
+ parser.raiseParseError(tokens, "Expected one of 'into', 'before', 'afterbegin', 'beforeend', 'after'")
+ }
+ var target = parser.parseElement("target", tokens);
+ var propPath = []
+ while (tokens.matchOpToken(".")) {
+ propPath.push(tokens.requireTokenType("IDENTIFIER").value)
+ }
+
+ var directWrite = propPath.length === 0 && operation.value === "into";
+ var symbolWrite = directWrite && target.value.type === "symbol";
+ if (directWrite && !symbolWrite) {
+ parser.raiseParseError(tokens, "Can only put directly into symbols, not references")
+ }
+
+ return {
+ type: "putCmd",
+ target: target,
+ propPath: propPath,
+ op: operation.value,
+ symbolWrite: symbolWrite,
+ value: value,
+ transpile: function () {
+ if (this.symbolWrite) {
+ return "var " + target.value.name + " = " + parser.transpile(value);
+ } else {
+ var dotPath = propPath.length === 0 ? "" : "." + propPath.join(".");
+ if (this.op === "into") {
+ return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" +
+ " target" + dotPath + "=" + parser.transpile(value) +
+ "})";
+ } else if (this.op === "before") {
+ return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" +
+ " target" + dotPath + ".insertAdjacentHTML('beforebegin', " + parser.transpile(value) + ")" +
+ "})";
+ } else if (this.op === "afterbegin") {
+ return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" +
+ " target" + dotPath + ".insertAdjacentHTML('afterbegin', " + parser.transpile(value) + ")" +
+ "})";
+ } else if (this.op === "beforeend") {
+ return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" +
+ " target" + dotPath + ".insertAdjacentHTML('beforeend', " + parser.transpile(value) + ")" +
+ "})";
+ } else if (this.op === "after") {
+ return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" +
+ " target" + dotPath + ".insertAdjacentHTML('afterend', " + parser.transpile(value) + ")" +
+ "})";
+ }
+ }
+ }
+ }
+ }
+ })
+
+ _parser.addGrammarElement("ifCmd", function (parser, tokens) {
+ if (tokens.matchToken("if")) {
+ var expr = parser.parseElement("expression", tokens);
+ 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");
+ }
+ if (method.value === "GET") {
+ tokens.requireToken("from");
+ } else {
+ tokens.requireToken("to");
+ }
+
+ var url = parser.parseElement("string", tokens);
+ if (url == null) {
+ parser.raiseParseError(tokens, "Requires a URL");
+ }
+ if (tokens.matchToken("with")) {
+ tokens.requireToken("body");
+ var data = parser.parseElement("expression", tokens);
+ if (data == null) {
+ parser.raiseParseError(tokens, "Requires a URL");
+ }
+ }
+ return {
+ type: "requestCommand",
+ method: method,
+ transpile: function () {
+ var capturedNext = this.next;
+ delete this.next;
+ return "_hyperscript.runtime.ajax('" + method.value + "', " +
+ parser.transpile(url) + ", " +
+ "function(it){ " + parser.transpile(capturedNext) + " }," +
+ parser.transpile(data, "null") + ")";
+ }
+ };
+ }
+ })
+
+ //-----------------------------------------------
+ // 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 evaluate(str) {
+ return _runtime.evaluate(str);
+ }
+
+ return {
+ lexer: _lexer,
+ parser: _parser,
+ runtime: _runtime,
+ evaluate: evaluate,
+ init: init,
+ start: start
+ }
+ }
+ )()
+})); \ No newline at end of file
diff --git a/www/attributes/hx-error-url.md b/www/attributes/hx-error-url.md
deleted file mode 100644
index b6d2715b..00000000
--- a/www/attributes/hx-error-url.md
+++ /dev/null
@@ -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
diff --git a/www/docs.md b/www/docs.md
index c1007ae3..8b3818b6 100644
--- a/www/docs.md
+++ b/www/docs.md
@@ -568,8 +568,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
diff --git a/www/reference.md b/www/reference.md
index 873715f6..c9747666 100644
--- a/www/reference.md
+++ b/www/reference.md
@@ -13,7 +13,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