Contents
Expressions in hyperscript are a mix of familiar and new. Many of the expected operations from javascript are there:
1 true "this is a string" null
[1, 2, 3], {foo:"bar", [computeKey()]: computeValue()}
Most of the common comparison and mathematical operators are there as well:
x > 10
y == null
lst.length < 5
a + b < 42
There are some minor improvements to them:
Property names in object literals can contain hyphens. This is convenient for representing CSS:
add { font-weight: bold } to the #element in me
However, once you get past the basics, hyperscript starts to get a little wild.
Let's start with CSS literals.
Hyperscript gives you the ability to embed CSS literals directly in your code to select elements. There are four main expression types:
You can refer to an element by ID directly in hyperscript as follows:
<div _="on click put 'Clicked!' into #example.innerHTML">Click Me</div>
<div id="example"></div>
The #example
is an ID literal and will evaluate to the element with the given id. Here we put some text into its
innerHTML
when the top div is clicked.
You can refer to a group of elements by class directly in hyperscript as follows:
<div _="on click put 'Clicked!' into .example.innerHTML">Click Me</div>
<div class="example"></div>
<div class="example"></div>
The #example
is an ID literal and will evaluate all the elements with the class example
on them. Here we put some
text into their innerHTML
when the top div is clicked. Note that the put command can work with
collections as well as single values, so it can put the given value into all the returned elements.
You can refer to a group of elements by an arbitrary CSS selector
by enclosing the selector in a <
and />
:
<div _="on click put 'Clicked!' into <div/>.innerHTML">Click Me</div>
<div class="example"></div>
<div class="example"></div>
This example will put "Clicked" into every div on the page!
<div _="on click put 'Clicked!' into <div:not(.example)/>.innerHTML">Click Me</div>
<div class="example"></div>
<div class="example"></div>
This example will put "Clicked" into every div that does not have the example
class on it.
We can also refer to <html/>
, <body/>
, or even <:root>
:
<button _="on click toggle @data-theme='light' on <:root/>">Dark/Light</button>
This example will toggle the data-theme='light'
property on :root
so we can toggle css-variable values as follows:
:root {
--bg-color: black;
--text-color: white;
}
:root[data-theme='light'] {
--bg-color: white;
--text-color: black;
}
Finally, you can refer to an attribute with two syntaxes:
<div foo="bar" _="on click put @foo into me">Click Me</div>
<div _="on click toggle [@foo='bar'] into me">Click Me</div>
The short syntax, @<attribute name>
can be used to get or set attribute values, and may be chained with
possessives:
for anchor in <a/>
log the anchor's @href
end
or with non-possessive property chains:
for anchor in <a/>
log anchor@href
end
The longer syntax, surrounding the @<attribute-name>
with square brackets, may be used for queries that require a
value, or for commands like toggle
or add
that require a value
for anchor in [@href]
log anchor@href
end
The in
expression isn't a literal, but can be used in conjunction with them for common patterns:
<div _="on click put 'Clicked!' into (<p/> in me).innerHTML">Click Me
<p></p>
<p></p>
</div>
The in
expression in this case evaluates the query in the given context on the right hand side. Here we are looking
up paragraph tags inside the clicked element (me
) and setting their innerHTML
to "Clicked!"
In addition to the typical comparison operators, such as ==
and !=
, hyperscript supports the following natural
language aliases
is
- equivalent to ==
: 1 is 1
is not
- equivalent to !=
: 1 is not 1
am
- equivalent to ==
: I am 1
am not
- equivalent to !=
: I am not 1
no
- equivalent to != null
: no .example in me
Note that I
is an alias for me
, the current element.
Furthermore, hyperscript supports two more comparison operators: matches
and contains
which can be used in the
following forms:
I match <:hover/>
it matches <:hover/>
I do not match <:hover/>
it does not match <:hover/>
I contain <:focus/>
it contains <:focus/>
I do not contain <:focus/>
it does not contain <:focus/>
Strings are similar to javascript, and can start with "
or '
.
<div _="on click set world to 'hyperscript' put 'Hello $world' into my innerHTML">
Click Me
</div>
In a few places, hyperscript allows "naked" strings, strings without a leading quote or double quote. An example is the fetch command, which can take a URL as a naked string:
<button _="on click fetch /example then put it into my innerHTML">
Fetch It!
</button>
Here the /example
element is an example of a naked string. Naked strings are ended by whitespace.
Javascript string templates are supported by using the same syntax, enclosing backtics:
<button _="on click fetch /example then put `result: ${the result}` into my innerHTML">
Fetch It!
</button>
The possessive expression is an expression that starts with my
or its
or a symbol followed by a `'s' and that is
roughly the equivalent of a property acesss
<div _="on click put the window's location into me">
Click Me
</div>
This is equivalent to:
<div _="on click put window.location into me">
Click Me
</div>
You may also access and set DOM attributes using the possessive with attribute literals:
Hyperscript centralizes conversions into a single construct, the as
expression:
10 as String
"10" as Int
"10.3" as Float
Out of the box hyperscript provides the following conversions:
String
- converts to stringInt
- converts to an integerFloat
- converts to a floatNumber
- converts to a numberDate
- converts to a dateThe .
operator in hyperscript is null safe, so elt.parent
will evaluate to null
if elt
is null
Properties on arrays, except for length
, are expanded via a flat map
set divs to </div> -- get all divs in the document
set divParents to divs.parentElement -- get all parents of those divs
set divChildren to divs.children -- get all children of those divs
Hyperscript does not have anonymous functions or complex arrow functions. Because hyperscript is async transparent complicated callbacks are generally not necessary.
However it does support a simple, expression-only version of arrow functions called "blocks", with a slightly different syntax:
set strs to ["a", "ab", "abc"]
set lens to strs.map( \ s -> s.length )
Blocks start with a backslash, followed by args, then an ->
and then an expression value.
By default, hyperscript synchronizes on any Promises that go through its runtime. Consider the following code:
<script type="text/hyperscript">
def waitThenReturn10()
wait 10ms
return 10
end
</script>
<button _="on click put 'The answer was $waitThenReturn10()' into my.innerHTML">
Click Me...
</button>
Here we have an asynchronous function with a wait
in it that will cause the function to return a Promise rather than
the value. Hyperscript will "pause" evaluation of the event handler until that promise resolves and can provide a
value to the string expression, and then continue. This is very cool and is usually what you want.
However, there may be a case where you don't want hyperscript to pause and, instead, want to pass the raw promise on
somewhere else. To do this, you can use the async
prefix for the expression
<button _="on click call handleAPromise(async waitThenReturn10())">
Click Me...
</button>
Here we pass the promise returned by waitThenReturn10()
out to handleAPromise()
, as a Promise, rather than resolving
it.
In a few places in the hyperscript grammar you can use "time expressions", which are just natural ways to specify a time interval. The wait command is one such place:
<button _="on click wait 2s then put 'I waited!' into my.innerHTML">
Click to Wait...
</button>
You can use the following formats for time expressions:
10 ms
- milliseconds10 milliseconds
- milliseconds10 s
- seconds10 seconds
- secondsNote that a space between the number and modifier are not necessary, and that the ms
modifier is just for clarity
since milliseconds is the normal interpretation for things like setTimeout()
, which the wait command is based on.
The of
expression allows you to reverse the normal object oriented syntax and write logic in more natural english
<button _="on click call window.location.reload()">
Reload the Location
</button>
Can be rewritten like this:
<button _="on click reload() the location of the window">
Reload the Location
</button>
The closest
expression allows you to find the closest match of a CSS selector
<%!-- logs the closest section to the div -->
<div _="on click log the closest <section/>">
...
</div>
If you pass an attribute literal to the closest expression, it will evaluate to the value of that attribute on the closest element that has it:
<%!-- logs the data-example attribute's value from the body tag -->
<body data-example="An example attribute">
<div _="on click log the closest @data-example">
...
</div>
</body>
You can use the parent
modifier in the closest expression to begin the search from the parent element of the current
element:
<%!-- logs the closest parent div to this div -->
<div _="on click log the closest parent <div/>">
...
</div>
The positional expressions, first
, last
and random
allows you to get the
first, last or a random element from an array-like object
<%!-- logs the first section in the document -->
<div _="on click log the first <section/>">
...
</div>
The relative positional expressions, next
and previous
allows you refer to the
next or previous element of a given type within a linear, depth first forward or backward scan of the elements in the
DOM tree
<%!-- add the focused class to the next div in the dom with the header class on it -->
<div _="on click add .focused to the next <div.header/>">
...
</div>
The cookies
symbol is an automatically available symbol in all contexts that presents a
localStorage-like API for accessing cookies:
set cookies.myCookie to 'foo' # sets the cooke 'myCookie' to the value true, forever
set cookies['myCookie'] to 'bar' # updates the previous cookies value
set cookies['myCookie'] to
{value: 'doh',
expires: maxAge:6000} # set value and make it expire in 6000 seconds or 100 minutes
cookies.clear('myCookie') # clears the given cookie
cookies.clearAll() # clears all cookies
cookies.length # returns the number of cookies
cookies[0] # returns the first cookie as a {name:<name>, value:<value>} struct
for c in cookies ... end # iterates over all cookies as {name:<name>, value:<value>} structs