diff --git a/compiler/generate/binding/index.js b/compiler/generate/binding/index.js new file mode 100644 index 0000000000..c3449d8ab5 --- /dev/null +++ b/compiler/generate/binding/index.js @@ -0,0 +1,60 @@ +import deindent from '../utils/deindent.js'; + +export default function createBinding ( node, name, attribute, current, initStatements, updateStatements, teardownStatements, allUsedContexts ) { + const parts = attribute.value.split( '.' ); + + const deep = parts.length > 1; + const contextual = parts[0] in current.contexts; + if ( contextual ) allUsedContexts.add( parts[0] ); + + const handler = current.counter( `${name}ChangeHandler` ); + let setter; + + let eventName = 'change'; + if ( node.name === 'input' ) { + const type = node.attributes.find( attr => attr.type === 'Attribute' && attr.name === 'type' ); + if ( !type || type.value[0].data === 'text' ) { + // TODO in validation, should throw if type attribute is not static + eventName = 'input'; + } + } + + if ( deep && contextual ) { + // TODO can we target only things that have changed? + setter = deindent` + var context = this.__context.${parts[0]}; + context.${parts.slice( 1 ).join( '.' )} = this.${attribute.name}; + component.set({}); + `; + } else if ( deep ) { + setter = deindent` + var ${parts[0]} = component.get( '${parts[0]}' ); + ${parts[0]}.${parts.slice( 1 ).join( '.' )} = this.${attribute.name}; + component.set({ ${parts[0]}: ${parts[0]} }); + `; + } else if ( contextual ) { + throw new Error( `Reassigning context is not currently supported` ); + } else { + setter = `component.set({ ${attribute.value}: ${name}.${attribute.name} });`; + } + + initStatements.push( deindent` + var ${name}_updating = false; + + function ${handler} () { + ${name}_updating = true; + ${setter} + ${name}_updating = false; + } + + ${name}.addEventListener( '${eventName}', ${handler}, false ); + ` ); + + updateStatements.push( deindent` + if ( !${name}_updating ) ${name}.${attribute.name} = ${contextual ? attribute.value : `root.${attribute.value}`} + ` ); + + teardownStatements.push( deindent` + ${name}.removeEventListener( '${eventName}', ${handler}, false ); + ` ); +} diff --git a/compiler/generate/index.js b/compiler/generate/index.js index 22b37ee52c..8c599bf321 100644 --- a/compiler/generate/index.js +++ b/compiler/generate/index.js @@ -6,6 +6,7 @@ import isReference from './utils/isReference.js'; import contextualise from './utils/contextualise.js'; import counter from './utils/counter.js'; import attributeLookup from './attributes/lookup.js'; +import createBinding from './binding/index.js'; function createRenderer ( fragment ) { return deindent` @@ -235,42 +236,7 @@ export default function generate ( parsed, template ) { } else if ( attribute.type === 'Binding' ) { - const parts = attribute.value.split( '.' ); - const contextual = parts[0] in current.contexts; - - const handler = current.counter( `${name}ChangeHandler` ); - let setter; - - if ( contextual ) { - allUsedContexts.add( parts[0] ); - setter = deindent` - var context = this.__context.${parts[0]}; - context.${parts.slice( 1 ).join( '.' )} = this.value; - component.set({ ${current.contextChain[0]}: component.get( '${current.contextChain[0]}' ) }); - `; - } else { - setter = `component.set({ ${attribute.value}: ${name}.value });`; - } - - initStatements.push( deindent` - var ${name}_updating = false; - - function ${handler} () { - ${name}_updating = true; - ${setter} - ${name}_updating = false; - } - - ${name}.addEventListener( 'input', ${handler}, false ); - ` ); - - updateStatements.push( deindent` - if ( !${name}_updating ) ${name}.value = ${contextual ? attribute.value : `root.${attribute.value}`} - ` ); - - teardownStatements.push( deindent` - ${name}.removeEventListener( 'input', ${handler}, false ); - ` ); + createBinding( node, name, attribute, current, initStatements, updateStatements, teardownStatements, allUsedContexts ); } else { diff --git a/test/compiler/binding-input-checkbox/_config.js b/test/compiler/binding-input-checkbox/_config.js new file mode 100644 index 0000000000..634504a4d0 --- /dev/null +++ b/test/compiler/binding-input-checkbox/_config.js @@ -0,0 +1,23 @@ +import * as assert from 'assert'; + +export default { + data: { + foo: true + }, + html: `\n
true
`, + test ( component, target, window ) { + const input = target.querySelector( 'input' ); + assert.equal( input.checked, true ); + + const event = new window.Event( 'change' ); + + input.checked = false; + input.dispatchEvent( event ); + + assert.equal( target.innerHTML, `\nfalse
` ); + + component.set({ foo: true }); + assert.equal( input.checked, true ); + assert.equal( target.innerHTML, `\ntrue
` ); + } +}; diff --git a/test/compiler/binding-input-checkbox/main.svelte b/test/compiler/binding-input-checkbox/main.svelte new file mode 100644 index 0000000000..2ab8592831 --- /dev/null +++ b/test/compiler/binding-input-checkbox/main.svelte @@ -0,0 +1,2 @@ + +{{foo}}
diff --git a/test/compiler/binding-input-text-contextual/_config.js b/test/compiler/binding-input-text-contextual/_config.js new file mode 100644 index 0000000000..24a9a81fc6 --- /dev/null +++ b/test/compiler/binding-input-text-contextual/_config.js @@ -0,0 +1,31 @@ +import * as assert from 'assert'; + +export default { + data: { + items: [ + 'one', + 'two', + 'three' + ] + }, + html: `one
two
three
one
four
three
one
four
five
{{item}}
one
two
three
one
four
three
one
four
five
{{item.description}}
one
two
three
hello alice
`, test ( component, target, window ) { - const inputs = [ ...target.querySelectorAll( 'input' ) ]; + const input = target.querySelector( 'input' ); - assert.equal( inputs[0].value, 'one' ); + assert.equal( input.value, 'alice' ); const event = new window.Event( 'input' ); - inputs[1].value = 'four'; - inputs[1].dispatchEvent( event ); + input.value = 'bob'; + input.dispatchEvent( event ); - assert.equal( target.innerHTML, `one
four
three
hello bob
` ); - const items = component.get( 'items' ); - items[2].description = 'five'; + const user = component.get( 'user' ); + user.name = 'carol'; - component.set({ items }); - assert.equal( inputs[2].value, 'five' ); - assert.equal( target.innerHTML, `one
four
five
hello carol
` ); } }; diff --git a/test/compiler/binding-input-text-deep/main.svelte b/test/compiler/binding-input-text-deep/main.svelte index 210e4e4fea..8ed32a7e90 100644 --- a/test/compiler/binding-input-text-deep/main.svelte +++ b/test/compiler/binding-input-text-deep/main.svelte @@ -1,3 +1,2 @@ -{{#each items as item}} -{{item.description}}
hello {{user.name}}