refactor(render): user render compiler
This commit is contained in:
8
modules/angular2/src/core/application.js
vendored
8
modules/angular2/src/core/application.js
vendored
@ -7,9 +7,10 @@ import {ProtoView} from './compiler/view';
|
||||
import {Reflector, reflector} from 'angular2/src/reflection/reflection';
|
||||
import {Parser, Lexer, ChangeDetection, dynamicChangeDetection, jitChangeDetection} from 'angular2/change_detection';
|
||||
import {ExceptionHandler} from './exception_handler';
|
||||
import {TemplateLoader} from './compiler/template_loader';
|
||||
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
|
||||
import {TemplateResolver} from './compiler/template_resolver';
|
||||
import {DirectiveMetadataReader} from './compiler/directive_metadata_reader';
|
||||
import {DirectiveBinding} from './compiler/element_injector';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
|
||||
@ -24,7 +25,6 @@ import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mappe
|
||||
import {UrlResolver} from 'angular2/src/services/url_resolver';
|
||||
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
|
||||
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
|
||||
import {CssProcessor} from 'angular2/src/core/compiler/css_processor';
|
||||
import {Component} from 'angular2/src/core/annotations/annotations';
|
||||
import {PrivateComponentLoader} from 'angular2/src/core/compiler/private_component_loader';
|
||||
import {TestabilityRegistry, Testability} from 'angular2/src/core/testability/testability';
|
||||
@ -74,7 +74,8 @@ function _injectorBindings(appComponentType): List<Binding> {
|
||||
return compiler.compile(appComponentAnnotatedType.type).then(
|
||||
(protoView) => {
|
||||
var appProtoView = ProtoView.createRootProtoView(protoView, appElement,
|
||||
appComponentAnnotatedType, changeDetection.createProtoChangeDetector('root'),
|
||||
DirectiveBinding.createFromType(appComponentAnnotatedType.type, appComponentAnnotatedType.annotation),
|
||||
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
|
||||
@ -112,7 +113,6 @@ function _injectorBindings(appComponentType): List<Binding> {
|
||||
UrlResolver,
|
||||
StyleUrlResolver,
|
||||
StyleInliner,
|
||||
bind(CssProcessor).toFactory(() => new CssProcessor(null), []),
|
||||
PrivateComponentLoader,
|
||||
Testability,
|
||||
];
|
||||
|
275
modules/angular2/src/core/compiler/compiler.js
vendored
275
modules/angular2/src/core/compiler/compiler.js
vendored
@ -6,18 +6,22 @@ import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection
|
||||
import {ChangeDetection, Parser} from 'angular2/change_detection';
|
||||
|
||||
import {DirectiveMetadataReader} from './directive_metadata_reader';
|
||||
import {Component, Viewport, DynamicComponent, Decorator} from '../annotations/annotations';
|
||||
import {ProtoView} from './view';
|
||||
import {CompilePipeline} from './pipeline/compile_pipeline';
|
||||
import {CompileElement} from './pipeline/compile_element';
|
||||
import {createDefaultSteps} from './pipeline/default_steps';
|
||||
import {TemplateLoader} from './template_loader';
|
||||
import {DirectiveBinding} from './element_injector';
|
||||
import {TemplateResolver} from './template_resolver';
|
||||
import {Template} from '../annotations/template';
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
import {CompileStep} from './pipeline/compile_step';
|
||||
import {ComponentUrlMapper} from './component_url_mapper';
|
||||
import {ProtoViewFactory} from './proto_view_factory';
|
||||
import {UrlResolver} from 'angular2/src/services/url_resolver';
|
||||
import {CssProcessor} from './css_processor';
|
||||
|
||||
import {TemplateLoader} from 'angular2/src/render/dom/compiler/template_loader';
|
||||
import {DefaultStepFactory} from 'angular2/src/render/dom/compiler/compile_step_factory';
|
||||
import {DirectDomRenderer} from 'angular2/src/render/dom/direct_dom_renderer';
|
||||
|
||||
import * as rc from 'angular2/src/render/dom/compiler/compiler';
|
||||
import * as renderApi from 'angular2/src/render/api';
|
||||
|
||||
/**
|
||||
* Cache that stores the ProtoView of the template of a component.
|
||||
@ -44,72 +48,51 @@ export class CompilerCache {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The compiler loads and translates the html templates of components into
|
||||
* nested ProtoViews. To decompose its functionality it uses
|
||||
* the CompilePipeline and the CompileSteps.
|
||||
*
|
||||
* @publicModule angular2/template
|
||||
*/
|
||||
@Injectable()
|
||||
export class Compiler {
|
||||
|
||||
// TODO(tbosch): rename this class to Compiler
|
||||
// and remove the current Compiler when core uses the render views.
|
||||
export class NewCompiler {
|
||||
_reader: DirectiveMetadataReader;
|
||||
_parser:Parser;
|
||||
_compilerCache:CompilerCache;
|
||||
_changeDetection:ChangeDetection;
|
||||
_templateLoader:TemplateLoader;
|
||||
_compiling:Map<Type, Promise>;
|
||||
_shadowDomStrategy: ShadowDomStrategy;
|
||||
_templateResolver: TemplateResolver;
|
||||
_componentUrlMapper: ComponentUrlMapper;
|
||||
_urlResolver: UrlResolver;
|
||||
_appUrl: string;
|
||||
_cssProcessor: CssProcessor;
|
||||
_renderer: renderApi.Renderer;
|
||||
_protoViewFactory:ProtoViewFactory;
|
||||
|
||||
constructor(changeDetection:ChangeDetection,
|
||||
templateLoader:TemplateLoader,
|
||||
reader: DirectiveMetadataReader,
|
||||
parser:Parser,
|
||||
constructor(reader: DirectiveMetadataReader,
|
||||
cache:CompilerCache,
|
||||
shadowDomStrategy: ShadowDomStrategy,
|
||||
templateResolver: TemplateResolver,
|
||||
componentUrlMapper: ComponentUrlMapper,
|
||||
urlResolver: UrlResolver,
|
||||
cssProcessor: CssProcessor) {
|
||||
this._changeDetection = changeDetection;
|
||||
renderer: renderApi.Renderer,
|
||||
protoViewFactory: ProtoViewFactory) {
|
||||
this._reader = reader;
|
||||
this._parser = parser;
|
||||
this._compilerCache = cache;
|
||||
this._templateLoader = templateLoader;
|
||||
this._compiling = MapWrapper.create();
|
||||
this._shadowDomStrategy = shadowDomStrategy;
|
||||
this._templateResolver = templateResolver;
|
||||
this._componentUrlMapper = componentUrlMapper;
|
||||
this._urlResolver = urlResolver;
|
||||
this._appUrl = urlResolver.resolve(null, './');
|
||||
this._cssProcessor = cssProcessor;
|
||||
this._renderer = renderer;
|
||||
this._protoViewFactory = protoViewFactory;
|
||||
}
|
||||
|
||||
// todo(misko): should be private method
|
||||
createSteps(component:Type, template: Template):List<CompileStep> {
|
||||
var dirMetadata = ListWrapper.map(this._flattenDirectives(template),
|
||||
(d) => this._reader.read(d));
|
||||
|
||||
var cmpMetadata = this._reader.read(component);
|
||||
|
||||
var templateUrl = this._templateLoader.getTemplateUrl(template);
|
||||
|
||||
return createDefaultSteps(this._changeDetection, this._parser, cmpMetadata, dirMetadata,
|
||||
this._shadowDomStrategy, templateUrl, this._cssProcessor);
|
||||
_bindDirective(directive) {
|
||||
var meta = this._reader.read(directive);
|
||||
return DirectiveBinding.createFromType(meta.type, meta.annotation);
|
||||
}
|
||||
|
||||
compile(component: Type):Promise<ProtoView> {
|
||||
var protoView = this._compile(component);
|
||||
var protoView = this._compile(this._bindDirective(component));
|
||||
return PromiseWrapper.isPromise(protoView) ? protoView : PromiseWrapper.resolve(protoView);
|
||||
}
|
||||
|
||||
// TODO(vicb): union type return ProtoView or Promise<ProtoView>
|
||||
_compile(component: Type) {
|
||||
_compile(componentBinding: DirectiveBinding) {
|
||||
var component = componentBinding.key.token;
|
||||
var protoView = this._compilerCache.get(component);
|
||||
if (isPresent(protoView)) {
|
||||
// The component has already been compiled into a ProtoView,
|
||||
@ -126,81 +109,112 @@ export class Compiler {
|
||||
}
|
||||
|
||||
var template = this._templateResolver.resolve(component);
|
||||
var directives = ListWrapper.map(
|
||||
this._flattenDirectives(template),
|
||||
(directive) => this._bindDirective(directive)
|
||||
);
|
||||
|
||||
var componentUrl = this._componentUrlMapper.getUrl(component);
|
||||
var baseUrl = this._urlResolver.resolve(this._appUrl, componentUrl);
|
||||
this._templateLoader.setBaseUrl(template, baseUrl);
|
||||
pvPromise = this._compileNoRecurse(componentBinding, template, directives).then( (protoView) => {
|
||||
// Populate the cache before compiling the nested components,
|
||||
// so that components can reference themselves in their template.
|
||||
this._compilerCache.set(component, protoView);
|
||||
MapWrapper.delete(this._compiling, component);
|
||||
|
||||
var tplElement = this._templateLoader.load(template);
|
||||
|
||||
if (PromiseWrapper.isPromise(tplElement)) {
|
||||
pvPromise = PromiseWrapper.then(tplElement,
|
||||
(el) => this._compileTemplate(template, el, component),
|
||||
(_) => { throw new BaseException(`Failed to load the template for ${stringify(component)}`); }
|
||||
);
|
||||
MapWrapper.set(this._compiling, component, pvPromise);
|
||||
return pvPromise;
|
||||
}
|
||||
|
||||
return this._compileTemplate(template, tplElement, component);
|
||||
}
|
||||
|
||||
// TODO(vicb): union type return ProtoView or Promise<ProtoView>
|
||||
_compileTemplate(template: Template, tplElement, component: Type) {
|
||||
var pipeline = new CompilePipeline(this.createSteps(component, template));
|
||||
var compileElements;
|
||||
|
||||
try {
|
||||
compileElements = pipeline.process(tplElement, stringify(component));
|
||||
} catch(ex) {
|
||||
return PromiseWrapper.reject(ex);
|
||||
}
|
||||
|
||||
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(component, protoView);
|
||||
MapWrapper.delete(this._compiling, component);
|
||||
|
||||
// Compile all the components from the template
|
||||
var nestedPVPromises = [];
|
||||
for (var i = 0; i < compileElements.length; i++) {
|
||||
var ce = compileElements[i];
|
||||
if (ce.hasNestedView) {
|
||||
this._compileNestedProtoView(ce, nestedPVPromises);
|
||||
// Compile all the components from the template
|
||||
var nestedPVPromises = this._compileNestedComponents(protoView);
|
||||
if (nestedPVPromises.length > 0) {
|
||||
// Returns ProtoView Promise when there are any asynchronous nested ProtoViews.
|
||||
// The promise will resolved after nested ProtoViews are compiled.
|
||||
return PromiseWrapper.then(PromiseWrapper.all(nestedPVPromises),
|
||||
(_) => protoView,
|
||||
(e) => { throw new BaseException(`${e} -> Failed to compile ${stringify(component)}`); }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (protoView.stylePromises.length > 0) {
|
||||
// The protoView is ready after all asynchronous styles are ready
|
||||
var syncProtoView = protoView;
|
||||
protoView = PromiseWrapper.all(syncProtoView.stylePromises).then((_) => syncProtoView);
|
||||
}
|
||||
|
||||
if (nestedPVPromises.length > 0) {
|
||||
// Returns ProtoView Promise when there are any asynchronous nested ProtoViews.
|
||||
// The promise will resolved after nested ProtoViews are compiled.
|
||||
return PromiseWrapper.then(PromiseWrapper.all(nestedPVPromises),
|
||||
(_) => protoView,
|
||||
(e) => { throw new BaseException(`${e.message} -> Failed to compile ${stringify(component)}`); }
|
||||
);
|
||||
}
|
||||
|
||||
return protoView;
|
||||
return protoView;
|
||||
});
|
||||
MapWrapper.set(this._compiling, component, pvPromise);
|
||||
return pvPromise;
|
||||
}
|
||||
|
||||
_compileNestedProtoView(ce: CompileElement, promises: List<Promise>) {
|
||||
var protoView = this._compile(ce.componentDirective.type);
|
||||
|
||||
if (PromiseWrapper.isPromise(protoView)) {
|
||||
ListWrapper.push(
|
||||
promises,
|
||||
protoView.then(function(pv) { ce.inheritedElementBinder.nestedProtoView = pv;})
|
||||
);
|
||||
_compileNoRecurse(componentBinding, template, directives):Promise<ProtoView> {
|
||||
var component = componentBinding.key.token;
|
||||
var componentUrl = this._urlResolver.resolve(
|
||||
this._appUrl, this._componentUrlMapper.getUrl(component)
|
||||
);
|
||||
var templateAbsUrl = null;
|
||||
if (isPresent(template.url)) {
|
||||
templateAbsUrl = this._urlResolver.resolve(componentUrl, template.url);
|
||||
} else {
|
||||
ce.inheritedElementBinder.nestedProtoView = protoView;
|
||||
// Note: If we have an inline template, we also need to send
|
||||
// the url for the component to the renderer so that it
|
||||
// is able to resolve urls in stylesheets.
|
||||
templateAbsUrl = componentUrl;
|
||||
}
|
||||
var renderTemplate = new renderApi.Template({
|
||||
componentId: stringify(component),
|
||||
absUrl: templateAbsUrl,
|
||||
inline: template.inline,
|
||||
directives: ListWrapper.map(directives, this._buildRenderDirective)
|
||||
});
|
||||
return this._renderer.compile(renderTemplate).then( (renderPv) => {
|
||||
return this._protoViewFactory.createProtoView(componentBinding.annotation, renderPv, directives);
|
||||
});
|
||||
}
|
||||
|
||||
_compileNestedComponents(protoView, nestedPVPromises = null):List<Promise> {
|
||||
if (isBlank(nestedPVPromises)) {
|
||||
nestedPVPromises = [];
|
||||
}
|
||||
ListWrapper.map(protoView.elementBinders, (elementBinder) => {
|
||||
var nestedComponent = elementBinder.componentDirective;
|
||||
if (isPresent(nestedComponent) && !(nestedComponent.annotation instanceof DynamicComponent)) {
|
||||
var nestedCall = this._compile(nestedComponent);
|
||||
if (PromiseWrapper.isPromise(nestedCall)) {
|
||||
ListWrapper.push(nestedPVPromises, nestedCall.then( (nestedPv) => {
|
||||
elementBinder.nestedProtoView = nestedPv;
|
||||
}));
|
||||
} else {
|
||||
elementBinder.nestedProtoView = nestedCall;
|
||||
}
|
||||
} else if (isPresent(elementBinder.nestedProtoView)) {
|
||||
this._compileNestedComponents(elementBinder.nestedProtoView, nestedPVPromises);
|
||||
}
|
||||
});
|
||||
return nestedPVPromises;
|
||||
}
|
||||
|
||||
_buildRenderDirective(directiveBinding) {
|
||||
var ann = directiveBinding.annotation;
|
||||
var renderType;
|
||||
var compileChildren = true;
|
||||
if ((ann instanceof Component) || (ann instanceof DynamicComponent)) {
|
||||
renderType = renderApi.DirectiveMetadata.COMPONENT_TYPE;
|
||||
} else if (ann instanceof Viewport) {
|
||||
renderType = renderApi.DirectiveMetadata.VIEWPORT_TYPE;
|
||||
} else if (ann instanceof Decorator) {
|
||||
renderType = renderApi.DirectiveMetadata.DECORATOR_TYPE;
|
||||
compileChildren = ann.compileChildren;
|
||||
}
|
||||
var setters = [];
|
||||
var readAttributes = [];
|
||||
ListWrapper.forEach(directiveBinding.dependencies, (dep) => {
|
||||
if (isPresent(dep.propSetterName)) {
|
||||
ListWrapper.push(setters, dep.propSetterName);
|
||||
}
|
||||
if (isPresent(dep.attributeName)) {
|
||||
ListWrapper.push(readAttributes, dep.attributeName);
|
||||
}
|
||||
});
|
||||
return new renderApi.DirectiveMetadata({
|
||||
id: stringify(directiveBinding.key.token),
|
||||
type: renderType,
|
||||
selector: ann.selector,
|
||||
compileChildren: compileChildren,
|
||||
events: isPresent(ann.events) ? MapWrapper.createFromStringMap(ann.events) : null,
|
||||
bind: isPresent(ann.bind) ? MapWrapper.createFromStringMap(ann.bind) : null,
|
||||
setters: setters,
|
||||
readAttributes: readAttributes
|
||||
});
|
||||
}
|
||||
|
||||
_flattenDirectives(template: Template):List<Type> {
|
||||
@ -225,4 +239,39 @@ export class Compiler {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// TODO(tbosch): delete this class once we use the render views
|
||||
/**
|
||||
* The compiler loads and translates the html templates of components into
|
||||
* nested ProtoViews. To decompose its functionality it uses
|
||||
* the render compiler.
|
||||
*
|
||||
* @publicModule angular2/template
|
||||
*/
|
||||
@Injectable()
|
||||
export class Compiler extends NewCompiler {
|
||||
constructor(changeDetection:ChangeDetection,
|
||||
templateLoader:TemplateLoader,
|
||||
reader: DirectiveMetadataReader,
|
||||
parser:Parser,
|
||||
cache:CompilerCache,
|
||||
shadowDomStrategy: ShadowDomStrategy,
|
||||
templateResolver: TemplateResolver,
|
||||
componentUrlMapper: ComponentUrlMapper,
|
||||
urlResolver: UrlResolver) {
|
||||
super(
|
||||
reader,
|
||||
cache,
|
||||
templateResolver,
|
||||
componentUrlMapper,
|
||||
urlResolver,
|
||||
new DirectDomRenderer(
|
||||
new rc.Compiler(
|
||||
new DefaultStepFactory(parser, shadowDomStrategy.render),
|
||||
templateLoader
|
||||
),
|
||||
null, null
|
||||
),
|
||||
new ProtoViewFactory(changeDetection, shadowDomStrategy)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,71 +0,0 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {List} from 'angular2/src/facade/collection';
|
||||
|
||||
import {CompileStep} from './pipeline/compile_step';
|
||||
import {CompileElement} from './pipeline/compile_element';
|
||||
import {CompileControl} from './pipeline/compile_control';
|
||||
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
|
||||
/**
|
||||
* Processes the <style> tags during the compilation:
|
||||
* - Apply any given transformers,
|
||||
* - Apply the shadow DOM strategy style step.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CssProcessor {
|
||||
_transformers: List<CssTransformer>;
|
||||
|
||||
constructor(transformers: List<CssTransformer>) {
|
||||
this._transformers = transformers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a compile step to be added to the compiler pipeline.
|
||||
*
|
||||
* @param {DirectiveMetadata} cmpMetadata
|
||||
* @param {ShadowDomStrategy} shadowDomStrategy
|
||||
* @param {string} templateUrl The base URL of the template
|
||||
*/
|
||||
getCompileStep(cmpMetadata: DirectiveMetadata, shadowDomStrategy: ShadowDomStrategy,
|
||||
templateUrl: string) {
|
||||
var strategyStep = shadowDomStrategy.getStyleCompileStep(cmpMetadata, templateUrl);
|
||||
return new _CssProcessorStep(strategyStep, this._transformers);
|
||||
}
|
||||
}
|
||||
|
||||
export class CssTransformer {
|
||||
transform(styleElement) {};
|
||||
}
|
||||
|
||||
class _CssProcessorStep extends CompileStep {
|
||||
_strategyStep: CompileStep;
|
||||
_transformers: List<CssTransformer>;
|
||||
|
||||
constructor(strategyStep: CompileStep, transformers: List<CssTransformer>) {
|
||||
super();
|
||||
this._strategyStep = strategyStep;
|
||||
this._transformers = transformers;
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
if (DOM.tagName(current.element) == 'STYLE') {
|
||||
current.ignoreBindings = true;
|
||||
|
||||
if (isPresent(this._transformers)) {
|
||||
var styleEl = current.element;
|
||||
for (var i = 0; i < this._transformers.length; i++) {
|
||||
this._transformers[i].transform(styleEl);
|
||||
}
|
||||
}
|
||||
|
||||
if (isPresent(this._strategyStep)) {
|
||||
this._strategyStep.process(parent, current, control);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
import {int, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import * as eiModule from './element_injector';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {DirectiveBinding} from './element_injector';
|
||||
import {List, StringMap} from 'angular2/src/facade/collection';
|
||||
import * as viewModule from './view';
|
||||
|
||||
export class ElementBinder {
|
||||
protoElementInjector:eiModule.ProtoElementInjector;
|
||||
componentDirective:DirectiveMetadata;
|
||||
viewportDirective:DirectiveMetadata;
|
||||
componentDirective:DirectiveBinding;
|
||||
viewportDirective:DirectiveBinding;
|
||||
textNodeIndices:List<int>;
|
||||
hasElementPropertyBindings:boolean;
|
||||
nestedProtoView: viewModule.ProtoView;
|
||||
@ -17,9 +17,9 @@ export class ElementBinder {
|
||||
index:int;
|
||||
distanceToParent:int;
|
||||
constructor(
|
||||
index:int, parent:ElementBinder, distanceToParent: int,
|
||||
protoElementInjector: eiModule.ProtoElementInjector, componentDirective:DirectiveMetadata,
|
||||
viewportDirective:DirectiveMetadata) {
|
||||
index:int, parent:ElementBinder, distanceToParent: int,
|
||||
protoElementInjector: eiModule.ProtoElementInjector, componentDirective:DirectiveBinding,
|
||||
viewportDirective:DirectiveBinding) {
|
||||
if (isBlank(index)) {
|
||||
throw new BaseException('null index not allowed.');
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {NgElement} from 'angular2/src/core/dom/element';
|
||||
import {Directive, onChange, onDestroy, onAllChangesDone} from 'angular2/src/core/annotations/annotations';
|
||||
import {BindingPropagationConfig} from 'angular2/change_detection';
|
||||
import * as pclModule from 'angular2/src/core/compiler/private_component_location';
|
||||
import {setterFactory} from 'angular2/src/render/dom/compiler/property_setter_factory';
|
||||
import {setterFactory} from 'angular2/src/render/dom/view/property_setter_factory';
|
||||
|
||||
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
|
||||
|
||||
@ -132,12 +132,14 @@ export class DirectiveBinding extends Binding {
|
||||
callOnDestroy:boolean;
|
||||
callOnChange:boolean;
|
||||
callOnAllChangesDone:boolean;
|
||||
annotation:Directive;
|
||||
|
||||
constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean, annotation:Directive) {
|
||||
super(key, factory, dependencies, providedAsPromise);
|
||||
this.callOnDestroy = isPresent(annotation) && annotation.hasLifecycleHook(onDestroy);
|
||||
this.callOnChange = isPresent(annotation) && annotation.hasLifecycleHook(onChange);
|
||||
this.callOnAllChangesDone = isPresent(annotation) && annotation.hasLifecycleHook(onAllChangesDone);
|
||||
this.annotation = annotation;
|
||||
}
|
||||
|
||||
static createFromBinding(b:Binding, annotation:Directive):Binding {
|
||||
|
@ -1,58 +0,0 @@
|
||||
import {isBlank} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileStep} from './compile_step';
|
||||
|
||||
/**
|
||||
* Controls the processing order of elements.
|
||||
* Right now it only allows to add a parent element.
|
||||
*/
|
||||
export class CompileControl {
|
||||
_steps:List<CompileStep>;
|
||||
_currentStepIndex:number;
|
||||
_parent:CompileElement;
|
||||
_results;
|
||||
_additionalChildren;
|
||||
constructor(steps) {
|
||||
this._steps = steps;
|
||||
this._currentStepIndex = 0;
|
||||
this._parent = null;
|
||||
this._results = null;
|
||||
this._additionalChildren = null;
|
||||
}
|
||||
|
||||
// only public so that it can be used by compile_pipeline
|
||||
internalProcess(results, startStepIndex, parent:CompileElement, current:CompileElement) {
|
||||
this._results = results;
|
||||
var previousStepIndex = this._currentStepIndex;
|
||||
var previousParent = this._parent;
|
||||
|
||||
for (var i=startStepIndex; i<this._steps.length; i++) {
|
||||
var step = this._steps[i];
|
||||
this._parent = parent;
|
||||
this._currentStepIndex = i;
|
||||
step.process(parent, current, this);
|
||||
parent = this._parent;
|
||||
}
|
||||
ListWrapper.push(results, current);
|
||||
|
||||
this._currentStepIndex = previousStepIndex;
|
||||
this._parent = previousParent;
|
||||
|
||||
var localAdditionalChildren = this._additionalChildren;
|
||||
this._additionalChildren = null;
|
||||
return localAdditionalChildren;
|
||||
}
|
||||
|
||||
addParent(newElement:CompileElement) {
|
||||
this.internalProcess(this._results, this._currentStepIndex+1, this._parent, newElement);
|
||||
this._parent = newElement;
|
||||
}
|
||||
|
||||
addChild(element:CompileElement) {
|
||||
if (isBlank(this._additionalChildren)) {
|
||||
this._additionalChildren = ListWrapper.create();
|
||||
}
|
||||
ListWrapper.push(this._additionalChildren, element);
|
||||
}
|
||||
}
|
@ -1,227 +0,0 @@
|
||||
import {List, Map, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {int, isBlank, isPresent, Type, StringJoiner, assertionsEnabled} from 'angular2/src/facade/lang';
|
||||
import {DirectiveMetadata} from '../directive_metadata';
|
||||
import {Decorator, Component, Viewport, DynamicComponent} from '../../annotations/annotations';
|
||||
import {ElementBinder} from '../element_binder';
|
||||
import {ProtoElementInjector} from '../element_injector';
|
||||
import * as viewModule from '../view';
|
||||
import {dashCaseToCamelCase} from '../string_utils';
|
||||
|
||||
import {AST} from 'angular2/change_detection';
|
||||
|
||||
/**
|
||||
* Collects all data that is needed to process an element
|
||||
* in the compile process. Fields are filled
|
||||
* by the CompileSteps starting out with the pure HTMLElement.
|
||||
*/
|
||||
export class CompileElement {
|
||||
element;
|
||||
_attrs:Map;
|
||||
_classList:List;
|
||||
textNodeBindings:Map;
|
||||
propertyBindings:Map;
|
||||
eventBindings:Map;
|
||||
attributes:Map;
|
||||
|
||||
/// Store directive name to template name mapping.
|
||||
/// Directive name is what the directive exports the variable as
|
||||
/// Template name is how it is reffered to it in template
|
||||
variableBindings:Map;
|
||||
decoratorDirectives:List<DirectiveMetadata>;
|
||||
viewportDirective:DirectiveMetadata;
|
||||
componentDirective:DirectiveMetadata;
|
||||
hasNestedView:boolean;
|
||||
_allDirectives:List<DirectiveMetadata>;
|
||||
isViewRoot:boolean;
|
||||
hasBindings:boolean;
|
||||
inheritedProtoView:viewModule.ProtoView;
|
||||
inheritedProtoElementInjector:ProtoElementInjector;
|
||||
inheritedElementBinder:ElementBinder;
|
||||
distanceToParentInjector:int;
|
||||
distanceToParentBinder:int;
|
||||
compileChildren: boolean;
|
||||
ignoreBindings: boolean;
|
||||
elementDescription: string; // e.g. '<div [class]="foo">' : used to provide context in case of error
|
||||
contentTagSelector: string;
|
||||
|
||||
constructor(element, compilationUnit = '') {
|
||||
this.element = element;
|
||||
this._attrs = null;
|
||||
this._classList = null;
|
||||
this.textNodeBindings = null;
|
||||
this.propertyBindings = null;
|
||||
this.eventBindings = null;
|
||||
this.variableBindings = null;
|
||||
this.decoratorDirectives = null;
|
||||
this.viewportDirective = null;
|
||||
this.componentDirective = null;
|
||||
this.hasNestedView = false;
|
||||
this._allDirectives = null;
|
||||
this.isViewRoot = false;
|
||||
this.hasBindings = false;
|
||||
// inherited down to children if they don't have
|
||||
// an own protoView
|
||||
this.inheritedProtoView = null;
|
||||
// inherited down to children if they don't have
|
||||
// an own protoElementInjector
|
||||
this.inheritedProtoElementInjector = null;
|
||||
// inherited down to children if they don't have
|
||||
// an own elementBinder
|
||||
this.inheritedElementBinder = null;
|
||||
this.distanceToParentInjector = 0;
|
||||
this.distanceToParentBinder = 0;
|
||||
this.compileChildren = true;
|
||||
// set to true to ignore all the bindings on the element
|
||||
this.ignoreBindings = false;
|
||||
this.contentTagSelector = null;
|
||||
// description is calculated here as compilation steps may change the element
|
||||
var tplDesc = getElementDescription(element);
|
||||
if (compilationUnit !== '') {
|
||||
this.elementDescription = compilationUnit;
|
||||
if (isPresent(tplDesc)) this.elementDescription += ": " + tplDesc;
|
||||
} else {
|
||||
this.elementDescription = tplDesc;
|
||||
}
|
||||
}
|
||||
|
||||
refreshAttrs() {
|
||||
this._attrs = null;
|
||||
}
|
||||
|
||||
attrs():Map<string,string> {
|
||||
if (isBlank(this._attrs)) {
|
||||
this._attrs = DOM.attributeMap(this.element);
|
||||
}
|
||||
return this._attrs;
|
||||
}
|
||||
|
||||
refreshClassList() {
|
||||
this._classList = null;
|
||||
}
|
||||
|
||||
classList():List<string> {
|
||||
if (isBlank(this._classList)) {
|
||||
this._classList = ListWrapper.create();
|
||||
var elClassList = DOM.classList(this.element);
|
||||
for (var i = 0; i < elClassList.length; i++) {
|
||||
ListWrapper.push(this._classList, elClassList[i]);
|
||||
}
|
||||
}
|
||||
return this._classList;
|
||||
}
|
||||
|
||||
addTextNodeBinding(indexInParent:int, expression:AST) {
|
||||
if (isBlank(this.textNodeBindings)) {
|
||||
this.textNodeBindings = MapWrapper.create();
|
||||
}
|
||||
MapWrapper.set(this.textNodeBindings, indexInParent, expression);
|
||||
}
|
||||
|
||||
addPropertyBinding(property:string, expression:AST) {
|
||||
if (isBlank(this.propertyBindings)) {
|
||||
this.propertyBindings = MapWrapper.create();
|
||||
}
|
||||
MapWrapper.set(this.propertyBindings, dashCaseToCamelCase(property), expression);
|
||||
}
|
||||
|
||||
addVariableBinding(variableName:string, variableValue:string) {
|
||||
if (isBlank(this.variableBindings)) {
|
||||
this.variableBindings = MapWrapper.create();
|
||||
}
|
||||
|
||||
// Store the variable map from value to variable, reflecting how it will be used later by
|
||||
// View. When a local is set to the view, a lookup for the variable name will take place keyed
|
||||
// by the "value", or exported identifier. For example, ng-repeat sets a view local of "index".
|
||||
// When this occurs, a lookup keyed by "index" must occur to find if there is a var referencing
|
||||
// it.
|
||||
MapWrapper.set(this.variableBindings, variableValue, dashCaseToCamelCase(variableName));
|
||||
}
|
||||
|
||||
addEventBinding(eventName:string, expression:AST) {
|
||||
if (isBlank(this.eventBindings)) {
|
||||
this.eventBindings = MapWrapper.create();
|
||||
}
|
||||
MapWrapper.set(this.eventBindings, eventName, expression);
|
||||
}
|
||||
|
||||
addAttribute(attributeName:string, attributeValue:string) {
|
||||
if (isBlank(this.attributes)) {
|
||||
this.attributes = MapWrapper.create();
|
||||
}
|
||||
MapWrapper.set(this.attributes, attributeName, attributeValue);
|
||||
}
|
||||
|
||||
addDirective(directive:DirectiveMetadata) {
|
||||
var annotation = directive.annotation;
|
||||
this._allDirectives = null;
|
||||
if (annotation instanceof Decorator) {
|
||||
if (isBlank(this.decoratorDirectives)) {
|
||||
this.decoratorDirectives = ListWrapper.create();
|
||||
}
|
||||
ListWrapper.push(this.decoratorDirectives, directive);
|
||||
if (!annotation.compileChildren) {
|
||||
this.compileChildren = false;
|
||||
}
|
||||
} else if (annotation instanceof Viewport) {
|
||||
this.viewportDirective = directive;
|
||||
} else if (annotation instanceof Component) {
|
||||
this.componentDirective = directive;
|
||||
this.hasNestedView = true;
|
||||
} else if (annotation instanceof DynamicComponent) {
|
||||
this.componentDirective = directive;
|
||||
}
|
||||
}
|
||||
|
||||
getAllDirectives(): List<DirectiveMetadata> {
|
||||
if (this._allDirectives === null) {
|
||||
// Collect all the directives
|
||||
// When present the component directive must be first
|
||||
var directives = ListWrapper.create();
|
||||
if (isPresent(this.componentDirective)) {
|
||||
ListWrapper.push(directives, this.componentDirective);
|
||||
}
|
||||
if (isPresent(this.viewportDirective)) {
|
||||
ListWrapper.push(directives, this.viewportDirective);
|
||||
}
|
||||
if (isPresent(this.decoratorDirectives)) {
|
||||
directives = ListWrapper.concat(directives, this.decoratorDirectives);
|
||||
}
|
||||
this._allDirectives = directives;
|
||||
}
|
||||
return this._allDirectives;
|
||||
}
|
||||
}
|
||||
|
||||
// return an HTML representation of an element start tag - without its content
|
||||
// this is used to give contextual information in case of errors
|
||||
function getElementDescription(domElement):string {
|
||||
var buf = new StringJoiner();
|
||||
var atts = DOM.attributeMap(domElement);
|
||||
|
||||
buf.add("<");
|
||||
buf.add(DOM.tagName(domElement).toLowerCase());
|
||||
|
||||
// show id and class first to ease element identification
|
||||
addDescriptionAttribute(buf, "id", MapWrapper.get(atts, "id"));
|
||||
addDescriptionAttribute(buf, "class", MapWrapper.get(atts, "class"));
|
||||
MapWrapper.forEach(atts, (attValue, attName) => {
|
||||
if (attName !== "id" && attName !== "class") {
|
||||
addDescriptionAttribute(buf, attName, attValue);
|
||||
}
|
||||
});
|
||||
|
||||
buf.add(">");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
|
||||
function addDescriptionAttribute(buffer:StringJoiner, attName:string, attValue) {
|
||||
if (isPresent(attValue)) {
|
||||
if (attValue.length === 0) {
|
||||
buffer.add(' ' + attName);
|
||||
} else {
|
||||
buffer.add(' ' + attName + '="' + attValue + '"');
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
import {CompileStep} from './compile_step';
|
||||
|
||||
/**
|
||||
* CompilePipeline for executing CompileSteps recursively for
|
||||
* all elements in a template.
|
||||
*/
|
||||
export class CompilePipeline {
|
||||
_control:CompileControl;
|
||||
constructor(steps:List<CompileStep>) {
|
||||
this._control = new CompileControl(steps);
|
||||
}
|
||||
|
||||
process(rootElement, compilationCtxtDescription:string = ''):List {
|
||||
var results = ListWrapper.create();
|
||||
this._process(results, null, new CompileElement(rootElement, compilationCtxtDescription), compilationCtxtDescription);
|
||||
return results;
|
||||
}
|
||||
|
||||
_process(results, parent:CompileElement, current:CompileElement, compilationCtxtDescription:string = '') {
|
||||
var additionalChildren = this._control.internalProcess(results, 0, parent, current);
|
||||
|
||||
if (current.compileChildren) {
|
||||
var node = DOM.firstChild(DOM.templateAwareRoot(current.element));
|
||||
while (isPresent(node)) {
|
||||
// compiliation can potentially move the node, so we need to store the
|
||||
// next sibling before recursing.
|
||||
var nextNode = DOM.nextSibling(node);
|
||||
if (DOM.isElementNode(node)) {
|
||||
this._process(results, current, new CompileElement(node, compilationCtxtDescription));
|
||||
}
|
||||
node = nextNode;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPresent(additionalChildren)) {
|
||||
for (var i=0; i<additionalChildren.length; i++) {
|
||||
this._process(results, current, additionalChildren[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import {CompileElement} from './compile_element';
|
||||
import * as ccModule from './compile_control';
|
||||
|
||||
/**
|
||||
* One part of the compile process.
|
||||
* Is guaranteed to be called in depth first order
|
||||
*/
|
||||
export class CompileStep {
|
||||
process(parent:CompileElement, current:CompileElement, control:ccModule.CompileControl) {}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
import {ChangeDetection, Parser} from 'angular2/change_detection';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {PropertyBindingParser} from './property_binding_parser';
|
||||
import {TextInterpolationParser} from './text_interpolation_parser';
|
||||
import {DirectiveParser} from './directive_parser';
|
||||
import {ViewSplitter} from './view_splitter';
|
||||
import {ElementBindingMarker} from './element_binding_marker';
|
||||
import {ProtoViewBuilder} from './proto_view_builder';
|
||||
import {ProtoElementInjectorBuilder} from './proto_element_injector_builder';
|
||||
import {ElementBinderBuilder} from './element_binder_builder';
|
||||
|
||||
import {CssProcessor} from 'angular2/src/core/compiler/css_processor';
|
||||
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
||||
import {ShadowDomStrategy, EmulatedScopedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||
|
||||
/**
|
||||
* Default steps used for compiling a template.
|
||||
* Takes in an HTMLElement and produces the ProtoViews,
|
||||
* ProtoElementInjectors and ElementBinders in the end.
|
||||
*/
|
||||
export function createDefaultSteps(
|
||||
changeDetection:ChangeDetection,
|
||||
parser:Parser,
|
||||
compiledComponent: DirectiveMetadata,
|
||||
directives: List<DirectiveMetadata>,
|
||||
shadowDomStrategy: ShadowDomStrategy,
|
||||
templateUrl: string,
|
||||
cssProcessor: CssProcessor) {
|
||||
|
||||
var steps = [
|
||||
new ViewSplitter(parser),
|
||||
cssProcessor.getCompileStep(compiledComponent, shadowDomStrategy, templateUrl),
|
||||
shadowDomStrategy.getTemplateCompileStep(compiledComponent),
|
||||
new PropertyBindingParser(parser),
|
||||
new DirectiveParser(directives),
|
||||
new TextInterpolationParser(parser),
|
||||
new ElementBindingMarker(),
|
||||
new ProtoViewBuilder(compiledComponent, changeDetection, shadowDomStrategy),
|
||||
new ProtoElementInjectorBuilder(),
|
||||
new ElementBinderBuilder(parser)
|
||||
];
|
||||
|
||||
return steps;
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
import {isPresent, isBlank, BaseException, assertionsEnabled, RegExpWrapper} from 'angular2/src/facade/lang';
|
||||
import {List, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {SelectorMatcher, CssSelector} from 'angular2/src/render/dom/compiler/selector';
|
||||
|
||||
import {DirectiveMetadata} from '../directive_metadata';
|
||||
import {DynamicComponent, Component, Viewport} from '../../annotations/annotations';
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
||||
var PROPERTY_BINDING_REGEXP = RegExpWrapper.create('^ *([^\\s\\|]+)');
|
||||
|
||||
/**
|
||||
* Parses the directives on a single element. Assumes ViewSplitter has already created
|
||||
* <template> elements for template directives.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#decoratorDirectives
|
||||
* - CompileElement#templateDirecitve
|
||||
* - CompileElement#componentDirective.
|
||||
*
|
||||
* Reads:
|
||||
* - CompileElement#propertyBindings (to find directives contained
|
||||
* in the property bindings)
|
||||
* - CompileElement#variableBindings (to find directives contained
|
||||
* in the variable bindings)
|
||||
*/
|
||||
export class DirectiveParser extends CompileStep {
|
||||
_selectorMatcher:SelectorMatcher;
|
||||
constructor(directives:List<DirectiveMetadata>) {
|
||||
super();
|
||||
var selector;
|
||||
|
||||
this._selectorMatcher = new SelectorMatcher();
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
var directiveMetadata = directives[i];
|
||||
selector = CssSelector.parse(directiveMetadata.annotation.selector);
|
||||
this._selectorMatcher.addSelectables(selector, directiveMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var attrs = current.attrs();
|
||||
var classList = current.classList();
|
||||
|
||||
var cssSelector = new CssSelector();
|
||||
var nodeName = DOM.nodeName(current.element);
|
||||
cssSelector.setElement(nodeName);
|
||||
for (var i=0; i < classList.length; i++) {
|
||||
cssSelector.addClassName(classList[i]);
|
||||
}
|
||||
|
||||
MapWrapper.forEach(attrs, (attrValue, attrName) => {
|
||||
cssSelector.addAttribute(attrName, attrValue);
|
||||
});
|
||||
|
||||
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
|
||||
// only be present on <template> elements!
|
||||
var isTemplateElement = DOM.isTemplateElement(current.element);
|
||||
|
||||
this._selectorMatcher.match(cssSelector, (selector, directive) => {
|
||||
current.addDirective(checkDirectiveValidity(directive, current, isTemplateElement));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// check if the directive is compatible with the current element
|
||||
function checkDirectiveValidity(directive, current, isTemplateElement) {
|
||||
var isComponent = directive.annotation instanceof Component || directive.annotation instanceof DynamicComponent;
|
||||
var alreadyHasComponent = isPresent(current.componentDirective);
|
||||
|
||||
if (directive.annotation instanceof Viewport) {
|
||||
if (!isTemplateElement) {
|
||||
throw new BaseException(`Viewport directives need to be placed on <template> elements or elements ` +
|
||||
`with template attribute - check ${current.elementDescription}`);
|
||||
} else if (isPresent(current.viewportDirective)) {
|
||||
throw new BaseException(`Only one viewport directive can be used per element - check ${current.elementDescription}`);
|
||||
}
|
||||
} else if (isTemplateElement) {
|
||||
throw new BaseException(`Only template directives are allowed on template elements - check ${current.elementDescription}`);
|
||||
|
||||
} else if (isComponent && alreadyHasComponent) {
|
||||
throw new BaseException(`Multiple component directives not allowed on the same element - check ${current.elementDescription}`);
|
||||
}
|
||||
|
||||
return directive;
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
import {int, isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
|
||||
import {Parser, ProtoChangeDetector} from 'angular2/change_detection';
|
||||
|
||||
import {DirectiveMetadata} from '../directive_metadata';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
import {dashCaseToCamelCase} from '../string_utils';
|
||||
import {setterFactory} from 'angular2/src/render/dom/compiler/property_setter_factory'
|
||||
|
||||
/**
|
||||
* Creates the ElementBinders and adds watches to the
|
||||
* ProtoChangeDetector.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#inheritedElementBinder
|
||||
*
|
||||
* Reads:
|
||||
* - (in parent) CompileElement#inheritedElementBinder
|
||||
* - CompileElement#hasBindings
|
||||
* - CompileElement#inheritedProtoView
|
||||
* - CompileElement#inheritedProtoElementInjector
|
||||
* - CompileElement#textNodeBindings
|
||||
* - CompileElement#propertyBindings
|
||||
* - CompileElement#eventBindings
|
||||
* - CompileElement#decoratorDirectives
|
||||
* - CompileElement#componentDirective
|
||||
* - CompileElement#viewportDirective
|
||||
*
|
||||
* Note: This actually only needs the CompileElements with the flags
|
||||
* `hasBindings` and `isViewRoot`,
|
||||
* and only needs the actual HTMLElement for the ones
|
||||
* with the flag `isViewRoot`.
|
||||
*/
|
||||
export class ElementBinderBuilder extends CompileStep {
|
||||
_parser:Parser;
|
||||
constructor(parser:Parser) {
|
||||
super();
|
||||
this._parser = parser;
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var elementBinder = null;
|
||||
var parentElementBinder = null;
|
||||
var distanceToParentBinder = this._getDistanceToParentBinder(parent, current);
|
||||
if (isPresent(parent)) {
|
||||
parentElementBinder = parent.inheritedElementBinder;
|
||||
}
|
||||
if (current.hasBindings) {
|
||||
var protoView = current.inheritedProtoView;
|
||||
var protoInjectorWasBuilt = isBlank(parent) ? true :
|
||||
current.inheritedProtoElementInjector !== parent.inheritedProtoElementInjector;
|
||||
|
||||
var currentProtoElementInjector = protoInjectorWasBuilt ?
|
||||
current.inheritedProtoElementInjector : null;
|
||||
|
||||
elementBinder = protoView.bindElement(parentElementBinder, distanceToParentBinder,
|
||||
currentProtoElementInjector, current.componentDirective, current.viewportDirective);
|
||||
current.distanceToParentBinder = 0;
|
||||
|
||||
if (isPresent(current.textNodeBindings)) {
|
||||
this._bindTextNodes(protoView, current);
|
||||
}
|
||||
if (isPresent(current.propertyBindings)) {
|
||||
this._bindElementProperties(protoView, current);
|
||||
}
|
||||
if (isPresent(current.eventBindings)) {
|
||||
this._bindEvents(protoView, current);
|
||||
}
|
||||
if (isPresent(current.contentTagSelector)) {
|
||||
elementBinder.contentTagSelector = current.contentTagSelector;
|
||||
}
|
||||
var directives = current.getAllDirectives();
|
||||
this._bindDirectiveProperties(directives, current);
|
||||
this._bindDirectiveEvents(directives, current);
|
||||
} else if (isPresent(parent)) {
|
||||
elementBinder = parentElementBinder;
|
||||
current.distanceToParentBinder = distanceToParentBinder;
|
||||
}
|
||||
current.inheritedElementBinder = elementBinder;
|
||||
}
|
||||
|
||||
_getDistanceToParentBinder(parent, current) {
|
||||
return isPresent(parent) ? parent.distanceToParentBinder + 1 : 0;
|
||||
}
|
||||
|
||||
_bindTextNodes(protoView, compileElement) {
|
||||
MapWrapper.forEach(compileElement.textNodeBindings, (expression, indexInParent) => {
|
||||
protoView.bindTextNode(indexInParent, expression);
|
||||
});
|
||||
}
|
||||
|
||||
_bindElementProperties(protoView, compileElement) {
|
||||
MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => {
|
||||
var setterFn = setterFactory(property);
|
||||
protoView.bindElementProperty(expression.ast, property, setterFn);
|
||||
});
|
||||
}
|
||||
|
||||
_bindEvents(protoView, compileElement) {
|
||||
MapWrapper.forEach(compileElement.eventBindings, (expression, eventName) => {
|
||||
protoView.bindEvent(eventName, expression);
|
||||
});
|
||||
}
|
||||
|
||||
_bindDirectiveEvents(directives: List<DirectiveMetadata>, compileElement: CompileElement) {
|
||||
for (var directiveIndex = 0; directiveIndex < directives.length; directiveIndex++) {
|
||||
var directive = directives[directiveIndex];
|
||||
var annotation = directive.annotation;
|
||||
if (isBlank(annotation.events)) continue;
|
||||
var protoView = compileElement.inheritedProtoView;
|
||||
StringMapWrapper.forEach(annotation.events, (action, eventName) => {
|
||||
var expression = this._parser.parseAction(action, compileElement.elementDescription);
|
||||
protoView.bindEvent(eventName, expression, directiveIndex);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_bindDirectiveProperties(directives: List<DirectiveMetadata>,
|
||||
compileElement: CompileElement) {
|
||||
var protoView = compileElement.inheritedProtoView;
|
||||
|
||||
for (var directiveIndex = 0; directiveIndex < directives.length; directiveIndex++) {
|
||||
var directive = ListWrapper.get(directives, directiveIndex);
|
||||
var annotation = directive.annotation;
|
||||
if (isBlank(annotation.bind)) continue;
|
||||
StringMapWrapper.forEach(annotation.bind, (bindConfig, dirProp) => {
|
||||
var pipes = this._splitBindConfig(bindConfig);
|
||||
var elProp = ListWrapper.removeAt(pipes, 0);
|
||||
|
||||
var bindingAst = isPresent(compileElement.propertyBindings) ?
|
||||
MapWrapper.get(compileElement.propertyBindings, dashCaseToCamelCase(elProp)) :
|
||||
null;
|
||||
|
||||
if (isBlank(bindingAst)) {
|
||||
var attributeValue = MapWrapper.get(compileElement.attrs(), elProp);
|
||||
if (isPresent(attributeValue)) {
|
||||
bindingAst = this._parser.wrapLiteralPrimitive(attributeValue, compileElement.elementDescription);
|
||||
}
|
||||
}
|
||||
|
||||
// Bindings are optional, so this binding only needs to be set up if an expression is given.
|
||||
if (isPresent(bindingAst)) {
|
||||
var fullExpAstWithBindPipes = this._parser.addPipes(bindingAst, pipes);
|
||||
protoView.bindDirectiveProperty(
|
||||
directiveIndex,
|
||||
fullExpAstWithBindPipes,
|
||||
dirProp,
|
||||
reflector.setter(dashCaseToCamelCase(dirProp))
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_splitBindConfig(bindConfig:string) {
|
||||
return ListWrapper.map(bindConfig.split('|'), (s) => s.trim());
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
||||
const NG_BINDING_CLASS = 'ng-binding';
|
||||
|
||||
/**
|
||||
* Marks elements that have bindings with a css class
|
||||
* and sets the CompileElement.hasBindings flag.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#hasBindings
|
||||
*
|
||||
* Reads:
|
||||
* - CompileElement#textNodeBindings
|
||||
* - CompileElement#propertyBindings
|
||||
* - CompileElement#variableBindings
|
||||
* - CompileElement#eventBindings
|
||||
* - CompileElement#decoratorDirectives
|
||||
* - CompileElement#componentDirective
|
||||
* - CompileElement#viewportDirective
|
||||
*/
|
||||
export class ElementBindingMarker extends CompileStep {
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
if (current.ignoreBindings) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hasBindings =
|
||||
(isPresent(current.textNodeBindings) && MapWrapper.size(current.textNodeBindings)>0) ||
|
||||
(isPresent(current.propertyBindings) && MapWrapper.size(current.propertyBindings)>0) ||
|
||||
(isPresent(current.variableBindings) && MapWrapper.size(current.variableBindings)>0) ||
|
||||
(isPresent(current.eventBindings) && MapWrapper.size(current.eventBindings)>0) ||
|
||||
(isPresent(current.decoratorDirectives) && current.decoratorDirectives.length > 0) ||
|
||||
isPresent(current.viewportDirective) ||
|
||||
isPresent(current.componentDirective) ||
|
||||
isPresent(current.contentTagSelector);
|
||||
|
||||
if (hasBindings) {
|
||||
var element = current.element;
|
||||
DOM.addClass(element, NG_BINDING_CLASS);
|
||||
current.hasBindings = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
import {isPresent, isBlank, RegExpWrapper, BaseException} from 'angular2/src/facade/lang';
|
||||
import {MapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {Parser, AST, ExpressionWithSource} from 'angular2/change_detection';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
||||
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
|
||||
// Group 1 = "bind"
|
||||
// Group 2 = "var"
|
||||
// Group 3 = "on"
|
||||
// Group 4 = the identifier after "bind", "var", or "on"
|
||||
// Group 5 = idenitifer inside square braces
|
||||
// Group 6 = identifier inside parenthesis
|
||||
// Group 7 = "#"
|
||||
// Group 8 = identifier after "#"
|
||||
var BIND_NAME_REGEXP = RegExpWrapper.create(
|
||||
'^(?:(?:(?:(bind)|(var)|(on))-(.+))|\\[([^\\]]+)\\]|\\(([^\\)]+)\\)|(#)(.+))$');
|
||||
|
||||
/**
|
||||
* Parses the property bindings on a single element.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#propertyBindings
|
||||
* - CompileElement#eventBindings
|
||||
* - CompileElement#variableBindings
|
||||
*/
|
||||
export class PropertyBindingParser extends CompileStep {
|
||||
_parser:Parser;
|
||||
constructor(parser:Parser) {
|
||||
super();
|
||||
this._parser = parser;
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
if (current.ignoreBindings) {
|
||||
return;
|
||||
}
|
||||
|
||||
var attrs = current.attrs();
|
||||
var newAttrs = MapWrapper.create();
|
||||
var desc = current.elementDescription;
|
||||
|
||||
MapWrapper.forEach(attrs, (attrValue, attrName) => {
|
||||
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
|
||||
if (isPresent(bindParts)) {
|
||||
if (isPresent(bindParts[1])) {
|
||||
// match: bind-prop
|
||||
current.addPropertyBinding(bindParts[4], this._parseBinding(attrValue, desc));
|
||||
MapWrapper.set(newAttrs, bindParts[4], attrValue);
|
||||
} else if (isPresent(bindParts[2]) || isPresent(bindParts[7])) {
|
||||
// match: var-name / var-name="iden" / #name / #name="iden"
|
||||
var identifier = (isPresent(bindParts[4]) && bindParts[4] !== '') ?
|
||||
bindParts[4] : bindParts[8];
|
||||
var value = attrValue == '' ? '\$implicit' : attrValue;
|
||||
current.addVariableBinding(identifier, value);
|
||||
MapWrapper.set(newAttrs, identifier, value);
|
||||
} else if (isPresent(bindParts[3])) {
|
||||
// match: on-event
|
||||
current.addEventBinding(bindParts[4], this._parseAction(attrValue, desc));
|
||||
} else if (isPresent(bindParts[5])) {
|
||||
// match: [prop]
|
||||
current.addPropertyBinding(bindParts[5], this._parseBinding(attrValue, desc));
|
||||
MapWrapper.set(newAttrs, bindParts[5], attrValue);
|
||||
} else if (isPresent(bindParts[6])) {
|
||||
// match: (event)
|
||||
current.addEventBinding(bindParts[6], this._parseAction(attrValue, desc));
|
||||
}
|
||||
} else {
|
||||
var ast = this._parseInterpolation(attrValue, desc);
|
||||
if (isPresent(ast)) {
|
||||
current.addPropertyBinding(attrName, ast);
|
||||
} else {
|
||||
current.addAttribute(attrName, attrValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
MapWrapper.forEach(newAttrs, (attrValue, attrName) => {
|
||||
MapWrapper.set(attrs, attrName, attrValue);
|
||||
});
|
||||
}
|
||||
|
||||
_parseInterpolation(input:string, location:string):AST {
|
||||
return this._parser.parseInterpolation(input, location);
|
||||
}
|
||||
|
||||
_parseBinding(input:string, location:string):AST {
|
||||
return this._parser.parseBinding(input, location);
|
||||
}
|
||||
|
||||
_parseAction(input:string, location:string):AST {
|
||||
return this._parser.parseAction(input, location);
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {ProtoElementInjector, ComponentKeyMetaData, DirectiveBinding} from '../element_injector';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
import {DirectiveMetadata} from '../directive_metadata';
|
||||
|
||||
/**
|
||||
* Creates the ProtoElementInjectors.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#inheritedProtoElementInjector
|
||||
* - CompileElement#distanceToParentInjector
|
||||
*
|
||||
* Reads:
|
||||
* - (in parent) CompileElement#inheritedProtoElementInjector
|
||||
* - (in parent) CompileElement#distanceToParentInjector
|
||||
* - CompileElement#isViewRoot
|
||||
* - CompileElement#inheritedProtoView
|
||||
* - CompileElement#decoratorDirectives
|
||||
* - CompileElement#componentDirective
|
||||
* - CompileElement#viewportDirective
|
||||
*/
|
||||
export class ProtoElementInjectorBuilder extends CompileStep {
|
||||
// public so that we can overwrite it in tests
|
||||
internalCreateProtoElementInjector(parent, index, directives, firstBindingIsComponent, distance) {
|
||||
return new ProtoElementInjector(parent, index, directives, firstBindingIsComponent, distance);
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var distanceToParentInjector = this._getDistanceToParentInjector(parent, current);
|
||||
var parentProtoElementInjector = this._getParentProtoElementInjector(parent, current);
|
||||
var injectorBindings = ListWrapper.map(current.getAllDirectives(), this._createBinding);
|
||||
|
||||
// Create a protoElementInjector for any element that either has bindings *or* has one
|
||||
// or more var- defined. Elements with a var- defined need a their own element injector
|
||||
// so that, when hydrating, $implicit can be set to the element.
|
||||
if (injectorBindings.length > 0 || isPresent(current.variableBindings)) {
|
||||
var protoView = current.inheritedProtoView;
|
||||
var hasComponent = isPresent(current.componentDirective);
|
||||
|
||||
current.inheritedProtoElementInjector = this.internalCreateProtoElementInjector(
|
||||
parentProtoElementInjector, protoView.elementBinders.length, injectorBindings,
|
||||
hasComponent, distanceToParentInjector
|
||||
);
|
||||
current.distanceToParentInjector = 0;
|
||||
|
||||
// Viewport directives are treated differently than other element with var- definitions.
|
||||
if (isPresent(current.variableBindings) && !isPresent(current.viewportDirective)) {
|
||||
current.inheritedProtoElementInjector.exportComponent = hasComponent;
|
||||
current.inheritedProtoElementInjector.exportElement = !hasComponent;
|
||||
|
||||
// experiment
|
||||
var exportImplicitName = MapWrapper.get(current.variableBindings, '\$implicit');
|
||||
if (isPresent(exportImplicitName)) {
|
||||
current.inheritedProtoElementInjector.exportImplicitName = exportImplicitName;
|
||||
}
|
||||
}
|
||||
current.inheritedProtoElementInjector.attributes = current.attributes;
|
||||
|
||||
} else {
|
||||
current.inheritedProtoElementInjector = parentProtoElementInjector;
|
||||
current.distanceToParentInjector = distanceToParentInjector;
|
||||
}
|
||||
}
|
||||
|
||||
_getDistanceToParentInjector(parent, current) {
|
||||
return isPresent(parent) ? parent.distanceToParentInjector + 1 : 0;
|
||||
}
|
||||
|
||||
_getParentProtoElementInjector(parent, current) {
|
||||
if (isPresent(parent) && !current.isViewRoot) {
|
||||
return parent.inheritedProtoElementInjector;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_createBinding(d:DirectiveMetadata): DirectiveBinding {
|
||||
return DirectiveBinding.createFromType(d.type, d.annotation);
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
import {isPresent, BaseException} from 'angular2/src/facade/lang';
|
||||
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {ProtoView} from '../view';
|
||||
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';
|
||||
import {DirectiveMetadata} from '../directive_metadata';
|
||||
import {Component} from 'angular2/src/core/annotations/annotations';
|
||||
|
||||
/**
|
||||
* Creates ProtoViews and forwards variable bindings from parent to children.
|
||||
*
|
||||
* Fills:
|
||||
* - (in parent): CompileElement#inheritedElementBinder.nestedProtoView
|
||||
* - CompileElement#inheritedProtoView
|
||||
*
|
||||
* Reads:
|
||||
* - (in parent): CompileElement#inheritedProtoView
|
||||
* - (in parent): CompileElement#variableBindings
|
||||
* - CompileElement#isViewRoot
|
||||
*/
|
||||
export class ProtoViewBuilder extends CompileStep {
|
||||
changeDetection:ChangeDetection;
|
||||
_shadowDomStrategy:ShadowDomStrategy;
|
||||
_compiledComponent:DirectiveMetadata;
|
||||
|
||||
constructor(compiledComponent:DirectiveMetadata,
|
||||
changeDetection:ChangeDetection, shadowDomStrategy:ShadowDomStrategy) {
|
||||
super();
|
||||
this._compiledComponent = compiledComponent;
|
||||
this._shadowDomStrategy = shadowDomStrategy;
|
||||
this.changeDetection = changeDetection;
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var inheritedProtoView = null;
|
||||
if (current.isViewRoot) {
|
||||
var componentAnnotation:Component = this._compiledComponent.annotation;
|
||||
var protoChangeDetector = this.changeDetection.createProtoChangeDetector('dummy',
|
||||
componentAnnotation.changeDetection);
|
||||
|
||||
inheritedProtoView = new ProtoView(current.element, protoChangeDetector,
|
||||
this._shadowDomStrategy, this._getParentProtoView(parent));
|
||||
|
||||
if (isPresent(parent)) {
|
||||
if (isPresent(parent.inheritedElementBinder.nestedProtoView)) {
|
||||
throw new BaseException('Only one nested view per element is allowed');
|
||||
}
|
||||
parent.inheritedElementBinder.nestedProtoView = inheritedProtoView;
|
||||
|
||||
// When current is a view root, the variable bindings are set to the *nested* proto view.
|
||||
// The root view conceptually signifies a new "block scope" (the nested view), to which
|
||||
// the variables are bound.
|
||||
if (isPresent(parent.variableBindings)) {
|
||||
MapWrapper.forEach(parent.variableBindings, (mappedName, varName) => {
|
||||
inheritedProtoView.bindVariable(varName, mappedName);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (isPresent(parent)) {
|
||||
inheritedProtoView = parent.inheritedProtoView;
|
||||
}
|
||||
|
||||
// The view's locals needs to have a full set of variable names at construction time
|
||||
// in order to prevent new variables from being set later in the lifecycle. Since we don't want
|
||||
// to actually create variable bindings for the $implicit bindings, add to the
|
||||
// protoLocals manually.
|
||||
if (isPresent(current.variableBindings)) {
|
||||
MapWrapper.forEach(current.variableBindings, (mappedName, varName) => {
|
||||
MapWrapper.set(inheritedProtoView.protoLocals, mappedName, null);
|
||||
});
|
||||
}
|
||||
|
||||
current.inheritedProtoView = inheritedProtoView;
|
||||
}
|
||||
|
||||
_getParentProtoView(parent:CompileElement) {
|
||||
return isPresent(parent) ? parent.inheritedProtoView : null;
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
import {RegExpWrapper, StringWrapper, isPresent} from 'angular2/src/facade/lang';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {Parser} from 'angular2/change_detection';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
|
||||
/**
|
||||
* Parses interpolations in direct text child nodes of the current element.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#textNodeBindings
|
||||
*/
|
||||
export class TextInterpolationParser extends CompileStep {
|
||||
_parser:Parser;
|
||||
constructor(parser:Parser) {
|
||||
super();
|
||||
this._parser = parser;
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
if (!current.compileChildren || current.ignoreBindings) {
|
||||
return;
|
||||
}
|
||||
var element = current.element;
|
||||
var childNodes = DOM.childNodes(DOM.templateAwareRoot(element));
|
||||
for (var i=0; i<childNodes.length; i++) {
|
||||
var node = childNodes[i];
|
||||
if (DOM.isTextNode(node)) {
|
||||
this._parseTextNode(current, node, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_parseTextNode(pipelineElement, node, nodeIndex) {
|
||||
var ast = this._parser.parseInterpolation(DOM.nodeValue(node), pipelineElement.elementDescription);
|
||||
if (isPresent(ast)) {
|
||||
DOM.setText(node, ' ');
|
||||
pipelineElement.addTextNodeBinding(nodeIndex, ast);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
import {isBlank, isPresent, BaseException} from 'angular2/src/facade/lang';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {Parser} from 'angular2/change_detection';
|
||||
|
||||
import {CompileStep} from './compile_step';
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileControl} from './compile_control';
|
||||
import {StringWrapper} from 'angular2/src/facade/lang';
|
||||
|
||||
/**
|
||||
* Splits views at `<template>` elements or elements with `template` attribute:
|
||||
* For `<template>` elements:
|
||||
* - moves the content into a new and disconnected `<template>` element
|
||||
* that is marked as view root.
|
||||
*
|
||||
* For elements with a `template` attribute:
|
||||
* - replaces the element with an empty `<template>` element,
|
||||
* parses the content of the `template` attribute and adds the information to that
|
||||
* `<template>` element. Marks the elements as view root.
|
||||
*
|
||||
* Note: In both cases the root of the nested view is disconnected from its parent element.
|
||||
* This is needed for browsers that don't support the `<template>` element
|
||||
* as we want to do locate elements with bindings using `getElementsByClassName` later on,
|
||||
* which should not descend into the nested view.
|
||||
*
|
||||
* Fills:
|
||||
* - CompileElement#isViewRoot
|
||||
* - CompileElement#variableBindings
|
||||
* - CompileElement#propertyBindings
|
||||
*/
|
||||
export class ViewSplitter extends CompileStep {
|
||||
_parser:Parser;
|
||||
constructor(parser:Parser) {
|
||||
super();
|
||||
this._parser = parser;
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var attrs = current.attrs();
|
||||
var templateBindings = MapWrapper.get(attrs, 'template');
|
||||
var hasTemplateBinding = isPresent(templateBindings);
|
||||
|
||||
// look for template shortcuts such as *if="condition" and treat them as template="if condition"
|
||||
MapWrapper.forEach(attrs, (attrValue, attrName) => {
|
||||
if (StringWrapper.startsWith(attrName, '*')) {
|
||||
var key = StringWrapper.substring(attrName, 1); // remove the star
|
||||
if (hasTemplateBinding) {
|
||||
// 2nd template binding detected
|
||||
throw new BaseException(`Only one template directive per element is allowed: ` +
|
||||
`${templateBindings} and ${key} cannot be used simultaneously ` +
|
||||
`in ${current.elementDescription}`);
|
||||
} else {
|
||||
templateBindings = (attrValue.length == 0) ? key : key + ' ' + attrValue;
|
||||
hasTemplateBinding = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (isBlank(parent)) {
|
||||
current.isViewRoot = true;
|
||||
} else {
|
||||
if (DOM.isTemplateElement(current.element)) {
|
||||
if (!current.isViewRoot) {
|
||||
var viewRoot = new CompileElement(DOM.createTemplate(''));
|
||||
var currentElement = current.element;
|
||||
var viewRootElement = viewRoot.element;
|
||||
this._moveChildNodes(DOM.content(currentElement), DOM.content(viewRootElement));
|
||||
// viewRoot doesn't appear in the original template, so we associate
|
||||
// the current element description to get a more meaningful message in case of error
|
||||
viewRoot.elementDescription = current.elementDescription;
|
||||
viewRoot.isViewRoot = true;
|
||||
control.addChild(viewRoot);
|
||||
}
|
||||
} else {
|
||||
if (hasTemplateBinding) {
|
||||
var newParent = new CompileElement(DOM.createTemplate(''));
|
||||
// newParent doesn't appear in the original template, so we associate
|
||||
// the current element description to get a more meaningful message in case of error
|
||||
newParent.elementDescription = current.elementDescription;
|
||||
current.isViewRoot = true;
|
||||
this._parseTemplateBindings(templateBindings, newParent);
|
||||
this._addParentElement(current.element, newParent.element);
|
||||
|
||||
control.addParent(newParent);
|
||||
DOM.remove(current.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_moveChildNodes(source, target) {
|
||||
var next = DOM.firstChild(source);
|
||||
while (isPresent(next)) {
|
||||
DOM.appendChild(target, next);
|
||||
next = DOM.firstChild(source);
|
||||
}
|
||||
}
|
||||
|
||||
_addParentElement(currentElement, newParentElement) {
|
||||
DOM.insertBefore(currentElement, newParentElement);
|
||||
DOM.appendChild(newParentElement, currentElement);
|
||||
}
|
||||
|
||||
_parseTemplateBindings(templateBindings:string, compileElement:CompileElement) {
|
||||
var bindings = this._parser.parseTemplateBindings(templateBindings, compileElement.elementDescription);
|
||||
for (var i=0; i<bindings.length; i++) {
|
||||
var binding = bindings[i];
|
||||
if (binding.keyIsVar) {
|
||||
compileElement.addVariableBinding(binding.key, binding.name);
|
||||
MapWrapper.set(compileElement.attrs(), binding.key, binding.name);
|
||||
} else if (isPresent(binding.expression)) {
|
||||
compileElement.addPropertyBinding(binding.key, binding.expression);
|
||||
MapWrapper.set(compileElement.attrs(), binding.key, binding.expression.source);
|
||||
} else {
|
||||
DOM.setAttribute(compileElement.element, binding.key, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
200
modules/angular2/src/core/compiler/proto_view_factory.js
vendored
Normal file
200
modules/angular2/src/core/compiler/proto_view_factory.js
vendored
Normal file
@ -0,0 +1,200 @@
|
||||
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
|
||||
import {ChangeDetection} from 'angular2/change_detection';
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
import {Component, Viewport, DynamicComponent} from '../annotations/annotations';
|
||||
|
||||
import * as renderApi from 'angular2/src/render/api';
|
||||
import {DirectDomProtoViewRef} from 'angular2/src/render/dom/direct_dom_renderer';
|
||||
import {ProtoView} from './view';
|
||||
import {ProtoElementInjector, DirectiveBinding} from './element_injector';
|
||||
|
||||
export class ProtoViewFactory {
|
||||
_changeDetection:ChangeDetection;
|
||||
_shadowDomStrategy:ShadowDomStrategy;
|
||||
|
||||
constructor(changeDetection, shadowDomStrategy) {
|
||||
this._changeDetection = changeDetection;
|
||||
this._shadowDomStrategy = shadowDomStrategy;
|
||||
}
|
||||
|
||||
createProtoView(componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
|
||||
return this._createProtoView(null, componentAnnotation, renderProtoView, directives);
|
||||
}
|
||||
|
||||
_createProtoView(parent:ProtoView, componentAnnotation:Component, renderProtoView: renderApi.ProtoView, directives:List<DirectiveBinding>):ProtoView {
|
||||
var protoChangeDetector = this._changeDetection.createProtoChangeDetector('dummy',
|
||||
componentAnnotation.changeDetection);
|
||||
var domProtoView = this._getDomProtoView(renderProtoView.render);
|
||||
var protoView = new ProtoView(domProtoView.element, protoChangeDetector,
|
||||
this._shadowDomStrategy, parent);
|
||||
|
||||
for (var i=0; i<renderProtoView.elementBinders.length; i++) {
|
||||
var renderElementBinder = renderProtoView.elementBinders[i];
|
||||
var domElementBinder = domProtoView.elementBinders[i];
|
||||
var sortedDirectives = new SortedDirectives(renderElementBinder.directives, directives);
|
||||
var parentPeiWithDistance = this._findParentProtoElementInjectorWithDistance(
|
||||
i, protoView.elementBinders, renderProtoView.elementBinders
|
||||
);
|
||||
var protoElementInjector = this._createProtoElementInjector(
|
||||
i, parentPeiWithDistance,
|
||||
sortedDirectives, renderElementBinder
|
||||
);
|
||||
var elementBinder = this._createElementBinder(
|
||||
protoView, renderElementBinder, domElementBinder, protoElementInjector, sortedDirectives
|
||||
);
|
||||
this._createDirectiveBinders(protoView, sortedDirectives);
|
||||
if (isPresent(renderElementBinder.nestedProtoView)) {
|
||||
elementBinder.nestedProtoView = this._createProtoView(protoView, componentAnnotation, renderElementBinder.nestedProtoView, directives);
|
||||
}
|
||||
}
|
||||
MapWrapper.forEach(renderProtoView.variableBindings, (mappedName, varName) => {
|
||||
protoView.bindVariable(varName, mappedName);
|
||||
});
|
||||
return protoView;
|
||||
}
|
||||
|
||||
// This method is needed to make DartAnalyzer happy
|
||||
_getDomProtoView(protoViewRef: DirectDomProtoViewRef) {
|
||||
return protoViewRef.delegate;
|
||||
}
|
||||
|
||||
_findParentProtoElementInjectorWithDistance(binderIndex, elementBinders, renderElementBinders) {
|
||||
var distance = 0;
|
||||
do {
|
||||
var renderElementBinder = renderElementBinders[binderIndex];
|
||||
binderIndex = renderElementBinder.parentIndex;
|
||||
if (binderIndex !== -1) {
|
||||
distance += renderElementBinder.distanceToParent;
|
||||
var elementBinder = elementBinders[binderIndex];
|
||||
if (isPresent(elementBinder.protoElementInjector)) {
|
||||
return new ParentProtoElementInjectorWithDistance(elementBinder.protoElementInjector, distance);
|
||||
}
|
||||
}
|
||||
} while (binderIndex !== -1);
|
||||
return new ParentProtoElementInjectorWithDistance(null, -1);
|
||||
}
|
||||
|
||||
_createProtoElementInjector(binderIndex, parentPeiWithDistance, sortedDirectives, renderElementBinder) {
|
||||
var protoElementInjector = null;
|
||||
// Create a protoElementInjector for any element that either has bindings *or* has one
|
||||
// or more var- defined. Elements with a var- defined need a their own element injector
|
||||
// so that, when hydrating, $implicit can be set to the element.
|
||||
var hasVariables = MapWrapper.size(renderElementBinder.variableBindings) > 0;
|
||||
if (sortedDirectives.directives.length > 0 || hasVariables) {
|
||||
protoElementInjector = new ProtoElementInjector(
|
||||
parentPeiWithDistance.protoElementInjector, binderIndex,
|
||||
sortedDirectives.directives,
|
||||
isPresent(sortedDirectives.componentDirective), parentPeiWithDistance.distance
|
||||
);
|
||||
protoElementInjector.attributes = renderElementBinder.readAttributes;
|
||||
// Viewport directives are treated differently than other element with var- definitions.
|
||||
if (hasVariables && !isPresent(sortedDirectives.viewportDirective)) {
|
||||
protoElementInjector.exportComponent = isPresent(sortedDirectives.componentDirective);
|
||||
protoElementInjector.exportElement = isBlank(sortedDirectives.componentDirective);
|
||||
|
||||
// experiment
|
||||
var exportImplicitName = MapWrapper.get(renderElementBinder.variableBindings, '\$implicit');
|
||||
if (isPresent(exportImplicitName)) {
|
||||
protoElementInjector.exportImplicitName = exportImplicitName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return protoElementInjector;
|
||||
}
|
||||
|
||||
_createElementBinder(protoView, renderElementBinder, domElementBinder, protoElementInjector, sortedDirectives) {
|
||||
var parent = null;
|
||||
if (renderElementBinder.parentIndex !== -1) {
|
||||
parent = protoView.elementBinders[renderElementBinder.parentIndex];
|
||||
}
|
||||
var elBinder = protoView.bindElement(
|
||||
parent,
|
||||
renderElementBinder.distanceToParent,
|
||||
protoElementInjector,
|
||||
sortedDirectives.componentDirective,
|
||||
sortedDirectives.viewportDirective
|
||||
);
|
||||
elBinder.contentTagSelector = domElementBinder.contentTagSelector;
|
||||
// text nodes
|
||||
for (var i=0; i<renderElementBinder.textBindings.length; i++) {
|
||||
protoView.bindTextNode(domElementBinder.textNodeIndices[i], renderElementBinder.textBindings[i].ast);
|
||||
}
|
||||
// element properties
|
||||
MapWrapper.forEach(renderElementBinder.propertyBindings, (astWithSource, propertyName) => {
|
||||
protoView.bindElementProperty(astWithSource.ast, propertyName, MapWrapper.get(domElementBinder.propertySetters, propertyName));
|
||||
});
|
||||
// events
|
||||
MapWrapper.forEach(renderElementBinder.eventBindings, (astWithSource, eventName) => {
|
||||
protoView.bindEvent(eventName, astWithSource.ast, -1);
|
||||
});
|
||||
// variables
|
||||
// The view's locals needs to have a full set of variable names at construction time
|
||||
// in order to prevent new variables from being set later in the lifecycle. Since we don't want
|
||||
// to actually create variable bindings for the $implicit bindings, add to the
|
||||
// protoLocals manually.
|
||||
MapWrapper.forEach(renderElementBinder.variableBindings, (mappedName, varName) => {
|
||||
MapWrapper.set(protoView.protoLocals, mappedName, null);
|
||||
});
|
||||
return elBinder;
|
||||
}
|
||||
|
||||
_createDirectiveBinders(protoView, sortedDirectives) {
|
||||
for (var i=0; i<sortedDirectives.renderDirectives.length; i++) {
|
||||
var renderDirectiveMetadata = sortedDirectives.renderDirectives[i];
|
||||
// directive properties
|
||||
MapWrapper.forEach(renderDirectiveMetadata.propertyBindings, (astWithSource, propertyName) => {
|
||||
// TODO: these setters should eventually be created by change detection, to make
|
||||
// it monomorphic!
|
||||
var setter = reflector.setter(propertyName);
|
||||
protoView.bindDirectiveProperty(i, astWithSource.ast, propertyName, setter);
|
||||
});
|
||||
// directive events
|
||||
MapWrapper.forEach(renderDirectiveMetadata.eventBindings, (astWithSource, eventName) => {
|
||||
protoView.bindEvent(eventName, astWithSource.ast, i);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SortedDirectives {
|
||||
componentDirective: DirectiveBinding;
|
||||
viewportDirective: DirectiveBinding;
|
||||
renderDirectives: List<renderApi.DirectiveMetadata>;
|
||||
directives: List<DirectiveBinding>;
|
||||
|
||||
constructor(renderDirectives, allDirectives) {
|
||||
this.renderDirectives = [];
|
||||
this.directives = [];
|
||||
this.viewportDirective = null;
|
||||
this.componentDirective = null;
|
||||
ListWrapper.forEach(renderDirectives, (renderDirectiveMetadata) => {
|
||||
var directiveBinding = allDirectives[renderDirectiveMetadata.directiveIndex];
|
||||
if ((directiveBinding.annotation instanceof Component) || (directiveBinding.annotation instanceof DynamicComponent)) {
|
||||
// component directives need to be the first binding in ElementInjectors!
|
||||
this.componentDirective = directiveBinding;
|
||||
ListWrapper.insert(this.renderDirectives, 0, renderDirectiveMetadata);
|
||||
ListWrapper.insert(this.directives, 0, directiveBinding);
|
||||
} else {
|
||||
if (directiveBinding.annotation instanceof Viewport) {
|
||||
this.viewportDirective = directiveBinding;
|
||||
}
|
||||
ListWrapper.push(this.renderDirectives, renderDirectiveMetadata);
|
||||
ListWrapper.push(this.directives, directiveBinding);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class ParentProtoElementInjectorWithDistance {
|
||||
protoElementInjector:ProtoElementInjector;
|
||||
distance:number;
|
||||
constructor(protoElementInjector:ProtoElementInjector, distance:number) {
|
||||
this.protoElementInjector = protoElementInjector;
|
||||
this.distance = distance;
|
||||
}
|
||||
}
|
@ -1,75 +1,30 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {Type, isBlank, isPresent, int, StringWrapper, assertionsEnabled} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, MapWrapper, Map} from 'angular2/src/facade/collection';
|
||||
import {PromiseWrapper} from 'angular2/src/facade/async';
|
||||
|
||||
import {stringify} from 'angular2/src/facade/lang';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import * as viewModule from './view';
|
||||
|
||||
import {LightDom} from './shadow_dom_emulation/light_dom';
|
||||
import {ShadowCss} from 'angular2/src/render/dom/shadow_dom/shadow_css';
|
||||
|
||||
import {StyleInliner} from 'angular2/src/render/dom/shadow_dom/style_inliner';
|
||||
import {StyleUrlResolver} from 'angular2/src/render/dom/shadow_dom/style_url_resolver';
|
||||
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
|
||||
import * as NS from './pipeline/compile_step';
|
||||
import {CompileElement} from './pipeline/compile_element';
|
||||
import {CompileControl} from './pipeline/compile_control';
|
||||
|
||||
var _EMPTY_STEP;
|
||||
|
||||
// Note: fill _EMPTY_STEP to prevent
|
||||
// problems from cyclic dependencies
|
||||
function _emptyStep() {
|
||||
if (isBlank(_EMPTY_STEP)) {
|
||||
_EMPTY_STEP = new _EmptyCompileStep();
|
||||
}
|
||||
return _EMPTY_STEP;
|
||||
}
|
||||
// temporal import while we migrated the views over
|
||||
import * as sds from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
|
||||
import * as nsds from 'angular2/src/render/dom/shadow_dom/native_shadow_dom_strategy';
|
||||
import * as eusds from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
|
||||
import * as essds from 'angular2/src/render/dom/shadow_dom/emulated_scoped_shadow_dom_strategy';
|
||||
|
||||
/**
|
||||
* @publicModule angular2/template
|
||||
*/
|
||||
export class ShadowDomStrategy {
|
||||
render: sds.ShadowDomStrategy;
|
||||
|
||||
attachTemplate(el, view:viewModule.View) {}
|
||||
constructLightDom(lightDomView:viewModule.View, shadowDomView:viewModule.View, el): LightDom { return null; }
|
||||
|
||||
/**
|
||||
* An optional step that can modify the template style elements.
|
||||
*
|
||||
* @param {DirectiveMetadata} cmpMetadata
|
||||
* @param {string} templateUrl the template base URL
|
||||
* @returns {CompileStep} a compile step to append to the compiler pipeline
|
||||
*/
|
||||
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): NS.CompileStep {
|
||||
return _emptyStep();
|
||||
shimAppElement(componentType, insertionElement) {
|
||||
this.render.processElement(null, stringify(componentType), insertionElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* An optional step that can modify the template elements (style elements exlcuded).
|
||||
*
|
||||
* This step could be used to modify the template in order to scope the styles.
|
||||
*
|
||||
* @param {DirectiveMetadata} cmpMetadata
|
||||
* @returns {CompileStep} a compile step to append to the compiler pipeline
|
||||
*/
|
||||
getTemplateCompileStep(cmpMetadata: DirectiveMetadata): NS.CompileStep { return _emptyStep(); }
|
||||
|
||||
/**
|
||||
* The application element does not go through the compiler pipeline.
|
||||
*
|
||||
* This methods is called when the root ProtoView is created and to optionnaly update the
|
||||
* application root element.
|
||||
*
|
||||
* @see ProtoView.createRootProtoView
|
||||
*
|
||||
* @param {DirectiveMetadata} cmpMetadata
|
||||
* @param element
|
||||
*/
|
||||
shimAppElement(cmpMetadata: DirectiveMetadata, element) {}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,18 +35,15 @@ export class ShadowDomStrategy {
|
||||
* 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
|
||||
*
|
||||
*
|
||||
* @publicModule angular2/template
|
||||
*/
|
||||
@Injectable()
|
||||
export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
|
||||
_styleUrlResolver: StyleUrlResolver;
|
||||
_styleHost;
|
||||
|
||||
constructor(styleUrlResolver: StyleUrlResolver, styleHost) {
|
||||
super();
|
||||
this._styleUrlResolver = styleUrlResolver;
|
||||
this._styleHost = styleHost;
|
||||
this.render = new eusds.EmulatedUnscopedShadowDomStrategy(styleUrlResolver, styleHost);
|
||||
}
|
||||
|
||||
attachTemplate(el, view:viewModule.View) {
|
||||
@ -102,15 +54,6 @@ export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
|
||||
constructLightDom(lightDomView:viewModule.View, shadowDomView:viewModule.View, el): LightDom {
|
||||
return new LightDom(lightDomView, shadowDomView, el);
|
||||
}
|
||||
|
||||
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): NS.CompileStep {
|
||||
return new _EmulatedUnscopedCssStep(cmpMetadata, templateUrl, this._styleUrlResolver,
|
||||
this._styleHost);
|
||||
}
|
||||
|
||||
getTemplateCompileStep(cmpMetadata: DirectiveMetadata): NS.CompileStep {
|
||||
return new _BaseEmulatedShadowDomStep();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -124,31 +67,24 @@ export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
|
||||
* - 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.
|
||||
*
|
||||
*
|
||||
* @publicModule angular2/template
|
||||
*/
|
||||
@Injectable()
|
||||
export class EmulatedScopedShadowDomStrategy extends EmulatedUnscopedShadowDomStrategy {
|
||||
_styleInliner: StyleInliner;
|
||||
export class EmulatedScopedShadowDomStrategy extends ShadowDomStrategy {
|
||||
|
||||
constructor(styleInliner: StyleInliner, styleUrlResolver: StyleUrlResolver, styleHost) {
|
||||
super(styleUrlResolver, styleHost);
|
||||
this._styleInliner = styleInliner;
|
||||
super();
|
||||
this.render = new essds.EmulatedScopedShadowDomStrategy(styleInliner, styleUrlResolver, styleHost);
|
||||
}
|
||||
|
||||
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): NS.CompileStep {
|
||||
return new _EmulatedScopedCssStep(cmpMetadata, templateUrl, this._styleInliner,
|
||||
this._styleUrlResolver, this._styleHost);
|
||||
attachTemplate(el, view:viewModule.View) {
|
||||
DOM.clearNodes(el);
|
||||
_moveViewNodesIntoParent(el, view);
|
||||
}
|
||||
|
||||
getTemplateCompileStep(cmpMetadata: DirectiveMetadata): NS.CompileStep {
|
||||
return new _ShimShadowDomStep(cmpMetadata);
|
||||
}
|
||||
|
||||
shimAppElement(cmpMetadata: DirectiveMetadata, element) {
|
||||
var cmpType = cmpMetadata.type;
|
||||
var hostAttribute = _getHostAttribute(_getComponentId(cmpType));
|
||||
DOM.setAttribute(element, hostAttribute, '');
|
||||
constructLightDom(lightDomView:viewModule.View, shadowDomView:viewModule.View, el): LightDom {
|
||||
return new LightDom(lightDomView, shadowDomView, el);
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,170 +96,15 @@ export class EmulatedScopedShadowDomStrategy extends EmulatedUnscopedShadowDomSt
|
||||
*/
|
||||
@Injectable()
|
||||
export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
||||
_styleUrlResolver: StyleUrlResolver;
|
||||
|
||||
constructor(styleUrlResolver: StyleUrlResolver) {
|
||||
super();
|
||||
this._styleUrlResolver = styleUrlResolver;
|
||||
this.render = new nsds.NativeShadowDomStrategy(styleUrlResolver);
|
||||
}
|
||||
|
||||
attachTemplate(el, view:viewModule.View){
|
||||
_moveViewNodesIntoParent(DOM.createShadowRoot(el), view);
|
||||
}
|
||||
|
||||
getStyleCompileStep(cmpMetadata: DirectiveMetadata, templateUrl: string): NS.CompileStep {
|
||||
return new _NativeCssStep(templateUrl, this._styleUrlResolver);
|
||||
}
|
||||
}
|
||||
|
||||
class _BaseEmulatedShadowDomStep extends NS.CompileStep {
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
if (current.ignoreBindings) {
|
||||
return;
|
||||
}
|
||||
var nodeName = DOM.nodeName(current.element);
|
||||
if (StringWrapper.equals(nodeName.toUpperCase(), 'CONTENT')) {
|
||||
var attrs = current.attrs();
|
||||
var selector = MapWrapper.get(attrs, 'select');
|
||||
current.contentTagSelector = isPresent(selector) ? selector : '';
|
||||
|
||||
var contentStart = DOM.createScriptTag('type', 'ng/contentStart');
|
||||
if (assertionsEnabled()) {
|
||||
DOM.setAttribute(contentStart, 'select', current.contentTagSelector);
|
||||
}
|
||||
var contentEnd = DOM.createScriptTag('type', 'ng/contentEnd');
|
||||
DOM.insertBefore(current.element, contentStart);
|
||||
DOM.insertBefore(current.element, contentEnd);
|
||||
DOM.remove(current.element);
|
||||
|
||||
current.element = contentStart;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class _EmptyCompileStep extends NS.CompileStep {
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class _ShimShadowDomStep extends _BaseEmulatedShadowDomStep {
|
||||
_contentAttribute: string;
|
||||
|
||||
constructor(cmpMetadata: DirectiveMetadata) {
|
||||
super();
|
||||
var id = _getComponentId(cmpMetadata.type);
|
||||
this._contentAttribute = _getContentAttribute(id);
|
||||
}
|
||||
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
super.process(parent, current, control);
|
||||
if (current.ignoreBindings) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Shim the element as a child of the compiled component
|
||||
DOM.setAttribute(current.element, this._contentAttribute, '');
|
||||
|
||||
// If the current element is also a component, shim it as a host
|
||||
var host = current.componentDirective;
|
||||
if (isPresent(host)) {
|
||||
var hostId = _getComponentId(host.type);
|
||||
var hostAttribute = _getHostAttribute(hostId);
|
||||
DOM.setAttribute(current.element, hostAttribute, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _EmulatedUnscopedCssStep extends NS.CompileStep {
|
||||
_templateUrl: string;
|
||||
_styleUrlResolver: StyleUrlResolver;
|
||||
_styleHost;
|
||||
|
||||
constructor(cmpMetadata: DirectiveMetadata, templateUrl: string,
|
||||
styleUrlResolver: StyleUrlResolver, styleHost) {
|
||||
super();
|
||||
this._templateUrl = templateUrl;
|
||||
this._styleUrlResolver = styleUrlResolver;
|
||||
this._styleHost = styleHost;
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var styleEl = current.element;
|
||||
var cssText = DOM.getText(styleEl);
|
||||
cssText = this._styleUrlResolver.resolveUrls(cssText, this._templateUrl);
|
||||
DOM.setText(styleEl, cssText);
|
||||
DOM.remove(styleEl);
|
||||
|
||||
if (!MapWrapper.contains(_sharedStyleTexts, cssText)) {
|
||||
// Styles are unscoped and shared across components, only append them to the head
|
||||
// when there are not present yet
|
||||
MapWrapper.set(_sharedStyleTexts, cssText, true);
|
||||
_insertStyleElement(this._styleHost, styleEl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _EmulatedScopedCssStep extends NS.CompileStep {
|
||||
_templateUrl: string;
|
||||
_component: Type;
|
||||
_styleInliner: StyleInliner;
|
||||
_styleUrlResolver: StyleUrlResolver;
|
||||
_styleHost;
|
||||
|
||||
constructor(cmpMetadata: DirectiveMetadata, templateUrl: string, styleInliner: StyleInliner,
|
||||
styleUrlResolver: StyleUrlResolver, styleHost) {
|
||||
super();
|
||||
this._templateUrl = templateUrl;
|
||||
this._component = cmpMetadata.type;
|
||||
this._styleInliner = styleInliner;
|
||||
this._styleUrlResolver = styleUrlResolver;
|
||||
this._styleHost = styleHost;
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var styleEl = current.element;
|
||||
|
||||
var cssText = DOM.getText(styleEl);
|
||||
|
||||
cssText = this._styleUrlResolver.resolveUrls(cssText, this._templateUrl);
|
||||
var css = this._styleInliner.inlineImports(cssText, this._templateUrl);
|
||||
|
||||
if (PromiseWrapper.isPromise(css)) {
|
||||
DOM.setText(styleEl, '');
|
||||
ListWrapper.push(parent.inheritedProtoView.stylePromises, css);
|
||||
return css.then((css) => {
|
||||
css = _shimCssForComponent(css, this._component);
|
||||
DOM.setText(styleEl, css);
|
||||
});
|
||||
} else {
|
||||
css = _shimCssForComponent(css, this._component);
|
||||
DOM.setText(styleEl, css);
|
||||
}
|
||||
|
||||
DOM.remove(styleEl);
|
||||
_insertStyleElement(this._styleHost, styleEl);
|
||||
}
|
||||
}
|
||||
|
||||
class _NativeCssStep extends NS.CompileStep {
|
||||
_styleUrlResolver: StyleUrlResolver;
|
||||
_templateUrl: string;
|
||||
|
||||
constructor(templateUrl: string, styleUrlResover: StyleUrlResolver) {
|
||||
super();
|
||||
this._styleUrlResolver = styleUrlResover;
|
||||
this._templateUrl = templateUrl;
|
||||
}
|
||||
|
||||
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||
var styleEl = current.element;
|
||||
var cssText = DOM.getText(styleEl);
|
||||
cssText = this._styleUrlResolver.resolveUrls(cssText, this._templateUrl);
|
||||
DOM.setText(styleEl, cssText);
|
||||
}
|
||||
}
|
||||
|
||||
function _moveViewNodesIntoParent(parent, view) {
|
||||
@ -331,55 +112,3 @@ function _moveViewNodesIntoParent(parent, view) {
|
||||
DOM.appendChild(parent, view.nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
var _componentUIDs: Map<Type, int> = MapWrapper.create();
|
||||
var _nextComponentUID: int = 0;
|
||||
var _sharedStyleTexts: Map<string, boolean> = MapWrapper.create();
|
||||
var _lastInsertedStyleEl;
|
||||
|
||||
function _getComponentId(component: Type) {
|
||||
var id = MapWrapper.get(_componentUIDs, component);
|
||||
if (isBlank(id)) {
|
||||
id = _nextComponentUID++;
|
||||
MapWrapper.set(_componentUIDs, component, id);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
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
|
||||
function _getHostAttribute(id: int) {
|
||||
return `_nghost-${id}`;
|
||||
}
|
||||
|
||||
// Returns the attribute to be added on every single element nodes in the component
|
||||
function _getContentAttribute(id: int) {
|
||||
return `_ngcontent-${id}`;
|
||||
}
|
||||
|
||||
function _shimCssForComponent(cssText: string, component: Type): string {
|
||||
var id = _getComponentId(component);
|
||||
var shadowCss = new ShadowCss();
|
||||
return shadowCss.shimCssText(cssText, _getContentAttribute(id), _getHostAttribute(id));
|
||||
}
|
||||
|
||||
// Reset the caches - used for tests only
|
||||
export function resetShadowDomCache() {
|
||||
MapWrapper.clear(_componentUIDs);
|
||||
_nextComponentUID = 0;
|
||||
MapWrapper.clear(_sharedStyleTexts);
|
||||
_lastInsertedStyleEl = null;
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
import {StringWrapper, RegExpWrapper} from 'angular2/src/facade/lang';
|
||||
|
||||
var DASH_CASE_REGEXP = RegExpWrapper.create('-([a-z])');
|
||||
var CAMEL_CASE_REGEXP = RegExpWrapper.create('([A-Z])');
|
||||
|
||||
export function dashCaseToCamelCase(input:string): string {
|
||||
return StringWrapper.replaceAllMapped(input, DASH_CASE_REGEXP, (m) => {
|
||||
return m[1].toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
export function camelCaseToDashCase(input:string): string {
|
||||
return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP, (m) => {
|
||||
return '-' + m[1].toLowerCase();
|
||||
});
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {isBlank, isPresent, BaseException, stringify} from 'angular2/src/facade/lang';
|
||||
import {Map, MapWrapper, StringMapWrapper, StringMap} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {XHR} from 'angular2/src/services/xhr';
|
||||
|
||||
import {Template} from 'angular2/src/core/annotations/template';
|
||||
|
||||
import {UrlResolver} from 'angular2/src/services/url_resolver';
|
||||
|
||||
/**
|
||||
* Strategy to load component templates.
|
||||
* @publicModule angular2/template
|
||||
*/
|
||||
@Injectable()
|
||||
export class TemplateLoader {
|
||||
_xhr: XHR;
|
||||
_htmlCache: StringMap;
|
||||
_baseUrls: Map<Type, string>;
|
||||
_urlCache: Map<Type, string>;
|
||||
_urlResolver: UrlResolver;
|
||||
|
||||
constructor(xhr: XHR, urlResolver: UrlResolver) {
|
||||
this._xhr = xhr;
|
||||
this._urlResolver = urlResolver;
|
||||
this._htmlCache = StringMapWrapper.create();
|
||||
this._baseUrls = MapWrapper.create();
|
||||
this._urlCache = MapWrapper.create();
|
||||
}
|
||||
|
||||
// TODO(vicb): union type: return an Element or a Promise<Element>
|
||||
load(template: Template) {
|
||||
if (isPresent(template.inline)) {
|
||||
return DOM.createTemplate(template.inline);
|
||||
}
|
||||
|
||||
if (isPresent(template.url)) {
|
||||
var url = this.getTemplateUrl(template);
|
||||
var promise = StringMapWrapper.get(this._htmlCache, url);
|
||||
|
||||
if (isBlank(promise)) {
|
||||
promise = this._xhr.get(url).then(function (html) {
|
||||
var template = DOM.createTemplate(html);
|
||||
return template;
|
||||
});
|
||||
StringMapWrapper.set(this._htmlCache, url, promise);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
throw new BaseException('Templates should have either their url or inline property set');
|
||||
}
|
||||
|
||||
setBaseUrl(template: Template, baseUrl: string) {
|
||||
MapWrapper.set(this._baseUrls, template, baseUrl);
|
||||
MapWrapper.delete(this._urlCache, template);
|
||||
}
|
||||
|
||||
getTemplateUrl(template: Template) {
|
||||
if (!MapWrapper.contains(this._urlCache, template)) {
|
||||
var baseUrl = MapWrapper.get(this._baseUrls, template);
|
||||
if (isBlank(baseUrl)) {
|
||||
throw new BaseException('The template base URL is not set');
|
||||
}
|
||||
var templateUrl;
|
||||
if (isPresent(template.url)) {
|
||||
templateUrl = this._urlResolver.resolve(baseUrl, template.url);
|
||||
} else {
|
||||
templateUrl = baseUrl;
|
||||
}
|
||||
MapWrapper.set(this._urlCache, template, templateUrl);
|
||||
}
|
||||
|
||||
return MapWrapper.get(this._urlCache, template);
|
||||
}
|
||||
}
|
13
modules/angular2/src/core/compiler/view.js
vendored
13
modules/angular2/src/core/compiler/view.js
vendored
@ -4,9 +4,8 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src
|
||||
import {AST, Locals, ChangeDispatcher, ProtoChangeDetector, ChangeDetector,
|
||||
ChangeRecord, BindingRecord, BindingPropagationConfig, uninitialized} from 'angular2/change_detection';
|
||||
|
||||
import {ProtoElementInjector, ElementInjector, PreBuiltObjects} from './element_injector';
|
||||
import {ProtoElementInjector, ElementInjector, PreBuiltObjects, DirectiveBinding} from './element_injector';
|
||||
import {ElementBinder} from './element_binder';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {SetterFn} from 'angular2/src/reflection/types';
|
||||
import {IMPLEMENTS, int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
||||
import {Injector} from 'angular2/di';
|
||||
@ -549,7 +548,7 @@ export class ProtoView {
|
||||
}
|
||||
|
||||
bindElement(parent:ElementBinder, distanceToParent:int, protoElementInjector:ProtoElementInjector,
|
||||
componentDirective:DirectiveMetadata = null, viewportDirective:DirectiveMetadata = null):ElementBinder {
|
||||
componentDirective:DirectiveBinding = null, viewportDirective:DirectiveBinding = null):ElementBinder {
|
||||
var elBinder = new ElementBinder(this.elementBinders.length, parent, distanceToParent,
|
||||
protoElementInjector, componentDirective, viewportDirective);
|
||||
ListWrapper.push(this.elementBinders, elBinder);
|
||||
@ -649,20 +648,20 @@ export class ProtoView {
|
||||
// Used for bootstrapping.
|
||||
static createRootProtoView(protoView: ProtoView,
|
||||
insertionElement,
|
||||
rootComponentAnnotatedType: DirectiveMetadata,
|
||||
rootComponentBinding: DirectiveBinding,
|
||||
protoChangeDetector:ProtoChangeDetector,
|
||||
shadowDomStrategy: ShadowDomStrategy
|
||||
): ProtoView {
|
||||
|
||||
DOM.addClass(insertionElement, NG_BINDING_CLASS);
|
||||
var cmpType = rootComponentAnnotatedType.type;
|
||||
var cmpType = rootComponentBinding.key.token;
|
||||
var rootProtoView = new ProtoView(insertionElement, protoChangeDetector, shadowDomStrategy);
|
||||
rootProtoView.instantiateInPlace = true;
|
||||
var binder = rootProtoView.bindElement(null, 0,
|
||||
new ProtoElementInjector(null, 0, [cmpType], true));
|
||||
binder.componentDirective = rootComponentAnnotatedType;
|
||||
binder.componentDirective = rootComponentBinding;
|
||||
binder.nestedProtoView = protoView;
|
||||
shadowDomStrategy.shimAppElement(rootComponentAnnotatedType, insertionElement);
|
||||
shadowDomStrategy.shimAppElement(cmpType, insertionElement);
|
||||
return rootProtoView;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user