Skip to content

Commit

Permalink
Merge pull request #209 from sveltejs/gh-81
Browse files Browse the repository at this point in the history
Keyed updates
  • Loading branch information
Rich-Harris authored Dec 18, 2016
2 parents e5b4df8 + d0ffb64 commit 9af5c27
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 34 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ coverage
coverage.lcov
test/sourcemaps/*/output.js
test/sourcemaps/*/output.js.map
scratch
33 changes: 21 additions & 12 deletions src/generators/dom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,30 @@ class DomGenerator extends Generator {
` );
}

const properties = new CodeBuilder();

if ( fragment.key ) properties.addBlock( `key: key,` );

properties.addBlock( deindent`
mount: function ( target, anchor ) {
${fragment.builders.mount}
},
update: function ( changed, ${fragment.params} ) {
${fragment.builders.update}
},
teardown: function ( detach ) {
${fragment.builders.teardown}
}
` );

this.renderers.push( deindent`
function ${fragment.name} ( ${fragment.params}, component ) {
function ${fragment.name} ( ${fragment.params}, component${fragment.key ? `, key` : ''} ) {
${fragment.builders.init}
return {
mount: function ( target, anchor ) {
${fragment.builders.mount}
},
update: function ( changed, ${fragment.params} ) {
${fragment.builders.update}
},
teardown: function ( detach ) {
${fragment.builders.teardown}
}
${properties}
};
}
` );
Expand Down Expand Up @@ -127,6 +135,7 @@ export default function dom ( parsed, source, options, names ) {
namespace,
target: 'target',
localElementDepth: 0,
key: null,

contexts: {},
indexes: {},
Expand Down
3 changes: 2 additions & 1 deletion src/generators/dom/visitors/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ export default {
namespace: local.namespace,
target: name,
parent: generator.current,
localElementDepth: generator.current.localElementDepth + 1
localElementDepth: generator.current.localElementDepth + 1,
key: null
});
},

Expand Down
106 changes: 86 additions & 20 deletions src/generators/dom/visitors/EachBlock.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CodeBuilder from '../../../utils/CodeBuilder.js';
import deindent from '../../../utils/deindent.js';
import getBuilders from '../utils/getBuilders.js';

Expand All @@ -22,16 +23,42 @@ export default {
const anchor = `${name}_anchor`;
generator.createAnchor( anchor, `#each ${generator.source.slice( node.expression.start, node.expression.end )}` );

generator.current.builders.init.addBlock( deindent`
var ${name}_value = ${snippet};
var ${iterations} = [];
${node.else ? `var ${elseName} = null;` : ''}
generator.current.builders.init.addLine( `var ${name}_value = ${snippet};` );
generator.current.builders.init.addLine( `var ${iterations} = [];` );
if ( node.key ) generator.current.builders.init.addLine( `var ${name}_lookup = Object.create( null );` );
if ( node.else ) generator.current.builders.init.addLine( `var ${elseName} = null;` );

const initialRender = new CodeBuilder();

const localVars = {};

if ( node.key ) {
localVars.fragment = generator.current.getUniqueName( 'fragment' );
localVars.value = generator.current.getUniqueName( 'value' );
localVars.key = generator.current.getUniqueName( 'key' );

initialRender.addBlock( deindent`
var ${localVars.key} = ${name}_value[${i}].${node.key};
${name}_iterations[${i}] = ${name}_lookup[ ${localVars.key} ] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, component${node.key ? `, ${localVars.key}` : `` } );
` );
} else {
initialRender.addLine(
`${name}_iterations[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, component );`
);
}

if ( !isToplevel ) {
initialRender.addLine(
`${name}_iterations[${i}].mount( ${anchor}.parentNode, ${anchor} );`
);
}

generator.current.builders.init.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${name}_value.length; ${i} += 1 ) {
${iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, component );
${!isToplevel ? `${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );` : ''}
${initialRender}
}
` );

if ( node.else ) {
generator.current.builders.init.addBlock( deindent`
if ( !${name}_value.length ) {
Expand All @@ -56,24 +83,62 @@ export default {
}
}

generator.current.builders.update.addBlock( deindent`
var ${name}_value = ${snippet};
if ( node.key ) {
generator.current.builders.update.addBlock( deindent`
var ${name}_value = ${snippet};
var _${name}_iterations = [];
var _${name}_lookup = Object.create( null );
var ${localVars.fragment} = document.createDocumentFragment();
// create new iterations as necessary
for ( var ${i} = 0; ${i} < ${name}_value.length; ${i} += 1 ) {
var ${localVars.value} = ${name}_value[${i}];
var ${localVars.key} = ${localVars.value}.${node.key};
if ( ${name}_lookup[ ${localVars.key} ] ) {
_${name}_iterations[${i}] = _${name}_lookup[ ${localVars.key} ] = ${name}_lookup[ ${localVars.key} ];
_${name}_lookup[ ${localVars.key} ].update( changed, ${params}, ${listName}, ${listName}[${i}], ${i} );
} else {
_${name}_iterations[${i}] = _${name}_lookup[ ${localVars.key} ] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, component${node.key ? `, ${localVars.key}` : `` } );
}
for ( var ${i} = 0; ${i} < ${name}_value.length; ${i} += 1 ) {
if ( !${iterations}[${i}] ) {
${iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, component );
${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
} else {
${iterations}[${i}].update( changed, ${params}, ${listName}, ${listName}[${i}], ${i} );
_${name}_iterations[${i}].mount( ${localVars.fragment}, null );
}
}
for ( var ${i} = ${name}_value.length; ${i} < ${iterations}.length; ${i} += 1 ) {
${iterations}[${i}].teardown( true );
}
// remove old iterations
for ( var ${i} = 0; ${i} < ${name}_iterations.length; ${i} += 1 ) {
var ${name}_iteration = ${name}_iterations[${i}];
if ( !_${name}_lookup[ ${name}_iteration.${localVars.key} ] ) {
${name}_iteration.teardown( true );
}
}
${iterations}.length = ${listName}.length;
` );
${name}_anchor.parentNode.insertBefore( ${localVars.fragment}, ${name}_anchor );
${name}_iterations = _${name}_iterations;
${name}_lookup = _${name}_lookup;
` );
} else {
generator.current.builders.update.addBlock( deindent`
var ${name}_value = ${snippet};
for ( var ${i} = 0; ${i} < ${name}_value.length; ${i} += 1 ) {
if ( !${iterations}[${i}] ) {
${iterations}[${i}] = ${renderer}( ${params}, ${listName}, ${listName}[${i}], ${i}, component );
${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
} else {
${iterations}[${i}].update( changed, ${params}, ${listName}, ${listName}[${i}], ${i} );
}
}
for ( var ${i} = ${name}_value.length; ${i} < ${iterations}.length; ${i} += 1 ) {
${iterations}[${i}].teardown( true );
}
${iterations}.length = ${listName}.length;
` );
}

if ( node.else ) {
generator.current.builders.update.addBlock( deindent`
Expand Down Expand Up @@ -128,6 +193,7 @@ export default {
target: 'target',
expression: node.expression,
context: node.context,
key: node.key,
localElementDepth: 0,

contextDependencies,
Expand Down
3 changes: 2 additions & 1 deletion src/generators/dom/visitors/Element.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ export default {
namespace: local.namespace,
target: name,
parent: generator.current,
localElementDepth: generator.current.localElementDepth + 1
localElementDepth: generator.current.localElementDepth + 1,
key: null
});
},

Expand Down
6 changes: 6 additions & 0 deletions src/parse/state/mustache.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ export default function mustache ( parser ) {
if ( !block.index ) parser.error( `Expected name` );
parser.allowWhitespace();
}

if ( parser.eat( '@' ) ) {
block.key = parser.read( validIdentifier );
if ( !block.key ) parser.error( `Expected name` );
parser.allowWhitespace();
}
}

parser.eat( '}}', true );
Expand Down
28 changes: 28 additions & 0 deletions test/generator/each-block-keyed/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export default {
data: {
todos: [
{ id: 123, description: 'implement keyed each blocks' },
{ id: 234, description: 'implement client-side hydration' }
]
},

html: '<p>implement keyed each blocks</p><p>implement client-side hydration</p>',

test ( assert, component, target ) {
const [ p1, p2 ] = target.querySelectorAll( 'p' );

component.set({
todos: [
{ id: 234, description: 'implement client-side hydration' }
]
});
assert.htmlEqual( target.innerHTML, '<p>implement client-side hydration</p>' );

const [ p3 ] = target.querySelectorAll( 'p' );

assert.ok( !target.contains( p1 ), 'first <p> element should be removed' );
assert.equal( p2, p3, 'second <p> element should be retained' );

component.teardown();
}
};
3 changes: 3 additions & 0 deletions test/generator/each-block-keyed/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{#each todos as todo @id}}
<p>{{todo.description}}</p>
{{/each}}
3 changes: 3 additions & 0 deletions test/parser/each-block-keyed/input.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{#each todos as todo @id}}
<p>{{todo}}</p>
{{/each}}
46 changes: 46 additions & 0 deletions test/parser/each-block-keyed/output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"html": {
"start": 0,
"end": 54,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 54,
"type": "EachBlock",
"expression": {
"start": 8,
"end": 13,
"type": "Identifier",
"name": "todos"
},
"context": "todo",
"key": "id",
"children": [
{
"start": 29,
"end": 44,
"type": "Element",
"name": "p",
"attributes": [],
"children": [
{
"start": 32,
"end": 40,
"type": "MustacheTag",
"expression": {
"start": 34,
"end": 38,
"type": "Identifier",
"name": "todo"
}
}
]
}
]
}
]
},
"css": null,
"js": null
}

0 comments on commit 9af5c27

Please sign in to comment.