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> {
|
||||
this._shadowDomStrategy.processTemplate(template, cmpMetadata);
|
||||
var pipeline = new CompilePipeline(this.createSteps(cmpMetadata));
|
||||
var compileElements = pipeline.process(template);
|
||||
var protoView = compileElements[0].inheritedProtoView;
|
||||
|
@ -39,6 +39,8 @@ export class CompileElement {
|
||||
inheritedElementBinder:ElementBinder;
|
||||
distanceToParentInjector:number;
|
||||
compileChildren: boolean;
|
||||
ignoreBindings: boolean;
|
||||
|
||||
constructor(element:Element) {
|
||||
this.element = element;
|
||||
this._attrs = null;
|
||||
@ -64,6 +66,8 @@ export class CompileElement {
|
||||
this.inheritedElementBinder = null;
|
||||
this.distanceToParentInjector = 0;
|
||||
this.compileChildren = true;
|
||||
// set to true to ignore all the bindings on the element
|
||||
this.ignoreBindings = false;
|
||||
}
|
||||
|
||||
refreshAttrs() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 {TextInterpolationParser} from './text_interpolation_parser';
|
||||
@ -9,9 +9,11 @@ import {ElementBindingMarker} from './element_binding_marker';
|
||||
import {ProtoViewBuilder} from './proto_view_builder';
|
||||
import {ProtoElementInjectorBuilder} from './proto_element_injector_builder';
|
||||
import {ElementBinderBuilder} from './element_binder_builder';
|
||||
import {ShadowDomTransformer} from './shadow_dom_transformer';
|
||||
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 {DOM} from 'angular2/src/facade/dom';
|
||||
|
||||
/**
|
||||
* Default steps used for compiling a template.
|
||||
@ -27,8 +29,14 @@ export function createDefaultSteps(
|
||||
|
||||
var compilationUnit = stringify(compiledComponent.type);
|
||||
|
||||
return [
|
||||
new ViewSplitter(parser, compilationUnit),
|
||||
var steps = [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 DirectiveParser(directives),
|
||||
new TextInterpolationParser(parser, compilationUnit),
|
||||
@ -36,5 +44,7 @@ export function createDefaultSteps(
|
||||
new ProtoViewBuilder(changeDetection, shadowDomStrategy),
|
||||
new ProtoElementInjectorBuilder(),
|
||||
new ElementBinderBuilder(parser, compilationUnit)
|
||||
];
|
||||
]);
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
@ -26,6 +26,10 @@ const NG_BINDING_CLASS = 'ng-binding';
|
||||
*/
|
||||
export class ElementBindingMarker extends CompileStep {
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
if (current.ignoreBindings) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hasBindings =
|
||||
(isPresent(current.textNodeBindings) && MapWrapper.size(current.textNodeBindings)>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) {
|
||||
if (current.ignoreBindings) {
|
||||
return;
|
||||
}
|
||||
|
||||
var attrs = current.attrs();
|
||||
MapWrapper.forEach(attrs, (attrValue, 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) {
|
||||
if (!current.compileChildren) {
|
||||
if (!current.compileChildren || current.ignoreBindings) {
|
||||
return;
|
||||
}
|
||||
var element = current.element;
|
||||
|
@ -1,28 +1,22 @@
|
||||
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 {View} from './view';
|
||||
import {Content} from './shadow_dom_emulation/content_tag';
|
||||
import {LightDom} from './shadow_dom_emulation/light_dom';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {shimCssText} from './shadow_dom_emulation/shim_css';
|
||||
|
||||
export class ShadowDomStrategy {
|
||||
attachTemplate(el:Element, view:View){}
|
||||
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){}
|
||||
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 {
|
||||
_styleHost: Element;
|
||||
|
||||
constructor(styleHost: Element = null) {
|
||||
constructor() {
|
||||
super();
|
||||
if (isBlank(styleHost)) {
|
||||
styleHost = DOM.defaultDoc().head;
|
||||
}
|
||||
this._styleHost = styleHost;
|
||||
}
|
||||
|
||||
attachTemplate(el:Element, view:View){
|
||||
@ -38,25 +32,20 @@ export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
||||
return [Content];
|
||||
}
|
||||
|
||||
processTemplate(template: Element, cmpMetadata: DirectiveMetadata) {
|
||||
var templateRoot = DOM.templateAwareRoot(template);
|
||||
var attrName = cmpMetadata.annotation.selector;
|
||||
shim(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Shim CSS for emulated shadow DOM and attach the styles do the document head
|
||||
var styles = _detachStyles(templateRoot);
|
||||
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);
|
||||
extractStyles(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
attachTemplate(el:Element, view:View){
|
||||
moveViewNodesIntoParent(el.createShadowRoot(), view);
|
||||
}
|
||||
@ -69,8 +58,12 @@ export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
||||
return [];
|
||||
}
|
||||
|
||||
processTemplate(template: Element, cmpMetadata: DirectiveMetadata) {
|
||||
return template;
|
||||
shim(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
extractStyles(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,38 +72,3 @@ function moveViewNodesIntoParent(parent, view) {
|
||||
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);
|
||||
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 bool hasProperty(Element element, String name) =>
|
||||
new JsObject.fromBrowserObject(element).hasProperty(name);
|
||||
|
Reference in New Issue
Block a user