diff --git a/compiler/generate/index.js b/compiler/generate/index.js index f3a7b9429b..ead297fa2e 100644 --- a/compiler/generate/index.js +++ b/compiler/generate/index.js @@ -34,12 +34,12 @@ export default function generate ( parsed, source, options ) { }, createMountStatement ( name ) { - if ( generator.current.useAnchor && generator.current.target === 'target' ) { - generator.current.initStatements.push( deindent ` - anchor.parentNode.insertBefore( ${name}, anchor ); + if ( generator.current.target === 'target' ) { + generator.current.mountStatements.push( deindent` + target.insertBefore( ${name}, anchor ); ` ); } else { - generator.current.initStatements.push( deindent ` + generator.current.initStatements.push( deindent` ${generator.current.target}.appendChild( ${name} ); ` ); } @@ -58,10 +58,14 @@ export default function generate ( parsed, source, options ) { } renderers.push( deindent` - function ${fragment.name} ( ${fragment.params}, component, target${fragment.useAnchor ? ', anchor' : ''} ) { + function ${fragment.name} ( ${fragment.params}, component ) { ${fragment.initStatements.join( '\n\n' )} return { + mount: function ( target, anchor ) { + ${fragment.mountStatements.join( '\n\n' )} + }, + update: function ( changed, ${fragment.params} ) { ${fragment.updateStatements.join( '\n\n' )} }, @@ -249,6 +253,7 @@ export default function generate ( parsed, source, options ) { localElementDepth: 0, initStatements: [], + mountStatements: [], updateStatements: [], teardownStatements: [], @@ -377,13 +382,17 @@ export default function generate ( parsed, source, options ) { if ( generator.hasComplexBindings ) { initStatements.push( deindent` this.__bindings = []; - var mainFragment = renderMainFragment( state, this, options.target ); + var mainFragment = renderMainFragment( state, this ); + if ( options.target ) this.mount( options.target ); while ( this.__bindings.length ) this.__bindings.pop()(); ` ); setStatements.push( `while ( this.__bindings.length ) this.__bindings.pop()();` ); } else { - initStatements.push( `var mainFragment = renderMainFragment( state, this, options.target );` ); + initStatements.push( deindent` + var mainFragment = renderMainFragment( state, this ); + if ( options.target ) this.mount( options.target ); + ` ); } if ( generator.hasComponents ) { @@ -462,6 +471,10 @@ export default function generate ( parsed, source, options ) { ${setStatements.join( '\n\n' )} }; + this.mount = function mount ( target, anchor ) { + mainFragment.mount( target, anchor ); + } + this.observe = function ( key, callback, options ) { var group = ( options && options.defer ) ? observers.deferred : observers.immediate; diff --git a/compiler/generate/visitors/EachBlock.js b/compiler/generate/visitors/EachBlock.js index 53f9a07d7e..bfe4cf0cff 100644 --- a/compiler/generate/visitors/EachBlock.js +++ b/compiler/generate/visitors/EachBlock.js @@ -5,10 +5,13 @@ export default { enter ( generator, node ) { const i = generator.counters.each++; const name = `eachBlock_${i}`; + const iterations = `${name}_iterations`; const renderer = `renderEachBlock_${i}`; const listName = `${name}_value`; + const isToplevel = generator.current.localElementDepth === 0; + generator.addSourcemapLocations( node.expression ); const { dependencies, snippet } = generator.contextualise( node.expression ); @@ -17,39 +20,44 @@ export default { generator.current.initStatements.push( deindent` var ${name}_value = ${snippet}; - var ${name}_fragment = document.createDocumentFragment(); - var ${name}_iterations = []; + var ${iterations} = []; for ( var i = 0; i < ${name}_value.length; i += 1 ) { - ${name}_iterations[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component, ${name}_fragment ); + ${iterations}[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component ); + ${!isToplevel ? `${iterations}[i].mount( ${anchor}.parentNode, ${anchor} );` : ''} } - - ${anchor}.parentNode.insertBefore( ${name}_fragment, ${anchor} ); ` ); + if ( isToplevel ) { + generator.current.mountStatements.push( deindent` + for ( var i = 0; i < ${iterations}.length; i += 1 ) { + ${iterations}[i].mount( ${anchor}.parentNode, ${anchor} ); + } + ` ); + } + generator.current.updateStatements.push( deindent` var ${name}_value = ${snippet}; for ( var i = 0; i < ${name}_value.length; i += 1 ) { - if ( !${name}_iterations[i] ) { - ${name}_iterations[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component, ${name}_fragment ); + if ( !${iterations}[i] ) { + ${iterations}[i] = ${renderer}( ${generator.current.params}, ${listName}, ${listName}[i], i, component ); + ${iterations}[i].mount( ${anchor}.parentNode, ${anchor} ); } else { - ${name}_iterations[i].update( changed, ${generator.current.params}, ${listName}, ${listName}[i], i ); + ${iterations}[i].update( changed, ${generator.current.params}, ${listName}, ${listName}[i], i ); } } - for ( var i = ${name}_value.length; i < ${name}_iterations.length; i += 1 ) { - ${name}_iterations[i].teardown( true ); + for ( var i = ${name}_value.length; i < ${iterations}.length; i += 1 ) { + ${iterations}[i].teardown( true ); } - ${anchor}.parentNode.insertBefore( ${name}_fragment, ${anchor} ); - ${name}_iterations.length = ${listName}.length; + ${iterations}.length = ${listName}.length; ` ); - const isToplevel = generator.current.localElementDepth === 0; generator.current.teardownStatements.push( deindent` - for ( var i = 0; i < ${name}_iterations.length; i += 1 ) { - ${name}_iterations[i].teardown( ${isToplevel ? 'detach' : 'false'} ); + for ( var i = 0; i < ${iterations}.length; i += 1 ) { + ${iterations}[i].teardown( ${isToplevel ? 'detach' : 'false'} ); } ` ); @@ -87,6 +95,7 @@ export default { params, initStatements: [], + mountStatements: [], updateStatements: [ Object.keys( contexts ).map( contextName => { const listName = listNames[ contextName ]; const indexName = indexNames[ contextName ]; diff --git a/compiler/generate/visitors/Element.js b/compiler/generate/visitors/Element.js index a55f048925..7b050cdb1a 100644 --- a/compiler/generate/visitors/Element.js +++ b/compiler/generate/visitors/Element.js @@ -15,6 +15,7 @@ export default { allUsedContexts: new Set(), init: [], + mount: [], update: [], teardown: [] }; @@ -57,7 +58,7 @@ export default { ${statements.join( '\n\n' )} var ${name} = new template.components.${node.name}({ - target: ${generator.current.target}, + target: ${!isToplevel ? generator.current.target: 'null'}, root: component.root || component, data: ${name}_initialData }); @@ -65,12 +66,16 @@ export default { } else { local.init.unshift( deindent` var ${name} = new template.components.${node.name}({ - target: ${generator.current.target}, + target: ${!isToplevel ? generator.current.target: 'null'}, root: component.root || component }); ` ); } + if ( isToplevel ) { + local.mount.unshift( `${name}.mount( target, anchor );` ); + } + if ( local.dynamicAttributes.length ) { const updates = local.dynamicAttributes.map( attribute => { return deindent` @@ -146,6 +151,7 @@ export default { generator.current.initStatements.push( local.init.join( '\n' ) ); if ( local.update.length ) generator.current.updateStatements.push( local.update.join( '\n' ) ); + if ( local.mount.length ) generator.current.mountStatements.push( local.mount.join( '\n' ) ); generator.current.teardownStatements.push( local.teardown.join( '\n' ) ); generator.push({ diff --git a/compiler/generate/visitors/IfBlock.js b/compiler/generate/visitors/IfBlock.js index 2c8fbd30ca..98fb4d05a0 100644 --- a/compiler/generate/visitors/IfBlock.js +++ b/compiler/generate/visitors/IfBlock.js @@ -11,6 +11,7 @@ function generateBlock ( generator, node, name ) { localElementDepth: 0, initStatements: [], + mountStatements: [], updateStatements: [], teardownStatements: [], @@ -54,11 +55,12 @@ export default { enter ( generator, node ) { const i = generator.counters.if++; - const { params, target } = generator.current; + const { params } = generator.current; const name = `ifBlock_${i}`; const getBlock = `getBlock_${i}`; const currentBlock = `currentBlock_${i}`; + const isToplevel = generator.current.localElementDepth === 0; const conditionsAndBlocks = getConditionsAndBlocks( generator, node, `renderIfBlock_${i}` ); const anchor = generator.createAnchor( name, `#if ${generator.source.slice( node.expression.start, node.expression.end )}` ); @@ -71,9 +73,16 @@ export default { } var ${currentBlock} = ${getBlock}( ${params} ); - var ${name} = ${currentBlock} && ${currentBlock}( ${params}, component, ${target}, ${anchor} ); + var ${name} = ${currentBlock} && ${currentBlock}( ${params}, component ); ` ); + const mountStatement = `if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );`; + if ( isToplevel ) { + generator.current.mountStatements.push( mountStatement ); + } else { + generator.current.initStatements.push( mountStatement ); + } + generator.current.updateStatements.push( deindent` var _${currentBlock} = ${currentBlock}; ${currentBlock} = ${getBlock}( ${params} ); @@ -81,11 +90,11 @@ export default { ${name}.update( changed, ${params} ); } else { if ( ${name} ) ${name}.teardown( true ); - ${name} = ${currentBlock} && ${currentBlock}( ${params}, component, ${target}, ${anchor} ); + ${name} = ${currentBlock} && ${currentBlock}( ${params}, component ); + if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} ); } ` ); - const isToplevel = generator.current.localElementDepth === 0; generator.current.teardownStatements.push( deindent` if ( ${name} ) ${name}.teardown( ${isToplevel ? 'detach' : 'false'} ); ` ); diff --git a/test/compiler/each-block-random-permute/_config.js b/test/compiler/each-block-random-permute/_config.js new file mode 100644 index 0000000000..14c667091b --- /dev/null +++ b/test/compiler/each-block-random-permute/_config.js @@ -0,0 +1,33 @@ +const VALUES = Array.from( 'abcdefghijklmnopqrstuvwxyz' ); + +function permute () { + const values = VALUES.slice(); + const number = Math.floor(Math.random() * VALUES.length); + const permuted = []; + for (let i = 0; i < number; i++) { + permuted.push( ...values.splice( Math.floor( Math.random() * ( number - i ) ), 1 ) ); + } + + return { + data: permuted, + expected: permuted.length ? `(${permuted.join(')(')})` : '' + }; +} + +let step = permute(); + +export default { + data: { + values: step.data + }, + + html: step.expected, + + test ( assert, component, target ) { + for (let i = 0; i < 100; i++) { + step = permute(); + component.set({ values: step.data }); + assert.htmlEqual( target.innerHTML, step.expected ); + } + } +}; diff --git a/test/compiler/each-block-random-permute/main.html b/test/compiler/each-block-random-permute/main.html new file mode 100644 index 0000000000..8aef076d0e --- /dev/null +++ b/test/compiler/each-block-random-permute/main.html @@ -0,0 +1,3 @@ +{{#each values as value}} + ({{value}}) +{{/each}}