diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 2513623c49..c77ff17158 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -178,6 +178,7 @@ export default function dom( this._root = options._root || this; this._yield = options._yield; + this._bind = options._bind; ${generator.stylesheet.hasStyles && options.css !== false && diff --git a/src/generators/dom/visitors/Component/Binding.ts b/src/generators/dom/visitors/Component/Binding.ts index 75cdd637e3..afd768c5bd 100644 --- a/src/generators/dom/visitors/Component/Binding.ts +++ b/src/generators/dom/visitors/Component/Binding.ts @@ -45,9 +45,11 @@ export default function visitBinding( local.bindings.push({ name: attribute.name, - value: snippet, + value: attribute.value, + snippet: snippet, obj, prop, + dependencies }); const setter = getSetter({ @@ -69,31 +71,33 @@ export default function visitBinding( const observer = block.getUniqueName('observer'); const value = block.getUniqueName('value'); - local.create.addBlock(deindent` - function ${observer} ( value ) { - if ( ${updating} ) return; - ${updating} = true; - ${setter} - ${updating} = false; - } + //console.log({ setter }); - ${local.name}.observe( '${attribute.name}', ${observer}, { init: false }); + // local.create.addBlock(deindent` + // function ${observer} ( value ) { + // if ( ${updating} ) return; + // ${updating} = true; + // ${setter} + // ${updating} = false; + // } - #component._root._beforecreate.push( function () { - var value = ${local.name}.get( '${attribute.name}' ); - if ( @differs( value, ${snippet} ) ) { - ${observer}.call( ${local.name}, value ); - } - }); - `); + // ${local.name}.observe( '${attribute.name}', ${observer}, { init: false }); - local.update.addBlock(deindent` - if ( !${updating} && ${dependencies - .map(dependency => `changed.${dependency}`) - .join(' || ')} ) { - ${updating} = true; - ${local.name}._set({ ${attribute.name}: ${snippet} }); - ${updating} = false; - } - `); + // #component._root._beforecreate.push( function () { + // var value = ${local.name}.get( '${attribute.name}' ); + // if ( @differs( value, ${snippet} ) ) { + // ${observer}.call( ${local.name}, value ); + // } + // }); + // `); + + // local.update.addBlock(deindent` + // if ( !${updating} && ${dependencies + // .map(dependency => `changed.${dependency}`) + // .join(' || ')} ) { + // ${updating} = true; + // ${local.name}._set({ ${attribute.name}: ${snippet} }); + // ${updating} = false; + // } + // `); } diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 0e966cdc0f..6e00f9069a 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -7,6 +7,8 @@ import visitBinding from './Binding'; import visitRef from './Ref'; import { DomGenerator } from '../../index'; import Block from '../../Block'; +import getTailSnippet from '../../../../utils/getTailSnippet'; +import getObject from '../../../../utils/getObject'; import { Node } from '../../../../interfaces'; import { State } from '../../interfaces'; @@ -149,6 +151,8 @@ export default function visitComponent( } const statements: string[] = []; + const name_updating = local.bindings.length && block.alias(`${name}_updating`); + if (local.bindings.length) block.addVariable(name_updating, '{}'); if ( local.staticAttributes.length || @@ -168,11 +172,52 @@ export default function visitComponent( local.bindings.forEach(binding => { statements.push( - `if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.value};` + `if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.snippet};` ); }); componentInitProperties.push(`data: ${initialData}`); + + componentInitProperties.push(deindent` + _bind: function(changed, childState) { + var state = #component.get(), newState = {}; + ${local.bindings.map(binding => { + const { name: key } = getObject(binding.value); + + if (block.contexts.has(key)) { + const prop = binding.dependencies[0]; + const computed = isComputed(binding.value); + const tail = binding.value.type === 'MemberExpression' ? getTailSnippet(binding.value) : ''; + + return deindent` + if (changed.${binding.name}) { + var list = ${name}._context.${block.listNames.get(key)}; + var index = ${name}._context.${block.indexNames.get(key)}; + list[index]${tail} = childState.${binding.name}; + + ${binding.dependencies + .map((prop: string) => `newState.${prop} = state.${prop};`) + .join('\n')} + } + `; + } + + if (binding.value.type === 'MemberExpression') { + return deindent` + if (changed.${binding.name}) { + ${binding.snippet} = childState.${binding.name}; + ${binding.dependencies.map((prop: string) => `newState.${prop} = state.${prop};`).join('\n')} + } + `; + } + + return `if (changed.${key}) newState.${binding.value.name} = childState.${key};`; + })} + ${name_updating} = changed; + #component._set(newState); + ${name_updating} = {}; + } + `); } else if (initialProps.length) { componentInitProperties.push(`data: ${initialPropString}`); } @@ -188,21 +233,41 @@ export default function visitComponent( var ${name} = new ${expression}({ ${componentInitProperties.join(',\n')} }); + + #component._root._beforecreate.push(function () { + var state = component.get(), newState = {}; + // TODO + #component._set(newState); + }); `); - if (local.dynamicAttributes.length) { - const updates = local.dynamicAttributes.map(attribute => { + if (local.dynamicAttributes.length || local.bindings.length) { + const updates: string[] = []; + + local.dynamicAttributes.forEach(attribute => { if (attribute.dependencies.length) { - return deindent` + updates.push(deindent` if ( ${attribute.dependencies .map(dependency => `changed.${dependency}`) .join(' || ')} ) ${name}_changes.${attribute.name} = ${attribute.value}; - `; + `); } - // TODO this is an odd situation to encounter – I *think* it should only happen with - // each block indices, in which case it may be possible to optimise this - return `${name}_changes.${attribute.name} = ${attribute.value};`; + else { + // TODO this is an odd situation to encounter – I *think* it should only happen with + // each block indices, in which case it may be possible to optimise this + updates.push(`${name}_changes.${attribute.name} = ${attribute.value};`); + } + }); + + local.bindings.forEach(binding => { + if (binding.dependencies.length) { + updates.push(deindent` + if ( !${name_updating}.${binding.name} && ${binding.dependencies + .map((dependency: string) => `changed.${dependency}`) + .join(' || ')} ) ${name}_changes.${binding.name} = ${binding.snippet}; + `); + } }); local.update.addBlock(deindent` @@ -233,3 +298,12 @@ export default function visitComponent( if (!local.update.isEmpty()) block.builders.update.addBlock(local.update); } + +function isComputed(node: Node) { + while (node.type === 'MemberExpression') { + if (node.computed) return true; + node = node.object; + } + + return false; +} \ No newline at end of file diff --git a/src/shared/index.js b/src/shared/index.js index 901fbfdb3b..b103f2e91d 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -143,6 +143,7 @@ export function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); + if (this._bind) this._bind(changed, this._state); dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); dispatchObservers(this, this._observers.post, changed, this._state, oldState); diff --git a/test/runtime/samples/bindings-coalesced/Foo.html b/test/runtime/samples/bindings-coalesced/Foo.html new file mode 100644 index 0000000000..5d453fbb73 --- /dev/null +++ b/test/runtime/samples/bindings-coalesced/Foo.html @@ -0,0 +1,20 @@ +

bar in Foo: {{bar}}

+

baz in Foo: {{baz}}

+ + \ No newline at end of file diff --git a/test/runtime/samples/bindings-coalesced/_config.js b/test/runtime/samples/bindings-coalesced/_config.js new file mode 100644 index 0000000000..2fe5cbf5cd --- /dev/null +++ b/test/runtime/samples/bindings-coalesced/_config.js @@ -0,0 +1,17 @@ +export default { + test(assert, component) { + const { foo, p } = component.refs; + + const values = []; + + Object.defineProperty(p.childNodes[0], 'data', { + set(value) { + values.push(value); + } + }); + + foo.double(); + + assert.deepEqual(values, [6]); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/bindings-coalesced/main.html b/test/runtime/samples/bindings-coalesced/main.html new file mode 100644 index 0000000000..fc17036077 --- /dev/null +++ b/test/runtime/samples/bindings-coalesced/main.html @@ -0,0 +1,12 @@ + +

{{bar + baz}}

+ + \ No newline at end of file