Skip to content

Commit 1816dbc

Browse files
Client update
1 parent 31bd23a commit 1816dbc

File tree

12 files changed

+551
-303
lines changed

12 files changed

+551
-303
lines changed

client/index.js

Lines changed: 53 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,15 @@ const superviews = (options, Base = window.HTMLElement) => class Superviews exte
124124
*/
125125

126126
// Hold a map of bound handers to the original handler
127-
const handlers = new Map()
127+
// const handlers = new Map()
128128

129129
// Initialise the delegator
130130
const del = delegator(this)
131-
131+
this.on = del.on.bind(del)
132+
this.off = del.off.bind(del)
132133
cache.delegate = del
133-
cache.handlers = handlers
134+
135+
// cache.handlers = handlers
134136
cache.events = options.events
135137

136138
this.__superviews = cache
@@ -151,7 +153,7 @@ const superviews = (options, Base = window.HTMLElement) => class Superviews exte
151153
}
152154

153155
propertyChangedCallback (name, oldValue, newValue) {
154-
console.log('Property changed', name, oldValue, newValue)
156+
// Render on any change to observed property
155157
this.render()
156158
}
157159

@@ -172,54 +174,54 @@ const superviews = (options, Base = window.HTMLElement) => class Superviews exte
172174
}
173175
}
174176

175-
on (eventType, selector, handler, useCapture) {
176-
const del = this.__superviews.delegate
177-
const handlers = this.__superviews.handlers
178-
179-
// handler can be passed as
180-
// the second or third argument
181-
let bound
182-
if (typeof selector === 'function') {
183-
bound = selector.bind(this)
184-
handlers.set(selector, bound)
185-
selector = bound
186-
} else {
187-
bound = handler.bind(this)
188-
handlers.set(handler, bound)
189-
handler = bound
190-
}
191-
192-
del.on(eventType, selector, handler, useCapture)
193-
194-
return this
195-
}
196-
197-
off (eventType, selector, handler, useCapture) {
198-
const del = this.__superviews.delegate
199-
const handlers = this.__superviews.handlers
200-
201-
if (arguments.length === 0) {
202-
// Remove all
203-
handlers.clear()
204-
} else {
205-
// handler can be passed as
206-
// the second or third argument
207-
let bound
208-
if (typeof selector === 'function') {
209-
bound = handlers.get(selector)
210-
handlers.delete(selector)
211-
selector = bound
212-
} else {
213-
bound = handlers.get(handler)
214-
handlers.delete(handler)
215-
handler = bound
216-
}
217-
}
218-
219-
del.off(eventType, selector, handler, useCapture)
177+
// on (eventType, selector, handler, useCapture) {
178+
// const del = this.__superviews.delegate
179+
// const handlers = this.__superviews.handlers
180+
181+
// // handler can be passed as
182+
// // the second or third argument
183+
// let bound
184+
// if (typeof selector === 'function') {
185+
// bound = selector.bind(this)
186+
// handlers.set(selector, bound)
187+
// selector = bound
188+
// } else {
189+
// bound = handler.bind(this)
190+
// handlers.set(handler, bound)
191+
// handler = bound
192+
// }
193+
194+
// del.on(eventType, selector, handler, useCapture)
195+
196+
// return this
197+
// }
220198

221-
return this
222-
}
199+
// off (eventType, selector, handler, useCapture) {
200+
// const del = this.__superviews.delegate
201+
// const handlers = this.__superviews.handlers
202+
203+
// if (arguments.length === 0) {
204+
// // Remove all
205+
// handlers.clear()
206+
// } else {
207+
// // handler can be passed as
208+
// // the second or third argument
209+
// let bound
210+
// if (typeof selector === 'function') {
211+
// bound = handlers.get(selector)
212+
// handlers.delete(selector)
213+
// selector = bound
214+
// } else {
215+
// bound = handlers.get(handler)
216+
// handlers.delete(handler)
217+
// handler = bound
218+
// }
219+
// }
220+
221+
// del.off(eventType, selector, handler, useCapture)
222+
223+
// return this
224+
// }
223225

224226
emit (name, detail) {
225227
// Only emit registered events

docs/client.md

Lines changed: 252 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,256 @@
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);
122
```
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
254
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
8198
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
9234
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

Comments
 (0)