fix(compiler): always wrap views into an own `<template>` element
This is needed to allow view instantiation also in browsers that don’t support the `<template>` element and because of this would return elements from the content of `<template>` elements when using `element.querySelectorAll`. Also stores the `elementBinder.nestedProtoView` correctly when a template directive was used on a `<template>` element.
This commit is contained in:
parent
95d86d151a
commit
63053438ea
|
@ -1,5 +1,6 @@
|
|||
import {isBlank} from 'facade/lang';
|
||||
import {List, ListWrapper} from 'facade/collection';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {DOM, Element} from 'facade/dom';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileStep} from './compile_step';
|
||||
|
||||
|
@ -13,12 +14,14 @@ export class CompileControl {
|
|||
_parent:CompileElement;
|
||||
_current:CompileElement;
|
||||
_results;
|
||||
_additionalChildren;
|
||||
constructor(steps) {
|
||||
this._steps = steps;
|
||||
this._currentStepIndex = 0;
|
||||
this._parent = null;
|
||||
this._current = null;
|
||||
this._results = null;
|
||||
this._additionalChildren = null;
|
||||
}
|
||||
|
||||
// only public so that it can be used by compile_pipeline
|
||||
|
@ -39,15 +42,21 @@ export class CompileControl {
|
|||
|
||||
this._currentStepIndex = previousStepIndex;
|
||||
this._parent = previousParent;
|
||||
|
||||
var localAdditionalChildren = this._additionalChildren;
|
||||
this._additionalChildren = null;
|
||||
return localAdditionalChildren;
|
||||
}
|
||||
|
||||
addParent(newElement:CompileElement) {
|
||||
var currEl = this._current.element;
|
||||
var newEl = newElement.element;
|
||||
DOM.parentElement(currEl).insertBefore(newEl, currEl);
|
||||
DOM.appendChild(newEl, currEl);
|
||||
|
||||
this.internalProcess(this._results, this._currentStepIndex+1, this._parent, newElement);
|
||||
this._parent = newElement;
|
||||
}
|
||||
|
||||
addChild(element:CompileElement) {
|
||||
if (isBlank(this._additionalChildren)) {
|
||||
this._additionalChildren = ListWrapper.create();
|
||||
}
|
||||
ListWrapper.push(this._additionalChildren, element);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import {isPresent} from 'facade/lang';
|
||||
import {List, ListWrapper} from 'facade/collection';
|
||||
import {Element, Node, DOM} from 'facade/dom';
|
||||
import {CompileElement} from './compile_element';
|
||||
|
@ -17,18 +18,24 @@ export class CompilePipeline {
|
|||
|
||||
process(rootElement:Element):List {
|
||||
var results = ListWrapper.create();
|
||||
this._process(results, null, rootElement);
|
||||
this._process(results, null, new CompileElement(rootElement));
|
||||
return results;
|
||||
}
|
||||
|
||||
_process(results, parent:CompileElement, element:Element) {
|
||||
var current = new CompileElement(element);
|
||||
this._control.internalProcess(results, 0, parent, current);
|
||||
var childNodes = DOM.templateAwareRoot(element).childNodes;
|
||||
_process(results, parent:CompileElement, current:CompileElement) {
|
||||
var additionalChildren = this._control.internalProcess(results, 0, parent, current);
|
||||
|
||||
var childNodes = DOM.templateAwareRoot(current.element).childNodes;
|
||||
for (var i=0; i<childNodes.length; i++) {
|
||||
var node = childNodes[i];
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
this._process(results, current, node);
|
||||
this._process(results, current, new CompileElement(node));
|
||||
}
|
||||
}
|
||||
|
||||
if (isPresent(additionalChildren)) {
|
||||
for (var i=0; i<additionalChildren.length; i++) {
|
||||
this._process(results, current, additionalChildren[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {isBlank, isPresent} from 'facade/lang';
|
||||
import {DOM, TemplateElement} from 'facade/dom';
|
||||
import {MapWrapper, StringMapWrapper} from 'facade/collection';
|
||||
import {MapWrapper, ListWrapper} from 'facade/collection';
|
||||
|
||||
import {Parser} from 'change_detection/parser/parser';
|
||||
|
||||
|
@ -9,9 +9,20 @@ import {CompileElement} from './compile_element';
|
|||
import {CompileControl} from './compile_control';
|
||||
|
||||
/**
|
||||
* Splits views at template directives:
|
||||
* Replaces the element with an empty <template> element that contains the
|
||||
* template directive and all property bindings needed for the template directive.
|
||||
* Splits views at `<template>` elements or elements with `template` attribute:
|
||||
* For `<template>` elements:
|
||||
* - moves the content into a new and disconnected `<template>` element
|
||||
* that is marked as view root.
|
||||
*
|
||||
* For elements with a `template` attribute:
|
||||
* - replaces the element with an empty `<template>` element,
|
||||
* parses the content of the `template` attribute and adds the information to that
|
||||
* `<template>` element. Marks the elements as view root.
|
||||
*
|
||||
* Note: In both cases the root of the nested view is disconnected from its parent element.
|
||||
* This is needed for browsers that don't support the `<template>` element
|
||||
* as we want to do locate elements with bindings using `getElementsByClassName` later on,
|
||||
* which should not descend into the nested view.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#isViewRoot
|
||||
|
@ -26,20 +37,42 @@ export class ViewSplitter extends CompileStep {
|
|||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var element = current.element;
|
||||
if (isBlank(parent) || (current.element instanceof TemplateElement)) {
|
||||
if (isBlank(parent)) {
|
||||
current.isViewRoot = true;
|
||||
} else {
|
||||
var templateBindings = MapWrapper.get(current.attrs(), 'template');
|
||||
if (isPresent(templateBindings)) {
|
||||
current.isViewRoot = true;
|
||||
var templateElement = DOM.createTemplate('');
|
||||
var newParentElement = new CompileElement(templateElement);
|
||||
this._parseTemplateBindings(templateBindings, newParentElement);
|
||||
control.addParent(newParentElement);
|
||||
if (current.element instanceof TemplateElement) {
|
||||
if (!current.isViewRoot) {
|
||||
var viewRoot = new CompileElement(DOM.createTemplate(''));
|
||||
this._moveChildNodes(current.element.content, viewRoot.element.content);
|
||||
viewRoot.isViewRoot = true;
|
||||
control.addChild(viewRoot);
|
||||
}
|
||||
} else {
|
||||
var templateBindings = MapWrapper.get(current.attrs(), 'template');
|
||||
if (isPresent(templateBindings)) {
|
||||
var newParent = new CompileElement(DOM.createTemplate(''));
|
||||
current.isViewRoot = true;
|
||||
this._parseTemplateBindings(templateBindings, newParent);
|
||||
this._addParentElement(current.element, newParent.element);
|
||||
|
||||
control.addParent(newParent);
|
||||
current.element.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_moveChildNodes(source, target) {
|
||||
while (isPresent(source.firstChild)) {
|
||||
DOM.appendChild(target, source.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
_addParentElement(currentElement, newParentElement) {
|
||||
DOM.parentElement(currentElement).insertBefore(newParentElement, currentElement);
|
||||
DOM.appendChild(newParentElement, currentElement);
|
||||
}
|
||||
|
||||
_parseTemplateBindings(templateBindings:string, compileElement:CompileElement) {
|
||||
var bindings = this._parser.parseTemplateBindings(templateBindings);
|
||||
for (var i=0; i<bindings.length; i++) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {describe, beforeEach, it, expect, iit, ddescribe} from 'test_lib/test_lib';
|
||||
import {ListWrapper, List} from 'facade/collection';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {isPresent, NumberWrapper} from 'facade/lang';
|
||||
import {isPresent, NumberWrapper, StringWrapper} from 'facade/lang';
|
||||
|
||||
import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
|
||||
import {CompileElement} from 'core/compiler/pipeline/compile_element';
|
||||
|
@ -21,16 +21,6 @@ export function main() {
|
|||
});
|
||||
|
||||
describe('control.addParent', () => {
|
||||
it('should wrap the underlying DOM element', () => {
|
||||
var element = createElement('<div id="1"><span wrap0="1" id="2"><b id="3"></b></span></div>');
|
||||
var pipeline = new CompilePipeline([
|
||||
createWrapperStep('wrap0', [])
|
||||
]);
|
||||
pipeline.process(element);
|
||||
|
||||
expect(DOM.getOuterHTML(element)).toEqual('<div id="1"><a id="wrap0#0"><span wrap0="1" id="2"><b id="3"></b></span></a></div>');
|
||||
});
|
||||
|
||||
it('should report the new parent to the following processor and the result', () => {
|
||||
var element = createElement('<div id="1"><span wrap0="1" id="2"><b id="3"></b></span></div>');
|
||||
var step0Log = [];
|
||||
|
@ -95,6 +85,26 @@ export function main() {
|
|||
|
||||
});
|
||||
|
||||
describe('control.addChild', () => {
|
||||
it('should report the new child to all processors and the result', () => {
|
||||
var element = createElement('<div id="1"><div id="2"></div></div>');
|
||||
var resultLog = [];
|
||||
var newChild = new CompileElement(createElement('<div id="3"></div>'));
|
||||
var pipeline = new CompilePipeline([
|
||||
new MockStep((parent, current, control) => {
|
||||
if (StringWrapper.equals(current.element.id, '1')) {
|
||||
control.addChild(newChild);
|
||||
}
|
||||
}),
|
||||
createLoggerStep(resultLog)
|
||||
]);
|
||||
var result = pipeline.process(element);
|
||||
expect(result[2]).toBe(newChild);
|
||||
expect(resultLog).toEqual(['1', '1<2', '1<3']);
|
||||
expect(resultIdLog(result)).toEqual(['1', '2', '3']);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -22,21 +22,35 @@ export function main() {
|
|||
expect(results[0].isViewRoot).toBe(true);
|
||||
});
|
||||
|
||||
it('should mark <template> elements as viewRoot', () => {
|
||||
var rootElement = createElement('<div><template></template></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[1].isViewRoot).toBe(true);
|
||||
describe('<template> elements', () => {
|
||||
|
||||
it('should move the content into a new <template> element and mark that as viewRoot', () => {
|
||||
var rootElement = createElement('<div><template if="true">a</template></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(DOM.getOuterHTML(results[1].element)).toEqual('<template if="true"></template>');
|
||||
expect(results[1].isViewRoot).toBe(false);
|
||||
expect(DOM.getOuterHTML(results[2].element)).toEqual('<template>a</template>');
|
||||
expect(results[2].isViewRoot).toBe(true);
|
||||
});
|
||||
|
||||
it('should not wrap a root <template> element', () => {
|
||||
var rootElement = createElement('<div></div>');
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results.length).toBe(1);
|
||||
expect(DOM.getOuterHTML(rootElement)).toEqual('<div></div>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('elements with template attribute', () => {
|
||||
|
||||
it('should insert an empty <template> element', () => {
|
||||
var rootElement = createElement('<div><div template></div></div>');
|
||||
it('should replace the element with an empty <template> element', () => {
|
||||
var rootElement = createElement('<div><span template=""></span></div>');
|
||||
var originalChild = rootElement.childNodes[0];
|
||||
var results = createPipeline().process(rootElement);
|
||||
expect(results[0].element).toBe(rootElement);
|
||||
expect(results[1].element instanceof TemplateElement).toBe(true);
|
||||
expect(DOM.getInnerHTML(results[1].element)).toEqual('');
|
||||
expect(DOM.getOuterHTML(results[0].element)).toEqual('<div><template></template></div>');
|
||||
expect(DOM.getOuterHTML(results[2].element)).toEqual('<span template=""></span>')
|
||||
expect(results[2].element).toBe(originalChild);
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue