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-24 15:28:44 -07:00
parent e40ff36832
commit 16e3d7e96e
77 changed files with 1228 additions and 890 deletions

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 {

View File

@ -14,5 +14,6 @@ export {
RenderViewWithFragments,
DomRenderer,
DOCUMENT_TOKEN,
APP_ID_TOKEN,
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES
} from './src/render/render';

View File

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

View File

@ -1 +1 @@
export {View as ViewAnnotation} from '../annotations_impl/view';
export {View as ViewAnnotation, ViewEncapsulation} from '../annotations_impl/view';

View File

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

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),

View File

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

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';

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {}
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -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';

View File

@ -16,7 +16,7 @@ import {
import {el} from './utils';
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
import {DOCUMENT_TOKEN} from 'angular2/src/render/render';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {DebugElement} from 'angular2/src/debug/debug_element';

View File

@ -15,10 +15,6 @@ import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
import {ViewResolver} from 'angular2/src/core/compiler/view_resolver';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
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 {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
import {UrlResolver} from 'angular2/src/services/url_resolver';
@ -54,9 +50,12 @@ import {RenderCompiler, Renderer} 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_TOKEN,
SharedStylesHost,
DomSharedStylesHost
} from 'angular2/src/render/render';
import {Serializer} from "angular2/src/web-workers/shared/serializer";
import {Log} from './utils';
@ -94,13 +93,14 @@ function _getAppBindings() {
return [
bind(DOCUMENT_TOKEN)
.toValue(appDoc),
bind(ShadowDomStrategy)
.toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]),
DomRenderer,
DefaultDomCompiler,
bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false),
bind(Renderer).toAlias(DomRenderer),
bind(APP_ID_TOKEN).toValue('a'),
DefaultDomCompiler,
bind(RenderCompiler).toAlias(DefaultDomCompiler),
DomSharedStylesHost,
bind(SharedStylesHost).toAlias(DomSharedStylesHost),
bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false),
ProtoViewFactory,
AppViewPool,
AppViewManager,

View File

@ -5,6 +5,7 @@ import 'dart:async';
import 'package:angular2/src/change_detection/parser/lexer.dart' as ng;
import 'package:angular2/src/change_detection/parser/parser.dart' as ng;
import 'package:angular2/src/core/compiler/proto_view_factory.dart';
import 'package:angular2/src/dom/dom_adapter.dart';
import 'package:angular2/src/render/api.dart';
import 'package:angular2/src/render/dom/compiler/compile_pipeline.dart';
import 'package:angular2/src/render/dom/compiler/style_inliner.dart';
@ -96,7 +97,7 @@ class _TemplateExtractor {
// Check for "imperative views".
if (viewDef.template == null && viewDef.templateAbsUrl == null) return null;
var templateEl = await _loader.load(viewDef);
var templateAndStyles = await _loader.load(viewDef);
// NOTE(kegluneq): Since this is a global, we must not have any async
// operations between saving and restoring it, otherwise we can get into
@ -108,7 +109,7 @@ class _TemplateExtractor {
var pipeline = new CompilePipeline(_factory.createSteps(viewDef));
var compileElements =
pipeline.process(templateEl, ViewType.COMPONENT, viewDef.componentId);
pipeline.processElements(DOM.createTemplate(templateAndStyles.template), ViewType.COMPONENT, viewDef);
var protoViewDto = compileElements[0].inheritedProtoView.build();
reflector.reflectionCapabilities = savedReflectionCapabilities;

View File

@ -213,7 +213,8 @@ export class Serializer {
'template': view.template,
'directives': this.serialize(view.directives, DirectiveMetadata),
'styleAbsUrls': view.styleAbsUrls,
'styles': view.styles
'styles': view.styles,
'encapsulation': view.encapsulation
};
}
@ -223,7 +224,8 @@ export class Serializer {
templateAbsUrl: obj['templateAbsUrl'], template: obj['template'],
directives: this.deserialize(obj['directives'], DirectiveMetadata),
styleAbsUrls: obj['styleAbsUrls'],
styles: obj['styles']
styles: obj['styles'],
encapsulation: obj['encapsulation']
});
}

View File

@ -24,15 +24,16 @@ 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 {DOM} from 'angular2/src/dom/dom_adapter';
import {NgZone} from 'angular2/src/core/zone/ng_zone';
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 {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
@ -89,14 +90,15 @@ function _injectorBindings(): List<Type | Binding | List<any>> {
return new EventManager(plugins, ngZone);
},
[NgZone]),
bind(ShadowDomStrategy)
.toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]),
bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false),
DomRenderer,
DefaultDomCompiler,
Serializer,
bind(Renderer).toAlias(DomRenderer),
APP_ID_RANDOM_BINDING,
DefaultDomCompiler,
bind(RenderCompiler).toAlias(DefaultDomCompiler),
DomSharedStylesHost,
bind(SharedStylesHost).toAlias(DomSharedStylesHost),
Serializer,
bind(ON_WEBWORKER).toValue(false),
RenderViewWithFragmentsStore,
RenderProtoViewRefStore,

View File

@ -20,7 +20,7 @@ import {bind, Inject, Injector} from 'angular2/di';
import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle';
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
import {Testability, TestabilityRegistry} from 'angular2/src/core/testability/testability';
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
import {DOCUMENT_TOKEN} from 'angular2/src/render/render';
@Component({selector: 'hello-app'})
@View({template: '{{greeting}} world!'})

View File

@ -24,7 +24,7 @@ import {Component, View, LifecycleEvent} from 'angular2/annotations';
import * as viewAnn from 'angular2/src/core/annotations_impl/view';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
import {DOCUMENT_TOKEN} from 'angular2/src/render/render';
import {DOM} from 'angular2/src/dom/dom_adapter';
export function main() {

View File

@ -1452,7 +1452,7 @@ class MyService {
}
@Component({selector: 'simple-imp-cmp'})
@View({renderer: 'simple-imp-cmp-renderer', template: ''})
@View({template: ''})
@Injectable()
class SimpleImperativeViewComponent {
done;

View File

@ -34,9 +34,9 @@ import {
ViewContainerRef,
ElementRef,
TemplateRef,
bind
bind,
ViewEncapsulation
} from 'angular2/angular2';
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/render/render';
export function main() {
describe('projection', () => {
@ -399,27 +399,21 @@ export function main() {
}));
if (DOM.supportsNativeShadowDOM()) {
describe('native shadow dom support', () => {
beforeEachBindings(
() => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; });
it('should support native content projection',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '<simple-native>' +
'<div>A</div>' +
'</simple-native>',
directives: [SimpleNative]
}))
.createAsync(MainComp)
.then((main) => {
it('should support native content projection',
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({
template: '<simple-native>' +
'<div>A</div>' +
'</simple-native>',
directives: [SimpleNative]
}))
.createAsync(MainComp)
.then((main) => {
expect(main.nativeElement).toHaveText('SIMPLE(A)');
async.done();
});
}));
});
expect(main.nativeElement).toHaveText('SIMPLE(A)');
async.done();
});
}));
}
});
@ -438,7 +432,11 @@ class Simple {
}
@Component({selector: 'simple-native'})
@View({template: 'SIMPLE(<content></content>)', directives: []})
@View({
template: 'SIMPLE(<content></content>)',
directives: [],
encapsulation: ViewEncapsulation.NATIVE
})
class SimpleNative {
}

View File

