refactor(shadow_dom): remove ShadowDomStrategy in favor of @View(encapsulation)

BREAKING CHANGES:
- `ShadowDomStrategy` was removed. To specify the encapsulation of a component use `@View(encapsulation: ViewEncapsulation.NONE | ViewEncapsulation.EMULATED | ViewEncapsulation.NATIVE)`
- The default encapsulation strategy is now `ViewEncapsulation.EMULATED` if a component contains styles and `ViewEncapsulation.NONE` if it does not. Before this was always `NONE`.
- `ViewLoader` now returns the template as a string and the styles as a separate array
This commit is contained in:
Tobias Bosch
2015-07-28 22:33:11 -07:00
parent e40ff36832
commit 16e3d7e96e
77 changed files with 1228 additions and 890 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ export {
LifecycleEvent
} from './src/core/annotations/annotations';
export {ViewAnnotation} from 'angular2/src/core/annotations/view';
export {ViewAnnotation, ViewEncapsulation} from 'angular2/src/core/annotations/view';
export {QueryAnnotation, AttributeAnnotation} from 'angular2/src/core/annotations/di';
export {
+1
View File
@@ -14,5 +14,6 @@ export {
RenderViewWithFragments,
DomRenderer,
DOCUMENT_TOKEN,
APP_ID_TOKEN,
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES
} from './src/render/render';
@@ -9,6 +9,7 @@ import {
Class
} from '../../util/decorators';
import {Type} from 'angular2/src/facade/lang';
import {ViewEncapsulation} from 'angular2/src/render/api';
/**
* Interface for the {@link Directive} decorator function.
@@ -226,7 +227,7 @@ export interface ViewFactory {
templateUrl?: string,
template?: string,
directives?: List<Type | any | List<any>>,
renderer?: string,
encapsulation?: ViewEncapsulation,
styles?: List<string>,
styleUrls?: List<string>,
}): ViewDecorator;
@@ -234,7 +235,7 @@ export interface ViewFactory {
templateUrl?: string,
template?: string,
directives?: List<Type | any | List<any>>,
renderer?: string,
encapsulation?: ViewEncapsulation,
styles?: List<string>,
styleUrls?: List<string>,
}): ViewAnnotation;
@@ -1 +1 @@
export {View as ViewAnnotation} from '../annotations_impl/view';
export {View as ViewAnnotation, ViewEncapsulation} from '../annotations_impl/view';
@@ -1,4 +1,7 @@
import {ABSTRACT, CONST, Type} from 'angular2/src/facade/lang';
import {ViewEncapsulation} from 'angular2/src/render/api';
export {ViewEncapsulation} from 'angular2/src/render/api';
/**
* Declares the available HTML templates for an application.
@@ -85,17 +88,17 @@ export class View {
directives: List<Type | any | List<any>>;
/**
* Specify a custom renderer for this View.
* If this is set, neither `template`, `templateUrl`, `styles`, `styleUrls` nor `directives` are
* used.
* Specify how the template and the styles should be encapsulated.
* The default is {@link ViewEncapsulation.EMULATED} if the view has styles,
* otherwise {@link ViewEncapsulation.NONE}.
*/
renderer: string;
encapsulation: ViewEncapsulation;
constructor({templateUrl, template, directives, renderer, styles, styleUrls}: {
constructor({templateUrl, template, directives, encapsulation, styles, styleUrls}: {
templateUrl?: string,
template?: string,
directives?: List<Type | any | List<any>>,
renderer?: string,
encapsulation?: ViewEncapsulation,
styles?: List<string>,
styleUrls?: List<string>,
} = {}) {
@@ -104,6 +107,6 @@ export class View {
this.styleUrls = styleUrls;
this.styles = styles;
this.directives = directives;
this.renderer = renderer;
this.encapsulation = encapsulation;
}
}
+12 -10
View File
@@ -34,10 +34,6 @@ import {List, ListWrapper} from 'angular2/src/facade/collection';
import {Promise, PromiseWrapper, PromiseCompleter} from 'angular2/src/facade/async';
import {NgZone} from 'angular2/src/core/zone/ng_zone';
import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle';
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
import {
EmulatedUnscopedShadowDomStrategy
} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
import {XHR} from 'angular2/src/render/xhr';
import {XHRImpl} from 'angular2/src/render/xhr_impl';
import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager';
@@ -61,9 +57,14 @@ import {Renderer, RenderCompiler} from 'angular2/src/render/api';
import {
DomRenderer,
DOCUMENT_TOKEN,
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES
} from 'angular2/src/render/dom/dom_renderer';
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES,
DefaultDomCompiler,
APP_ID_RANDOM_BINDING
} from 'angular2/src/render/render';
import {
SharedStylesHost,
DomSharedStylesHost
} from 'angular2/src/render/dom/view/shared_styles_host';
import {internalView} from 'angular2/src/core/compiler/view_ref';
import {appComponentRefPromiseToken, appComponentTypeToken} from './application_tokens';
@@ -108,12 +109,13 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
return new EventManager(plugins, ngZone);
},
[NgZone]),
bind(ShadowDomStrategy)
.toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]),
DomRenderer,
DefaultDomCompiler,
bind(Renderer).toAlias(DomRenderer),
APP_ID_RANDOM_BINDING,
DefaultDomCompiler,
bind(RenderCompiler).toAlias(DefaultDomCompiler),
DomSharedStylesHost,
bind(SharedStylesHost).toAlias(DomSharedStylesHost),
ProtoViewFactory,
AppViewPool,
bind(APP_VIEW_POOL_CAPACITY).toValue(10000),
@@ -307,7 +307,8 @@ export class Compiler {
templateAbsUrl: templateAbsUrl, template: view.template,
styleAbsUrls: styleAbsUrls,
styles: view.styles,
directives: ListWrapper.map(directives, directiveBinding => directiveBinding.metadata)
directives: ListWrapper.map(directives, directiveBinding => directiveBinding.metadata),
encapsulation: view.encapsulation
});
}
+2 -2
View File
@@ -317,10 +317,10 @@ class Html5LibDomAdapter implements DomAdapter {
throw 'not implemented';
}
bool supportsDOMEvents() {
throw 'not implemented';
return false;
}
bool supportsNativeShadowDOM() {
throw 'not implemented';
return false;
}
getHistory() {
throw 'not implemented';
@@ -212,4 +212,7 @@ void iterateListLike(iter, fn(item)) {
class SetWrapper {
static Set createFromList(List l) => new Set.from(l);
static bool has(Set s, key) => s.contains(key);
static void delete(Set m, k) {
m.remove(k);
}
}
@@ -290,4 +290,5 @@ var createSetFromList: {(lst: List<any>): Set<any>} = (function() {
export class SetWrapper {
static createFromList<T>(lst: List<T>): Set<T> { return createSetFromList(lst); }
static has<T>(s: Set<T>, key: T): boolean { return s.has(key); }
static delete<K>(m: Set<K>, k: K) { m.delete(k); }
}
+26 -3
View File
@@ -273,6 +273,25 @@ export class RenderFragmentRef {}
// An opaque reference to a view
export class RenderViewRef {}
/**
* How the template and styles of a view should be encapsulated.
*/
export enum ViewEncapsulation {
/**
* Emulate scoping of styles by preprocessing the style rules
* and adding additional attributes to elements. This is the default.
*/
EMULATED,
/**
* Uses the native mechanism of the renderer. For the DOM this means creating a ShadowRoot.
*/
NATIVE,
/**
* Don't scope the template nor the styles.
*/
NONE
}
export class ViewDefinition {
componentId: string;
templateAbsUrl: string;
@@ -280,21 +299,25 @@ export class ViewDefinition {
directives: List<DirectiveMetadata>;
styleAbsUrls: List<string>;
styles: List<string>;
encapsulation: ViewEncapsulation;
constructor({componentId, templateAbsUrl, template, styleAbsUrls, styles, directives}: {
constructor({componentId, templateAbsUrl, template, styleAbsUrls, styles, directives,
encapsulation}: {
componentId?: string,
templateAbsUrl?: string,
template?: string,
styleAbsUrls?: List<string>,
styles?: List<string>,
directives?: List<DirectiveMetadata>
}) {
directives?: List<DirectiveMetadata>,
encapsulation?: ViewEncapsulation
} = {}) {
this.componentId = componentId;
this.templateAbsUrl = templateAbsUrl;
this.template = template;
this.styleAbsUrls = styleAbsUrls;
this.styles = styles;
this.directives = directives;
this.encapsulation = isPresent(encapsulation) ? encapsulation : ViewEncapsulation.EMULATED;
}
}
@@ -29,7 +29,7 @@ export class CompileControl {
var step = this._steps[i];
this._parent = parent;
this._currentStepIndex = i;
step.process(parent, current, this);
step.processElement(parent, current, this);
parent = this._parent;
}
@@ -5,7 +5,7 @@ import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {CompileStep} from './compile_step';
import {ProtoViewBuilder} from '../view/proto_view_builder';
import {ProtoViewDto, ViewType} from '../../api';
import {ProtoViewDto, ViewType, ViewDefinition} from '../../api';
/**
* CompilePipeline for executing CompileSteps recursively for
@@ -13,26 +13,29 @@ import {ProtoViewDto, ViewType} from '../../api';
*/
export class CompilePipeline {
_control: CompileControl;
constructor(steps: List<CompileStep>, private _useNativeShadowDom: boolean = false) {
this._control = new CompileControl(steps);
constructor(public steps: List<CompileStep>) { this._control = new CompileControl(steps); }
processStyles(styles: string[]): string[] {
return styles.map(style => {
this.steps.forEach(step => { style = step.processStyle(style); });
return style;
});
}
process(rootElement: HTMLElement, protoViewType: ViewType = null,
compilationCtxtDescription: string = ''): List<CompileElement> {
if (isBlank(protoViewType)) {
protoViewType = ViewType.COMPONENT;
}
var results = [];
processElements(rootElement: Element, protoViewType: ViewType,
viewDef: ViewDefinition): CompileElement[] {
var results: CompileElement[] = [];
var compilationCtxtDescription = viewDef.componentId;
var rootCompileElement = new CompileElement(rootElement, compilationCtxtDescription);
rootCompileElement.inheritedProtoView =
new ProtoViewBuilder(rootElement, protoViewType, this._useNativeShadowDom);
new ProtoViewBuilder(rootElement, protoViewType, viewDef.encapsulation);
rootCompileElement.isViewRoot = true;
this._process(results, null, rootCompileElement, compilationCtxtDescription);
this._processElement(results, null, rootCompileElement, compilationCtxtDescription);
return results;
}
_process(results, parent: CompileElement, current: CompileElement,
compilationCtxtDescription: string = '') {
_processElement(results: CompileElement[], parent: CompileElement, current: CompileElement,
compilationCtxtDescription: string = '') {
var additionalChildren = this._control.internalProcess(results, 0, parent, current);
if (current.compileChildren) {
@@ -46,7 +49,7 @@ export class CompilePipeline {
childCompileElement.inheritedProtoView = current.inheritedProtoView;
childCompileElement.inheritedElementBinder = current.inheritedElementBinder;
childCompileElement.distanceToInheritedBinder = current.distanceToInheritedBinder + 1;
this._process(results, current, childCompileElement);
this._processElement(results, current, childCompileElement);
}
node = nextNode;
}
@@ -54,7 +57,7 @@ export class CompilePipeline {
if (isPresent(additionalChildren)) {
for (var i = 0; i < additionalChildren.length; i++) {
this._process(results, current, additionalChildren[i]);
this._processElement(results, current, additionalChildren[i]);
}
}
}
@@ -6,6 +6,8 @@ import * as compileControlModule from './compile_control';
* Is guaranteed to be called in depth first order
*/
export interface CompileStep {
process(parent: CompileElement, current: CompileElement,
control: compileControlModule.CompileControl): void;
processElement(parent: CompileElement, current: CompileElement,
control: compileControlModule.CompileControl): void;
processStyle(style: string): string;
}
@@ -6,15 +6,15 @@ import {PropertyBindingParser} from './property_binding_parser';
import {TextInterpolationParser} from './text_interpolation_parser';
import {DirectiveParser} from './directive_parser';
import {ViewSplitter} from './view_splitter';
import {ShadowDomCompileStep} from '../shadow_dom/shadow_dom_compile_step';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
import {StyleEncapsulator} from './style_encapsulator';
export class CompileStepFactory {
createSteps(view: ViewDefinition): List<CompileStep> { return null; }
}
export class DefaultStepFactory extends CompileStepFactory {
constructor(public _parser: Parser, public _shadowDomStrategy: ShadowDomStrategy) { super(); }
private _componentUIDsCache: Map<string, string> = new Map();
constructor(private _parser: Parser, private _appId: string) { super(); }
createSteps(view: ViewDefinition): List<CompileStep> {
return [
@@ -22,7 +22,7 @@ export class DefaultStepFactory extends CompileStepFactory {
new PropertyBindingParser(this._parser),
new DirectiveParser(this._parser, view.directives),
new TextInterpolationParser(this._parser),
new ShadowDomCompileStep(this._shadowDomStrategy, view)
new StyleEncapsulator(this._appId, view, this._componentUIDsCache)
];
}
}
@@ -1,7 +1,7 @@
import {Injectable} from 'angular2/di';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {BaseException, isPresent} from 'angular2/src/facade/lang';
import {BaseException, isPresent, isBlank} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {
@@ -11,14 +11,18 @@ import {
DirectiveMetadata,
RenderCompiler,
RenderProtoViewRef,
RenderProtoViewMergeMapping
RenderProtoViewMergeMapping,
ViewEncapsulation
} from '../../api';
import {CompilePipeline} from './compile_pipeline';
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
import {ViewLoader, TemplateAndStyles} from 'angular2/src/render/dom/compiler/view_loader';
import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory';
import {Parser} from 'angular2/src/change_detection/change_detection';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
import * as pvm from '../view/proto_view_merger';
import {DOCUMENT_TOKEN, APP_ID_TOKEN} from '../dom_tokens';
import {Inject} from 'angular2/di';
import {SharedStylesHost} from '../view/shared_styles_host';
import {prependAll} from '../util';
/**
* The compiler loads and translates the html templates of components into
@@ -26,15 +30,17 @@ import * as pvm from '../view/proto_view_merger';
* the CompilePipeline and the CompileSteps.
*/
export class DomCompiler extends RenderCompiler {
constructor(public _stepFactory: CompileStepFactory, public _viewLoader: ViewLoader,
public _useNativeShadowDom: boolean) {
constructor(private _stepFactory: CompileStepFactory, private _viewLoader: ViewLoader,
private _sharedStylesHost: SharedStylesHost) {
super();
}
compile(view: ViewDefinition): Promise<ProtoViewDto> {
var tplPromise = this._viewLoader.load(view);
return PromiseWrapper.then(
tplPromise, (el) => this._compileTemplate(view, el, ViewType.COMPONENT), (e) => {
tplPromise, (tplAndStyles: TemplateAndStyles) =>
this._compileView(view, tplAndStyles, ViewType.COMPONENT),
(e) => {
throw new BaseException(`Failed to load the template for "${view.componentId}" : ${e}`);
return null;
});
@@ -46,11 +52,13 @@ export class DomCompiler extends RenderCompiler {
templateAbsUrl: null, template: null,
styles: null,
styleAbsUrls: null,
directives: [directiveMetadata]
directives: [directiveMetadata],
encapsulation: ViewEncapsulation.NONE
});
var template = DOM.createTemplate('');
DOM.appendChild(DOM.content(template), DOM.createElement(directiveMetadata.selector));
return this._compileTemplate(hostViewDef, template, ViewType.HOST);
return this._compileView(
hostViewDef, new TemplateAndStyles(
`<${directiveMetadata.selector}></${directiveMetadata.selector}>`, []),
ViewType.HOST);
}
mergeProtoViewsRecursively(
@@ -58,20 +66,47 @@ export class DomCompiler extends RenderCompiler {
return PromiseWrapper.resolve(pvm.mergeProtoViewsRecursively(protoViewRefs));
}
_compileTemplate(viewDef: ViewDefinition, tplElement,
protoViewType: ViewType): Promise<ProtoViewDto> {
var pipeline =
new CompilePipeline(this._stepFactory.createSteps(viewDef), this._useNativeShadowDom);
var compileElements = pipeline.process(tplElement, protoViewType, viewDef.componentId);
_compileView(viewDef: ViewDefinition, templateAndStyles: TemplateAndStyles,
protoViewType: ViewType): Promise<ProtoViewDto> {
if (viewDef.encapsulation === ViewEncapsulation.EMULATED &&
templateAndStyles.styles.length === 0) {
viewDef = this._normalizeViewEncapsulationIfThereAreNoStyles(viewDef);
}
var pipeline = new CompilePipeline(this._stepFactory.createSteps(viewDef));
var compiledStyles = pipeline.processStyles(templateAndStyles.styles);
var compileElements = pipeline.processElements(DOM.createTemplate(templateAndStyles.template),
protoViewType, viewDef);
if (viewDef.encapsulation === ViewEncapsulation.NATIVE) {
prependAll(DOM.content(compileElements[0].element),
compiledStyles.map(style => DOM.createStyleElement(style)));
} else {
this._sharedStylesHost.addStyles(compiledStyles);
}
return PromiseWrapper.resolve(compileElements[0].inheritedProtoView.build());
}
_normalizeViewEncapsulationIfThereAreNoStyles(viewDef: ViewDefinition): ViewDefinition {
if (viewDef.encapsulation === ViewEncapsulation.EMULATED) {
return new ViewDefinition({
componentId: viewDef.componentId,
templateAbsUrl: viewDef.templateAbsUrl, template: viewDef.template,
styleAbsUrls: viewDef.styleAbsUrls,
styles: viewDef.styles,
directives: viewDef.directives,
encapsulation: ViewEncapsulation.NONE
});
} else {
return viewDef;
}
}
}
@Injectable()
export class DefaultDomCompiler extends DomCompiler {
constructor(parser: Parser, shadowDomStrategy: ShadowDomStrategy, viewLoader: ViewLoader) {
super(new DefaultStepFactory(parser, shadowDomStrategy), viewLoader,
shadowDomStrategy.hasNativeContentElement());
constructor(parser: Parser, viewLoader: ViewLoader, sharedStylesHost: SharedStylesHost,
@Inject(APP_ID_TOKEN) appId: any) {
super(new DefaultStepFactory(parser, appId), viewLoader, sharedStylesHost);
}
}
@@ -29,6 +29,8 @@ export class DirectiveParser implements CompileStep {
}
}
processStyle(style: string): string { return style; }
_ensureComponentOnlyHasElementSelector(selector, directive) {
var isElementSelector = selector.length === 1 && selector[0].isElementSelector();
if (!isElementSelector && directive.type === DirectiveMetadata.COMPONENT_TYPE) {
@@ -37,7 +39,7 @@ export class DirectiveParser implements CompileStep {
}
}
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
var attrs = current.attrs();
var classList = current.classList();
var cssSelector = new CssSelector();
@@ -25,7 +25,9 @@ var BIND_NAME_REGEXP =
export class PropertyBindingParser implements CompileStep {
constructor(private _parser: Parser) {}
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
processStyle(style: string): string { return style; }
processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
var attrs = current.attrs();
var newAttrs = new Map();
@@ -0,0 +1,73 @@
import {CompileStep} from '../compiler/compile_step';
import {CompileElement} from '../compiler/compile_element';
import {CompileControl} from '../compiler/compile_control';
import {ViewDefinition, ViewEncapsulation, ViewType} from '../../api';
import {NG_CONTENT_ELEMENT_NAME, isElementWithTag} from '../util';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {ShadowCss} from './shadow_css';
export class StyleEncapsulator implements CompileStep {
constructor(private _appId: string, private _view: ViewDefinition,
private _componentUIDsCache: Map<string, string>) {}
processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
if (isElementWithTag(current.element, NG_CONTENT_ELEMENT_NAME)) {
current.inheritedProtoView.bindNgContent();
} else {
if (this._view.encapsulation === ViewEncapsulation.EMULATED) {
this._processEmulatedScopedElement(current, parent);
}
}
}
processStyle(style: string): string {
var encapsulation = this._view.encapsulation;
if (encapsulation === ViewEncapsulation.EMULATED) {
return this._shimCssForComponent(style, this._view.componentId);
} else {
return style;
}
}
_processEmulatedScopedElement(current: CompileElement, parent: CompileElement): void {
var element = current.element;
var hostComponentId = this._view.componentId;
var viewType = current.inheritedProtoView.type;
// Shim the element as a child of the compiled component
if (viewType !== ViewType.HOST && isPresent(hostComponentId)) {
var contentAttribute = getContentAttribute(this._getComponentId(hostComponentId));
DOM.setAttribute(element, contentAttribute, '');
// also shim the host
if (isBlank(parent) && viewType == ViewType.COMPONENT) {
var hostAttribute = getHostAttribute(this._getComponentId(hostComponentId));
current.inheritedProtoView.setHostAttribute(hostAttribute, '');
}
}
}
_shimCssForComponent(cssText: string, componentId: string): string {
var id = this._getComponentId(componentId);
var shadowCss = new ShadowCss();
return shadowCss.shimCssText(cssText, getContentAttribute(id), getHostAttribute(id));
}
_getComponentId(componentStringId: string): string {
var id = this._componentUIDsCache.get(componentStringId);
if (isBlank(id)) {
id = `${this._appId}-${this._componentUIDsCache.size}`;
this._componentUIDsCache.set(componentStringId, id);
}
return id;
}
}
// Return the attribute to be added to the component
function getHostAttribute(compId: string): string {
return `_nghost-${compId}`;
}
// Returns the attribute to be added on every single element nodes in the component
function getContentAttribute(compId: string): string {
return `_ngcontent-${compId}`;
}
@@ -13,7 +13,9 @@ import {CompileControl} from './compile_control';
export class TextInterpolationParser implements CompileStep {
constructor(public _parser: Parser) {}
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
processStyle(style: string): string { return style; }
processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
if (!current.compileChildren) {
return;
}
@@ -10,14 +10,17 @@ import {
import {Map, MapWrapper, ListWrapper, List} from 'angular2/src/facade/collection';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ViewDefinition} from '../../api';
import {XHR} from 'angular2/src/render/xhr';
import {ViewDefinition} from '../../api';
import {StyleInliner} from './style_inliner';
import {StyleUrlResolver} from './style_url_resolver';
export class TemplateAndStyles {
constructor(public template: string, public styles: string[]) {}
}
/**
* Strategy to load component views.
* TODO: Make public API once we are more confident in this approach.
@@ -29,33 +32,32 @@ export class ViewLoader {
constructor(private _xhr: XHR, private _styleInliner: StyleInliner,
private _styleUrlResolver: StyleUrlResolver) {}
load(view: ViewDefinition): Promise</*element*/ any> {
let tplElAndStyles: List<string | Promise<string>> = [this._loadHtml(view)];
if (isPresent(view.styles)) {
view.styles.forEach((cssText: string) => {
let textOrPromise = this._resolveAndInlineCssText(cssText, view.templateAbsUrl);
tplElAndStyles.push(textOrPromise);
load(viewDef: ViewDefinition): Promise<TemplateAndStyles> {
let tplAndStyles: List<Promise<TemplateAndStyles>| Promise<string>| string> =
[this._loadHtml(viewDef.template, viewDef.templateAbsUrl)];
if (isPresent(viewDef.styles)) {
viewDef.styles.forEach((cssText: string) => {
let textOrPromise = this._resolveAndInlineCssText(cssText, viewDef.templateAbsUrl);
tplAndStyles.push(textOrPromise);
});
}
if (isPresent(view.styleAbsUrls)) {
view.styleAbsUrls.forEach(url => {
if (isPresent(viewDef.styleAbsUrls)) {
viewDef.styleAbsUrls.forEach(url => {
let promise = this._loadText(url).then(
cssText => this._resolveAndInlineCssText(cssText, view.templateAbsUrl));
tplElAndStyles.push(promise);
cssText => this._resolveAndInlineCssText(cssText, viewDef.templateAbsUrl));
tplAndStyles.push(promise);
});
}
// Inline the styles from the @View annotation and return a template element
return PromiseWrapper.all(tplElAndStyles)
.then((res: List<string>) => {
let tplEl = res[0];
let cssTexts = ListWrapper.slice(res, 1);
// Inline the styles from the @View annotation
return PromiseWrapper.all(tplAndStyles)
.then((res: List<TemplateAndStyles | string>) => {
let loadedTplAndStyles = <TemplateAndStyles>res[0];
let styles = <string[]>ListWrapper.slice(res, 1);
_insertCssTexts(DOM.content(tplEl), cssTexts);
return tplEl;
return new TemplateAndStyles(loadedTplAndStyles.template,
loadedTplAndStyles.styles.concat(styles));
});
}
@@ -77,40 +79,54 @@ export class ViewLoader {
}
// Load the html and inline any style tags
private _loadHtml(view: ViewDefinition): Promise<any /* element */> {
private _loadHtml(template: string, templateAbsUrl: string): Promise<TemplateAndStyles> {
let html;
// Load the HTML
if (isPresent(view.template)) {
html = PromiseWrapper.resolve(view.template);
} else if (isPresent(view.templateAbsUrl)) {
html = this._loadText(view.templateAbsUrl);
if (isPresent(template)) {
html = PromiseWrapper.resolve(template);
} else if (isPresent(templateAbsUrl)) {
html = this._loadText(templateAbsUrl);
} else {
throw new BaseException('View should have either the templateUrl or template property set');
}
return html.then(html => {
var tplEl = DOM.createTemplate(html);
// Replace $baseUrl with the base url for the template
let templateAbsUrl = view.templateAbsUrl;
if (isPresent(templateAbsUrl) && templateAbsUrl.indexOf("/") >= 0) {
let baseUrl = templateAbsUrl.substring(0, templateAbsUrl.lastIndexOf("/"));
this._substituteBaseUrl(DOM.content(tplEl), baseUrl);
}
let styleEls = DOM.querySelectorAll(DOM.content(tplEl), 'STYLE');
let unresolvedStyles: string[] = [];
for (let i = 0; i < styleEls.length; i++) {
var styleEl = styleEls[i];
unresolvedStyles.push(DOM.getText(styleEl));
DOM.remove(styleEl);
}
let syncStyles: string[] = [];
let asyncStyles: Promise<string>[] = [];
// Inline the style tags from the html
let styleEls = DOM.querySelectorAll(DOM.content(tplEl), 'STYLE');
let promises: List<Promise<string>> = [];
for (let i = 0; i < styleEls.length; i++) {
let promise = this._resolveAndInlineElement(styleEls[i], view.templateAbsUrl);
if (isPromise(promise)) {
promises.push(promise);
let styleEl = styleEls[i];
let resolvedStyled = this._resolveAndInlineCssText(DOM.getText(styleEl), templateAbsUrl);
if (isPromise(resolvedStyled)) {
asyncStyles.push(<Promise<string>>resolvedStyled);
} else {
syncStyles.push(<string>resolvedStyled);
}
}
return promises.length > 0 ? PromiseWrapper.all(promises).then(_ => tplEl) : tplEl;
if (asyncStyles.length === 0) {
return PromiseWrapper.resolve(new TemplateAndStyles(DOM.getInnerHTML(tplEl), syncStyles));
} else {
return PromiseWrapper.all(asyncStyles)
.then(loadedStyles => new TemplateAndStyles(DOM.getInnerHTML(tplEl),
syncStyles.concat(<string[]>loadedStyles)));
}
});
}
@@ -139,43 +155,8 @@ export class ViewLoader {
}
}
/**
* Inlines a style element.
*
* @param styleEl The style element
* @param baseUrl The base url
* @returns {Promise<any>} null when no @import rule exist in the css or a Promise
* @private
*/
private _resolveAndInlineElement(styleEl, baseUrl: string): Promise<any> {
let textOrPromise = this._resolveAndInlineCssText(DOM.getText(styleEl), baseUrl);
if (isPromise(textOrPromise)) {
return (<Promise<string>>textOrPromise).then(css => { DOM.setText(styleEl, css); });
} else {
DOM.setText(styleEl, <string>textOrPromise);
return null;
}
}
private _resolveAndInlineCssText(cssText: string, baseUrl: string): string | Promise<string> {
cssText = this._styleUrlResolver.resolveUrls(cssText, baseUrl);
return this._styleInliner.inlineImports(cssText, baseUrl);
}
}
function _insertCssTexts(element, cssTexts: List<string>): void {
if (cssTexts.length == 0) return;
let insertBefore = DOM.firstChild(element);
for (let i = cssTexts.length - 1; i >= 0; i--) {
let styleEl = DOM.createStyleElement(cssTexts[i]);
if (isPresent(insertBefore)) {
DOM.insertBefore(insertBefore, styleEl);
} else {
DOM.appendChild(element, styleEl);
}
insertBefore = styleEl;
}
}
@@ -28,7 +28,9 @@ import {dashCaseToCamelCase} from '../util';
export class ViewSplitter implements CompileStep {
constructor(public _parser: Parser) {}
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
processStyle(style: string): string { return style; }
processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
var attrs = current.attrs();
var templateBindings = attrs.get('template');
var hasTemplateBinding = isPresent(templateBindings);
@@ -15,6 +15,7 @@ import {EventManager} from './events/event_manager';
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './view/proto_view';
import {DomView, DomViewRef, resolveInternalDomView} from './view/view';
import {DomFragmentRef, resolveInternalDomFragment} from './view/fragment';
import {DomSharedStylesHost} from './view/shared_styles_host';
import {
NG_BINDING_CLASS_SELECTOR,
NG_BINDING_CLASS,
@@ -31,9 +32,8 @@ import {
RenderViewWithFragments
} from '../api';
export const DOCUMENT_TOKEN: OpaqueToken = CONST_EXPR(new OpaqueToken('DocumentToken'));
export const DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES: OpaqueToken =
CONST_EXPR(new OpaqueToken('DomReflectPropertiesAsAttributes'));
import {DOCUMENT_TOKEN, DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from './dom_tokens';
const REFLECT_PREFIX: string = 'ng-reflect-';
@Injectable()
@@ -41,7 +41,8 @@ export class DomRenderer extends Renderer {
_document;
_reflectPropertiesAsAttributes: boolean;
constructor(public _eventManager: EventManager, @Inject(DOCUMENT_TOKEN) document,
constructor(public _eventManager: EventManager, private _domSharedStylesHost: DomSharedStylesHost,
@Inject(DOCUMENT_TOKEN) document,
@Inject(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES) reflectPropertiesAsAttributes:
boolean) {
super();
@@ -65,7 +66,14 @@ export class DomRenderer extends Renderer {
}
destroyView(viewRef: RenderViewRef) {
// noop for now
var view = resolveInternalDomView(viewRef);
var elementBinders = view.proto.elementBinders;
for (var i = 0; i < elementBinders.length; i++) {
var binder = elementBinders[i];
if (binder.hasNativeShadowRoot) {
this._domSharedStylesHost.removeHost(DOM.getShadowRoot(view.boundElements[i]));
}
}
}
getNativeElementSync(location: RenderElementRef): any {
@@ -226,7 +234,9 @@ export class DomRenderer extends Renderer {
// native shadow DOM
if (binder.hasNativeShadowRoot) {
var shadowRootWrapper = DOM.firstChild(element);
moveChildNodes(shadowRootWrapper, DOM.createShadowRoot(element));
var shadowRoot = DOM.createShadowRoot(element);
this._domSharedStylesHost.addHost(shadowRoot);
moveChildNodes(shadowRootWrapper, shadowRoot);
DOM.remove(shadowRootWrapper);
}
@@ -0,0 +1,24 @@
import {OpaqueToken, bind, Binding} from 'angular2/di';
import {CONST_EXPR, StringWrapper, Math} from 'angular2/src/facade/lang';
export const DOCUMENT_TOKEN: OpaqueToken = CONST_EXPR(new OpaqueToken('DocumentToken'));
export const DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES: OpaqueToken =
CONST_EXPR(new OpaqueToken('DomReflectPropertiesAsAttributes'));
/**
* A unique id (string) for an angular application.
*/
export const APP_ID_TOKEN: OpaqueToken = CONST_EXPR(new OpaqueToken('AppId'));
/**
* Bindings that will generate a random APP_ID_TOKEN.
*/
export var APP_ID_RANDOM_BINDING: Binding =
bind(APP_ID_TOKEN).toFactory(() => `${randomChar()}${randomChar()}${randomChar()}`, []);
function randomChar(): string {
return StringWrapper.fromCharCode(97 + Math.floor(Math.random() * 25));
}
@@ -1,53 +0,0 @@
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {EmulatedUnscopedShadowDomStrategy} from './emulated_unscoped_shadow_dom_strategy';
import {
getContentAttribute,
getHostAttribute,
getComponentId,
shimCssForComponent,
insertStyleElement
} from './util';
/**
* This strategy emulates the Shadow DOM for the templates, styles **included**:
* - components templates are added as children of their component element,
* - both the template and the styles are modified so that styles are scoped to the component
* they belong to,
* - styles are moved from the templates to the styleHost (i.e. the document head).
*
* Notes:
* - styles are scoped to their component and will apply only to it,
* - a common subset of shadow DOM selectors are supported,
* - see `ShadowCss` for more information and limitations.
*/
export class EmulatedScopedShadowDomStrategy extends EmulatedUnscopedShadowDomStrategy {
constructor(styleHost) { super(styleHost); }
processStyleElement(hostComponentId: string, templateUrl: string, styleEl: Element): void {
let cssText = DOM.getText(styleEl);
cssText = shimCssForComponent(cssText, hostComponentId);
DOM.setText(styleEl, cssText);
this._moveToStyleHost(styleEl);
}
_moveToStyleHost(styleEl): void {
DOM.remove(styleEl);
insertStyleElement(this.styleHost, styleEl);
}
processElement(hostComponentId: string, elementComponentId: string, element: Element): void {
// Shim the element as a child of the compiled component
if (isPresent(hostComponentId)) {
var contentAttribute = getContentAttribute(getComponentId(hostComponentId));
DOM.setAttribute(element, contentAttribute, '');
}
// If the current element is also a component, shim it as a host
if (isPresent(elementComponentId)) {
var hostAttribute = getHostAttribute(getComponentId(elementComponentId));
DOM.setAttribute(element, hostAttribute, '');
}
}
}
@@ -1,24 +0,0 @@
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {insertSharedStyleText} from './util';
/**
* This strategy emulates the Shadow DOM for the templates, styles **excluded**:
* - components templates are added as children of their component element,
* - styles are moved from the templates to the styleHost (i.e. the document head).
*
* Notes:
* - styles are **not** scoped to their component and will apply to the whole document,
* - you can **not** use shadow DOM specific selectors in the styles
*/
export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
constructor(public styleHost) { super(); }
hasNativeContentElement(): boolean { return false; }
processStyleElement(hostComponentId: string, templateUrl: string, styleEl: Element): void {
var cssText = DOM.getText(styleEl);
insertSharedStyleText(cssText, this.styleHost, styleEl);
}
}
@@ -1,13 +0,0 @@
import {Injectable} from 'angular2/di';
import {ShadowDomStrategy} from './shadow_dom_strategy';
/**
* This strategies uses the native Shadow DOM support.
*
* The templates for the component are inserted in a Shadow Root created on the component element.
* Hence they are strictly isolated.
*/
@Injectable()
export class NativeShadowDomStrategy extends ShadowDomStrategy {
hasNativeContentElement(): boolean { return true; }
}
@@ -1,30 +0,0 @@
import {CompileStep} from '../compiler/compile_step';
import {CompileElement} from '../compiler/compile_element';
import {CompileControl} from '../compiler/compile_control';
import {ViewDefinition} from '../../api';
import {ShadowDomStrategy} from './shadow_dom_strategy';
import {NG_CONTENT_ELEMENT_NAME, isElementWithTag} from '../util';
export class ShadowDomCompileStep implements CompileStep {
constructor(public _shadowDomStrategy: ShadowDomStrategy, public _view: ViewDefinition) {}
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
if (isElementWithTag(current.element, NG_CONTENT_ELEMENT_NAME)) {
current.inheritedProtoView.bindNgContent();
} else if (isElementWithTag(current.element, 'style')) {
this._processStyleElement(current, control);
} else {
var componentId = current.isBound() ? current.inheritedElementBinder.componentId : null;
this._shadowDomStrategy.processElement(this._view.componentId, componentId, current.element);
}
}
_processStyleElement(current: CompileElement, control: CompileControl) {
this._shadowDomStrategy.processStyleElement(this._view.componentId, this._view.templateAbsUrl,
current.element);
// 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();
}
}
@@ -1,13 +0,0 @@
import {isBlank, isPresent} from 'angular2/src/facade/lang';
export class ShadowDomStrategy {
// Whether the strategy understands the native <content> tag
hasNativeContentElement(): boolean { return true; }
// An optional step that can modify the template style elements.
processStyleElement(hostComponentId: string, templateUrl: string, styleElement: HTMLStyleElement):
void {}
// An optional step that can modify the template elements (style elements exlcuded).
processElement(hostComponentId: string, elementComponentId: string, element: HTMLElement): void {}
}

Some files were not shown because too many files have changed in this diff Show More