some fictional {{#each}} code

pull/31/head
Rich-Harris 9 years ago
parent 4b2d8f7e3d
commit ba9238b864

@ -36,45 +36,56 @@ export default function generate ( parsed, template ) {
children: [], children: [],
renderBlocks: [], renderBlocks: [],
removeBlocks: [], removeBlocks: [],
anchor: null anchor: null,
renderImmediately: true
}; };
const stack = [ current ]; const stack = [ current ];
walkHtml( child, { walkHtml( child, {
enter ( node ) { Element: {
if ( node.type === 'Element' ) { enter ( node ) {
current = { const target = `element_${counters.element++}`;
target: `element_${counters.element++}`,
conditions: current.conditions,
children: current.children,
renderBlocks: current.renderBlocks,
removeBlocks: current.removeBlocks,
anchor: current.anchor
};
stack.push( current ); stack.push( current );
declarations.push( `var ${current.target};` ); declarations.push( `var ${target};` );
if ( current.anchor ) { if ( current.renderImmediately ) {
current.renderBlocks.push( deindent` current.renderBlocks.push( deindent`
${current.target} = document.createElement( '${node.name}' ); ${target} = document.createElement( '${node.name}' );
${current.anchor}.parentNode.insertBefore( ${current.target}, ${current.anchor} ); ${current.target}.appendChild( ${target} );
` ); ` );
} else { } else {
current.renderBlocks.push( deindent` current.renderBlocks.push( deindent`
${current.target} = document.createElement( '${node.name}' ); ${target} = document.createElement( '${node.name}' );
options.target.appendChild( ${current.target} ); ${current.anchor}.parentNode.insertBefore( ${target}, ${current.anchor} );
` ); ` );
} }
current.removeBlocks.push( deindent` current.removeBlocks.push( deindent`
${current.target}.parentNode.removeChild( ${current.target} ); ${target}.parentNode.removeChild( ${target} );
` ); ` );
current = {
target,
conditions: current.conditions,
children: current.children,
renderBlocks: current.renderBlocks,
removeBlocks: current.removeBlocks,
anchor: current.anchor,
renderImmediately: false
};
},
leave () {
stack.pop();
current = stack[ stack.length - 1 ];
} }
},
else if ( node.type === 'Text' ) { Text: {
enter ( node ) {
if ( current.target === ROOT ) { if ( current.target === ROOT ) {
const identifier = `text_${counters.text++}`; const identifier = `text_${counters.text++}`;
@ -97,8 +108,10 @@ export default function generate ( parsed, template ) {
` ); ` );
} }
} }
},
else if ( node.type === 'MustacheTag' ) { MustacheTag: {
enter ( node ) {
const identifier = `text_${counters.text++}`; const identifier = `text_${counters.text++}`;
const expression = node.expression.type === 'Identifier' ? node.expression.name : 'TODO'; // TODO handle block-local state const expression = node.expression.type === 'Identifier' ? node.expression.name : 'TODO'; // TODO handle block-local state
@ -120,8 +133,10 @@ export default function generate ( parsed, template ) {
${identifier} = null; ${identifier} = null;
` ); ` );
} }
},
else if ( node.type === 'IfBlock' ) { IfBlock: {
enter ( node ) {
const anchor = `anchor_${counters.anchor++}`; const anchor = `anchor_${counters.anchor++}`;
const suffix = `if_${counters.if++}`; const suffix = `if_${counters.if++}`;
@ -146,7 +161,8 @@ export default function generate ( parsed, template ) {
conditions: current.conditions.concat( expression ), conditions: current.conditions.concat( expression ),
renderBlocks: [], renderBlocks: [],
removeBlocks: [], removeBlocks: [],
anchor anchor,
renderImmediately: false
}; };
setStatements.push( deindent` setStatements.push( deindent`
@ -161,9 +177,29 @@ export default function generate ( parsed, template ) {
` ); ` );
stack.push( current ); stack.push( current );
},
leave ( node ) {
const { line, column } = locator( node.start );
initStatements.push( deindent`
// (${line}:${column}) {{#if ${template.slice( node.expression.start, node.expression.end )}}}...{{/if}}
function ${current.renderName} () {
${current.renderBlocks.join( '\n\n' )}
}
function ${current.removeName} () {
${current.removeBlocks.join( '\n\n' )}
}
` );
stack.pop();
current = stack[ stack.length - 1 ];
} }
},
else if ( node.type === 'EachBlock' ) { EachBlock: {
enter ( node ) {
const loopIndex = counters.loop++; const loopIndex = counters.loop++;
const anchor = `anchor_${counters.anchor++}`; const anchor = `anchor_${counters.anchor++}`;
@ -184,137 +220,98 @@ export default function generate ( parsed, template ) {
` ); ` );
current = { current = {
target: current.target, target: `fragment_${loopIndex}`,
expression,
conditions: current.conditions, conditions: current.conditions,
renderBlocks: [], renderBlocks: [],
removeBlocks: [], removeBlocks: [],
anchor, anchor,
loopIndex loopIndex,
renderImmediately: true
}; };
setStatements.push( deindent` setStatements.push( deindent`
// TODO account for conditions (nested ifs) // TODO account for conditions (nested ifs)
if ( state.${expression} && !oldState.${expression} ) render_each_${loopIndex}(); if ( '${expression}' in state ) each_${loopIndex}.update();
else if ( !state.${expression} && oldState.${expression} ) remove_each_${loopIndex}();
` ); ` );
teardownStatements.push( deindent` // need to add teardown logic if this is at the
// TODO account for conditions (nested ifs) // top level (TODO or if there are event handlers attached?)
if ( state.${expression} ) remove_each_${loopIndex}(); if ( current.target === ROOT ) {
` ); teardownStatements.push( deindent`
if ( true ) { // <!-- TODO conditions
for ( let i = 0; i < state.${expression}.length; i += 1 ) {
each_${loopIndex}.removeIteration( i );
}
}
` );
}
stack.push( current ); stack.push( current );
} },
else { leave ( node ) {
throw new Error( `Not implemented: ${node.type}` );
}
},
leave ( node ) {
if ( node.type === 'IfBlock' ) {
const { line, column } = locator( node.start );
initStatements.push( deindent`
// (${line}:${column}) {{#if ${template.slice( node.expression.start, node.expression.end )}}}...{{/if}}
function ${current.renderName} () {
${current.renderBlocks.join( '\n\n' )}
}
function ${current.removeName} () {
${current.removeBlocks.join( '\n\n' )}
}
` );
stack.pop();
current = stack[ stack.length - 1 ];
}
else if ( node.type === 'EachBlock' ) {
const { line, column } = locator( node.start ); const { line, column } = locator( node.start );
const loopIndex = current.loopIndex; const loopIndex = current.loopIndex;
initStatements.push( deindent` initStatements.push( deindent`
// (${line}:${column}) {{#each ${template.slice( node.expression.start, node.expression.end )}}}...{{/each}} // (${line}:${column}) {{#each ${template.slice( node.expression.start, node.expression.end )}}}...{{/each}}
${current.renderBlocks.join( '\n\n' )}
var each_${loopIndex} = { var each_${loopIndex} = {
iterations: [], iterations: [],
render () { update: function () {
const target = document.createDocumentFragment(); var target = document.createDocumentFragment();
let i; var i;
for ( i = 0; i < state.${current.expression}.length; i += 1 ) { for ( i = 0; i < state.${current.expression}.length; i += 1 ) {
if ( !this.iterations[i] ) { if ( !this.iterations[i] ) {
this.iterations[i] = render_iteration_${loopIndex}( target ); this.iterations[i] = this.renderIteration( target );
} }
const iteration = this.iterations[i]; const iteration = this.iterations[i];
update_iteration_${loopIndex}( this.iterations[i], state.people[i] ); this.updateIteration( this.iterations[i], state.${current.expression}[i] );
} }
for ( ; i < this.iterations.length; i += 1 ) { for ( ; i < this.iterations.length; i += 1 ) {
remove_iteration_${loopIndex}( this.iterations[i] ); this.removeIteration( i );
} }
${current.anchor}.parentNode.insertBefore( target, ${current.anchor} ); ${current.anchor}.parentNode.insertBefore( target, ${current.anchor} );
loop_${loopIndex}.length = state.people.length; each_${loopIndex}.length = state.${current.expression}.length;
},
renderIteration ( target ) {
}, },
update () { renderIteration: function ( target ) {
var fragment = fragment_0.cloneNode( true );
}, var element_0 = fragment.childNodes[0];
var text_0 = element_0.childNodes[0];
updateIteration ( iteration, context ) { var iteration = {
element_0: element_0,
text_0: text_0
};
target.appendChild( fragment );
return iteration;
}, },
remove () { updateIteration: function ( iteration, context ) {
iteration.text_0.data = context;
}, },
removeIteration ( iteration ) { removeIteration: function ( i ) {
var iteration = this.iterations[i];
iteration.element_0.parentNode.removeChild( iteration.element_0 );
} }
}; };
function ${current.renderName} () {
const target = document.createDocumentFragment();
let i;
for ( i = 0; i < state.${current.expression}.length; i += 1 ) {
if ( !loop_${loopIndex}[i] ) {
loop_${loopIndex}[i] = render_iteration_${loopIndex}( target );
}
const iteration = loop_${loopIndex}[i];
update_iteration_${loopIndex}( loop_${loopIndex}[i], state.people[i] );
}
for ( ; i < loop_${loopIndex}.length; i += 1 ) {
remove_iteration_${loopIndex}( loop_${loopIndex}[i] );
}
${current.anchor}.parentNode.insertBefore( target, ${current.anchor} );
loop_${loopIndex}.length = state.people.length;
}
function ${current.removeName} () {
${current.removeBlocks.join( '\n\n' )}
}
` ); ` );
stack.pop(); teardownStatements.push( ...current.removeBlocks );
current = stack[ stack.length - 1 ];
}
else if ( node.type === 'Element' ) {
stack.pop(); stack.pop();
current = stack[ stack.length - 1 ]; current = stack[ stack.length - 1 ];
} }
@ -322,12 +319,12 @@ export default function generate ( parsed, template ) {
}); });
initStatements.push( ...current.renderBlocks ); initStatements.push( ...current.renderBlocks );
initStatements.unshift( declarations.join( '\n' ) );
teardownStatements.push( ...current.removeBlocks ); teardownStatements.push( ...current.removeBlocks );
}); });
teardownStatements.push( deindent` teardownStatements.push( 'state = {};' );
state = {};
` );
const code = deindent` const code = deindent`
export default function createComponent ( options ) { export default function createComponent ( options ) {
@ -383,6 +380,7 @@ export default function generate ( parsed, template ) {
${teardownStatements.join( '\n\n' )} ${teardownStatements.join( '\n\n' )}
}; };
// initialisation
${initStatements.join( '\n\n' )} ${initStatements.join( '\n\n' )}
component.set( options.data ); component.set( options.data );

@ -1,6 +1,9 @@
export default function walkHtml ( html, { enter, leave } ) { export default function walkHtml ( html, visitors ) {
function visit ( node ) { function visit ( node ) {
enter( node ); const visitor = visitors[ node.type ];
if ( !visitor ) throw new Error( `Not implemented: ${node.type}` );
if ( visitor.enter ) visitor.enter( node );
if ( node.children ) { if ( node.children ) {
node.children.forEach( child => { node.children.forEach( child => {
@ -8,7 +11,7 @@ export default function walkHtml ( html, { enter, leave } ) {
}); });
} }
leave( node ); if ( visitor.leave ) visitor.leave( node );
} }
visit( html ); visit( html );

@ -0,0 +1,11 @@
export default {
description: 'nested {{#each}} blocks',
data: {
columns: [ 'a', 'b', 'c' ],
row: [ 1, 2, 3 ]
},
html: `<div>a, 1</div><div>b, 1</div><div>c, 1</div><div>a, 2</div><div>b, 2</div><div>c, 2</div><div>a, 3</div><div>b, 3</div><div>c, 3</div>`,
test ( component, target ) {
// TODO
}
};

@ -0,0 +1,5 @@
{{#each columns as x}}
{{#each rows as y}}
<div>{{x}}, {{y}}</div>
{{/each}}
{{/each}}
Loading…
Cancel
Save