implement else blocks

pull/31/head
Rich-Harris 8 years ago
parent 77b600d726
commit c79b38ff6a

@ -98,9 +98,39 @@ export default function generate ( parsed, source, options = {} ) {
helpers: {}, helpers: {},
pop () {
const tail = generator.current;
generator.current = tail.parent;
return tail;
},
push ( fragment ) {
const newFragment = Object.assign( {}, generator.current, fragment, {
parent: generator.current
});
generator.current = newFragment;
},
usesRefs: false, usesRefs: false,
source source,
visit ( node ) {
const visitor = visitors[ node.type ];
if ( !visitor ) throw new Error( `Not implemented: ${node.type}` );
if ( visitor.enter ) visitor.enter( generator, node );
if ( node.children ) {
node.children.forEach( child => {
generator.visit( child );
});
}
if ( visitor.leave ) visitor.leave( generator, node );
}
}; };
const templateProperties = {}; const templateProperties = {};
@ -161,7 +191,7 @@ export default function generate ( parsed, source, options = {} ) {
}); });
} }
generator.current = { generator.push({
useAnchor: false, useAnchor: false,
name: 'renderMainFragment', name: 'renderMainFragment',
namespace: null, namespace: null,
@ -179,27 +209,12 @@ export default function generate ( parsed, source, options = {} ) {
indexNames: {}, indexNames: {},
listNames: {}, listNames: {},
counter: counter(), counter: counter()
parent: null
};
parsed.html.children.forEach( function visit ( node ) {
const visitor = visitors[ node.type ];
if ( !visitor ) throw new Error( `Not implemented: ${node.type}` );
if ( visitor.enter ) visitor.enter( generator, node );
if ( node.children ) {
node.children.forEach( child => {
visit( child );
});
}
if ( visitor.leave ) visitor.leave( generator, node );
}); });
generator.addRenderer( generator.current ); parsed.html.children.forEach( generator.visit );
generator.addRenderer( generator.pop() );
const setStatements = [ deindent` const setStatements = [ deindent`
const oldState = state; const oldState = state;

@ -0,0 +1,23 @@
import deindent from '../utils/deindent.js';
import counter from '../utils/counter.js';
export default {
enter ( generator ) {
const name = generator.current.name.replace( 'If', 'Else' );
generator.push({
name,
initStatements: [],
updateStatements: [],
teardownStatements: [],
counter: counter()
});
},
leave ( generator ) {
generator.addRenderer( generator.current );
generator.pop();
}
};

@ -1,5 +1,4 @@
import deindent from '../utils/deindent.js'; import deindent from '../utils/deindent.js';
import isReference from '../utils/isReference.js';
import counter from '../utils/counter.js'; import counter from '../utils/counter.js';
export default { export default {
@ -8,57 +7,74 @@ export default {
const name = `ifBlock_${i}`; const name = `ifBlock_${i}`;
const renderer = `renderIfBlock_${i}`; const renderer = `renderIfBlock_${i}`;
const elseName = `elseBlock_${i}`;
const elseRenderer = `renderElseBlock_${i}`;
generator.current.initStatements.push( deindent` generator.current.initStatements.push( deindent`
var ${name}_anchor = document.createComment( ${JSON.stringify( `#if ${generator.source.slice( node.expression.start, node.expression.end )}` )} ); var ${name}_anchor = document.createComment( ${JSON.stringify( `#if ${generator.source.slice( node.expression.start, node.expression.end )}` )} );
${generator.current.target}.appendChild( ${name}_anchor ); ${generator.current.target}.appendChild( ${name}_anchor );
var ${name} = null; var ${name} = null;${node.else ? `\nvar ${elseName} = null;` : ``}
` ); ` );
generator.addSourcemapLocations( node.expression ); generator.addSourcemapLocations( node.expression );
generator.contextualise( node.expression );
const usedContexts = generator.contextualise( node.expression );
const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`; const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`;
let expression; const ifTrue = [ deindent`
if ( !${name } ) {
if ( isReference( node.expression ) ) { ${name} = ${renderer}( component, ${generator.current.target}, ${name}_anchor );
const reference = `${generator.source.slice( node.expression.start, node.expression.end )}`; }
expression = usedContexts[0] === 'root' ? `root.${reference}` : reference; ` ];
generator.current.updateStatements.push( deindent` if ( node.else ) {
if ( ${snippet} && !${name} ) { ifTrue.push( deindent`
${name} = ${renderer}( component, ${generator.current.target}, ${name}_anchor ); if ( ${elseName } ) {
${elseName}.teardown();
${elseName} = null;
} }
` ); ` );
} else { }
expression = `${name}_value`;
generator.current.updateStatements.push( deindent` const ifFalse = [ deindent`
var ${expression} = ${snippet}; if ( ${name} ) {
${name}.teardown();
${name} = null;
}
` ];
if ( ${expression} && !${name} ) { if ( node.else ) {
${name} = ${renderer}( component, ${generator.current.target}, ${name}_anchor ); ifFalse.push( deindent`
if ( !${elseName } ) {
${elseName} = ${elseRenderer}( component, ${generator.current.target}, ${name}_anchor );
} }
` ); ` );
} }
generator.current.updateStatements.push( deindent` let update = deindent`
else if ( !${expression} && ${name} ) { if ( ${snippet} ) {
${name}.teardown(); ${ifTrue.join( '\n\n' )}
${name} = null;
} }
if ( ${name} ) { else {
${name}.update( ${generator.current.contextChain.join( ', ' )} ); ${ifFalse.join( '\n\n' )}
} }
` );
if ( ${name} ) ${name}.update( ${generator.current.contextChain.join( ', ' )} );
`;
if ( node.else ) {
update += `\nif ( ${elseName} ) ${elseName}.update( ${generator.current.contextChain.join( ', ' )} );`;
}
generator.current.updateStatements.push( update );
generator.current.teardownStatements.push( deindent` generator.current.teardownStatements.push( deindent`
if ( ${name} ) ${name}.teardown(); if ( ${name} ) ${name}.teardown();${node.else ? `\nif ( ${elseName} ) ${elseName}.teardown();` : ``}
${name}_anchor.parentNode.removeChild( ${name}_anchor ); ${name}_anchor.parentNode.removeChild( ${name}_anchor );
` ); ` );
generator.current = Object.assign( {}, generator.current, { generator.push({
useAnchor: true, useAnchor: true,
name: renderer, name: renderer,
target: 'target', target: 'target',
@ -67,14 +83,17 @@ export default {
updateStatements: [], updateStatements: [],
teardownStatements: [], teardownStatements: [],
counter: counter(), counter: counter()
parent: generator.current
}); });
}, },
leave ( generator ) { leave ( generator, node ) {
generator.addRenderer( generator.current ); generator.addRenderer( generator.current );
generator.current = generator.current.parent;
if ( node.else ) {
generator.visit( node.else );
}
generator.pop();
} }
}; };

