Skip to content

Commit

Permalink
Merge pull request #13 from heapwolf/v8.0.0
Browse files Browse the repository at this point in the history
fixes nested initialization
  • Loading branch information
heapwolf committed Jan 29, 2019
2 parents c2c5f30 + e9775a0 commit 1f15a0b
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 22 deletions.
5 changes: 3 additions & 2 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

| Method | Description |
| :--- | :--- |
| `add(Class, Object)` | Register a class as a new custom-tag and provide options for it. |
| `add(Class)` | Register a class as a new custom-tag and provide options for it. |
| `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. |
| `escape(String)` | Escapes HTML characters from a string (based on [he][3]). |
| `sanitize(Object)` | Escapes all the strings found in an object literal. |
| `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. |
Expand All @@ -31,7 +32,7 @@

| Method | Description |
| :--- | :--- |
| `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)`. |
| `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)`. |
| `willConnect()` | Called prior to the element being inserted into the DOM. Useful for updating configuration, state and preparing for the render. |
| `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. |
| `disconnected()` | Called every time the element is removed from the DOM. Useful for running clean up code. |
Expand Down
29 changes: 20 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Tonic {
const render = this.render
this.render = () => this.wrap(render.bind(this))
}

this._connect()
Tonic.refs.push(this.root)
}
Expand All @@ -28,9 +29,9 @@ class Tonic {
return el.matches(s) ? el : el.closest(s)
}

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

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

Tonic._constructTags()
if (isReady || c.name === 'App') Tonic.init(document.firstElementChild)
}

static _constructTags (root, states = {}) { /* eslint-disable no-new */
for (const tagName of Tonic.tags) {
for (const node of (root || document).getElementsByTagName(tagName)) {
if (!node.disconnect) new Tonic.registry[tagName]({ node, state: states[node.id] })
static init (node = document.firstElementChild, states = {}) {
node = node.firstElementChild

while (node) {
const tagName = node.tagName

if (Tonic.tags.includes(tagName)) { /* eslint-disable no-new */
new Tonic.registry[tagName]({ node, state: states[node.id] })
node = node.nextElementSibling
continue
}

Tonic.init(node, states)
node = node.nextElementSibling
}
}

Expand Down Expand Up @@ -79,6 +89,7 @@ class Tonic {
if (typeof v === 'object' && v.__children__) return this._children(v)
if (typeof v === 'object' || typeof v === 'function') return this._prop(v)
if (typeof v === 'number') return `${v}__float`
if (typeof v === 'boolean') return `${v.toString()}`
return v
}
return values.map(ref).reduce(reduce, [s]).filter(filter).join('')
Expand All @@ -96,7 +107,7 @@ class Tonic {
const oldProps = JSON.parse(JSON.stringify(this.props))
this.props = Tonic.sanitize(typeof o === 'function' ? o(this.props) : o)
if (!this.root) throw new Error('.reRender called on destroyed component, see guide.')
Tonic._constructTags(this.root, this._setContent(this.root, this.render()))
Tonic.init(this.root, this._setContent(this.root, this.render()))
this.updated && this.updated(oldProps)
}

Expand Down Expand Up @@ -193,7 +204,7 @@ class Tonic {
this.children = [...this.root.childNodes].map(node => node.cloneNode(true))
this.children.__children__ = true
this._setContent(this.root, this.render())
Tonic._constructTags(this.root)
Tonic.init(this.root)
const style = this.stylesheet && this.stylesheet()

if (style && !Tonic.registry[this.root.tagName].styled) {
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"name": "@conductorlab/tonic",
"version": "7.3.0",
"version": "8.0.0",
"description": "A composable component inspired by React.",
"main": "events.js",
"scripts": {
"test": "npm run test:browser",
"test:browser": "browserify --bare ./test | tape-run --node",
"test:h": "browserify --bare ./test/h | tape-run --node",
"build:demo": "browserify --bare ./demo > ./docs/bundle.js"
},
"author": "",
"author": "heapwolf",
"license": "MIT",
"devDependencies": {
"browserify": "^16.2.2",
Expand Down
101 changes: 92 additions & 9 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ test('attach to dom', t => {
<component-a></component-a>
`

Tonic.add(ComponentA)
Tonic.add(ComponentA, document.body)

const div = document.querySelector('div')
t.ok(div, 'a div was created and attached')
Expand Down Expand Up @@ -63,7 +63,7 @@ test('pass props', t => {
</component-b-b>
`
}
})
}, document.body)

const bb = document.getElementById('y')
{
Expand Down Expand Up @@ -98,7 +98,7 @@ test('get element by id and set properties via the api', t => {
}
}

Tonic.add(ComponentC)
Tonic.add(ComponentC, document.body)

{
const div = document.getElementById('test')
Expand Down Expand Up @@ -178,7 +178,7 @@ test('stylesheets and inline styles', t => {
}
}

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

Tonic.add(Foo)
Tonic.add(Bar)
Tonic.add(Foo, document.body)
Tonic.add(Bar, document.body)

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

Tonic.add(StatefulParent)
Tonic.add(StatefulChild)
Tonic.add(StatefulParent, document.body)
const parent = document.getElementsByTagName('stateful-parent')[0]
parent.reRender()
const child = document.getElementsByTagName('stateful-child')[0]
Expand Down Expand Up @@ -303,7 +303,7 @@ test('lifecycle events', t => {
}

Tonic.add(Bazz)
Tonic.add(Quxx)
Tonic.add(Quxx, document.body)
const q = document.querySelector('quxx')
q.reRender({})
const refsLength = Tonic.refs.length
Expand Down Expand Up @@ -339,6 +339,7 @@ test('compose sugar (this.children)', t => {

Tonic.add(ComponentG)
Tonic.add(ComponentH)
Tonic.init(document.body)

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

Tonic.add(ComponentI)
Tonic.add(ComponentJ)
Tonic.add(ComponentK)
Tonic.add(ComponentI)
Tonic.init()

t.comment('Uses init() instead of <app>')

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

test('mixed order declaration', t => {
class App extends Tonic {
render () {
return this.html`<div class="app">${this.children}</div>`
}
}

class ComponentA extends Tonic {
render () {
return `<div class="a">A</div>`
}
}

class ComponentB extends Tonic {
render () {
return this.html`<div class="b">${this.children}</div>`
}
}

class ComponentC extends Tonic {
render () {
return this.html`<div class="c">${this.children}</div>`
}
}

class ComponentD extends Tonic {
render () {
return `<div class="d">D</div>`
}
}

document.body.innerHTML = `
<App>
<component-a>
</component-a>
<component-b>
<component-c>
<component-d>
</component-d>
</component-c>
</component-b>
</App>
`

Tonic.add(ComponentD)
Tonic.add(ComponentA)
Tonic.add(ComponentC)
Tonic.add(ComponentB)
Tonic.add(App)

{
const div = document.querySelector('.app')
t.ok(div, 'a div was created and attached')
}

{
const div = document.querySelector('body .app .a')
t.ok(div, 'a div was created and attached')
}

{
const div = document.querySelector('body .app .b')
t.ok(div, 'a div was created and attached')
}

{
const div = document.querySelector('body .app .b .c')
t.ok(div, 'a div was created and attached')
}

{
const div = document.querySelector('body .app .b .c .d')
t.ok(div, 'a div was created and attached')
}

t.end()
})

test('cleanup, ensure exist', t => {
t.end()
process.exit(0)
Expand Down

0 comments on commit 1f15a0b

Please sign in to comment.