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:
Tobias Bosch
2014-12-01 15:18:55 -08:00
parent 95d86d151a
commit 63053438ea
5 changed files with 116 additions and 43 deletions

View File

@ -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);
}
}

View File

@ -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]);
}
}
}

View File

@ -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++) {