update hyperscript and docs

This commit is contained in:
carson 2020-06-13 13:17:25 -07:00
parent 06dc449e91
commit 84200f4599
3 changed files with 165 additions and 58 deletions

View File

@ -980,7 +980,7 @@ return (function () {
}
if (detail.error) {
logError(detail.error);
triggerEvent(elt, "error.htmx", {errorDetail:detail})
triggerEvent(elt, "error.htmx", {errorInfo:detail})
}
var eventResult = elt.dispatchEvent(event);
withExtensions(elt, function (extension) {

View File

@ -78,6 +78,9 @@
function makeTokensObject(tokens, consumed, source) {
var ignoreWhiteSpace = true;
matchTokenType("WHITESPACE"); // consume any initial whitespace
function raiseError(tokens, error) {
_parser.raiseParseError(tokens, error);
}
@ -141,9 +144,22 @@
function consumeToken() {
var match = tokens.shift();
consumed.push(match);
if(ignoreWhiteSpace) {
matchTokenType("WHITESPACE"); // consume any whitespace until the next token
}
return match;
}
function consumeUntilWhitespace() {
var tokenList = [];
ignoreWhiteSpace = false;
while (currentToken() && currentToken().type !== "WHITESPACE") {
tokenList.push(consumeToken());
}
ignoreWhiteSpace = true;
return tokenList;
}
function hasMore() {
return tokens.length > 0;
}
@ -164,7 +180,8 @@
list: tokens,
source: source,
hasMore: hasMore,
currentToken: currentToken
currentToken: currentToken,
consumeUntilWhitespace: consumeUntilWhitespace
}
}
@ -177,11 +194,12 @@
var lastToken = "<START>";
while (position < source.length) {
consumeWhitespace();
if (currentChar() === "-" && nextChar() === "-") {
consumeComment();
} else {
if (!possiblePrecedingSymbol() && currentChar() === "." && isAlpha(nextChar())) {
if (isWhitespace(currentChar())) {
tokens.push(consumeWhitespace());
} else if (!possiblePrecedingSymbol() && currentChar() === "." && isAlpha(nextChar())) {
tokens.push(consumeClassReference());
} else if (!possiblePrecedingSymbol() && currentChar() === "#" && isAlpha(nextChar())) {
tokens.push(consumeIdReference());
@ -281,7 +299,7 @@
function consumeOp() {
var value = consumeChar(); // consume leading char
while (currentChar() && OP_TABLE[value + currentChar()]) {
value += consumeChar();
value += consumeChar();
}
var op = makeOpToken(OP_TABLE[value], value);
op.value = value;
@ -329,13 +347,18 @@
}
function consumeWhitespace() {
var whitespace = makeToken("WHITESPACE");
var value = "";
while (currentChar() && isWhitespace(currentChar())) {
if (isNewline(currentChar())) {
column = 0;
line++;
}
consumeChar();
value += consumeChar();
}
whitespace.value = value;
whitespace.end = position;
return whitespace;
}
}
@ -458,6 +481,32 @@
}
}
function evalTarget(root, path) {
if (root.length) {
var last = root;
} else {
var last = [root];
}
while (path.length > 0) {
var prop = path.shift();
var next = []
// flat map
for (var i = 0; i < last.length; i++) {
var element = last[i];
var nextVal = element[prop];
if (nextVal && nextVal.length) {
next = next.concat(nextVal);
} else {
next.push(nextVal);
}
}
last = next;
}
return last;
}
function getScript(elt) {
for (var i = 0; i < SCRIPT_ATTRIBUTES.length; i++) {
var scriptAttribute = SCRIPT_ATTRIBUTES[i];
@ -499,7 +548,7 @@
var ctx = ctxArg;
} else {
var src = typeOrSrc;
var ctx = {};
var ctx = {};
var type = "expression";
}
ctx = ctx || {};
@ -552,6 +601,7 @@
return {
typeCheck: typeCheck,
forEach: forEach,
evalTarget: evalTarget,
triggerEvent: triggerEvent,
matchesSelector: matchesSelector,
getScript: getScript,
@ -599,6 +649,20 @@
}
})
_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) {
@ -1019,21 +1083,24 @@
});
_parser.addGrammarElement("target", function (parser, tokens) {
var value = parser.parseAnyOf(["symbol", "classRef", "idRef"], tokens);
if (value == null) {
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",
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?
}
propPath: propPath,
root: root,
transpile: function () {
return "_hyperscript.runtime.evalTarget(" + parser.transpile(root) + ", [" + propPath.map(function (prop) {
return "\"" + prop + "\""
}).join(", ") + "])";
}
};
});
@ -1091,6 +1158,15 @@
} 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",
@ -1102,6 +1178,7 @@
"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" +
@ -1301,14 +1378,24 @@
} 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 + "') }); " +
"me.classList.add('" + clazz + "');";
"_hyperscript.runtime.forEach( " + parser.transpile(forElt) + ", function (target) {" +
" target.classList.add('" + clazz + "')" +
"})";
}
}
}
@ -1369,13 +1456,9 @@
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";
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")
}
@ -1383,34 +1466,33 @@
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);
return "var " + target.root.name + " = " + parser.transpile(value);
} else {
var dotPath = propPath.length === 0 ? "" : "." + propPath.join(".");
if (this.op === "into") {
var lastProperty = target.propPath.pop(); // steal last property for assignment
return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" +
" target" + dotPath + "=" + parser.transpile(value) +
" target." + lastProperty + "=" + parser.transpile(value) +
"})";
} else if (this.op === "before") {
return "_hyperscript.runtime.forEach( " + parser.transpile(target) + ", function (target) {" +
" target" + dotPath + ".insertAdjacentHTML('beforebegin', " + parser.transpile(value) + ")" +
" target.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) + ")" +
" target.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) + ")" +
" target.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) + ")" +
" target.insertAdjacentHTML('afterend', " + parser.transpile(value) + ")" +
"})";
}
}
@ -1452,20 +1534,17 @@
if (method.value === "GET") {
tokens.requireToken("from");
} else {
tokens.requireToken("to");
if (!tokens.matchToken("to")) {
var data = parser.parseElement("expression", tokens);
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");
}
var url = parser.parseElement("nakedString", tokens);
}
return {
type: "requestCommand",
method: method,
@ -1473,8 +1552,8 @@
var capturedNext = this.next;
delete this.next;
return "_hyperscript.runtime.ajax('" + method.value + "', " +
parser.transpile(url) + ", " +
"function(it){ " + parser.transpile(capturedNext) + " }," +
parser.transpile(url) + ", " +
"function(response){ " + parser.transpile(capturedNext) + " }," +
parser.transpile(data, "null") + ")";
}
};

