Skip to content

Commit 1f15a0b

Browse files
authored
Merge pull request #13 from heapwolf/v8.0.0
fixes nested initialization
2 parents c2c5f30 + e9775a0 commit 1f15a0b

File tree

4 files changed

+118
-22
lines changed

4 files changed

+118
-22
lines changed

API.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
| Method | Description |
66
| :--- | :--- |
7-
| `add(Class, Object)` | Register a class as a new custom-tag and provide options for it. |
7+
| `add(Class)` | Register a class as a new custom-tag and provide options for it. |
8+
| `init(root?)` | Initialize all components (optionally starating at a root node in the DOM). This is called automatically when an <App></App> component is added. |
89
| `escape(String)` | Escapes HTML characters from a string (based on [he][3]). |
910
| `sanitize(Object)` | Escapes all the strings found in an object literal. |
1011
| `match(Node, Selector)` | Match the given node against a selector or any matching parent of the given node. This is useful when trying to locate a node from the actual node that was interacted with. |
@@ -31,7 +32,7 @@
3132

3233
| Method | Description |
3334
| :--- | :--- |
34-
| `constructor(props)` | An instance of the element is created or upgraded. Useful for initializing state, setting up event listeners, or creating shadow dom. See the spec for restrictions on what you can do in the constructor. A constructor will receive an argument of `props` and must call `super(props)`. |
35+
| `constructor(object)` | An instance of the element is created or upgraded. Useful for initializing state, setting up event listeners, or creating shadow dom. See the spec for restrictions on what you can do in the constructor. The constructor's arguments must be forwarded by calling `super(object)`. |
3536
| `willConnect()` | Called prior to the element being inserted into the DOM. Useful for updating configuration, state and preparing for the render. |
3637
| `connected()` | Called every time the element is inserted into the DOM. Useful for running setup code, such as fetching resources or rendering. Generally, you should try to delay work until this time. |
3738
| `disconnected()` | Called every time the element is removed from the DOM. Useful for running clean up code. |

index.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class Tonic {
1515
const render = this.render
1616
this.render = () => this.wrap(render.bind(this))
1717
}
18+
1819
this._connect()
1920
Tonic.refs.push(this.root)
2021
}
@@ -28,9 +29,9 @@ class Tonic {
2829
return el.matches(s) ? el : el.closest(s)
2930
}
3031

