refactor(compiler): add control.ignoreCurrentElement() to skip the current element

relates to #808
This commit is contained in:
Victor Berchet 2015-04-10 16:12:37 +02:00 committed by Misko Hevery
parent f0477e164a
commit 678d541da7
8 changed files with 60 additions and 52 deletions

View File

@ -13,6 +13,8 @@ export class CompileControl {
_parent:CompileElement; _parent:CompileElement;
_results; _results;
_additionalChildren; _additionalChildren;
_ignoreCurrentElement: boolean;
constructor(steps) { constructor(steps) {
this._steps = steps; this._steps = steps;
this._currentStepIndex = 0; this._currentStepIndex = 0;
@ -27,14 +29,21 @@ export class CompileControl {
var previousStepIndex = this._currentStepIndex; var previousStepIndex = this._currentStepIndex;
var previousParent = this._parent; var previousParent = this._parent;
for (var i=startStepIndex; i<this._steps.length; i++) { this._ignoreCurrentElement = false;
for (var i = startStepIndex;
i < this._steps.length && !this._ignoreCurrentElement;
i++) {
var step = this._steps[i]; var step = this._steps[i];
this._parent = parent; this._parent = parent;
this._currentStepIndex = i; this._currentStepIndex = i;
step.process(parent, current, this); step.process(parent, current, this);
parent = this._parent; parent = this._parent;
} }
if (!this._ignoreCurrentElement) {
ListWrapper.push(results, current); ListWrapper.push(results, current);
}
this._currentStepIndex = previousStepIndex; this._currentStepIndex = previousStepIndex;
this._parent = previousParent; this._parent = previousParent;
@ -55,4 +64,14 @@ export class CompileControl {
} }
ListWrapper.push(this._additionalChildren, element); ListWrapper.push(this._additionalChildren, element);
} }
/**
* Ignores the current element.
*
* When a step call [ignoreCurrentElement], no further steps are executed on the current
* element and no [CompileElement] is added to the result list.
*/
ignoreCurrentElement() {
this._ignoreCurrentElement = true;
}
} }

View File

