on FeatureThe on feature is the primary way you hook hyperscript into the DOM event system. You place it directly on elements using the _, script, or data-script attribute, and it lets you respond to any DOM event -- clicks, keypresses, custom events, and more -- with a body of hyperscript commands.
The event-name can be a symbol, a dot-separated symbol, or a string that names the event. The most obvious events
of interest are standard HTML DOM events such as click,
focus/blur, change, etc. There are many, many standard browser events that
may be of interest depending on what you are trying to build. You may also wish to define your own events for higher level abstractions.
If the every prefix is used, the event handler will not be synchronized (see queueing below.)
If the first prefix is used, the event handler will only fire once (equivalent to a count of 1).
The optional param-list is a comma separated list of parameter names. Parameters will be set from properties directly
on the event or in the details property. The event symbol is always available in an on feature and is set to the triggering event.
So if an event has the value event.detail.foo = "bar" then the on declaration could look like this:
<div _="on anEvent(foo) log foo">Log Foo</div>
The above could also be written in the following more long-winded manner:
<div _="on anEvent log event.detail.foo">Log Foo</div>
The optional filter is a boolean expression that will filter the event. Symbols in the expression will be resolved
against the event first, then against the global scope.
The optional count is a count filter with a value of either a specific number, a range, or an unbounded start:
on click 1
on click 2 to 10
on click 11 and on
You can listen to an event from another element using the from <expr> syntax, including the special
value elsewhere, which will listen for the event from elsewhere in the DOM. This is useful if you want "click-away to
close" behavior.
You can also scope the listener to a subtree with in <expr>, which restricts the handler to events whose target matches the given expression:
<div _="on click in .menu-item log 'menu item clicked'">...</div>
An event can specify a debounced at or throttled at value to debounce or throttle the events:
-- will wait until 500ms have passed without a keyup to trigger
on keyup debounced at 500ms ...
-- will fire every 500ms regardless of the number of events
on mousemove throttled at 500ms ...
Events can be repeated separated by an or to assign one handler to multiple events:
<div _="on click or touchstart fetch /example then put it into my innerHTML">
Fetch it...
</div>
The queue keyword allows you to specify an event queue strategy across all events for the handler (see queueing below.)
The body is a list of commands, optionally separated by the then keyword.
The end is optional if you are chaining on features together.
When the element is removed, the listener is removed too -- even if it's listening to another element that's still in the document:
Count: <output _="
on click from #inc
log "Increment"
increment my textContent
init
remove me
">0</output>
<!--After the <output/> is removed, clicking this will not log anything to
the console-->
<button id="inc">Increment</button>
You can control the event queuing behavior of an event handler by using the every and queue keyword.
If you prefix the event with every then every time the event is triggered the event handler will fire, even
if a previous event has not completed. The event handlers will run in parallel, and there will be no
queuing of events.
If you postfix the event with queue 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.
If an exception occurs during an event handler, the exception event will be triggered on the element, and may
be handled as a normal event:
<div
_="on click call mightThrow()
on exception(error) log error"
>
Click Me!
</div>
Hyperscript includes a few synthetic events that make use of more complex APIs. You can listen for
mutations on an element with the on mutation form. This uses the Mutation Observer
API, but acts 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.
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:
The resize synthetic event uses the Resize Observer
API to fire whenever an element's size changes. The event detail exposes width, height,
contentRect, and the raw observer entry:
<div _="on resize(width, height) put `${width} × ${height}` into me"></div>
<div _="on click call alert('You clicked me!')">Click Me!</div>
<div
_="on mouseenter add .visible to #help end
on mouseleave remove .visible from #help end"
>
Mouse Over Me!
</div>
<div id="help">I'm a helpful message!</div>
on [every | first] <event-spec>
(or [every | first] <event-spec>)*
[queue (all | first | last | none)]
<command>+
[end]
<event-spec> ::= <event-name> [(<param-list>)] [[\<filter>]] [<count>]
[<synthetic-modifier>]
[(from <expression> | from elsewhere | elsewhere)]
[in <expression>]
[<debounce> | <throttle>]
<synthetic-modifier> ::=
| mutation [of (anything | childList | attributes | subtree | characterData | @<attr>)
(or ...)*]
| intersection [with <expression>]
[having (margin <string> | threshold <expression>) (and ...)*]
| resize
<count> ::= <number> | <number> to <number> | <number> and on
<debounce> ::= debounced at <time-expression>
<throttle> ::= throttled at <time-expression>