deep/contextual binding

pull/31/head
Rich-Harris 9 years ago
parent 2aeaaa24b6
commit 48fb01cf7e

@ -19,11 +19,17 @@ export default function createBinding ( node, name, attribute, current, initStat
} }
} }
if ( deep && contextual ) { if ( contextual ) {
// TODO can we target only things that have changed? // TODO can we target only things that have changed?
// TODO computed values/observers that depend on this probably won't update...
const listName = current.listNames[ parts[0] ];
const indexName = current.indexNames[ parts[0] ];
setter = deindent` setter = deindent`
var context = this.__context.${parts[0]}; var list = this.__svelte.${listName};
context.${parts.slice( 1 ).join( '.' )} = this.${attribute.name}; var index = this.__svelte.${indexName};
list[index]${parts.slice( 1 ).map( part => `.${part}` ).join( '' )} = this.${attribute.name};
component.set({}); component.set({});
`; `;
} else if ( deep ) { } else if ( deep ) {
@ -32,8 +38,6 @@ export default function createBinding ( node, name, attribute, current, initStat
${parts[0]}.${parts.slice( 1 ).join( '.' )} = this.${attribute.name}; ${parts[0]}.${parts.slice( 1 ).join( '.' )} = this.${attribute.name};
component.set({ ${parts[0]}: ${parts[0]} }); component.set({ ${parts[0]}: ${parts[0]} });
`; `;
} else if ( contextual ) {
throw new Error( `Reassigning context is not currently supported` );
} else { } else {
setter = `component.set({ ${attribute.value}: ${name}.${attribute.name} });`; setter = `component.set({ ${attribute.value}: ${name}.${attribute.name} });`;
} }

@ -83,7 +83,11 @@ export default function generate ( parsed, template ) {
teardownStatements: [], teardownStatements: [],
contexts: {}, contexts: {},
indexes: {},
contextChain: [ 'root' ], contextChain: [ 'root' ],
indexNames: {},
listNames: {},
counter: counter(), counter: counter(),
@ -152,7 +156,7 @@ export default function generate ( parsed, template ) {
else { else {
// dynamic but potentially non-string attributes // dynamic but potentially non-string attributes
contextualise( code, value.expression, current.contexts, helpers ); contextualise( code, value.expression, current.contexts, current.indexes, helpers );
result = `[✂${value.expression.start}-${value.expression.end}✂]`; result = `[✂${value.expression.start}-${value.expression.end}✂]`;
if ( metadata ) { if ( metadata ) {
@ -173,7 +177,7 @@ export default function generate ( parsed, template ) {
if ( chunk.type === 'Text' ) { if ( chunk.type === 'Text' ) {
return JSON.stringify( chunk.data ); return JSON.stringify( chunk.data );
} else { } else {
contextualise( code, chunk.expression, current.contexts, helpers ); contextualise( code, chunk.expression, current.contexts, current.indexes, helpers );
return `( [✂${chunk.expression.start}-${chunk.expression.end}✂] )`; return `( [✂${chunk.expression.start}-${chunk.expression.end}✂] )`;
} }
}).join( ' + ' ) }).join( ' + ' )
@ -200,7 +204,7 @@ export default function generate ( parsed, template ) {
const usedContexts = new Set(); const usedContexts = new Set();
attribute.expression.arguments.forEach( arg => { attribute.expression.arguments.forEach( arg => {
const contexts = contextualise( code, arg, current.contexts, helpers ); const contexts = contextualise( code, arg, current.contexts, current.indexes, helpers );
contexts.forEach( context => { contexts.forEach( context => {
usedContexts.add( context ); usedContexts.add( context );
@ -210,10 +214,18 @@ export default function generate ( parsed, template ) {
// TODO hoist event handlers? can do `this.__component.method(...)` // TODO hoist event handlers? can do `this.__component.method(...)`
if ( usedContexts.size ) { if ( usedContexts.size ) {
const declarations = [...usedContexts].map( name => {
if ( name === 'root' ) return 'var root = this.__svelte.root; // 2';
const listName = current.listNames[ name ];
const indexName = current.indexNames[ name ];
return `var ${listName} = this.__svelte.${listName}, ${indexName} = this.__svelte.${indexName}, ${name} = ${listName}[${indexName}]`;
});
initStatements.push( deindent` initStatements.push( deindent`
function ${handler} ( event ) { function ${handler} ( event ) {
var context = this.__context; ${declarations}
${[...usedContexts].map( name => `var ${name} = context.${name}` ).join( '\n' )}
[${attribute.expression.start}-${attribute.expression.end}]; [${attribute.expression.start}-${attribute.expression.end}];
} }
@ -246,12 +258,19 @@ export default function generate ( parsed, template ) {
if ( allUsedContexts.size ) { if ( allUsedContexts.size ) {
initStatements.push( deindent` initStatements.push( deindent`
${name}.__context = {}; ${name}.__svelte = {};
` ); ` );
updateStatements.push( deindent` const declarations = [...allUsedContexts].map( contextName => {
${[...allUsedContexts].map( contextName => `${name}.__context.${contextName} = ${contextName};` ).join( '\n' )} if ( contextName === 'root' ) return `${name}.__svelte.root = root;`;
` );
const listName = current.listNames[ contextName ];
const indexName = current.indexNames[ contextName ];
return `${name}.__svelte.${listName} = ${listName};\n${name}.__svelte.${indexName} = ${indexName};`;
}).join( '\n' );
updateStatements.push( declarations );
} }
current.initStatements.push( initStatements.join( '\n' ) ); current.initStatements.push( initStatements.join( '\n' ) );
@ -299,7 +318,7 @@ export default function generate ( parsed, template ) {
${current.target}.appendChild( ${name} ); ${current.target}.appendChild( ${name} );
` ); ` );
const usedContexts = contextualise( code, node.expression, current.contexts, helpers ); const usedContexts = contextualise( code, node.expression, current.contexts, current.indexes, helpers );
const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`; const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`;
if ( isReference( node.expression ) ) { if ( isReference( node.expression ) ) {
@ -338,7 +357,7 @@ export default function generate ( parsed, template ) {
var ${name} = null; var ${name} = null;
` ); ` );
const usedContexts = contextualise( code, node.expression, current.contexts, helpers ); const usedContexts = contextualise( code, node.expression, current.contexts, current.indexes, helpers );
const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`; const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`;
let expression; let expression;
@ -410,6 +429,8 @@ export default function generate ( parsed, template ) {
const name = `eachBlock_${i}`; const name = `eachBlock_${i}`;
const renderer = `renderEachBlock_${i}`; const renderer = `renderEachBlock_${i}`;
const listName = `${name}_value`;
current.initStatements.push( deindent` current.initStatements.push( deindent`
var ${name}_anchor = document.createComment( ${JSON.stringify( `#each ${template.slice( node.expression.start, node.expression.end )}` )} ); var ${name}_anchor = document.createComment( ${JSON.stringify( `#each ${template.slice( node.expression.start, node.expression.end )}` )} );
${current.target}.appendChild( ${name}_anchor ); ${current.target}.appendChild( ${name}_anchor );
@ -417,7 +438,7 @@ export default function generate ( parsed, template ) {
const ${name}_fragment = document.createDocumentFragment(); const ${name}_fragment = document.createDocumentFragment();
` ); ` );
contextualise( code, node.expression, current.contexts, helpers ); contextualise( code, node.expression, current.contexts, current.indexes, helpers );
const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`; const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`;
current.updateStatements.push( deindent` current.updateStatements.push( deindent`
@ -429,7 +450,7 @@ export default function generate ( parsed, template ) {
} }
const iteration = ${name}_iterations[i]; const iteration = ${name}_iterations[i];
${name}_iterations[i].update( ${current.contextChain.join( ', ' )}, ${name}_value[i]${node.index ? `, i` : ''} ); ${name}_iterations[i].update( ${current.contextChain.join( ', ' )}, ${listName}, ${listName}[i], i );
} }
for ( var i = ${name}_value.length; i < ${name}_iterations.length; i += 1 ) { for ( var i = ${name}_value.length; i < ${name}_iterations.length; i += 1 ) {
@ -437,7 +458,7 @@ export default function generate ( parsed, template ) {
} }
${name}_anchor.parentNode.insertBefore( ${name}_fragment, ${name}_anchor ); ${name}_anchor.parentNode.insertBefore( ${name}_fragment, ${name}_anchor );
${name}_iterations.length = ${name}_value.length; ${name}_iterations.length = ${listName}.length;
` ); ` );
current.teardownStatements.push( deindent` current.teardownStatements.push( deindent`
@ -448,16 +469,19 @@ export default function generate ( parsed, template ) {
${name}_anchor.parentNode.removeChild( ${name}_anchor ); ${name}_anchor.parentNode.removeChild( ${name}_anchor );
` ); ` );
const contexts = Object.assign( {}, current.contexts ); const indexNames = Object.assign( {}, current.indexNames );
const contextChain = current.contextChain.concat( node.context ); const indexName = indexNames[ node.context ] = ( node.index || `${node.context}__index` );
const listNames = Object.assign( {}, current.listNames );
listNames[ node.context ] = listName;
const contexts = Object.assign( {}, current.contexts );
contexts[ node.context ] = true; contexts[ node.context ] = true;
if ( node.index ) { const indexes = Object.assign( {}, current.indexes );
// not strictly a context, but we can treat it as such if ( node.index ) indexes[ indexName ] = node.context;
contextChain.push( node.index );
contexts[ node.index ] = true; const contextChain = current.contextChain.concat( listName, node.context, indexName );
}
current = { current = {
useAnchor: false, useAnchor: false,
@ -465,10 +489,19 @@ export default function generate ( parsed, template ) {
target: 'target', target: 'target',
contexts, contexts,
indexes,
indexNames,
listNames,
contextChain, contextChain,
initStatements: [], initStatements: [],
updateStatements: [], updateStatements: [ Object.keys( contexts ).map( contextName => {
const listName = listNames[ contextName ];
const indexName = indexNames[ contextName ];
return `var ${contextName} = ${listName}[${indexName}];`;
}).join( '\n' ) ],
teardownStatements: [], teardownStatements: [],
counter: counter(), counter: counter(),
@ -639,6 +672,7 @@ export default function generate ( parsed, template ) {
}); });
return { return {
code: code.toString() code: code.toString(),
map: code.generateMap()
}; };
} }

@ -2,7 +2,7 @@ import { walk } from 'estree-walker';
import isReference from './isReference.js'; import isReference from './isReference.js';
import flattenReference from './flattenReference.js'; import flattenReference from './flattenReference.js';
export default function contextualise ( code, expression, contexts, helpers ) { export default function contextualise ( code, expression, contexts, indexes, helpers ) {
const usedContexts = []; const usedContexts = [];
walk( expression, { walk( expression, {
@ -17,6 +17,9 @@ export default function contextualise ( code, expression, contexts, helpers ) {
if ( contexts[ name ] ) { if ( contexts[ name ] ) {
if ( !~usedContexts.indexOf( name ) ) usedContexts.push( name ); if ( !~usedContexts.indexOf( name ) ) usedContexts.push( name );
} else if ( indexes[ name ] ) {
const context = indexes[ name ];
if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context );
} else { } else {
code.insertRight( node.start, `root.` ); code.insertRight( node.start, `root.` );
if ( !~usedContexts.indexOf( 'root' ) ) usedContexts.push( 'root' ); if ( !~usedContexts.indexOf( 'root' ) ) usedContexts.push( 'root' );

@ -11,17 +11,17 @@ export default {
html: `<div><input><p>one</p></div><div><input><p>two</p></div><div><input><p>three</p></div><!--#each items-->`, html: `<div><input><p>one</p></div><div><input><p>two</p></div><div><input><p>three</p></div><!--#each items-->`,
test ( component, target, window ) { test ( component, target, window ) {
const inputs = [ ...target.querySelectorAll( 'input' ) ]; const inputs = [ ...target.querySelectorAll( 'input' ) ];
const items = component.get( 'items' );
const event = new window.Event( 'input' );
assert.equal( inputs[0].value, 'one' ); assert.equal( inputs[0].value, 'one' );
const event = new window.Event( 'input' );
inputs[1].value = 'four'; inputs[1].value = 'four';
inputs[1].dispatchEvent( event ); inputs[1].dispatchEvent( event );
assert.equal( items[1], 'four' );
assert.equal( target.innerHTML, `<div><input><p>one</p></div><div><input><p>four</p></div><div><input><p>three</p></div><!--#each items-->` ); assert.equal( target.innerHTML, `<div><input><p>one</p></div><div><input><p>four</p></div><div><input><p>three</p></div><!--#each items-->` );
const items = component.get( 'items' );
items[2] = 'five'; items[2] = 'five';
component.set({ items }); component.set({ items });

Loading…
Cancel
Save