@ -17,26 +17,38 @@ import {Type, isBlank, stringify, isPresent, BaseException} from 'angular2/src/f
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {DomCompiler} from 'angular2/src/render/dom/compiler/compiler';
import {ProtoViewDto, ViewDefinition, DirectiveMetadata, ViewType} from 'angular2/src/render/api';
import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element';
import {
ProtoViewDto,
ViewDefinition,
DirectiveMetadata,
ViewType,
ViewEncapsulation
} from 'angular2/src/render/api';
import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step';
import {CompileStepFactory} from 'angular2/src/render/dom/compiler/compile_step_factory';
import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control';
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
import {ViewLoader, TemplateAndStyles} from 'angular2/src/render/dom/compiler/view_loader';
import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view';
import {SharedStylesHost} from 'angular2/src/render/dom/view/shared_styles_host';
import {MockStep} from './pipeline_spec';
export function runCompilerCommonTests() {
describe('DomCompiler', function() {
var mockStepFactory: MockStepFactory;
var sharedStylesHost: SharedStylesHost;
function createCompiler(processClosure, urlData = null) {
beforeEach(() => {sharedStylesHost = new SharedStylesHost()});
function createCompiler(processElementClosure = null, processStyleClosure = null,
urlData = null) {
if (isBlank(urlData)) {
urlData = new Map();
}
var tplLoader = new FakeViewLoader(urlData);
mockStepFactory = new MockStepFactory([new MockStep(processClosure)]);
return new DomCompiler(mockStepFactory, tplLoader, false);
mockStepFactory =
new MockStepFactory([new MockStep(processElementClosure, processStyleClosure)]);
return new DomCompiler(mockStepFactory, tplLoader, sharedStylesHost);
}
describe('compile', () => {
@ -61,12 +73,13 @@ export function runCompilerCommonTests() {
});
var dirMetadata = DirectiveMetadata.create(
{id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE});
{id: 'id', selector: 'custom', type: DirectiveMetadata.COMPONENT_TYPE});
compiler.compileHost(dirMetadata)
.then((protoView) => {
expect(DOM.tagName(DOM.firstChild(DOM.content(
resolveInternalDomProtoView(protoView.render).rootElement))))
.toEqual('CUSTOM');
resolveInternalDomProtoView(protoView.render).rootElement)))
.toLowerCase())
.toEqual('custom');
expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]);
expect(protoView.variableBindings)
.toEqual(MapWrapper.createFromStringMap({'a': 'b'}));
@ -88,7 +101,7 @@ export function runCompilerCommonTests() {
it('should load url templates', inject([AsyncTestCompleter], (async) => {
var urlData = MapWrapper.createFromStringMap({'someUrl': 'url component'});
var compiler = createCompiler(EMPTY_STEP, urlData);
var compiler = createCompiler(EMPTY_STEP, null, urlData);
compiler.compile(new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'}))
.then((protoView) => {
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement))
@ -98,7 +111,7 @@ export function runCompilerCommonTests() {
}));
it('should report loading errors', inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler(EMPTY_STEP, new Map());
var compiler = createCompiler(EMPTY_STEP, null, new Map());
PromiseWrapper.catchError(
compiler.compile(
new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'})),
@ -137,6 +150,110 @@ export function runCompilerCommonTests() {
});
describe('compile styles', () => {
it('should run the steps', inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler(null, (style) => { return style + 'b {};'; });
compiler.compile(new ViewDefinition(
{componentId: 'someComponent', template: '', styles: ['a {};']}))
.then((protoViewDto) => {
expect(sharedStylesHost.getAllStyles()).toEqual(['a {};b {};']);
async.done();
});
}));
it('should store the styles in the SharedStylesHost for ViewEncapsulation.NONE',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler();
compiler.compile(new ViewDefinition({
componentId: 'someComponent',
template: '',
styles: ['a {};'],
encapsulation: ViewEncapsulation.NONE
}))
.then((protoViewDto) => {
var domProtoView = resolveInternalDomProtoView(protoViewDto.render);
expect(DOM.getInnerHTML(domProtoView.rootElement)).toEqual('');
expect(sharedStylesHost.getAllStyles()).toEqual(['a {};']);
async.done();
});
}));
it('should store the styles in the SharedStylesHost for ViewEncapsulation.EMULATED',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler();
compiler.compile(new ViewDefinition({
componentId: 'someComponent',
template: '',
styles: ['a {};'],
encapsulation: ViewEncapsulation.EMULATED
}))
.then((protoViewDto) => {
var domProtoView = resolveInternalDomProtoView(protoViewDto.render);
expect(DOM.getInnerHTML(domProtoView.rootElement)).toEqual('');
expect(sharedStylesHost.getAllStyles()).toEqual(['a {};']);
async.done();
});
}));
if (DOM.supportsNativeShadowDOM()) {
it('should store the styles in the template for ViewEncapsulation.NATIVE',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler();
compiler.compile(new ViewDefinition({
componentId: 'someComponent',
template: '',
styles: ['a {};'],
encapsulation: ViewEncapsulation.NATIVE
}))
.then((protoViewDto) => {
var domProtoView = resolveInternalDomProtoView(protoViewDto.render);
expect(DOM.getInnerHTML(domProtoView.rootElement))
.toEqual('<style>a {};</style>');
expect(sharedStylesHost.getAllStyles()).toEqual([]);
async.done();
});
}));
}
it('should default to ViewEncapsulation.NONE if no styles are specified',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler();
compiler.compile(
new ViewDefinition({componentId: 'someComponent', template: '', styles: []}))
.then((protoView) => {
expect(mockStepFactory.viewDef.encapsulation).toBe(ViewEncapsulation.NONE);
async.done();
});
}));
it('should default to ViewEncapsulation.EMULATED if styles are specified',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler();
compiler.compile(new ViewDefinition(
{componentId: 'someComponent', template: '', styles: ['a {};']}))
.then((protoView) => {
expect(mockStepFactory.viewDef.encapsulation).toBe(ViewEncapsulation.EMULATED);
async.done();
});
}));
});
describe('mergeProtoViews', () => {
it('should store the styles of the merged ProtoView in the SharedStylesHost',
inject([AsyncTestCompleter], (async) => {
var compiler = createCompiler();
compiler.compile(new ViewDefinition(
{componentId: 'someComponent', template: '', styles: ['a {};']}))
.then(protoViewDto => compiler.mergeProtoViewsRecursively([protoViewDto.render]))
.then(_ => {
expect(sharedStylesHost.getAllStyles()).toEqual(['a {};']);
async.done();
});
}));
});
});
}
@ -155,14 +272,6 @@ class MockStepFactory extends CompileStepFactory {
}
}
class MockStep implements CompileStep {
processClosure: Function;
constructor(process) { this.processClosure = process; }
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
this.processClosure(parent, current, control);
}
}
var EMPTY_STEP = (parent, current, control) => {
if (isPresent(parent)) {
current.inheritedProtoView = parent.inheritedProtoView;
@ -176,16 +285,17 @@ class FakeViewLoader extends ViewLoader {
this._urlData = urlData;
}
load(view: ViewDefinition): Promise<any> {
if (isPresent(view.template)) {
return PromiseWrapper.resolve(DOM.createTemplate(view.template));
load(viewDef): Promise<any> {
var styles = isPresent(viewDef.styles) ? viewDef.styles : [];
if (isPresent(viewDef.template)) {
return PromiseWrapper.resolve(new TemplateAndStyles(viewDef.template, styles));
}
if (isPresent(view.templateAbsUrl)) {
var content = this._urlData.get(view.templateAbsUrl);
if (isPresent(viewDef.templateAbsUrl)) {
var content = this._urlData.get(viewDef.templateAbsUrl);
return isPresent(content) ?
PromiseWrapper.resolve(DOM.createTemplate(content)) :
PromiseWrapper.reject(`Failed to fetch url "${view.templateAbsUrl}"`, null);
PromiseWrapper.resolve(new TemplateAndStyles(content, styles)) :
PromiseWrapper.reject(`Failed to fetch url "${viewDef.templateAbsUrl}"`, null);
}
throw new BaseException('View should have either the templateUrl or template property set');

View File

@ -4,12 +4,10 @@ import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/col
import {DOM} from 'angular2/src/dom/dom_adapter';
import {DirectiveParser} from 'angular2/src/render/dom/compiler/directive_parser';
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step';
import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element';
import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control';
import {ViewDefinition, DirectiveMetadata} from 'angular2/src/render/api';
import {ViewDefinition, DirectiveMetadata, ViewType} from 'angular2/src/render/api';
import {Lexer, Parser} from 'angular2/src/change_detection/change_detection';
import {ElementBinderBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
import {MockStep} from './pipeline_spec';
export function main() {
describe('DirectiveParser', () => {
@ -47,9 +45,15 @@ export function main() {
]);
}
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
function process(el, propertyBindings = null, directives = null): List<ElementBinderBuilder> {
var pipeline = createPipeline(propertyBindings, directives);
return ListWrapper.map(pipeline.process(el), (ce) => ce.inheritedElementBinder);
return ListWrapper.map(
pipeline.processElements(el, ViewType.COMPONENT, createViewDefinition()),
(ce) => ce.inheritedElementBinder);
}
it('should not add directives if they are not used', () => {
@ -70,12 +74,14 @@ export function main() {
});
it('should compile children by default', () => {
var results = createPipeline().process(el('<div some-decor></div>'));
var results = createPipeline().processElements(el('<div some-decor></div>'),
ViewType.COMPONENT, createViewDefinition());
expect(results[0].compileChildren).toEqual(true);
});
it('should stop compiling children when specified in the directive config', () => {
var results = createPipeline().process(el('<div some-decor-ignoring-children></div>'));
var results = createPipeline().processElements(el('<div some-decor-ignoring-children></div>'),
ViewType.COMPONENT, createViewDefinition());
expect(results[0].compileChildren).toEqual(false);
});
@ -191,14 +197,6 @@ export function main() {
});
}
class MockStep implements CompileStep {
processClosure: Function;
constructor(process) { this.processClosure = process; }
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
this.processClosure(parent, current, control);
}
}
var someComponent = DirectiveMetadata.create(
{selector: 'some-comp', id: 'someComponent', type: DirectiveMetadata.COMPONENT_TYPE});

View File

@ -9,16 +9,21 @@ import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step';
import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control';
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
import {ProtoViewDto, ViewType} from 'angular2/src/render/api';
import {ProtoViewDto, ViewType, ViewEncapsulation, ViewDefinition} from 'angular2/src/render/api';
export function main() {
describe('compile_pipeline', () => {
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
describe('children compilation', () => {
it('should walk the tree in depth first order including template contents', () => {
var element = el('<div id="1"><template id="2"><span id="3"></span></template></div>');
var step0Log = [];
var results = new CompilePipeline([createLoggerStep(step0Log)]).process(element);
var results = new CompilePipeline([createLoggerStep(step0Log)])
.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2', '2<3']);
expect(resultIdLog(results)).toEqual(['1', '2', '3']);
@ -30,7 +35,7 @@ export function main() {
var step0Log = [];
var pipeline = new CompilePipeline([new IgnoreChildrenStep(), createLoggerStep(step0Log)]);
var results = pipeline.process(element);
var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2']);
expect(resultIdLog(results)).toEqual(['1', '2']);
@ -42,11 +47,12 @@ export function main() {
var pipeline = new CompilePipeline([
new MockStep((parent, current, control) => {
if (isPresent(DOM.getAttribute(current.element, 'viewroot'))) {
current.inheritedProtoView = new ProtoViewBuilder(current.element, ViewType.EMBEDDED);
current.inheritedProtoView =
new ProtoViewBuilder(current.element, ViewType.EMBEDDED, ViewEncapsulation.NONE);
}
})
]);
var results = pipeline.process(element);
var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(results[0].inheritedProtoView).toBe(results[1].inheritedProtoView);
expect(results[2].inheritedProtoView).toBe(results[3].inheritedProtoView);
});
@ -60,14 +66,15 @@ export function main() {
}
})
]);
var results = pipeline.process(element);
var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(results[0].inheritedElementBinder).toBe(results[1].inheritedElementBinder);
expect(results[2].inheritedElementBinder).toBe(results[3].inheritedElementBinder);
});
it('should mark root elements as viewRoot', () => {
var rootElement = el('<div></div>');
var results = new CompilePipeline([]).process(rootElement);
var results = new CompilePipeline([])
.processElements(rootElement, ViewType.COMPONENT, createViewDefinition());
expect(results[0].isViewRoot).toBe(true);
});
@ -80,7 +87,7 @@ export function main() {
}
})
]);
var results = pipeline.process(element);
var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(results[0].inheritedElementBinder.distanceToParent).toBe(0);
expect(results[1].inheritedElementBinder.distanceToParent).toBe(1);
expect(results[3].inheritedElementBinder.distanceToParent).toBe(2);
@ -95,7 +102,7 @@ export function main() {
new IgnoreCurrentElementStep(),
createLoggerStep(logs),
]);
var results = pipeline.process(element);
var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(results.length).toBe(2);
expect(logs).toEqual(['1', '1<3'])
@ -108,7 +115,7 @@ export function main() {
var step1Log = [];
var pipeline =
new CompilePipeline([createWrapperStep('wrap0', step0Log), createLoggerStep(step1Log)]);
var result = pipeline.process(element);
var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2', '2<3']);
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']);
expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', '2', '3']);
@ -125,7 +132,7 @@ export function main() {
createWrapperStep('wrap1', step1Log),
createLoggerStep(step2Log)
]);
var result = pipeline.process(element);
var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2', '2<3']);
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']);
expect(step2Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<wrap1#0', 'wrap1#0<2', '2<3']);
@ -143,7 +150,7 @@ export function main() {
createWrapperStep('wrap1', step1Log),
createLoggerStep(step2Log)
]);
var result = pipeline.process(element);
var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2', '2<3']);
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']);
expect(step2Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<wrap1#0', 'wrap1#0<3']);
@ -156,7 +163,7 @@ export function main() {
var step1Log = [];
var pipeline =
new CompilePipeline([createWrapperStep('wrap0', step0Log), createLoggerStep(step1Log)]);
var result = pipeline.process(element);
var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2', '2<3']);
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<wrap0#1', 'wrap0#1<2', '2<3']);
expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', 'wrap0#1', '2', '3']);
@ -177,40 +184,68 @@ export function main() {
}),
createLoggerStep(resultLog)
]);
var result = pipeline.process(element);
var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(result[2]).toBe(newChild);
expect(resultLog).toEqual(['1', '1<2', '1<3']);
expect(resultIdLog(result)).toEqual(['1', '2', '3']);
});
});
describe('processStyles', () => {
it('should call the steps for every style', () => {
var stepCalls = [];
var pipeline = new CompilePipeline([
new MockStep(null,
(style) => {
stepCalls.push(style);
return style;
})
]);
var result = pipeline.processStyles(['a', 'b']);
expect(result[0]).toEqual('a');
expect(result[1]).toEqual('b');
expect(result).toEqual(stepCalls);
});
});
});
}
class MockStep implements CompileStep {
processClosure: Function;
constructor(process) { this.processClosure = process; }
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
this.processClosure(parent, current, control);
export class MockStep implements CompileStep {
constructor(private processElementClosure: Function,
private processStyleClosure: Function = null) {}
processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
if (isPresent(this.processElementClosure)) {
this.processElementClosure(parent, current, control);
}
}
processStyle(style: string): string {
if (isPresent(this.processStyleClosure)) {
return this.processStyleClosure(style);
} else {
return style;
}
}
}
export class IgnoreChildrenStep implements CompileStep {
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
var attributeMap = DOM.attributeMap(current.element);
if (attributeMap.has('ignore-children')) {
current.compileChildren = false;
}
}
processStyle(style: string): string { return style; }
}
class IgnoreCurrentElementStep implements CompileStep {
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
var attributeMap = DOM.attributeMap(current.element);
if (attributeMap.has('ignore-current')) {
control.ignoreCurrentElement();
}
}
processStyle(style: string): string { return style; }
}
function logEntry(log: string[], parent, current) {

View File

@ -3,11 +3,10 @@ import {IMPLEMENTS} from 'angular2/src/facade/lang';
import {PropertyBindingParser} from 'angular2/src/render/dom/compiler/property_binding_parser';
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element';
import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step';
import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control';
import {Lexer, Parser} from 'angular2/src/change_detection/change_detection';
import {ElementBinderBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
import {ViewDefinition, ViewType} from 'angular2/src/render/api';
import {MockStep} from './pipeline_spec';
var EMPTY_MAP = new Map();
@ -24,9 +23,15 @@ export function main() {
]);
}
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
function process(element, hasNestedProtoView = false): List<ElementBinderBuilder> {
return ListWrapper.map(createPipeline(hasNestedProtoView).process(element),
(compileElement) => compileElement.inheritedElementBinder);
return ListWrapper.map(
createPipeline(hasNestedProtoView)
.processElements(element, ViewType.COMPONENT, createViewDefinition()),
(compileElement) => compileElement.inheritedElementBinder);
}
it('should detect [] syntax', () => {
@ -174,13 +179,15 @@ export function main() {
});
it('should store bound properties as temporal attributes', () => {
var results = createPipeline().process(el('<div bind-a="b" [c]="d"></div>'));
var results = createPipeline().processElements(el('<div bind-a="b" [c]="d"></div>'),
ViewType.COMPONENT, createViewDefinition());
expect(results[0].attrs().get('a')).toEqual('b');
expect(results[0].attrs().get('c')).toEqual('d');
});
it('should store variables as temporal attributes', () => {
var results = createPipeline().process(el('<div var-a="b" #c="d"></div>'));
var results = createPipeline().processElements(el('<div var-a="b" #c="d"></div>'),
ViewType.COMPONENT, createViewDefinition());
expect(results[0].attrs().get('a')).toEqual('b');
expect(results[0].attrs().get('c')).toEqual('d');
});
@ -210,11 +217,3 @@ export function main() {
});
});
}
class MockStep implements CompileStep {
processClosure: Function;
constructor(process) { this.processClosure = process; }
process(parent: CompileElement, current: CompileElement, control: CompileControl) {
this.processClosure(parent, current, control);
}
}

