Skip to content

Event Pub-Sub

Event publication and subscription, also known as event emitter, is a commonly used pattern in JavaScript programming.

  • Publishing an event means raising an event for some other code to handle. Publishing an event is also referred to as firing an event or triggering an event.
  • Subscribing an event means registering an event handler function to the event. The event handler function executes when the correspondent event

At the core, AppRun is an event pub-sub system.

Event Life Cycle

When an AppRun event is published, the following steps take place:

  1. AppRun dispatches the events to the event handlers defined in the update along with the current state.
  2. The event handlers create a new state based on the current state.
  3. AppRun passes the new state to the view function.
  4. The view function creates HTML or a Virtual DOM.
  5. AppRun renders the HTML/Virtual DOM to the screen
  6. AppRun calls the optional rendered function to complete the AppRun event life cycle.

AppRun event life cycle

AppRun Event Life Cycle connects the state, view, and update (event handlers) together. Take a look at the Counter example again.

const state = 0;
const view = state => {
  return `<div>
    <h1>${state}</h1>
    <button onclick='app.run("-1")'>-1</button>
    <button onclick='app.run("+1")'>+1</button>
  </div>`;
};
const update = {
  '+1': state => state + 1,
  '-1': state => state - 1
};
app.start(document.body, state, view, update);

When one of the buttons is clicked, it publishes AppRun event +1 or -1. The event handlers increase or decrease the state and return a new state. The view function creates the virtual DOM using the new state. Finally, AppRun renders the virtual DOM.

Event Scope

In AppRun global application mode, events are global events, which means that events are published and handled globally by all modules.

In AppRun components, events are limited within the component's local scope. Local events are only available inside the components.

Note

app.run publishes global events this.run publishes local events

Event Directives

In addition to using app.run and this.run for publishing events

AppRun Directives provides syntax sugar to simplify event publishing.

JSX Directives

The directives are special HTML attributes with names starting with $, such as $onclick. They are extensions to the JSX syntax to simplify the JSX or add extra features.

We can use $onclick to simplify the syntax of publishing AppRun events from

<button onclick={()=>app.run('+1')}>+1</button>

to We use tuples for passing event parameters.

<button $onclick={['add', +1]}>+1</button>

Also, the $onclick directive can call the event directly.

const add = count => count + 1;
const view = count => <button $onclick={add}>
  Clicks: {count}
</button>;
app.start(document.body, 0, view);

You can see, because there are no events in this case, we don't need the update object anymore.

lit-html Directive

lit-html is the DOM rendering technology that lets us write HTML templates using string literals.

Following the idea of using the JSX event directive what can we do similar thing with lit-html?

The good news is that lit-html also has the directive concept to bring the event directive to lit-html. So the directive for lit-html is called run.

The example below shows how to use the run directive to trigger AppRun events. Also, similar to the JSX event directives, the run directive can call the event lifecycle directly.

const add = (state, delta) => state + delta;
const view = state => {
  return html`<div>
    <h1>${state}</h1>
      <button @click=${run('add', -1)}>-1</button>
      <button @click=${run('add', +1)}>+1</button>
    </div>`;
};
app.start(document.body, 0, view, {add});

The run directive will:

  • Call the add function
  • Call the view function
  • Render the HTML element (document.body)

Asynchronous Events

In the service API-oriented applications, the state is created asynchronous operations. e.g., getting remote data from the server.

It is easy to handle asynchronous operations in the AppRun event handlers. We only need to add the async keyword in front of the event handler and call the functions that return a Promise object with the await keyword.

const state = {};
const view = state => <>
  <div><button $onclick="fetchComic">fetch ...</button></div>
  {state.loading && <div>loading ... </div>}
  {state.comic && <img src={state.comic.url}/>}
</>;
const update = {
  'loading': (state, loading) => ({...state, loading }),
  'fetchComic': async _ => {
    app.run('loading', true);
    const response = await fetch('https://xkcd-imgs.herokuapp.com/');
    const comic = await response.json();
    return {comic};
  }
};
app.start(document.body, state, view, update);

Use Events for Everything

Web programming is event-driven. All we have to do is to convert DOM events to AppRun events to trigger the AppRun event life cycle.

DOM events => AppRun Events => (current state) => Update => (new state) => View => (HTML/Virtual DOM) => Render Web Page

Events are not only for handling user interactions. They are used for everything in AppRun.

Event Typing

Events can be strongly typed using TypeScript Discriminated Unions. If you are interested, please read this post: strong-typing.

typed events