@ -1,6 +1,7 @@
import Comment from './Comment.js'; import Comment from './Comment.js';
import EachBlock from './EachBlock.js'; import EachBlock from './EachBlock.js';
import Element from './Element.js'; import Element from './Element.js';
import ElseBlock from './ElseBlock.js';
import IfBlock from './IfBlock.js'; import IfBlock from './IfBlock.js';
import MustacheTag from './MustacheTag.js'; import MustacheTag from './MustacheTag.js';
import Text from './Text.js'; import Text from './Text.js';
@ -9,6 +10,7 @@ export default {
Comment, Comment,
EachBlock, EachBlock,
Element, Element,
ElseBlock,
IfBlock, IfBlock,
MustacheTag, MustacheTag,
Text Text

@ -12,9 +12,16 @@ export default function mustache ( parser ) {
// {{/if}} or {{/each}} // {{/if}} or {{/each}}
if ( parser.eat( '/' ) ) { if ( parser.eat( '/' ) ) {
const block = parser.current(); let block = parser.current();
let expected; let expected;
if ( block.type === 'ElseBlock' ) {
// TODO need to strip whitespace from else blocks
block.end = start;
parser.stack.pop();
block = parser.current();
}
if ( block.type === 'IfBlock' ) { if ( block.type === 'IfBlock' ) {
expected = 'if'; expected = 'if';
} else if ( block.type === 'EachBlock' ) { } else if ( block.type === 'EachBlock' ) {
@ -49,7 +56,26 @@ export default function mustache ( parser ) {
parser.stack.pop(); parser.stack.pop();
} }
// TODO {{else}} and {{elseif expression}} else if ( parser.eat( 'elseif' ) ) {
throw new Error( 'TODO elseif' );
}
else if ( parser.eat( 'else' ) ) {
const block = parser.current();
if ( block.type !== 'IfBlock' ) parser.error( 'Cannot have an {{else}} block outside an {{#if ...}} block' );
parser.allowWhitespace();
parser.eat( '}}', true );
block.else = {
start: parser.index,
end: null,
type: 'ElseBlock',
children: []
};
parser.stack.push( block.else );
}
// {{#if foo}} or {{#each foo}} // {{#if foo}} or {{#each foo}}
else if ( parser.eat( '#' ) ) { else if ( parser.eat( '#' ) ) {

@ -5,13 +5,13 @@ export default {
foo: true, foo: true,
bar: false bar: false
}, },
html: '<p>foo</p><!--#if foo--><p>not bar</p><!--#if bar-->', html: '<p>foo</p><!--#if foo-->\n\n<p>not bar</p><!--#if bar-->',
test ( component, target ) { test ( component, target ) {
component.set({ foo: false }); component.set({ foo: false });
assert.equal( target.innerHTML, '<p>not foo</p><!--#if foo--><p>not bar</p><!--#if bar-->' ); assert.equal( target.innerHTML, '<p>not foo</p><!--#if foo-->\n\n<p>not bar</p><!--#if bar-->' );
component.set({ bar: true }); component.set({ bar: true });
assert.equal( target.innerHTML, '<p>not foo</p><!--#if foo--><p>bar</p><!--#if bar-->' ); assert.equal( target.innerHTML, '<p>not foo</p><!--#if foo-->\n\n<p>bar</p><!--#if bar-->' );
component.set({ foo: true }); component.set({ foo: true });
assert.equal( target.innerHTML, '<p>foo</p><!--#if foo--><p>bar</p><!--#if bar-->' ); assert.equal( target.innerHTML, '<p>foo</p><!--#if foo-->\n\n<p>bar</p><!--#if bar-->' );
} }
}; };

Loading…
Cancel
Save