diff --git a/compiler/generate/index.js b/compiler/generate/index.js index 347d7062cf..ead297fa2e 100644 --- a/compiler/generate/index.js +++ b/compiler/generate/index.js @@ -15,28 +15,41 @@ export default function generate ( parsed, source, options ) { const generator = { addElement ( name, renderStatement, needsIdentifier = false ) { - const needsTeardown = generator.current.localElementDepth === 0; - if ( needsIdentifier || needsTeardown ) { + const isToplevel = generator.current.localElementDepth === 0; + if ( needsIdentifier || isToplevel ) { generator.current.initStatements.push( deindent` var ${name} = ${renderStatement}; - ${generator.appendToTarget( name )}; ` ); + generator.createMountStatement( name ); } else { generator.current.initStatements.push( deindent` ${generator.current.target}.appendChild( ${renderStatement} ); ` ); } - if ( needsTeardown ) { + if ( isToplevel ) { generator.current.teardownStatements.push( deindent` if ( detach ) ${name}.parentNode.removeChild( ${name} ); ` ); } }, - appendToTarget ( name ) { - if ( generator.current.useAnchor && generator.current.target === 'target' ) { - return `anchor.parentNode.insertBefore( ${name}, anchor )`; + + createMountStatement ( name ) { + if ( generator.current.target === 'target' ) { + generator.current.mountStatements.push( deindent` + target.insertBefore( ${name}, anchor ); + ` ); + } else { + generator.current.initStatements.push( deindent` + ${generator.current.target}.appendChild( ${name} ); + ` ); } - return `${generator.current.target}.appendChild( ${name} )`; + }, + + createAnchor ( _name, description = '' ) { + const name = `${_name}_anchor`; + const statement = `document.createComment( ${JSON.stringify( description )} )`; + generator.addElement( name, statement, true ); + return name; }, addRenderer ( fragment ) { @@ -45,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' )} }, @@ -101,7 +118,7 @@ export default function generate ( parsed, source, options ) { const context = indexes[ name ]; if ( !~usedContexts.indexOf( context ) ) usedContexts.push( context ); } else { - dependencies.push( node.name ); + dependencies.push( name ); generator.code.prependRight( node.start, `root.` ); if ( !~usedContexts.indexOf( 'root' ) ) usedContexts.push( 'root' ); } @@ -236,6 +253,7 @@ export default function generate ( parsed, source, options ) { localElementDepth: 0, initStatements: [], + mountStatements: [], updateStatements: [], teardownStatements: [], @@ -364,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 ) { @@ -387,8 +409,8 @@ export default function generate ( parsed, source, options ) { if ( templateProperties.onrender ) { initStatements.push( deindent` - if ( options.parent ) { - options.parent.__renderHooks.push({ fn: template.onrender, context: this }); + if ( options.root ) { + options.root.__renderHooks.push({ fn: template.onrender, context: this }); } else { template.onrender.call( this ); } @@ -449,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; @@ -489,6 +515,8 @@ export default function generate ( parsed, source, options ) { state = {}; }; + this.root = options.root; + ${initStatements.join( '\n\n' )} } ` ); diff --git a/compiler/generate/visitors/EachBlock.js b/compiler/generate/visitors/EachBlock.js index c20e9b8320..bfe4cf0cff 100644 --- a/compiler/generate/visitors/EachBlock.js +++ b/compiler/generate/visitors/EachBlock.js @@ -5,52 +5,59 @@ export default { enter ( generator, node ) { const i = generator.counters.each++; const name = `eachBlock_${i}`; - const anchor = `${name}_anchor`; + 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 ); - generator.addElement( anchor, `document.createComment( ${JSON.stringify( `#each ${generator.source.slice( node.expression.start, node.expression.end )}` )} )`, true ); + const anchor = generator.createAnchor( name, `#each ${generator.source.slice( node.expression.start, node.expression.end )}` ); 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 needsTeardown = generator.current.localElementDepth === 0; generator.current.teardownStatements.push( deindent` - for ( var i = 0; i < ${name}_iterations.length; i += 1 ) { - ${name}_iterations[i].teardown( ${needsTeardown ? 'detach' : 'false'} ); + for ( var i = 0; i < ${iterations}.length; i += 1 ) { + ${iterations}[i].teardown( ${isToplevel ? 'detach' : 'false'} ); } ` ); @@ -88,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 8f5bf5f861..7b050cdb1a 100644 --- a/compiler/generate/visitors/Element.js +++ b/compiler/generate/visitors/Element.js @@ -15,11 +15,12 @@ export default { allUsedContexts: new Set(), init: [], + mount: [], update: [], teardown: [] }; - const shouldDetach = generator.current.localElementDepth === 0; + const isToplevel = generator.current.localElementDepth === 0; if ( isComponent ) { generator.hasComponents = true; @@ -57,20 +58,24 @@ export default { ${statements.join( '\n\n' )} var ${name} = new template.components.${node.name}({ - target: ${generator.current.target}, - parent: component, + target: ${!isToplevel ? generator.current.target: 'null'}, + root: component.root || component, data: ${name}_initialData }); ` ); } else { local.init.unshift( deindent` var ${name} = new template.components.${node.name}({ - target: ${generator.current.target}, - parent: component + 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` @@ -87,7 +92,7 @@ export default { ` ); } - local.teardown.push( `${name}.teardown( ${shouldDetach} );` ); + local.teardown.push( `${name}.teardown( ${isToplevel ? 'detach' : 'false'} );` ); } else { @@ -132,7 +137,7 @@ export default { } local.init.unshift( render ); - if ( shouldDetach ) { + if ( isToplevel ) { local.teardown.push( `if ( detach ) ${name}.parentNode.removeChild( ${name} );` ); } } @@ -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({ @@ -166,7 +172,6 @@ export default { if ( isComponent ) return; - generator.current.initStatements.push( - generator.appendToTarget( name ) ); + generator.createMountStatement( name ); } }; diff --git a/compiler/generate/visitors/IfBlock.js b/compiler/generate/visitors/IfBlock.js index 76ab5255e5..98fb4d05a0 100644 --- a/compiler/generate/visitors/IfBlock.js +++ b/compiler/generate/visitors/IfBlock.js @@ -11,18 +11,19 @@ function generateBlock ( generator, node, name ) { localElementDepth: 0, initStatements: [], + mountStatements: [], updateStatements: [], teardownStatements: [], counter: counter() }); node.children.forEach( generator.visit ); - //generator.visit( node.children ); generator.addRenderer( generator.current ); generator.pop(); // unset the children, to avoid them being visited again node.children = []; } + function getConditionsAndBlocks ( generator, node, _name, i = 0 ) { generator.addSourcemapLocations( node.expression ); const name = `${_name}_${i}`; @@ -54,15 +55,15 @@ export default { enter ( generator, node ) { const i = generator.counters.if++; - const { params, target } = generator.current; + const { params } = generator.current; const name = `ifBlock_${i}`; - const anchor = `${name}_anchor`; const getBlock = `getBlock_${i}`; const currentBlock = `currentBlock_${i}`; + const isToplevel = generator.current.localElementDepth === 0; const conditionsAndBlocks = getConditionsAndBlocks( generator, node, `renderIfBlock_${i}` ); - generator.addElement( anchor, `document.createComment( ${JSON.stringify( `#if ${generator.source.slice( node.expression.start, node.expression.end )}` )} )`, true ); + const anchor = generator.createAnchor( name, `#if ${generator.source.slice( node.expression.start, node.expression.end )}` ); generator.current.initStatements.push( deindent` function ${getBlock} ( ${params} ) { @@ -72,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} ); @@ -82,10 +90,13 @@ 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} ); } ` ); - generator.current.teardownStatements.push( `if ( ${name} ) ${name}.teardown( detach );` ); + generator.current.teardownStatements.push( deindent` + if ( ${name} ) ${name}.teardown( ${isToplevel ? 'detach' : 'false'} ); + ` ); } }; diff --git a/test/compiler/component-data-dynamic/Widget.html b/test/compiler/component-data-dynamic/Widget.html index 5886087d16..43184b9ad8 100644 --- a/test/compiler/component-data-dynamic/Widget.html +++ b/test/compiler/component-data-dynamic/Widget.html @@ -1,3 +1,4 @@

foo: {{foo}}

baz: {{baz}} ({{typeof baz}})

qux: {{qux}}

+

quux: {{quux}}

diff --git a/test/compiler/component-data-dynamic/_config.js b/test/compiler/component-data-dynamic/_config.js index 514faf667e..48c8fff2bf 100644 --- a/test/compiler/component-data-dynamic/_config.js +++ b/test/compiler/component-data-dynamic/_config.js @@ -2,16 +2,18 @@ export default { data: { bar: 'lol', x: 2, - compound: 'piece of' + compound: 'piece of', + go: { deeper: 'core' } }, - html: `

foo: lol

\n

baz: 42 (number)

\n

qux: this is a piece of string

`, + html: `

foo: lol

\n

baz: 42 (number)

\n

qux: this is a piece of string

\n

quux: core

`, test ( assert, component, target ) { component.set({ bar: 'wut', x: 3, - compound: 'rather boring' + compound: 'rather boring', + go: { deeper: 'heart' } }); - assert.equal( target.innerHTML, `

foo: wut

\n

baz: 43 (number)

\n

qux: this is a rather boring string

` ); + assert.equal( target.innerHTML, `

foo: wut

\n

baz: 43 (number)

\n

qux: this is a rather boring string

\n

quux: heart

` ); } }; diff --git a/test/compiler/component-data-dynamic/main.html b/test/compiler/component-data-dynamic/main.html index 118ba7b76e..12d5f3208b 100644 --- a/test/compiler/component-data-dynamic/main.html +++ b/test/compiler/component-data-dynamic/main.html @@ -1,5 +1,5 @@
- +
diff --git a/test/compiler/onrender-fires-when-ready-nested/ParentWidget.html b/test/compiler/onrender-fires-when-ready-nested/ParentWidget.html new file mode 100644 index 0000000000..1ab8608eff --- /dev/null +++ b/test/compiler/onrender-fires-when-ready-nested/ParentWidget.html @@ -0,0 +1,11 @@ +{{#if foo}}{{/if}} + + diff --git a/test/compiler/onrender-fires-when-ready-nested/Widget.html b/test/compiler/onrender-fires-when-ready-nested/Widget.html new file mode 100644 index 0000000000..10de81c6d4 --- /dev/null +++ b/test/compiler/onrender-fires-when-ready-nested/Widget.html @@ -0,0 +1,9 @@ +

{{inDocument}}

+ + diff --git a/test/compiler/onrender-fires-when-ready-nested/_config.js b/test/compiler/onrender-fires-when-ready-nested/_config.js new file mode 100644 index 0000000000..06327ebc4d --- /dev/null +++ b/test/compiler/onrender-fires-when-ready-nested/_config.js @@ -0,0 +1,3 @@ +export default { + html: `

true

\n

true

` +}; diff --git a/test/compiler/onrender-fires-when-ready-nested/main.html b/test/compiler/onrender-fires-when-ready-nested/main.html new file mode 100644 index 0000000000..6ba25d0a68 --- /dev/null +++ b/test/compiler/onrender-fires-when-ready-nested/main.html @@ -0,0 +1,16 @@ +
+ + +
+ +