View File

@ -581,42 +581,70 @@ if you want to log everything while developing.
**NOTE: hyperscript is in very early alpha**
Htmx has a sister project named [hyperscript](https://hyperscript.org). Hyperscript is a small scripting language
designed to be expressive, making it ideal for embedding directly in HTML, handling custom events, etc. The language
is inspired by [HyperTalk](http://hypercard.org/HyperTalk%20Reference%202.4.pdf), javascript, [gosu](https://gosu-lang.github.io/)
and others.
Hyperscript is a small scripting language designed to be expressive, making it ideal for embedding directly in HTML,
handling custom events, etc. The language is inspired by [HyperTalk](http://hypercard.org/HyperTalk%20Reference%202.4.pdf),
javascript, [gosu](https://gosu-lang.github.io/) and others.
The language can be explored more fully on its website:
You can explore the language more fully on its main website:
<http://hyperscript.org>
### Catching Events with Hyperscript
### Events & Hyperscript
A primary use case of hyperscript is catching and responding to events triggered by htmx.
Hyperscript was designed to help address features and functionality from intercooler.js that are not implemented in htmx
directly, in a more flexible and open manner. One of its prime features is the ability to respond to arbitrary events
on a DOM element, using the `on` syntax:
Here is an example with an element that is shown for 5 seconds, then removes the element:
```html
<div _="on afterSettle.htmx log 'Settled!'">
...
</div>
```
This will log `Settled!` to the console when the `afterSettle.htmx` event is triggered.
#### intercooler.js features & hyperscript implementations
Below are some examples of intercooler features and the hyperscript equivalent.
##### `ic-remove-after`
Intercooler provided the [`ic-remove-after`](http://intercoolerjs.org/attributes/ic-remove-after.html) attribute
for removing an element after a given amount of time.
In hyperscript this is implemented like so:
```html
<div _="on load wait 5s then remove me">Here is a temporary message!</div>
```
Here is an example that posts all htmx errors to a server URL:
##### `ic-post-errors-to`
Intercooler provided the [`ic-post-errors-to`](http://intercoolerjs.org/attributes/ic-post-errors-to.html) attribute
for posting errors that occured during requests and responses.
In hyperscript similar functionality is implemented like so:
```html
<body _="on error.htmx ajax POST to /errors with body event.detail.errorDetail">
<body _="on error.htmx(errorInfo) ajax POST errorInfo to /errors">
...
</body>
```
Here is an example that takes an `active` class from other tabs:
##### `ic-switch-class`
Intercooler provided the [`ic-switch-class`](http://intercoolerjs.org/attributes/ic-switch-class.html) attribute, which
let you switch a class between siblings.
In hyperscript you can implement similar functionality like so:
```html
<div hx-target="#tabBody" _="on beforeOnLoad.htmx take .active from .tabs for event.target">
<a class="tabs" hx-get="/tabl1" >Tab 1</a>
<div hx-target="#content" _="on beforeOnLoad.htmx take .active from .tabs for event.target">
<a class="tabs active" hx-get="/tabl1" >Tab 1</a>
<a class="tabs" hx-get="/tabl2">Tab 2</a>
<a class="tabs" hx-get="/tabl3">Tab 3</a>
</div>
<div id="tabBody">...</div>
<div id="content">Tab 1 Content</div>
```
## <a name="config"></a>[Configuring htmx](#config)