The on Feature

The 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>

Event Queuing

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:

queue last is the default behavior.

Exceptions

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>

Mutation Events

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.

Intersection Events

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:

Resize Events

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>

Examples

<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>

Syntax

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>