diff --git a/example/app.js b/example/app.js index d174c5d4..16ec4715 100644 --- a/example/app.js +++ b/example/app.js @@ -1,4 +1,4 @@ -import { o, mount } from '../dist/one' +import { o, mount, register } from '../dist/one' import { getItems } from './services/get-items' const Component = { @@ -11,92 +11,46 @@ const Component = { async onmount ({ data }) { data.items = await getItems() }, - view ({ data }) { + view () { return o('div', [ o('h1', 'Items'), - o(List, { items: data.items }) + o(List), + o(List, { + items: [ + { id: 1, name: 'Props: Hey!' }, + { id: 2, name: 'Props: Hi!' } + ] + }) ]) } } const List = { name: 'list', - view ({ props }) { - return o('div', [ - o('ul', - props.items.length - ? props.items.map(item => o('li', `${item.name}`)) - : o('li', 'Loading') - ) - ]) - } -} - -const If = { - name: 'if', data () { return { - count: 0 + items: [ + { id: 1, name: 'Hey!' }, + { id: 2, name: 'Hi!' } + ] } }, - onmount () { - console.log('If mounted') - }, - view ({ data }) { + view ({ data, props }) { return o('div', [ - o('h1', 'If'), - o('p', 'Paragraph inside If'), - o('button', data.count ? `Clicked ${data.count} times` : 'Click', { + o('ul', props?.items + ? props.items.map(item => o('li', `${item.name}`)) + : data.items.map(item => o('li', `${item.name}`))), + o('button', 'Click', { onclick () { console.log('clicked') - data.count = data.count + 1 + data.items = [...data.items, { id: 3, name: 'Hello!' }] } }) ]) } } -const Else = { - name: 'else', - view ({ props }) { - return o('div', [ - o('h1', 'Else'), - o('p', `Paragraph inside Else, with prop "name": ${props.name}`), - o('p', 'Another one') - ]) - } -} - -const View = { - name: 'view', - data () { - return { - name: 'Sebastian', - show: true - } - }, - view ({ data }) { - return o('div', [ - o(Component), - o('p', `Hello ${data.name}`), - o('input', [], { - value: data.name, - name: 'input', - type: 'text', - oninput (e) { - data.name = e.target.value - } - }), - data.show - ? o(If, null, { redraw: true }) - : o(Else, { name: data.name }, { redraw: true }), - o('button', 'Click to toggle', { - onclick () { - data.show = !data.show - } - }) - ]) - } -} +register(Component) +register(List) -mount(View, document.body) +mount(Component, document.body) diff --git a/lib/one.js b/lib/one.js index b232f896..0f21615a 100644 --- a/lib/one.js +++ b/lib/one.js @@ -1,5 +1,22 @@ const components = {} +function register (component) { + if (!component.name) { + throw new Error('One.register(): The component must have a name.') + } + + if (components[component.name]) { + throw new Error(`One.register(): The component "${component.name}" already exists.`) + } + + components[component.name] = { + component, + instances: [] + } + + return component +} + function o (tag, content, attrs) { if (Object.prototype.hasOwnProperty.call(tag, 'view')) { if (content != null && typeof content !== 'object') { @@ -8,8 +25,47 @@ function o (tag, content, attrs) { ) } - if (tag.props) { - const oldProps = tag.props + if (!components[tag.name]) { + throw new Error(`The component "${tag.name}" does not exist. Remember to register it first with the register() function.`) + } + + const name = tag.name + const oldProps = content + + let component = null + + // TODO: This is shit, since each call to `view()` on a component would + // create new instances of nested components..... :( + if (tag?.id) { + // the component is already registered as an instance, + // so we need to grab that instance instead of adding a new one. + console.log('find component', name, tag.id) + // not a big fan of the `find` usage here, but it works for now. + // maybe it's ok for performance, since we don't expect a lot of instances. + component = components[name].instances.find((c) => c.id === tag.id) + } else { + component = { ...components[name].component } + + // TODO: Find a better way to generate a unique id. + // Does it have to be a unique random string? Maybe it's find to grab the position in the array? + // But what happens when the array changes? The position will change too. + // component.id = Math.random().toString(36).substring(2, 9) + component.id = `${name}#${components[name].instances.length}` + component.props = content + component.attrs = attrs + + components[name].instances.push(component) + } + + console.log('render component', component) + // TODO: Find a way to check if the component already exists in the components object + // and if it does, we should add a new instance of the component. + // But how do we know what instance to update? + // Maybe an approach with a register function is a good idea? That gives a bit more control. + // That won't change much, since we still need to know what instance to update. duh. + + if (component.props) { + // fix this, it's confusing that `tag` is used here now. const newProps = content const keys = Object.keys(oldProps) @@ -17,35 +73,35 @@ function o (tag, content, attrs) { keys.some((key) => oldProps[key] !== newProps[key]) if (changed) { - const rendered = tag.view({ - data: setupData(tag, attrs), + const rendered = component.view({ + data: setupData(component, attrs), props: content }) draw( - tag, + component, rendered ) - tag.props = newProps - tag.attrs = attrs + component.props = newProps + + console.log('return rendered with props', component.name) return rendered } } - if (tag.rendered && !attrs?.redraw) { - const cache = tag.rendered.cloneNode(true) - sync(tag.rendered, cache, tag.rendered) + if (component.rendered && !component.attrs?.redraw) { + const cache = component.rendered.cloneNode(true) + sync(component.rendered, cache, component.rendered) + console.log('return cache', component.name) return cache } - tag.props = content - tag.attrs = attrs + console.log('render', component.id) + const cmpt = render(component, attrs) + cmpt.onmount() - const component = render(tag, attrs) - component.onmount() - - return component.rendered + return cmpt.rendered } const el = document.createElement(tag) @@ -68,9 +124,9 @@ function render (component, attrs) { throw new Error('One.render(): The component must have a name.') } - if (component.rendered && !attrs?.redraw) { - return component - } + // if (component.rendered && !attrs?.redraw) { + // return component + // } if (typeof component.data === 'function' && !component.dataFn) { component.dataFn = component.data @@ -86,17 +142,16 @@ function render (component, attrs) { onmount?.({ data: $data, props: component.props }) } + component.rendered = el + el.$one = { name: component.name, + id: component.id, isComponent: true, props: component.props, attrs } - component.rendered = el - - components[component.name] = component - return component } @@ -114,6 +169,8 @@ function setupData (component, attrs) { set (target, key, value) { const s = Reflect.set(target, key, value) + console.log('set', key, value, component) + draw( component, component.view({ @@ -136,6 +193,8 @@ function draw (component, view) { throw new Error('The component could not be found.') } + console.log('draw', component.rendered, view) + remove(component.rendered, view.children) diff(component.rendered, view) } @@ -147,6 +206,8 @@ function diff (dom, view) { append(dom, view.children) } + console.log('diff', dom, view) + for (const vn of view.children) { const dn = dom.children[i] @@ -170,6 +231,7 @@ function diff (dom, view) { } if (dn.$one?.isComponent && vn.$one?.isComponent) { + console.log('diff component', dn.$one.name, vn.$one.name) const cmp = components[vn.$one.name] const rendered = cmp.rendered.cloneNode(true) @@ -222,7 +284,9 @@ function sync (from, to, parent) { }) } + // TODO: This probably doesn't work with the new instances array. if (components[from.$one?.name]) { + console.log('FIX ME', from.$one.name) components[from.$one.name].rendered = to } } @@ -312,4 +376,4 @@ if (process.env.NODE_ENV !== 'production') { } } -export { o, render, mount } +export { o, render, mount, register }