all repos — h3 @ 46d47399e2530acbb4a6e5bdffe3aaccf3fd2d19

A tiny, extremely minimalist JavaScript microframework.

docs/md/key-concepts.md

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
## Key Concepts

There are just a few things you should know about if you want to use H3. 

Oh... and a solid understanding of HTML and JavaScript wouldn't hurt either ;)

### HyperScript

H3 uses a [HyperScript](https://openbase.io/js/hyperscript)-like syntax to create HTML elements in pure JavaScript. No, you are actually creating Virtual DOM nodes with it but it can be easier to think about them as HTML elements, or better, something that *eventually* will be rendered as an HTML element.

The main difference between H3's HyperScript implementation and others is that it uses **h3** as the main constructor to create nodes. HyperScript uses **h**, Mithril uses **m**, ...kind of an obvious choice if you ask me. If you don't like it, you can rename it to *piripicchio* if you want, and it will *still* be used in the same way.

How, you ask? Like this:

```js
h3("div.test", [
  h3("ul", [
    h3("li", "This is..."),
    h3("li", "...a simple..."),
    h3("li", "unordered list.")
  ])
]);
```

...which will output:

```html
<div class="test">
  <ul>
    <li>This is...</li>
    <li>...a simple...</li>
    <li>...unordered list.</li>
  </ul>
</div>
```

Simple enough. Yes there are some quirks to it, but check the API or Usage docs for those.

### Components

In H3, a component is a function that returns a Virtual Node or a string (that will be treated as a textual DOM node). 

Yes that's it. An example? here:

```js
let count = 0;
const CounterButton = () => {
  return h3("button", {
    onclick: () => count +=1 && h3.redraw()
  }, `You clicked me ${count} times.`);
}
```

### Store

H3 essentially uses something very, *very* similar to [Storeon](https://github.com/storeon/storeon) for state management *and* also as a very simple client-side event dispatcher/subscriber (seriously, it is virtually the same code as Storeon). Typically you'll only use the default store created by H3 upon initialization, and you'll use the `h3.dispatch()` and `h3.on()` methods to dispatch and subscribe to events.

The current application state is accessible via the `h3.state` property.

### Modules

The `h3.init()` method takes an array of *modules* that can be used to manipulate the application state when specific events are received. A simple module looks like this:

```js
const error = () => {
  h3.on("$init", () => ({ displayEmptyTodoError: false }));
  h3.on("error/clear", (state) => ({ displayEmptyTodoError: false }));
  h3.on("error/set", (state) => ({ displayEmptyTodoError: true }));
};
```

Essentially a module is just a function that typically is meant to run only once to define one or more event subscriptions. Modules are the place where you should handle state changes in your application.

### Router

H3 comes with a very minimal but fully functional URL fragment router. You create your application routes when initializing your application, and you can navigate to them using ordinary `href` links or programmatically using the `h3.navigateTo` method.

The current route is always accessible via the `h3.route` property.


#### Route Components

A route components is a top-level component specified to handle a specific route. Unlike ordinary components, route components:

* may have a dedicated *setup* (after the route component is added to the DOM) and *teardown* phase (after the route component is removed from the DOM and the new route component is loaded).
* may have built-in local state, initialized during setup and (typically) destroyed during teardown.

Route components are stll created using ordinary function returning a VNode, but you can optionally define a **setup** and a **teardown** async methods on them (Functions are Objects in JavaScript after all...) to be executed during the corresponding phase.

Note that:
* Both the **setup** method take an object as a parameter, representing the component state. Such object will be empty the first time the **setup** method is called for a given component, but it may contain properties not removed during teardowns.
* The **teardown** method can return an object, which will be retained as component state. If however nothing is returned, the component state is deleted.
* Both methods can be asynchronous, in which case H3 will wait for their completion before proceeding.

### How everything works...

The following sequence diagram summarizes how H3 works, from its initialization to the redraw and navigation phases.

![Sequence Diagram](images/h3.sequence.svg)

When the `h3.init()` method is called at application level, the following operations are performed in sequence:

1. The *Store* is created and initialized.
2. Any *Module* specified when calling `h3.init()` is executed.
3. The **$init** event is dispatched.
4. The *preStart* function (if specified when calling `h3.init()`) is executed.
5. The *Router* is initialized and started.
6. The **setup()** method of the matching Route Component is called (if any).
8. The **$navigation** event is dispatched.
9. The *Route Component* matching the current route and all its child components are rendered for the first time.
10. Any callback specified via the **$onrender** special attributes in the loaded components is executed once all components are rendered.
11. The **$redraw** event is dispatched.

Then, whenever the `h3.redraw()` method is called (typically within a component):

1. The whole application is redrawn, i.e. every *Component* currently rendered on the page is redrawn.
2. The **$redraw** event is dispatched.

Similarly, whenever the `h3.navigateTo()` method is called (typically within a component), or the URL fragment changes:

1. The *Router* processes the new path and determine which component to render based on the routing configuration.
2. The **teardow()** method of the current Route Component is called (if any).
3. The **setup()** method of the new matching Route Component is called (if any).
4. All DOM nodes within the scope of the routing are removed, all components are removed.
5. Any **$onrender** callback defined in the added components is executed once all components are rendered.
6. The **$navigation** event is dispatched.
7. All DOM nodes are removed.
8. The *Route Component* matching the new route and all its child components are rendered.
9. Any callback specified via the **$onrender** special attributes in the loaded components is executed once all components are rendered.
10. The **$redraw** event is dispatched.

And that's it. The whole idea is to make the system extremely *simple* and *predictable* &mdash; which means everything should be very easy to debug, too.