View File

@ -9,7 +9,7 @@ import {
el,
normalizeCSS
} from 'angular2/test_lib';
import {ShadowCss} from 'angular2/src/render/dom/shadow_dom/shadow_css';
import {ShadowCss} from 'angular2/src/render/dom/compiler/shadow_css';
import {RegExpWrapper, StringWrapper, isPresent} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter';

View File

@ -0,0 +1,135 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
el,
expect,
iit,
inject,
it,
xit,
SpyObject,
} from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {
ProtoViewBuilder,
ElementBinderBuilder
} from 'angular2/src/render/dom/view/proto_view_builder';
import {ViewDefinition, ViewType, ViewEncapsulation} from 'angular2/src/render/api';
import {StyleEncapsulator} from 'angular2/src/render/dom/compiler/style_encapsulator';
import {MockStep} from './pipeline_spec';
export function main() {
describe('StyleEncapsulator', () => {
var componentIdCache;
beforeEach(() => { componentIdCache = new Map(); });
function createPipeline(viewDef: ViewDefinition) {
return new CompilePipeline([
new MockStep((parent, current, control) => {
var tagName = DOM.tagName(current.element).toLowerCase();
if (tagName.startsWith('comp-')) {
current.bindElement().setComponentId(tagName);
}
}),
new StyleEncapsulator('someapp', viewDef, componentIdCache)
]);
}
function createViewDefinition(encapsulation: ViewEncapsulation, componentId: string):
ViewDefinition {
return new ViewDefinition({encapsulation: encapsulation, componentId: componentId});
}
function processStyles(encapsulation: ViewEncapsulation, componentId: string, styles: string[]):
string[] {
var viewDef = createViewDefinition(encapsulation, componentId);
return createPipeline(viewDef).processStyles(styles);
}
function processElements(encapsulation: ViewEncapsulation, componentId: string,
template: Element, viewType: ViewType = ViewType.COMPONENT):
ProtoViewBuilder {
var viewDef = createViewDefinition(encapsulation, componentId);
var compileElements = createPipeline(viewDef).processElements(template, viewType, viewDef);
return compileElements[0].inheritedProtoView;
}
describe('ViewEncapsulation.NONE', () => {
it('should not change the styles', () => {
var cs = processStyles(ViewEncapsulation.NONE, 'someComponent', ['.one {}']);
expect(cs[0]).toEqual('.one {}');
});
});
describe('ViewEncapsulation.NATIVE', () => {
it('should not change the styles', () => {
var cs = processStyles(ViewEncapsulation.NATIVE, 'someComponent', ['.one {}']);
expect(cs[0]).toEqual('.one {}');
});
});
describe('ViewEncapsulation.EMULATED', () => {
it('should scope styles', () => {
var cs = processStyles(ViewEncapsulation.EMULATED, 'someComponent', ['.foo {} :host {}']);
expect(cs[0]).toEqual(".foo[_ngcontent-someapp-0] {\n\n}\n\n[_nghost-someapp-0] {\n\n}");
});
it('should return the same style given the same component', () => {
var style = '.foo {} :host {}';
var cs1 = processStyles(ViewEncapsulation.EMULATED, 'someComponent', [style]);
var cs2 = processStyles(ViewEncapsulation.EMULATED, 'someComponent', [style]);
expect(cs1[0]).toEqual(cs2[0]);
});
it('should return different styles given different components', () => {
var style = '.foo {} :host {}';
var cs1 = processStyles(ViewEncapsulation.EMULATED, 'someComponent1', [style]);
var cs2 = processStyles(ViewEncapsulation.EMULATED, 'someComponent2', [style]);
expect(cs1[0]).not.toEqual(cs2[0]);
});
it('should add a host attribute to component proto views', () => {
var template = DOM.createTemplate('<div></div>');
var protoViewBuilder =
processElements(ViewEncapsulation.EMULATED, 'someComponent', template);
expect(protoViewBuilder.hostAttributes.get('_nghost-someapp-0')).toEqual('');
});
it('should not add a host attribute to embedded proto views', () => {
var template = DOM.createTemplate('<div></div>');
var protoViewBuilder = processElements(ViewEncapsulation.EMULATED, 'someComponent',
template, ViewType.EMBEDDED);
expect(protoViewBuilder.hostAttributes.size).toBe(0);
});
it('should not add a host attribute to host proto views', () => {
var template = DOM.createTemplate('<div></div>');
var protoViewBuilder =
processElements(ViewEncapsulation.EMULATED, 'someComponent', template, ViewType.HOST);
expect(protoViewBuilder.hostAttributes.size).toBe(0);
});
it('should add an attribute to the content elements', () => {
var template = DOM.createTemplate('<div></div>');
processElements(ViewEncapsulation.EMULATED, 'someComponent', template);
expect(DOM.getInnerHTML(template)).toEqual('<div _ngcontent-someapp-0=""></div>');
});
it('should not add an attribute to the content elements for host views', () => {
var template = DOM.createTemplate('<div></div>');
processElements(ViewEncapsulation.EMULATED, 'someComponent', template, ViewType.HOST);
expect(DOM.getInnerHTML(template)).toEqual('<div></div>');
});
});
});
}

