targeted updates of deep contextual bindings

pull/31/head
Rich-Harris 8 years ago
parent 240b00dd68
commit ce79d3eca5

@ -1,4 +1,6 @@
import deindent from '../utils/deindent.js';
import isReference from '../utils/isReference.js';
import flattenReference from '../utils/flattenReference.js';
export default function createBinding ( node, name, attribute, current, initStatements, updateStatements, teardownStatements, allUsedContexts ) {
const parts = attribute.value.split( '.' );
@ -20,8 +22,21 @@ export default function createBinding ( node, name, attribute, current, initStat
}
if ( contextual ) {
// TODO can we target only things that have changed?
// TODO computed values/observers that depend on this probably won't update...
// find the top-level property that this is a child of
let fragment = current;
let prop = parts[0];
do {
if ( fragment.expression && fragment.context === prop ) {
if ( !isReference( fragment.expression ) ) {
// TODO this should happen in prior validation step
throw new Error( `${prop} is read-only, it cannot be bound` );
}
prop = flattenReference( fragment.expression ).name;
}
} while ( fragment = fragment.parent );
const listName = current.listNames[ parts[0] ];
const indexName = current.indexNames[ parts[0] ];
@ -30,7 +45,7 @@ export default function createBinding ( node, name, attribute, current, initStat
var index = this.__svelte.${indexName};
list[index]${parts.slice( 1 ).map( part => `.${part}` ).join( '' )} = this.${attribute.name};
component.set({});
component.set({ ${prop}: component.get( '${prop}' ) });
`;
} else if ( deep ) {
setter = deindent`

@ -495,6 +495,8 @@ export default function generate ( parsed, template ) {
useAnchor: false,
name: renderer,
target: 'target',
expression: node.expression,
context: node.context,
contexts,
indexes,

@ -0,0 +1,34 @@
import * as assert from 'assert';
export default {
data: {
items: [
{ description: 'one', completed: true },
{ description: 'two', completed: false },
{ description: 'three', completed: false }
]
},
html: `<div><input type="checkbox"><p>one</p></div><div><input type="checkbox"><p>two</p></div><div><input type="checkbox"><p>three</p></div><!--#each items-->\n\n<p>1 completed</p>`,
test ( component, target, window ) {
const inputs = [ ...target.querySelectorAll( 'input' ) ];
assert.ok( inputs[0].checked );
assert.ok( !inputs[1].checked );
assert.ok( !inputs[2].checked );
const event = new window.Event( 'change' );
inputs[1].checked = true;
inputs[1].dispatchEvent( event );
assert.equal( component.get( 'numCompleted' ), 2 );
assert.equal( target.innerHTML, `<div><input type="checkbox"><p>one</p></div><div><input type="checkbox"><p>two</p></div><div><input type="checkbox"><p>three</p></div><!--#each items-->\n\n<p>2 completed</p>` );
const items = component.get( 'items' );
items[2].completed = true;
component.set({ items });
assert.ok( inputs[2].checked );
assert.equal( target.innerHTML, `<div><input type="checkbox"><p>one</p></div><div><input type="checkbox"><p>two</p></div><div><input type="checkbox"><p>three</p></div><!--#each items-->\n\n<p>3 completed</p>` );
}
};

@ -0,0 +1,15 @@
{{#each items as item}}
<div><input type='checkbox' bind:checked='item.completed'><p>{{item.description}}</p></div>
{{/each}}
<p>{{numCompleted}} completed</p>
<script>
export default {
computed: {
numCompleted ( items ) {
return items.reduce( ( total, item ) => total + ( item.completed ? 1 : 0 ), 0 );
}
}
};
</script>
Loading…
Cancel
Save