feature(ShadowDomTransformer): create a compiler step to transform the shadow DOM
This commit is contained in:
@ -120,7 +120,6 @@ export class Compiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_compileTemplate(template: Element, cmpMetadata): Promise<ProtoView> {
|
_compileTemplate(template: Element, cmpMetadata): Promise<ProtoView> {
|
||||||
this._shadowDomStrategy.processTemplate(template, cmpMetadata);
|
|
||||||
var pipeline = new CompilePipeline(this.createSteps(cmpMetadata));
|
var pipeline = new CompilePipeline(this.createSteps(cmpMetadata));
|
||||||
var compileElements = pipeline.process(template);
|
var compileElements = pipeline.process(template);
|
||||||
var protoView = compileElements[0].inheritedProtoView;
|
var protoView = compileElements[0].inheritedProtoView;
|
||||||
|
@ -39,6 +39,8 @@ export class CompileElement {
|
|||||||
inheritedElementBinder:ElementBinder;
|
inheritedElementBinder:ElementBinder;
|
||||||
distanceToParentInjector:number;
|
distanceToParentInjector:number;
|
||||||
compileChildren: boolean;
|
compileChildren: boolean;
|
||||||
|
ignoreBindings: boolean;
|
||||||
|
|
||||||
constructor(element:Element) {
|
constructor(element:Element) {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this._attrs = null;
|
this._attrs = null;
|
||||||
@ -64,6 +66,8 @@ export class CompileElement {
|
|||||||
this.inheritedElementBinder = null;
|
this.inheritedElementBinder = null;
|
||||||
this.distanceToParentInjector = 0;
|
this.distanceToParentInjector = 0;
|
||||||
this.compileChildren = true;
|
this.compileChildren = true;
|
||||||
|
// set to true to ignore all the bindings on the element
|
||||||
|
this.ignoreBindings = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshAttrs() {
|
refreshAttrs() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {ChangeDetection, Parser} from 'angular2/change_detection';
|
import {ChangeDetection, Parser} from 'angular2/change_detection';
|
||||||
import {List} from 'angular2/src/facade/collection';
|
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {PropertyBindingParser} from './property_binding_parser';
|
import {PropertyBindingParser} from './property_binding_parser';
|
||||||
import {TextInterpolationParser} from './text_interpolation_parser';
|
import {TextInterpolationParser} from './text_interpolation_parser';
|
||||||
@ -9,9 +9,11 @@ import {ElementBindingMarker} from './element_binding_marker';
|
|||||||
import {ProtoViewBuilder} from './proto_view_builder';
|
import {ProtoViewBuilder} from './proto_view_builder';
|
||||||
import {ProtoElementInjectorBuilder} from './proto_element_injector_builder';
|
import {ProtoElementInjectorBuilder} from './proto_element_injector_builder';
|
||||||
import {ElementBinderBuilder} from './element_binder_builder';
|
import {ElementBinderBuilder} from './element_binder_builder';
|
||||||
|
import {ShadowDomTransformer} from './shadow_dom_transformer';
|
||||||
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
||||||
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||||
import {stringify} from 'angular2/src/facade/lang';
|
import {stringify} from 'angular2/src/facade/lang';
|
||||||
|
import {DOM} from 'angular2/src/facade/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default steps used for compiling a template.
|
* Default steps used for compiling a template.
|
||||||
@ -27,8 +29,14 @@ export function createDefaultSteps(
|
|||||||
|
|
||||||
var compilationUnit = stringify(compiledComponent.type);
|
var compilationUnit = stringify(compiledComponent.type);
|
||||||
|
|
||||||
return [
|
var steps = [new ViewSplitter(parser, compilationUnit)];
|
||||||
new ViewSplitter(parser, compilationUnit),
|
|
||||||
|
if (!(shadowDomStrategy instanceof NativeShadowDomStrategy)) {
|
||||||
|
var step = new ShadowDomTransformer(compiledComponent, shadowDomStrategy, DOM.defaultDoc().head);
|
||||||
|
ListWrapper.push(steps, step);
|
||||||
|
}
|
||||||
|
|
||||||
|
steps = ListWrapper.concat(steps,[
|
||||||
new PropertyBindingParser(parser, compilationUnit),
|
new PropertyBindingParser(parser, compilationUnit),
|
||||||
new DirectiveParser(directives),
|
new DirectiveParser(directives),
|
||||||
new TextInterpolationParser(parser, compilationUnit),
|
new TextInterpolationParser(parser, compilationUnit),
|
||||||
@ -36,5 +44,7 @@ export function createDefaultSteps(
|
|||||||
new ProtoViewBuilder(changeDetection, shadowDomStrategy),
|
new ProtoViewBuilder(changeDetection, shadowDomStrategy),
|
||||||
new ProtoElementInjectorBuilder(),
|
new ProtoElementInjectorBuilder(),
|
||||||
new ElementBinderBuilder(parser, compilationUnit)
|
new ElementBinderBuilder(parser, compilationUnit)
|
||||||
];
|
]);
|
||||||
|
|
||||||
|
return steps;
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,10 @@ const NG_BINDING_CLASS = 'ng-binding';
|
|||||||
*/
|
*/
|
||||||
export class ElementBindingMarker extends CompileStep {
|
export class ElementBindingMarker extends CompileStep {
|
||||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||||
|
if (current.ignoreBindings) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var hasBindings =
|
var hasBindings =
|
||||||
(isPresent(current.textNodeBindings) && MapWrapper.size(current.textNodeBindings)>0) ||
|
(isPresent(current.textNodeBindings) && MapWrapper.size(current.textNodeBindings)>0) ||
|
||||||
(isPresent(current.propertyBindings) && MapWrapper.size(current.propertyBindings)>0) ||
|
(isPresent(current.propertyBindings) && MapWrapper.size(current.propertyBindings)>0) ||
|
||||||
|
@ -38,6 +38,10 @@ 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();
|
||||||
MapWrapper.forEach(attrs, (attrValue, attrName) => {
|
MapWrapper.forEach(attrs, (attrValue, attrName) => {
|
||||||
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
|
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
|
||||||
|
79
modules/angular2/src/core/compiler/pipeline/shadow_dom_transformer.js
vendored
Normal file
79
modules/angular2/src/core/compiler/pipeline/shadow_dom_transformer.js
vendored
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import {CompileStep} from './compile_step';
|
||||||
|
import {CompileElement} from './compile_element';
|
||||||
|
import {CompileControl} from './compile_control';
|
||||||
|
|
||||||
|
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
||||||
|
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||||
|
import {shimCssText} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_css';
|
||||||
|
|
||||||
|
import {DOM, Element} from 'angular2/src/facade/dom';
|
||||||
|
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||||
|
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
|
var _cssCache = StringMapWrapper.create();
|
||||||
|
|
||||||
|
export class ShadowDomTransformer extends CompileStep {
|
||||||
|
_selector: string;
|
||||||
|
_strategy: ShadowDomStrategy;
|
||||||
|
_styleHost: Element;
|
||||||
|
_lastInsertedStyle: Element;
|
||||||
|
|
||||||
|
constructor(cmpMetadata: DirectiveMetadata, strategy: ShadowDomStrategy, styleHost: Element) {
|
||||||
|
super();
|
||||||
|
this._strategy = strategy;
|
||||||
|
this._selector = cmpMetadata.annotation.selector;
|
||||||
|
this._styleHost = styleHost;
|
||||||
|
this._lastInsertedStyle = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||||
|
// May be remove the styles
|
||||||
|
if (DOM.tagName(current.element) == 'STYLE') {
|
||||||
|
current.ignoreBindings = true;
|
||||||
|
if (this._strategy.extractStyles()) {
|
||||||
|
DOM.remove(current.element);
|
||||||
|
var css = DOM.getText(current.element);
|
||||||
|
if (this._strategy.shim()) {
|
||||||
|
// The css generated here is unique for the component (because of the shim).
|
||||||
|
// Then we do not need to cache it.
|
||||||
|
css = shimCssText(css, this._selector);
|
||||||
|
this._insertStyle(this._styleHost, css);
|
||||||
|
} else {
|
||||||
|
var seen = isPresent(StringMapWrapper.get(_cssCache, css));
|
||||||
|
if (!seen) {
|
||||||
|
StringMapWrapper.set(_cssCache, css, true);
|
||||||
|
this._insertStyle(this._styleHost, css);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this._strategy.shim()) {
|
||||||
|
try {
|
||||||
|
DOM.setAttribute(current.element, this._selector, '');
|
||||||
|
} catch(e) {
|
||||||
|
// TODO(vicb): for now only simple selector (tag name) are supported
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCache() {
|
||||||
|
_cssCache = StringMapWrapper.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
_insertStyle(el: Element, css: string) {
|
||||||
|
var style = DOM.createStyleElement(css);
|
||||||
|
if (isBlank(this._lastInsertedStyle)) {
|
||||||
|
var firstChild = DOM.firstChild(el);
|
||||||
|
if (isPresent(firstChild)) {
|
||||||
|
DOM.insertBefore(firstChild, style);
|
||||||
|
} else {
|
||||||
|
DOM.appendChild(el, style);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DOM.insertAfter(this._lastInsertedStyle, style);
|
||||||
|
}
|
||||||
|
this._lastInsertedStyle = style;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ export class TextInterpolationParser extends CompileStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||||
if (!current.compileChildren) {
|
if (!current.compileChildren || current.ignoreBindings) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var element = current.element;
|
var element = current.element;
|
||||||
|
@ -1,28 +1,22 @@
|
|||||||
import {Type, isBlank, isPresent} from 'angular2/src/facade/lang';
|
import {Type, isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||||
import {DOM, Element, StyleElement} from 'angular2/src/facade/dom';
|
import {DOM, Element} from 'angular2/src/facade/dom';
|
||||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
import {View} from './view';
|
import {View} from './view';
|
||||||
import {Content} from './shadow_dom_emulation/content_tag';
|
import {Content} from './shadow_dom_emulation/content_tag';
|
||||||
import {LightDom} from './shadow_dom_emulation/light_dom';
|
import {LightDom} from './shadow_dom_emulation/light_dom';
|
||||||
import {DirectiveMetadata} from './directive_metadata';
|
import {DirectiveMetadata} from './directive_metadata';
|
||||||
import {shimCssText} from './shadow_dom_emulation/shim_css';
|
|
||||||
|
|
||||||
export class ShadowDomStrategy {
|
export class ShadowDomStrategy {
|
||||||
attachTemplate(el:Element, view:View){}
|
attachTemplate(el:Element, view:View){}
|
||||||
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){}
|
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){}
|
||||||
polyfillDirectives():List<Type>{ return null; }
|
polyfillDirectives():List<Type>{ return null; }
|
||||||
processTemplate(template: Element, cmpMetadata: DirectiveMetadata) { return null; }
|
shim(): boolean { return false; }
|
||||||
|
extractStyles(): boolean { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
||||||
_styleHost: Element;
|
constructor() {
|
||||||
|
|
||||||
constructor(styleHost: Element = null) {
|
|
||||||
super();
|
super();
|
||||||
if (isBlank(styleHost)) {
|
|
||||||
styleHost = DOM.defaultDoc().head;
|
|
||||||
}
|
|
||||||
this._styleHost = styleHost;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attachTemplate(el:Element, view:View){
|
attachTemplate(el:Element, view:View){
|
||||||
@ -38,25 +32,20 @@ export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
|||||||
return [Content];
|
return [Content];
|
||||||
}
|
}
|
||||||
|
|
||||||
processTemplate(template: Element, cmpMetadata: DirectiveMetadata) {
|
shim(): boolean {
|
||||||
var templateRoot = DOM.templateAwareRoot(template);
|
return true;
|
||||||
var attrName = cmpMetadata.annotation.selector;
|
}
|
||||||
|
|
||||||
// Shim CSS for emulated shadow DOM and attach the styles do the document head
|
extractStyles(): boolean {
|
||||||
var styles = _detachStyles(templateRoot);
|
return true;
|
||||||
for (var i = 0; i < styles.length; i++) {
|
|
||||||
var style = styles[i];
|
|
||||||
var processedCss = shimCssText(DOM.getText(style), attrName);
|
|
||||||
DOM.setText(style, processedCss);
|
|
||||||
}
|
|
||||||
_attachStyles(this._styleHost, styles);
|
|
||||||
|
|
||||||
// Update the DOM to trigger the CSS
|
|
||||||
_addAttributeToChildren(templateRoot, attrName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
attachTemplate(el:Element, view:View){
|
attachTemplate(el:Element, view:View){
|
||||||
moveViewNodesIntoParent(el.createShadowRoot(), view);
|
moveViewNodesIntoParent(el.createShadowRoot(), view);
|
||||||
}
|
}
|
||||||
@ -69,8 +58,12 @@ export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
processTemplate(template: Element, cmpMetadata: DirectiveMetadata) {
|
shim(): boolean {
|
||||||
return template;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
extractStyles(): boolean {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,38 +72,3 @@ function moveViewNodesIntoParent(parent, view) {
|
|||||||
DOM.appendChild(parent, view.nodes[i]);
|
DOM.appendChild(parent, view.nodes[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(vicb): union types: el is an Element or a Document Fragment
|
|
||||||
function _detachStyles(el): List<StyleElement> {
|
|
||||||
var nodeList = DOM.querySelectorAll(el, 'style');
|
|
||||||
var styles = [];
|
|
||||||
for (var i = 0; i < nodeList.length; i++) {
|
|
||||||
var style = DOM.remove(nodeList[i]);
|
|
||||||
ListWrapper.push(styles, style);
|
|
||||||
}
|
|
||||||
return styles;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the styles as the first children of the template
|
|
||||||
function _attachStyles(el: Element, styles: List<StyleElement>) {
|
|
||||||
var firstChild = DOM.firstChild(el);
|
|
||||||
for (var i = styles.length - 1; i >= 0; i--) {
|
|
||||||
var style = styles[i];
|
|
||||||
if (isPresent(firstChild)) {
|
|
||||||
DOM.insertBefore(firstChild, style);
|
|
||||||
} else {
|
|
||||||
DOM.appendChild(el, style);
|
|
||||||
}
|
|
||||||
firstChild = style;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(vicb): union types: el is an Element or a Document Fragment
|
|
||||||
function _addAttributeToChildren(el, attrName:string) {
|
|
||||||
// TODO(vicb): currently the code crashes when the attrName is not an el selector
|
|
||||||
var children = DOM.querySelectorAll(el, "*");
|
|
||||||
for (var i = 0; i < children.length; i++) {
|
|
||||||
var child = children[i];
|
|
||||||
DOM.setAttribute(child, attrName, '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -103,6 +103,12 @@ class DOM {
|
|||||||
el.setAttribute(attrName, attrValue);
|
el.setAttribute(attrName, attrValue);
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
static StyleElement createStyleElement(String css, [HtmlDocument doc = null]) {
|
||||||
|
if (doc == null) doc = document;
|
||||||
|
var el = doc.createElement("STYLE");
|
||||||
|
el.text = css;
|
||||||
|
return el;
|
||||||
|
}
|
||||||
static clone(Node node) => node.clone(true);
|
static clone(Node node) => node.clone(true);
|
||||||
static bool hasProperty(Element element, String name) =>
|
static bool hasProperty(Element element, String name) =>
|
||||||
new JsObject.fromBrowserObject(element).hasProperty(name);
|
new JsObject.fromBrowserObject(element).hasProperty(name);
|
||||||
|
@ -70,23 +70,6 @@ export function main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use the shadow dom strategy to process the template', (done) => {
|
|
||||||
// TODO(vicb) test in Dart when the bug is fixed
|
|
||||||
// https://code.google.com/p/dart/issues/detail?id=18249
|
|
||||||
if (IS_DARTIUM) {
|
|
||||||
done();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var templateHtml = 'processed template';
|
|
||||||
var compiler = createCompiler((parent, current, control) => {
|
|
||||||
current.inheritedProtoView = new ProtoView(current.element, null, null);
|
|
||||||
}, new FakeShadowDomStrategy(templateHtml));
|
|
||||||
compiler.compile(MainComponent, null).then( (protoView) => {
|
|
||||||
expect(DOM.getInnerHTML(protoView.element)).toEqual('processed template');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should load nested components', (done) => {
|
it('should load nested components', (done) => {
|
||||||
var mainEl = el('<div></div>');
|
var mainEl = el('<div></div>');
|
||||||
var compiler = createCompiler( (parent, current, control) => {
|
var compiler = createCompiler( (parent, current, control) => {
|
||||||
@ -244,15 +227,3 @@ class MockStep extends CompileStep {
|
|||||||
this.processClosure(parent, current, control);
|
this.processClosure(parent, current, control);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeShadowDomStrategy extends NativeShadowDomStrategy {
|
|
||||||
templateHtml: string;
|
|
||||||
constructor(templateHtml: string) {
|
|
||||||
super();
|
|
||||||
this.templateHtml = templateHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
processTemplate(template: Element, cmpMetadata: DirectiveMetadata) {
|
|
||||||
DOM.setInnerHTML(template, this.templateHtml);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -14,7 +14,8 @@ import {Template, Decorator, Component} from 'angular2/src/core/annotations/anno
|
|||||||
export function main() {
|
export function main() {
|
||||||
describe('ElementBindingMarker', () => {
|
describe('ElementBindingMarker', () => {
|
||||||
|
|
||||||
function createPipeline({textNodeBindings, propertyBindings, variableBindings, eventBindings, directives}={}) {
|
function createPipeline({textNodeBindings, propertyBindings, variableBindings, eventBindings,
|
||||||
|
directives, ignoreBindings}={}) {
|
||||||
var reader = new DirectiveMetadataReader();
|
var reader = new DirectiveMetadataReader();
|
||||||
return new CompilePipeline([
|
return new CompilePipeline([
|
||||||
new MockStep((parent, current, control) => {
|
new MockStep((parent, current, control) => {
|
||||||
@ -30,6 +31,9 @@ export function main() {
|
|||||||
if (isPresent(eventBindings)) {
|
if (isPresent(eventBindings)) {
|
||||||
current.eventBindings = eventBindings;
|
current.eventBindings = eventBindings;
|
||||||
}
|
}
|
||||||
|
if (isPresent(ignoreBindings)) {
|
||||||
|
current.ignoreBindings = ignoreBindings;
|
||||||
|
}
|
||||||
if (isPresent(directives)) {
|
if (isPresent(directives)) {
|
||||||
for (var i=0; i<directives.length; i++) {
|
for (var i=0; i<directives.length; i++) {
|
||||||
current.addDirective(reader.read(directives[i]));
|
current.addDirective(reader.read(directives[i]));
|
||||||
@ -44,6 +48,14 @@ export function main() {
|
|||||||
assertBinding(results[0], false);
|
assertBinding(results[0], false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not mark elements when ignoreBindings is true', () => {
|
||||||
|
var textNodeBindings = MapWrapper.create();
|
||||||
|
MapWrapper.set(textNodeBindings, 0, 'expr');
|
||||||
|
var results = createPipeline({textNodeBindings: textNodeBindings,
|
||||||
|
ignoreBindings: true}).process(el('<div></div>'));
|
||||||
|
assertBinding(results[0], false);
|
||||||
|
});
|
||||||
|
|
||||||
it('should mark elements with text node bindings', () => {
|
it('should mark elements with text node bindings', () => {
|
||||||
var textNodeBindings = MapWrapper.create();
|
var textNodeBindings = MapWrapper.create();
|
||||||
MapWrapper.set(textNodeBindings, 0, 'expr');
|
MapWrapper.set(textNodeBindings, 0, 'expr');
|
||||||
|
@ -3,15 +3,24 @@ import {PropertyBindingParser} from 'angular2/src/core/compiler/pipeline/propert
|
|||||||
import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline';
|
import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline';
|
||||||
import {DOM} from 'angular2/src/facade/dom';
|
import {DOM} from 'angular2/src/facade/dom';
|
||||||
import {MapWrapper} from 'angular2/src/facade/collection';
|
import {MapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
import {CompileElement} from 'angular2/src/core/compiler/pipeline/compile_element';
|
||||||
|
import {CompileStep} from 'angular2/src/core/compiler/pipeline/compile_step'
|
||||||
|
import {CompileControl} from 'angular2/src/core/compiler/pipeline/compile_control';
|
||||||
import {Lexer, Parser} from 'angular2/change_detection';
|
import {Lexer, Parser} from 'angular2/change_detection';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('PropertyBindingParser', () => {
|
describe('PropertyBindingParser', () => {
|
||||||
function createPipeline() {
|
function createPipeline(ignoreBindings = false) {
|
||||||
return new CompilePipeline([new PropertyBindingParser(new Parser(new Lexer()), null)]);
|
return new CompilePipeline([
|
||||||
|
new MockStep((parent, current, control) => { current.ignoreBindings = ignoreBindings; }),
|
||||||
|
new PropertyBindingParser(new Parser(new Lexer()), null)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it('should not parse bindings when ignoreBindings is true', () => {
|
||||||
|
var results = createPipeline(true).process(el('<div [a]="b"></div>'));
|
||||||
|
expect(results[0].propertyBindings).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
it('should detect [] syntax', () => {
|
it('should detect [] syntax', () => {
|
||||||
var results = createPipeline().process(el('<div [a]="b"></div>'));
|
var results = createPipeline().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');
|
||||||
@ -69,3 +78,14 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
128
modules/angular2/test/core/compiler/pipeline/shadow_dom_transformer_spec.js
vendored
Normal file
128
modules/angular2/test/core/compiler/pipeline/shadow_dom_transformer_spec.js
vendored
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import {describe, beforeEach, expect, it, iit, ddescribe, el} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline';
|
||||||
|
import {ShadowDomTransformer} from 'angular2/src/core/compiler/pipeline/shadow_dom_transformer';
|
||||||
|
import {Component} from 'angular2/src/core/annotations/annotations';
|
||||||
|
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
||||||
|
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||||
|
import {shimCssText} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_css';
|
||||||
|
|
||||||
|
import {DOM} from 'angular2/src/facade/dom';
|
||||||
|
import {MapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('ShadowDomTransformer', () => {
|
||||||
|
function createPipeline(selector, strategy:ShadowDomStrategy, styleHost) {
|
||||||
|
var component = new Component({selector: selector});
|
||||||
|
var meta = new DirectiveMetadata(null, component, null);
|
||||||
|
var transformer = new ShadowDomTransformer(meta, strategy, styleHost);
|
||||||
|
transformer.clearCache();
|
||||||
|
return new CompilePipeline([transformer]);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('it should set ignoreBindings to true for style elements', () => {
|
||||||
|
var host = DOM.createElement('div');
|
||||||
|
var pipeline = createPipeline('foo', new FakeStrategy(false, false), host);
|
||||||
|
var results = pipeline.process(el('<div><style></style></div>'));
|
||||||
|
expect(results[0].ignoreBindings).toBe(false);
|
||||||
|
expect(results[1].ignoreBindings).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('css', () => {
|
||||||
|
it('should not extract the styles when extractStyles() is false', () => {
|
||||||
|
var host = DOM.createElement('div');
|
||||||
|
var pipeline = createPipeline('foo', new FakeStrategy(false, false), host);
|
||||||
|
var template = el('<style>.s{}</style>');
|
||||||
|
pipeline.process(template);
|
||||||
|
expect(template).toHaveText('.s{}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should move the styles to the host when extractStyles() is true', () => {
|
||||||
|
var host = DOM.createElement('div');
|
||||||
|
var pipeline = createPipeline('foo', new FakeStrategy(true, false), host);
|
||||||
|
var template = el('<div><style>.s{}</style></div>');
|
||||||
|
pipeline.process(template);
|
||||||
|
expect(template).toHaveText('');
|
||||||
|
expect(host).toHaveText('.s{}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve original content when moving styles', () => {
|
||||||
|
var host = el('<div>original content</div>');
|
||||||
|
var pipeline = createPipeline('foo', new FakeStrategy(true, false), host);
|
||||||
|
var template = el('<div><style>.s{}</style></div>');
|
||||||
|
pipeline.process(template);
|
||||||
|
expect(template).toHaveText('');
|
||||||
|
expect(host).toHaveText('.s{}original content');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should move the styles to the host in the original order', () => {
|
||||||
|
var host = DOM.createElement('div');
|
||||||
|
var pipeline = createPipeline('foo', new FakeStrategy(true, false), host);
|
||||||
|
var template = el('<div><style>.s1{}</style><style>.s2{}</style></div>');
|
||||||
|
pipeline.process(template);
|
||||||
|
expect(host).toHaveText('.s1{}.s2{}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should shim the styles when shim() and extractStyles() are true', () => {
|
||||||
|
var host = DOM.createElement('div');
|
||||||
|
var pipeline = createPipeline('foo', new FakeStrategy(true, true), host);
|
||||||
|
var template = el('<div><style>.s1{}</style></div>');
|
||||||
|
pipeline.process(template);
|
||||||
|
expect(host).toHaveText(shimCssText('.s1{}', 'foo'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deduplicate styles before moving them when shim() is false', () => {
|
||||||
|
var host = DOM.createElement('div');
|
||||||
|
var pipeline = createPipeline('foo', new FakeStrategy(true, false), host);
|
||||||
|
var template = el('<div><style>.s1{}</style><style>.s1{}</style><style>.s1{}</style></div>');
|
||||||
|
pipeline.process(template);
|
||||||
|
expect(host).toHaveText('.s1{}');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('html', () => {
|
||||||
|
it('should add an attribute to all children when shim() is true', () => {
|
||||||
|
var host = DOM.createElement('div');
|
||||||
|
var pipeline = createPipeline('foo', new FakeStrategy(false, true), host);
|
||||||
|
var template = el('<div><span></span></div>');
|
||||||
|
pipeline.process(template);
|
||||||
|
expect(DOM.getOuterHTML(template)).toEqual('<div foo=""><span foo=""></span></div>')
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not modify the template when shim() is false', () => {
|
||||||
|
var host = DOM.createElement('div');
|
||||||
|
var pipeline = createPipeline('foo', new FakeStrategy(false, false), host);
|
||||||
|
var template = el('<div><span></span></div>');
|
||||||
|
pipeline.process(template);
|
||||||
|
expect(DOM.getOuterHTML(template)).toEqual('<div><span></span></div>')
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not throw with complex selectors', () => {
|
||||||
|
var host = DOM.createElement('div');
|
||||||
|
var pipeline = createPipeline('foo[bar]', new FakeStrategy(false, true), host);
|
||||||
|
var template = el('<div><span></span></div>');
|
||||||
|
expect(() => pipeline.process(template)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeStrategy extends ShadowDomStrategy {
|
||||||
|
_extractStyles: boolean;
|
||||||
|
_shim: boolean;
|
||||||
|
|
||||||
|
constructor(extractStyles: boolean, shim: boolean) {
|
||||||
|
super();
|
||||||
|
this._extractStyles = extractStyles;
|
||||||
|
this._shim = shim;
|
||||||
|
}
|
||||||
|
|
||||||
|
extractStyles(): boolean {
|
||||||
|
return this._extractStyles;
|
||||||
|
}
|
||||||
|
|
||||||
|
shim(): boolean {
|
||||||
|
return this._shim;
|
||||||
|
}
|
||||||
|
}
|
@ -3,19 +3,27 @@ import {TextInterpolationParser} from 'angular2/src/core/compiler/pipeline/text_
|
|||||||
import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline';
|
import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline';
|
||||||
import {DOM} from 'angular2/src/facade/dom';
|
import {DOM} from 'angular2/src/facade/dom';
|
||||||
import {MapWrapper} from 'angular2/src/facade/collection';
|
import {MapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {Lexer, Parser} from 'angular2/change_detection';
|
import {Lexer, Parser} from 'angular2/change_detection';
|
||||||
|
import {CompileElement} from 'angular2/src/core/compiler/pipeline/compile_element';
|
||||||
|
import {CompileStep} from 'angular2/src/core/compiler/pipeline/compile_step'
|
||||||
|
import {CompileControl} from 'angular2/src/core/compiler/pipeline/compile_control';
|
||||||
import {IgnoreChildrenStep} from './pipeline_spec';
|
import {IgnoreChildrenStep} from './pipeline_spec';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('TextInterpolationParser', () => {
|
describe('TextInterpolationParser', () => {
|
||||||
function createPipeline() {
|
function createPipeline(ignoreBindings = false) {
|
||||||
return new CompilePipeline([
|
return new CompilePipeline([
|
||||||
|
new MockStep((parent, current, control) => { current.ignoreBindings = ignoreBindings; }),
|
||||||
new IgnoreChildrenStep(),
|
new IgnoreChildrenStep(),
|
||||||
new TextInterpolationParser(new Parser(new Lexer()), null)
|
new TextInterpolationParser(new Parser(new Lexer()), null)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it('should not look for text interpolation when ignoreBindings is true', () => {
|
||||||
|
var results = createPipeline(true).process(el('<div>{{expr1}}<span></span>{{expr2}}</div>'));
|
||||||
|
expect(results[0].textNodeBindings).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
it('should find text interpolation in normal elements', () => {
|
it('should find text interpolation in normal elements', () => {
|
||||||
var results = createPipeline().process(el('<div>{{expr1}}<span></span>{{expr2}}</div>'));
|
var results = createPipeline().process(el('<div>{{expr1}}<span></span>{{expr2}}</div>'));
|
||||||
var bindings = results[0].textNodeBindings;
|
var bindings = results[0].textNodeBindings;
|
||||||
@ -55,3 +63,14 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'angular2/test_lib';
|
|
||||||
import {NativeShadowDomStrategy, EmulatedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
|
||||||
import {DOM} from 'angular2/src/facade/dom';
|
|
||||||
import {Component} from 'angular2/src/core/annotations/annotations';
|
|
||||||
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('Shadow DOM strategy', () => {
|
|
||||||
var strategy,
|
|
||||||
component = new Component({selector: 'mycmp'}),
|
|
||||||
metadata = new DirectiveMetadata(null, component, null);
|
|
||||||
|
|
||||||
describe('Native', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
strategy = new NativeShadowDomStrategy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should leave the styles in the template', () => {
|
|
||||||
var tpl = DOM.createTemplate('<style>.s1{}</style><div>content</div>');
|
|
||||||
strategy.processTemplate(tpl, metadata);
|
|
||||||
expect(tpl.content).toHaveText('.s1{}content');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not modify the content of the template', () => {
|
|
||||||
var html = '<p>content<span></span></p>';
|
|
||||||
var tpl = DOM.createTemplate(html);
|
|
||||||
strategy.processTemplate(tpl, metadata);
|
|
||||||
expect(DOM.getInnerHTML(tpl)).toEqual(html);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Emulated', () => {
|
|
||||||
var root;
|
|
||||||
beforeEach(() => {
|
|
||||||
root = el('<div>');
|
|
||||||
strategy = new EmulatedShadowDomStrategy(root);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should move the styles from the template to the root', () => {
|
|
||||||
var tpl = DOM.createTemplate('<style>.s1{}</style><div>content</div><style>.s2{}</style>');
|
|
||||||
strategy.processTemplate(tpl, metadata);
|
|
||||||
expect(root).toHaveText('.s1[mycmp] {}.s2[mycmp] {}');
|
|
||||||
expect(tpl.content).toHaveText('content');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should insert the styles as the first children of the host', () => {
|
|
||||||
DOM.setInnerHTML(root, '<p>root content</p>')
|
|
||||||
var tpl = DOM.createTemplate('<style>.s1{}</style><style>.s2{}</style>');
|
|
||||||
strategy.processTemplate(tpl, metadata);
|
|
||||||
expect(root).toHaveText('.s1[mycmp] {}.s2[mycmp] {}root content');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add the component selector to all template children', () => {
|
|
||||||
var html = '<p>content<span></span></p>';
|
|
||||||
var processedHtml = '<p mycmp="">content<span mycmp=""></span></p>';
|
|
||||||
var tpl = DOM.createTemplate(html);
|
|
||||||
strategy.processTemplate(tpl, metadata);
|
|
||||||
expect(DOM.getInnerHTML(tpl)).toEqual(processedHtml);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
Reference in New Issue
Block a user