View File

@ -9,6 +9,7 @@ import {
ElementBinderBuilder
} from 'angular2/src/render/dom/view/proto_view_builder';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ViewDefinition, ViewType} from 'angular2/src/render/api';
export function main() {
describe('TextInterpolationParser', () => {
@ -17,8 +18,13 @@ export function main() {
[new IgnoreChildrenStep(), new TextInterpolationParser(new Parser(new Lexer()))]);
}
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
function process(templateString: string): ProtoViewBuilder {
var compileElements = createPipeline().process(DOM.createTemplate(templateString));
var compileElements = createPipeline().processElements(
DOM.createTemplate(templateString), ViewType.COMPONENT, createViewDefinition());
return compileElements[0].inheritedProtoView;
}

View File

@ -10,21 +10,21 @@ import {
it,
xit,
} from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
import {ViewLoader, TemplateAndStyles} from 'angular2/src/render/dom/compiler/view_loader';
import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner';
import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver';
import {UrlResolver} from 'angular2/src/services/url_resolver';
import {ViewDefinition} from 'angular2/src/render/api';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {XHR} from 'angular2/src/render/xhr';
import {MockXHR} from 'angular2/src/render/xhr_mock';
import {ViewDefinition} from 'angular2/src/render/api';
export function main() {
describe('ViewLoader', () => {
var loader, xhr, styleUrlResolver, urlResolver;
var loader: ViewLoader;
var xhr, styleUrlResolver, urlResolver;
beforeEach(() => {
xhr = new MockXHR();
@ -36,32 +36,33 @@ export function main() {
describe('html', () => {
it('should load inline templates', inject([AsyncTestCompleter], (async) => {
var view = new ViewDefinition({template: 'template template'});
loader.load(view).then((el) => {
expect(DOM.content(el)).toHaveText('template template');
async.done();
});
loader.load(new ViewDefinition({template: 'template template'}))
.then((el) => {
expect(el.template).toEqual('template template');
async.done();
});
}));
it('should load templates through XHR', inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.html', 'xhr template');
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'});
loader.load(view).then((el) => {
expect(DOM.content(el)).toHaveText('xhr template');
async.done();
});
loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}))
.then((el) => {
expect(el.template).toEqual('xhr template');
async.done();
});
xhr.flush();
}));
it('should resolve urls in styles', inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.html',
'<style>.foo { background-image: url("double.jpg"); }</style>');
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'});
loader.load(view).then((el) => {
expect(DOM.content(el))
.toHaveText(".foo { background-image: url('http://ng.io/double.jpg'); }");
async.done();
});
loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}))
.then((el) => {
expect(el.template).toEqual('');
expect(el.styles)
.toEqual([".foo { background-image: url('http://ng.io/double.jpg'); }"]);
async.done();
});
xhr.flush();
}));
@ -73,85 +74,66 @@ export function main() {
let styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
let loader = new ViewLoader(xhr, styleInliner, styleUrlResolver);
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'});
loader.load(view).then((el) => {
expect(DOM.getInnerHTML(el)).toEqual("<style>/* foo.css */\n</style>");
async.done();
});
}));
it('should return a new template element on each call',
inject([AsyncTestCompleter], (async) => {
var firstEl;
// we have only one xhr.expect, so there can only be one xhr call!
xhr.expect('http://ng.io/foo.html', 'xhr template');
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'});
loader.load(view)
loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}))
.then((el) => {
expect(DOM.content(el)).toHaveText('xhr template');
firstEl = el;
return loader.load(view);
})
.then((el) => {
expect(el).not.toBe(firstEl);
expect(DOM.content(el)).toHaveText('xhr template');
expect(el.template).toEqual('');
expect(el.styles).toEqual(["/* foo.css */\n"]);
async.done();
});
xhr.flush();
}));
it('should throw when no template is defined', () => {
var view = new ViewDefinition({template: null, templateAbsUrl: null});
expect(() => loader.load(view))
expect(() => loader.load(new ViewDefinition({template: null, templateAbsUrl: null})))
.toThrowError('View should have either the templateUrl or template property set');
});
it('should return a rejected Promise when XHR loading fails',
inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.html', null);
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'});
PromiseWrapper.then(loader.load(view), function(_) { throw 'Unexpected response'; },
function(error) {
expect(error.message)
.toEqual('Failed to fetch url "http://ng.io/foo.html"');
async.done();
});
PromiseWrapper.then(
loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'})),
function(_) { throw 'Unexpected response'; },
function(error) {
expect(error.message).toEqual('Failed to fetch url "http://ng.io/foo.html"');
async.done();
});
xhr.flush();
}));
it('should replace $baseUrl in attributes with the template base url',
inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/path/foo.html', '<img src="$baseUrl/logo.png">');
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/path/foo.html'});
loader.load(view).then((el) => {
expect(DOM.getInnerHTML(el)).toEqual('<img src="http://ng.io/path/logo.png">');
async.done();
});
loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/path/foo.html'}))
.then((el) => {
expect(el.template).toEqual('<img src="http://ng.io/path/logo.png">');
async.done();
});
xhr.flush();
}));
});
describe('css', () => {
it('should load inline styles', inject([AsyncTestCompleter], (async) => {
var view = new ViewDefinition({template: 'html', styles: ['style 1', 'style 2']});
loader.load(view).then((el) => {
expect(DOM.getInnerHTML(el))
.toEqual('<style>style 1</style><style>style 2</style>html');
async.done();
});
loader.load(new ViewDefinition({template: 'html', styles: ['style 1', 'style 2']}))
.then((el) => {
expect(el.template).toEqual('html');
expect(el.styles).toEqual(['style 1', 'style 2']);
async.done();
});
}));
it('should resolve urls in inline styles', inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.html', 'html');
var view = new ViewDefinition({
templateAbsUrl: 'http://ng.io/foo.html',
styles: ['.foo { background-image: url("double.jpg"); }']
});
loader.load(view).then((el) => {
expect(DOM.getInnerHTML(el))
.toEqual(
"<style>.foo { background-image: url('http://ng.io/double.jpg'); }</style>html");
async.done();
});
loader.load(new ViewDefinition({
templateAbsUrl: 'http://ng.io/foo.html',
styles: ['.foo { background-image: url("double.jpg"); }']
}))
.then((el) => {
expect(el.template).toEqual('html');
expect(el.styles)
.toEqual([".foo { background-image: url('http://ng.io/double.jpg'); }"]);
async.done();
});
xhr.flush();
}));
@ -159,16 +141,16 @@ export function main() {
xhr.expect('http://ng.io/foo.html', 'xhr template');
xhr.expect('http://ng.io/foo-1.css', '1');
xhr.expect('http://ng.io/foo-2.css', '2');
var view = new ViewDefinition({
templateAbsUrl: 'http://ng.io/foo.html',
styles: ['i1'],
styleAbsUrls: ['http://ng.io/foo-1.css', 'http://ng.io/foo-2.css']
});
loader.load(view).then((el) => {
expect(DOM.getInnerHTML(el))
.toEqual('<style>i1</style><style>1</style><style>2</style>xhr template');
async.done();
});
loader.load(new ViewDefinition({
templateAbsUrl: 'http://ng.io/foo.html',
styles: ['i1'],
styleAbsUrls: ['http://ng.io/foo-1.css', 'http://ng.io/foo-2.css']
}))
.then((el) => {
expect(el.template).toEqual('xhr template');
expect(el.styles).toEqual(['i1', '1', '2']);
async.done();
});
xhr.flush();
}));
@ -180,25 +162,27 @@ export function main() {
let styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
let loader = new ViewLoader(xhr, styleInliner, styleUrlResolver);
var view = new ViewDefinition(
{templateAbsUrl: 'http://ng.io/foo.html', styles: ['@import "foo.css";']});
loader.load(view).then((el) => {
expect(DOM.getInnerHTML(el)).toEqual("<style>/* foo.css */\n</style><p>template</p>");
async.done();
});
loader.load(
new ViewDefinition(
{templateAbsUrl: 'http://ng.io/foo.html', styles: ['@import "foo.css";']}))
.then((el) => {
expect(el.template).toEqual("<p>template</p>");
expect(el.styles).toEqual(["/* foo.css */\n"]);
async.done();
});
}));
it('should return a rejected Promise when XHR loading fails',
inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.css', null);
var view = new ViewDefinition({template: '', styleAbsUrls: ['http://ng.io/foo.css']});
PromiseWrapper.then(loader.load(view), function(_) { throw 'Unexpected response'; },
function(error) {
expect(error.message)
.toEqual('Failed to fetch url "http://ng.io/foo.css"');
async.done();
});
PromiseWrapper.then(
loader.load(
new ViewDefinition({template: '', styleAbsUrls: ['http://ng.io/foo.css']})),
function(_) { throw 'Unexpected response'; },
function(error) {
expect(error.message).toEqual('Failed to fetch url "http://ng.io/foo.css"');
async.done();
});
xhr.flush();
}));
});

