feat(compiler, ShadowDom): adds TemplateLoader using XHR.
Also adds css shimming for emulated shadow dom and makes the shadowDom strategy global to the application.
This commit is contained in:

committed by
Rado Kirov

parent
fcbdf02767
commit
746f85a621
@ -1,8 +1,6 @@
|
||||
import {ABSTRACT, CONST, normalizeBlank} from 'angular2/src/facade/lang';
|
||||
import {List} from 'angular2/src/facade/collection';
|
||||
import {TemplateConfig} from './template_config';
|
||||
import {ShadowDomStrategy} from '../compiler/shadow_dom';
|
||||
|
||||
|
||||
@ABSTRACT()
|
||||
export class Directive {
|
||||
@ -40,7 +38,6 @@ export class Component extends Directive {
|
||||
lightDomServices:any; //List;
|
||||
shadowDomServices:any; //List;
|
||||
componentServices:any; //List;
|
||||
shadowDom:any; //ShadowDomStrategy;
|
||||
lifecycle:any; //List
|
||||
|
||||
@CONST()
|
||||
@ -52,7 +49,6 @@ export class Component extends Directive {
|
||||
shadowDomServices,
|
||||
componentServices,
|
||||
implementsTypes,
|
||||
shadowDom,
|
||||
lifecycle
|
||||
}:{
|
||||
selector:String,
|
||||
@ -62,7 +58,6 @@ export class Component extends Directive {
|
||||
shadowDomServices:List,
|
||||
componentServices:List,
|
||||
implementsTypes:List,
|
||||
shadowDom:ShadowDomStrategy,
|
||||
lifecycle:List
|
||||
}={})
|
||||
{
|
||||
@ -78,7 +73,6 @@ export class Component extends Directive {
|
||||
this.lightDomServices = lightDomServices;
|
||||
this.shadowDomServices = shadowDomServices;
|
||||
this.componentServices = componentServices;
|
||||
this.shadowDom = shadowDom;
|
||||
this.lifecycle = lifecycle;
|
||||
}
|
||||
}
|
||||
|
17
modules/angular2/src/core/application.js
vendored
17
modules/angular2/src/core/application.js
vendored
@ -12,6 +12,9 @@ import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
|
||||
import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle';
|
||||
import {ShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||
import {XHR} from 'angular2/src/core/compiler/xhr/xhr';
|
||||
import {XHRImpl} from 'angular2/src/core/compiler/xhr/xhr_impl';
|
||||
|
||||
var _rootInjector: Injector;
|
||||
|
||||
@ -24,7 +27,9 @@ var _rootBindings = [
|
||||
TemplateLoader,
|
||||
DirectiveMetadataReader,
|
||||
Parser,
|
||||
Lexer
|
||||
Lexer,
|
||||
bind(ShadowDomStrategy).toValue(new NativeShadowDomStrategy()),
|
||||
bind(XHR).toValue(new XHRImpl()),
|
||||
];
|
||||
|
||||
export var appViewToken = new OpaqueToken('AppView');
|
||||
@ -53,11 +58,12 @@ function _injectorBindings(appComponentType) {
|
||||
}, [appComponentAnnotatedTypeToken, appDocumentToken]),
|
||||
|
||||
bind(appViewToken).toAsyncFactory((changeDetection, compiler, injector, appElement,
|
||||
appComponentAnnotatedType) => {
|
||||
appComponentAnnotatedType, strategy) => {
|
||||
return compiler.compile(appComponentAnnotatedType.type, null).then(
|
||||
(protoView) => {
|
||||
var appProtoView = ProtoView.createRootProtoView(protoView,
|
||||
appElement, appComponentAnnotatedType, changeDetection.createProtoChangeDetector('root'));
|
||||
var appProtoView = ProtoView.createRootProtoView(protoView, appElement,
|
||||
appComponentAnnotatedType, changeDetection.createProtoChangeDetector('root'),
|
||||
strategy);
|
||||
// The light Dom of the app element is not considered part of
|
||||
// the angular application. Thus the context and lightDomInjector are
|
||||
// empty.
|
||||
@ -65,7 +71,8 @@ function _injectorBindings(appComponentType) {
|
||||
view.hydrate(injector, null, new Object());
|
||||
return view;
|
||||
});
|
||||
}, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken]),
|
||||
}, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken,
|
||||
ShadowDomStrategy]),
|
||||
|
||||
bind(appChangeDetectorToken).toFactory((rootView) => rootView.changeDetector,
|
||||
[appViewToken]),
|
||||
|
123
modules/angular2/src/core/compiler/compiler.js
vendored
123
modules/angular2/src/core/compiler/compiler.js
vendored
@ -1,6 +1,6 @@
|
||||
import {Type, FIELD, isBlank, isPresent, BaseException, stringify} from 'angular2/src/facade/lang';
|
||||
import {Type, isBlank, isPresent, BaseException, normalizeBlank, stringify} from 'angular2/src/facade/lang';
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM, Element} from 'angular2/src/facade/dom';
|
||||
|
||||
import {ChangeDetection, Parser} from 'angular2/change_detection';
|
||||
@ -14,6 +14,7 @@ import {TemplateLoader} from './template_loader';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {Component} from '../annotations/annotations';
|
||||
import {Content} from './shadow_dom_emulation/content_tag';
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
|
||||
/**
|
||||
* Cache that stores the ProtoView of the template of a component.
|
||||
@ -31,15 +32,11 @@ export class CompilerCache {
|
||||
|
||||
get(component:Type):ProtoView {
|
||||
var result = MapWrapper.get(this._cache, component);
|
||||
if (isBlank(result)) {
|
||||
// need to normalize undefined to null so that type checking passes :-(
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
return normalizeBlank(result);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._cache = MapWrapper.create();
|
||||
MapWrapper.clear(this._cache);
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,59 +50,107 @@ export class Compiler {
|
||||
_parser:Parser;
|
||||
_compilerCache:CompilerCache;
|
||||
_changeDetection:ChangeDetection;
|
||||
_templateLoader:TemplateLoader;
|
||||
_compiling:Map<Type, Promise>;
|
||||
_shadowDomStrategy: ShadowDomStrategy;
|
||||
_shadowDomDirectives: List<DirectiveMetadata>;
|
||||
|
||||
constructor(changeDetection:ChangeDetection, templateLoader:TemplateLoader, reader: DirectiveMetadataReader, parser:Parser, cache:CompilerCache) {
|
||||
constructor(changeDetection:ChangeDetection,
|
||||
templateLoader:TemplateLoader,
|
||||
reader: DirectiveMetadataReader,
|
||||
parser:Parser,
|
||||
cache:CompilerCache,
|
||||
shadowDomStrategy: ShadowDomStrategy) {
|
||||
this._changeDetection = changeDetection;
|
||||
this._reader = reader;
|
||||
this._parser = parser;
|
||||
this._compilerCache = cache;
|
||||
this._templateLoader = templateLoader;
|
||||
this._compiling = MapWrapper.create();
|
||||
this._shadowDomStrategy = shadowDomStrategy;
|
||||
this._shadowDomDirectives = [];
|
||||
var types = shadowDomStrategy.polyfillDirectives();
|
||||
for (var i = 0; i < types.length; i++) {
|
||||
ListWrapper.push(this._shadowDomDirectives, reader.read(types[i]));
|
||||
}
|
||||
}
|
||||
|
||||
createSteps(component:DirectiveMetadata):List<CompileStep> {
|
||||
var dirs = ListWrapper.map(component.componentDirectives, (d) => this._reader.read(d));
|
||||
return createDefaultSteps(this._changeDetection, this._parser, component, dirs);
|
||||
var directives = []
|
||||
var cmpDirectives = ListWrapper.map(component.componentDirectives, (d) => this._reader.read(d));
|
||||
directives = ListWrapper.concat(directives, cmpDirectives);
|
||||
directives = ListWrapper.concat(directives, this._shadowDomDirectives);
|
||||
return createDefaultSteps(this._changeDetection, this._parser, component, directives,
|
||||
this._shadowDomStrategy);
|
||||
}
|
||||
|
||||
compile(component:Type, templateRoot:Element = null):Promise<ProtoView> {
|
||||
var templateCache = null;
|
||||
// TODO load all components that have urls
|
||||
// transitively via the _templateLoader and store them in templateCache
|
||||
|
||||
return PromiseWrapper.resolve(this.compileAllLoaded(
|
||||
templateCache, this._reader.read(component), templateRoot)
|
||||
);
|
||||
return this._compile(this._reader.read(component), templateRoot);
|
||||
}
|
||||
|
||||
// public so that we can compile in sync in performance tests.
|
||||
compileAllLoaded(templateCache, component:DirectiveMetadata, templateRoot:Element = null):ProtoView {
|
||||
var rootProtoView = this._compilerCache.get(component.type);
|
||||
if (isPresent(rootProtoView)) {
|
||||
return rootProtoView;
|
||||
_compile(cmpMetadata: DirectiveMetadata, templateRoot:Element = null) {
|
||||
var pvCached = this._compilerCache.get(cmpMetadata.type);
|
||||
if (isPresent(pvCached)) {
|
||||
// The component has already been compiled into a ProtoView,
|
||||
// returns a resolved Promise.
|
||||
return PromiseWrapper.resolve(pvCached);
|
||||
}
|
||||
|
||||
if (isBlank(templateRoot)) {
|
||||
// TODO: read out the cache if templateRoot = null. Could contain:
|
||||
// - templateRoot string
|
||||
// - precompiled template
|
||||
// - ProtoView
|
||||
var annotation:any = component.annotation;
|
||||
templateRoot = DOM.createTemplate(annotation.template.inline);
|
||||
var pvPromise = MapWrapper.get(this._compiling, cmpMetadata.type);
|
||||
if (isPresent(pvPromise)) {
|
||||
// The component is already being compiled, attach to the existing Promise
|
||||
// instead of re-compiling the component.
|
||||
// It happens when a template references a component multiple times.
|
||||
return pvPromise;
|
||||
}
|
||||
|
||||
var pipeline = new CompilePipeline(this.createSteps(component));
|
||||
var compileElements = pipeline.process(templateRoot);
|
||||
rootProtoView = compileElements[0].inheritedProtoView;
|
||||
// Save the rootProtoView before we recurse so that we are able
|
||||
// to compile components that use themselves in their template.
|
||||
this._compilerCache.set(component.type, rootProtoView);
|
||||
var tplPromise = isBlank(templateRoot) ?
|
||||
this._templateLoader.load(cmpMetadata) :
|
||||
PromiseWrapper.resolve(templateRoot);
|
||||
|
||||
for (var i=0; i<compileElements.length; i++) {
|
||||
pvPromise = PromiseWrapper.then(tplPromise,
|
||||
(el) => this._compileTemplate(el, cmpMetadata),
|
||||
(_) => { throw new BaseException(`Failed to load the template for ${stringify(cmpMetadata.type)}`) }
|
||||
);
|
||||
|
||||
MapWrapper.set(this._compiling, cmpMetadata.type, pvPromise);
|
||||
|
||||
return pvPromise;
|
||||
}
|
||||
|
||||
_compileTemplate(template: Element, cmpMetadata): Promise<ProtoView> {
|
||||
this._shadowDomStrategy.processTemplate(template, cmpMetadata);
|
||||
var pipeline = new CompilePipeline(this.createSteps(cmpMetadata));
|
||||
var compileElements = pipeline.process(template);
|
||||
var protoView = compileElements[0].inheritedProtoView;
|
||||
|
||||
// Populate the cache before compiling the nested components,
|
||||
// so that components can reference themselves in their template.
|
||||
this._compilerCache.set(cmpMetadata.type, protoView);
|
||||
MapWrapper.delete(this._compiling, cmpMetadata.type);
|
||||
|
||||
// Compile all the components from the template
|
||||
var componentPromises = [];
|
||||
for (var i = 0; i < compileElements.length; i++) {
|
||||
var ce = compileElements[i];
|
||||
if (isPresent(ce.componentDirective)) {
|
||||
ce.inheritedElementBinder.nestedProtoView = this.compileAllLoaded(templateCache, ce.componentDirective, null);
|
||||
var componentPromise = this._compileNestedProtoView(ce);
|
||||
ListWrapper.push(componentPromises, componentPromise);
|
||||
}
|
||||
}
|
||||
|
||||
return rootProtoView;
|
||||
// The protoView is resolved after all the components in the template have been compiled.
|
||||
return PromiseWrapper.then(PromiseWrapper.all(componentPromises),
|
||||
(_) => protoView,
|
||||
(e) => { throw new BaseException(`${e} -> Failed to compile ${stringify(cmpMetadata.type)}`) }
|
||||
);
|
||||
}
|
||||
|
||||
_compileNestedProtoView(ce: CompileElement):Promise<ProtoView> {
|
||||
var pvPromise = this._compile(ce.componentDirective);
|
||||
pvPromise.then(function(protoView) {
|
||||
ce.inheritedElementBinder.nestedProtoView = protoView;
|
||||
});
|
||||
return pvPromise;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {Type} from 'angular2/src/facade/lang';
|
||||
import {Directive} from '../annotations/annotations'
|
||||
import {Directive} from 'angular2/src/core/annotations/annotations'
|
||||
import {List} from 'angular2/src/facade/collection'
|
||||
import {ShadowDomStrategy} from './shadow_dom';
|
||||
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||
|
||||
/**
|
||||
* Combination of a type with the Directive annotation
|
||||
@ -9,14 +9,13 @@ import {ShadowDomStrategy} from './shadow_dom';
|
||||
export class DirectiveMetadata {
|
||||
type:Type;
|
||||
annotation:Directive;
|
||||
shadowDomStrategy:ShadowDomStrategy;
|
||||
componentDirectives:List<Type>;
|
||||
|
||||
constructor(type:Type, annotation:Directive, shadowDomStrategy:ShadowDomStrategy,
|
||||
constructor(type:Type,
|
||||
annotation:Directive,
|
||||
componentDirectives:List<Type>) {
|
||||
this.annotation = annotation;
|
||||
this.type = type;
|
||||
this.shadowDomStrategy = shadowDomStrategy;
|
||||
this.componentDirectives = componentDirectives;
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {Directive, Component} from '../annotations/annotations';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
import {ShadowDom, ShadowDomStrategy, ShadowDomNative} from './shadow_dom';
|
||||
import {ShadowDom, ShadowDomStrategy, ShadowDomNative} from './shadow_dom_strategy';
|
||||
|
||||
export class DirectiveMetadataReader {
|
||||
read(type:Type):DirectiveMetadata {
|
||||
@ -13,36 +13,23 @@ export class DirectiveMetadataReader {
|
||||
var annotation = annotations[i];
|
||||
|
||||
if (annotation instanceof Component) {
|
||||
var shadowDomStrategy = this.parseShadowDomStrategy(annotation);
|
||||
return new DirectiveMetadata(
|
||||
type,
|
||||
annotation,
|
||||
shadowDomStrategy,
|
||||
this.componentDirectivesMetadata(annotation, shadowDomStrategy)
|
||||
this.componentDirectivesMetadata(annotation)
|
||||
);
|
||||
}
|
||||
|
||||
if (annotation instanceof Directive) {
|
||||
return new DirectiveMetadata(type, annotation, null, null);
|
||||
return new DirectiveMetadata(type, annotation, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new BaseException(`No Directive annotation found on ${stringify(type)}`);
|
||||
}
|
||||
|
||||
parseShadowDomStrategy(annotation:Component):ShadowDomStrategy{
|
||||
return isPresent(annotation.shadowDom) ? annotation.shadowDom : ShadowDomNative;
|
||||
}
|
||||
|
||||
componentDirectivesMetadata(annotation:Component, shadowDomStrategy:ShadowDomStrategy):List<Type> {
|
||||
var polyDirs = shadowDomStrategy.polyfillDirectives();
|
||||
componentDirectivesMetadata(annotation:Component):List<Type> {
|
||||
var template = annotation.template;
|
||||
var templateDirs = isPresent(template) && isPresent(template.directives) ? template.directives : [];
|
||||
|
||||
var res = [];
|
||||
res = ListWrapper.concat(res, templateDirs)
|
||||
res = ListWrapper.concat(res, polyDirs)
|
||||
|
||||
return res;
|
||||
return isPresent(template) && isPresent(template.directives) ? template.directives : [];
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import {ProtoViewBuilder} from './proto_view_builder';
|
||||
import {ProtoElementInjectorBuilder} from './proto_element_injector_builder';
|
||||
import {ElementBinderBuilder} from './element_binder_builder';
|
||||
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
||||
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||
import {stringify} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
@ -21,7 +22,8 @@ export function createDefaultSteps(
|
||||
changeDetection:ChangeDetection,
|
||||
parser:Parser,
|
||||
compiledComponent: DirectiveMetadata,
|
||||
directives: List<DirectiveMetadata>) {
|
||||
directives: List<DirectiveMetadata>,
|
||||
shadowDomStrategy: ShadowDomStrategy) {
|
||||
|
||||
var compilationUnit = stringify(compiledComponent.type);
|
||||
|
||||
@ -31,7 +33,7 @@ export function createDefaultSteps(
|
||||
new DirectiveParser(directives),
|
||||
new TextInterpolationParser(parser, compilationUnit),
|
||||
new ElementBindingMarker(),
|
||||
new ProtoViewBuilder(changeDetection),
|
||||
new ProtoViewBuilder(changeDetection, shadowDomStrategy),
|
||||
new ProtoElementInjectorBuilder(),
|
||||
new ElementBinderBuilder()
|
||||
];
|
||||
|
@ -10,6 +10,7 @@ import {Component} from '../../annotations/annotations';
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
import {ShadowDomStrategy} from '../shadow_dom_strategy';
|
||||
|
||||
/**
|
||||
* Parses the directives on a single element. Assumes ViewSplitter has already created
|
||||
|
@ -7,6 +7,7 @@ import {ChangeDetection} from 'angular2/change_detection';
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
import {ShadowDomStrategy} from '../shadow_dom_strategy';
|
||||
|
||||
/**
|
||||
* Creates ProtoViews and forwards variable bindings from parent to children.
|
||||
@ -22,7 +23,9 @@ import {CompileControl} from './compile_control';
|
||||
*/
|
||||
export class ProtoViewBuilder extends CompileStep {
|
||||
changeDetection:ChangeDetection;
|
||||
constructor(changeDetection:ChangeDetection) {
|
||||
_shadowDomStrategy:ShadowDomStrategy;
|
||||
constructor(changeDetection:ChangeDetection, shadowDomStrategy:ShadowDomStrategy) {
|
||||
this._shadowDomStrategy = shadowDomStrategy;
|
||||
this.changeDetection = changeDetection;
|
||||
}
|
||||
|
||||
@ -30,7 +33,8 @@ export class ProtoViewBuilder extends CompileStep {
|
||||
var inheritedProtoView = null;
|
||||
if (current.isViewRoot) {
|
||||
var protoChangeDetector = this.changeDetection.createProtoChangeDetector('dummy');
|
||||
inheritedProtoView = new ProtoView(current.element, protoChangeDetector);
|
||||
inheritedProtoView = new ProtoView(current.element, protoChangeDetector,
|
||||
this._shadowDomStrategy);
|
||||
if (isPresent(parent)) {
|
||||
if (isPresent(parent.inheritedElementBinder.nestedProtoView)) {
|
||||
throw new BaseException('Only one nested view per element is allowed');
|
||||
|
@ -1,9 +0,0 @@
|
||||
library angular.core.compiler.shadow_dom;
|
||||
|
||||
//TODO: merge this file with shadow_dom.es6 when the traspiler support creating const globals
|
||||
|
||||
import './shadow_dom_strategy.dart';
|
||||
export './shadow_dom_strategy.dart';
|
||||
|
||||
const ShadowDomEmulated = const EmulatedShadowDomStrategy();
|
||||
const ShadowDomNative = const NativeShadowDomStrategy();
|
@ -1,5 +0,0 @@
|
||||
import {EmulatedShadowDomStrategy, NativeShadowDomStrategy} from './shadow_dom_strategy';
|
||||
export * from './shadow_dom_strategy';
|
||||
|
||||
export var ShadowDomEmulated = new EmulatedShadowDomStrategy();
|
||||
export var ShadowDomNative = new NativeShadowDomStrategy();
|
431
modules/angular2/src/core/compiler/shadow_dom_emulation/shim_css.js
vendored
Normal file
431
modules/angular2/src/core/compiler/shadow_dom_emulation/shim_css.js
vendored
Normal file
@ -0,0 +1,431 @@
|
||||
import {StringWrapper, RegExpWrapper, isPresent, BaseException, int} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
export function shimCssText(css: string, tag: string) {
|
||||
return new CssShim(tag).shimCssText(css);
|
||||
}
|
||||
|
||||
var _HOST_RE = RegExpWrapper.create(':host', 'i');
|
||||
var _HOST_TOKEN = '-host-element';
|
||||
var _HOST_TOKEN_RE = RegExpWrapper.create('-host-element');
|
||||
var _PAREN_SUFFIX = ')(?:\\((' +
|
||||
'(?:\\([^)(]*\\)|[^)(]*)+?' +
|
||||
')\\))?([^,{]*)';
|
||||
var _COLON_HOST_RE = RegExpWrapper.create(`(${_HOST_TOKEN}${_PAREN_SUFFIX}`, 'im');
|
||||
|
||||
var _POLYFILL_NON_STRICT = 'polyfill-non-strict';
|
||||
var _POLYFILL_UNSCOPED_NEXT_SELECTOR = 'polyfill-unscoped-next-selector';
|
||||
var _POLYFILL_NEXT_SELECTOR = 'polyfill-next-selector';
|
||||
var _CONTENT_RE = RegExpWrapper.create('[^}]*content:[\\s]*[\'"](.*?)[\'"][;\\s]*[^}]*}', 'im');
|
||||
var _COMBINATORS = [
|
||||
RegExpWrapper.create('/shadow/', 'i'),
|
||||
RegExpWrapper.create('/shadow-deep/', 'i'),
|
||||
RegExpWrapper.create('::shadow', 'i'),
|
||||
RegExpWrapper.create('/deep/', 'i'),
|
||||
];
|
||||
var _COLON_SELECTORS = RegExpWrapper.create('(' + _HOST_TOKEN + ')(\\(.*\\))?(.*)', 'i');
|
||||
var _SELECTOR_SPLITS = [' ', '>', '+', '~'];
|
||||
var _SIMPLE_SELECTORS = RegExpWrapper.create('([^:]*)(:*)(.*)', 'i');
|
||||
var _IS_SELECTORS = RegExpWrapper.create('\\[is=[\'"]([^\\]]*)[\'"]\\]', 'i');
|
||||
|
||||
var _$EOF = 0;
|
||||
var _$LBRACE = 123;
|
||||
var _$RBRACE = 125;
|
||||
var _$TAB = 9;
|
||||
var _$SPACE = 32;
|
||||
var _$NBSP = 160;
|
||||
|
||||
export class CssShim {
|
||||
_tag: string;
|
||||
_attr: string;
|
||||
|
||||
constructor(tag: string) {
|
||||
this._tag = tag;
|
||||
this._attr = `[${tag}]`;
|
||||
}
|
||||
|
||||
shimCssText(css: string): string {
|
||||
var preprocessed = this.convertColonHost(css);
|
||||
var rules = this.cssToRules(preprocessed);
|
||||
return this.scopeRules(rules);
|
||||
}
|
||||
|
||||
convertColonHost(css: string):string {
|
||||
css = StringWrapper.replaceAll(css, _HOST_RE, _HOST_TOKEN);
|
||||
|
||||
var partReplacer = function(host, part, suffix) {
|
||||
part = StringWrapper.replaceAll(part, _HOST_TOKEN_RE, '');
|
||||
return `${host}${part}${suffix}`;
|
||||
}
|
||||
|
||||
return StringWrapper.replaceAllMapped(css, _COLON_HOST_RE, function(m) {
|
||||
var base = _HOST_TOKEN;
|
||||
var inParens = m[2];
|
||||
var rest = m[3];
|
||||
|
||||
if (isPresent(inParens)) {
|
||||
var srcParts = inParens.split(',');
|
||||
var dstParts = [];
|
||||
|
||||
for (var i = 0; i < srcParts.length; i++) {
|
||||
var part = srcParts[i].trim();
|
||||
if (part.length > 0) {
|
||||
ListWrapper.push(dstParts, partReplacer(base, part, rest));
|
||||
}
|
||||
}
|
||||
|
||||
return ListWrapper.join(dstParts, ',');
|
||||
} else {
|
||||
return `${base}${rest}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cssToRules(css: string): List<_Rule> {
|
||||
return new _Parser(css).parse();
|
||||
}
|
||||
|
||||
scopeRules(rules: List<_Rule>): string {
|
||||
var scopedRules = [];
|
||||
var prevRule = null;
|
||||
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
var rule = rules[i];
|
||||
if (isPresent(prevRule) &&
|
||||
prevRule.selectorText == _POLYFILL_NON_STRICT) {
|
||||
ListWrapper.push(scopedRules, this.scopeNonStrictMode(rule));
|
||||
|
||||
} else if (isPresent(prevRule) &&
|
||||
prevRule.selectorText == _POLYFILL_UNSCOPED_NEXT_SELECTOR) {
|
||||
var content = this.extractContent(prevRule);
|
||||
var r = new _Rule(content, rule.body, null);
|
||||
ListWrapper.push(scopedRules, this.ruleToString(r));
|
||||
|
||||
} else if (isPresent(prevRule) &&
|
||||
prevRule.selectorText == _POLYFILL_NEXT_SELECTOR) {
|
||||
|
||||
var content = this.extractContent(prevRule);
|
||||
var r = new _Rule(content, rule.body, null);
|
||||
ListWrapper.push(scopedRules, this.scopeStrictMode(r))
|
||||
|
||||
} else if (rule.selectorText != _POLYFILL_NON_STRICT &&
|
||||
rule.selectorText != _POLYFILL_UNSCOPED_NEXT_SELECTOR &&
|
||||
rule.selectorText != _POLYFILL_NEXT_SELECTOR) {
|
||||
ListWrapper.push(scopedRules, this.scopeStrictMode(rule));
|
||||
}
|
||||
prevRule = rule;
|
||||
}
|
||||
|
||||
return ListWrapper.join(scopedRules, '\n');
|
||||
}
|
||||
|
||||
extractContent(rule: _Rule): string {
|
||||
var match = RegExpWrapper.firstMatch(_CONTENT_RE, rule.body);
|
||||
return isPresent(match) ? match[1] : '';
|
||||
}
|
||||
|
||||
ruleToString(rule: _Rule): string {
|
||||
return `${rule.selectorText} ${rule.body}`;
|
||||
}
|
||||
|
||||
scopeStrictMode(rule: _Rule): string {
|
||||
if (rule.hasNestedRules()) {
|
||||
var selector = rule.selectorText;
|
||||
var rules = this.scopeRules(rule.rules);
|
||||
return `${selector} {\n${rules}\n}`;
|
||||
}
|
||||
|
||||
var scopedSelector = this.scopeSelector(rule.selectorText, true);
|
||||
var scopedBody = rule.body;
|
||||
return `${scopedSelector} ${scopedBody}`;
|
||||
}
|
||||
|
||||
scopeNonStrictMode(rule: _Rule): string {
|
||||
var scopedSelector = this.scopeSelector(rule.selectorText, false);
|
||||
var scopedBody = rule.body;
|
||||
return `${scopedSelector} ${scopedBody}`;
|
||||
}
|
||||
|
||||
scopeSelector(selector: string, strict: boolean) {
|
||||
var parts = this.replaceCombinators(selector).split(',');
|
||||
var scopedParts = [];
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var part = parts[i];
|
||||
var sel = this.scopeSimpleSelector(part.trim(), strict);
|
||||
ListWrapper.push(scopedParts, sel)
|
||||
}
|
||||
return ListWrapper.join(scopedParts, ', ');
|
||||
}
|
||||
|
||||
replaceCombinators(selector: string): string {
|
||||
for (var i = 0; i < _COMBINATORS.length; i++) {
|
||||
var combinator = _COMBINATORS[i];
|
||||
selector = StringWrapper.replaceAll(selector, combinator, '');
|
||||
}
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
scopeSimpleSelector(selector: string, strict: boolean) {
|
||||
if (StringWrapper.contains(selector, _HOST_TOKEN)) {
|
||||
return this.replaceColonSelectors(selector);
|
||||
} else if (strict) {
|
||||
return this.insertTagToEverySelectorPart(selector);
|
||||
} else {
|
||||
return `${this._tag} ${selector}`;
|
||||
}
|
||||
}
|
||||
|
||||
replaceColonSelectors(css: string): string {
|
||||
return StringWrapper.replaceAllMapped(css, _COLON_SELECTORS, (m) => {
|
||||
var selectorInParens;
|
||||
if (isPresent(m[2])) {
|
||||
var len = selectorInParens.length;
|
||||
selectorInParens = StringWrapper.substring(selectorInParens, 1, len - 1);
|
||||
} else {
|
||||
selectorInParens = '';
|
||||
}
|
||||
var rest = m[3];
|
||||
return `${this._tag}${selectorInParens}${rest}`;
|
||||
});
|
||||
}
|
||||
|
||||
insertTagToEverySelectorPart(selector: string): string {
|
||||
selector = this.handleIsSelector(selector);
|
||||
|
||||
for (var i = 0; i < _SELECTOR_SPLITS.length; i++) {
|
||||
var split = _SELECTOR_SPLITS[i];
|
||||
var parts = selector.split(split);
|
||||
for (var j = 0; j < parts.length; j++) {
|
||||
parts[j] = this.insertAttrSuffixIntoSelectorPart(parts[j].trim());
|
||||
}
|
||||
selector = parts.join(split);
|
||||
}
|
||||
return selector;
|
||||
}
|
||||
|
||||
insertAttrSuffixIntoSelectorPart(p: string): string {
|
||||
var shouldInsert = p.length > 0 &&
|
||||
!ListWrapper.contains(_SELECTOR_SPLITS, p) &&
|
||||
!StringWrapper.contains(p, this._attr);
|
||||
return shouldInsert ? this.insertAttr(p) : p;
|
||||
}
|
||||
|
||||
insertAttr(selector: string): string {
|
||||
return StringWrapper.replaceAllMapped(selector, _SIMPLE_SELECTORS, (m) => {
|
||||
var basePart = m[1];
|
||||
var colonPart = m[2];
|
||||
var rest = m[3];
|
||||
return (m[0].length > 0) ? `${basePart}${this._attr}${colonPart}${rest}` : '';
|
||||
});
|
||||
}
|
||||
|
||||
handleIsSelector(selector: string) {
|
||||
return StringWrapper.replaceAllMapped(selector, _IS_SELECTORS, function(m) {
|
||||
return m[1];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _Token {
|
||||
string: string;
|
||||
type: string;
|
||||
|
||||
constructor(string: string, type: string) {
|
||||
this.string = string;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
var _EOF_TOKEN = new _Token(null, null);
|
||||
|
||||
class _Lexer {
|
||||
peek: int;
|
||||
index: int;
|
||||
input: string;
|
||||
length: int;
|
||||
|
||||
constructor(input: string) {
|
||||
this.input = input;
|
||||
this.length = input.length;
|
||||
this.index = -1;
|
||||
this.advance();
|
||||
}
|
||||
|
||||
parse(): List<_Token> {
|
||||
var tokens = [];
|
||||
var token = this.scanToken();
|
||||
while (token !== _EOF_TOKEN) {
|
||||
ListWrapper.push(tokens, token);
|
||||
token = this.scanToken();
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
scanToken(): _Token {
|
||||
this.skipWhitespace();
|
||||
if (this.peek === _$EOF) return _EOF_TOKEN;
|
||||
if (this.isBodyEnd(this.peek)) {
|
||||
this.advance();
|
||||
return new _Token('}', 'rparen');
|
||||
}
|
||||
if (this.isMedia(this.peek)) return this.scanMedia();
|
||||
if (this.isSelector(this.peek)) return this.scanSelector();
|
||||
if (this.isBodyStart(this.peek)) return this.scanBody();
|
||||
|
||||
return _EOF_TOKEN;
|
||||
}
|
||||
|
||||
isSelector(v: int): boolean {
|
||||
return !this.isBodyStart(v) && v !== _$EOF;
|
||||
}
|
||||
|
||||
isBodyStart(v: int): boolean {
|
||||
return v === _$LBRACE;
|
||||
}
|
||||
|
||||
isBodyEnd(v: int): boolean {
|
||||
return v === _$RBRACE;
|
||||
}
|
||||
|
||||
isMedia(v: int): boolean {
|
||||
return v === 64; // @ -> 64
|
||||
}
|
||||
|
||||
isWhitespace(v: int): boolean {
|
||||
return (v >= _$TAB && v <= _$SPACE) || (v == _$NBSP)
|
||||
}
|
||||
|
||||
skipWhitespace() {
|
||||
while (this.isWhitespace(this.peek)) {
|
||||
if (++this.index >= this.length) {
|
||||
this.peek = _$EOF;
|
||||
return;
|
||||
} else {
|
||||
this.peek = StringWrapper.charCodeAt(this.input, this.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scanSelector(): _Token {
|
||||
var start = this.index;
|
||||
this.advance();
|
||||
while (this.isSelector(this.peek)) {
|
||||
this.advance();
|
||||
}
|
||||
var selector = StringWrapper.substring(this.input, start, this.index);
|
||||
return new _Token(selector.trim(), 'selector');
|
||||
}
|
||||
|
||||
scanBody(): _Token {
|
||||
var start = this.index;
|
||||
this.advance();
|
||||
while (!this.isBodyEnd(this.peek)) {
|
||||
this.advance();
|
||||
}
|
||||
this.advance();
|
||||
var body = StringWrapper.substring(this.input, start, this.index);
|
||||
return new _Token(body, 'body');
|
||||
}
|
||||
|
||||
scanMedia(): _Token {
|
||||
var start = this.index;
|
||||
this.advance();
|
||||
while (!this.isBodyStart(this.peek)) {
|
||||
this.advance();
|
||||
}
|
||||
var media = StringWrapper.substring(this.input, start, this.index);
|
||||
this.advance(); // skip "{"
|
||||
return new _Token(media, 'media');
|
||||
}
|
||||
|
||||
advance() {
|
||||
this.index++;
|
||||
if (this.index >= this.length) {
|
||||
this.peek = _$EOF;
|
||||
} else {
|
||||
this.peek = StringWrapper.charCodeAt(this.input, this.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Parser {
|
||||
tokens: List<_Token>;
|
||||
currentIndex: int;
|
||||
|
||||
constructor(input: string) {
|
||||
this.tokens = new _Lexer(input).parse();
|
||||
this.currentIndex = -1;
|
||||
}
|
||||
|
||||
parse(): List<_Rule> {
|
||||
var rules = [];
|
||||
var rule;
|
||||
while (isPresent(rule = this.parseRule())) {
|
||||
ListWrapper.push(rules, rule);
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
parseRule(): _Rule {
|
||||
try {
|
||||
if (this.getNext().type === 'media') {
|
||||
return this.parseMedia();
|
||||
} else {
|
||||
return this.parseCssRule();
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
parseMedia(): _Rule {
|
||||
this.advance('media');
|
||||
var media = this.getCurrent().string;
|
||||
var rules = [];
|
||||
while (this.getNext().type !== 'rparen') {
|
||||
ListWrapper.push(rules, this.parseCssRule());
|
||||
}
|
||||
this.advance('rparen');
|
||||
return new _Rule(media.trim(), null, rules);
|
||||
}
|
||||
|
||||
parseCssRule() {
|
||||
this.advance('selector');
|
||||
var selector = this.getCurrent().string;
|
||||
this.advance('body');
|
||||
var body = this.getCurrent().string;
|
||||
return new _Rule(selector, body, null);
|
||||
}
|
||||
|
||||
advance(expected: string) {
|
||||
this.currentIndex++;
|
||||
if (this.getCurrent().type !== expected) {
|
||||
throw new BaseException(`Unexpected token "${this.getCurrent().type}". Expected "${expected}"`);
|
||||
}
|
||||
}
|
||||
|
||||
getNext(): _Token {
|
||||
return this.tokens[this.currentIndex + 1];
|
||||
}
|
||||
|
||||
getCurrent(): _Token {
|
||||
return this.tokens[this.currentIndex];
|
||||
}
|
||||
}
|
||||
|
||||
export class _Rule {
|
||||
selectorText: string;
|
||||
body: string;
|
||||
rules: List<_Rule>;
|
||||
|
||||
constructor(selectorText: string, body: string, rules: List<_Rule>) {
|
||||
this.selectorText = selectorText;
|
||||
this.body = body;
|
||||
this.rules = rules;
|
||||
}
|
||||
|
||||
hasNestedRules() {
|
||||
return isPresent(this.rules);
|
||||
}
|
||||
}
|
@ -1,19 +1,29 @@
|
||||
import {CONST} from 'angular2/src/facade/lang';
|
||||
import {DOM, Element} from 'angular2/src/facade/dom';
|
||||
import {List} from 'angular2/src/facade/collection';
|
||||
import {Type, isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
import {DOM, Element, StyleElement} from 'angular2/src/facade/dom';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {View} from './view';
|
||||
import {Content} from './shadow_dom_emulation/content_tag';
|
||||
import {LightDom} from './shadow_dom_emulation/light_dom';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {shimCssText} from './shadow_dom_emulation/shim_css';
|
||||
|
||||
export class ShadowDomStrategy {
|
||||
@CONST() constructor() {}
|
||||
attachTemplate(el:Element, view:View){}
|
||||
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){}
|
||||
polyfillDirectives():List<Type>{ return null; };
|
||||
polyfillDirectives():List<Type>{ return null; }
|
||||
processTemplate(template: Element, cmpMetadata: DirectiveMetadata) { return null; }
|
||||
}
|
||||
|
||||
export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
||||
@CONST() constructor() {}
|
||||
_styleHost: Element;
|
||||
|
||||
constructor(styleHost: Element = null) {
|
||||
if (isBlank(styleHost)) {
|
||||
styleHost = DOM.defaultDoc().head;
|
||||
}
|
||||
this._styleHost = styleHost;
|
||||
}
|
||||
|
||||
attachTemplate(el:Element, view:View){
|
||||
DOM.clearNodes(el);
|
||||
moveViewNodesIntoParent(el, view);
|
||||
@ -26,10 +36,26 @@ export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
||||
polyfillDirectives():List<Type> {
|
||||
return [Content];
|
||||
}
|
||||
|
||||
processTemplate(template: Element, cmpMetadata: DirectiveMetadata) {
|
||||
var templateRoot = DOM.templateAwareRoot(template);
|
||||
var attrName = cmpMetadata.annotation.selector;
|
||||
|
||||
// Shim CSS for emulated shadow DOM and attach the styles do the document head
|
||||
var styles = _detachStyles(templateRoot);
|
||||
for (var i = 0; i < styles.length; i++) {
|
||||
var style = styles[i];
|
||||
var processedCss = shimCssText(DOM.getText(style), attrName);
|
||||
DOM.setText(style, processedCss);
|
||||
}
|
||||
_attachStyles(this._styleHost, styles);
|
||||
|
||||
// Update the DOM to trigger the CSS
|
||||
_addAttributeToChildren(templateRoot, attrName);
|
||||
}
|
||||
}
|
||||
|
||||
export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
||||
@CONST() constructor() {}
|
||||
attachTemplate(el:Element, view:View){
|
||||
moveViewNodesIntoParent(el.createShadowRoot(), view);
|
||||
}
|
||||
@ -41,10 +67,49 @@ export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
||||
polyfillDirectives():List<Type> {
|
||||
return [];
|
||||
}
|
||||
|
||||
processTemplate(template: Element, cmpMetadata: DirectiveMetadata) {
|
||||
return template;
|
||||
}
|
||||
}
|
||||
|
||||
function moveViewNodesIntoParent(parent, view) {
|
||||
for (var i = 0; i < view.nodes.length; ++i) {
|
||||
DOM.appendChild(parent, view.nodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(vicb): union types: el is an Element or a Document Fragment
|
||||
function _detachStyles(el): List<StyleElement> {
|
||||
var nodeList = DOM.querySelectorAll(el, 'style');
|
||||
var styles = [];
|
||||
for (var i = 0; i < nodeList.length; i++) {
|
||||
var style = DOM.remove(nodeList[i]);
|
||||
ListWrapper.push(styles, style);
|
||||
}
|
||||
return styles;
|
||||
}
|
||||
|
||||
// Move the styles as the first children of the template
|
||||
function _attachStyles(el: Element, styles: List<StyleElement>) {
|
||||
var firstChild = DOM.firstChild(el);
|
||||
for (var i = styles.length - 1; i >= 0; i--) {
|
||||
var style = styles[i];
|
||||
if (isPresent(firstChild)) {
|
||||
DOM.insertBefore(firstChild, style);
|
||||
} else {
|
||||
DOM.appendChild(el, style);
|
||||
}
|
||||
firstChild = style;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(vicb): union types: el is an Element or a Document Fragment
|
||||
function _addAttributeToChildren(el, attrName:string) {
|
||||
// TODO(vicb): currently the code crashes when the attrName is not an el selector
|
||||
var children = DOM.querySelectorAll(el, "*");
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
var child = children[i];
|
||||
DOM.setAttribute(child, attrName, '');
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,51 @@
|
||||
import {Promise} from 'angular2/src/facade/async';
|
||||
//import {Document} from 'angular2/src/facade/dom';
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {isBlank, isPresent, BaseException, stringify} from 'angular2/src/facade/lang';
|
||||
import {TemplateElement, DOM} from 'angular2/src/facade/dom';
|
||||
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {TemplateConfig} from 'angular2/src/core/annotations/template_config';
|
||||
import {Component} from 'angular2/src/core/annotations/annotations';
|
||||
|
||||
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
||||
|
||||
import {XHR} from './xhr/xhr';
|
||||
|
||||
/**
|
||||
* Strategy to load component templates.
|
||||
*/
|
||||
export class TemplateLoader {
|
||||
load(url:string):Promise<Document> {
|
||||
return null;
|
||||
_xhr: XHR;
|
||||
_cache;
|
||||
|
||||
constructor(xhr: XHR) {
|
||||
this._xhr = xhr;
|
||||
this._cache = StringMapWrapper.create();
|
||||
}
|
||||
}
|
||||
|
||||
load(cmpMetadata: DirectiveMetadata):Promise<Element> {
|
||||
var annotation:Component = cmpMetadata.annotation;
|
||||
var tplConfig:TemplateConfig = annotation.template;
|
||||
|
||||
if (isPresent(tplConfig.inline)) {
|
||||
var template = DOM.createTemplate(tplConfig.inline);
|
||||
return PromiseWrapper.resolve(template);
|
||||
}
|
||||
|
||||
if (isPresent(tplConfig.url)) {
|
||||
var url = tplConfig.url;
|
||||
var promise = StringMapWrapper.get(this._cache, url);
|
||||
|
||||
if (isBlank(promise)) {
|
||||
promise = this._xhr.get(url).then(function (html) {
|
||||
var template = DOM.createTemplate(html);
|
||||
return template;
|
||||
});
|
||||
StringMapWrapper.set(this._cache, url, promise);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
throw new BaseException(`No template configured for component ${stringify(cmpMetadata.type)}`);
|
||||
}
|
||||
}
|
||||
|
19
modules/angular2/src/core/compiler/view.js
vendored
19
modules/angular2/src/core/compiler/view.js
vendored
@ -15,6 +15,7 @@ import {ViewPort} from './viewport';
|
||||
import {OnChange} from './interfaces';
|
||||
import {Content} from './shadow_dom_emulation/content_tag';
|
||||
import {LightDom, DestinationLightDom} from './shadow_dom_emulation/light_dom';
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
|
||||
const NG_BINDING_CLASS = 'ng-binding';
|
||||
const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
|
||||
@ -256,9 +257,11 @@ export class ProtoView {
|
||||
instantiateInPlace:boolean;
|
||||
rootBindingOffset:int;
|
||||
isTemplateElement:boolean;
|
||||
shadowDomStrategy: ShadowDomStrategy;
|
||||
constructor(
|
||||
template:Element,
|
||||
protoChangeDetector:ProtoChangeDetector) {
|
||||
protoChangeDetector:ProtoChangeDetector,
|
||||
shadowDomStrategy: ShadowDomStrategy) {
|
||||
this.element = template;
|
||||
this.elementBinders = [];
|
||||
this.variableBindings = MapWrapper.create();
|
||||
@ -270,6 +273,7 @@ export class ProtoView {
|
||||
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS))
|
||||
? 1 : 0;
|
||||
this.isTemplateElement = this.element instanceof TemplateElement;
|
||||
this.shadowDomStrategy = shadowDomStrategy;
|
||||
}
|
||||
|
||||
// TODO(rado): hostElementInjector should be moved to hydrate phase.
|
||||
@ -353,11 +357,12 @@ export class ProtoView {
|
||||
var lightDom = null;
|
||||
var bindingPropagationConfig = null;
|
||||
if (isPresent(binder.componentDirective)) {
|
||||
var strategy = this.shadowDomStrategy;
|
||||
var childView = binder.nestedProtoView.instantiate(elementInjector);
|
||||
view.changeDetector.addChild(childView.changeDetector);
|
||||
|
||||
lightDom = binder.componentDirective.shadowDomStrategy.constructLightDom(view, childView, element);
|
||||
binder.componentDirective.shadowDomStrategy.attachTemplate(element, childView);
|
||||
lightDom = strategy.constructLightDom(view, childView, element);
|
||||
strategy.attachTemplate(element, childView);
|
||||
|
||||
bindingPropagationConfig = new BindingPropagationConfig(view.changeDetector);
|
||||
|
||||
@ -497,12 +502,14 @@ export class ProtoView {
|
||||
// and the component template is already compiled into protoView.
|
||||
// Used for bootstrapping.
|
||||
static createRootProtoView(protoView: ProtoView,
|
||||
insertionElement, rootComponentAnnotatedType: DirectiveMetadata,
|
||||
protoChangeDetector:ProtoChangeDetector
|
||||
insertionElement,
|
||||
rootComponentAnnotatedType: DirectiveMetadata,
|
||||
protoChangeDetector:ProtoChangeDetector,
|
||||
shadowDomStrategy: ShadowDomStrategy
|
||||
): ProtoView {
|
||||
|
||||
DOM.addClass(insertionElement, 'ng-binding');
|
||||
var rootProtoView = new ProtoView(insertionElement, protoChangeDetector);
|
||||
var rootProtoView = new ProtoView(insertionElement, protoChangeDetector, shadowDomStrategy);
|
||||
rootProtoView.instantiateInPlace = true;
|
||||
var binder = rootProtoView.bindElement(
|
||||
new ProtoElementInjector(null, 0, [rootComponentAnnotatedType.type], true));
|
||||
|
7
modules/angular2/src/core/compiler/xhr/xhr.js
vendored
Normal file
7
modules/angular2/src/core/compiler/xhr/xhr.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import {Promise} from 'angular2/src/facade/async';
|
||||
|
||||
export class XHR {
|
||||
get(url: string): Promise<string> {
|
||||
return null;
|
||||
}
|
||||
}
|
12
modules/angular2/src/core/compiler/xhr/xhr_impl.dart
Normal file
12
modules/angular2/src/core/compiler/xhr/xhr_impl.dart
Normal file
@ -0,0 +1,12 @@
|
||||
import 'dart:async';
|
||||
import 'dart:html';
|
||||
import './xhr.dart' show XHR;
|
||||
|
||||
class XHRImpl extends XHR {
|
||||
Future<String> get(String url) {
|
||||
return HttpRequest.request(url).then(
|
||||
(HttpRequest request) => request.responseText,
|
||||
onError: (Error e) => throw 'Failed to load $url'
|
||||
);
|
||||
}
|
||||
}
|
27
modules/angular2/src/core/compiler/xhr/xhr_impl.es6
Normal file
27
modules/angular2/src/core/compiler/xhr/xhr_impl.es6
Normal file
@ -0,0 +1,27 @@
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {XHR} from './xhr';
|
||||
|
||||
export class XHRImpl extends XHR {
|
||||
get(url: string): Promise<string> {
|
||||
var completer = PromiseWrapper.completer();
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.responseType = 'text';
|
||||
|
||||
xhr.onload = function() {
|
||||
var status = xhr.status;
|
||||
if (200 <= status && status <= 300) {
|
||||
completer.complete(xhr.responseText);
|
||||
} else {
|
||||
completer.reject(`Failed to load ${url}`);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
completer.reject(`Failed to load ${url}`);
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
return completer.promise;
|
||||
}
|
||||
}
|
@ -104,9 +104,7 @@ class ListWrapper {
|
||||
static void insert(List l, int index, value) {
|
||||
l.insert(index, value);
|
||||
}
|
||||
static void removeAt(List l, int index) {
|
||||
l.removeAt(index);
|
||||
}
|
||||
static removeAt(List l, int index) => l.removeAt(index);
|
||||
static void removeAll(List list, List items) {
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
list.remove(items[i]);
|
||||
|
@ -3,7 +3,16 @@ library angular.core.facade.dom;
|
||||
import 'dart:html';
|
||||
import 'dart:js' show JsObject, context;
|
||||
|
||||
export 'dart:html' show DocumentFragment, Node, Element, TemplateElement, Text, document, location, window;
|
||||
export 'dart:html' show
|
||||
document,
|
||||
DocumentFragment,
|
||||
Element,
|
||||
location,
|
||||
Node,
|
||||
StyleElement,
|
||||
TemplateElement,
|
||||
Text,
|
||||
window;
|
||||
|
||||
// TODO(tbosch): Is there a builtin one? Why is Dart
|
||||
// removing unknown elements by default?
|
||||
@ -62,7 +71,10 @@ class DOM {
|
||||
static void removeChild(Element el, Node node) {
|
||||
node.remove();
|
||||
}
|
||||
static void insertBefore(Node el, Node node) {
|
||||
static Element remove(Element el) {
|
||||
return el..remove();
|
||||
}
|
||||
static insertBefore(Node el, node) {
|
||||
el.parentNode.insertBefore(node, el);
|
||||
}
|
||||
static void insertAllBefore(Node el, Iterable<Node> nodes) {
|
||||
@ -72,8 +84,8 @@ class DOM {
|
||||
el.parentNode.insertBefore(node, el.nextNode);
|
||||
}
|
||||
static String getText(Node el) => el.text;
|
||||
static void setText(Text text, String value) {
|
||||
text.text = value;
|
||||
static void setText(Node el, String value) {
|
||||
el.text = value;
|
||||
}
|
||||
static TemplateElement createTemplate(String html) {
|
||||
var t = new TemplateElement();
|
||||
@ -116,6 +128,10 @@ class DOM {
|
||||
static String getAttribute(Element element, String attribute) =>
|
||||
element.getAttribute(attribute);
|
||||
|
||||
static void setAttribute(Element element, String name, String value) {
|
||||
element.setAttribute(name, value);
|
||||
}
|
||||
|
||||
static Node templateAwareRoot(Element el) =>
|
||||
el is TemplateElement ? el.content : el;
|
||||
|
||||
|
@ -5,6 +5,7 @@ export var NodeList = window.NodeList;
|
||||
export var Text = window.Text;
|
||||
export var Element = window.HTMLElement;
|
||||
export var TemplateElement = window.HTMLTemplateElement;
|
||||
export var StyleElement = window.HTMLStyleElement;
|
||||
export var document = window.document;
|
||||
export var location = window.location;
|
||||
export var gc = window.gc ? () => window.gc() : () => null;
|
||||
@ -70,6 +71,11 @@ export class DOM {
|
||||
static removeChild(el, node) {
|
||||
el.removeChild(node);
|
||||
}
|
||||
static remove(el: Element): Element {
|
||||
var parent = el.parentNode;
|
||||
parent.removeChild(el);
|
||||
return el;
|
||||
}
|
||||
static insertBefore(el, node) {
|
||||
el.parentNode.insertBefore(node, el);
|
||||
}
|
||||
@ -87,8 +93,9 @@ export class DOM {
|
||||
static getText(el: Element) {
|
||||
return el.textContent;
|
||||
}
|
||||
static setText(text:Text, value:string) {
|
||||
text.nodeValue = value;
|
||||
// TODO(vicb): removed Element type because it does not support StyleElement
|
||||
static setText(el, value:string) {
|
||||
el.textContent = value;
|
||||
}
|
||||
static createTemplate(html) {
|
||||
var t = document.createElement('template');
|
||||
@ -103,6 +110,11 @@ export class DOM {
|
||||
el.setAttribute(attrName, attrValue);
|
||||
return el;
|
||||
}
|
||||
static createStyleElement(css:string, doc=document):StyleElement {
|
||||
var style = doc.createElement('STYLE');
|
||||
style.innerText = css;
|
||||
return style;
|
||||
}
|
||||
static clone(node:Node) {
|
||||
return node.cloneNode(true);
|
||||
}
|
||||
@ -142,6 +154,9 @@ export class DOM {
|
||||
static getAttribute(element:Element, attribute:string) {
|
||||
return element.getAttribute(attribute);
|
||||
}
|
||||
static setAttribute(element:Element, name:string, value:string) {
|
||||
element.setAttribute(name, value);
|
||||
}
|
||||
static templateAwareRoot(el:Element):Node {
|
||||
return el instanceof TemplateElement ? el.content : el;
|
||||
}
|
||||
|
@ -69,6 +69,14 @@ class StringWrapper {
|
||||
static String substring(String s, int start, [int end]) {
|
||||
return s.substring(start, end);
|
||||
}
|
||||
|
||||
static String replaceAllMapped(String s, RegExp from, Function cb) {
|
||||
return s.replaceAllMapped(from, cb);
|
||||
}
|
||||
|
||||
static bool contains(String s, String substr) {
|
||||
return s.contains(substr);
|
||||
}
|
||||
}
|
||||
|
||||
class StringJoiner {
|
||||
@ -102,8 +110,10 @@ class NumberWrapper {
|
||||
}
|
||||
|
||||
class RegExpWrapper {
|
||||
static RegExp create(String regExpStr) {
|
||||
return new RegExp(regExpStr);
|
||||
static RegExp create(regExpStr, [String flags = '']) {
|
||||
bool multiLine = flags.contains('m');
|
||||
bool caseSensitive = !flags.contains('i');
|
||||
return new RegExp(regExpStr, multiLine: multiLine, caseSensitive: caseSensitive);
|
||||
}
|
||||
static Match firstMatch(RegExp regExp, String input) {
|
||||
return regExp.firstMatch(input);
|
||||
|
@ -75,6 +75,16 @@ export class StringWrapper {
|
||||
static substring(s:string, start:int, end:int = undefined) {
|
||||
return s.substring(start, end);
|
||||
}
|
||||
|
||||
static replaceAllMapped(s:string, from:RegExp, cb:Function): string {
|
||||
return s.replace(from.multiple, function(...matches) {
|
||||
return cb(matches);
|
||||
});
|
||||
}
|
||||
|
||||
static contains(s:string, substr:string): boolean {
|
||||
return s.indexOf(substr) != -1;
|
||||
}
|
||||
}
|
||||
|
||||
export class StringJoiner {
|
||||
@ -160,10 +170,11 @@ export var RegExp = assert.define('RegExp', function(obj) {
|
||||
});
|
||||
|
||||
export class RegExpWrapper {
|
||||
static create(regExpStr):RegExp {
|
||||
static create(regExpStr, flags:string = ''):RegExp {
|
||||
flags = flags.replace(/g/g, '');
|
||||
return {
|
||||
multiple: new window.RegExp(regExpStr, 'g'),
|
||||
single: new window.RegExp(regExpStr)
|
||||
multiple: new window.RegExp(regExpStr, flags + 'g'),
|
||||
single: new window.RegExp(regExpStr, flags)
|
||||
};
|
||||
}
|
||||
static firstMatch(regExp, input) {
|
||||
|
108
modules/angular2/src/mock/xhr_mock.js
vendored
Normal file
108
modules/angular2/src/mock/xhr_mock.js
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
import {XHR} from 'angular2/src/core/compiler/xhr/xhr';
|
||||
import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {isBlank, isPresent, normalizeBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import {PromiseWrapper, Promise} from 'angular2/src/facade/async';
|
||||
|
||||
export class XHRMock extends XHR {
|
||||
_expectations: List<_Expectation>;
|
||||
_definitions: Map;
|
||||
_requests: List<Promise>;
|
||||
|
||||
constructor() {
|
||||
this._expectations = [];
|
||||
this._definitions = MapWrapper.create();
|
||||
this._requests = [];
|
||||
}
|
||||
|
||||
get(url: string): Promise<string> {
|
||||
var request = new _PendingRequest(url);
|
||||
ListWrapper.push(this._requests, request);
|
||||
return request.getPromise();
|
||||
}
|
||||
|
||||
expect(url: string, response: string) {
|
||||
var expectation = new _Expectation(url, response);
|
||||
ListWrapper.push(this._expectations, expectation);
|
||||
}
|
||||
|
||||
when(url: string, response: string) {
|
||||
MapWrapper.set(this._definitions, url, response);
|
||||
}
|
||||
|
||||
flush() {
|
||||
if (this._requests.length === 0) {
|
||||
throw new BaseException('No pending requests to flush');
|
||||
}
|
||||
|
||||
do {
|
||||
var request = ListWrapper.removeAt(this._requests, 0);
|
||||
this._processRequest(request);
|
||||
} while (this._requests.length > 0);
|
||||
|
||||
this.verifyNoOustandingExpectations();
|
||||
}
|
||||
|
||||
verifyNoOustandingExpectations() {
|
||||
if (this._expectations.length === 0) return;
|
||||
|
||||
var urls = [];
|
||||
for (var i = 0; i < this._expectations.length; i++) {
|
||||
var expectation = this._expectations[i];
|
||||
ListWrapper.push(urls, expectation.url);
|
||||
}
|
||||
|
||||
throw new BaseException(`Unsatisfied requests: ${ListWrapper.join(urls, ', ')}`);
|
||||
}
|
||||
|
||||
_processRequest(request: _PendingRequest) {
|
||||
var url = request.url;
|
||||
|
||||
if (this._expectations.length > 0) {
|
||||
var expectation = this._expectations[0];
|
||||
if (expectation.url === url) {
|
||||
ListWrapper.remove(this._expectations, expectation);
|
||||
request.complete(expectation.response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (MapWrapper.contains(this._definitions, url)) {
|
||||
var response = MapWrapper.get(this._definitions, url);
|
||||
request.complete(normalizeBlank(response));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new BaseException(`Unexpected request ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
class _PendingRequest {
|
||||
url: string;
|
||||
completer;
|
||||
|
||||
constructor(url) {
|
||||
this.url = url;
|
||||
this.completer = PromiseWrapper.completer();
|
||||
}
|
||||
|
||||
complete(response: string) {
|
||||
if (isBlank(response)) {
|
||||
this.completer.reject(`Failed to load ${this.url}`);
|
||||
} else {
|
||||
this.completer.complete(response);
|
||||
}
|
||||
}
|
||||
|
||||
getPromise(): Promise<string> {
|
||||
return this.completer.promise;
|
||||
}
|
||||
}
|
||||
|
||||
class _Expectation {
|
||||
url: string;
|
||||
response: string;
|
||||
constructor(url: string, response: string) {
|
||||
this.url = url;
|
||||
this.response = response;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user