|
| 1 | +# superviews client |
| 2 | + |
| 3 | +Work in Progress |
| 4 | + |
| 5 | +In addition to being a template language that compiles to [incremental-dom](https://github.com/google/incremental-dom), superviews is a clientside library with a set of helpful classes and methods for building web applications based on the Web Components spec, specifically [Custom Elements V1](https://www.w3.org/TR/custom-elements/). |
| 6 | + |
| 7 | +Custom Elements V1 gives the ability to extend by simply defining classes. |
| 8 | + |
| 9 | +E.g. |
| 10 | + |
| 11 | +```js |
| 12 | +// Create a class with custom methods |
| 13 | +// overrides, special behavior |
| 14 | +class Greetings extends HTMLElement { |
| 15 | + show() { |
| 16 | + alert(this.textContent); |
| 17 | + } |
| 18 | +} |
| 19 | + |
| 20 | +// Define it in the CustomElementRegistry |
| 21 | +window.customElements.define('x-greeting', Greetings); |
1 | 22 | ``` |
| 23 | + |
| 24 | +This can then be used in HTML like so: |
| 25 | + |
| 26 | +```html |
| 27 | +<x-greeting></x-greeting> |
| 28 | +``` |
| 29 | + |
| 30 | +`superviews.js` provides a thin wrapper around this pattern that includes events, event delegation and property and attribute validation. |
| 31 | + |
| 32 | +The idea is that by describing what your components inputs (property/attributes) and outputs (events) are, you document the component and can also use this information to validate the state of the component. |
| 33 | + |
| 34 | +JSON Schema is used to describe the properties and attributes. |
| 35 | + |
| 36 | +While you'll probably be using `incremental-dom` to build the internal html of the component, there's no necessity to. |
| 37 | + |
| 38 | +## Example |
| 39 | + |
| 40 | +All the examples are CJS/browserify. |
| 41 | + |
| 42 | +Say you want to build a Todo List Web Component. |
| 43 | + |
| 44 | +It's to have a `theme` that can be either `light` or `dark` but defaults to `dark`. |
| 45 | +It also needs to be supplied a list of items called `todos`. Both these inputs are required. The list of `todo` items should be an array of objects with a required integer `id`, required `text` string and a boolean flag to indicate if the task `isCompleted`. |
| 46 | + |
| 47 | +A third optional input is a `title` for the todo list and has a max length of 10. |
| 48 | + |
| 49 | +The Todos component should emit 3 events - `change`, `add` and `remove`. |
| 50 | + |
| 51 | +Using `superviews.js`, it looks like this: |
| 52 | + |
| 53 | +```js |
2 | 54 | const superviews = require('superviews.js') |
3 | | -const validator = require('superviews.js/validator') |
4 | | -const idom = require('superviews.js/incremental-dom') |
5 | | -const patch = require('superviews.js/patch') |
6 | | -const patchOuter = require('superviews.js/patch-outer') |
7 | | -const documentRegisterElement = require('superviews.js/dre') |
| 55 | + |
| 56 | +// `options` should be an object. |
| 57 | +// `options.schema` should be a JSON Schema describing the component inputs |
| 58 | +// `options.events` should be an array describing the events the component can emit |
| 59 | +const options = { |
| 60 | + schema: { |
| 61 | + properties: { |
| 62 | + title: { type: 'string', maxlength: 10 } |
| 63 | + theme: { type: 'string', enum: ['light', 'dark'], default: 'dark' } |
| 64 | + todos: { |
| 65 | + type: 'array', |
| 66 | + items: { |
| 67 | + properties: { |
| 68 | + id: { type: 'integer' }, |
| 69 | + text: { type: 'string' }, |
| 70 | + isCompleted: { type: 'boolean', default: false } |
| 71 | + }, |
| 72 | + required: ['id', 'text'] |
| 73 | + } |
| 74 | + } |
| 75 | + }, |
| 76 | + required: ['todos'] |
| 77 | + }, |
| 78 | + events: { |
| 79 | + add: 'add', |
| 80 | + remove: 'remove', |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +class Todos extends superviews(options) { |
| 85 | + constructor () { |
| 86 | + super() |
| 87 | + // Any initialisation code goes here |
| 88 | + } |
| 89 | + |
| 90 | + // Fires when the element is inserted into the DOM |
| 91 | + connectedCallback () {} |
| 92 | + |
| 93 | + // Fires when a property is changed |
| 94 | + propertyChangedCallback (name, oldValue, newValue) { |
| 95 | + } |
| 96 | + |
| 97 | + // Fires when an attribute is changed |
| 98 | + attributeChangedCallback (name, oldValue, newValue) { |
| 99 | + } |
| 100 | + |
| 101 | + // Fires when the element should re-render. This can be |
| 102 | + // as a consequence of an update to a prop or attr or from a |
| 103 | + // call to `this.render()`. |
| 104 | + renderCallback () {} |
| 105 | + |
| 106 | + // Fires when the element is removed from the DOM |
| 107 | + disconnectedCallback () {} |
| 108 | +} |
| 109 | + |
| 110 | +window.customElements.define('x-todos', Todos) |
| 111 | +``` |
| 112 | + |
| 113 | +This could then be used like: |
| 114 | + |
| 115 | +```html |
| 116 | +<x-todos theme="light" title="My Todos"> |
| 117 | +</x-todos> |
| 118 | + |
| 119 | +<script> |
| 120 | + const myList = document.querySelector('x-todos') |
| 121 | +
|
| 122 | + // Set the initial todos |
| 123 | + myList.todos = [{ |
| 124 | + id: 1, |
| 125 | + text: 'Walk dog' |
| 126 | + }, { |
| 127 | + id: 2, |
| 128 | + text: 'Buy milk' |
| 129 | + }, { |
| 130 | + id: 3, |
| 131 | + text: 'Send birthday card to Liz', |
| 132 | + isCompleted: true |
| 133 | + }] |
| 134 | +</script> |
| 135 | +``` |
| 136 | + |
| 137 | +## superviews(options[, Base = HTMLElement]) |
| 138 | + |
| 139 | +Creates a reusable Custom Element class using the provided `options`. |
| 140 | + |
| 141 | +`Base` defaults to HTMLElement, use this to provide a different base class e.g. `HTMLButtonElement` if you want to inherit from a different base class. See [here](https://developers.google.com/web/fundamentals/getting-started/primers/customelements#extendhtml) for more information |
| 142 | + |
| 143 | +## Options |
| 144 | + |
| 145 | +The `options` object provided to superviews can contain 2 keys, `schema` and `events`. |
| 146 | + |
| 147 | +### Schema |
| 148 | + |
| 149 | +The component attributes and properties are defined with a JSON Schema specified as `options.schema`. |
| 150 | + |
| 151 | +All top level properties of the root schema are defined as properties on the element. |
| 152 | + |
| 153 | +Primitive properties (`string`, `boolean`, `number`, `integer` etc.) are stored as attributes, while `array`s and `object`s are stored as properties on the element. |
| 154 | + |
| 155 | +Only these registered properties and attributes will cause the `attributeChangedCallback` or `propertyChangedCallback` to fire. |
| 156 | + |
| 157 | +### Events |
| 158 | + |
| 159 | +Events defined in `options.events` can be subscribed to is the usual way: |
| 160 | + |
| 161 | +```js |
| 162 | +// Listeners can be handled in the usual way |
| 163 | +myList.addEventListener('add', function (e) {}) |
| 164 | +myList.addEventListener('remove', function (e) {}) |
| 165 | +``` |
| 166 | + |
| 167 | +DOM Level 1 events are also supported |
| 168 | + |
| 169 | +```html |
| 170 | +<x-todos theme="light" title="My Todos" onremove="..."> |
| 171 | +</x-todos> |
| 172 | +``` |
| 173 | + |
| 174 | +`emit(name, detail)` is used to emit an event. |
| 175 | + |
| 176 | +## Event Delegation |
| 177 | + |
| 178 | +Use `on` and `off` to add/remove events. This is usually done in the constructor: |
| 179 | + |
| 180 | +```js |
| 181 | +constructor () { |
| 182 | + super() |
| 183 | + this |
| 184 | + .on('click', (e) => {}) |
| 185 | + .on('click', 'button.update', (e) => {}) |
| 186 | + .on('change', 'input[type=checkbox]', (e) => {}) |
| 187 | +} |
| 188 | + |
| 189 | +addTodo () { |
| 190 | + // const newTodo = ... |
| 191 | + this.emit('add', newTodo) |
| 192 | +} |
| 193 | +``` |
| 194 | + |
| 195 | +Internally this uses [ftlabs/ftdomdelegate](https://github.com/ftlabs/ftdomdelegate) which is available independently if you have a need for it here. |
| 196 | + |
| 197 | +```js |
8 | 198 | const delegator = require('superviews.js/delegator') |
| 199 | +``` |
| 200 | + |
| 201 | + |
| 202 | +## Rendering |
| 203 | + |
| 204 | +`renderCallback` is called when a property or attribute has changed. You should update the UI at this point. Using incremental-dom this might look like this: |
| 205 | + |
| 206 | +```js |
| 207 | +renderCallback () { |
| 208 | + patch(this, fn, data) |
| 209 | +} |
| 210 | +``` |
| 211 | + |
| 212 | +To trigger a render callback manually `this.render()`. This will happen on the next event loop. If you want to update immediately use `this.render(true)`. |
| 213 | + |
| 214 | +## Validation |
| 215 | + |
| 216 | +You can call `validate()` on the element to check the properties and attributes are valid. This uses [is-my-json-valid](https://www.npmjs.com/package/is-my-json-valid). |
| 217 | + |
| 218 | +```js |
| 219 | +const result = this.validate() |
| 220 | +if (!result.ok) { |
| 221 | + console.log(result.errors) |
| 222 | +} |
| 223 | +``` |
| 224 | + |
| 225 | +```js |
| 226 | +const validator = require('superviews.js/validator') |
| 227 | +``` |
| 228 | + |
| 229 | +The validator can be `require`d from `superviews.js/validator` if you find a need to use it for other purposes. |
| 230 | + |
| 231 | +## Store |
| 232 | + |
| 233 | +```js |
9 | 234 | const Store = require('superviews.js/store') |
10 | | -``` |
| 235 | +``` |
| 236 | +[Freezer.js](https://github.com/arqex/freezer) is a tree data structure that emits events on updates, even if the modification is triggered by one of the leaves, making it easier to think in a reactive way. Freezer uses real immutable structures. It is the perfect store for you application. |
| 237 | + |
| 238 | +## Incremental DOM |
| 239 | + |
| 240 | +You can import `incremental-dom` |
| 241 | + |
| 242 | +```js |
| 243 | +const idom = require('superviews.js/incremental-dom') |
| 244 | +``` |
| 245 | + |
| 246 | +## W3C Custom Elements polyfill |
| 247 | + |
| 248 | +[WebReflection/document-register-element](https://github.com/WebReflection/document-register-element) is a stand-alone working lightweight version of the W3C Custom Elements specification. Use this if you need to support browsers that don't yet support Custom Elements V1. |
| 249 | + |
| 250 | +You can use it via `superviews.js` if you like: |
| 251 | + |
| 252 | +```js |
| 253 | +require('superviews.js/dre') |
| 254 | +``` |
| 255 | + |
| 256 | +For more info see the [examples](../examples) folder or open [todos](../examples/client/x-todos/index.html) or [widget](../examples/client/x-widget/test.html) |
0 commit comments