feat(template): add bang syntax shortcut

Closes #522
This commit is contained in:
Bertrand Laporte 2015-02-04 14:35:10 -08:00 committed by Alex Eagle
parent cdb1e82216
commit 3395624cb3
4 changed files with 105 additions and 3 deletions

View File

@ -1,4 +1,4 @@
import {isBlank, isPresent} from 'facade/src/lang'; import {isBlank, isPresent, BaseException} from 'facade/src/lang';
import {DOM, TemplateElement} from 'facade/src/dom'; import {DOM, TemplateElement} from 'facade/src/dom';
import {MapWrapper, ListWrapper} from 'facade/src/collection'; import {MapWrapper, ListWrapper} from 'facade/src/collection';
@ -7,6 +7,9 @@ import {Parser} from 'change_detection/change_detection';
import {CompileStep} from './compile_step'; import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control'; import {CompileControl} from './compile_control';
import {StringWrapper} from 'facade/src/lang';
import {$BANG} from 'change_detection/src/parser/lexer';
/** /**
* Splits views at `<template>` elements or elements with `template` attribute: * Splits views at `<template>` elements or elements with `template` attribute:
@ -51,8 +54,26 @@ export class ViewSplitter extends CompileStep {
control.addChild(viewRoot); control.addChild(viewRoot);
} }
} else { } else {
var templateBindings = MapWrapper.get(current.attrs(), 'template'); var attrs = current.attrs();
if (isPresent(templateBindings)) { var templateBindings = MapWrapper.get(attrs, 'template');
var hasTemplateBinding = isPresent(templateBindings);
// look for template shortcuts such as !if="condition" and treat them as template="if condition"
MapWrapper.forEach(attrs, (attrValue, attrName) => {
if (StringWrapper.charCodeAt(attrName, 0) == $BANG) {
var key = StringWrapper.substring(attrName, 1); // remove the bang
if (hasTemplateBinding) {
// 2nd template binding detected
throw new BaseException(`Only one template directive per element is allowed: ` +
`${templateBindings} and ${key} cannot be used simultaneously!`);
} else {
templateBindings = (attrValue.length == 0) ? key : key + ' ' + attrValue;
hasTemplateBinding = true;
}
}
});
if (hasTemplateBinding) {
var newParent = new CompileElement(DOM.createTemplate('')); var newParent = new CompileElement(DOM.createTemplate(''));
current.isViewRoot = true; current.isViewRoot = true;
this._parseTemplateBindings(templateBindings, newParent); this._parseTemplateBindings(templateBindings, newParent);

View File

@ -100,5 +100,78 @@ export function main() {
}); });
describe('elements with !directive_name attribute', () => {
it('should replace the element with an empty <template> element', () => {
var rootElement = el('<div><span !if></span></div>');
var originalChild = rootElement.childNodes[0];
var results = createPipeline().process(rootElement);
expect(results[0].element).toBe(rootElement);
expect(DOM.getOuterHTML(results[0].element)).toEqual('<div><template if=""></template></div>');
expect(DOM.getOuterHTML(results[2].element)).toEqual('<span !if=""></span>')
expect(results[2].element).toBe(originalChild);
});
it('should mark the element as viewRoot', () => {
var rootElement = el('<div><div !foo="bar"></div></div>');
var results = createPipeline().process(rootElement);
expect(results[2].isViewRoot).toBe(true);
});
it('should work with top-level template node', () => {
var rootElement = DOM.createTemplate('<div !foo>x</div>');
var originalChild = rootElement.content.childNodes[0];
var results = createPipeline().process(rootElement);
expect(results[0].element).toBe(rootElement);
expect(results[0].isViewRoot).toBe(true);
expect(results[2].isViewRoot).toBe(true);
expect(DOM.getOuterHTML(results[0].element)).toEqual('<template><template foo=""></template></template>');
expect(results[2].element).toBe(originalChild);
});
it('should add property bindings from the template attribute', () => {
var rootElement = el('<div><div !prop="expr"></div></div>');
var results = createPipeline().process(rootElement);
expect(MapWrapper.get(results[1].propertyBindings, 'prop').source).toEqual('expr');
});
it('should add variable mappings from the template attribute', () => {
var rootElement = el('<div><div !foreach="var varName=mapName"></div></div>');
var results = createPipeline().process(rootElement);
expect(results[1].variableBindings).toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
});
it('should add entries without value as attribute to the element', () => {
var rootElement = el('<div><div !varname></div></div>');
var results = createPipeline().process(rootElement);
expect(results[1].attrs()).toEqual(MapWrapper.createFromStringMap({'varname': ''}));
expect(results[1].propertyBindings).toBe(null);
expect(results[1].variableBindings).toBe(null);
});
it('should iterate properly after a template dom modification', () => {
var rootElement = el('<div><div !foo></div><after></after></div>');
var results = createPipeline().process(rootElement);
// 1 root + 2 initial + 1 generated template elements
expect(results.length).toEqual(4);
});
it('should not allow multiple template directives on the same element', () => {
expect( () => {
var rootElement = el('<div><div !foo !bar="blah"></div></div>');
createPipeline().process(rootElement);
}).toThrowError('Only one template directive per element is allowed: foo and bar cannot be used simultaneously!');
});
it('should not allow template and bang directives on the same element', () => {
expect( () => {
var rootElement = el('<div><div !foo template="blah"></div></div>');
createPipeline().process(rootElement);
}).toThrowError('Only one template directive per element is allowed: blah and foo cannot be used simultaneously!');
});
});
}); });
} }

View File

@ -61,6 +61,10 @@ class StringWrapper {
static String replaceAll(String s, RegExp from, String replace) { static String replaceAll(String s, RegExp from, String replace) {
return s.replaceAll(from, replace); return s.replaceAll(from, replace);
} }
static String substring(String s, int start, [int end]) {
return s.substring(start, end);
}
} }
class StringJoiner { class StringJoiner {

View File

@ -67,6 +67,10 @@ export class StringWrapper {
static replaceAll(s:string, from:RegExp, replace:string):string { static replaceAll(s:string, from:RegExp, replace:string):string {
return s.replace(from.multiple, replace); return s.replace(from.multiple, replace);
} }
static substring(s:string, start:int, end:int = undefined) {
return s.substring(start, end);
}
} }
export class StringJoiner { export class StringJoiner {