Events are at the core of hyperscript, and event handlers are the primary entry point into most hyperscript code.
hyperscript's event handlers allow you to respond to any event (not just DOM events, as with onClick handlers) and
provide a slew of features for making working with events easier.
Here is an example:
<style>
.clicked::after {
content: " ... Clicked!"
}
</style>
<button _="on click add .clicked">
Add The "clicked" Class To Me
</button>
The script above, again, found on the _ attribute, does, well, almost exactly what it says:
On the 'click' event for this button, add the 'clicked' class to this button
This is the beauty of hyperscript: you probably knew what it was doing immediately, when reading it.
Event handlers have a very extensive syntax that allows you to, for example:
first modifier (e.g. on first click), counts (e.g. on click 1) or event filters (on keyup[key is 'Escape'])elsewhere (i.e. outside the current element)You can read all the gory details on the event handler page, but chances are, if you want some special handling of an event, hyperscript has a nice, clear syntax for doing so.
By default, the event handler will use the queue last strategy, so if the event is triggered again before the event handler
finishes, only the most recent event will be queued and handled when the current event handler completes.
You can modify this behavior in a few different ways:
An event handler with the every modifier will execute the event handler for every event that is received,
even if the preceding handler execution has not finished.
<button _="on every click add .clicked">
Add The "clicked" Class To Me
</button>
This is useful in cases where you want to make sure you get the handler logic for every event going immediately.
The every keyword is a prefix to the event name, but for other queuing options, you postfix the event name
with the queue keyword.
You may pick from one of four strategies:
none - Any events that arrive while the event handler is active will be droppedall - All events that arrive will be added to a queue and handled in orderfirst - The first event that arrives will be queued, all others will be droppedlast - The last event that arrives will be queued, all others will be droppedqueue last is the default behavior
<button _="on click queue all
increment :count
wait 1s then put it into the next <output/>">
Click Me Quickly...
</button>
<output>--</output>
If you click quickly on the button above you will see that the count slowly increases as each event waits 1 second and then completes, and the next event that has queued up executes.
You can destructure properties found either on the
event or in the event.detail properties by appending a parenthesized list of names after the event name.
This will create a local variable of the same name as the referenced property:
<button _="on mousedown(button) put the button into the next <output/>">
Click Me With Different Buttons...
</button>
<output>--</output>
Here the event.button property is being destructured into a local variable, which we then put into the next
output element
You can filter events by adding a bracketed expression after the event name and destructured properties (if any).
The expression should return a boolean value true if the event handler should execute.
Note that symbols referenced in the expression will be resolved as properties of the event, then as symbols in the global scope.
This lets you, for example, test for a middle click on the click event, by referencing the button property on that event directly:
<button _="on mousedown[button==1] add .clicked">
Middle-Click To Add The "clicked" Class To Me
</button>
An event handler can exit with the halt command. By default this command will halt the current event
bubbling, call preventDefault() and exit the current event handlers. However, there are forms available to stop only
the event from bubbling, but continue on in the event handler:
<script type="text/hyperscript">
on mousedown
halt the event -- prevent text selection...
-- do other stuff...
end
</script>
You may also use the exit command to exit a function, discussed below.
hyperscript not only makes it easy to respond to events, but also makes it very easy to send events to other elements
using the send and trigger commands. Both commands do the same thing:
sending an event to an element (possibly the current element!) to handle.
Here are a few examples:
<button _="on click send foo to the next <output/>">Send Foo</button>
<button _="on click trigger bar on the next <output/>">Send Bar</button>
<output _="on foo put 'I got a foo event!' into me
on bar put 'I got a bar event!' into me">
No Events Yet...
</output>
You can also pass arguments to events via the event.detail property, and use the destructuring syntax discussed above to parameterize events:
<button _="on click send showMessage(message:'Foo!') to the next <output/>">Send Foo</button>
<button _="on click send showMessage(message:'Bar!') to the next <output/>">Send Bar</button>
<output _="on showMessage(message) put `The message '${message}' was sent to me` into me">
No Events Yet...
</output>
As you can see, working with events is very natural in hyperscript. This allows you to build clear, readable event-driven code without a lot of fuss.
hyperscript includes a few synthetic events that make it easier to use more complex APIs in JavaScript.
You can listen for mutations on an element with the on mutation form. This will use the Mutation Observer
API, but will act more like a regular event handler.
<div _='on mutation of @foo put "Mutated" into me'></div>
This div will listen for mutations of the foo attribute on this div and, when one occurs, will put the value
"Mutated" into the element.
Here is a div that is set to content-editable='true' and that listens to mutations and updates a mutation count
below:
<div contenteditable="true"
_="on mutation of anything increment :mutationCount then put it into the next <output/>">
Hello World
</div>
<output>--</output>
Another synthetic event is the intersection event that uses the Intersection Observer
API. Again, hyperscript makes this API feel more event-driven:
<img _="on intersection(intersecting) having threshold 0.5
if intersecting transition opacity to 1
else transition opacity to 0 "
src="https://placebear.com/200/300"/>
This image will become visible when 50% or more of it has scrolled into view. Note that the intersecting property
is destructured into a local symbol, and the having threshold modifier is used to specify that 50% of the image
must be showing.
Here is a demo:
You can listen for element resizes using the on resize form. This uses the Resize Observer API under the covers:
<div _="on resize put `${detail.width}x${detail.height}` into #size">
Resize me
</div>
The detail object contains width, height, and the full contentRect from the resize entry.
Like mutation and intersection events, the from clause can be used to observe a different element:
<div _="on resize from #panel put detail.width into me">
--
</div>
If you have logic that you wish to run when an element is initialized, you can use the init block to do so:
<div _="init transition my opacity to 100% over 3 seconds">
Fade Me In
</div>
The init keyword should be followed by a set of commands to execute when the element is loaded.
For simple cases, on is the right tool. But when a value can be
changed from multiple places, or when you don't want to list every source of change,
reactive features let you just declare what you want and it stays in sync.
live keeps the DOM in sync with values:
<button _="on click increment $count">+1</button>
<button _="on click set $count to 0">Reset</button>
<output _="live put 'Count: ' + $count into me"></output>
when reacts to changes with side effects or chained logic:
<div _="when $source changes set $derived to (it * 2)"></div>
<output _="when $derived changes put it into me"></output>
bind keeps two values in sync (two-way):
<input type="checkbox" id="dark-toggle" />
<body _="bind .dark and #dark-toggle's checked">
See the live, when, and bind pages
for full details.
Functions in hyperscript are defined by using the def keyword.
Functions defined on elements will be available to the element the function is defined on, as well as any child elements.
Functions can also be defined in a hyperscript script tag:
<script type="text/hyperscript">
def waitAndReturn()
wait 2s
return "I waited..."
end
</script>
This will define a global function, waitAndReturn() that can be invoked from anywhere in hyperscript.
Hyperscript can also be loaded remotely in ._hs files.
When loaded in this manner, the script tags must appear before loading hyperscript:
<script type="text/hyperscript" src="/functions._hs"></script>
<script src="https://unpkg.com/hyperscript.org"></script>
Hyperscript is fully interoperable with JavaScript, and global hyperscript functions can be called from JavaScript as well as vice-versa:
var str = waitAndReturn();
str.then(function(val){
console.log("String is: " + val);
})
Hyperscript functions can take parameters and return values in the expected way:
<script type="text/hyperscript">
def increment(i)
return i + 1
end
</script>
You may exit a function using return if you wish to return a value or
exit if you do not want to return a value.
You can namespace a function by prefixing it with dot separated identifiers. This allows you to place functions into a specific namespace, rather than polluting the global namespace:
<script type="text/hyperscript">
def utils.increment(i)
return i + 1
end
</script>
<script>
console.log(utils.increment(41)); // access it from JavaScript
</script>
Both functions and event handlers may have a catch block associated with them:
def example
call mightThrowAnException()
catch e
log e
end
on click
call mightThrowAnException()
catch e
log e
end
This allows you to handle exceptions that occur during the execution of the function or event handler.
If you do not include a catch block on an event handler and an uncaught exception occurs, an exception event
will be triggered on the current element and can be handled via an event handler, with the error set to the
message of the exception:
on exception(error)
log "An error occurred: " + error
Note that exception handling in hyperscript respects the async-transparent behavior of the language.
Both functions and event handlers also support a finally block to ensure that some cleanup code is executed:
on click
add @disabled to me
fetch /example
put the result after me
finally
remove @disabled from me
In this code we ensure that the disabled property is removed from the current element.
You may throw an exception using the familiar throw keyword:
on click
if I do not match .selected
throw "I am not selected!"
...