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 LifecycleEvent
} from './src/core/annotations/annotations'; } 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 {QueryAnnotation, AttributeAnnotation} from 'angular2/src/core/annotations/di';
export { export {

View File

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

View File

@ -9,6 +9,7 @@ import {
Class Class
} from '../../util/decorators'; } from '../../util/decorators';
import {Type} from 'angular2/src/facade/lang'; import {Type} from 'angular2/src/facade/lang';
import {ViewEncapsulation} from 'angular2/src/render/api';
/** /**
* Interface for the {@link Directive} decorator function. * Interface for the {@link Directive} decorator function.
@ -226,7 +227,7 @@ export interface ViewFactory {
templateUrl?: string, templateUrl?: string,
template?: string, template?: string,
directives?: List<Type | any | List<any>>, directives?: List<Type | any | List<any>>,
renderer?: string, encapsulation?: ViewEncapsulation,
styles?: List<string>, styles?: List<string>,
styleUrls?: List<string>, styleUrls?: List<string>,
}): ViewDecorator; }): ViewDecorator;
@ -234,7 +235,7 @@ export interface ViewFactory {
templateUrl?: string, templateUrl?: string,
template?: string, template?: string,
directives?: List<Type | any | List<any>>, directives?: List<Type | any | List<any>>,
renderer?: string, encapsulation?: ViewEncapsulation,
styles?: List<string>, styles?: List<string>,
styleUrls?: List<string>, styleUrls?: List<string>,
}): ViewAnnotation; }): 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 {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. * Declares the available HTML templates for an application.
@ -85,17 +88,17 @@ export class View {
directives: List<Type | any | List<any>>; directives: List<Type | any | List<any>>;
/** /**
* Specify a custom renderer for this View. * Specify how the template and the styles should be encapsulated.
* If this is set, neither `template`, `templateUrl`, `styles`, `styleUrls` nor `directives` are * The default is {@link ViewEncapsulation.EMULATED} if the view has styles,
* used. * otherwise {@link ViewEncapsulation.NONE}.
*/ */
renderer: string; encapsulation: ViewEncapsulation;
constructor({templateUrl, template, directives, renderer, styles, styleUrls}: { constructor({templateUrl, template, directives, encapsulation, styles, styleUrls}: {
templateUrl?: string, templateUrl?: string,
template?: string, template?: string,
directives?: List<Type | any | List<any>>, directives?: List<Type | any | List<any>>,
renderer?: string, encapsulation?: ViewEncapsulation,
styles?: List<string>, styles?: List<string>,
styleUrls?: List<string>, styleUrls?: List<string>,
} = {}) { } = {}) {
@ -104,6 +107,6 @@ export class View {
this.styleUrls = styleUrls; this.styleUrls = styleUrls;
this.styles = styles; this.styles = styles;
this.directives = directives; 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 {Promise, PromiseWrapper, PromiseCompleter} from 'angular2/src/facade/async';
import {NgZone} from 'angular2/src/core/zone/ng_zone'; import {NgZone} from 'angular2/src/core/zone/ng_zone';
import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle'; 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 {XHR} from 'angular2/src/render/xhr';
import {XHRImpl} from 'angular2/src/render/xhr_impl'; import {XHRImpl} from 'angular2/src/render/xhr_impl';
import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager'; import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager';
@ -61,9 +57,14 @@ import {Renderer, RenderCompiler} from 'angular2/src/render/api';
import { import {
DomRenderer, DomRenderer,
DOCUMENT_TOKEN, DOCUMENT_TOKEN,
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES,
} from 'angular2/src/render/dom/dom_renderer'; DefaultDomCompiler,
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler'; 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 {internalView} from 'angular2/src/core/compiler/view_ref';
import {appComponentRefPromiseToken, appComponentTypeToken} from './application_tokens'; import {appComponentRefPromiseToken, appComponentTypeToken} from './application_tokens';
@ -108,12 +109,13 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
return new EventManager(plugins, ngZone); return new EventManager(plugins, ngZone);
}, },
[NgZone]), [NgZone]),
bind(ShadowDomStrategy)
.toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]),
DomRenderer, DomRenderer,
DefaultDomCompiler,
bind(Renderer).toAlias(DomRenderer), bind(Renderer).toAlias(DomRenderer),
APP_ID_RANDOM_BINDING,
DefaultDomCompiler,
bind(RenderCompiler).toAlias(DefaultDomCompiler), bind(RenderCompiler).toAlias(DefaultDomCompiler),
DomSharedStylesHost,
bind(SharedStylesHost).toAlias(DomSharedStylesHost),
ProtoViewFactory, ProtoViewFactory,
AppViewPool, AppViewPool,
bind(APP_VIEW_POOL_CAPACITY).toValue(10000), bind(APP_VIEW_POOL_CAPACITY).toValue(10000),

View File

