Skip to content

Commit a0237c7

Browse files
committed
fixes nested initialization
when initializing tags, siblings were initialized more than once, this PR adds hierarchy to the initialization process. Because of the added hierarchy, the top-most node must be known so an argument is now accepted by the add method.
1 parent efcf005 commit a0237c7

File tree

4 files changed

+112
-20
lines changed

4 files changed

+112
-20
lines changed

API.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
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, root?)` | Register a class as a new custom-tag and provide options for it. |
88
| `escape(String)` | Escapes HTML characters from a string (based on [he][3]). |
99
| `sanitize(Object)` | Escapes all the strings found in an object literal. |
1010
| `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 +31,7 @@
3131

3232
| Method | Description |
3333
| :--- | :--- |
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)`. |
34+
| `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)`. |
3535
| `willConnect()` | Called prior to the element being inserted into the DOM. Useful for updating configuration, state and preparing for the render. |
3636
| `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. |
3737
| `disconnected()` | Called every time the element is removed from the DOM. Useful for running clean up code. |

index.js

Lines changed: 18 additions & 6 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,7 +29,7 @@ class Tonic {
2829
return el.matches(s) ? el : el.closest(s)
2930
}
3031

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

@@ -44,14 +45,24 @@ class Tonic {
4445
Tonic.styleNode = document.head.appendChild(styleTag)
4546
}
4647

47-
Tonic._constructTags()
48+
if (!root) return
49+
Tonic._constructTags(root)
4850
}
4951

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] })
52+
static _constructTags (node, states = {}) { /* eslint-disable no-new */
53+
node = node.firstElementChild
54+
55+
while (node) {
56+
const tagName = node.tagName
57+
58+
if (Tonic.tags.includes(tagName)) {
59+
new Tonic.registry[tagName]({ node, state: states[node.id] })
60+
node = node.nextElementSibling
61+
continue
5462
}
63+
64+
Tonic._constructTags(node, states)
65+
node = node.nextElementSibling
5566
}
5667
}
5768

@@ -79,6 +90,7 @@ class Tonic {
7990
if (typeof v === 'object' && v.__children__) return this._children(v)
8091
if (typeof v === 'object' || typeof v === 'function') return this._prop(v)
8192
if (typeof v === 'number') return `${v}__float`
93+
if (typeof v === 'boolean') return `${v.toString()}`
8294
return v
8395
}
8496
return values.map(ref).reduce(reduce, [s]).filter(filter).join('')

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: 89 additions & 10 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
@@ -338,7 +338,7 @@ test('compose sugar (this.children)', t => {
338338
`
339339

340340
Tonic.add(ComponentG)
341-
Tonic.add(ComponentH)
341+
Tonic.add(ComponentH, document.body)
342342

343343
const g = document.querySelector('component-g')
344344
const children = g.querySelectorAll('.child')
@@ -386,9 +386,9 @@ test('check that composed elements use (and re-use) their initial innerHTML corr
386386
</component-i>
387387
`
388388

389-
Tonic.add(ComponentI)
390389
Tonic.add(ComponentJ)
391390
Tonic.add(ComponentK)
391+
Tonic.add(ComponentI, document.body)
392392

393393
const i = document.querySelector('component-i')
394394
const kTags = i.getElementsByTagName('component-k')
@@ -414,6 +414,85 @@ test('check that composed elements use (and re-use) their initial innerHTML corr
414414
t.end()
415415
})
416416

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

0 commit comments

Comments
 (0)