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:
@ -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);
|
||||
}
|
||||
|
||||
|
24
modules/angular2/src/render/dom/dom_tokens.ts
Normal file
24
modules/angular2/src/render/dom/dom_tokens.ts
Normal file
@ -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 {}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
import {MapWrapper, Map} from 'angular2/src/facade/collection';
|
||||
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {ShadowCss} from './shadow_css';
|
||||
|
||||
var _componentUIDs: Map<string, int> = new Map();
|
||||
var _nextComponentUID: int = 0;
|
||||
var _sharedStyleTexts: Map<string, boolean> = new Map();
|
||||
var _lastInsertedStyleEl;
|
||||
|
||||
export function getComponentId(componentStringId: string): number {
|
||||
var id = _componentUIDs.get(componentStringId);
|
||||
if (isBlank(id)) {
|
||||
id = _nextComponentUID++;
|
||||
_componentUIDs.set(componentStringId, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
export function insertSharedStyleText(cssText, styleHost, styleEl) {
|
||||
if (!_sharedStyleTexts.has(cssText)) {
|
||||
// Styles are unscoped and shared across components, only append them to the head
|
||||
// when there are not present yet
|
||||
_sharedStyleTexts.set(cssText, true);
|
||||
insertStyleElement(styleHost, styleEl);
|
||||
}
|
||||
}
|
||||
|
||||
export function insertStyleElement(host, styleEl) {
|
||||
if (isBlank(_lastInsertedStyleEl)) {
|
||||
var firstChild = DOM.firstChild(host);
|
||||
if (isPresent(firstChild)) {
|
||||
DOM.insertBefore(firstChild, styleEl);
|
||||
} else {
|
||||
DOM.appendChild(host, styleEl);
|
||||
}
|
||||
} else {
|
||||
DOM.insertAfter(_lastInsertedStyleEl, styleEl);
|
||||
}
|
||||
_lastInsertedStyleEl = styleEl;
|
||||
}
|
||||
|
||||
// Return the attribute to be added to the component
|
||||
export function getHostAttribute(id: int): string {
|
||||
return `_nghost-${id}`;
|
||||
}
|
||||
|
||||
// Returns the attribute to be added on every single element nodes in the component
|
||||
export function getContentAttribute(id: int): string {
|
||||
return `_ngcontent-${id}`;
|
||||
}
|
||||
|
||||
export function shimCssForComponent(cssText: string, componentId: string): string {
|
||||
var id = getComponentId(componentId);
|
||||
var shadowCss = new ShadowCss();
|
||||
return shadowCss.shimCssText(cssText, getContentAttribute(id), getHostAttribute(id));
|
||||
}
|
||||
|
||||
// Reset the caches - used for tests only
|
||||
export function resetShadowDomCache() {
|
||||
_componentUIDs.clear();
|
||||
_nextComponentUID = 0;
|
||||
_sharedStyleTexts.clear();
|
||||
_lastInsertedStyleEl = null;
|
||||
}
|
@ -122,4 +122,21 @@ export function queryBoundTextNodeIndices(parentNode: Node, boundTextNodes: Map<
|
||||
resultCallback(node, j, boundTextNodes.get(node));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function prependAll(parentNode: Node, nodes: Node[]) {
|
||||
var lastInsertedNode = null;
|
||||
nodes.forEach(node => {
|
||||
if (isBlank(lastInsertedNode)) {
|
||||
var firstChild = DOM.firstChild(parentNode);
|
||||
if (isPresent(firstChild)) {
|
||||
DOM.insertBefore(firstChild, node);
|
||||
} else {
|
||||
DOM.appendChild(parentNode, node);
|
||||
}
|
||||
} else {
|
||||
DOM.insertAfter(lastInsertedNode, node);
|
||||
}
|
||||
lastInsertedNode = node;
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {DomElementBinder} from './element_binder';
|
||||
import {RenderProtoViewRef, ViewType} from '../../api';
|
||||
import {RenderProtoViewRef, ViewType, ViewEncapsulation} from '../../api';
|
||||
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
@ -14,9 +14,10 @@ export class DomProtoViewRef extends RenderProtoViewRef {
|
||||
}
|
||||
|
||||
export class DomProtoView {
|
||||
static create(type: ViewType, rootElement: Element, fragmentsRootNodeCount: number[],
|
||||
rootTextNodeIndices: number[],
|
||||
elementBinders: List<DomElementBinder>): DomProtoView {
|
||||
static create(type: ViewType, rootElement: Element, viewEncapsulation: ViewEncapsulation,
|
||||
fragmentsRootNodeCount: number[], rootTextNodeIndices: number[],
|
||||
elementBinders: List<DomElementBinder>,
|
||||
hostAttributes: Map<string, string>): DomProtoView {
|
||||
var boundTextNodeCount = rootTextNodeIndices.length;
|
||||
for (var i = 0; i < elementBinders.length; i++) {
|
||||
boundTextNodeCount += elementBinders[i].textNodeIndices.length;
|
||||
@ -24,12 +25,15 @@ export class DomProtoView {
|
||||
var isSingleElementFragment = fragmentsRootNodeCount.length === 1 &&
|
||||
fragmentsRootNodeCount[0] === 1 &&
|
||||
DOM.isElementNode(DOM.firstChild(DOM.content(rootElement)));
|
||||
return new DomProtoView(type, rootElement, elementBinders, rootTextNodeIndices,
|
||||
boundTextNodeCount, fragmentsRootNodeCount, isSingleElementFragment);
|
||||
return new DomProtoView(type, rootElement, viewEncapsulation, elementBinders, hostAttributes,
|
||||
rootTextNodeIndices, boundTextNodeCount, fragmentsRootNodeCount,
|
||||
isSingleElementFragment);
|
||||
}
|
||||
|
||||
constructor(public type: ViewType, public rootElement: Element,
|
||||
public elementBinders: List<DomElementBinder>, public rootTextNodeIndices: number[],
|
||||
public encapsulation: ViewEncapsulation,
|
||||
public elementBinders: List<DomElementBinder>,
|
||||
public hostAttributes: Map<string, string>, public rootTextNodeIndices: number[],
|
||||
public boundTextNodeCount: number, public fragmentsRootNodeCount: number[],
|
||||
public isSingleElementFragment: boolean) {}
|
||||
}
|
||||
|
@ -35,9 +35,10 @@ export class ProtoViewBuilder {
|
||||
elements: List<ElementBinderBuilder> = [];
|
||||
rootTextBindings: Map<Node, ASTWithSource> = new Map();
|
||||
ngContentCount: number = 0;
|
||||
hostAttributes: Map<string, string> = new Map();
|
||||
|
||||
constructor(public rootElement, public type: api.ViewType,
|
||||
public useNativeShadowDom: boolean = false) {}
|
||||
public viewEncapsulation: api.ViewEncapsulation) {}
|
||||
|
||||
bindElement(element: HTMLElement, description: string = null): ElementBinderBuilder {
|
||||
var builder = new ElementBinderBuilder(this.elements.length, element, description);
|
||||
@ -65,6 +66,8 @@ export class ProtoViewBuilder {
|
||||
|
||||
bindNgContent() { this.ngContentCount++; }
|
||||
|
||||
setHostAttribute(name: string, value: string) { this.hostAttributes.set(name, value); }
|
||||
|
||||
build(): api.ProtoViewDto {
|
||||
var domElementBinders = [];
|
||||
|
||||
@ -119,7 +122,7 @@ export class ProtoViewBuilder {
|
||||
domElementBinders.push(new DomElementBinder({
|
||||
textNodeIndices: textNodeIndices,
|
||||
hasNestedProtoView: isPresent(nestedProtoView) || isPresent(ebb.componentId),
|
||||
hasNativeShadowRoot: isPresent(ebb.componentId) && this.useNativeShadowDom,
|
||||
hasNativeShadowRoot: false,
|
||||
eventLocals: new LiteralArray(ebb.eventBuilder.buildEventLocals()),
|
||||
localEvents: ebb.eventBuilder.buildLocalEvents(),
|
||||
globalEvents: ebb.eventBuilder.buildGlobalEvents()
|
||||
@ -127,8 +130,9 @@ export class ProtoViewBuilder {
|
||||
});
|
||||
var rootNodeCount = DOM.childNodes(DOM.content(this.rootElement)).length;
|
||||
return new api.ProtoViewDto({
|
||||
render: new DomProtoViewRef(DomProtoView.create(this.type, this.rootElement, [rootNodeCount],
|
||||
rootTextNodeIndices, domElementBinders)),
|
||||
render: new DomProtoViewRef(
|
||||
DomProtoView.create(this.type, this.rootElement, this.viewEncapsulation, [rootNodeCount],
|
||||
rootTextNodeIndices, domElementBinders, this.hostAttributes)),
|
||||
type: this.type,
|
||||
elementBinders: apiElementBinders,
|
||||
variableBindings: this.variableBindings,
|
||||
@ -178,7 +182,8 @@ export class ElementBinderBuilder {
|
||||
if (isPresent(this.nestedProtoView)) {
|
||||
throw new BaseException('Only one nested view per element is allowed');
|
||||
}
|
||||
this.nestedProtoView = new ProtoViewBuilder(rootElement, api.ViewType.EMBEDDED);
|
||||
this.nestedProtoView =
|
||||
new ProtoViewBuilder(rootElement, api.ViewType.EMBEDDED, api.ViewEncapsulation.NONE);
|
||||
return this.nestedProtoView;
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,15 @@
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {isPresent, isBlank, BaseException, isArray} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {ListWrapper, SetWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view';
|
||||
import {DomElementBinder} from './element_binder';
|
||||
import {RenderProtoViewMergeMapping, RenderProtoViewRef, ViewType} from '../../api';
|
||||
import {
|
||||
RenderProtoViewMergeMapping,
|
||||
RenderProtoViewRef,
|
||||
ViewType,
|
||||
ViewEncapsulation
|
||||
} from '../../api';
|
||||
import {
|
||||
NG_BINDING_CLASS,
|
||||
NG_CONTENT_ELEMENT_NAME,
|
||||
@ -12,7 +17,9 @@ import {
|
||||
cloneAndQueryProtoView,
|
||||
queryBoundElements,
|
||||
queryBoundTextNodeIndices,
|
||||
NG_SHADOW_ROOT_ELEMENT_NAME
|
||||
NG_SHADOW_ROOT_ELEMENT_NAME,
|
||||
isElementWithTag,
|
||||
prependAll
|
||||
} from '../util';
|
||||
|
||||
export function mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRef | List<any>>):
|
||||
@ -26,7 +33,9 @@ export function mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRe
|
||||
// modify the DOM
|
||||
mergeEmbeddedPvsIntoComponentOrRootPv(clonedProtoViews, hostViewAndBinderIndices);
|
||||
var fragments = [];
|
||||
mergeComponents(clonedProtoViews, hostViewAndBinderIndices, fragments);
|
||||
var elementsWithNativeShadowRoot: Set<Element> = new Set();
|
||||
mergeComponents(clonedProtoViews, hostViewAndBinderIndices, fragments,
|
||||
elementsWithNativeShadowRoot);
|
||||
// Note: Need to remark parent elements of bound text nodes
|
||||
// so that we can find them later via queryBoundElements!
|
||||
markBoundTextNodeParentsAsBoundElements(clonedProtoViews);
|
||||
@ -42,8 +51,9 @@ export function mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRe
|
||||
var boundTextNodeMap: Map<Node, any> = indexBoundTextNodes(clonedProtoViews);
|
||||
var rootTextNodeIndices =
|
||||
calcRootTextNodeIndices(rootNode, boundTextNodeMap, mergedBoundTextIndices);
|
||||
var mergedElementBinders = calcElementBinders(clonedProtoViews, mergedBoundElements,
|
||||
boundTextNodeMap, mergedBoundTextIndices);
|
||||
var mergedElementBinders =
|
||||
calcElementBinders(clonedProtoViews, mergedBoundElements, elementsWithNativeShadowRoot,
|
||||
boundTextNodeMap, mergedBoundTextIndices);
|
||||
|
||||
// create element / text index mappings
|
||||
var mappedElementIndices = calcMappedElementIndices(clonedProtoViews, mergedBoundElements);
|
||||
@ -53,9 +63,9 @@ export function mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRe
|
||||
var hostElementIndicesByViewIndex =
|
||||
calcHostElementIndicesByViewIndex(clonedProtoViews, hostViewAndBinderIndices);
|
||||
var nestedViewCounts = calcNestedViewCounts(hostViewAndBinderIndices);
|
||||
var mergedProtoView =
|
||||
DomProtoView.create(mainProtoView.original.type, rootElement, fragmentsRootNodeCount,
|
||||
rootTextNodeIndices, mergedElementBinders);
|
||||
var mergedProtoView = DomProtoView.create(
|
||||
mainProtoView.original.type, rootElement, mainProtoView.original.encapsulation,
|
||||
fragmentsRootNodeCount, rootTextNodeIndices, mergedElementBinders, new Map());
|
||||
return new RenderProtoViewMergeMapping(new DomProtoViewRef(mergedProtoView),
|
||||
fragmentsRootNodeCount.length, mappedElementIndices,
|
||||
mergedBoundElements.length, mappedTextIndices,
|
||||
@ -143,7 +153,8 @@ function calcNearestHostComponentOrRootPvIndices(clonedProtoViews: ClonedProtoVi
|
||||
}
|
||||
|
||||
function mergeComponents(clonedProtoViews: ClonedProtoView[], hostViewAndBinderIndices: number[][],
|
||||
targetFragments: Node[][]) {
|
||||
targetFragments: Node[][],
|
||||
targetElementsWithNativeShadowRoot: Set<Element>) {
|
||||
var hostProtoView = clonedProtoViews[0];
|
||||
hostProtoView.fragments.forEach((fragment) => targetFragments.push(fragment));
|
||||
|
||||
@ -153,13 +164,15 @@ function mergeComponents(clonedProtoViews: ClonedProtoView[], hostViewAndBinderI
|
||||
var hostProtoView = clonedProtoViews[hostViewIdx];
|
||||
var clonedProtoView = clonedProtoViews[viewIdx];
|
||||
if (clonedProtoView.original.type === ViewType.COMPONENT) {
|
||||
mergeComponent(hostProtoView, hostBinderIdx, clonedProtoView, targetFragments);
|
||||
mergeComponent(hostProtoView, hostBinderIdx, clonedProtoView, targetFragments,
|
||||
targetElementsWithNativeShadowRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mergeComponent(hostProtoView: ClonedProtoView, binderIdx: number,
|
||||
nestedProtoView: ClonedProtoView, targetFragments: Node[][]) {
|
||||
nestedProtoView: ClonedProtoView, targetFragments: Node[][],
|
||||
targetElementsWithNativeShadowRoot: Set<Element>) {
|
||||
var hostElement = hostProtoView.boundElements[binderIdx];
|
||||
|
||||
// We wrap the fragments into elements so that we can expand <ng-content>
|
||||
@ -176,8 +189,14 @@ function mergeComponent(hostProtoView: ClonedProtoView, binderIdx: number,
|
||||
|
||||
// unwrap the fragment elements into arrays of nodes after projecting
|
||||
var fragments = extractFragmentNodesFromElements(fragmentElements);
|
||||
appendComponentNodesToHost(hostProtoView, binderIdx, fragments[0]);
|
||||
|
||||
var useNativeShadowRoot = nestedProtoView.original.encapsulation === ViewEncapsulation.NATIVE;
|
||||
if (useNativeShadowRoot) {
|
||||
targetElementsWithNativeShadowRoot.add(hostElement);
|
||||
}
|
||||
MapWrapper.forEach(nestedProtoView.original.hostAttributes, (attrValue, attrName) => {
|
||||
DOM.setAttribute(hostElement, attrName, attrValue);
|
||||
});
|
||||
appendComponentNodesToHost(hostProtoView, binderIdx, fragments[0], useNativeShadowRoot);
|
||||
for (var i = 1; i < fragments.length; i++) {
|
||||
targetFragments.push(fragments[i]);
|
||||
}
|
||||
@ -209,10 +228,9 @@ function findContentElements(fragmentElements: Element[]): Element[] {
|
||||
}
|
||||
|
||||
function appendComponentNodesToHost(hostProtoView: ClonedProtoView, binderIdx: number,
|
||||
componentRootNodes: Node[]) {
|
||||
componentRootNodes: Node[], useNativeShadowRoot: boolean) {
|
||||
var hostElement = hostProtoView.boundElements[binderIdx];
|
||||
var binder = hostProtoView.original.elementBinders[binderIdx];
|
||||
if (binder.hasNativeShadowRoot) {
|
||||
if (useNativeShadowRoot) {
|
||||
var shadowRootWrapper = DOM.createElement(NG_SHADOW_ROOT_ELEMENT_NAME);
|
||||
for (var i = 0; i < componentRootNodes.length; i++) {
|
||||
DOM.appendChild(shadowRootWrapper, componentRootNodes[i]);
|
||||
@ -298,6 +316,7 @@ function calcRootTextNodeIndices(rootNode: Node, boundTextNodes: Map<Node, any>,
|
||||
}
|
||||
|
||||
function calcElementBinders(clonedProtoViews: ClonedProtoView[], mergedBoundElements: Element[],
|
||||
elementsWithNativeShadowRoot: Set<Element>,
|
||||
boundTextNodes: Map<Node, any>,
|
||||
targetBoundTextIndices: Map<Node, number>): DomElementBinder[] {
|
||||
var elementBinderByElement: Map<Element, DomElementBinder> =
|
||||
@ -311,7 +330,8 @@ function calcElementBinders(clonedProtoViews: ClonedProtoView[], mergedBoundElem
|
||||
targetBoundTextIndices.set(textNode, targetBoundTextIndices.size);
|
||||
});
|
||||
mergedElementBinders.push(
|
||||
updateElementBinderTextNodeIndices(elementBinderByElement.get(element), textNodeIndices));
|
||||
updateElementBinders(elementBinderByElement.get(element), textNodeIndices,
|
||||
SetWrapper.has(elementsWithNativeShadowRoot, element)));
|
||||
}
|
||||
return mergedElementBinders;
|
||||
}
|
||||
@ -330,8 +350,8 @@ function indexElementBindersByElement(mergableProtoViews: ClonedProtoView[]):
|
||||
return elementBinderByElement;
|
||||
}
|
||||
|
||||
function updateElementBinderTextNodeIndices(elementBinder: DomElementBinder,
|
||||
textNodeIndices: number[]): DomElementBinder {
|
||||
function updateElementBinders(elementBinder: DomElementBinder, textNodeIndices: number[],
|
||||
hasNativeShadowRoot: boolean): DomElementBinder {
|
||||
var result;
|
||||
if (isBlank(elementBinder)) {
|
||||
result = new DomElementBinder({
|
||||
@ -349,7 +369,7 @@ function updateElementBinderTextNodeIndices(elementBinder: DomElementBinder,
|
||||
eventLocals: elementBinder.eventLocals,
|
||||
localEvents: elementBinder.localEvents,
|
||||
globalEvents: elementBinder.globalEvents,
|
||||
hasNativeShadowRoot: elementBinder.hasNativeShadowRoot
|
||||
hasNativeShadowRoot: hasNativeShadowRoot
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
52
modules/angular2/src/render/dom/view/shared_styles_host.ts
Normal file
52
modules/angular2/src/render/dom/view/shared_styles_host.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {Inject, Injectable} from 'angular2/di';
|
||||
import {SetWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOCUMENT_TOKEN} from '../dom_tokens';
|
||||
|
||||
@Injectable()
|
||||
export class SharedStylesHost {
|
||||
protected _styles: string[] = [];
|
||||
protected _stylesSet: Set<string> = new Set();
|
||||
|
||||
constructor() {}
|
||||
|
||||
addStyles(styles: string[]) {
|
||||
var additions = [];
|
||||
styles.forEach(style => {
|
||||
if (!SetWrapper.has(this._stylesSet, style)) {
|
||||
this._stylesSet.add(style);
|
||||
this._styles.push(style);
|
||||
additions.push(style);
|
||||
}
|
||||
});
|
||||
this.onStylesAdded(additions);
|
||||
}
|
||||
|
||||
protected onStylesAdded(additions: string[]) {}
|
||||
|
||||
getAllStyles(): string[] { return this._styles; }
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DomSharedStylesHost extends SharedStylesHost {
|
||||
private _hostNodes: Set<Node> = new Set();
|
||||
constructor(@Inject(DOCUMENT_TOKEN) doc: any) {
|
||||
super();
|
||||
this._hostNodes.add(doc.head);
|
||||
}
|
||||
_addStylesToHost(styles: string[], host: Node) {
|
||||
for (var i = 0; i < styles.length; i++) {
|
||||
var style = styles[i];
|
||||
DOM.appendChild(host, DOM.createStyleElement(style));
|
||||
}
|
||||
}
|
||||
addHost(hostNode: Node) {
|
||||
this._addStylesToHost(this._styles, hostNode);
|
||||
this._hostNodes.add(hostNode);
|
||||
}
|
||||
removeHost(hostNode: Node) { SetWrapper.delete(this._hostNodes, hostNode); }
|
||||
|
||||
onStylesAdded(additions: string[]) {
|
||||
this._hostNodes.forEach((hostNode) => { this._addStylesToHost(additions, hostNode); });
|
||||
}
|
||||
}
|
@ -5,9 +5,8 @@
|
||||
*/
|
||||
|
||||
export * from './dom/compiler/view_loader';
|
||||
export * from './dom/view/shared_styles_host';
|
||||
export * from './dom/compiler/compiler';
|
||||
export * from './dom/dom_renderer';
|
||||
export * from './dom/shadow_dom/shadow_dom_strategy';
|
||||
export * from './dom/shadow_dom/native_shadow_dom_strategy';
|
||||
export * from './dom/shadow_dom/emulated_scoped_shadow_dom_strategy';
|
||||
export * from './dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
|
||||
export * from './dom/dom_tokens';
|
||||
export * from './api';
|
||||
|
Reference in New Issue
Block a user