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: {},
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,
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 = {};
@ -161,7 +191,7 @@ export default function generate ( parsed, source, options = {} ) {
});
}
generator.current = {
generator.push({
useAnchor: false,
name: 'renderMainFragment',
namespace: null,
@ -179,27 +209,12 @@ export default function generate ( parsed, source, options = {} ) {
indexNames: {},
listNames: {},
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 );
counter: counter()
});
generator.addRenderer( generator.current );
parsed.html.children.forEach( generator.visit );
generator.addRenderer( generator.pop() );
const setStatements = [ deindent`
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 isReference from '../utils/isReference.js';
import counter from '../utils/counter.js';
export default {
@ -8,57 +7,74 @@ export default {
const name = `ifBlock_${i}`;
const renderer = `renderIfBlock_${i}`;
const elseName = `elseBlock_${i}`;
const elseRenderer = `renderElseBlock_${i}`;
generator.current.initStatements.push( deindent`
var ${name}_anchor = document.createComment( ${JSON.stringify( `#if ${generator.source.slice( node.expression.start, node.expression.end )}` )} );
${generator.current.target}.appendChild( ${name}_anchor );
var ${name} = null;
var ${name} = null;${node.else ? `\nvar ${elseName} = null;` : ``}
` );
generator.addSourcemapLocations( node.expression );
generator.contextualise( node.expression );
const usedContexts = generator.contextualise( node.expression );
const snippet = `[✂${node.expression.start}-${node.expression.end}✂]`;
let expression;
if ( isReference( node.expression ) ) {
const reference = `${generator.source.slice( node.expression.start, node.expression.end )}`;
expression = usedContexts[0] === 'root' ? `root.${reference}` : reference;
const ifTrue = [ deindent`
if ( !${name } ) {
${name} = ${renderer}( component, ${generator.current.target}, ${name}_anchor );
}
` ];
generator.current.updateStatements.push( deindent`
if ( ${snippet} && !${name} ) {
${name} = ${renderer}( component, ${generator.current.target}, ${name}_anchor );
if ( node.else ) {
ifTrue.push( deindent`
if ( ${elseName } ) {
${elseName}.teardown();
${elseName} = null;
}
` );
} else {
expression = `${name}_value`;
}
generator.current.updateStatements.push( deindent`
var ${expression} = ${snippet};
const ifFalse = [ deindent`
if ( ${name} ) {
${name}.teardown();
${name} = null;
}
` ];
if ( ${expression} && !${name} ) {
${name} = ${renderer}( component, ${generator.current.target}, ${name}_anchor );
if ( node.else ) {
ifFalse.push( deindent`
if ( !${elseName } ) {
${elseName} = ${elseRenderer}( component, ${generator.current.target}, ${name}_anchor );
}
` );
}
generator.current.updateStatements.push( deindent`
else if ( !${expression} && ${name} ) {
${name}.teardown();
${name} = null;
let update = deindent`
if ( ${snippet} ) {
${ifTrue.join( '\n\n' )}
}
if ( ${name} ) {
${name}.update( ${generator.current.contextChain.join( ', ' )} );
else {
${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`
if ( ${name} ) ${name}.teardown();
if ( ${name} ) ${name}.teardown();${node.else ? `\nif ( ${elseName} ) ${elseName}.teardown();` : ``}
${name}_anchor.parentNode.removeChild( ${name}_anchor );
` );
generator.current = Object.assign( {}, generator.current, {
generator.push({
useAnchor: true,
name: renderer,
target: 'target',
@ -67,14 +83,17 @@ export default {
updateStatements: [],
teardownStatements: [],
counter: counter(),
parent: generator.current
counter: counter()
});
},
leave ( generator ) {
leave ( generator, node ) {
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 EachBlock from './EachBlock.js';
import Element from './Element.js';
import ElseBlock from './ElseBlock.js';
import IfBlock from './IfBlock.js';
import MustacheTag from './MustacheTag.js';
import Text from './Text.js';
@ -9,6 +10,7 @@ export default {
Comment,
EachBlock,
Element,
ElseBlock,
IfBlock,
MustacheTag,
Text

@ -12,9 +12,16 @@ export default function mustache ( parser ) {
// {{/if}} or {{/each}}
if ( parser.eat( '/' ) ) {
const block = parser.current();
let block = parser.current();
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' ) {
expected = 'if';
} else if ( block.type === 'EachBlock' ) {
@ -49,7 +56,26 @@ export default function mustache ( parser ) {
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}}
else if ( parser.eat( '#' ) ) {

@ -5,13 +5,13 @@ export default {
foo: true,
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 ) {
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 });
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 });
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