31-
static add (c) {
32+
static add (c, isReady) {
3233
c.prototype._props = Object.getOwnPropertyNames(c.prototype)
33-
if (!c.name || c.name.length === 1) throw Error('Mangling detected, see guide. https://github.com/hxoht/tonic/blob/master/HELP.md.')
34+
if (!c.name || c.name.length === 1) throw Error('Mangling detected. https://github.com/heapwolf/tonic/blob/master/HELP.md')
3435

3536
const name = Tonic._splitName(c.name)
3637
Tonic.registry[name.toUpperCase()] = Tonic[c.name] = c
@@ -44,14 +45,23 @@ class Tonic {
4445
Tonic.styleNode = document.head.appendChild(styleTag)
4546
}
4647

47-
Tonic._constructTags()
48+
if (isReady || c.name === 'App') Tonic.init(document.firstElementChild)
4849
}
4950

50-
static _constructTags (root, states = {}) { /* eslint-disable no-new */
51-
for (const tagName of Tonic.tags) {
52-
for (const node of (root || document).getElementsByTagName(tagName)) {
53-
if (!node.disconnect) new Tonic.registry[tagName]({ node, state: states[node.id] })
51+
static init (node = document.firstElementChild, states = {}) {
52+
node = node.firstElementChild
53+
54+
while (node) {
55+
const tagName = node.tagName
56+
57+
if (Tonic.tags.includes(tagName)) { /* eslint-disable no-new */
58+
new Tonic.registry[tagName]({ node, state: states[node.id] })
59+
node = node.nextElementSibling
60+
continue
5461
}
62+
63+
Tonic.init(node, states)
64+
node = node.nextElementSibling
5565
}
5666
}
5767

@@ -79,6 +89,7 @@ class Tonic {
7989
if (typeof v === 'object' && v.__children__) return this._children(v)
8090
if (typeof v === 'object' || typeof v === 'function') return this._prop(v)
8191
if (typeof v === 'number') return `${v}__float`
92+
if (typeof v === 'boolean') return `${v.toString()}`
8293
return v
8394
}
8495
return values.map(ref).reduce(reduce, [s]).filter(filter).join('')
@@ -96,7 +107,7 @@ class Tonic {
96107
const oldProps = JSON.parse(JSON.stringify(this.props))
97108
this.props = Tonic.sanitize(typeof o === 'function' ? o(this.props) : o)
98109
if (!this.root) throw new Error('.reRender called on destroyed component, see guide.')
99-
Tonic._constructTags(this.root, this._setContent(this.root, this.render()))
110+
Tonic.init(this.root, this._setContent(this.root, this.render()))
100111
this.updated && this.updated(oldProps)
101112
}
102113

@@ -193,7 +204,7 @@ class Tonic {
193204
this.children = [...this.root.childNodes].map(node => node.cloneNode(true))
194205
this.children.__children__ = true
195206
this._setContent(this.root, this.render())
196-
Tonic._constructTags(this.root)
207+
Tonic.init(this.root)
197208
const style = this.stylesheet && this.stylesheet()
198209

199210
if (style && !Tonic.registry[this.root.tagName].styled) {

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
{
22
"name": "@conductorlab/tonic",
3-
"version": "7.3.0",
3+
"version": "8.0.0",
44
"description": "A composable component inspired by React.",
55
"main": "events.js",
66
"scripts": {
77
"test": "npm run test:browser",
88
"test:browser": "browserify --bare ./test | tape-run --node",
9+
"test:h": "browserify --bare ./test/h | tape-run --node",
910
"build:demo": "browserify --bare ./demo > ./docs/bundle.js"
1011
},
11-
"author": "",
12+
"author": "heapwolf",
1213
"license": "MIT",
1314
"devDependencies": {
1415
"browserify": "^16.2.2",

test/index.js

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ test('attach to dom', t => {
1717
<component-a></component-a>
1818
`
1919

20-
Tonic.add(ComponentA)
20+
Tonic.add(ComponentA, document.body)
2121

2222
const div = document.querySelector('div')
2323
t.ok(div, 'a div was created and attached')
@@ -63,7 +63,7 @@ test('pass props', t => {
6363
</component-b-b>
6464
`
6565
}
66-
})
66+
}, document.body)
6767

6868
const bb = document.getElementById('y')
6969
{
@@ -98,7 +98,7 @@ test('get element by id and set properties via the api', t => {
9898
}
9999
}
100100

101-
Tonic.add(ComponentC)
101+
Tonic.add(ComponentC, document.body)
102102

103103
{
104104
const div = document.getElementById('test')
@@ -178,7 +178,7 @@ test('stylesheets and inline styles', t => {
178178
}
179179
}
180180

181-
Tonic.add(ComponentF)
181+
Tonic.add(ComponentF, document.body)
182182
const style = document.head.getElementsByTagName('style')[0]
183183
const expected = `component-f div { color: red; }`
184184
t.equal(style.textContent, expected, 'style was prefixed')
@@ -216,8 +216,8 @@ test('component composition', t => {
216216
}
217217
}
218218

219-
Tonic.add(Foo)
220-
Tonic.add(Bar)
219+
Tonic.add(Foo, document.body)
220+
Tonic.add(Bar, document.body)
221221

222222
t.equal(document.body.querySelectorAll('.bar').length, 2, 'two bar divs')
223223
t.equal(document.body.querySelectorAll('.foo').length, 4, 'four foo divs')
@@ -251,8 +251,8 @@ test('persist named component state after re-renering', t => {
251251
}
252252
}
253253

254-
Tonic.add(StatefulParent)
255254
Tonic.add(StatefulChild)
255+
Tonic.add(StatefulParent, document.body)
256256
const parent = document.getElementsByTagName('stateful-parent')[0]
257257
parent.reRender()
258258
const child = document.getElementsByTagName('stateful-child')[0]
@@ -303,7 +303,7 @@ test('lifecycle events', t => {
303303
}
304304

305305
Tonic.add(Bazz)
306-
Tonic.add(Quxx)
306+
Tonic.add(Quxx, document.body)
307307
const q = document.querySelector('quxx')
308308
q.reRender({})
309309
const refsLength = Tonic.refs.length
@@ -339,6 +339,7 @@ test('compose sugar (this.children)', t => {
339339

340340
Tonic.add(ComponentG)
341341
Tonic.add(ComponentH)
342+
Tonic.init(document.body)
342343

343344
const g = document.querySelector('component-g')
344345
const children = g.querySelectorAll('.child')
@@ -386,9 +387,12 @@ test('check that composed elements use (and re-use) their initial innerHTML corr
386387
</component-i>
387388
`
388389

389-
Tonic.add(ComponentI)
390390
Tonic.add(ComponentJ)
391391
Tonic.add(ComponentK)
392+
Tonic.add(ComponentI)
393+
Tonic.init()
394+
395+
t.comment('Uses init() instead of <app>')
392396

393397
const i = document.querySelector('component-i')
394398
const kTags = i.getElementsByTagName('component-k')
@@ -414,6 +418,85 @@ test('check that composed elements use (and re-use) their initial innerHTML corr
414418
t.end()
415419
})
416420

421+
test('mixed order declaration', t => {
422+
class App extends Tonic {
423+
render () {
424+
return this.html`<div class="app">${this.children}</div>`
425+
}
426+
}
427+
428+
class ComponentA extends Tonic {
429+
render () {
430+
return `<div class="a">A</div>`
431+
}
432+
}
433+
434+
class ComponentB extends Tonic {
435+
render () {
436+
return this.html`<div class="b">${this.children}</div>`
437+
}
438+
}
439+
440+
class ComponentC extends Tonic {
441+
render () {
442+
return this.html`<div class="c">${this.children}</div>`
443+
}
444+
}
445+
446+
class ComponentD extends Tonic {
447+
render () {
448+
return `<div class="d">D</div>`
449+
}
450+
}
451+
452+
document.body.innerHTML = `
453+
<App>
454+
<component-a>
455+
</component-a>
456+
457+
<component-b>
458+
<component-c>
459+
<component-d>
460+
</component-d>
461+
</component-c>
462+
</component-b>
463+
</App>
464+
`
465+
466+
Tonic.add(ComponentD)
467+
Tonic.add(ComponentA)
468+
Tonic.add(ComponentC)
469+
Tonic.add(ComponentB)
470+
Tonic.add(App)
471+
472+
{
473+
const div = document.querySelector('.app')
474+
t.ok(div, 'a div was created and attached')
475+
}
476+
477+
{
478+
const div = document.querySelector('body .app .a')
479+
t.ok(div, 'a div was created and attached')
480+
}
481+
482+
{
483+
const div = document.querySelector('body .app .b')
484+
t.ok(div, 'a div was created and attached')
485+
}
486+
487+
{
488+
const div = document.querySelector('body .app .b .c')
489+
t.ok(div, 'a div was created and attached')
490+
}
491+
492+
{
493+
const div = document.querySelector('body .app .b .c .d')
494+
t.ok(div, 'a div was created and attached')
495+
}
496+
497+
t.end()
498+
})
499+
417500
test('cleanup, ensure exist', t => {
418501
t.end()
419502
process.exit(0)

0 commit comments

Comments
 (0)