View File

@ -12,7 +12,8 @@ import {MapWrapper} from 'angular2/src/facade/collection';
import {ViewSplitter} from 'angular2/src/render/dom/compiler/view_splitter';
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
import {ProtoViewDto, ViewType} from 'angular2/src/render/api';
import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element';
import {ProtoViewDto, ViewType, ViewDefinition} from 'angular2/src/render/api';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {Lexer, Parser} from 'angular2/src/change_detection/change_detection';
@ -20,15 +21,23 @@ import {Lexer, Parser} from 'angular2/src/change_detection/change_detection';
export function main() {
describe('ViewSplitter', () => {
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
function createPipeline() {
return new CompilePipeline([new ViewSplitter(new Parser(new Lexer()))]);
}
function proceess(el): CompileElement[] {
return createPipeline().processElements(el, ViewType.COMPONENT, createViewDefinition());
}
describe('<template> elements', () => {
it('should move the content into a new <template> element and mark that as viewRoot', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(stringifyElement(results[1].element))
.toEqual('<template class="ng-binding" if="true"></template>');
@ -39,32 +48,32 @@ export function main() {
it('should mark the new <template> element as viewRoot', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].isViewRoot).toBe(true);
});
it('should not wrap the root element', () => {
var rootElement = DOM.createTemplate('');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results.length).toBe(1);
expect(stringifyElement(rootElement)).toEqual('<template></template>');
});
it('should copy over the elementDescription', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].elementDescription).toBe(results[1].elementDescription);
});
it('should clean out the inheritedElementBinder', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].inheritedElementBinder).toBe(null);
});
it('should create a nestedProtoView', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].inheritedProtoView).not.toBe(null);
expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView);
@ -80,7 +89,7 @@ export function main() {
it('should replace the element with an empty <template> element', () => {
var rootElement = DOM.createTemplate('<span template=""></span>');
var originalChild = DOM.firstChild(DOM.content(rootElement));
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[0].element).toBe(rootElement);
expect(stringifyElement(results[0].element))
.toEqual('<template><template class="ng-binding"></template></template>');
@ -92,7 +101,7 @@ export function main() {
it('should work with top-level template node', () => {
var rootElement = DOM.createTemplate('<div template>x</div>');
var originalChild = DOM.content(rootElement).childNodes[0];
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[0].element).toBe(rootElement);
expect(results[0].isViewRoot).toBe(true);
@ -104,13 +113,13 @@ export function main() {
it('should mark the element as viewRoot', () => {
var rootElement = DOM.createTemplate('<div template></div>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].isViewRoot).toBe(true);
});
it('should add property bindings from the template attribute', () => {
var rootElement = DOM.createTemplate('<div template="some-prop:expr"></div>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[1].inheritedElementBinder.propertyBindings.get('someProp').source)
.toEqual('expr');
expect(results[1].attrs().get('some-prop')).toEqual('expr');
@ -118,14 +127,14 @@ export function main() {
it('should add variable mappings from the template attribute to the nestedProtoView', () => {
var rootElement = DOM.createTemplate('<div template="var var-name=mapName"></div>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].inheritedProtoView.variableBindings)
.toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
});
it('should add entries without value as attributes to the element', () => {
var rootElement = DOM.createTemplate('<div template="varname"></div>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[1].attrs().get('varname')).toEqual('');
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
expect(results[1].inheritedElementBinder.variableBindings).toEqual(new Map());
@ -133,26 +142,26 @@ export function main() {
it('should iterate properly after a template dom modification', () => {
var rootElement = DOM.createTemplate('<div template></div><after></after>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
// 1 root + 2 initial + 2 generated template elements
expect(results.length).toEqual(5);
});
it('should copy over the elementDescription', () => {
var rootElement = DOM.createTemplate('<span template=""></span>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].elementDescription).toBe(results[1].elementDescription);
});
it('should clean out the inheritedElementBinder', () => {
var rootElement = DOM.createTemplate('<span template=""></span>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].inheritedElementBinder).toBe(null);
});
it('should create a nestedProtoView', () => {
var rootElement = DOM.createTemplate('<span template=""></span>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].inheritedProtoView).not.toBe(null);
expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView);
@ -167,7 +176,7 @@ export function main() {
it('should replace the element with an empty <template> element', () => {
var rootElement = DOM.createTemplate('<span *ng-if></span>');
var originalChild = DOM.firstChild(DOM.content(rootElement));
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[0].element).toBe(rootElement);
expect(stringifyElement(results[0].element))
.toEqual('<template><template class="ng-binding" ng-if=""></template></template>');
@ -178,14 +187,14 @@ export function main() {
it('should mark the element as viewRoot', () => {
var rootElement = DOM.createTemplate('<div *foo="bar"></div>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].isViewRoot).toBe(true);
});
it('should work with top-level template node', () => {
var rootElement = DOM.createTemplate('<div *foo>x</div>');
var originalChild = DOM.content(rootElement).childNodes[0];
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[0].element).toBe(rootElement);
expect(results[0].isViewRoot).toBe(true);
@ -197,7 +206,7 @@ export function main() {
it('should add property bindings from the template attribute', () => {
var rootElement = DOM.createTemplate('<div *prop="expr"></div>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[1].inheritedElementBinder.propertyBindings.get('prop').source)
.toEqual('expr');
expect(results[1].attrs().get('prop')).toEqual('expr');
@ -205,14 +214,14 @@ export function main() {
it('should add variable mappings from the template attribute to the nestedProtoView', () => {
var rootElement = DOM.createTemplate('<div *foreach="var varName=mapName"></div>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].inheritedProtoView.variableBindings)
.toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
});
it('should add entries without value as attribute to the element', () => {
var rootElement = DOM.createTemplate('<div *varname></div>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[1].attrs().get('varname')).toEqual('');
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
expect(results[1].inheritedElementBinder.variableBindings).toEqual(new Map());
@ -220,26 +229,26 @@ export function main() {
it('should iterate properly after a template dom modification', () => {
var rootElement = DOM.createTemplate('<div *foo></div><after></after>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
// 1 root + 2 initial + 2 generated template elements
expect(results.length).toEqual(5);
});
it('should copy over the elementDescription', () => {
var rootElement = DOM.createTemplate('<span *foo></span>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].elementDescription).toBe(results[1].elementDescription);
});
it('should clean out the inheritedElementBinder', () => {
var rootElement = DOM.createTemplate('<span *foo></span>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].inheritedElementBinder).toBe(null);
});
it('should create a nestedProtoView', () => {
var rootElement = DOM.createTemplate('<span *foo></span>');
var results = createPipeline().process(rootElement);
var results = proceess(rootElement);
expect(results[2].inheritedProtoView).not.toBe(null);
expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView);

View File

@ -18,9 +18,13 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
import {DomTestbed, TestRootView, elRef} from './dom_testbed';
import {ViewDefinition, DirectiveMetadata, RenderViewRef} from 'angular2/src/render/api';
import {DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from 'angular2/src/render/dom/dom_renderer';
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/render/render';
import {
ViewDefinition,
DirectiveMetadata,
RenderViewRef,
ViewEncapsulation
} from 'angular2/src/render/api';
import {DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from 'angular2/src/render/dom/dom_tokens';
import {bind} from 'angular2/di';
export function main() {
@ -271,17 +275,17 @@ export function main() {
if (DOM.supportsNativeShadowDOM()) {
describe('native shadow dom support', () => {
beforeEachBindings(
() => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; });
it('should support shadow dom components',
it('should put the template into a shadow root',
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compileAndMerge(
someComponent,
[
new ViewDefinition(
{componentId: 'someComponent', template: 'hello', directives: []})
])
tb.compileAndMerge(someComponent,
[
new ViewDefinition({
componentId: 'someComponent',
template: 'hello',
directives: [],
encapsulation: ViewEncapsulation.NATIVE
})
])
.then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings);
expect(DOM.getShadowRoot(rootView.hostElement)).toHaveText('hello');
@ -289,6 +293,48 @@ export function main() {
});
}));
it('should add styles from non native components to shadow roots while the view is not destroyed',
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compileAndMerge(someComponent,
[
new ViewDefinition({
componentId: 'someComponent',
template: '',
directives: [],
encapsulation: ViewEncapsulation.NATIVE,
styles: ['a {};']
})
])
.then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings);
tb.compiler.compile(new ViewDefinition({
componentId: 'someComponent',
template: '',
directives: [],
encapsulation: ViewEncapsulation.NONE,
styles: ['b {};']
}))
.then(_ => {
expect(DOM.getShadowRoot(rootView.hostElement)).toHaveText('a {};b {};');
tb.renderer.destroyView(rootView.viewRef);
tb.compiler.compile(new ViewDefinition({
componentId: 'someComponent',
template: '',
directives: [],
encapsulation: ViewEncapsulation.NONE,
styles: ['c {};']
}))
.then(_ => {
expect(DOM.getShadowRoot(rootView.hostElement))
.toHaveText('a {};b {};');
async.done();
});
});
});
}));
});
}

View File

@ -4,7 +4,8 @@ import {MapWrapper, ListWrapper, List, Map} from 'angular2/src/facade/collection
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {DomRenderer, DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
import {DomRenderer} from 'angular2/src/render/dom/dom_renderer';
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_tokens';
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
import {
RenderViewWithFragments,

View File

@ -1,86 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
el,
expect,
iit,
inject,
it,
xit,
SpyObject,
normalizeCSS
} from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {
EmulatedScopedShadowDomStrategy,
} from 'angular2/src/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy';
import {
resetShadowDomCache,
} from 'angular2/src/render/dom/shadow_dom/util';
export function main() {
describe('EmulatedScopedShadowDomStrategy', () => {
var styleHost, strategy;
beforeEach(() => {
styleHost = el('<div></div>');
strategy = new EmulatedScopedShadowDomStrategy(styleHost);
resetShadowDomCache();
});
it('should report that this is not the native strategy',
() => { expect(strategy.hasNativeContentElement()).toBe(false); });
it('should scope styles', () => {
var styleElement = el('<style>.foo {} :host {}</style>');
strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(styleElement).toHaveText(".foo[_ngcontent-0] {\n\n}\n\n[_nghost-0] {\n\n}");
});
it('should return the same style given the same component', () => {
var styleElement = el('<style>.foo {} :host {}</style>');
strategy.processStyleElement('someComponent', 'http://base', styleElement);
var styleElement2 = el('<style>.foo {} :host {}</style>');
strategy.processStyleElement('someComponent', 'http://base', styleElement2);
expect(DOM.getText(styleElement)).toEqual(DOM.getText(styleElement2));
});
it('should return different styles given different components', () => {
var styleElement = el('<style>.foo {} :host {}</style>');
strategy.processStyleElement('someComponent1', 'http://base', styleElement);
var styleElement2 = el('<style>.foo {} :host {}</style>');
strategy.processStyleElement('someComponent2', 'http://base', styleElement2);
expect(DOM.getText(styleElement)).not.toEqual(DOM.getText(styleElement2));
});
it('should move the style element to the style host', () => {
var compileElement = el('<div><style>.one {}</style></div>');
var styleElement = DOM.firstChild(compileElement);
strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(compileElement).toHaveText('');
expect(styleHost).toHaveText('.one[_ngcontent-0] {\n\n}');
});
it('should add an attribute to component elements', () => {
var element = el('<div></div>');
strategy.processElement(null, 'elComponent', element);
expect(DOM.getAttribute(element, '_nghost-0')).toEqual('');
});
it('should add an attribute to the content elements', () => {
var element = el('<div></div>');
strategy.processElement('hostComponent', null, element);
expect(DOM.getAttribute(element, '_ngcontent-0')).toEqual('');
});
});
}

View File

@ -1,63 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
el,
expect,
iit,
inject,
it,
xit,
SpyObject,
} from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ListWrapper} from 'angular2/src/facade/collection';
import {
EmulatedUnscopedShadowDomStrategy,
} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
import {
resetShadowDomCache,
} from 'angular2/src/render/dom/shadow_dom/util';
export function main() {
var strategy;
describe('EmulatedUnscopedShadowDomStrategy', () => {
var styleHost;
beforeEach(() => {
styleHost = el('<div></div>');
strategy = new EmulatedUnscopedShadowDomStrategy(styleHost);
resetShadowDomCache();
});
it('should report that this is not the native strategy',
() => { expect(strategy.hasNativeContentElement()).toBe(false); });
it('should move the style element to the style host', () => {
var compileElement = el('<div><style>.one {}</style></div>');
var styleElement = DOM.firstChild(compileElement);
strategy.processStyleElement('someComponent', 'http://base', styleElement);
expect(compileElement).toHaveText('');
expect(styleHost).toHaveText('.one {}');
});
it('should insert the same style only once in the style host', () => {
var styleEls = [
el('<style>/*css1*/</style>'),
el('<style>/*css2*/</style>'),
el('<style>/*css1*/</style>')
];
ListWrapper.forEach(styleEls, (styleEl) => {
strategy.processStyleElement('someComponent', 'http://base', styleEl);
});
expect(styleHost).toHaveText("/*css1*//*css2*/");
});
});
}

View File

@ -1,28 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
el,
expect,
iit,
inject,
it,
xit,
SpyObject,
} from 'angular2/test_lib';
import {
NativeShadowDomStrategy
} from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
export function main() {
var strategy: NativeShadowDomStrategy;
describe('NativeShadowDomStrategy', () => {
beforeEach(() => { strategy = new NativeShadowDomStrategy(); });
it('should report that this is the native strategy',
() => { expect(strategy.hasNativeContentElement()).toBe(true); });
});
}

View File

@ -13,7 +13,7 @@ import {
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
import {ASTWithSource, AST} from 'angular2/src/change_detection/change_detection';
import {PropertyBindingType, ViewType} from 'angular2/src/render/api';
import {PropertyBindingType, ViewType, ViewEncapsulation} from 'angular2/src/render/api';
import {DOM} from 'angular2/src/dom/dom_adapter';
export function main() {
@ -21,8 +21,10 @@ export function main() {
describe('ProtoViewBuilder', () => {
var builder;
beforeEach(
() => { builder = new ProtoViewBuilder(DOM.createTemplate(''), ViewType.EMBEDDED); });
beforeEach(() => {
builder =
new ProtoViewBuilder(DOM.createTemplate(''), ViewType.EMBEDDED, ViewEncapsulation.NONE);
});
if (!IS_DARTIUM) {
describe('verification of properties', () => {

View File

@ -21,15 +21,15 @@ import {DomTestbed} from '../dom_testbed';
import {
ViewDefinition,
DirectiveMetadata,
RenderProtoViewMergeMapping
RenderProtoViewMergeMapping,
ViewEncapsulation,
ViewType
} from 'angular2/src/render/api';
import {bind} from 'angular2/di';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {cloneAndQueryProtoView} from 'angular2/src/render/dom/util';
import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view';
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/render/render';
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
export function main() {
describe('ProtoViewMerger integration test', () => {
@ -232,29 +232,46 @@ export function main() {
});
describe('native shadow dom support', () => {
beforeEachBindings(
() => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; });
it('should keep the non projected light dom and wrap the component view into a shadow-root element',
runAndAssert('root', ['<a>b</a>', 'c'], [
'<root class="ng-binding" idx="0"><shadow-root><a class="ng-binding" idx="1"><shadow-root>c</shadow-root>b</a></shadow-root></root>'
runAndAssert('native-root', ['<a>b</a>', 'c'], [
'<native-root class="ng-binding" idx="0"><shadow-root><a class="ng-binding" idx="1"><shadow-root>c</shadow-root>b</a></shadow-root></native-root>'
]));
});
describe('host attributes', () => {
it('should set host attributes while merging',
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compiler.compileHost(rootDirective('root'))
.then((rootProtoViewDto) => {
var builder = new ProtoViewBuilder(DOM.createTemplate(''), ViewType.COMPONENT,
ViewEncapsulation.NONE);
builder.setHostAttribute('a', 'b');
var componentProtoViewDto = builder.build();
tb.merge([rootProtoViewDto, componentProtoViewDto])
.then(mergeMappings => {
var domPv = resolveInternalDomProtoView(mergeMappings.mergedProtoViewRef);
expect(DOM.getInnerHTML(domPv.rootElement))
.toEqual('<root class="ng-binding" a="b"></root>');
async.done();
});
});
}));
});
});
}
function runAndAssert(hostElementName: string, componentTemplates: string[],
expectedFragments: string[]) {
var rootComp = DirectiveMetadata.create(
{id: 'rootComp', type: DirectiveMetadata.COMPONENT_TYPE, selector: hostElementName});
var useNativeEncapsulation = hostElementName.startsWith('native-');
var rootComp = rootDirective(hostElementName);
return inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compileAndMerge(rootComp, componentTemplates.map(template => new ViewDefinition({
componentId: 'someComp',
template: template,
directives: [aComp, bComp, cComp]
})))
tb.compileAndMerge(rootComp, componentTemplates.map(template => componentView(
template, useNativeEncapsulation ?
ViewEncapsulation.NATIVE :
ViewEncapsulation.NONE)))
.then((mergeMappings) => {
expect(stringify(mergeMappings)).toEqual(expectedFragments);
async.done();
@ -262,6 +279,21 @@ function runAndAssert(hostElementName: string, componentTemplates: string[],
});
}
function rootDirective(hostElementName: string) {
return DirectiveMetadata.create(
{id: 'rootComp', type: DirectiveMetadata.COMPONENT_TYPE, selector: hostElementName});
}
function componentView(template: string,
encapsulation: ViewEncapsulation = ViewEncapsulation.NONE) {
return new ViewDefinition({
componentId: 'someComp',
template: template,
directives: [aComp, bComp, cComp],
encapsulation: encapsulation
});
}
function stringify(protoViewMergeMapping: RenderProtoViewMergeMapping): string[] {
var testView = cloneAndQueryProtoView(
resolveInternalDomProtoView(protoViewMergeMapping.mergedProtoViewRef), false);

View File

@ -0,0 +1,58 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
xdescribe,
describe,
el,
dispatchEvent,
expect,
iit,
inject,
beforeEachBindings,
it,
xit,
SpyObject,
proxy
} from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {DomSharedStylesHost} from 'angular2/src/render/dom/view/shared_styles_host';
export function main() {
describe('DomSharedStylesHost', () => {
var doc;
var ssh: DomSharedStylesHost;
var someHost: Element;
beforeEach(() => {
doc = DOM.createHtmlDocument();
doc.title = '';
ssh = new DomSharedStylesHost(doc);
someHost = DOM.createElement('div');
});
it('should add existing styles to new hosts', () => {
ssh.addStyles(['a {};']);
ssh.addHost(someHost);
expect(DOM.getInnerHTML(someHost)).toEqual('<style>a {};</style>');
});
it('should add new styles to hosts', () => {
ssh.addHost(someHost);
ssh.addStyles(['a {};']);
expect(DOM.getInnerHTML(someHost)).toEqual('<style>a {};</style>');
});
it('should add styles only once to hosts', () => {
ssh.addStyles(['a {};']);
ssh.addHost(someHost);
ssh.addStyles(['a {};']);
expect(DOM.getInnerHTML(someHost)).toEqual('<style>a {};</style>');
});
it('should use the document head as default host', () => {
ssh.addStyles(['a {};', 'b {};']);
expect(doc.head).toHaveText('a {};b {};');
});
});
}

View File

@ -30,7 +30,7 @@ export function main() {
binders = [];
}
var rootEl = DOM.createTemplate('<div></div>');
return DomProtoView.create(null, <Element>rootEl, [1], [], binders);
return DomProtoView.create(null, <Element>rootEl, null, [1], [], binders, null);
}
function createElementBinder() { return new DomElementBinder({textNodeIndices: []}); }

View File

@ -15,7 +15,7 @@ import {bootstrap} from 'angular2/bootstrap';
import {Component, Directive, View} from 'angular2/annotations';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {bind} from 'angular2/di';
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
import {DOCUMENT_TOKEN} from 'angular2/src/render/render';
import {
routerInjectables,

View File

@ -18,7 +18,7 @@ import {bootstrap} from 'angular2/src/core/application';
import {Component, Directive, View} from 'angular2/src/core/annotations/decorators';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {bind} from 'angular2/di';
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer';
import {DOCUMENT_TOKEN} from 'angular2/src/render/render';
import {RouteConfig, Route, Redirect} from 'angular2/src/router/route_config_decorator';
import {PromiseWrapper} from 'angular2/src/facade/async';
import {BaseException} from 'angular2/src/facade/lang';

View File

@ -104,12 +104,13 @@ export function main() {
var compiler: WorkerCompiler = createWorkerCompiler(workerSerializer, uiSerializer, tb);
var dirMetadata = DirectiveMetadata.create(
{id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE});
{id: 'id', selector: 'custom', type: DirectiveMetadata.COMPONENT_TYPE});
compiler.compileHost(dirMetadata)
.then((protoView) => {
expect(DOM.tagName(DOM.firstChild(
DOM.content(resolveWebWorkerRef(protoView.render).rootElement))))
.toEqual('CUSTOM');
expect(DOM.tagName(DOM.firstChild(DOM.content(
resolveWebWorkerRef(protoView.render).rootElement)))
.toLowerCase())
.toEqual('custom');
expect(protoView).not.toBeNull();
async.done();
});

View File

@ -1,9 +1,12 @@
import {Component, View, LifecycleEvent} from 'angular2/angular2';
import {Component, View, LifecycleEvent, ViewEncapsulation} from 'angular2/angular2';
import {isPresent} from 'angular2/src/facade/lang';
@Component({selector: '[md-button]:not([href])'})
@View({templateUrl: 'package:angular2_material/src/components/button/button.html'})
@View({
templateUrl: 'package:angular2_material/src/components/button/button.html',
encapsulation: ViewEncapsulation.NONE
})
export class MdButton {
// TODO(jelbourn): Ink ripples.
}
@ -15,7 +18,10 @@ export class MdButton {
host: {'(click)': 'onClick($event)', '[tabIndex]': 'tabIndex'},
lifecycle: [LifecycleEvent.onChange]
})
@View({templateUrl: 'package:angular2_material/src/components/button/button.html'})
@View({
templateUrl: 'package:angular2_material/src/components/button/button.html',
encapsulation: ViewEncapsulation.NONE
})
export class MdAnchor {
tabIndex: number;

View File

@ -1,4 +1,4 @@
import {Component, View, Attribute} from 'angular2/angular2';
import {Component, View, Attribute, ViewEncapsulation} from 'angular2/angular2';
import {isPresent} from 'angular2/src/facade/lang';
import {KEY_SPACE} from 'angular2_material/src/core/constants';
import {KeyboardEvent} from 'angular2/src/facade/browser';
@ -17,7 +17,8 @@ import {NumberWrapper} from 'angular2/src/facade/lang';
})
@View({
templateUrl: 'package:angular2_material/src/components/checkbox/checkbox.html',
directives: []
directives: [],
encapsulation: ViewEncapsulation.NONE
})
export class MdCheckbox {
/** Whether this checkbox is checked. */

View File

@ -2,6 +2,7 @@ import {
Component,
Directive,
View,
ViewEncapsulation,
Ancestor,
ElementRef,
DynamicComponentLoader,
@ -211,7 +212,8 @@ export class MdDialogConfig {
})
@View({
templateUrl: 'package:angular2_material/src/components/dialog/dialog.html',
directives: [forwardRef(() => MdDialogContent)]
directives: [forwardRef(() => MdDialogContent)],
encapsulation: ViewEncapsulation.NONE
})
class MdDialogContainer {
// Ref to the dialog content. Used by the DynamicComponentLoader to load the dialog content.

View File

@ -1,4 +1,4 @@
import {Component, View, Ancestor, LifecycleEvent} from 'angular2/angular2';
import {Component, View, ViewEncapsulation, Ancestor, LifecycleEvent} from 'angular2/angular2';
import {ListWrapper} from 'angular2/src/facade/collection';
import {StringWrapper, isPresent, isString, NumberWrapper} from 'angular2/src/facade/lang';
@ -16,7 +16,10 @@ import {Math} from 'angular2/src/facade/math';
properties: ['cols', 'rowHeight', 'gutterSize'],
lifecycle: [LifecycleEvent.onAllChangesDone]
})
@View({templateUrl: 'package:angular2_material/src/components/grid_list/grid_list.html'})
@View({
templateUrl: 'package:angular2_material/src/components/grid_list/grid_list.html',
encapsulation: ViewEncapsulation.NONE
})
export class MdGridList {
/** List of tiles that are being rendered. */
tiles: List<MdGridTile>;
@ -223,7 +226,10 @@ export class MdGridList {
},
lifecycle: [LifecycleEvent.onDestroy, LifecycleEvent.onChange]
})
@View({templateUrl: 'package:angular2_material/src/components/grid_list/grid_tile.html'})
@View({
templateUrl: 'package:angular2_material/src/components/grid_list/grid_tile.html',
encapsulation: ViewEncapsulation.NONE
})
export class MdGridTile {
gridList: MdGridList;
_rowspan: number;

View File

@ -1,8 +1,9 @@
import {Component, View} from 'angular2/angular2';
import {Component, View, ViewEncapsulation} from 'angular2/angular2';
@Component({selector: 'md-progress-circular'})
@View({
templateUrl: 'package:angular2_material/src/components/progress-circular/progress_circular.html'
templateUrl: 'package:angular2_material/src/components/progress-circular/progress_circular.html',
encapsulation: ViewEncapsulation.NONE
})
export class MdProgressCircular {
constructor() {}

View File

@ -1,4 +1,4 @@
import {Component, LifecycleEvent, View, Attribute} from 'angular2/angular2';
import {Component, LifecycleEvent, View, ViewEncapsulation, Attribute} from 'angular2/angular2';
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {Math} from 'angular2/src/facade/math';
@ -16,7 +16,8 @@ import {Math} from 'angular2/src/facade/math';
})
@View({
templateUrl: 'package:angular2_material/src/components/progress-linear/progress_linear.html',
directives: []
directives: [],
encapsulation: ViewEncapsulation.NONE
})
export class MdProgressLinear {
/** Value for the primary bar. */

View File

@ -1,4 +1,12 @@
import {Component, View, LifecycleEvent, Ancestor, Attribute, Optional} from 'angular2/angular2';
import {
Component,
View,
ViewEncapsulation,
LifecycleEvent,
Ancestor,
Attribute,
Optional
} from 'angular2/angular2';
import {isPresent, StringWrapper, NumberWrapper} from 'angular2/src/facade/lang';
import {ObservableWrapper, EventEmitter} from 'angular2/src/facade/async';
@ -36,7 +44,10 @@ var _uniqueIdCounter: number = 0;
'[attr.aria-activedescendant]': 'activedescendant'
}
})
@View({templateUrl: 'package:angular2_material/src/components/radio/radio_group.html'})
@View({
templateUrl: 'package:angular2_material/src/components/radio/radio_group.html',
encapsulation: ViewEncapsulation.NONE
})
export class MdRadioGroup {
/** The selected value for the radio group. The value comes from the options. */
value: any;
@ -192,7 +203,8 @@ export class MdRadioGroup {
})
@View({
templateUrl: 'package:angular2_material/src/components/radio/radio_button.html',
directives: []
directives: [],
encapsulation: ViewEncapsulation.NONE
})
export class MdRadioButton {
/** Whether this radio is checked. */

View File

@ -1,4 +1,4 @@
import {Component, View, Attribute} from 'angular2/angular2';
import {Component, View, ViewEncapsulation, Attribute} from 'angular2/angular2';
import {isPresent} from 'angular2/src/facade/lang';
import {KEY_SPACE} from 'angular2_material/src/core/constants';
import {KeyboardEvent} from 'angular2/src/facade/browser';
@ -16,8 +16,11 @@ import {NumberWrapper} from 'angular2/src/facade/lang';
'[attr.role]': '"checkbox"'
}
})
@View(
{templateUrl: 'package:angular2_material/src/components/switcher/switch.html', directives: []})
@View({
templateUrl: 'package:angular2_material/src/components/switcher/switch.html',
directives: [],
encapsulation: ViewEncapsulation.NONE
})
export class MdSwitch {
/** Whether this switch is checked. */
checked: boolean;

View File

@ -2,9 +2,6 @@ import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
import {PromiseWrapper} from 'angular2/src/facade/async';
import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection';
import {DateWrapper, Type, print} from 'angular2/src/facade/lang';
import {
NativeShadowDomStrategy
} from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
import {
Parser,
@ -17,7 +14,6 @@ import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import * as viewModule from 'angular2/src/core/annotations_impl/view';
import {Component, Directive, View} from 'angular2/angular2';
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
import {ViewResolver} from 'angular2/src/core/compiler/view_resolver';
import {UrlResolver} from 'angular2/src/services/url_resolver';
import {AppRootUrl} from 'angular2/src/services/app_root_url';
@ -28,7 +24,7 @@ import {ReflectionCapabilities} from 'angular2/src/reflection/reflection_capabil
import {getIntParameter, bindAction} from 'angular2/src/test_lib/benchmark_util';
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
import * as rc from 'angular2/src/render/dom/compiler/compiler';
import {ViewLoader, DefaultDomCompiler, SharedStylesHost} from 'angular2/src/render/render';
export function main() {
BrowserDomAdapter.makeCurrent();
@ -40,12 +36,12 @@ export function main() {
var viewResolver = new MultipleViewResolver(
count, [BenchmarkComponentNoBindings, BenchmarkComponentWithBindings]);
var urlResolver = new UrlResolver();
var shadowDomStrategy = new NativeShadowDomStrategy();
var renderCompiler = new rc.DefaultDomCompiler(new Parser(new Lexer()), shadowDomStrategy,
new ViewLoader(null, null, null));
var compiler = new Compiler(reader, cache, viewResolver, new ComponentUrlMapper(), urlResolver,
renderCompiler, new ProtoViewFactory(new DynamicChangeDetection()),
new AppRootUrl(""));
var appRootUrl = new AppRootUrl("");
var renderCompiler = new DefaultDomCompiler(
new Parser(new Lexer()), new ViewLoader(null, null, null), new SharedStylesHost(), 'a');
var compiler =
new Compiler(reader, cache, viewResolver, new ComponentUrlMapper(), urlResolver,
renderCompiler, new ProtoViewFactory(new DynamicChangeDetection()), appRootUrl);
function measureWrapper(func, desc) {
return function() {

View File

@ -67,7 +67,7 @@ class DemoApp {
<p>There are {{numCoconuts}} coconuts.</p>
<p>Return: <input (input)="updateValue($event)"></p>
<button type="button" (click)="done()">Done</button>
`,
`
})
class SimpleDialogComponent {
numCoconuts: number;