@ -18,7 +18,6 @@ export class CompileElement {
distanceToInheritedBinder:number; distanceToInheritedBinder:number;
inheritedElementBinder:ElementBinderBuilder; inheritedElementBinder:ElementBinderBuilder;
compileChildren: boolean; compileChildren: boolean;
ignoreBindings: boolean;
elementDescription: string; // e.g. '<div [class]="foo">' : used to provide context in case of error elementDescription: string; // e.g. '<div [class]="foo">' : used to provide context in case of error
constructor(element, compilationUnit = '') { constructor(element, compilationUnit = '') {
@ -34,8 +33,6 @@ export class CompileElement {
this.inheritedElementBinder = null; this.inheritedElementBinder = null;
this.distanceToInheritedBinder = 0; this.distanceToInheritedBinder = 0;
this.compileChildren = true; this.compileChildren = true;
// set to true to ignore all the bindings on the element
this.ignoreBindings = false;
// description is calculated here as compilation steps may change the element // description is calculated here as compilation steps may change the element
var tplDesc = assertionsEnabled()? getElementDescription(element) : null; var tplDesc = assertionsEnabled()? getElementDescription(element) : null;
if (compilationUnit !== '') { if (compilationUnit !== '') {

View File

@ -30,10 +30,6 @@ export class PropertyBindingParser extends CompileStep {
} }
process(parent:CompileElement, current:CompileElement, control:CompileControl) { process(parent:CompileElement, current:CompileElement, control:CompileControl) {
if (current.ignoreBindings) {
return;
}
var attrs = current.attrs(); var attrs = current.attrs();
var newAttrs = MapWrapper.create(); var newAttrs = MapWrapper.create();

View File

@ -19,7 +19,7 @@ export class TextInterpolationParser extends CompileStep {
} }
process(parent:CompileElement, current:CompileElement, control:CompileControl) { process(parent:CompileElement, current:CompileElement, control:CompileControl) {
if (!current.compileChildren || current.ignoreBindings) { if (!current.compileChildren) {
return; return;
} }
var element = current.element; var element = current.element;

View File

@ -23,12 +23,9 @@ export class ShadowDomCompileStep extends CompileStep {
} }
process(parent:CompileElement, current:CompileElement, control:CompileControl) { process(parent:CompileElement, current:CompileElement, control:CompileControl) {
if (current.ignoreBindings) {
return;
}
var tagName = DOM.tagName(current.element).toUpperCase(); var tagName = DOM.tagName(current.element).toUpperCase();
if (tagName == 'STYLE') { if (tagName == 'STYLE') {
this._processStyleElement(current); this._processStyleElement(current, control);
} else if (tagName == 'CONTENT') { } else if (tagName == 'CONTENT') {
this._processContentElement(current); this._processContentElement(current);
} else { } else {
@ -39,17 +36,20 @@ export class ShadowDomCompileStep extends CompileStep {
} }
} }
_processStyleElement(current) { _processStyleElement(current:CompileElement, control:CompileControl) {
current.ignoreBindings = true;
var stylePromise = this._shadowDomStrategy.processStyleElement( var stylePromise = this._shadowDomStrategy.processStyleElement(
this._template.componentId, this._template.absUrl, current.element this._template.componentId, this._template.absUrl, current.element
); );
if (isPresent(stylePromise) && PromiseWrapper.isPromise(stylePromise)) { if (isPresent(stylePromise) && PromiseWrapper.isPromise(stylePromise)) {
ListWrapper.push(this._subTaskPromises, stylePromise); ListWrapper.push(this._subTaskPromises, stylePromise);
} }
// Style elements should not be further processed by the compiler, as they can not contain
// bindings. Skipping further compiler steps allow speeding up the compilation process.
control.ignoreCurrentElement();
} }
_processContentElement(current) { _processContentElement(current:CompileElement) {
if (this._shadowDomStrategy.hasNativeContentElement()) { if (this._shadowDomStrategy.hasNativeContentElement()) {
return; return;
} }

View File

@ -80,6 +80,19 @@ export function main() {
expect(results[3].inheritedElementBinder.parent).toBe(results[0].inheritedElementBinder); expect(results[3].inheritedElementBinder.parent).toBe(results[0].inheritedElementBinder);
}); });
it('should not execute further steps when ignoreCurrentElement has been called', () => {
var element = el('<div id="1"><span id="2" ignore-current></span><span id="3"></span></div>');
var logs = [];
var pipeline = new CompilePipeline([
new IgnoreCurrentElementStep(),
createLoggerStep(logs),
]);
var results = pipeline.process(element);
expect(results.length).toBe(2);
expect(logs).toEqual(['1', '1<3'])
});
describe('control.addParent', () => { describe('control.addParent', () => {
it('should report the new parent to the following processor and the result', () => { it('should report the new parent to the following processor and the result', () => {
var element = el('<div id="1"><span wrap0="1" id="2"><b id="3"></b></span></div>'); var element = el('<div id="1"><span wrap0="1" id="2"><b id="3"></b></span></div>');
@ -188,6 +201,15 @@ export class IgnoreChildrenStep extends CompileStep {
} }
} }
class IgnoreCurrentElementStep extends CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var attributeMap = DOM.attributeMap(current.element);
if (MapWrapper.contains(attributeMap, 'ignore-current')) {
control.ignoreCurrentElement();
}
}
}
function logEntry(log, parent, current) { function logEntry(log, parent, current) {
var parentId = ''; var parentId = '';
if (isPresent(parent)) { if (isPresent(parent)) {

View File

@ -11,10 +11,9 @@ var EMPTY_MAP = MapWrapper.create();
export function main() { export function main() {
describe('PropertyBindingParser', () => { describe('PropertyBindingParser', () => {
function createPipeline(ignoreBindings = false, hasNestedProtoView = false) { function createPipeline(hasNestedProtoView = false) {
return new CompilePipeline([ return new CompilePipeline([
new MockStep((parent, current, control) => { new MockStep((parent, current, control) => {
current.ignoreBindings = ignoreBindings;
if (hasNestedProtoView) { if (hasNestedProtoView) {
current.bindElement().bindNestedProtoView(el('<template></template>')); current.bindElement().bindNestedProtoView(el('<template></template>'));
} }
@ -22,18 +21,13 @@ export function main() {
new PropertyBindingParser(new Parser(new Lexer()))]); new PropertyBindingParser(new Parser(new Lexer()))]);
} }
function process(element, ignoreBindings = false, hasNestedProtoView = false) { function process(element, hasNestedProtoView = false) {
return ListWrapper.map( return ListWrapper.map(
createPipeline(ignoreBindings, hasNestedProtoView).process(element), createPipeline(hasNestedProtoView).process(element),
(compileElement) => compileElement.inheritedElementBinder (compileElement) => compileElement.inheritedElementBinder
); );
} }
it('should not parse bindings when ignoreBindings is true', () => {
var results = process(el('<div [a]="b"></div>'), true);
expect(results[0]).toEqual(null);
});
it('should detect [] syntax', () => { it('should detect [] syntax', () => {
var results = process(el('<div [a]="b"></div>')); var results = process(el('<div [a]="b"></div>'));
expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('b'); expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('b');
@ -64,13 +58,13 @@ export function main() {
}); });
it('should store variable binding for a template element on the nestedProtoView', () => { it('should store variable binding for a template element on the nestedProtoView', () => {
var results = process(el('<template var-george="washington"></p>'), false, true); var results = process(el('<template var-george="washington"></p>'), true);
expect(results[0].variableBindings).toEqual(EMPTY_MAP); expect(results[0].variableBindings).toEqual(EMPTY_MAP);
expect(MapWrapper.get(results[0].nestedProtoView.variableBindings, 'washington')).toEqual('george'); expect(MapWrapper.get(results[0].nestedProtoView.variableBindings, 'washington')).toEqual('george');
}); });
it('should store variable binding for a non-template element using shorthand syntax on the nestedProtoView', () => { it('should store variable binding for a non-template element using shorthand syntax on the nestedProtoView', () => {
var results = process(el('<template #george="washington"></template>'), false, true); var results = process(el('<template #george="washington"></template>'), true);
expect(results[0].variableBindings).toEqual(EMPTY_MAP); expect(results[0].variableBindings).toEqual(EMPTY_MAP);
expect(MapWrapper.get(results[0].nestedProtoView.variableBindings, 'washington')).toEqual('george'); expect(MapWrapper.get(results[0].nestedProtoView.variableBindings, 'washington')).toEqual('george');
}); });

View File

@ -3,24 +3,20 @@ import {TextInterpolationParser} from 'angular2/src/render/dom/compiler/text_int
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline'; import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {Lexer, Parser} from 'angular2/change_detection'; import {Lexer, Parser} from 'angular2/change_detection';
import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element';
import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step'
import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control';
import {IgnoreChildrenStep} from './pipeline_spec'; import {IgnoreChildrenStep} from './pipeline_spec';
export function main() { export function main() {
describe('TextInterpolationParser', () => { describe('TextInterpolationParser', () => {
function createPipeline(ignoreBindings = false) { function createPipeline() {
return new CompilePipeline([ return new CompilePipeline([
new MockStep((parent, current, control) => { current.ignoreBindings = ignoreBindings; }),
new IgnoreChildrenStep(), new IgnoreChildrenStep(),
new TextInterpolationParser(new Parser(new Lexer())) new TextInterpolationParser(new Parser(new Lexer()))
]); ]);
} }
function process(element, ignoreBindings = false) { function process(element) {
return ListWrapper.map( return ListWrapper.map(
createPipeline(ignoreBindings).process(element), createPipeline().process(element),
(compileElement) => compileElement.inheritedElementBinder (compileElement) => compileElement.inheritedElementBinder
); );
} }
@ -30,11 +26,6 @@ export function main() {
expect(elementBinder.textBindingIndices[bindingIndex]).toEqual(nodeIndex); expect(elementBinder.textBindingIndices[bindingIndex]).toEqual(nodeIndex);
} }
it('should not look for text interpolation when ignoreBindings is true', () => {
var results = process(el('<div>{{expr1}}<span></span>{{expr2}}</div>'), true);
expect(results[0]).toEqual(null);
});
it('should find text interpolation in normal elements', () => { it('should find text interpolation in normal elements', () => {
var result = process(el('<div>{{expr1}}<span></span>{{expr2}}</div>'))[0]; var result = process(el('<div>{{expr1}}<span></span>{{expr2}}</div>'))[0];
assertTextBinding(result, 0, 0, "{{expr1}}"); assertTextBinding(result, 0, 0, "{{expr1}}");
@ -69,14 +60,3 @@ export function main() {
}); });
}); });
} }
class MockStep extends CompileStep {
processClosure:Function;
constructor(process) {
super();
this.processClosure = process;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
this.processClosure(parent, current, control);
}
}