@ -307,7 +307,8 @@ export class Compiler {
templateAbsUrl: templateAbsUrl, template: view.template, templateAbsUrl: templateAbsUrl, template: view.template,
styleAbsUrls: styleAbsUrls, styleAbsUrls: styleAbsUrls,
styles: view.styles, 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'; throw 'not implemented';
} }
bool supportsDOMEvents() { bool supportsDOMEvents() {
throw 'not implemented'; return false;
} }
bool supportsNativeShadowDOM() { bool supportsNativeShadowDOM() {
throw 'not implemented'; return false;
} }
getHistory() { getHistory() {
throw 'not implemented'; throw 'not implemented';

View File

@ -212,4 +212,7 @@ void iterateListLike(iter, fn(item)) {
class SetWrapper { class SetWrapper {
static Set createFromList(List l) => new Set.from(l); static Set createFromList(List l) => new Set.from(l);
static bool has(Set s, key) => s.contains(key); 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 { export class SetWrapper {
static createFromList<T>(lst: List<T>): Set<T> { return createSetFromList(lst); } 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 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 // An opaque reference to a view
export class RenderViewRef {} 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 { export class ViewDefinition {
componentId: string; componentId: string;
templateAbsUrl: string; templateAbsUrl: string;
@ -280,21 +299,25 @@ export class ViewDefinition {
directives: List<DirectiveMetadata>; directives: List<DirectiveMetadata>;
styleAbsUrls: List<string>; styleAbsUrls: List<string>;
styles: List<string>; styles: List<string>;
encapsulation: ViewEncapsulation;
constructor({componentId, templateAbsUrl, template, styleAbsUrls, styles, directives}: { constructor({componentId, templateAbsUrl, template, styleAbsUrls, styles, directives,
encapsulation}: {
componentId?: string, componentId?: string,
templateAbsUrl?: string, templateAbsUrl?: string,
template?: string, template?: string,
styleAbsUrls?: List<string>, styleAbsUrls?: List<string>,
styles?: List<string>, styles?: List<string>,
directives?: List<DirectiveMetadata> directives?: List<DirectiveMetadata>,
}) { encapsulation?: ViewEncapsulation
} = {}) {
this.componentId = componentId; this.componentId = componentId;
this.templateAbsUrl = templateAbsUrl; this.templateAbsUrl = templateAbsUrl;
this.template = template; this.template = template;
this.styleAbsUrls = styleAbsUrls; this.styleAbsUrls = styleAbsUrls;
this.styles = styles; this.styles = styles;
this.directives = directives; this.directives = directives;
this.encapsulation = isPresent(encapsulation) ? encapsulation : ViewEncapsulation.EMULATED;
} }
} }

View File

@ -29,7 +29,7 @@ export class CompileControl {
var step = this._steps[i]; var step = this._steps[i];
this._parent = parent; this._parent = parent;
this._currentStepIndex = i; this._currentStepIndex = i;
step.process(parent, current, this); step.processElement(parent, current, this);
parent = this._parent; parent = this._parent;
} }

View File

@ -5,7 +5,7 @@ import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control'; import {CompileControl} from './compile_control';
import {CompileStep} from './compile_step'; import {CompileStep} from './compile_step';
import {ProtoViewBuilder} from '../view/proto_view_builder'; import {ProtoViewBuilder} from '../view/proto_view_builder';
import {ProtoViewDto, ViewType} from '../../api'; import {ProtoViewDto, ViewType, ViewDefinition} from '../../api';
/** /**
* CompilePipeline for executing CompileSteps recursively for * CompilePipeline for executing CompileSteps recursively for
@ -13,25 +13,28 @@ import {ProtoViewDto, ViewType} from '../../api';
*/ */
export class CompilePipeline { export class CompilePipeline {
_control: CompileControl; _control: CompileControl;
constructor(steps: List<CompileStep>, private _useNativeShadowDom: boolean = false) { constructor(public steps: List<CompileStep>) { this._control = new CompileControl(steps); }
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, processElements(rootElement: Element, protoViewType: ViewType,
compilationCtxtDescription: string = ''): List<CompileElement> { viewDef: ViewDefinition): CompileElement[] {
if (isBlank(protoViewType)) { var results: CompileElement[] = [];
protoViewType = ViewType.COMPONENT; var compilationCtxtDescription = viewDef.componentId;
}
var results = [];
var rootCompileElement = new CompileElement(rootElement, compilationCtxtDescription); var rootCompileElement = new CompileElement(rootElement, compilationCtxtDescription);
rootCompileElement.inheritedProtoView = rootCompileElement.inheritedProtoView =
new ProtoViewBuilder(rootElement, protoViewType, this._useNativeShadowDom); new ProtoViewBuilder(rootElement, protoViewType, viewDef.encapsulation);
rootCompileElement.isViewRoot = true; rootCompileElement.isViewRoot = true;
this._process(results, null, rootCompileElement, compilationCtxtDescription); this._processElement(results, null, rootCompileElement, compilationCtxtDescription);
return results; return results;
} }
_process(results, parent: CompileElement, current: CompileElement, _processElement(results: CompileElement[], parent: CompileElement, current: CompileElement,
compilationCtxtDescription: string = '') { compilationCtxtDescription: string = '') {
var additionalChildren = this._control.internalProcess(results, 0, parent, current); var additionalChildren = this._control.internalProcess(results, 0, parent, current);
@ -46,7 +49,7 @@ export class CompilePipeline {
childCompileElement.inheritedProtoView = current.inheritedProtoView; childCompileElement.inheritedProtoView = current.inheritedProtoView;
childCompileElement.inheritedElementBinder = current.inheritedElementBinder; childCompileElement.inheritedElementBinder = current.inheritedElementBinder;
childCompileElement.distanceToInheritedBinder = current.distanceToInheritedBinder + 1; childCompileElement.distanceToInheritedBinder = current.distanceToInheritedBinder + 1;
this._process(results, current, childCompileElement); this._processElement(results, current, childCompileElement);
} }
node = nextNode; node = nextNode;
} }
@ -54,7 +57,7 @@ export class CompilePipeline {
if (isPresent(additionalChildren)) { if (isPresent(additionalChildren)) {
for (var i = 0; i < additionalChildren.length; i++) { 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 * Is guaranteed to be called in depth first order
*/ */
export interface CompileStep { export interface CompileStep {
process(parent: CompileElement, current: CompileElement, processElement(parent: CompileElement, current: CompileElement,
control: compileControlModule.CompileControl): void; 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 {TextInterpolationParser} from './text_interpolation_parser';
import {DirectiveParser} from './directive_parser'; import {DirectiveParser} from './directive_parser';
import {ViewSplitter} from './view_splitter'; import {ViewSplitter} from './view_splitter';
import {ShadowDomCompileStep} from '../shadow_dom/shadow_dom_compile_step'; import {StyleEncapsulator} from './style_encapsulator';
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
export class CompileStepFactory { export class CompileStepFactory {
createSteps(view: ViewDefinition): List<CompileStep> { return null; } createSteps(view: ViewDefinition): List<CompileStep> { return null; }
} }
export class DefaultStepFactory extends CompileStepFactory { 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> { createSteps(view: ViewDefinition): List<CompileStep> {
return [ return [
@ -22,7 +22,7 @@ export class DefaultStepFactory extends CompileStepFactory {
new PropertyBindingParser(this._parser), new PropertyBindingParser(this._parser),
new DirectiveParser(this._parser, view.directives), new DirectiveParser(this._parser, view.directives),
new TextInterpolationParser(this._parser), 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 {Injectable} from 'angular2/di';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; 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 {DOM} from 'angular2/src/dom/dom_adapter';
import { import {
@ -11,14 +11,18 @@ import {
DirectiveMetadata, DirectiveMetadata,
RenderCompiler, RenderCompiler,
RenderProtoViewRef, RenderProtoViewRef,
RenderProtoViewMergeMapping RenderProtoViewMergeMapping,
ViewEncapsulation
} from '../../api'; } from '../../api';
import {CompilePipeline} from './compile_pipeline'; 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 {CompileStepFactory, DefaultStepFactory} from './compile_step_factory';
import {Parser} from 'angular2/src/change_detection/change_detection'; 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 * 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 * 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. * the CompilePipeline and the CompileSteps.
*/ */
export class DomCompiler extends RenderCompiler { export class DomCompiler extends RenderCompiler {
constructor(public _stepFactory: CompileStepFactory, public _viewLoader: ViewLoader, constructor(private _stepFactory: CompileStepFactory, private _viewLoader: ViewLoader,
public _useNativeShadowDom: boolean) { private _sharedStylesHost: SharedStylesHost) {
super(); super();
} }
compile(view: ViewDefinition): Promise<ProtoViewDto> { compile(view: ViewDefinition): Promise<ProtoViewDto> {
var tplPromise = this._viewLoader.load(view); var tplPromise = this._viewLoader.load(view);
return PromiseWrapper.then( 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}`); throw new BaseException(`Failed to load the template for "${view.componentId}" : ${e}`);
return null; return null;
}); });
@ -46,11 +52,13 @@ export class DomCompiler extends RenderCompiler {
templateAbsUrl: null, template: null, templateAbsUrl: null, template: null,
styles: null, styles: null,
styleAbsUrls: null, styleAbsUrls: null,
directives: [directiveMetadata] directives: [directiveMetadata],
encapsulation: ViewEncapsulation.NONE
}); });
var template = DOM.createTemplate(''); return this._compileView(
DOM.appendChild(DOM.content(template), DOM.createElement(directiveMetadata.selector)); hostViewDef, new TemplateAndStyles(
return this._compileTemplate(hostViewDef, template, ViewType.HOST); `<${directiveMetadata.selector}></${directiveMetadata.selector}>`, []),
ViewType.HOST);
} }
mergeProtoViewsRecursively( mergeProtoViewsRecursively(
@ -58,20 +66,47 @@ export class DomCompiler extends RenderCompiler {
return PromiseWrapper.resolve(pvm.mergeProtoViewsRecursively(protoViewRefs)); return PromiseWrapper.resolve(pvm.mergeProtoViewsRecursively(protoViewRefs));
} }
_compileTemplate(viewDef: ViewDefinition, tplElement, _compileView(viewDef: ViewDefinition, templateAndStyles: TemplateAndStyles,
protoViewType: ViewType): Promise<ProtoViewDto> { protoViewType: ViewType): Promise<ProtoViewDto> {
var pipeline = if (viewDef.encapsulation === ViewEncapsulation.EMULATED &&
new CompilePipeline(this._stepFactory.createSteps(viewDef), this._useNativeShadowDom); templateAndStyles.styles.length === 0) {
var compileElements = pipeline.process(tplElement, protoViewType, viewDef.componentId); 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()); 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() @Injectable()
export class DefaultDomCompiler extends DomCompiler { export class DefaultDomCompiler extends DomCompiler {
constructor(parser: Parser, shadowDomStrategy: ShadowDomStrategy, viewLoader: ViewLoader) { constructor(parser: Parser, viewLoader: ViewLoader, sharedStylesHost: SharedStylesHost,
super(new DefaultStepFactory(parser, shadowDomStrategy), viewLoader, @Inject(APP_ID_TOKEN) appId: any) {
shadowDomStrategy.hasNativeContentElement()); 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) { _ensureComponentOnlyHasElementSelector(selector, directive) {
var isElementSelector = selector.length === 1 && selector[0].isElementSelector(); var isElementSelector = selector.length === 1 && selector[0].isElementSelector();
if (!isElementSelector && directive.type === DirectiveMetadata.COMPONENT_TYPE) { 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 attrs = current.attrs();
var classList = current.classList(); var classList = current.classList();
var cssSelector = new CssSelector(); var cssSelector = new CssSelector();

View File

@ -25,7 +25,9 @@ var BIND_NAME_REGEXP =
export class PropertyBindingParser implements CompileStep { export class PropertyBindingParser implements CompileStep {
constructor(private _parser: Parser) {} 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 attrs = current.attrs();
var newAttrs = new Map(); 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 { export class TextInterpolationParser implements CompileStep {
constructor(public _parser: Parser) {} 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) { if (!current.compileChildren) {
return; return;
} }

View File

@ -10,14 +10,17 @@ import {
import {Map, MapWrapper, ListWrapper, List} from 'angular2/src/facade/collection'; import {Map, MapWrapper, ListWrapper, List} from 'angular2/src/facade/collection';
import {PromiseWrapper, Promise} from 'angular2/src/facade/async'; import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {ViewDefinition} from '../../api';
import {XHR} from 'angular2/src/render/xhr'; import {XHR} from 'angular2/src/render/xhr';
import {ViewDefinition} from '../../api';
import {StyleInliner} from './style_inliner'; import {StyleInliner} from './style_inliner';
import {StyleUrlResolver} from './style_url_resolver'; import {StyleUrlResolver} from './style_url_resolver';
export class TemplateAndStyles {
constructor(public template: string, public styles: string[]) {}
}
/** /**
* Strategy to load component views. * Strategy to load component views.
* TODO: Make public API once we are more confident in this approach. * 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, constructor(private _xhr: XHR, private _styleInliner: StyleInliner,
private _styleUrlResolver: StyleUrlResolver) {} private _styleUrlResolver: StyleUrlResolver) {}
load(view: ViewDefinition): Promise</*element*/ any> { load(viewDef: ViewDefinition): Promise<TemplateAndStyles> {
let tplElAndStyles: List<string | Promise<string>> = [this._loadHtml(view)]; let tplAndStyles: List<Promise<TemplateAndStyles>| Promise<string>| string> =
[this._loadHtml(viewDef.template, viewDef.templateAbsUrl)];
if (isPresent(view.styles)) { if (isPresent(viewDef.styles)) {
view.styles.forEach((cssText: string) => { viewDef.styles.forEach((cssText: string) => {
let textOrPromise = this._resolveAndInlineCssText(cssText, view.templateAbsUrl); let textOrPromise = this._resolveAndInlineCssText(cssText, viewDef.templateAbsUrl);
tplElAndStyles.push(textOrPromise); tplAndStyles.push(textOrPromise);
}); });
} }
if (isPresent(view.styleAbsUrls)) { if (isPresent(viewDef.styleAbsUrls)) {
view.styleAbsUrls.forEach(url => { viewDef.styleAbsUrls.forEach(url => {
let promise = this._loadText(url).then( let promise = this._loadText(url).then(
cssText => this._resolveAndInlineCssText(cssText, view.templateAbsUrl)); cssText => this._resolveAndInlineCssText(cssText, viewDef.templateAbsUrl));
tplElAndStyles.push(promise); tplAndStyles.push(promise);
}); });
} }
// Inline the styles from the @View annotation and return a template element // Inline the styles from the @View annotation
return PromiseWrapper.all(tplElAndStyles) return PromiseWrapper.all(tplAndStyles)
.then((res: List<string>) => { .then((res: List<TemplateAndStyles | string>) => {
let tplEl = res[0]; let loadedTplAndStyles = <TemplateAndStyles>res[0];
let cssTexts = ListWrapper.slice(res, 1); let styles = <string[]>ListWrapper.slice(res, 1);
_insertCssTexts(DOM.content(tplEl), cssTexts); return new TemplateAndStyles(loadedTplAndStyles.template,
loadedTplAndStyles.styles.concat(styles));
return tplEl;
}); });
} }
@ -77,40 +79,54 @@ export class ViewLoader {
} }
// Load the html and inline any style tags // 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; let html;
// Load the HTML // Load the HTML
if (isPresent(view.template)) { if (isPresent(template)) {
html = PromiseWrapper.resolve(view.template); html = PromiseWrapper.resolve(template);
} else if (isPresent(view.templateAbsUrl)) { } else if (isPresent(templateAbsUrl)) {
html = this._loadText(view.templateAbsUrl); html = this._loadText(templateAbsUrl);
} else { } else {
throw new BaseException('View should have either the templateUrl or template property set'); throw new BaseException('View should have either the templateUrl or template property set');
} }
return html.then(html => { return html.then(html => {
var tplEl = DOM.createTemplate(html); var tplEl = DOM.createTemplate(html);
// Replace $baseUrl with the base url for the template // Replace $baseUrl with the base url for the template
let templateAbsUrl = view.templateAbsUrl;
if (isPresent(templateAbsUrl) && templateAbsUrl.indexOf("/") >= 0) { if (isPresent(templateAbsUrl) && templateAbsUrl.indexOf("/") >= 0) {
let baseUrl = templateAbsUrl.substring(0, templateAbsUrl.lastIndexOf("/")); let baseUrl = templateAbsUrl.substring(0, templateAbsUrl.lastIndexOf("/"));
this._substituteBaseUrl(DOM.content(tplEl), baseUrl); 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 // 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++) { for (let i = 0; i < styleEls.length; i++) {
let promise = this._resolveAndInlineElement(styleEls[i], view.templateAbsUrl); let styleEl = styleEls[i];
if (isPromise(promise)) { let resolvedStyled = this._resolveAndInlineCssText(DOM.getText(styleEl), templateAbsUrl);
promises.push(promise); 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> { private _resolveAndInlineCssText(cssText: string, baseUrl: string): string | Promise<string> {
cssText = this._styleUrlResolver.resolveUrls(cssText, baseUrl); cssText = this._styleUrlResolver.resolveUrls(cssText, baseUrl);
return this._styleInliner.inlineImports(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 { export class ViewSplitter implements CompileStep {
constructor(public _parser: Parser) {} 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 attrs = current.attrs();
var templateBindings = attrs.get('template'); var templateBindings = attrs.get('template');
var hasTemplateBinding = isPresent(templateBindings); 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 {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './view/proto_view';
import {DomView, DomViewRef, resolveInternalDomView} from './view/view'; import {DomView, DomViewRef, resolveInternalDomView} from './view/view';
import {DomFragmentRef, resolveInternalDomFragment} from './view/fragment'; import {DomFragmentRef, resolveInternalDomFragment} from './view/fragment';
import {DomSharedStylesHost} from './view/shared_styles_host';
import { import {
NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS_SELECTOR,
NG_BINDING_CLASS, NG_BINDING_CLASS,
@ -31,9 +32,8 @@ import {
RenderViewWithFragments RenderViewWithFragments
} from '../api'; } from '../api';
export const DOCUMENT_TOKEN: OpaqueToken = CONST_EXPR(new OpaqueToken('DocumentToken')); import {DOCUMENT_TOKEN, DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from './dom_tokens';
export const DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES: OpaqueToken =
CONST_EXPR(new OpaqueToken('DomReflectPropertiesAsAttributes'));
const REFLECT_PREFIX: string = 'ng-reflect-'; const REFLECT_PREFIX: string = 'ng-reflect-';
@Injectable() @Injectable()
@ -41,7 +41,8 @@ export class DomRenderer extends Renderer {
_document; _document;
_reflectPropertiesAsAttributes: boolean; _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: @Inject(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES) reflectPropertiesAsAttributes:
boolean) { boolean) {
super(); super();
@ -65,7 +66,14 @@ export class DomRenderer extends Renderer {
} }
destroyView(viewRef: RenderViewRef) { 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 { getNativeElementSync(location: RenderElementRef): any {
@ -226,7 +234,9 @@ export class DomRenderer extends Renderer {
// native shadow DOM // native shadow DOM
if (binder.hasNativeShadowRoot) { if (binder.hasNativeShadowRoot) {
var shadowRootWrapper = DOM.firstChild(element); 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); 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

@ -123,3 +123,20 @@ export function queryBoundTextNodeIndices(parentNode: Node, boundTextNodes: Map<
} }
} }
} }
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 {List, ListWrapper} from 'angular2/src/facade/collection';
import {DomElementBinder} from './element_binder'; import {DomElementBinder} from './element_binder';
import {RenderProtoViewRef, ViewType} from '../../api'; import {RenderProtoViewRef, ViewType, ViewEncapsulation} from '../../api';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
@ -14,9 +14,10 @@ export class DomProtoViewRef extends RenderProtoViewRef {
} }
export class DomProtoView { export class DomProtoView {
static create(type: ViewType, rootElement: Element, fragmentsRootNodeCount: number[], static create(type: ViewType, rootElement: Element, viewEncapsulation: ViewEncapsulation,
rootTextNodeIndices: number[], fragmentsRootNodeCount: number[], rootTextNodeIndices: number[],
elementBinders: List<DomElementBinder>): DomProtoView { elementBinders: List<DomElementBinder>,
hostAttributes: Map<string, string>): DomProtoView {
var boundTextNodeCount = rootTextNodeIndices.length; var boundTextNodeCount = rootTextNodeIndices.length;
for (var i = 0; i < elementBinders.length; i++) { for (var i = 0; i < elementBinders.length; i++) {
boundTextNodeCount += elementBinders[i].textNodeIndices.length; boundTextNodeCount += elementBinders[i].textNodeIndices.length;
@ -24,12 +25,15 @@ export class DomProtoView {
var isSingleElementFragment = fragmentsRootNodeCount.length === 1 && var isSingleElementFragment = fragmentsRootNodeCount.length === 1 &&
fragmentsRootNodeCount[0] === 1 && fragmentsRootNodeCount[0] === 1 &&
DOM.isElementNode(DOM.firstChild(DOM.content(rootElement))); DOM.isElementNode(DOM.firstChild(DOM.content(rootElement)));
return new DomProtoView(type, rootElement, elementBinders, rootTextNodeIndices, return new DomProtoView(type, rootElement, viewEncapsulation, elementBinders, hostAttributes,
boundTextNodeCount, fragmentsRootNodeCount, isSingleElementFragment); rootTextNodeIndices, boundTextNodeCount, fragmentsRootNodeCount,
isSingleElementFragment);
} }
constructor(public type: ViewType, public rootElement: Element, 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 boundTextNodeCount: number, public fragmentsRootNodeCount: number[],
public isSingleElementFragment: boolean) {} public isSingleElementFragment: boolean) {}
} }

View File

@ -35,9 +35,10 @@ export class ProtoViewBuilder {
elements: List<ElementBinderBuilder> = []; elements: List<ElementBinderBuilder> = [];
rootTextBindings: Map<Node, ASTWithSource> = new Map(); rootTextBindings: Map<Node, ASTWithSource> = new Map();
ngContentCount: number = 0; ngContentCount: number = 0;
hostAttributes: Map<string, string> = new Map();
constructor(public rootElement, public type: api.ViewType, constructor(public rootElement, public type: api.ViewType,
public useNativeShadowDom: boolean = false) {} public viewEncapsulation: api.ViewEncapsulation) {}
bindElement(element: HTMLElement, description: string = null): ElementBinderBuilder { bindElement(element: HTMLElement, description: string = null): ElementBinderBuilder {
var builder = new ElementBinderBuilder(this.elements.length, element, description); var builder = new ElementBinderBuilder(this.elements.length, element, description);
@ -65,6 +66,8 @@ export class ProtoViewBuilder {
bindNgContent() { this.ngContentCount++; } bindNgContent() { this.ngContentCount++; }
setHostAttribute(name: string, value: string) { this.hostAttributes.set(name, value); }
build(): api.ProtoViewDto { build(): api.ProtoViewDto {
var domElementBinders = []; var domElementBinders = [];
@ -119,7 +122,7 @@ export class ProtoViewBuilder {
domElementBinders.push(new DomElementBinder({ domElementBinders.push(new DomElementBinder({
textNodeIndices: textNodeIndices, textNodeIndices: textNodeIndices,
hasNestedProtoView: isPresent(nestedProtoView) || isPresent(ebb.componentId), hasNestedProtoView: isPresent(nestedProtoView) || isPresent(ebb.componentId),
hasNativeShadowRoot: isPresent(ebb.componentId) && this.useNativeShadowDom, hasNativeShadowRoot: false,
eventLocals: new LiteralArray(ebb.eventBuilder.buildEventLocals()), eventLocals: new LiteralArray(ebb.eventBuilder.buildEventLocals()),
localEvents: ebb.eventBuilder.buildLocalEvents(), localEvents: ebb.eventBuilder.buildLocalEvents(),
globalEvents: ebb.eventBuilder.buildGlobalEvents() globalEvents: ebb.eventBuilder.buildGlobalEvents()
@ -127,8 +130,9 @@ export class ProtoViewBuilder {
}); });
var rootNodeCount = DOM.childNodes(DOM.content(this.rootElement)).length; var rootNodeCount = DOM.childNodes(DOM.content(this.rootElement)).length;
return new api.ProtoViewDto({ return new api.ProtoViewDto({
render: new DomProtoViewRef(DomProtoView.create(this.type, this.rootElement, [rootNodeCount], render: new DomProtoViewRef(
rootTextNodeIndices, domElementBinders)), DomProtoView.create(this.type, this.rootElement, this.viewEncapsulation, [rootNodeCount],
rootTextNodeIndices, domElementBinders, this.hostAttributes)),
type: this.type, type: this.type,
elementBinders: apiElementBinders, elementBinders: apiElementBinders,
variableBindings: this.variableBindings, variableBindings: this.variableBindings,
@ -178,7 +182,8 @@ export class ElementBinderBuilder {
if (isPresent(this.nestedProtoView)) { if (isPresent(this.nestedProtoView)) {
throw new BaseException('Only one nested view per element is allowed'); 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; return this.nestedProtoView;
} }

View File

@ -1,10 +1,15 @@
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {isPresent, isBlank, BaseException, isArray} from 'angular2/src/facade/lang'; 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 {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view';
import {DomElementBinder} from './element_binder'; import {DomElementBinder} from './element_binder';
import {RenderProtoViewMergeMapping, RenderProtoViewRef, ViewType} from '../../api'; import {
RenderProtoViewMergeMapping,
RenderProtoViewRef,
ViewType,
ViewEncapsulation
} from '../../api';
import { import {
NG_BINDING_CLASS, NG_BINDING_CLASS,
NG_CONTENT_ELEMENT_NAME, NG_CONTENT_ELEMENT_NAME,
@ -12,7 +17,9 @@ import {
cloneAndQueryProtoView, cloneAndQueryProtoView,
queryBoundElements, queryBoundElements,
queryBoundTextNodeIndices, queryBoundTextNodeIndices,
NG_SHADOW_ROOT_ELEMENT_NAME NG_SHADOW_ROOT_ELEMENT_NAME,
isElementWithTag,
prependAll
} from '../util'; } from '../util';
export function mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRef | List<any>>): export function mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRef | List<any>>):
@ -26,7 +33,9 @@ export function mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRe
// modify the DOM // modify the DOM
mergeEmbeddedPvsIntoComponentOrRootPv(clonedProtoViews, hostViewAndBinderIndices); mergeEmbeddedPvsIntoComponentOrRootPv(clonedProtoViews, hostViewAndBinderIndices);
var fragments = []; 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 // Note: Need to remark parent elements of bound text nodes
// so that we can find them later via queryBoundElements! // so that we can find them later via queryBoundElements!
markBoundTextNodeParentsAsBoundElements(clonedProtoViews); markBoundTextNodeParentsAsBoundElements(clonedProtoViews);
@ -42,7 +51,8 @@ export function mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRe
var boundTextNodeMap: Map<Node, any> = indexBoundTextNodes(clonedProtoViews); var boundTextNodeMap: Map<Node, any> = indexBoundTextNodes(clonedProtoViews);
var rootTextNodeIndices = var rootTextNodeIndices =
calcRootTextNodeIndices(rootNode, boundTextNodeMap, mergedBoundTextIndices); calcRootTextNodeIndices(rootNode, boundTextNodeMap, mergedBoundTextIndices);
var mergedElementBinders = calcElementBinders(clonedProtoViews, mergedBoundElements, var mergedElementBinders =
calcElementBinders(clonedProtoViews, mergedBoundElements, elementsWithNativeShadowRoot,
boundTextNodeMap, mergedBoundTextIndices); boundTextNodeMap, mergedBoundTextIndices);
// create element / text index mappings // create element / text index mappings
@ -53,9 +63,9 @@ export function mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRe
var hostElementIndicesByViewIndex = var hostElementIndicesByViewIndex =
calcHostElementIndicesByViewIndex(clonedProtoViews, hostViewAndBinderIndices); calcHostElementIndicesByViewIndex(clonedProtoViews, hostViewAndBinderIndices);
var nestedViewCounts = calcNestedViewCounts(hostViewAndBinderIndices); var nestedViewCounts = calcNestedViewCounts(hostViewAndBinderIndices);
var mergedProtoView = var mergedProtoView = DomProtoView.create(
DomProtoView.create(mainProtoView.original.type, rootElement, fragmentsRootNodeCount, mainProtoView.original.type, rootElement, mainProtoView.original.encapsulation,
rootTextNodeIndices, mergedElementBinders); fragmentsRootNodeCount, rootTextNodeIndices, mergedElementBinders, new Map());
return new RenderProtoViewMergeMapping(new DomProtoViewRef(mergedProtoView), return new RenderProtoViewMergeMapping(new DomProtoViewRef(mergedProtoView),
fragmentsRootNodeCount.length, mappedElementIndices, fragmentsRootNodeCount.length, mappedElementIndices,
mergedBoundElements.length, mappedTextIndices, mergedBoundElements.length, mappedTextIndices,
@ -143,7 +153,8 @@ function calcNearestHostComponentOrRootPvIndices(clonedProtoViews: ClonedProtoVi
} }
function mergeComponents(clonedProtoViews: ClonedProtoView[], hostViewAndBinderIndices: number[][], function mergeComponents(clonedProtoViews: ClonedProtoView[], hostViewAndBinderIndices: number[][],
targetFragments: Node[][]) { targetFragments: Node[][],
targetElementsWithNativeShadowRoot: Set<Element>) {
var hostProtoView = clonedProtoViews[0]; var hostProtoView = clonedProtoViews[0];
hostProtoView.fragments.forEach((fragment) => targetFragments.push(fragment)); hostProtoView.fragments.forEach((fragment) => targetFragments.push(fragment));
@ -153,13 +164,15 @@ function mergeComponents(clonedProtoViews: ClonedProtoView[], hostViewAndBinderI
var hostProtoView = clonedProtoViews[hostViewIdx]; var hostProtoView = clonedProtoViews[hostViewIdx];
var clonedProtoView = clonedProtoViews[viewIdx]; var clonedProtoView = clonedProtoViews[viewIdx];
if (clonedProtoView.original.type === ViewType.COMPONENT) { if (clonedProtoView.original.type === ViewType.COMPONENT) {
mergeComponent(hostProtoView, hostBinderIdx, clonedProtoView, targetFragments); mergeComponent(hostProtoView, hostBinderIdx, clonedProtoView, targetFragments,
targetElementsWithNativeShadowRoot);
} }
} }
} }
function mergeComponent(hostProtoView: ClonedProtoView, binderIdx: number, function mergeComponent(hostProtoView: ClonedProtoView, binderIdx: number,
nestedProtoView: ClonedProtoView, targetFragments: Node[][]) { nestedProtoView: ClonedProtoView, targetFragments: Node[][],
targetElementsWithNativeShadowRoot: Set<Element>) {
var hostElement = hostProtoView.boundElements[binderIdx]; var hostElement = hostProtoView.boundElements[binderIdx];
// We wrap the fragments into elements so that we can expand <ng-content> // 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 // unwrap the fragment elements into arrays of nodes after projecting
var fragments = extractFragmentNodesFromElements(fragmentElements); 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++) { for (var i = 1; i < fragments.length; i++) {
targetFragments.push(fragments[i]); targetFragments.push(fragments[i]);
} }
@ -209,10 +228,9 @@ function findContentElements(fragmentElements: Element[]): Element[] {
} }
function appendComponentNodesToHost(hostProtoView: ClonedProtoView, binderIdx: number, function appendComponentNodesToHost(hostProtoView: ClonedProtoView, binderIdx: number,
componentRootNodes: Node[]) { componentRootNodes: Node[], useNativeShadowRoot: boolean) {
var hostElement = hostProtoView.boundElements[binderIdx]; var hostElement = hostProtoView.boundElements[binderIdx];
var binder = hostProtoView.original.elementBinders[binderIdx]; if (useNativeShadowRoot) {
if (binder.hasNativeShadowRoot) {
var shadowRootWrapper = DOM.createElement(NG_SHADOW_ROOT_ELEMENT_NAME); var shadowRootWrapper = DOM.createElement(NG_SHADOW_ROOT_ELEMENT_NAME);
for (var i = 0; i < componentRootNodes.length; i++) { for (var i = 0; i < componentRootNodes.length; i++) {
DOM.appendChild(shadowRootWrapper, componentRootNodes[i]); DOM.appendChild(shadowRootWrapper, componentRootNodes[i]);
@ -298,6 +316,7 @@ function calcRootTextNodeIndices(rootNode: Node, boundTextNodes: Map<Node, any>,
} }
function calcElementBinders(clonedProtoViews: ClonedProtoView[], mergedBoundElements: Element[], function calcElementBinders(clonedProtoViews: ClonedProtoView[], mergedBoundElements: Element[],
elementsWithNativeShadowRoot: Set<Element>,
boundTextNodes: Map<Node, any>, boundTextNodes: Map<Node, any>,
targetBoundTextIndices: Map<Node, number>): DomElementBinder[] { targetBoundTextIndices: Map<Node, number>): DomElementBinder[] {
var elementBinderByElement: Map<Element, DomElementBinder> = var elementBinderByElement: Map<Element, DomElementBinder> =
@ -311,7 +330,8 @@ function calcElementBinders(clonedProtoViews: ClonedProtoView[], mergedBoundElem
targetBoundTextIndices.set(textNode, targetBoundTextIndices.size); targetBoundTextIndices.set(textNode, targetBoundTextIndices.size);
}); });
mergedElementBinders.push( mergedElementBinders.push(
updateElementBinderTextNodeIndices(elementBinderByElement.get(element), textNodeIndices)); updateElementBinders(elementBinderByElement.get(element), textNodeIndices,
SetWrapper.has(elementsWithNativeShadowRoot, element)));
} }
return mergedElementBinders; return mergedElementBinders;
} }
@ -330,8 +350,8 @@ function indexElementBindersByElement(mergableProtoViews: ClonedProtoView[]):
return elementBinderByElement; return elementBinderByElement;
} }
function updateElementBinderTextNodeIndices(elementBinder: DomElementBinder, function updateElementBinders(elementBinder: DomElementBinder, textNodeIndices: number[],
textNodeIndices: number[]): DomElementBinder { hasNativeShadowRoot: boolean): DomElementBinder {
var result; var result;
if (isBlank(elementBinder)) { if (isBlank(elementBinder)) {
result = new DomElementBinder({ result = new DomElementBinder({
@ -349,7 +369,7 @@ function updateElementBinderTextNodeIndices(elementBinder: DomElementBinder,
eventLocals: elementBinder.eventLocals, eventLocals: elementBinder.eventLocals,
localEvents: elementBinder.localEvents, localEvents: elementBinder.localEvents,
globalEvents: elementBinder.globalEvents, globalEvents: elementBinder.globalEvents,
hasNativeShadowRoot: elementBinder.hasNativeShadowRoot hasNativeShadowRoot: hasNativeShadowRoot
}); });
} }
return result; 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/compiler/view_loader';
export * from './dom/view/shared_styles_host';
export * from './dom/compiler/compiler';
export * from './dom/dom_renderer'; export * from './dom/dom_renderer';
export * from './dom/shadow_dom/shadow_dom_strategy'; export * from './dom/dom_tokens';
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 './api'; export * from './api';

View File

@ -16,7 +16,7 @@ import {
import {el} from './utils'; 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 {DOM} from 'angular2/src/dom/dom_adapter';
import {DebugElement} from 'angular2/src/debug/debug_element'; 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 {ViewResolver} from 'angular2/src/core/compiler/view_resolver';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver'; import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; 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 {XHR} from 'angular2/src/render/xhr';
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper'; import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
import {UrlResolver} from 'angular2/src/services/url_resolver'; import {UrlResolver} from 'angular2/src/services/url_resolver';
@ -54,9 +50,12 @@ import {RenderCompiler, Renderer} from 'angular2/src/render/api';
import { import {
DomRenderer, DomRenderer,
DOCUMENT_TOKEN, DOCUMENT_TOKEN,
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES,
} from 'angular2/src/render/dom/dom_renderer'; DefaultDomCompiler,
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler'; APP_ID_TOKEN,
SharedStylesHost,
DomSharedStylesHost
} from 'angular2/src/render/render';
import {Serializer} from "angular2/src/web-workers/shared/serializer"; import {Serializer} from "angular2/src/web-workers/shared/serializer";
import {Log} from './utils'; import {Log} from './utils';
@ -94,13 +93,14 @@ function _getAppBindings() {
return [ return [
bind(DOCUMENT_TOKEN) bind(DOCUMENT_TOKEN)
.toValue(appDoc), .toValue(appDoc),
bind(ShadowDomStrategy)
.toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]),
DomRenderer, DomRenderer,
DefaultDomCompiler,
bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false),
bind(Renderer).toAlias(DomRenderer), bind(Renderer).toAlias(DomRenderer),
bind(APP_ID_TOKEN).toValue('a'),
DefaultDomCompiler,
bind(RenderCompiler).toAlias(DefaultDomCompiler), bind(RenderCompiler).toAlias(DefaultDomCompiler),
DomSharedStylesHost,
bind(SharedStylesHost).toAlias(DomSharedStylesHost),
bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false),
ProtoViewFactory, ProtoViewFactory,
AppViewPool, AppViewPool,
AppViewManager, 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/lexer.dart' as ng;
import 'package:angular2/src/change_detection/parser/parser.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/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/api.dart';
import 'package:angular2/src/render/dom/compiler/compile_pipeline.dart'; import 'package:angular2/src/render/dom/compiler/compile_pipeline.dart';
import 'package:angular2/src/render/dom/compiler/style_inliner.dart'; import 'package:angular2/src/render/dom/compiler/style_inliner.dart';
@ -96,7 +97,7 @@ class _TemplateExtractor {
// Check for "imperative views". // Check for "imperative views".
if (viewDef.template == null && viewDef.templateAbsUrl == null) return null; 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 // NOTE(kegluneq): Since this is a global, we must not have any async
// operations between saving and restoring it, otherwise we can get into // 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 pipeline = new CompilePipeline(_factory.createSteps(viewDef));
var compileElements = var compileElements =
pipeline.process(templateEl, ViewType.COMPONENT, viewDef.componentId); pipeline.processElements(DOM.createTemplate(templateAndStyles.template), ViewType.COMPONENT, viewDef);
var protoViewDto = compileElements[0].inheritedProtoView.build(); var protoViewDto = compileElements[0].inheritedProtoView.build();
reflector.reflectionCapabilities = savedReflectionCapabilities; reflector.reflectionCapabilities = savedReflectionCapabilities;

View File

@ -213,7 +213,8 @@ export class Serializer {
'template': view.template, 'template': view.template,
'directives': this.serialize(view.directives, DirectiveMetadata), 'directives': this.serialize(view.directives, DirectiveMetadata),
'styleAbsUrls': view.styleAbsUrls, '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'], templateAbsUrl: obj['templateAbsUrl'], template: obj['template'],
directives: this.deserialize(obj['directives'], DirectiveMetadata), directives: this.deserialize(obj['directives'], DirectiveMetadata),
styleAbsUrls: obj['styleAbsUrls'], 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 { import {
DomRenderer, DomRenderer,
DOCUMENT_TOKEN, DOCUMENT_TOKEN,
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES,
} from 'angular2/src/render/dom/dom_renderer'; DefaultDomCompiler,
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler'; 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 {DOM} from 'angular2/src/dom/dom_adapter';
import {NgZone} from 'angular2/src/core/zone/ng_zone'; 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 {AppViewManager} from 'angular2/src/core/compiler/view_manager';
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils'; import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
import {AppViewListener} from 'angular2/src/core/compiler/view_listener'; 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); return new EventManager(plugins, ngZone);
}, },
[NgZone]), [NgZone]),
bind(ShadowDomStrategy)
.toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]),
bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false), bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false),
DomRenderer, DomRenderer,
DefaultDomCompiler,
Serializer,
bind(Renderer).toAlias(DomRenderer), bind(Renderer).toAlias(DomRenderer),
APP_ID_RANDOM_BINDING,
DefaultDomCompiler,
bind(RenderCompiler).toAlias(DefaultDomCompiler), bind(RenderCompiler).toAlias(DefaultDomCompiler),
DomSharedStylesHost,
bind(SharedStylesHost).toAlias(DomSharedStylesHost),
Serializer,
bind(ON_WEBWORKER).toValue(false), bind(ON_WEBWORKER).toValue(false),
RenderViewWithFragmentsStore, RenderViewWithFragmentsStore,
RenderProtoViewRefStore, 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 {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle';
import {ExceptionHandler} from 'angular2/src/core/exception_handler'; import {ExceptionHandler} from 'angular2/src/core/exception_handler';
import {Testability, TestabilityRegistry} from 'angular2/src/core/testability/testability'; 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'}) @Component({selector: 'hello-app'})
@View({template: '{{greeting}} world!'}) @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 * as viewAnn from 'angular2/src/core/annotations_impl/view';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader'; import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
import {ElementRef} from 'angular2/src/core/compiler/element_ref'; 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'; import {DOM} from 'angular2/src/dom/dom_adapter';
export function main() { export function main() {

View File

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

View File

@ -34,9 +34,9 @@ import {
ViewContainerRef, ViewContainerRef,
ElementRef, ElementRef,
TemplateRef, TemplateRef,
bind bind,
ViewEncapsulation
} from 'angular2/angular2'; } from 'angular2/angular2';
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/render/render';
export function main() { export function main() {
describe('projection', () => { describe('projection', () => {
@ -399,13 +399,8 @@ export function main() {
})); }));
if (DOM.supportsNativeShadowDOM()) { if (DOM.supportsNativeShadowDOM()) {
describe('native shadow dom support', () => {
beforeEachBindings(
() => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; });
it('should support native content projection', it('should support native content projection',
inject([TestComponentBuilder, AsyncTestCompleter], inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
(tcb: TestComponentBuilder, async) => {
tcb.overrideView(MainComp, new viewAnn.View({ tcb.overrideView(MainComp, new viewAnn.View({
template: '<simple-native>' + template: '<simple-native>' +
'<div>A</div>' + '<div>A</div>' +
@ -419,7 +414,6 @@ export function main() {
async.done(); async.done();
}); });
})); }));
});
} }
}); });
@ -438,7 +432,11 @@ class Simple {
} }
@Component({selector: 'simple-native'}) @Component({selector: 'simple-native'})
@View({template: 'SIMPLE(<content></content>)', directives: []}) @View({
template: 'SIMPLE(<content></content>)',
directives: [],
encapsulation: ViewEncapsulation.NATIVE
})
class SimpleNative { 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 {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {DomCompiler} from 'angular2/src/render/dom/compiler/compiler'; import {DomCompiler} from 'angular2/src/render/dom/compiler/compiler';
import {ProtoViewDto, ViewDefinition, DirectiveMetadata, ViewType} from 'angular2/src/render/api'; import {
import {CompileElement} from 'angular2/src/render/dom/compiler/compile_element'; ProtoViewDto,
ViewDefinition,
DirectiveMetadata,
ViewType,
ViewEncapsulation
} from 'angular2/src/render/api';
import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step'; import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step';
import {CompileStepFactory} from 'angular2/src/render/dom/compiler/compile_step_factory'; import {CompileStepFactory} from 'angular2/src/render/dom/compiler/compile_step_factory';
import {CompileControl} from 'angular2/src/render/dom/compiler/compile_control'; import {ViewLoader, TemplateAndStyles} from 'angular2/src/render/dom/compiler/view_loader';
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view'; 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() { export function runCompilerCommonTests() {
describe('DomCompiler', function() { describe('DomCompiler', function() {
var mockStepFactory: MockStepFactory; 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)) { if (isBlank(urlData)) {
urlData = new Map(); urlData = new Map();
} }
var tplLoader = new FakeViewLoader(urlData); var tplLoader = new FakeViewLoader(urlData);
mockStepFactory = new MockStepFactory([new MockStep(processClosure)]); mockStepFactory =
return new DomCompiler(mockStepFactory, tplLoader, false); new MockStepFactory([new MockStep(processElementClosure, processStyleClosure)]);
return new DomCompiler(mockStepFactory, tplLoader, sharedStylesHost);
} }
describe('compile', () => { describe('compile', () => {
@ -61,12 +73,13 @@ export function runCompilerCommonTests() {
}); });
var dirMetadata = DirectiveMetadata.create( var dirMetadata = DirectiveMetadata.create(
{id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE}); {id: 'id', selector: 'custom', type: DirectiveMetadata.COMPONENT_TYPE});
compiler.compileHost(dirMetadata) compiler.compileHost(dirMetadata)
.then((protoView) => { .then((protoView) => {
expect(DOM.tagName(DOM.firstChild(DOM.content( expect(DOM.tagName(DOM.firstChild(DOM.content(
resolveInternalDomProtoView(protoView.render).rootElement)))) resolveInternalDomProtoView(protoView.render).rootElement)))
.toEqual('CUSTOM'); .toLowerCase())
.toEqual('custom');
expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]); expect(mockStepFactory.viewDef.directives).toEqual([dirMetadata]);
expect(protoView.variableBindings) expect(protoView.variableBindings)
.toEqual(MapWrapper.createFromStringMap({'a': 'b'})); .toEqual(MapWrapper.createFromStringMap({'a': 'b'}));
@ -88,7 +101,7 @@ export function runCompilerCommonTests() {
it('should load url templates', inject([AsyncTestCompleter], (async) => { it('should load url templates', inject([AsyncTestCompleter], (async) => {
var urlData = MapWrapper.createFromStringMap({'someUrl': 'url component'}); 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'})) compiler.compile(new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'}))
.then((protoView) => { .then((protoView) => {
expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement)) expect(DOM.getInnerHTML(resolveInternalDomProtoView(protoView.render).rootElement))
@ -98,7 +111,7 @@ export function runCompilerCommonTests() {
})); }));
it('should report loading errors', inject([AsyncTestCompleter], (async) => { 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( PromiseWrapper.catchError(
compiler.compile( compiler.compile(
new ViewDefinition({componentId: 'someId', templateAbsUrl: 'someUrl'})), 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) => { var EMPTY_STEP = (parent, current, control) => {
if (isPresent(parent)) { if (isPresent(parent)) {
current.inheritedProtoView = parent.inheritedProtoView; current.inheritedProtoView = parent.inheritedProtoView;
@ -176,16 +285,17 @@ class FakeViewLoader extends ViewLoader {
this._urlData = urlData; this._urlData = urlData;
} }
load(view: ViewDefinition): Promise<any> { load(viewDef): Promise<any> {
if (isPresent(view.template)) { var styles = isPresent(viewDef.styles) ? viewDef.styles : [];
return PromiseWrapper.resolve(DOM.createTemplate(view.template)); if (isPresent(viewDef.template)) {
return PromiseWrapper.resolve(new TemplateAndStyles(viewDef.template, styles));
} }
if (isPresent(view.templateAbsUrl)) { if (isPresent(viewDef.templateAbsUrl)) {
var content = this._urlData.get(view.templateAbsUrl); var content = this._urlData.get(viewDef.templateAbsUrl);
return isPresent(content) ? return isPresent(content) ?
PromiseWrapper.resolve(DOM.createTemplate(content)) : PromiseWrapper.resolve(new TemplateAndStyles(content, styles)) :
PromiseWrapper.reject(`Failed to fetch url "${view.templateAbsUrl}"`, null); PromiseWrapper.reject(`Failed to fetch url "${viewDef.templateAbsUrl}"`, null);
} }
throw new BaseException('View should have either the templateUrl or template property set'); 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 {DOM} from 'angular2/src/dom/dom_adapter';
import {DirectiveParser} from 'angular2/src/render/dom/compiler/directive_parser'; import {DirectiveParser} from 'angular2/src/render/dom/compiler/directive_parser';
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline'; import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
import {CompileStep} from 'angular2/src/render/dom/compiler/compile_step'; import {ViewDefinition, DirectiveMetadata, ViewType} from 'angular2/src/render/api';
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 {Lexer, Parser} from 'angular2/src/change_detection/change_detection'; import {Lexer, Parser} from 'angular2/src/change_detection/change_detection';
import {ElementBinderBuilder} from 'angular2/src/render/dom/view/proto_view_builder'; import {ElementBinderBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
import {MockStep} from './pipeline_spec';
export function main() { export function main() {
describe('DirectiveParser', () => { 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> { function process(el, propertyBindings = null, directives = null): List<ElementBinderBuilder> {
var pipeline = createPipeline(propertyBindings, directives); 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', () => { it('should not add directives if they are not used', () => {
@ -70,12 +74,14 @@ export function main() {
}); });
it('should compile children by default', () => { 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); expect(results[0].compileChildren).toEqual(true);
}); });
it('should stop compiling children when specified in the directive config', () => { 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); 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( var someComponent = DirectiveMetadata.create(
{selector: 'some-comp', id: 'someComponent', type: DirectiveMetadata.COMPONENT_TYPE}); {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 {CompileControl} from 'angular2/src/render/dom/compiler/compile_control';
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder'; 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() { export function main() {
describe('compile_pipeline', () => { describe('compile_pipeline', () => {
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
describe('children compilation', () => { describe('children compilation', () => {
it('should walk the tree in depth first order including template contents', () => { 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 element = el('<div id="1"><template id="2"><span id="3"></span></template></div>');
var step0Log = []; 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(step0Log).toEqual(['1', '1<2', '2<3']);
expect(resultIdLog(results)).toEqual(['1', '2', '3']); expect(resultIdLog(results)).toEqual(['1', '2', '3']);
@ -30,7 +35,7 @@ export function main() {
var step0Log = []; var step0Log = [];
var pipeline = new CompilePipeline([new IgnoreChildrenStep(), createLoggerStep(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(step0Log).toEqual(['1', '1<2']);
expect(resultIdLog(results)).toEqual(['1', '2']); expect(resultIdLog(results)).toEqual(['1', '2']);
@ -42,11 +47,12 @@ export function main() {
var pipeline = new CompilePipeline([ var pipeline = new CompilePipeline([
new MockStep((parent, current, control) => { new MockStep((parent, current, control) => {
if (isPresent(DOM.getAttribute(current.element, 'viewroot'))) { 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[0].inheritedProtoView).toBe(results[1].inheritedProtoView);
expect(results[2].inheritedProtoView).toBe(results[3].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[0].inheritedElementBinder).toBe(results[1].inheritedElementBinder);
expect(results[2].inheritedElementBinder).toBe(results[3].inheritedElementBinder); expect(results[2].inheritedElementBinder).toBe(results[3].inheritedElementBinder);
}); });
it('should mark root elements as viewRoot', () => { it('should mark root elements as viewRoot', () => {
var rootElement = el('<div></div>'); 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); 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[0].inheritedElementBinder.distanceToParent).toBe(0);
expect(results[1].inheritedElementBinder.distanceToParent).toBe(1); expect(results[1].inheritedElementBinder.distanceToParent).toBe(1);
expect(results[3].inheritedElementBinder.distanceToParent).toBe(2); expect(results[3].inheritedElementBinder.distanceToParent).toBe(2);
@ -95,7 +102,7 @@ export function main() {
new IgnoreCurrentElementStep(), new IgnoreCurrentElementStep(),
createLoggerStep(logs), createLoggerStep(logs),
]); ]);
var results = pipeline.process(element); var results = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(results.length).toBe(2); expect(results.length).toBe(2);
expect(logs).toEqual(['1', '1<3']) expect(logs).toEqual(['1', '1<3'])
@ -108,7 +115,7 @@ export function main() {
var step1Log = []; var step1Log = [];
var pipeline = var pipeline =
new CompilePipeline([createWrapperStep('wrap0', step0Log), createLoggerStep(step1Log)]); 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(step0Log).toEqual(['1', '1<2', '2<3']);
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']); expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<2', '2<3']);
expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', '2', '3']); expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', '2', '3']);
@ -125,7 +132,7 @@ export function main() {
createWrapperStep('wrap1', step1Log), createWrapperStep('wrap1', step1Log),
createLoggerStep(step2Log) createLoggerStep(step2Log)
]); ]);
var result = pipeline.process(element); var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2', '2<3']); expect(step0Log).toEqual(['1', '1<2', '2<3']);
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<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']); 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), createWrapperStep('wrap1', step1Log),
createLoggerStep(step2Log) createLoggerStep(step2Log)
]); ]);
var result = pipeline.process(element); var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(step0Log).toEqual(['1', '1<2', '2<3']); expect(step0Log).toEqual(['1', '1<2', '2<3']);
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<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']); 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 step1Log = [];
var pipeline = var pipeline =
new CompilePipeline([createWrapperStep('wrap0', step0Log), createLoggerStep(step1Log)]); 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(step0Log).toEqual(['1', '1<2', '2<3']);
expect(step1Log).toEqual(['1', '1<wrap0#0', 'wrap0#0<wrap0#1', 'wrap0#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']); expect(resultIdLog(result)).toEqual(['1', 'wrap0#0', 'wrap0#1', '2', '3']);
@ -177,40 +184,68 @@ export function main() {
}), }),
createLoggerStep(resultLog) createLoggerStep(resultLog)
]); ]);
var result = pipeline.process(element); var result = pipeline.processElements(element, ViewType.COMPONENT, createViewDefinition());
expect(result[2]).toBe(newChild); expect(result[2]).toBe(newChild);
expect(resultLog).toEqual(['1', '1<2', '1<3']); expect(resultLog).toEqual(['1', '1<2', '1<3']);
expect(resultIdLog(result)).toEqual(['1', '2', '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 { export class MockStep implements CompileStep {
processClosure: Function; constructor(private processElementClosure: Function,
constructor(process) { this.processClosure = process; } private processStyleClosure: Function = null) {}
process(parent: CompileElement, current: CompileElement, control: CompileControl) { processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
this.processClosure(parent, current, control); 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 { 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); var attributeMap = DOM.attributeMap(current.element);
if (attributeMap.has('ignore-children')) { if (attributeMap.has('ignore-children')) {
current.compileChildren = false; current.compileChildren = false;
} }
} }
processStyle(style: string): string { return style; }
} }
class IgnoreCurrentElementStep implements CompileStep { class IgnoreCurrentElementStep implements CompileStep {
process(parent: CompileElement, current: CompileElement, control: CompileControl) { processElement(parent: CompileElement, current: CompileElement, control: CompileControl) {
var attributeMap = DOM.attributeMap(current.element); var attributeMap = DOM.attributeMap(current.element);
if (attributeMap.has('ignore-current')) { if (attributeMap.has('ignore-current')) {
control.ignoreCurrentElement(); control.ignoreCurrentElement();
} }
} }
processStyle(style: string): string { return style; }
} }
function logEntry(log: string[], parent, current) { 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 {PropertyBindingParser} from 'angular2/src/render/dom/compiler/property_binding_parser';
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline'; import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; 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 {Lexer, Parser} from 'angular2/src/change_detection/change_detection';
import {ElementBinderBuilder} from 'angular2/src/render/dom/view/proto_view_builder'; 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(); var EMPTY_MAP = new Map();
@ -24,8 +23,14 @@ export function main() {
]); ]);
} }
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
function process(element, hasNestedProtoView = false): List<ElementBinderBuilder> { function process(element, hasNestedProtoView = false): List<ElementBinderBuilder> {
return ListWrapper.map(createPipeline(hasNestedProtoView).process(element), return ListWrapper.map(
createPipeline(hasNestedProtoView)
.processElements(element, ViewType.COMPONENT, createViewDefinition()),
(compileElement) => compileElement.inheritedElementBinder); (compileElement) => compileElement.inheritedElementBinder);
} }
@ -174,13 +179,15 @@ export function main() {
}); });
it('should store bound properties as temporal attributes', () => { 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('a')).toEqual('b');
expect(results[0].attrs().get('c')).toEqual('d'); expect(results[0].attrs().get('c')).toEqual('d');
}); });
it('should store variables as temporal attributes', () => { 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('a')).toEqual('b');
expect(results[0].attrs().get('c')).toEqual('d'); 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, el,
normalizeCSS normalizeCSS
} from 'angular2/test_lib'; } 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 {RegExpWrapper, StringWrapper, isPresent} from 'angular2/src/facade/lang';
import {DOM} from 'angular2/src/dom/dom_adapter'; 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 ElementBinderBuilder
} from 'angular2/src/render/dom/view/proto_view_builder'; } from 'angular2/src/render/dom/view/proto_view_builder';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {ViewDefinition, ViewType} from 'angular2/src/render/api';
export function main() { export function main() {
describe('TextInterpolationParser', () => { describe('TextInterpolationParser', () => {
@ -17,8 +18,13 @@ export function main() {
[new IgnoreChildrenStep(), new TextInterpolationParser(new Parser(new Lexer()))]); [new IgnoreChildrenStep(), new TextInterpolationParser(new Parser(new Lexer()))]);
} }
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
function process(templateString: string): ProtoViewBuilder { 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; return compileElements[0].inheritedProtoView;
} }

View File

@ -10,21 +10,21 @@ import {
it, it,
xit, xit,
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {ViewLoader, TemplateAndStyles} from 'angular2/src/render/dom/compiler/view_loader';
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner'; import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner';
import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver'; import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver';
import {UrlResolver} from 'angular2/src/services/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 {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {XHR} from 'angular2/src/render/xhr'; import {XHR} from 'angular2/src/render/xhr';
import {MockXHR} from 'angular2/src/render/xhr_mock'; import {MockXHR} from 'angular2/src/render/xhr_mock';
import {ViewDefinition} from 'angular2/src/render/api';
export function main() { export function main() {
describe('ViewLoader', () => { describe('ViewLoader', () => {
var loader, xhr, styleUrlResolver, urlResolver; var loader: ViewLoader;
var xhr, styleUrlResolver, urlResolver;
beforeEach(() => { beforeEach(() => {
xhr = new MockXHR(); xhr = new MockXHR();
@ -36,18 +36,18 @@ export function main() {
describe('html', () => { describe('html', () => {
it('should load inline templates', inject([AsyncTestCompleter], (async) => { it('should load inline templates', inject([AsyncTestCompleter], (async) => {
var view = new ViewDefinition({template: 'template template'}); loader.load(new ViewDefinition({template: 'template template'}))
loader.load(view).then((el) => { .then((el) => {
expect(DOM.content(el)).toHaveText('template template'); expect(el.template).toEqual('template template');
async.done(); async.done();
}); });
})); }));
it('should load templates through XHR', inject([AsyncTestCompleter], (async) => { it('should load templates through XHR', inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.html', 'xhr template'); xhr.expect('http://ng.io/foo.html', 'xhr template');
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}); loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}))
loader.load(view).then((el) => { .then((el) => {
expect(DOM.content(el)).toHaveText('xhr template'); expect(el.template).toEqual('xhr template');
async.done(); async.done();
}); });
xhr.flush(); xhr.flush();
@ -56,10 +56,11 @@ export function main() {
it('should resolve urls in styles', inject([AsyncTestCompleter], (async) => { it('should resolve urls in styles', inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.html', xhr.expect('http://ng.io/foo.html',
'<style>.foo { background-image: url("double.jpg"); }</style>'); '<style>.foo { background-image: url("double.jpg"); }</style>');
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}); loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}))
loader.load(view).then((el) => { .then((el) => {
expect(DOM.content(el)) expect(el.template).toEqual('');
.toHaveText(".foo { background-image: url('http://ng.io/double.jpg'); }"); expect(el.styles)
.toEqual([".foo { background-image: url('http://ng.io/double.jpg'); }"]);
async.done(); async.done();
}); });
xhr.flush(); xhr.flush();
@ -73,57 +74,38 @@ export function main() {
let styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver); let styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
let loader = new ViewLoader(xhr, styleInliner, styleUrlResolver); let loader = new ViewLoader(xhr, styleInliner, styleUrlResolver);
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}); loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}))
loader.load(view).then((el) => { .then((el) => {
expect(DOM.getInnerHTML(el)).toEqual("<style>/* foo.css */\n</style>"); expect(el.template).toEqual('');
expect(el.styles).toEqual(["/* foo.css */\n"]);
async.done(); 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)
.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');
async.done();
});
xhr.flush();
}));
it('should throw when no template is defined', () => { it('should throw when no template is defined', () => {
var view = new ViewDefinition({template: null, templateAbsUrl: null}); expect(() => loader.load(new ViewDefinition({template: null, templateAbsUrl: null})))
expect(() => loader.load(view))
.toThrowError('View should have either the templateUrl or template property set'); .toThrowError('View should have either the templateUrl or template property set');
}); });
it('should return a rejected Promise when XHR loading fails', it('should return a rejected Promise when XHR loading fails',
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.html', null); xhr.expect('http://ng.io/foo.html', null);
var view = new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'}); PromiseWrapper.then(
PromiseWrapper.then(loader.load(view), function(_) { throw 'Unexpected response'; }, loader.load(new ViewDefinition({templateAbsUrl: 'http://ng.io/foo.html'})),
function(_) { throw 'Unexpected response'; },
function(error) { function(error) {
expect(error.message) expect(error.message).toEqual('Failed to fetch url "http://ng.io/foo.html"');
.toEqual('Failed to fetch url "http://ng.io/foo.html"');
async.done(); async.done();
}); });
xhr.flush(); xhr.flush();
})); }));
it('should replace $baseUrl in attributes with the template base url', it('should replace $baseUrl in attributes with the template base url',
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/path/foo.html', '<img src="$baseUrl/logo.png">'); 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(new ViewDefinition({templateAbsUrl: 'http://ng.io/path/foo.html'}))
loader.load(view).then((el) => { .then((el) => {
expect(DOM.getInnerHTML(el)).toEqual('<img src="http://ng.io/path/logo.png">'); expect(el.template).toEqual('<img src="http://ng.io/path/logo.png">');
async.done(); async.done();
}); });
xhr.flush(); xhr.flush();
@ -132,24 +114,24 @@ export function main() {
describe('css', () => { describe('css', () => {
it('should load inline styles', inject([AsyncTestCompleter], (async) => { it('should load inline styles', inject([AsyncTestCompleter], (async) => {
var view = new ViewDefinition({template: 'html', styles: ['style 1', 'style 2']}); loader.load(new ViewDefinition({template: 'html', styles: ['style 1', 'style 2']}))
loader.load(view).then((el) => { .then((el) => {
expect(DOM.getInnerHTML(el)) expect(el.template).toEqual('html');
.toEqual('<style>style 1</style><style>style 2</style>html'); expect(el.styles).toEqual(['style 1', 'style 2']);
async.done(); async.done();
}); });
})); }));
it('should resolve urls in inline styles', inject([AsyncTestCompleter], (async) => { it('should resolve urls in inline styles', inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.html', 'html'); xhr.expect('http://ng.io/foo.html', 'html');
var view = new ViewDefinition({ loader.load(new ViewDefinition({
templateAbsUrl: 'http://ng.io/foo.html', templateAbsUrl: 'http://ng.io/foo.html',
styles: ['.foo { background-image: url("double.jpg"); }'] styles: ['.foo { background-image: url("double.jpg"); }']
}); }))
loader.load(view).then((el) => { .then((el) => {
expect(DOM.getInnerHTML(el)) expect(el.template).toEqual('html');
.toEqual( expect(el.styles)
"<style>.foo { background-image: url('http://ng.io/double.jpg'); }</style>html"); .toEqual([".foo { background-image: url('http://ng.io/double.jpg'); }"]);
async.done(); async.done();
}); });
xhr.flush(); xhr.flush();
@ -159,14 +141,14 @@ export function main() {
xhr.expect('http://ng.io/foo.html', 'xhr template'); xhr.expect('http://ng.io/foo.html', 'xhr template');
xhr.expect('http://ng.io/foo-1.css', '1'); xhr.expect('http://ng.io/foo-1.css', '1');
xhr.expect('http://ng.io/foo-2.css', '2'); xhr.expect('http://ng.io/foo-2.css', '2');
var view = new ViewDefinition({ loader.load(new ViewDefinition({
templateAbsUrl: 'http://ng.io/foo.html', templateAbsUrl: 'http://ng.io/foo.html',
styles: ['i1'], styles: ['i1'],
styleAbsUrls: ['http://ng.io/foo-1.css', 'http://ng.io/foo-2.css'] styleAbsUrls: ['http://ng.io/foo-1.css', 'http://ng.io/foo-2.css']
}); }))
loader.load(view).then((el) => { .then((el) => {
expect(DOM.getInnerHTML(el)) expect(el.template).toEqual('xhr template');
.toEqual('<style>i1</style><style>1</style><style>2</style>xhr template'); expect(el.styles).toEqual(['i1', '1', '2']);
async.done(); async.done();
}); });
xhr.flush(); xhr.flush();
@ -180,23 +162,25 @@ export function main() {
let styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver); let styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
let loader = new ViewLoader(xhr, styleInliner, styleUrlResolver); let loader = new ViewLoader(xhr, styleInliner, styleUrlResolver);
var view = new ViewDefinition( loader.load(
{templateAbsUrl: 'http://ng.io/foo.html', styles: ['@import "foo.css";']}); new ViewDefinition(
loader.load(view).then((el) => { {templateAbsUrl: 'http://ng.io/foo.html', styles: ['@import "foo.css";']}))
expect(DOM.getInnerHTML(el)).toEqual("<style>/* foo.css */\n</style><p>template</p>"); .then((el) => {
expect(el.template).toEqual("<p>template</p>");
expect(el.styles).toEqual(["/* foo.css */\n"]);
async.done(); async.done();
}); });
})); }));
it('should return a rejected Promise when XHR loading fails', it('should return a rejected Promise when XHR loading fails',
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
xhr.expect('http://ng.io/foo.css', null); xhr.expect('http://ng.io/foo.css', null);
var view = new ViewDefinition({template: '', styleAbsUrls: ['http://ng.io/foo.css']}); PromiseWrapper.then(
PromiseWrapper.then(loader.load(view), function(_) { throw 'Unexpected response'; }, loader.load(
new ViewDefinition({template: '', styleAbsUrls: ['http://ng.io/foo.css']})),
function(_) { throw 'Unexpected response'; },
function(error) { function(error) {
expect(error.message) expect(error.message).toEqual('Failed to fetch url "http://ng.io/foo.css"');
.toEqual('Failed to fetch url "http://ng.io/foo.css"');
async.done(); async.done();
}); });
xhr.flush(); 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 {ViewSplitter} from 'angular2/src/render/dom/compiler/view_splitter';
import {CompilePipeline} from 'angular2/src/render/dom/compiler/compile_pipeline'; 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 {DOM} from 'angular2/src/dom/dom_adapter';
import {Lexer, Parser} from 'angular2/src/change_detection/change_detection'; 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() { export function main() {
describe('ViewSplitter', () => { describe('ViewSplitter', () => {
function createViewDefinition(): ViewDefinition {
return new ViewDefinition({componentId: 'someComponent'});
}
function createPipeline() { function createPipeline() {
return new CompilePipeline([new ViewSplitter(new Parser(new Lexer()))]); return new CompilePipeline([new ViewSplitter(new Parser(new Lexer()))]);
} }
function proceess(el): CompileElement[] {
return createPipeline().processElements(el, ViewType.COMPONENT, createViewDefinition());
}
describe('<template> elements', () => { describe('<template> elements', () => {
it('should move the content into a new <template> element and mark that as viewRoot', () => { 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 rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement); var results = proceess(rootElement);
expect(stringifyElement(results[1].element)) expect(stringifyElement(results[1].element))
.toEqual('<template class="ng-binding" if="true"></template>'); .toEqual('<template class="ng-binding" if="true"></template>');
@ -39,32 +48,32 @@ export function main() {
it('should mark the new <template> element as viewRoot', () => { it('should mark the new <template> element as viewRoot', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>'); var rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement); var results = proceess(rootElement);
expect(results[2].isViewRoot).toBe(true); expect(results[2].isViewRoot).toBe(true);
}); });
it('should not wrap the root element', () => { it('should not wrap the root element', () => {
var rootElement = DOM.createTemplate(''); var rootElement = DOM.createTemplate('');
var results = createPipeline().process(rootElement); var results = proceess(rootElement);
expect(results.length).toBe(1); expect(results.length).toBe(1);
expect(stringifyElement(rootElement)).toEqual('<template></template>'); expect(stringifyElement(rootElement)).toEqual('<template></template>');
}); });
it('should copy over the elementDescription', () => { it('should copy over the elementDescription', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>'); 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); expect(results[2].elementDescription).toBe(results[1].elementDescription);
}); });
it('should clean out the inheritedElementBinder', () => { it('should clean out the inheritedElementBinder', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>'); var rootElement = DOM.createTemplate('<template if="true">a</template>');
var results = createPipeline().process(rootElement); var results = proceess(rootElement);
expect(results[2].inheritedElementBinder).toBe(null); expect(results[2].inheritedElementBinder).toBe(null);
}); });
it('should create a nestedProtoView', () => { it('should create a nestedProtoView', () => {
var rootElement = DOM.createTemplate('<template if="true">a</template>'); 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).not.toBe(null);
expect(results[2].inheritedProtoView) expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView); .toBe(results[1].inheritedElementBinder.nestedProtoView);
@ -80,7 +89,7 @@ export function main() {
it('should replace the element with an empty <template> element', () => { it('should replace the element with an empty <template> element', () => {
var rootElement = DOM.createTemplate('<span template=""></span>'); var rootElement = DOM.createTemplate('<span template=""></span>');
var originalChild = DOM.firstChild(DOM.content(rootElement)); var originalChild = DOM.firstChild(DOM.content(rootElement));
var results = createPipeline().process(rootElement); var results = proceess(rootElement);
expect(results[0].element).toBe(rootElement); expect(results[0].element).toBe(rootElement);
expect(stringifyElement(results[0].element)) expect(stringifyElement(results[0].element))
.toEqual('<template><template class="ng-binding"></template></template>'); .toEqual('<template><template class="ng-binding"></template></template>');
@ -92,7 +101,7 @@ export function main() {
it('should work with top-level template node', () => { it('should work with top-level template node', () => {
var rootElement = DOM.createTemplate('<div template>x</div>'); var rootElement = DOM.createTemplate('<div template>x</div>');
var originalChild = DOM.content(rootElement).childNodes[0]; 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].element).toBe(rootElement);
expect(results[0].isViewRoot).toBe(true); expect(results[0].isViewRoot).toBe(true);
@ -104,13 +113,13 @@ export function main() {
it('should mark the element as viewRoot', () => { it('should mark the element as viewRoot', () => {
var rootElement = DOM.createTemplate('<div template></div>'); var rootElement = DOM.createTemplate('<div template></div>');
var results = createPipeline().process(rootElement); var results = proceess(rootElement);
expect(results[2].isViewRoot).toBe(true); expect(results[2].isViewRoot).toBe(true);
}); });
it('should add property bindings from the template attribute', () => { it('should add property bindings from the template attribute', () => {
var rootElement = DOM.createTemplate('<div template="some-prop:expr"></div>'); 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) expect(results[1].inheritedElementBinder.propertyBindings.get('someProp').source)
.toEqual('expr'); .toEqual('expr');
expect(results[1].attrs().get('some-prop')).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', () => { it('should add variable mappings from the template attribute to the nestedProtoView', () => {
var rootElement = DOM.createTemplate('<div template="var var-name=mapName"></div>'); 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) expect(results[2].inheritedProtoView.variableBindings)
.toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'})); .toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
}); });
it('should add entries without value as attributes to the element', () => { it('should add entries without value as attributes to the element', () => {
var rootElement = DOM.createTemplate('<div template="varname"></div>'); 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].attrs().get('varname')).toEqual('');
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map()); expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
expect(results[1].inheritedElementBinder.variableBindings).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', () => { it('should iterate properly after a template dom modification', () => {
var rootElement = DOM.createTemplate('<div template></div><after></after>'); 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 // 1 root + 2 initial + 2 generated template elements
expect(results.length).toEqual(5); expect(results.length).toEqual(5);
}); });
it('should copy over the elementDescription', () => { it('should copy over the elementDescription', () => {
var rootElement = DOM.createTemplate('<span template=""></span>'); var rootElement = DOM.createTemplate('<span template=""></span>');
var results = createPipeline().process(rootElement); var results = proceess(rootElement);
expect(results[2].elementDescription).toBe(results[1].elementDescription); expect(results[2].elementDescription).toBe(results[1].elementDescription);
}); });
it('should clean out the inheritedElementBinder', () => { it('should clean out the inheritedElementBinder', () => {
var rootElement = DOM.createTemplate('<span template=""></span>'); var rootElement = DOM.createTemplate('<span template=""></span>');
var results = createPipeline().process(rootElement); var results = proceess(rootElement);
expect(results[2].inheritedElementBinder).toBe(null); expect(results[2].inheritedElementBinder).toBe(null);
}); });
it('should create a nestedProtoView', () => { it('should create a nestedProtoView', () => {
var rootElement = DOM.createTemplate('<span template=""></span>'); 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).not.toBe(null);
expect(results[2].inheritedProtoView) expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView); .toBe(results[1].inheritedElementBinder.nestedProtoView);
@ -167,7 +176,7 @@ export function main() {
it('should replace the element with an empty <template> element', () => { it('should replace the element with an empty <template> element', () => {
var rootElement = DOM.createTemplate('<span *ng-if></span>'); var rootElement = DOM.createTemplate('<span *ng-if></span>');
var originalChild = DOM.firstChild(DOM.content(rootElement)); var originalChild = DOM.firstChild(DOM.content(rootElement));
var results = createPipeline().process(rootElement); var results = proceess(rootElement);
expect(results[0].element).toBe(rootElement); expect(results[0].element).toBe(rootElement);
expect(stringifyElement(results[0].element)) expect(stringifyElement(results[0].element))
.toEqual('<template><template class="ng-binding" ng-if=""></template></template>'); .toEqual('<template><template class="ng-binding" ng-if=""></template></template>');
@ -178,14 +187,14 @@ export function main() {
it('should mark the element as viewRoot', () => { it('should mark the element as viewRoot', () => {
var rootElement = DOM.createTemplate('<div *foo="bar"></div>'); var rootElement = DOM.createTemplate('<div *foo="bar"></div>');
var results = createPipeline().process(rootElement); var results = proceess(rootElement);
expect(results[2].isViewRoot).toBe(true); expect(results[2].isViewRoot).toBe(true);
}); });
it('should work with top-level template node', () => { it('should work with top-level template node', () => {
var rootElement = DOM.createTemplate('<div *foo>x</div>'); var rootElement = DOM.createTemplate('<div *foo>x</div>');
var originalChild = DOM.content(rootElement).childNodes[0]; 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].element).toBe(rootElement);
expect(results[0].isViewRoot).toBe(true); expect(results[0].isViewRoot).toBe(true);
@ -197,7 +206,7 @@ export function main() {
it('should add property bindings from the template attribute', () => { it('should add property bindings from the template attribute', () => {
var rootElement = DOM.createTemplate('<div *prop="expr"></div>'); 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) expect(results[1].inheritedElementBinder.propertyBindings.get('prop').source)
.toEqual('expr'); .toEqual('expr');
expect(results[1].attrs().get('prop')).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', () => { it('should add variable mappings from the template attribute to the nestedProtoView', () => {
var rootElement = DOM.createTemplate('<div *foreach="var varName=mapName"></div>'); var rootElement = DOM.createTemplate('<div *foreach="var varName=mapName"></div>');
var results = createPipeline().process(rootElement); var results = proceess(rootElement);
expect(results[2].inheritedProtoView.variableBindings) expect(results[2].inheritedProtoView.variableBindings)
.toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'})); .toEqual(MapWrapper.createFromStringMap({'mapName': 'varName'}));
}); });
it('should add entries without value as attribute to the element', () => { it('should add entries without value as attribute to the element', () => {
var rootElement = DOM.createTemplate('<div *varname></div>'); 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].attrs().get('varname')).toEqual('');
expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map()); expect(results[1].inheritedElementBinder.propertyBindings).toEqual(new Map());
expect(results[1].inheritedElementBinder.variableBindings).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', () => { it('should iterate properly after a template dom modification', () => {
var rootElement = DOM.createTemplate('<div *foo></div><after></after>'); 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 // 1 root + 2 initial + 2 generated template elements
expect(results.length).toEqual(5); expect(results.length).toEqual(5);
}); });
it('should copy over the elementDescription', () => { it('should copy over the elementDescription', () => {
var rootElement = DOM.createTemplate('<span *foo></span>'); var rootElement = DOM.createTemplate('<span *foo></span>');
var results = createPipeline().process(rootElement); var results = proceess(rootElement);
expect(results[2].elementDescription).toBe(results[1].elementDescription); expect(results[2].elementDescription).toBe(results[1].elementDescription);
}); });
it('should clean out the inheritedElementBinder', () => { it('should clean out the inheritedElementBinder', () => {
var rootElement = DOM.createTemplate('<span *foo></span>'); var rootElement = DOM.createTemplate('<span *foo></span>');
var results = createPipeline().process(rootElement); var results = proceess(rootElement);
expect(results[2].inheritedElementBinder).toBe(null); expect(results[2].inheritedElementBinder).toBe(null);
}); });
it('should create a nestedProtoView', () => { it('should create a nestedProtoView', () => {
var rootElement = DOM.createTemplate('<span *foo></span>'); 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).not.toBe(null);
expect(results[2].inheritedProtoView) expect(results[2].inheritedProtoView)
.toBe(results[1].inheritedElementBinder.nestedProtoView); .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 {DomTestbed, TestRootView, elRef} from './dom_testbed';
import {ViewDefinition, DirectiveMetadata, RenderViewRef} from 'angular2/src/render/api'; import {
import {DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from 'angular2/src/render/dom/dom_renderer'; ViewDefinition,
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/render/render'; 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'; import {bind} from 'angular2/di';
export function main() { export function main() {
@ -271,16 +275,16 @@ export function main() {
if (DOM.supportsNativeShadowDOM()) { if (DOM.supportsNativeShadowDOM()) {
describe('native shadow dom support', () => { describe('native shadow dom support', () => {
beforeEachBindings( it('should put the template into a shadow root',
() => { return [bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy())]; });
it('should support shadow dom components',
inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => { inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compileAndMerge( tb.compileAndMerge(someComponent,
someComponent,
[ [
new ViewDefinition( new ViewDefinition({
{componentId: 'someComponent', template: 'hello', directives: []}) componentId: 'someComponent',
template: 'hello',
directives: [],
encapsulation: ViewEncapsulation.NATIVE
})
]) ])
.then((protoViewMergeMappings) => { .then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings); var rootView = tb.createView(protoViewMergeMappings);
@ -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 {PromiseWrapper, Promise} from 'angular2/src/facade/async';
import {DOM} from 'angular2/src/dom/dom_adapter'; 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 {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
import { import {
RenderViewWithFragments, 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 {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
import {ASTWithSource, AST} from 'angular2/src/change_detection/change_detection'; 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'; import {DOM} from 'angular2/src/dom/dom_adapter';
export function main() { export function main() {
@ -21,8 +21,10 @@ export function main() {
describe('ProtoViewBuilder', () => { describe('ProtoViewBuilder', () => {
var builder; var builder;
beforeEach( beforeEach(() => {
() => { builder = new ProtoViewBuilder(DOM.createTemplate(''), ViewType.EMBEDDED); }); builder =
new ProtoViewBuilder(DOM.createTemplate(''), ViewType.EMBEDDED, ViewEncapsulation.NONE);
});
if (!IS_DARTIUM) { if (!IS_DARTIUM) {
describe('verification of properties', () => { describe('verification of properties', () => {

View File

@ -21,15 +21,15 @@ import {DomTestbed} from '../dom_testbed';
import { import {
ViewDefinition, ViewDefinition,
DirectiveMetadata, DirectiveMetadata,
RenderProtoViewMergeMapping RenderProtoViewMergeMapping,
ViewEncapsulation,
ViewType
} from 'angular2/src/render/api'; } from 'angular2/src/render/api';
import {bind} from 'angular2/di';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {cloneAndQueryProtoView} from 'angular2/src/render/dom/util'; import {cloneAndQueryProtoView} from 'angular2/src/render/dom/util';
import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view'; import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view';
import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder';
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/render/render';
export function main() { export function main() {
describe('ProtoViewMerger integration test', () => { describe('ProtoViewMerger integration test', () => {
@ -232,29 +232,46 @@ export function main() {
}); });
describe('native shadow dom support', () => { 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', it('should keep the non projected light dom and wrap the component view into a shadow-root element',
runAndAssert('root', ['<a>b</a>', 'c'], [ runAndAssert('native-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>' '<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[], function runAndAssert(hostElementName: string, componentTemplates: string[],
expectedFragments: string[]) { expectedFragments: string[]) {
var rootComp = DirectiveMetadata.create( var useNativeEncapsulation = hostElementName.startsWith('native-');
{id: 'rootComp', type: DirectiveMetadata.COMPONENT_TYPE, selector: hostElementName}); var rootComp = rootDirective(hostElementName);
return inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => { return inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => {
tb.compileAndMerge(rootComp, componentTemplates.map(template => new ViewDefinition({ tb.compileAndMerge(rootComp, componentTemplates.map(template => componentView(
componentId: 'someComp', template, useNativeEncapsulation ?
template: template, ViewEncapsulation.NATIVE :
directives: [aComp, bComp, cComp] ViewEncapsulation.NONE)))
})))
.then((mergeMappings) => { .then((mergeMappings) => {
expect(stringify(mergeMappings)).toEqual(expectedFragments); expect(stringify(mergeMappings)).toEqual(expectedFragments);
async.done(); 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[] { function stringify(protoViewMergeMapping: RenderProtoViewMergeMapping): string[] {
var testView = cloneAndQueryProtoView( var testView = cloneAndQueryProtoView(
resolveInternalDomProtoView(protoViewMergeMapping.mergedProtoViewRef), false); 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 = []; binders = [];
} }
var rootEl = DOM.createTemplate('<div></div>'); 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: []}); } 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 {Component, Directive, View} from 'angular2/annotations';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {bind} from 'angular2/di'; import {bind} from 'angular2/di';
import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; import {DOCUMENT_TOKEN} from 'angular2/src/render/render';
import { import {
routerInjectables, 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 {Component, Directive, View} from 'angular2/src/core/annotations/decorators';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {bind} from 'angular2/di'; 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 {RouteConfig, Route, Redirect} from 'angular2/src/router/route_config_decorator';
import {PromiseWrapper} from 'angular2/src/facade/async'; import {PromiseWrapper} from 'angular2/src/facade/async';
import {BaseException} from 'angular2/src/facade/lang'; import {BaseException} from 'angular2/src/facade/lang';

View File

@ -104,12 +104,13 @@ export function main() {
var compiler: WorkerCompiler = createWorkerCompiler(workerSerializer, uiSerializer, tb); var compiler: WorkerCompiler = createWorkerCompiler(workerSerializer, uiSerializer, tb);
var dirMetadata = DirectiveMetadata.create( var dirMetadata = DirectiveMetadata.create(
{id: 'id', selector: 'CUSTOM', type: DirectiveMetadata.COMPONENT_TYPE}); {id: 'id', selector: 'custom', type: DirectiveMetadata.COMPONENT_TYPE});
compiler.compileHost(dirMetadata) compiler.compileHost(dirMetadata)
.then((protoView) => { .then((protoView) => {
expect(DOM.tagName(DOM.firstChild( expect(DOM.tagName(DOM.firstChild(DOM.content(
DOM.content(resolveWebWorkerRef(protoView.render).rootElement)))) resolveWebWorkerRef(protoView.render).rootElement)))
.toEqual('CUSTOM'); .toLowerCase())
.toEqual('custom');
expect(protoView).not.toBeNull(); expect(protoView).not.toBeNull();
async.done(); 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'; import {isPresent} from 'angular2/src/facade/lang';
@Component({selector: '[md-button]:not([href])'}) @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 { export class MdButton {
// TODO(jelbourn): Ink ripples. // TODO(jelbourn): Ink ripples.
} }
@ -15,7 +18,10 @@ export class MdButton {
host: {'(click)': 'onClick($event)', '[tabIndex]': 'tabIndex'}, host: {'(click)': 'onClick($event)', '[tabIndex]': 'tabIndex'},
lifecycle: [LifecycleEvent.onChange] 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 { export class MdAnchor {
tabIndex: number; 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 {isPresent} from 'angular2/src/facade/lang';
import {KEY_SPACE} from 'angular2_material/src/core/constants'; import {KEY_SPACE} from 'angular2_material/src/core/constants';
import {KeyboardEvent} from 'angular2/src/facade/browser'; import {KeyboardEvent} from 'angular2/src/facade/browser';
@ -17,7 +17,8 @@ import {NumberWrapper} from 'angular2/src/facade/lang';
}) })
@View({ @View({
templateUrl: 'package:angular2_material/src/components/checkbox/checkbox.html', templateUrl: 'package:angular2_material/src/components/checkbox/checkbox.html',
directives: [] directives: [],
encapsulation: ViewEncapsulation.NONE
}) })
export class MdCheckbox { export class MdCheckbox {
/** Whether this checkbox is checked. */ /** Whether this checkbox is checked. */

View File

@ -2,6 +2,7 @@ import {
Component, Component,
Directive, Directive,
View, View,
ViewEncapsulation,
Ancestor, Ancestor,
ElementRef, ElementRef,
DynamicComponentLoader, DynamicComponentLoader,
@ -211,7 +212,8 @@ export class MdDialogConfig {
}) })
@View({ @View({
templateUrl: 'package:angular2_material/src/components/dialog/dialog.html', templateUrl: 'package:angular2_material/src/components/dialog/dialog.html',
directives: [forwardRef(() => MdDialogContent)] directives: [forwardRef(() => MdDialogContent)],
encapsulation: ViewEncapsulation.NONE
}) })
class MdDialogContainer { class MdDialogContainer {
// Ref to the dialog content. Used by the DynamicComponentLoader to load the dialog content. // 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 {ListWrapper} from 'angular2/src/facade/collection';
import {StringWrapper, isPresent, isString, NumberWrapper} from 'angular2/src/facade/lang'; 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'], properties: ['cols', 'rowHeight', 'gutterSize'],
lifecycle: [LifecycleEvent.onAllChangesDone] 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 { export class MdGridList {
/** List of tiles that are being rendered. */ /** List of tiles that are being rendered. */
tiles: List<MdGridTile>; tiles: List<MdGridTile>;
@ -223,7 +226,10 @@ export class MdGridList {
}, },
lifecycle: [LifecycleEvent.onDestroy, LifecycleEvent.onChange] 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 { export class MdGridTile {
gridList: MdGridList; gridList: MdGridList;
_rowspan: number; _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'}) @Component({selector: 'md-progress-circular'})
@View({ @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 { export class MdProgressCircular {
constructor() {} 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 {isPresent, isBlank} from 'angular2/src/facade/lang';
import {Math} from 'angular2/src/facade/math'; import {Math} from 'angular2/src/facade/math';
@ -16,7 +16,8 @@ import {Math} from 'angular2/src/facade/math';
}) })
@View({ @View({
templateUrl: 'package:angular2_material/src/components/progress-linear/progress_linear.html', templateUrl: 'package:angular2_material/src/components/progress-linear/progress_linear.html',
directives: [] directives: [],
encapsulation: ViewEncapsulation.NONE
}) })
export class MdProgressLinear { export class MdProgressLinear {
/** Value for the primary bar. */ /** 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 {isPresent, StringWrapper, NumberWrapper} from 'angular2/src/facade/lang';
import {ObservableWrapper, EventEmitter} from 'angular2/src/facade/async'; import {ObservableWrapper, EventEmitter} from 'angular2/src/facade/async';
@ -36,7 +44,10 @@ var _uniqueIdCounter: number = 0;
'[attr.aria-activedescendant]': 'activedescendant' '[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 { export class MdRadioGroup {
/** The selected value for the radio group. The value comes from the options. */ /** The selected value for the radio group. The value comes from the options. */
value: any; value: any;
@ -192,7 +203,8 @@ export class MdRadioGroup {
}) })
@View({ @View({
templateUrl: 'package:angular2_material/src/components/radio/radio_button.html', templateUrl: 'package:angular2_material/src/components/radio/radio_button.html',
directives: [] directives: [],
encapsulation: ViewEncapsulation.NONE
}) })
export class MdRadioButton { export class MdRadioButton {
/** Whether this radio is checked. */ /** 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 {isPresent} from 'angular2/src/facade/lang';
import {KEY_SPACE} from 'angular2_material/src/core/constants'; import {KEY_SPACE} from 'angular2_material/src/core/constants';
import {KeyboardEvent} from 'angular2/src/facade/browser'; import {KeyboardEvent} from 'angular2/src/facade/browser';
@ -16,8 +16,11 @@ import {NumberWrapper} from 'angular2/src/facade/lang';
'[attr.role]': '"checkbox"' '[attr.role]': '"checkbox"'
} }
}) })
@View( @View({
{templateUrl: 'package:angular2_material/src/components/switcher/switch.html', directives: []}) templateUrl: 'package:angular2_material/src/components/switcher/switch.html',
directives: [],
encapsulation: ViewEncapsulation.NONE
})
export class MdSwitch { export class MdSwitch {
/** Whether this switch is checked. */ /** Whether this switch is checked. */
checked: boolean; checked: boolean;

View File

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

View File

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