chore: move core modules into core directory

BREAKING CHANGE:
    This change moves the http module into angular2/, so its import
    path is now angular2/http instead of http/http.

    Many other modules have also been moved around inside of angular2,
    but the public API paths have not changed as of this commit.
This commit is contained in:
Jeff Cross
2015-08-20 14:28:09 -07:00
parent 56e88058f1
commit 38a5a2a955
260 changed files with 0 additions and 0 deletions

View File

@ -0,0 +1,287 @@
import {isPresent, isBlank, BaseException, StringWrapper} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {ChangeDetectionUtil} from './change_detection_util';
import {ChangeDetectorRef} from './change_detector_ref';
import {DirectiveIndex} from './directive_record';
import {ChangeDetector, ChangeDispatcher} from './interfaces';
import {Pipes} from './pipes';
import {
ChangeDetectionError,
ExpressionChangedAfterItHasBeenCheckedException,
DehydratedException
} from './exceptions';
import {BindingTarget} from './binding_record';
import {Locals} from './parser/locals';
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './constants';
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
import {isObservable} from './observable_facade';
import {ON_PUSH_OBSERVE} from './constants';
var _scope_check: WtfScopeFn = wtfCreateScope(`ChangeDetector#check(ascii id, bool throwOnChange)`);
class _Context {
constructor(public element: any, public componentElement: any, public context: any,
public locals: any, public injector: any, public expression: any) {}
}
export class AbstractChangeDetector<T> implements ChangeDetector {
lightDomChildren: List<any> = [];
shadowDomChildren: List<any> = [];
parent: ChangeDetector;
ref: ChangeDetectorRef;
// The names of the below fields must be kept in sync with codegen_name_util.ts or
// change detection will fail.
alreadyChecked: any = false;
context: T;
locals: Locals = null;
mode: string = null;
pipes: Pipes = null;
propertyBindingIndex: number;
// This is an experimental feature. Works only in Dart.
subscriptions: any[];
streams: any[];
constructor(public id: string, public dispatcher: ChangeDispatcher,
public numberOfPropertyProtoRecords: number, public bindingTargets: BindingTarget[],
public directiveIndices: DirectiveIndex[], public strategy: string) {
this.ref = new ChangeDetectorRef(this);
}
addChild(cd: ChangeDetector): void {
this.lightDomChildren.push(cd);
cd.parent = this;
}
removeChild(cd: ChangeDetector): void { ListWrapper.remove(this.lightDomChildren, cd); }
addShadowDomChild(cd: ChangeDetector): void {
this.shadowDomChildren.push(cd);
cd.parent = this;
}
removeShadowDomChild(cd: ChangeDetector): void { ListWrapper.remove(this.shadowDomChildren, cd); }
remove(): void { this.parent.removeChild(this); }
handleEvent(eventName: string, elIndex: number, locals: Locals): boolean {
var res = this.handleEventInternal(eventName, elIndex, locals);
this.markPathToRootAsCheckOnce();
return res;
}
handleEventInternal(eventName: string, elIndex: number, locals: Locals): boolean { return false; }
detectChanges(): void { this.runDetectChanges(false); }
checkNoChanges(): void { throw new BaseException("Not implemented"); }
runDetectChanges(throwOnChange: boolean): void {
if (StringWrapper.equals(this.mode, DETACHED) || StringWrapper.equals(this.mode, CHECKED))
return;
var s = _scope_check(this.id, throwOnChange);
this.detectChangesInRecords(throwOnChange);
this._detectChangesInLightDomChildren(throwOnChange);
if (throwOnChange === false) this.callOnAllChangesDone();
this._detectChangesInShadowDomChildren(throwOnChange);
if (StringWrapper.equals(this.mode, CHECK_ONCE)) this.mode = CHECKED;
wtfLeave(s);
}
// This method is not intended to be overridden. Subclasses should instead provide an
// implementation of `detectChangesInRecordsInternal` which does the work of detecting changes
// and which this method will call.
// This method expects that `detectChangesInRecordsInternal` will set the property
// `this.propertyBindingIndex` to the propertyBindingIndex of the first proto record. This is to
// facilitate error reporting.
detectChangesInRecords(throwOnChange: boolean): void {
if (!this.hydrated()) {
this.throwDehydratedError();
}
try {
this.detectChangesInRecordsInternal(throwOnChange);
} catch (e) {
this._throwError(e, e.stack);
}
}
// Subclasses should override this method to perform any work necessary to detect and report
// changes. For example, changes should be reported via `ChangeDetectionUtil.addChange`, lifecycle
// methods should be called, etc.
// This implementation should also set `this.propertyBindingIndex` to the propertyBindingIndex of
// the
// first proto record to facilitate error reporting. See {@link #detectChangesInRecords}.
detectChangesInRecordsInternal(throwOnChange: boolean): void {}
// This method is not intended to be overridden. Subclasses should instead provide an
// implementation of `hydrateDirectives`.
hydrate(context: T, locals: Locals, directives: any, pipes: any): void {
this.mode = ChangeDetectionUtil.changeDetectionMode(this.strategy);
this.context = context;
if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) {
this.observeComponent(context);
}
this.locals = locals;
this.pipes = pipes;
this.hydrateDirectives(directives);
this.alreadyChecked = false;
}
// Subclasses should override this method to hydrate any directives.
hydrateDirectives(directives: any): void {}
// This method is not intended to be overridden. Subclasses should instead provide an
// implementation of `dehydrateDirectives`.
dehydrate(): void {
this.dehydrateDirectives(true);
// This is an experimental feature. Works only in Dart.
if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) {
this._unsubsribeFromObservables();
}
this.context = null;
this.locals = null;
this.pipes = null;
}
// Subclasses should override this method to dehydrate any directives. This method should reverse
// any work done in `hydrateDirectives`.
dehydrateDirectives(destroyPipes: boolean): void {}
hydrated(): boolean { return this.context !== null; }
callOnAllChangesDone(): void { this.dispatcher.notifyOnAllChangesDone(); }
_detectChangesInLightDomChildren(throwOnChange: boolean): void {
var c = this.lightDomChildren;
for (var i = 0; i < c.length; ++i) {
c[i].runDetectChanges(throwOnChange);
}
}
_detectChangesInShadowDomChildren(throwOnChange: boolean): void {
var c = this.shadowDomChildren;
for (var i = 0; i < c.length; ++i) {
c[i].runDetectChanges(throwOnChange);
}
}
markAsCheckOnce(): void { this.mode = CHECK_ONCE; }
markPathToRootAsCheckOnce(): void {
var c: ChangeDetector = this;
while (isPresent(c) && !StringWrapper.equals(c.mode, DETACHED)) {
if (StringWrapper.equals(c.mode, CHECKED)) c.mode = CHECK_ONCE;
c = c.parent;
}
}
// This is an experimental feature. Works only in Dart.
private _unsubsribeFromObservables(): void {
if (isPresent(this.subscriptions)) {
for (var i = 0; i < this.subscriptions.length; ++i) {
var s = this.subscriptions[i];
if (isPresent(this.subscriptions[i])) {
s.cancel();
this.subscriptions[i] = null;
}
}
}
}
// This is an experimental feature. Works only in Dart.
protected observeValue(value: any, index: number): any {
if (isObservable(value)) {
this._createArrayToStoreObservables();
if (isBlank(this.subscriptions[index])) {
this.streams[index] = value.changes;
this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck());
} else if (this.streams[index] !== value.changes) {
this.subscriptions[index].cancel();
this.streams[index] = value.changes;
this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck());
}
}
return value;
}
// This is an experimental feature. Works only in Dart.
protected observeDirective(value: any, index: number): any {
if (isObservable(value)) {
this._createArrayToStoreObservables();
var arrayIndex = this.numberOfPropertyProtoRecords + index + 2; // +1 is component
this.streams[arrayIndex] = value.changes;
this.subscriptions[arrayIndex] = value.changes.listen((_) => this.ref.requestCheck());
}
return value;
}
// This is an experimental feature. Works only in Dart.
protected observeComponent(value: any): any {
if (isObservable(value)) {
this._createArrayToStoreObservables();
var index = this.numberOfPropertyProtoRecords + 1;
this.streams[index] = value.changes;
this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck());
}
return value;
}
private _createArrayToStoreObservables(): void {
if (isBlank(this.subscriptions)) {
this.subscriptions = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords +
this.directiveIndices.length + 2);
this.streams = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords +
this.directiveIndices.length + 2);
}
}
protected getDirectiveFor(directives: any, index: number): any {
return directives.getDirectiveFor(this.directiveIndices[index]);
}
protected getDetectorFor(directives: any, index: number): ChangeDetector {
return directives.getDetectorFor(this.directiveIndices[index]);
}
protected notifyDispatcher(value: any): void {
this.dispatcher.notifyOnBinding(this._currentBinding(), value);
}
protected logBindingUpdate(value: any): void {
this.dispatcher.logBindingUpdate(this._currentBinding(), value);
}
protected addChange(changes: StringMap<string, any>, oldValue: any,
newValue: any): StringMap<string, any> {
if (isBlank(changes)) {
changes = {};
}
changes[this._currentBinding().name] = ChangeDetectionUtil.simpleChange(oldValue, newValue);
return changes;
}
private _throwError(exception: any, stack: any): void {
var c = this.dispatcher.getDebugContext(this._currentBinding().elementIndex, null);
var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.context, c.locals,
c.injector, this._currentBinding().debug) :
null;
throw new ChangeDetectionError(this._currentBinding().debug, exception, stack, context);
}
protected throwOnChangeError(oldValue: any, newValue: any): void {
throw new ExpressionChangedAfterItHasBeenCheckedException(this._currentBinding().debug,
oldValue, newValue, null);
}
protected throwDehydratedError(): void { throw new DehydratedException(); }
private _currentBinding(): BindingTarget {
return this.bindingTargets[this.propertyBindingIndex];
}
}

View File

@ -0,0 +1,148 @@
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {SetterFn} from 'angular2/src/reflection/types';
import {AST} from './parser/ast';
import {DirectiveIndex, DirectiveRecord} from './directive_record';
const DIRECTIVE_LIFECYCLE = "directiveLifecycle";
const BINDING = "native";
const DIRECTIVE = "directive";
const ELEMENT_PROPERTY = "elementProperty";
const ELEMENT_ATTRIBUTE = "elementAttribute";
const ELEMENT_CLASS = "elementClass";
const ELEMENT_STYLE = "elementStyle";
const TEXT_NODE = "textNode";
const EVENT = "event";
const HOST_EVENT = "hostEvent";
export class BindingTarget {
constructor(public mode: string, public elementIndex: number, public name: string,
public unit: string, public debug: string) {}
isDirective(): boolean { return this.mode === DIRECTIVE; }
isElementProperty(): boolean { return this.mode === ELEMENT_PROPERTY; }
isElementAttribute(): boolean { return this.mode === ELEMENT_ATTRIBUTE; }
isElementClass(): boolean { return this.mode === ELEMENT_CLASS; }
isElementStyle(): boolean { return this.mode === ELEMENT_STYLE; }
isTextNode(): boolean { return this.mode === TEXT_NODE; }
}
export class BindingRecord {
constructor(public mode: string, public target: BindingTarget, public implicitReceiver: any,
public ast: AST, public setter: SetterFn, public lifecycleEvent: string,
public directiveRecord: DirectiveRecord) {}
isDirectiveLifecycle(): boolean { return this.mode === DIRECTIVE_LIFECYCLE; }
callOnChange(): boolean {
return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange;
}
isDefaultChangeDetection(): boolean {
return isBlank(this.directiveRecord) || this.directiveRecord.isDefaultChangeDetection();
}
static createDirectiveOnCheck(directiveRecord: DirectiveRecord): BindingRecord {
return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onCheck", directiveRecord);
}
static createDirectiveOnInit(directiveRecord: DirectiveRecord): BindingRecord {
return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onInit", directiveRecord);
}
static createDirectiveOnChange(directiveRecord: DirectiveRecord): BindingRecord {
return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onChange", directiveRecord);
}
static createForDirective(ast: AST, propertyName: string, setter: SetterFn,
directiveRecord: DirectiveRecord): BindingRecord {
var elementIndex = directiveRecord.directiveIndex.elementIndex;
var t = new BindingTarget(DIRECTIVE, elementIndex, propertyName, null, ast.toString());
return new BindingRecord(DIRECTIVE, t, 0, ast, setter, null, directiveRecord);
}
static createForElementProperty(ast: AST, elementIndex: number,
propertyName: string): BindingRecord {
var t = new BindingTarget(ELEMENT_PROPERTY, elementIndex, propertyName, null, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}
static createForElementAttribute(ast: AST, elementIndex: number,
attributeName: string): BindingRecord {
var t = new BindingTarget(ELEMENT_ATTRIBUTE, elementIndex, attributeName, null, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}
static createForElementClass(ast: AST, elementIndex: number, className: string): BindingRecord {
var t = new BindingTarget(ELEMENT_CLASS, elementIndex, className, null, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}
static createForElementStyle(ast: AST, elementIndex: number, styleName: string,
unit: string): BindingRecord {
var t = new BindingTarget(ELEMENT_STYLE, elementIndex, styleName, unit, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}
static createForHostProperty(directiveIndex: DirectiveIndex, ast: AST,
propertyName: string): BindingRecord {
var t = new BindingTarget(ELEMENT_PROPERTY, directiveIndex.elementIndex, propertyName, null,
ast.toString());
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
}
static createForHostAttribute(directiveIndex: DirectiveIndex, ast: AST,
attributeName: string): BindingRecord {
var t = new BindingTarget(ELEMENT_ATTRIBUTE, directiveIndex.elementIndex, attributeName, null,
ast.toString());
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
}
static createForHostClass(directiveIndex: DirectiveIndex, ast: AST,
className: string): BindingRecord {
var t = new BindingTarget(ELEMENT_CLASS, directiveIndex.elementIndex, className, null,
ast.toString());
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
}
static createForHostStyle(directiveIndex: DirectiveIndex, ast: AST, styleName: string,
unit: string): BindingRecord {
var t = new BindingTarget(ELEMENT_STYLE, directiveIndex.elementIndex, styleName, unit,
ast.toString());
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
}
static createForTextNode(ast: AST, elementIndex: number): BindingRecord {
var t = new BindingTarget(TEXT_NODE, elementIndex, null, null, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}
static createForEvent(ast: AST, eventName: string, elementIndex: number): BindingRecord {
var t = new BindingTarget(EVENT, elementIndex, eventName, null, ast.toString());
return new BindingRecord(EVENT, t, 0, ast, null, null, null);
}
static createForHostEvent(ast: AST, eventName: string,
directiveRecord: DirectiveRecord): BindingRecord {
var directiveIndex = directiveRecord.directiveIndex;
var t =
new BindingTarget(HOST_EVENT, directiveIndex.elementIndex, eventName, null, ast.toString());
return new BindingRecord(HOST_EVENT, t, directiveIndex, ast, null, null, directiveRecord);
}
}

View File

@ -0,0 +1,171 @@
import {JitProtoChangeDetector} from './jit_proto_change_detector';
import {PregenProtoChangeDetector} from './pregen_proto_change_detector';
import {DynamicProtoChangeDetector} from './proto_change_detector';
import {IterableDiffers, IterableDifferFactory} from './differs/iterable_differs';
import {DefaultIterableDifferFactory} from './differs/default_iterable_differ';
import {KeyValueDiffers, KeyValueDifferFactory} from './differs/keyvalue_differs';
import {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ';
import {
ChangeDetection,
ProtoChangeDetector,
ChangeDetectorDefinition,
ChangeDetectorGenConfig
} from './interfaces';
import {Injector, Inject, Injectable, OpaqueToken, Optional, Binding} from 'angular2/di';
import {List, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
import {
CONST,
CONST_EXPR,
isPresent,
BaseException,
assertionsEnabled
} from 'angular2/src/facade/lang';
export {
ASTWithSource,
AST,
AstTransformer,
PropertyRead,
LiteralArray,
ImplicitReceiver
} from './parser/ast';
export {Lexer} from './parser/lexer';
export {Parser} from './parser/parser';
export {Locals} from './parser/locals';
export {
DehydratedException,
ExpressionChangedAfterItHasBeenCheckedException,
ChangeDetectionError
} from './exceptions';
export {
ProtoChangeDetector,
ChangeDetector,
ChangeDispatcher,
ChangeDetection,
ChangeDetectorDefinition,
DebugContext,
ChangeDetectorGenConfig
} from './interfaces';
export {CHECK_ONCE, CHECK_ALWAYS, DETACHED, CHECKED, ON_PUSH, DEFAULT} from './constants';
export {DynamicProtoChangeDetector} from './proto_change_detector';
export {BindingRecord, BindingTarget} from './binding_record';
export {DirectiveIndex, DirectiveRecord} from './directive_record';
export {DynamicChangeDetector} from './dynamic_change_detector';
export {ChangeDetectorRef} from './change_detector_ref';
export {IterableDiffers, IterableDiffer, IterableDifferFactory} from './differs/iterable_differs';
export {KeyValueDiffers, KeyValueDiffer, KeyValueDifferFactory} from './differs/keyvalue_differs';
export {PipeTransform, PipeOnDestroy} from './pipe_transform';
export {WrappedValue} from './change_detection_util';
/**
* Structural diffing for `Object`s and `Map`s.
*/
export const keyValDiff: KeyValueDifferFactory[] =
CONST_EXPR([CONST_EXPR(new DefaultKeyValueDifferFactory())]);
/**
* Structural diffing for `Iterable` types such as `Array`s.
*/
export const iterableDiff: IterableDifferFactory[] =
CONST_EXPR([CONST_EXPR(new DefaultIterableDifferFactory())]);
export const defaultIterableDiffers = CONST_EXPR(new IterableDiffers(iterableDiff));
export const defaultKeyValueDiffers = CONST_EXPR(new KeyValueDiffers(keyValDiff));
/**
* Map from {@link ChangeDetectorDefinition#id} to a factory method which takes a
* {@link Pipes} and a {@link ChangeDetectorDefinition} and generates a
* {@link ProtoChangeDetector} associated with the definition.
*/
// TODO(kegluneq): Use PregenProtoChangeDetectorFactory rather than Function once possible in
// dart2js. See https://github.com/dart-lang/sdk/issues/23630 for details.
export var preGeneratedProtoDetectors: StringMap<string, Function> = {};
/**
* Implements change detection using a map of pregenerated proto detectors.
*/
@Injectable()
export class PreGeneratedChangeDetection extends ChangeDetection {
_dynamicChangeDetection: ChangeDetection;
_protoChangeDetectorFactories: StringMap<string, Function>;
_genConfig: ChangeDetectorGenConfig;
constructor(config?: ChangeDetectorGenConfig,
protoChangeDetectorsForTest?: StringMap<string, Function>) {
super();
this._dynamicChangeDetection = new DynamicChangeDetection();
this._protoChangeDetectorFactories = isPresent(protoChangeDetectorsForTest) ?
protoChangeDetectorsForTest :
preGeneratedProtoDetectors;
this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false);
}
static isSupported(): boolean { return PregenProtoChangeDetector.isSupported(); }
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
if (StringMapWrapper.contains(this._protoChangeDetectorFactories, id)) {
return StringMapWrapper.get(this._protoChangeDetectorFactories, id)(definition);
}
return this._dynamicChangeDetection.getProtoChangeDetector(id, definition);
}
get genConfig(): ChangeDetectorGenConfig { return this._genConfig; }
get generateDetectors(): boolean { return true; }
}
/**
* Implements change detection that does not require `eval()`.
*
* This is slower than {@link JitChangeDetection}.
*/
@Injectable()
export class DynamicChangeDetection extends ChangeDetection {
_genConfig: ChangeDetectorGenConfig;
constructor(config?: ChangeDetectorGenConfig) {
super();
this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false);
}
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
return new DynamicProtoChangeDetector(definition);
}
get genConfig(): ChangeDetectorGenConfig { return this._genConfig; }
get generateDetectors(): boolean { return true; }
}
/**
* Implements faster change detection by generating source code.
*
* This requires `eval()`. For change detection that does not require `eval()`, see
* {@link DynamicChangeDetection} and {@link PreGeneratedChangeDetection}.
*/
@Injectable()
export class JitChangeDetection extends ChangeDetection {
_genConfig: ChangeDetectorGenConfig;
constructor(config?: ChangeDetectorGenConfig) {
super();
this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false);
}
static isSupported(): boolean { return JitProtoChangeDetector.isSupported(); }
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
return new JitProtoChangeDetector(definition);
}
get genConfig(): ChangeDetectorGenConfig { return this._genConfig; }
get generateDetectors(): boolean { return true; }
}

View File

@ -0,0 +1,17 @@
library change_detection.change_detection_jit_generator;
/// Placeholder JIT generator for Dart.
/// Dart does not support `eval`, so JIT generation is not an option. Instead,
/// the Dart transformer pre-generates these Change Detector classes and
/// registers them with the system. See `PreGeneratedChangeDetection`,
/// `PregenProtoChangeDetector`, and
/// `src/transform/template_compiler/change_detector_codegen.dart` for details.
class ChangeDetectorJITGenerator {
ChangeDetectorJITGenerator(typeName, strategy, records, directiveMementos) {}
generate() {
throw "Jit Change Detection is not supported in Dart";
}
static bool isSupported() => false;
}

View File

@ -0,0 +1,384 @@
import {BaseException, Type, isBlank, isPresent} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {AbstractChangeDetector} from './abstract_change_detector';
import {ChangeDetectionUtil} from './change_detection_util';
import {DirectiveIndex, DirectiveRecord} from './directive_record';
import {ProtoRecord, RecordType} from './proto_record';
import {CodegenNameUtil, sanitizeName} from './codegen_name_util';
import {CodegenLogicUtil} from './codegen_logic_util';
import {codify} from './codegen_facade';
import {EventBinding} from './event_binding';
import {BindingTarget} from './binding_record';
import {ChangeDetectorGenConfig} from './interfaces';
/**
* The code generator takes a list of proto records and creates a function/class
* that "emulates" what the developer would write by hand to implement the same
* kind of behaviour.
*
* This code should be kept in sync with the Dart transformer's
* `angular2.transform.template_compiler.change_detector_codegen` library. If you make updates
* here, please make equivalent changes there.
*/
var ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
var UTIL = "ChangeDetectionUtil";
var IS_CHANGED_LOCAL = "isChanged";
var CHANGES_LOCAL = "changes";
export class ChangeDetectorJITGenerator {
_logic: CodegenLogicUtil;
_names: CodegenNameUtil;
_typeName: string;
constructor(private id: string, private changeDetectionStrategy: string,
private records: List<ProtoRecord>, private propertyBindingTargets: BindingTarget[],
private eventBindings: EventBinding[], private directiveRecords: List<any>,
private genConfig: ChangeDetectorGenConfig) {
this._names =
new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords, UTIL);
this._logic = new CodegenLogicUtil(this._names, UTIL, changeDetectionStrategy);
this._typeName = sanitizeName(`ChangeDetector_${this.id}`);
}
generate(): Function {
var classDefinition = `
var ${this._typeName} = function ${this._typeName}(dispatcher) {
${ABSTRACT_CHANGE_DETECTOR}.call(
this, ${JSON.stringify(this.id)}, dispatcher, ${this.records.length},
${this._typeName}.gen_propertyBindingTargets, ${this._typeName}.gen_directiveIndices,
${codify(this.changeDetectionStrategy)});
this.dehydrateDirectives(false);
}
${this._typeName}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype);
${this._typeName}.prototype.detectChangesInRecordsInternal = function(throwOnChange) {
${this._names.genInitLocals()}
var ${IS_CHANGED_LOCAL} = false;
var ${CHANGES_LOCAL} = null;
${this.records.map((r) => this._genRecord(r)).join("\n")}
${this._names.getAlreadyCheckedName()} = true;
}
${this._maybeGenHandleEventInternal()}
${this._genCheckNoChanges()}
${this._maybeGenCallOnAllChangesDone()}
${this._maybeGenHydrateDirectives()}
${this._maybeGenDehydrateDirectives()}
${this._genPropertyBindingTargets()};
${this._genDirectiveIndices()};
return function(dispatcher) {
return new ${this._typeName}(dispatcher);
}
`;
return new Function(ABSTRACT_CHANGE_DETECTOR, UTIL, classDefinition)(AbstractChangeDetector,
ChangeDetectionUtil);
}
_genPropertyBindingTargets(): string {
var targets = this._logic.genPropertyBindingTargets(this.propertyBindingTargets,
this.genConfig.genDebugInfo);
return `${this._typeName}.gen_propertyBindingTargets = ${targets};`;
}
_genDirectiveIndices(): string {
var indices = this._logic.genDirectiveIndices(this.directiveRecords);
return `${this._typeName}.gen_directiveIndices = ${indices};`;
}
_maybeGenHandleEventInternal(): string {
if (this.eventBindings.length > 0) {
var handlers = this.eventBindings.map(eb => this._genEventBinding(eb)).join("\n");
return `
${this._typeName}.prototype.handleEventInternal = function(eventName, elIndex, locals) {
var ${this._names.getPreventDefaultAccesor()} = false;
${this._names.genInitEventLocals()}
${handlers}
return ${this._names.getPreventDefaultAccesor()};
}
`;
} else {
return '';
}
}
_genEventBinding(eb: EventBinding): string {
var recs = eb.records.map(r => this._genEventBindingEval(eb, r)).join("\n");
return `
if (eventName === "${eb.eventName}" && elIndex === ${eb.elIndex}) {
${recs}
}`;
}
_genEventBindingEval(eb: EventBinding, r: ProtoRecord): string {
if (r.lastInBinding) {
var evalRecord = this._logic.genEventBindingEvalValue(eb, r);
var markPath = this._genMarkPathToRootAsCheckOnce(r);
var prevDefault = this._genUpdatePreventDefault(eb, r);
return `${evalRecord}\n${markPath}\n${prevDefault}`;
} else {
return this._logic.genEventBindingEvalValue(eb, r);
}
}
_genMarkPathToRootAsCheckOnce(r: ProtoRecord): string {
var br = r.bindingRecord;
if (br.isDefaultChangeDetection()) {
return "";
} else {
return `${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();`;
}
}
_genUpdatePreventDefault(eb: EventBinding, r: ProtoRecord): string {
var local = this._names.getEventLocalName(eb, r.selfIndex);
return `if (${local} === false) { ${this._names.getPreventDefaultAccesor()} = true};`;
}
_maybeGenDehydrateDirectives(): string {
var destroyPipesCode = this._names.genPipeOnDestroy();
if (destroyPipesCode) {
destroyPipesCode = `if (destroyPipes) { ${destroyPipesCode} }`;
}
var dehydrateFieldsCode = this._names.genDehydrateFields();
if (!destroyPipesCode && !dehydrateFieldsCode) return '';
return `${this._typeName}.prototype.dehydrateDirectives = function(destroyPipes) {
${destroyPipesCode}
${dehydrateFieldsCode}
}`;
}
_maybeGenHydrateDirectives(): string {
var hydrateDirectivesCode = this._logic.genHydrateDirectives(this.directiveRecords);
var hydrateDetectorsCode = this._logic.genHydrateDetectors(this.directiveRecords);
if (!hydrateDirectivesCode && !hydrateDetectorsCode) return '';
return `${this._typeName}.prototype.hydrateDirectives = function(directives) {
${hydrateDirectivesCode}
${hydrateDetectorsCode}
}`;
}
_maybeGenCallOnAllChangesDone(): string {
var notifications = [];
var dirs = this.directiveRecords;
// NOTE(kegluneq): Order is important!
for (var i = dirs.length - 1; i >= 0; --i) {
var dir = dirs[i];
if (dir.callOnAllChangesDone) {
notifications.push(
`${this._names.getDirectiveName(dir.directiveIndex)}.onAllChangesDone();`);
}
}
if (notifications.length > 0) {
var directiveNotifications = notifications.join("\n");
return `
${this._typeName}.prototype.callOnAllChangesDone = function() {
${ABSTRACT_CHANGE_DETECTOR}.prototype.callOnAllChangesDone.call(this);
${directiveNotifications}
}
`;
} else {
return '';
}
}
_genRecord(r: ProtoRecord): string {
var rec;
if (r.isLifeCycleRecord()) {
rec = this._genDirectiveLifecycle(r);
} else if (r.isPipeRecord()) {
rec = this._genPipeCheck(r);
} else {
rec = this._genReferenceCheck(r);
}
return `
${this._maybeFirstInBinding(r)}
${rec}
${this._maybeGenLastInDirective(r)}
`;
}
_genDirectiveLifecycle(r: ProtoRecord): string {
if (r.name === "onCheck") {
return this._genOnCheck(r);
} else if (r.name === "onInit") {
return this._genOnInit(r);
} else if (r.name === "onChange") {
return this._genOnChange(r);
} else {
throw new BaseException(`Unknown lifecycle event '${r.name}'`);
}
}
_genPipeCheck(r: ProtoRecord): string {
var context = this._names.getLocalName(r.contextIndex);
var argString = r.args.map((arg) => this._names.getLocalName(arg)).join(", ");
var oldValue = this._names.getFieldName(r.selfIndex);
var newValue = this._names.getLocalName(r.selfIndex);
var pipe = this._names.getPipeName(r.selfIndex);
var pipeType = r.name;
var read = `
if (${pipe} === ${UTIL}.uninitialized) {
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}');
}
${newValue} = ${pipe}.transform(${context}, [${argString}]);
`;
var check = `
if (${oldValue} !== ${newValue}) {
${newValue} = ${UTIL}.unwrapValue(${newValue})
${this._genChangeMarker(r)}
${this._genUpdateDirectiveOrElement(r)}
${this._genAddToChanges(r)}
${oldValue} = ${newValue};
}
`;
return r.shouldBeChecked() ? `${read}${check}` : read;
}
_genReferenceCheck(r: ProtoRecord): string {
var oldValue = this._names.getFieldName(r.selfIndex);
var newValue = this._names.getLocalName(r.selfIndex);
var read = `
${this._logic.genPropertyBindingEvalValue(r)}
`;
var check = `
if (${newValue} !== ${oldValue}) {
${this._genChangeMarker(r)}
${this._genUpdateDirectiveOrElement(r)}
${this._genAddToChanges(r)}
${oldValue} = ${newValue};
}
`;
var genCode = r.shouldBeChecked() ? `${read}${check}` : read;
if (r.isPureFunction()) {
var condition = r.args.map((a) => this._names.getChangeName(a)).join(" || ");
if (r.isUsedByOtherRecord()) {
return `if (${condition}) { ${genCode} } else { ${newValue} = ${oldValue}; }`;
} else {
return `if (${condition}) { ${genCode} }`;
}
} else {
return genCode;
}
}
_genChangeMarker(r: ProtoRecord): string {
return r.argumentToPureFunction ? `${this._names.getChangeName(r.selfIndex)} = true` : ``;
}
_genUpdateDirectiveOrElement(r: ProtoRecord): string {
if (!r.lastInBinding) return "";
var newValue = this._names.getLocalName(r.selfIndex);
var oldValue = this._names.getFieldName(r.selfIndex);
var notifyDebug = this.genConfig.logBindingUpdate ? `this.logBindingUpdate(${newValue});` : "";
var br = r.bindingRecord;
if (br.target.isDirective()) {
var directiveProperty =
`${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.${br.target.name}`;
return `
${this._genThrowOnChangeCheck(oldValue, newValue)}
${directiveProperty} = ${newValue};
${notifyDebug}
${IS_CHANGED_LOCAL} = true;
`;
} else {
return `
${this._genThrowOnChangeCheck(oldValue, newValue)}
this.notifyDispatcher(${newValue});
${notifyDebug}
`;
}
}
_genThrowOnChangeCheck(oldValue: string, newValue: string): string {
if (this.genConfig.genCheckNoChanges) {
return `
if(throwOnChange) {
this.throwOnChangeError(${oldValue}, ${newValue});
}
`;
} else {
return '';
}
}
_genCheckNoChanges(): string {
if (this.genConfig.genCheckNoChanges) {
return `${this._typeName}.prototype.checkNoChanges = function() { this.runDetectChanges(true); }`;
} else {
return '';
}
}
_genAddToChanges(r: ProtoRecord): string {
var newValue = this._names.getLocalName(r.selfIndex);
var oldValue = this._names.getFieldName(r.selfIndex);
if (!r.bindingRecord.callOnChange()) return "";
return `${CHANGES_LOCAL} = this.addChange(${CHANGES_LOCAL}, ${oldValue}, ${newValue});`;
}
_maybeFirstInBinding(r: ProtoRecord): string {
var prev = ChangeDetectionUtil.protoByIndex(this.records, r.selfIndex - 1);
var firstInBindng = isBlank(prev) || prev.bindingRecord !== r.bindingRecord;
return firstInBindng && !r.bindingRecord.isDirectiveLifecycle() ?
`${this._names.getPropertyBindingIndex()} = ${r.propertyBindingIndex};` :
'';
}
_maybeGenLastInDirective(r: ProtoRecord): string {
if (!r.lastInDirective) return "";
return `
${CHANGES_LOCAL} = null;
${this._genNotifyOnPushDetectors(r)}
${IS_CHANGED_LOCAL} = false;
`;
}
_genOnCheck(r: ProtoRecord): string {
var br = r.bindingRecord;
return `if (!throwOnChange) ${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.onCheck();`;
}
_genOnInit(r: ProtoRecord): string {
var br = r.bindingRecord;
return `if (!throwOnChange && !${this._names.getAlreadyCheckedName()}) ${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.onInit();`;
}
_genOnChange(r: ProtoRecord): string {
var br = r.bindingRecord;
return `if (!throwOnChange && ${CHANGES_LOCAL}) ${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.onChange(${CHANGES_LOCAL});`;
}
_genNotifyOnPushDetectors(r: ProtoRecord): string {
var br = r.bindingRecord;
if (!r.lastInDirective || br.isDefaultChangeDetection()) return "";
var retVal = `
if(${IS_CHANGED_LOCAL}) {
${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markAsCheckOnce();
}
`;
return retVal;
}
}

View File

@ -0,0 +1,215 @@
import {
CONST_EXPR,
isPresent,
isBlank,
BaseException,
Type,
StringWrapper
} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {ProtoRecord} from './proto_record';
import {
CHECK_ALWAYS,
CHECK_ONCE,
CHECKED,
DETACHED,
isDefaultChangeDetectionStrategy
} from './constants';
import {implementsOnDestroy} from './pipe_lifecycle_reflector';
import {BindingTarget} from './binding_record';
import {DirectiveIndex} from './directive_record';
/**
* Indicates that the result of a {@link PipeMetadata} transformation has changed even though the
* reference
* has not changed.
*
* The wrapped value will be unwrapped by change detection, and the unwrapped value will be stored.
*
* Example:
*
* ```
* if (this._latestValue === this._latestReturnedValue) {
* return this._latestReturnedValue;
* } else {
* this._latestReturnedValue = this._latestValue;
* return WrappedValue.wrap(this._latestValue); // this will force update
* }
* ```
*/
export class WrappedValue {
constructor(public wrapped: any) {}
static wrap(value: any): WrappedValue {
var w = _wrappedValues[_wrappedIndex++ % 5];
w.wrapped = value;
return w;
}
}
var _wrappedValues = [
new WrappedValue(null),
new WrappedValue(null),
new WrappedValue(null),
new WrappedValue(null),
new WrappedValue(null)
];
var _wrappedIndex = 0;
export class SimpleChange {
constructor(public previousValue: any, public currentValue: any) {}
isFirstChange(): boolean { return this.previousValue === ChangeDetectionUtil.uninitialized; }
}
var _simpleChangesIndex = 0;
var _simpleChanges = [
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null)
];
function _simpleChange(previousValue, currentValue): SimpleChange {
var index = _simpleChangesIndex++ % 20;
var s = _simpleChanges[index];
s.previousValue = previousValue;
s.currentValue = currentValue;
return s;
}
/* tslint:disable:requireParameterType */
export class ChangeDetectionUtil {
static uninitialized: Object = CONST_EXPR<Object>(new Object());
static arrayFn0(): any[] { return []; }
static arrayFn1(a1): any[] { return [a1]; }
static arrayFn2(a1, a2): any[] { return [a1, a2]; }
static arrayFn3(a1, a2, a3): any[] { return [a1, a2, a3]; }
static arrayFn4(a1, a2, a3, a4): any[] { return [a1, a2, a3, a4]; }
static arrayFn5(a1, a2, a3, a4, a5): any[] { return [a1, a2, a3, a4, a5]; }
static arrayFn6(a1, a2, a3, a4, a5, a6): any[] { return [a1, a2, a3, a4, a5, a6]; }
static arrayFn7(a1, a2, a3, a4, a5, a6, a7): any[] { return [a1, a2, a3, a4, a5, a6, a7]; }
static arrayFn8(a1, a2, a3, a4, a5, a6, a7, a8): any[] {
return [a1, a2, a3, a4, a5, a6, a7, a8];
}
static arrayFn9(a1, a2, a3, a4, a5, a6, a7, a8, a9): any[] {
return [a1, a2, a3, a4, a5, a6, a7, a8, a9];
}
static operation_negate(value): any { return !value; }
static operation_add(left, right): any { return left + right; }
static operation_subtract(left, right): any { return left - right; }
static operation_multiply(left, right): any { return left * right; }
static operation_divide(left, right): any { return left / right; }
static operation_remainder(left, right): any { return left % right; }
static operation_equals(left, right): any { return left == right; }
static operation_not_equals(left, right): any { return left != right; }
static operation_identical(left, right): any { return left === right; }
static operation_not_identical(left, right): any { return left !== right; }
static operation_less_then(left, right): any { return left < right; }
static operation_greater_then(left, right): any { return left > right; }
static operation_less_or_equals_then(left, right): any { return left <= right; }
static operation_greater_or_equals_then(left, right): any { return left >= right; }
static operation_logical_and(left, right): any { return left && right; }
static operation_logical_or(left, right): any { return left || right; }
static cond(cond, trueVal, falseVal): any { return cond ? trueVal : falseVal; }
static mapFn(keys: List<any>): any {
function buildMap(values): StringMap<any, any> {
var res = StringMapWrapper.create();
for (var i = 0; i < keys.length; ++i) {
StringMapWrapper.set(res, keys[i], values[i]);
}
return res;
}
switch (keys.length) {
case 0:
return () => [];
case 1:
return (a1) => buildMap([a1]);
case 2:
return (a1, a2) => buildMap([a1, a2]);
case 3:
return (a1, a2, a3) => buildMap([a1, a2, a3]);
case 4:
return (a1, a2, a3, a4) => buildMap([a1, a2, a3, a4]);
case 5:
return (a1, a2, a3, a4, a5) => buildMap([a1, a2, a3, a4, a5]);
case 6:
return (a1, a2, a3, a4, a5, a6) => buildMap([a1, a2, a3, a4, a5, a6]);
case 7:
return (a1, a2, a3, a4, a5, a6, a7) => buildMap([a1, a2, a3, a4, a5, a6, a7]);
case 8:
return (a1, a2, a3, a4, a5, a6, a7, a8) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8]);
case 9:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9) =>
buildMap([a1, a2, a3, a4, a5, a6, a7, a8, a9]);
default:
throw new BaseException(`Does not support literal maps with more than 9 elements`);
}
}
static keyedAccess(obj, args): any { return obj[args[0]]; }
static unwrapValue(value: any): any {
if (value instanceof WrappedValue) {
return value.wrapped;
} else {
return value;
}
}
static changeDetectionMode(strategy: string): string {
return isDefaultChangeDetectionStrategy(strategy) ? CHECK_ALWAYS : CHECK_ONCE;
}
static simpleChange(previousValue: any, currentValue: any): SimpleChange {
return _simpleChange(previousValue, currentValue);
}
static isValueBlank(value: any): boolean { return isBlank(value); }
static s(value: any): string { return isPresent(value) ? `${value}` : ''; }
static protoByIndex(protos: ProtoRecord[], selfIndex: number): ProtoRecord {
return selfIndex < 1 ?
null :
protos[selfIndex - 1]; // self index is shifted by one because of context
}
static callPipeOnDestroy(pipe: any): void {
if (implementsOnDestroy(pipe)) {
pipe.onDestroy();
}
}
static bindingTarget(mode: string, elementIndex: number, name: string, unit: string,
debug: string): BindingTarget {
return new BindingTarget(mode, elementIndex, name, unit, debug);
}
static directiveIndex(elementIndex: number, directiveIndex: number): DirectiveIndex {
return new DirectiveIndex(elementIndex, directiveIndex);
}
}

View File

@ -0,0 +1,39 @@
import {ChangeDetector} from './interfaces';
import {CHECK_ONCE, DETACHED, CHECK_ALWAYS} from './constants';
/**
* Controls change detection.
*
* {@link ChangeDetectorRef} allows requesting checks for detectors that rely on observables. It
* also allows detaching and attaching change detector subtrees.
*/
export class ChangeDetectorRef {
/**
* @private
*/
constructor(private _cd: ChangeDetector) {}
/**
* Request to check all ON_PUSH ancestors.
*/
requestCheck(): void { this._cd.markPathToRootAsCheckOnce(); }
/**
* Detaches the change detector from the change detector tree.
*
* The detached change detector will not be checked until it is reattached.
*/
detach(): void { this._cd.mode = DETACHED; }
/**
* Reattach the change detector to the change detector tree.
*
* This also requests a check of this change detector. This reattached change detector will be
*checked during the
* next change detection run.
*/
reattach(): void {
this._cd.mode = CHECK_ALWAYS;
this.requestCheck();
}
}

View File

@ -0,0 +1,81 @@
import {isPresent, isBlank, looseIdentical} from 'angular2/src/facade/lang';
import {List, ListWrapper, Map} from 'angular2/src/facade/collection';
import {RecordType, ProtoRecord} from './proto_record';
/**
* Removes "duplicate" records. It assuming that record evaluation does not
* have side-effects.
*
* Records that are not last in bindings are removed and all the indices
* of the records that depend on them are updated.
*
* Records that are last in bindings CANNOT be removed, and instead are
* replaced with very cheap SELF records.
*/
export function coalesce(records: ProtoRecord[]): ProtoRecord[] {
var res: List<ProtoRecord> = [];
var indexMap: Map<number, number> = new Map<number, number>();
for (var i = 0; i < records.length; ++i) {
var r = records[i];
var record = _replaceIndices(r, res.length + 1, indexMap);
var matchingRecord = _findMatching(record, res);
if (isPresent(matchingRecord) && record.lastInBinding) {
res.push(_selfRecord(record, matchingRecord.selfIndex, res.length + 1));
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
matchingRecord.referencedBySelf = true;
} else if (isPresent(matchingRecord) && !record.lastInBinding) {
if (record.argumentToPureFunction) {
matchingRecord.argumentToPureFunction = true;
}
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
} else {
res.push(record);
indexMap.set(r.selfIndex, record.selfIndex);
}
}
return res;
}
function _selfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): ProtoRecord {
return new ProtoRecord(RecordType.SELF, "self", null, [], r.fixedArgs, contextIndex,
r.directiveIndex, selfIndex, r.bindingRecord, r.lastInBinding,
r.lastInDirective, false, false, r.propertyBindingIndex);
}
function _findMatching(r: ProtoRecord, rs: List<ProtoRecord>) {
return ListWrapper.find(
rs, (rr) => rr.mode !== RecordType.DIRECTIVE_LIFECYCLE && _sameDirIndex(rr, r) &&
rr.mode === r.mode && looseIdentical(rr.funcOrValue, r.funcOrValue) &&
rr.contextIndex === r.contextIndex && looseIdentical(rr.name, r.name) &&
ListWrapper.equals(rr.args, r.args));
}
function _sameDirIndex(a: ProtoRecord, b: ProtoRecord): boolean {
var di1 = isBlank(a.directiveIndex) ? null : a.directiveIndex.directiveIndex;
var ei1 = isBlank(a.directiveIndex) ? null : a.directiveIndex.elementIndex;
var di2 = isBlank(b.directiveIndex) ? null : b.directiveIndex.directiveIndex;
var ei2 = isBlank(b.directiveIndex) ? null : b.directiveIndex.elementIndex;
return di1 === di2 && ei1 === ei2;
}
function _replaceIndices(r: ProtoRecord, selfIndex: number, indexMap: Map<any, any>) {
var args = ListWrapper.map(r.args, (a) => _map(indexMap, a));
var contextIndex = _map(indexMap, r.contextIndex);
return new ProtoRecord(r.mode, r.name, r.funcOrValue, args, r.fixedArgs, contextIndex,
r.directiveIndex, selfIndex, r.bindingRecord, r.lastInBinding,
r.lastInDirective, r.argumentToPureFunction, r.referencedBySelf,
r.propertyBindingIndex);
}
function _map(indexMap: Map<any, any>, value: number) {
var r = indexMap.get(value);
return isPresent(r) ? r : value;
}

View File

@ -0,0 +1,19 @@
library angular2.src.change_detection.codegen_facade;
import 'dart:convert' show JSON;
/// Converts `funcOrValue` to a string which can be used in generated code.
String codify(funcOrValue) => JSON.encode(funcOrValue).replaceAll(r'$', r'\$');
/// Combine the strings of generated code into a single interpolated string.
/// Each element of `vals` is expected to be a string literal or a codegen'd
/// call to a method returning a string.
/// The return format interpolates each value as an expression which reads
/// poorly, but the resulting code is easily flattened by dart2js.
String combineGeneratedStrings(List<String> vals) {
return '"${vals.map((v) => '\${$v}').join('')}"';
}
String rawString(String str) {
return "r'$str'";
}

View File

@ -0,0 +1,21 @@
import {List} from 'angular2/src/facade/collection';
/**
* Converts `funcOrValue` to a string which can be used in generated code.
*/
export function codify(obj: any): string {
return JSON.stringify(obj);
}
export function rawString(str: string): string {
return `'${str}'`;
}
/**
* Combine the strings of generated code into a single interpolated string.
* Each element of `vals` is expected to be a string literal or a codegen'd
* call to a method returning a string.
*/
export function combineGeneratedStrings(vals: string[]): string {
return vals.join(' + ');
}

View File

@ -0,0 +1,180 @@
import {ListWrapper} from 'angular2/src/facade/collection';
import {BaseException, Json, StringWrapper, isPresent, isBlank} from 'angular2/src/facade/lang';
import {CodegenNameUtil} from './codegen_name_util';
import {codify, combineGeneratedStrings, rawString} from './codegen_facade';
import {ProtoRecord, RecordType} from './proto_record';
import {BindingTarget} from './binding_record';
import {DirectiveRecord} from './directive_record';
import {ON_PUSH_OBSERVE} from './constants';
/**
* Class responsible for providing change detection logic for chagne detector classes.
*/
export class CodegenLogicUtil {
constructor(private _names: CodegenNameUtil, private _utilName: string,
private _changeDetection: string) {}
/**
* Generates a statement which updates the local variable representing `protoRec` with the current
* value of the record. Used by property bindings.
*/
genPropertyBindingEvalValue(protoRec: ProtoRecord): string {
return this.genEvalValue(protoRec, idx => this._names.getLocalName(idx),
this._names.getLocalsAccessorName());
}
/**
* Generates a statement which updates the local variable representing `protoRec` with the current
* value of the record. Used by event bindings.
*/
genEventBindingEvalValue(eventRecord: any, protoRec: ProtoRecord): string {
return this.genEvalValue(protoRec, idx => this._names.getEventLocalName(eventRecord, idx),
"locals");
}
private genEvalValue(protoRec: ProtoRecord, getLocalName: Function,
localsAccessor: string): string {
var context = (protoRec.contextIndex == -1) ?
this._names.getDirectiveName(protoRec.directiveIndex) :
getLocalName(protoRec.contextIndex);
var argString = ListWrapper.map(protoRec.args, (arg) => getLocalName(arg)).join(", ");
var rhs: string;
switch (protoRec.mode) {
case RecordType.SELF:
rhs = context;
break;
case RecordType.CONST:
rhs = codify(protoRec.funcOrValue);
break;
case RecordType.PROPERTY_READ:
rhs = this._observe(`${context}.${protoRec.name}`, protoRec);
break;
case RecordType.SAFE_PROPERTY:
var read = this._observe(`${context}.${protoRec.name}`, protoRec);
rhs =
`${this._utilName}.isValueBlank(${context}) ? null : ${this._observe(read, protoRec)}`;
break;
case RecordType.PROPERTY_WRITE:
rhs = `${context}.${protoRec.name} = ${getLocalName(protoRec.args[0])}`;
break;
case RecordType.LOCAL:
rhs = this._observe(`${localsAccessor}.get(${rawString(protoRec.name)})`, protoRec);
break;
case RecordType.INVOKE_METHOD:
rhs = this._observe(`${context}.${protoRec.name}(${argString})`, protoRec);
break;
case RecordType.SAFE_INVOKE_METHOD:
var invoke = `${context}.${protoRec.name}(${argString})`;
rhs =
`${this._utilName}.isValueBlank(${context}) ? null : ${this._observe(invoke, protoRec)}`;
break;
case RecordType.INVOKE_CLOSURE:
rhs = `${context}(${argString})`;
break;
case RecordType.PRIMITIVE_OP:
rhs = `${this._utilName}.${protoRec.name}(${argString})`;
break;
case RecordType.COLLECTION_LITERAL:
rhs = `${this._utilName}.${protoRec.name}(${argString})`;
break;
case RecordType.INTERPOLATE:
rhs = this._genInterpolation(protoRec);
break;
case RecordType.KEYED_READ:
rhs = this._observe(`${context}[${getLocalName(protoRec.args[0])}]`, protoRec);
break;
case RecordType.KEYED_WRITE:
rhs = `${context}[${getLocalName(protoRec.args[0])}] = ${getLocalName(protoRec.args[1])}`;
break;
case RecordType.CHAIN:
rhs = 'null';
break;
default:
throw new BaseException(`Unknown operation ${protoRec.mode}`);
}
return `${getLocalName(protoRec.selfIndex)} = ${rhs};`;
}
_observe(exp: string, rec: ProtoRecord): string {
// This is an experimental feature. Works only in Dart.
if (StringWrapper.equals(this._changeDetection, ON_PUSH_OBSERVE)) {
return `this.observeValue(${exp}, ${rec.selfIndex})`;
} else {
return exp;
}
}
genPropertyBindingTargets(propertyBindingTargets: BindingTarget[],
genDebugInfo: boolean): string {
var bs = propertyBindingTargets.map(b => {
if (isBlank(b)) return "null";
var debug = genDebugInfo ? codify(b.debug) : "null";
return `${this._utilName}.bindingTarget(${codify(b.mode)}, ${b.elementIndex}, ${codify(b.name)}, ${codify(b.unit)}, ${debug})`;
});
return `[${bs.join(", ")}]`;
}
genDirectiveIndices(directiveRecords: DirectiveRecord[]): string {
var bs = directiveRecords.map(
b =>
`${this._utilName}.directiveIndex(${b.directiveIndex.elementIndex}, ${b.directiveIndex.directiveIndex})`);
return `[${bs.join(", ")}]`;
}
_genInterpolation(protoRec: ProtoRecord): string {
var iVals = [];
for (var i = 0; i < protoRec.args.length; ++i) {
iVals.push(codify(protoRec.fixedArgs[i]));
iVals.push(`${this._utilName}.s(${this._names.getLocalName(protoRec.args[i])})`);
}
iVals.push(codify(protoRec.fixedArgs[protoRec.args.length]));
return combineGeneratedStrings(iVals);
}
genHydrateDirectives(directiveRecords: DirectiveRecord[]): string {
var res = [];
for (var i = 0; i < directiveRecords.length; ++i) {
var r = directiveRecords[i];
res.push(`${this._names.getDirectiveName(r.directiveIndex)} = ${this._genReadDirective(i)};`);
}
return res.join("\n");
}
private _genReadDirective(index: number) {
// This is an experimental feature. Works only in Dart.
if (StringWrapper.equals(this._changeDetection, ON_PUSH_OBSERVE)) {
return `this.observeDirective(this.getDirectiveFor(directives, ${index}), ${index})`;
} else {
return `this.getDirectiveFor(directives, ${index})`;
}
}
genHydrateDetectors(directiveRecords: DirectiveRecord[]): string {
var res = [];
for (var i = 0; i < directiveRecords.length; ++i) {
var r = directiveRecords[i];
if (!r.isDefaultChangeDetection()) {
res.push(
`${this._names.getDetectorName(r.directiveIndex)} = this.getDetectorFor(directives, ${i});`);
}
}
return res.join("\n");
}
}

View File

@ -0,0 +1,198 @@
import {RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, Map} from 'angular2/src/facade/collection';
import {DirectiveIndex} from './directive_record';
import {ProtoRecord} from './proto_record';
import {EventBinding} from './event_binding';
// The names of these fields must be kept in sync with abstract_change_detector.ts or change
// detection will fail.
const _ALREADY_CHECKED_ACCESSOR = "alreadyChecked";
const _CONTEXT_ACCESSOR = "context";
const _PROP_BINDING_INDEX = "propertyBindingIndex";
const _DIRECTIVES_ACCESSOR = "directiveIndices";
const _DISPATCHER_ACCESSOR = "dispatcher";
const _LOCALS_ACCESSOR = "locals";
const _MODE_ACCESSOR = "mode";
const _PIPES_ACCESSOR = "pipes";
const _PROTOS_ACCESSOR = "protos";
// `context` is always first.
export const CONTEXT_INDEX = 0;
const _FIELD_PREFIX = 'this.';
var _whiteSpaceRegExp = RegExpWrapper.create("\\W", "g");
/**
* Returns `s` with all non-identifier characters removed.
*/
export function sanitizeName(s: string): string {
return StringWrapper.replaceAll(s, _whiteSpaceRegExp, '');
}
/**
* Class responsible for providing field and local variable names for change detector classes.
* Also provides some convenience functions, for example, declaring variables, destroying pipes,
* and dehydrating the detector.
*/
export class CodegenNameUtil {
/**
* Record names sanitized for use as fields.
* See [sanitizeName] for details.
*/
_sanitizedNames: List<string>;
_sanitizedEventNames: Map<EventBinding, List<string>>;
constructor(private records: List<ProtoRecord>, private eventBindings: EventBinding[],
private directiveRecords: List<any>, private utilName: string) {
this._sanitizedNames = ListWrapper.createFixedSize(this.records.length + 1);
this._sanitizedNames[CONTEXT_INDEX] = _CONTEXT_ACCESSOR;
for (var i = 0, iLen = this.records.length; i < iLen; ++i) {
this._sanitizedNames[i + 1] = sanitizeName(`${this.records[i].name}${i}`);
}
this._sanitizedEventNames = new Map();
for (var ebIndex = 0; ebIndex < eventBindings.length; ++ebIndex) {
var eb = eventBindings[ebIndex];
var names = [_CONTEXT_ACCESSOR];
for (var i = 0, iLen = eb.records.length; i < iLen; ++i) {
names.push(sanitizeName(`${eb.records[i].name}${i}_${ebIndex}`));
}
this._sanitizedEventNames.set(eb, names);
}
}
_addFieldPrefix(name: string): string { return `${_FIELD_PREFIX}${name}`; }
getDispatcherName(): string { return this._addFieldPrefix(_DISPATCHER_ACCESSOR); }
getPipesAccessorName(): string { return this._addFieldPrefix(_PIPES_ACCESSOR); }
getProtosName(): string { return this._addFieldPrefix(_PROTOS_ACCESSOR); }
getDirectivesAccessorName(): string { return this._addFieldPrefix(_DIRECTIVES_ACCESSOR); }
getLocalsAccessorName(): string { return this._addFieldPrefix(_LOCALS_ACCESSOR); }
getAlreadyCheckedName(): string { return this._addFieldPrefix(_ALREADY_CHECKED_ACCESSOR); }
getModeName(): string { return this._addFieldPrefix(_MODE_ACCESSOR); }
getPropertyBindingIndex(): string { return this._addFieldPrefix(_PROP_BINDING_INDEX); }
getLocalName(idx: number): string { return `l_${this._sanitizedNames[idx]}`; }
getEventLocalName(eb: EventBinding, idx: number): string {
return `l_${MapWrapper.get(this._sanitizedEventNames, eb)[idx]}`;
}
getChangeName(idx: number): string { return `c_${this._sanitizedNames[idx]}`; }
/**
* Generate a statement initializing local variables used when detecting changes.
*/
genInitLocals(): string {
var declarations = [];
var assignments = [];
for (var i = 0, iLen = this.getFieldCount(); i < iLen; ++i) {
if (i == CONTEXT_INDEX) {
declarations.push(`${this.getLocalName(i)} = ${this.getFieldName(i)}`);
} else {
var rec = this.records[i - 1];
if (rec.argumentToPureFunction) {
var changeName = this.getChangeName(i);
declarations.push(`${this.getLocalName(i)},${changeName}`);
assignments.push(changeName);
} else {
declarations.push(`${this.getLocalName(i)}`);
}
}
}
var assignmentsCode =
ListWrapper.isEmpty(assignments) ? '' : `${ListWrapper.join(assignments, '=')} = false;`;
return `var ${ListWrapper.join(declarations, ',')};${assignmentsCode}`;
}
/**
* Generate a statement initializing local variables for event handlers.
*/
genInitEventLocals(): string {
var res = [`${this.getLocalName(CONTEXT_INDEX)} = ${this.getFieldName(CONTEXT_INDEX)}`];
MapWrapper.forEach(this._sanitizedEventNames, (names, eb) => {
for (var i = 0; i < names.length; ++i) {
if (i !== CONTEXT_INDEX) {
res.push(`${this.getEventLocalName(eb, i)}`);
}
}
});
return res.length > 1 ? `var ${res.join(',')};` : '';
}
getPreventDefaultAccesor(): string { return "preventDefault"; }
getFieldCount(): number { return this._sanitizedNames.length; }
getFieldName(idx: number): string { return this._addFieldPrefix(this._sanitizedNames[idx]); }
getAllFieldNames(): List<string> {
var fieldList = [];
for (var k = 0, kLen = this.getFieldCount(); k < kLen; ++k) {
if (k === 0 || this.records[k - 1].shouldBeChecked()) {
fieldList.push(this.getFieldName(k));
}
}
for (var i = 0, iLen = this.records.length; i < iLen; ++i) {
var rec = this.records[i];
if (rec.isPipeRecord()) {
fieldList.push(this.getPipeName(rec.selfIndex));
}
}
for (var j = 0, jLen = this.directiveRecords.length; j < jLen; ++j) {
var dRec = this.directiveRecords[j];
fieldList.push(this.getDirectiveName(dRec.directiveIndex));
if (!dRec.isDefaultChangeDetection()) {
fieldList.push(this.getDetectorName(dRec.directiveIndex));
}
}
return fieldList;
}
/**
* Generates statements which clear all fields so that the change detector is dehydrated.
*/
genDehydrateFields(): string {
var fields = this.getAllFieldNames();
ListWrapper.removeAt(fields, CONTEXT_INDEX);
if (ListWrapper.isEmpty(fields)) return '';
// At least one assignment.
fields.push(`${this.utilName}.uninitialized;`);
return ListWrapper.join(fields, ' = ');
}
/**
* Generates statements destroying all pipe variables.
*/
genPipeOnDestroy(): string {
return ListWrapper.join(
ListWrapper.map(
ListWrapper.filter(this.records, (r) => { return r.isPipeRecord(); }),
(r) => {
return `${this.utilName}.callPipeOnDestroy(${this.getPipeName(r.selfIndex)});`;
}),
'\n');
}
getPipeName(idx: number): string {
return this._addFieldPrefix(`${this._sanitizedNames[idx]}_pipe`);
}
getDirectiveName(d: DirectiveIndex): string {
return this._addFieldPrefix(`directive_${d.name}`);
}
getDetectorName(d: DirectiveIndex): string { return this._addFieldPrefix(`detector_${d.name}`); }
}

View File

@ -0,0 +1,46 @@
// TODO:vsavkin Use enums after switching to TypeScript
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/facade/lang';
/**
* CHECK_ONCE means that after calling detectChanges the mode of the change detector
* will become CHECKED.
*/
export const CHECK_ONCE: string = "CHECK_ONCE";
/**
* CHECKED means that the change detector should be skipped until its mode changes to
* CHECK_ONCE or CHECK_ALWAYS.
*/
export const CHECKED: string = "CHECKED";
/**
* CHECK_ALWAYS means that after calling detectChanges the mode of the change detector
* will remain CHECK_ALWAYS.
*/
export const CHECK_ALWAYS: string = "ALWAYS_CHECK";
/**
* DETACHED means that the change detector sub tree is not a part of the main tree and
* should be skipped.
*/
export const DETACHED: string = "DETACHED";
/**
* ON_PUSH means that the change detector's mode will be set to CHECK_ONCE during hydration.
*/
export const ON_PUSH: string = "ON_PUSH";
/**
* DEFAULT means that the change detector's mode will be set to CHECK_ALWAYS during hydration.
*/
export const DEFAULT: string = "DEFAULT";
export function isDefaultChangeDetectionStrategy(changeDetectionStrategy: string): boolean {
return isBlank(changeDetectionStrategy) || StringWrapper.equals(changeDetectionStrategy, DEFAULT);
}
/**
* This is an experimental feature. Works only in Dart.
*/
export const ON_PUSH_OBSERVE = "ON_PUSH_OBSERVE";

View File

@ -0,0 +1,620 @@
import {CONST, BaseException} from 'angular2/src/facade/lang';
import {
isListLikeIterable,
iterateListLike,
ListWrapper,
MapWrapper
} from 'angular2/src/facade/collection';
import {
isBlank,
isPresent,
stringify,
getMapKey,
looseIdentical,
isArray
} from 'angular2/src/facade/lang';
import {ChangeDetectorRef} from '../change_detector_ref';
import {IterableDiffer, IterableDifferFactory} from '../differs/iterable_differs';
@CONST()
export class DefaultIterableDifferFactory implements IterableDifferFactory {
supports(obj: Object): boolean { return isListLikeIterable(obj); }
create(cdRef: ChangeDetectorRef): any { return new DefaultIterableDiffer(); }
}
export class DefaultIterableDiffer implements IterableDiffer {
private _collection = null;
private _length: number = null;
// Keeps track of the used records at any point in time (during & across `_check()` calls)
private _linkedRecords: _DuplicateMap = null;
// Keeps track of the removed records at any point in time during `_check()` calls.
private _unlinkedRecords: _DuplicateMap = null;
private _previousItHead: CollectionChangeRecord = null;
private _itHead: CollectionChangeRecord = null;
private _itTail: CollectionChangeRecord = null;
private _additionsHead: CollectionChangeRecord = null;
private _additionsTail: CollectionChangeRecord = null;
private _movesHead: CollectionChangeRecord = null;
private _movesTail: CollectionChangeRecord = null;
private _removalsHead: CollectionChangeRecord = null;
private _removalsTail: CollectionChangeRecord = null;
get collection() { return this._collection; }
get length(): number { return this._length; }
forEachItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._itHead; record !== null; record = record._next) {
fn(record);
}
}
forEachPreviousItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._previousItHead; record !== null; record = record._nextPrevious) {
fn(record);
}
}
forEachAddedItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
fn(record);
}
}
forEachMovedItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._movesHead; record !== null; record = record._nextMoved) {
fn(record);
}
}
forEachRemovedItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
fn(record);
}
}
diff(collection: any): DefaultIterableDiffer {
if (isBlank(collection)) collection = [];
if (!isListLikeIterable(collection)) {
throw new BaseException(`Error trying to diff '${collection}'`);
}
if (this.check(collection)) {
return this;
} else {
return null;
}
}
onDestroy() {}
// todo(vicb): optim for UnmodifiableListView (frozen arrays)
check(collection: any): boolean {
this._reset();
var record: CollectionChangeRecord = this._itHead;
var mayBeDirty: boolean = false;
var index: number;
var item;
if (isArray(collection)) {
var list = collection;
this._length = collection.length;
for (index = 0; index < this._length; index++) {
item = list[index];
if (record === null || !looseIdentical(record.item, item)) {
record = this._mismatch(record, item, index);
mayBeDirty = true;
} else if (mayBeDirty) {
// TODO(misko): can we limit this to duplicates only?
record = this._verifyReinsertion(record, item, index);
}
record = record._next;
}
} else {
index = 0;
iterateListLike(collection, (item) => {
if (record === null || !looseIdentical(record.item, item)) {
record = this._mismatch(record, item, index);
mayBeDirty = true;
} else if (mayBeDirty) {
// TODO(misko): can we limit this to duplicates only?
record = this._verifyReinsertion(record, item, index);
}
record = record._next;
index++;
});
this._length = index;
}
this._truncate(record);
this._collection = collection;
return this.isDirty;
}
// CollectionChanges is considered dirty if it has any additions, moves or removals.
get isDirty(): boolean {
return this._additionsHead !== null || this._movesHead !== null || this._removalsHead !== null;
}
/**
* Reset the state of the change objects to show no changes. This means set previousKey to
* currentKey, and clear all of the queues (additions, moves, removals).
* Set the previousIndexes of moved and added items to their currentIndexes
* Reset the list of additions, moves and removals
*/
_reset() {
if (this.isDirty) {
var record: CollectionChangeRecord;
var nextRecord: CollectionChangeRecord;
for (record = this._previousItHead = this._itHead; record !== null; record = record._next) {
record._nextPrevious = record._next;
}
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
record.previousIndex = record.currentIndex;
}
this._additionsHead = this._additionsTail = null;
for (record = this._movesHead; record !== null; record = nextRecord) {
record.previousIndex = record.currentIndex;
nextRecord = record._nextMoved;
}
this._movesHead = this._movesTail = null;
this._removalsHead = this._removalsTail = null;
// todo(vicb) when assert gets supported
// assert(!this.isDirty);
}
}
/**
* This is the core function which handles differences between collections.
*
* - `record` is the record which we saw at this position last time. If null then it is a new
* item.
* - `item` is the current item in the collection
* - `index` is the position of the item in the collection
*/
_mismatch(record: CollectionChangeRecord, item, index: number): CollectionChangeRecord {
// The previous record after which we will append the current one.
var previousRecord: CollectionChangeRecord;
if (record === null) {
previousRecord = this._itTail;
} else {
previousRecord = record._prev;
// Remove the record from the collection since we know it does not match the item.
this._remove(record);
}
// Attempt to see if we have seen the item before.
record = this._linkedRecords === null ? null : this._linkedRecords.get(item, index);
if (record !== null) {
// We have seen this before, we need to move it forward in the collection.
this._moveAfter(record, previousRecord, index);
} else {
// Never seen it, check evicted list.
record = this._unlinkedRecords === null ? null : this._unlinkedRecords.get(item);
if (record !== null) {
// It is an item which we have evicted earlier: reinsert it back into the list.
this._reinsertAfter(record, previousRecord, index);
} else {
// It is a new item: add it.
record = this._addAfter(new CollectionChangeRecord(item), previousRecord, index);
}
}
return record;
}
/**
* This check is only needed if an array contains duplicates. (Short circuit of nothing dirty)
*
* Use case: `[a, a]` => `[b, a, a]`
*
* If we did not have this check then the insertion of `b` would:
* 1) evict first `a`
* 2) insert `b` at `0` index.
* 3) leave `a` at index `1` as is. <-- this is wrong!
* 3) reinsert `a` at index 2. <-- this is wrong!
*
* The correct behavior is:
* 1) evict first `a`
* 2) insert `b` at `0` index.
* 3) reinsert `a` at index 1.
* 3) move `a` at from `1` to `2`.
*
*
* Double check that we have not evicted a duplicate item. We need to check if the item type may
* have already been removed:
* The insertion of b will evict the first 'a'. If we don't reinsert it now it will be reinserted
* at the end. Which will show up as the two 'a's switching position. This is incorrect, since a
* better way to think of it is as insert of 'b' rather then switch 'a' with 'b' and then add 'a'
* at the end.
*/
_verifyReinsertion(record: CollectionChangeRecord, item, index: number): CollectionChangeRecord {
var reinsertRecord: CollectionChangeRecord =
this._unlinkedRecords === null ? null : this._unlinkedRecords.get(item);
if (reinsertRecord !== null) {
record = this._reinsertAfter(reinsertRecord, record._prev, index);
} else if (record.currentIndex != index) {
record.currentIndex = index;
this._addToMoves(record, index);
}
return record;
}
/**
* Get rid of any excess {@link CollectionChangeRecord}s from the previous collection
*
* - `record` The first excess {@link CollectionChangeRecord}.
*/
_truncate(record: CollectionChangeRecord) {
// Anything after that needs to be removed;
while (record !== null) {
var nextRecord: CollectionChangeRecord = record._next;
this._addToRemovals(this._unlink(record));
record = nextRecord;
}
if (this._unlinkedRecords !== null) {
this._unlinkedRecords.clear();
}
if (this._additionsTail !== null) {
this._additionsTail._nextAdded = null;
}
if (this._movesTail !== null) {
this._movesTail._nextMoved = null;
}
if (this._itTail !== null) {
this._itTail._next = null;
}
if (this._removalsTail !== null) {
this._removalsTail._nextRemoved = null;
}
}
_reinsertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index: number): CollectionChangeRecord {
if (this._unlinkedRecords !== null) {
this._unlinkedRecords.remove(record);
}
var prev = record._prevRemoved;
var next = record._nextRemoved;
if (prev === null) {
this._removalsHead = next;
} else {
prev._nextRemoved = next;
}
if (next === null) {
this._removalsTail = prev;
} else {
next._prevRemoved = prev;
}
this._insertAfter(record, prevRecord, index);
this._addToMoves(record, index);
return record;
}
_moveAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index: number): CollectionChangeRecord {
this._unlink(record);
this._insertAfter(record, prevRecord, index);
this._addToMoves(record, index);
return record;
}
_addAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index: number): CollectionChangeRecord {
this._insertAfter(record, prevRecord, index);
if (this._additionsTail === null) {
// todo(vicb)
// assert(this._additionsHead === null);
this._additionsTail = this._additionsHead = record;
} else {
// todo(vicb)
// assert(_additionsTail._nextAdded === null);
// assert(record._nextAdded === null);
this._additionsTail = this._additionsTail._nextAdded = record;
}
return record;
}
_insertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index: number): CollectionChangeRecord {
// todo(vicb)
// assert(record != prevRecord);
// assert(record._next === null);
// assert(record._prev === null);
var next: CollectionChangeRecord = prevRecord === null ? this._itHead : prevRecord._next;
// todo(vicb)
// assert(next != record);
// assert(prevRecord != record);
record._next = next;
record._prev = prevRecord;
if (next === null) {
this._itTail = record;
} else {
next._prev = record;
}
if (prevRecord === null) {
this._itHead = record;
} else {
prevRecord._next = record;
}
if (this._linkedRecords === null) {
this._linkedRecords = new _DuplicateMap();
}
this._linkedRecords.put(record);
record.currentIndex = index;
return record;
}
_remove(record: CollectionChangeRecord): CollectionChangeRecord {
return this._addToRemovals(this._unlink(record));
}
_unlink(record: CollectionChangeRecord): CollectionChangeRecord {
if (this._linkedRecords !== null) {
this._linkedRecords.remove(record);
}
var prev = record._prev;
var next = record._next;
// todo(vicb)
// assert((record._prev = null) === null);
// assert((record._next = null) === null);
if (prev === null) {
this._itHead = next;
} else {
prev._next = next;
}
if (next === null) {
this._itTail = prev;
} else {
next._prev = prev;
}
return record;
}
_addToMoves(record: CollectionChangeRecord, toIndex: number): CollectionChangeRecord {
// todo(vicb)
// assert(record._nextMoved === null);
if (record.previousIndex === toIndex) {
return record;
}
if (this._movesTail === null) {
// todo(vicb)
// assert(_movesHead === null);
this._movesTail = this._movesHead = record;
} else {
// todo(vicb)
// assert(_movesTail._nextMoved === null);
this._movesTail = this._movesTail._nextMoved = record;
}
return record;
}
_addToRemovals(record: CollectionChangeRecord): CollectionChangeRecord {
if (this._unlinkedRecords === null) {
this._unlinkedRecords = new _DuplicateMap();
}
this._unlinkedRecords.put(record);
record.currentIndex = null;
record._nextRemoved = null;
if (this._removalsTail === null) {
// todo(vicb)
// assert(_removalsHead === null);
this._removalsTail = this._removalsHead = record;
record._prevRemoved = null;
} else {
// todo(vicb)
// assert(_removalsTail._nextRemoved === null);
// assert(record._nextRemoved === null);
record._prevRemoved = this._removalsTail;
this._removalsTail = this._removalsTail._nextRemoved = record;
}
return record;
}
toString(): string {
var record: CollectionChangeRecord;
var list = [];
for (record = this._itHead; record !== null; record = record._next) {
list.push(record);
}
var previous = [];
for (record = this._previousItHead; record !== null; record = record._nextPrevious) {
previous.push(record);
}
var additions = [];
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
additions.push(record);
}
var moves = [];
for (record = this._movesHead; record !== null; record = record._nextMoved) {
moves.push(record);
}
var removals = [];
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
removals.push(record);
}
return "collection: " + list.join(', ') + "\n" + "previous: " + previous.join(', ') + "\n" +
"additions: " + additions.join(', ') + "\n" + "moves: " + moves.join(', ') + "\n" +
"removals: " + removals.join(', ') + "\n";
}
}
export class CollectionChangeRecord {
currentIndex: number = null;
previousIndex: number = null;
_nextPrevious: CollectionChangeRecord = null;
_prev: CollectionChangeRecord = null;
_next: CollectionChangeRecord = null;
_prevDup: CollectionChangeRecord = null;
_nextDup: CollectionChangeRecord = null;
_prevRemoved: CollectionChangeRecord = null;
_nextRemoved: CollectionChangeRecord = null;
_nextAdded: CollectionChangeRecord = null;
_nextMoved: CollectionChangeRecord = null;
constructor(public item: any) {}
toString(): string {
return this.previousIndex === this.currentIndex ?
stringify(this.item) :
stringify(this.item) + '[' + stringify(this.previousIndex) + '->' +
stringify(this.currentIndex) + ']';
}
}
// A linked list of CollectionChangeRecords with the same CollectionChangeRecord.item
class _DuplicateItemRecordList {
_head: CollectionChangeRecord = null;
_tail: CollectionChangeRecord = null;
/**
* Append the record to the list of duplicates.
*
* Note: by design all records in the list of duplicates hold the same value in record.item.
*/
add(record: CollectionChangeRecord): void {
if (this._head === null) {
this._head = this._tail = record;
record._nextDup = null;
record._prevDup = null;
} else {
// todo(vicb)
// assert(record.item == _head.item ||
// record.item is num && record.item.isNaN && _head.item is num && _head.item.isNaN);
this._tail._nextDup = record;
record._prevDup = this._tail;
record._nextDup = null;
this._tail = record;
}
}
// Returns a CollectionChangeRecord having CollectionChangeRecord.item == item and
// CollectionChangeRecord.currentIndex >= afterIndex
get(item: any, afterIndex: number): CollectionChangeRecord {
var record: CollectionChangeRecord;
for (record = this._head; record !== null; record = record._nextDup) {
if ((afterIndex === null || afterIndex < record.currentIndex) &&
looseIdentical(record.item, item)) {
return record;
}
}
return null;
}
/**
* Remove one {@link CollectionChangeRecord} from the list of duplicates.
*
* Returns whether the list of duplicates is empty.
*/
remove(record: CollectionChangeRecord): boolean {
// todo(vicb)
// assert(() {
// // verify that the record being removed is in the list.
// for (CollectionChangeRecord cursor = _head; cursor != null; cursor = cursor._nextDup) {
// if (identical(cursor, record)) return true;
// }
// return false;
//});
var prev: CollectionChangeRecord = record._prevDup;
var next: CollectionChangeRecord = record._nextDup;
if (prev === null) {
this._head = next;
} else {
prev._nextDup = next;
}
if (next === null) {
this._tail = prev;
} else {
next._prevDup = prev;
}
return this._head === null;
}
}
class _DuplicateMap {
map: Map<any, _DuplicateItemRecordList> = new Map();
put(record: CollectionChangeRecord) {
// todo(vicb) handle corner cases
var key = getMapKey(record.item);
var duplicates = this.map.get(key);
if (!isPresent(duplicates)) {
duplicates = new _DuplicateItemRecordList();
this.map.set(key, duplicates);
}
duplicates.add(record);
}
/**
* Retrieve the `value` using key. Because the CollectionChangeRecord value maybe one which we
* have already iterated over, we use the afterIndex to pretend it is not there.
*
* Use case: `[a, b, c, a, a]` if we are at index `3` which is the second `a` then asking if we
* have any more `a`s needs to return the last `a` not the first or second.
*/
get(value: any, afterIndex: number = null): CollectionChangeRecord {
var key = getMapKey(value);
var recordList = this.map.get(key);
return isBlank(recordList) ? null : recordList.get(value, afterIndex);
}
/**
* Removes a {@link CollectionChangeRecord} from the list of duplicates.
*
* The list of duplicates also is removed from the map if it gets empty.
*/
remove(record: CollectionChangeRecord): CollectionChangeRecord {
var key = getMapKey(record.item);
// todo(vicb)
// assert(this.map.containsKey(key));
var recordList: _DuplicateItemRecordList = this.map.get(key);
// Remove the list of duplicates when it gets empty
if (recordList.remove(record)) {
MapWrapper.delete(this.map, key);
}
return record;
}
get isEmpty(): boolean { return MapWrapper.size(this.map) === 0; }
clear() { this.map.clear(); }
toString(): string { return '_DuplicateMap(' + stringify(this.map) + ')'; }
}

View File

@ -0,0 +1,353 @@
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {
stringify,
looseIdentical,
isJsObject,
CONST,
isBlank,
BaseException
} from 'angular2/src/facade/lang';
import {ChangeDetectorRef} from '../change_detector_ref';
import {KeyValueDiffer, KeyValueDifferFactory} from '../differs/keyvalue_differs';
@CONST()
export class DefaultKeyValueDifferFactory implements KeyValueDifferFactory {
supports(obj: any): boolean { return obj instanceof Map || isJsObject(obj); }
create(cdRef: ChangeDetectorRef): KeyValueDiffer { return new DefaultKeyValueDiffer(); }
}
export class DefaultKeyValueDiffer implements KeyValueDiffer {
private _records: Map<any, any> = new Map();
private _mapHead: KVChangeRecord = null;
private _previousMapHead: KVChangeRecord = null;
private _changesHead: KVChangeRecord = null;
private _changesTail: KVChangeRecord = null;
private _additionsHead: KVChangeRecord = null;
private _additionsTail: KVChangeRecord = null;
private _removalsHead: KVChangeRecord = null;
private _removalsTail: KVChangeRecord = null;
get isDirty(): boolean {
return this._additionsHead !== null || this._changesHead !== null ||
this._removalsHead !== null;
}
forEachItem(fn: Function) {
var record: KVChangeRecord;
for (record = this._mapHead; record !== null; record = record._next) {
fn(record);
}
}
forEachPreviousItem(fn: Function) {
var record: KVChangeRecord;
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
fn(record);
}
}
forEachChangedItem(fn: Function) {
var record: KVChangeRecord;
for (record = this._changesHead; record !== null; record = record._nextChanged) {
fn(record);
}
}
forEachAddedItem(fn: Function) {
var record: KVChangeRecord;
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
fn(record);
}
}
forEachRemovedItem(fn: Function) {
var record: KVChangeRecord;
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
fn(record);
}
}
diff(map: Map<any, any>): any {
if (isBlank(map)) map = MapWrapper.createFromPairs([]);
if (!(map instanceof Map || isJsObject(map))) {
throw new BaseException(`Error trying to diff '${map}'`);
}
if (this.check(map)) {
return this;
} else {
return null;
}
}
onDestroy() {}
check(map: Map<any, any>): boolean {
this._reset();
var records = this._records;
var oldSeqRecord: KVChangeRecord = this._mapHead;
var lastOldSeqRecord: KVChangeRecord = null;
var lastNewSeqRecord: KVChangeRecord = null;
var seqChanged: boolean = false;
this._forEach(map, (value, key) => {
var newSeqRecord;
if (oldSeqRecord !== null && key === oldSeqRecord.key) {
newSeqRecord = oldSeqRecord;
if (!looseIdentical(value, oldSeqRecord.currentValue)) {
oldSeqRecord.previousValue = oldSeqRecord.currentValue;
oldSeqRecord.currentValue = value;
this._addToChanges(oldSeqRecord);
}
} else {
seqChanged = true;
if (oldSeqRecord !== null) {
oldSeqRecord._next = null;
this._removeFromSeq(lastOldSeqRecord, oldSeqRecord);
this._addToRemovals(oldSeqRecord);
}
if (records.has(key)) {
newSeqRecord = records.get(key);
} else {
newSeqRecord = new KVChangeRecord(key);
records.set(key, newSeqRecord);
newSeqRecord.currentValue = value;
this._addToAdditions(newSeqRecord);
}
}
if (seqChanged) {
if (this._isInRemovals(newSeqRecord)) {
this._removeFromRemovals(newSeqRecord);
}
if (lastNewSeqRecord == null) {
this._mapHead = newSeqRecord;
} else {
lastNewSeqRecord._next = newSeqRecord;
}
}
lastOldSeqRecord = oldSeqRecord;
lastNewSeqRecord = newSeqRecord;
oldSeqRecord = oldSeqRecord === null ? null : oldSeqRecord._next;
});
this._truncate(lastOldSeqRecord, oldSeqRecord);
return this.isDirty;
}
_reset() {
if (this.isDirty) {
var record: KVChangeRecord;
// Record the state of the mapping
for (record = this._previousMapHead = this._mapHead; record !== null; record = record._next) {
record._nextPrevious = record._next;
}
for (record = this._changesHead; record !== null; record = record._nextChanged) {
record.previousValue = record.currentValue;
}
for (record = this._additionsHead; record != null; record = record._nextAdded) {
record.previousValue = record.currentValue;
}
// todo(vicb) once assert is supported
// assert(() {
// var r = _changesHead;
// while (r != null) {
// var nextRecord = r._nextChanged;
// r._nextChanged = null;
// r = nextRecord;
// }
//
// r = _additionsHead;
// while (r != null) {
// var nextRecord = r._nextAdded;
// r._nextAdded = null;
// r = nextRecord;
// }
//
// r = _removalsHead;
// while (r != null) {
// var nextRecord = r._nextRemoved;
// r._nextRemoved = null;
// r = nextRecord;
// }
//
// return true;
//});
this._changesHead = this._changesTail = null;
this._additionsHead = this._additionsTail = null;
this._removalsHead = this._removalsTail = null;
}
}
_truncate(lastRecord: KVChangeRecord, record: KVChangeRecord) {
while (record !== null) {
if (lastRecord === null) {
this._mapHead = null;
} else {
lastRecord._next = null;
}
var nextRecord = record._next;
// todo(vicb) assert
// assert((() {
// record._next = null;
// return true;
//}));
this._addToRemovals(record);
lastRecord = record;
record = nextRecord;
}
for (var rec: KVChangeRecord = this._removalsHead; rec !== null; rec = rec._nextRemoved) {
rec.previousValue = rec.currentValue;
rec.currentValue = null;
MapWrapper.delete(this._records, rec.key);
}
}
_isInRemovals(record: KVChangeRecord) {
return record === this._removalsHead || record._nextRemoved !== null ||
record._prevRemoved !== null;
}
_addToRemovals(record: KVChangeRecord) {
// todo(vicb) assert
// assert(record._next == null);
// assert(record._nextAdded == null);
// assert(record._nextChanged == null);
// assert(record._nextRemoved == null);
// assert(record._prevRemoved == null);
if (this._removalsHead === null) {
this._removalsHead = this._removalsTail = record;
} else {
this._removalsTail._nextRemoved = record;
record._prevRemoved = this._removalsTail;
this._removalsTail = record;
}
}
_removeFromSeq(prev: KVChangeRecord, record: KVChangeRecord) {
var next = record._next;
if (prev === null) {
this._mapHead = next;
} else {
prev._next = next;
}
// todo(vicb) assert
// assert((() {
// record._next = null;
// return true;
//})());
}
_removeFromRemovals(record: KVChangeRecord) {
// todo(vicb) assert
// assert(record._next == null);
// assert(record._nextAdded == null);
// assert(record._nextChanged == null);
var prev = record._prevRemoved;
var next = record._nextRemoved;
if (prev === null) {
this._removalsHead = next;
} else {
prev._nextRemoved = next;
}
if (next === null) {
this._removalsTail = prev;
} else {
next._prevRemoved = prev;
}
record._prevRemoved = record._nextRemoved = null;
}
_addToAdditions(record: KVChangeRecord) {
// todo(vicb): assert
// assert(record._next == null);
// assert(record._nextAdded == null);
// assert(record._nextChanged == null);
// assert(record._nextRemoved == null);
// assert(record._prevRemoved == null);
if (this._additionsHead === null) {
this._additionsHead = this._additionsTail = record;
} else {
this._additionsTail._nextAdded = record;
this._additionsTail = record;
}
}
_addToChanges(record: KVChangeRecord) {
// todo(vicb) assert
// assert(record._nextAdded == null);
// assert(record._nextChanged == null);
// assert(record._nextRemoved == null);
// assert(record._prevRemoved == null);
if (this._changesHead === null) {
this._changesHead = this._changesTail = record;
} else {
this._changesTail._nextChanged = record;
this._changesTail = record;
}
}
toString(): string {
var items = [];
var previous = [];
var changes = [];
var additions = [];
var removals = [];
var record: KVChangeRecord;
for (record = this._mapHead; record !== null; record = record._next) {
items.push(stringify(record));
}
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
previous.push(stringify(record));
}
for (record = this._changesHead; record !== null; record = record._nextChanged) {
changes.push(stringify(record));
}
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
additions.push(stringify(record));
}
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
removals.push(stringify(record));
}
return "map: " + items.join(', ') + "\n" + "previous: " + previous.join(', ') + "\n" +
"additions: " + additions.join(', ') + "\n" + "changes: " + changes.join(', ') + "\n" +
"removals: " + removals.join(', ') + "\n";
}
_forEach(obj, fn: Function) {
if (obj instanceof Map) {
MapWrapper.forEach(obj, fn);
} else {
StringMapWrapper.forEach(obj, fn);
}
}
}
export class KVChangeRecord {
previousValue: any = null;
currentValue: any = null;
_nextPrevious: KVChangeRecord = null;
_next: KVChangeRecord = null;
_nextAdded: KVChangeRecord = null;
_nextRemoved: KVChangeRecord = null;
_prevRemoved: KVChangeRecord = null;
_nextChanged: KVChangeRecord = null;
constructor(public key: any) {}
toString(): string {
return looseIdentical(this.previousValue, this.currentValue) ?
stringify(this.key) :
(stringify(this.key) + '[' + stringify(this.previousValue) + '->' +
stringify(this.currentValue) + ']');
}
}

View File

@ -0,0 +1,80 @@
import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {ChangeDetectorRef} from '../change_detector_ref';
import {Binding, SkipSelfMetadata, OptionalMetadata, Injectable} from 'angular2/di';
export interface IterableDiffer {
diff(object: Object): any;
onDestroy();
}
/**
* Provides a factory for {@link IterableDiffer}.
*/
export interface IterableDifferFactory {
supports(objects: Object): boolean;
create(cdRef: ChangeDetectorRef): IterableDiffer;
}
/**
* A repository of different iterable diffing strategies used by NgFor, NgClass, and others.
*/
@Injectable()
@CONST()
export class IterableDiffers {
constructor(public factories: IterableDifferFactory[]) {}
static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers {
if (isPresent(parent)) {
var copied = ListWrapper.clone(parent.factories);
factories = factories.concat(copied);
return new IterableDiffers(factories);
} else {
return new IterableDiffers(factories);
}
}
/**
* Takes an array of {@link IterableDifferFactory} and returns a binding used to extend the
* inherited {@link IterableDiffers} instance with the provided factories and return a new
* {@link IterableDiffers} instance.
*
* The following example shows how to extend an existing list of factories,
* which will only be applied to the injector for this component and its children.
* This step is all that's required to make a new {@link IterableDiffer} available.
*
* # Example
*
* ```
* @Component({
* viewBindings: [
* IterableDiffers.extend([new ImmutableListDiffer()])
* ]
* })
* ```
*/
static extend(factories: IterableDifferFactory[]): Binding {
return new Binding(IterableDiffers, {
toFactory: (parent: IterableDiffers) => {
if (isBlank(parent)) {
// Typically would occur when calling IterableDiffers.extend inside of dependencies passed
// to
// bootstrap(), which would override default pipes instead of extending them.
throw new BaseException('Cannot extend IterableDiffers without a parent injector');
}
return IterableDiffers.create(factories, parent);
},
// Dependency technically isn't optional, but we can provide a better error message this way.
deps: [[IterableDiffers, new SkipSelfMetadata(), new OptionalMetadata()]]
});
}
find(iterable: Object): IterableDifferFactory {
var factory = ListWrapper.find(this.factories, f => f.supports(iterable));
if (isPresent(factory)) {
return factory;
} else {
throw new BaseException(`Cannot find a differ supporting object '${iterable}'`);
}
}
}

View File

@ -0,0 +1,80 @@
import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {ChangeDetectorRef} from '../change_detector_ref';
import {Binding, SkipSelfMetadata, OptionalMetadata, Injectable} from 'angular2/di';
export interface KeyValueDiffer {
diff(object: Object);
onDestroy();
}
/**
* Provides a factory for {@link KeyValueDiffer}.
*/
export interface KeyValueDifferFactory {
supports(objects: Object): boolean;
create(cdRef: ChangeDetectorRef): KeyValueDiffer;
}
/**
* A repository of different Map diffing strategies used by NgClass, NgStyle, and others.
*/
@Injectable()
@CONST()
export class KeyValueDiffers {
constructor(public factories: KeyValueDifferFactory[]) {}
static create(factories: KeyValueDifferFactory[], parent?: KeyValueDiffers): KeyValueDiffers {
if (isPresent(parent)) {
var copied = ListWrapper.clone(parent.factories);
factories = factories.concat(copied);
return new KeyValueDiffers(factories);
} else {
return new KeyValueDiffers(factories);
}
}
/**
* Takes an array of {@link KeyValueDifferFactory} and returns a binding used to extend the
* inherited {@link KeyValueDiffers} instance with the provided factories and return a new
* {@link KeyValueDiffers} instance.
*
* The following example shows how to extend an existing list of factories,
* which will only be applied to the injector for this component and its children.
* This step is all that's required to make a new {@link KeyValueDiffer} available.
*
* # Example
*
* ```
* @Component({
* viewBindings: [
* KeyValueDiffers.extend([new ImmutableMapDiffer()])
* ]
* })
* ```
*/
static extend(factories: KeyValueDifferFactory[]): Binding {
return new Binding(KeyValueDiffers, {
toFactory: (parent: KeyValueDiffers) => {
if (isBlank(parent)) {
// Typically would occur when calling KeyValueDiffers.extend inside of dependencies passed
// to
// bootstrap(), which would override default pipes instead of extending them.
throw new BaseException('Cannot extend KeyValueDiffers without a parent injector');
}
return KeyValueDiffers.create(factories, parent);
},
// Dependency technically isn't optional, but we can provide a better error message this way.
deps: [[KeyValueDiffers, new SkipSelfMetadata(), new OptionalMetadata()]]
});
}
find(kv: Object): KeyValueDifferFactory {
var factory = ListWrapper.find(this.factories, f => f.supports(kv));
if (isPresent(factory)) {
return factory;
} else {
throw new BaseException(`Cannot find a differ supporting object '${kv}'`);
}
}
}

View File

@ -0,0 +1,38 @@
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/facade/lang';
import {isDefaultChangeDetectionStrategy} from './constants';
export class DirectiveIndex {
constructor(public elementIndex: number, public directiveIndex: number) {}
get name() { return `${this.elementIndex}_${this.directiveIndex}`; }
}
export class DirectiveRecord {
directiveIndex: DirectiveIndex;
callOnAllChangesDone: boolean;
callOnChange: boolean;
callOnCheck: boolean;
callOnInit: boolean;
changeDetection: string;
constructor({directiveIndex, callOnAllChangesDone, callOnChange, callOnCheck, callOnInit,
changeDetection}: {
directiveIndex?: DirectiveIndex,
callOnAllChangesDone?: boolean,
callOnChange?: boolean,
callOnCheck?: boolean,
callOnInit?: boolean,
changeDetection?: string
} = {}) {
this.directiveIndex = directiveIndex;
this.callOnAllChangesDone = normalizeBool(callOnAllChangesDone);
this.callOnChange = normalizeBool(callOnChange);
this.callOnCheck = normalizeBool(callOnCheck);
this.callOnInit = normalizeBool(callOnInit);
this.changeDetection = changeDetection;
}
isDefaultChangeDetection(): boolean {
return isDefaultChangeDetectionStrategy(this.changeDetection);
}
}

View File

@ -0,0 +1,417 @@
import {
isPresent,
isBlank,
BaseException,
FunctionWrapper,
StringWrapper
} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {AbstractChangeDetector} from './abstract_change_detector';
import {EventBinding} from './event_binding';
import {BindingRecord, BindingTarget} from './binding_record';
import {DirectiveRecord, DirectiveIndex} from './directive_record';
import {Locals} from './parser/locals';
import {ChangeDetectorGenConfig} from './interfaces';
import {ChangeDetectionUtil, SimpleChange} from './change_detection_util';
import {ON_PUSH_OBSERVE} from './constants';
import {ProtoRecord, RecordType} from './proto_record';
export class DynamicChangeDetector extends AbstractChangeDetector<any> {
values: List<any>;
changes: List<any>;
localPipes: List<any>;
prevContexts: List<any>;
directives: any = null;
constructor(id: string, dispatcher: any, numberOfPropertyProtoRecords: number,
propertyBindingTargets: BindingTarget[], directiveIndices: DirectiveIndex[],
strategy: string, private records: ProtoRecord[],
private eventBindings: EventBinding[], private directiveRecords: DirectiveRecord[],
private genConfig: ChangeDetectorGenConfig) {
super(id, dispatcher, numberOfPropertyProtoRecords, propertyBindingTargets, directiveIndices,
strategy);
var len = records.length + 1;
this.values = ListWrapper.createFixedSize(len);
this.localPipes = ListWrapper.createFixedSize(len);
this.prevContexts = ListWrapper.createFixedSize(len);
this.changes = ListWrapper.createFixedSize(len);
this.dehydrateDirectives(false);
}
handleEventInternal(eventName: string, elIndex: number, locals: Locals): boolean {
var preventDefault = false;
this._matchingEventBindings(eventName, elIndex)
.forEach(rec => {
var res = this._processEventBinding(rec, locals);
if (res === false) {
preventDefault = true;
}
});
return preventDefault;
}
_processEventBinding(eb: EventBinding, locals: Locals): any {
var values = ListWrapper.createFixedSize(eb.records.length);
values[0] = this.values[0];
for (var i = 0; i < eb.records.length; ++i) {
var proto = eb.records[i];
var res = this._calculateCurrValue(proto, values, locals);
if (proto.lastInBinding) {
this._markPathAsCheckOnce(proto);
return res;
} else {
this._writeSelf(proto, res, values);
}
}
throw new BaseException("Cannot be reached");
}
_markPathAsCheckOnce(proto: ProtoRecord): void {
if (!proto.bindingRecord.isDefaultChangeDetection()) {
var dir = proto.bindingRecord.directiveRecord;
this._getDetectorFor(dir.directiveIndex).markPathToRootAsCheckOnce();
}
}
_matchingEventBindings(eventName: string, elIndex: number): EventBinding[] {
return ListWrapper.filter(this.eventBindings,
eb => eb.eventName == eventName && eb.elIndex === elIndex);
}
hydrateDirectives(directives: any): void {
this.values[0] = this.context;
this.directives = directives;
if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) {
for (var i = 0; i < this.directiveIndices.length; ++i) {
var index = this.directiveIndices[i];
super.observeDirective(directives.getDirectiveFor(index), i);
}
}
}
dehydrateDirectives(destroyPipes: boolean) {
if (destroyPipes) {
this._destroyPipes();
}
this.values[0] = null;
this.directives = null;
ListWrapper.fill(this.values, ChangeDetectionUtil.uninitialized, 1);
ListWrapper.fill(this.changes, false);
ListWrapper.fill(this.localPipes, null);
ListWrapper.fill(this.prevContexts, ChangeDetectionUtil.uninitialized);
}
_destroyPipes() {
for (var i = 0; i < this.localPipes.length; ++i) {
if (isPresent(this.localPipes[i])) {
ChangeDetectionUtil.callPipeOnDestroy(this.localPipes[i]);
}
}
}
checkNoChanges(): void { this.runDetectChanges(true); }
detectChangesInRecordsInternal(throwOnChange: boolean) {
var protos = this.records;
var changes = null;
var isChanged = false;
for (var i = 0; i < protos.length; ++i) {
var proto: ProtoRecord = protos[i];
var bindingRecord = proto.bindingRecord;
var directiveRecord = bindingRecord.directiveRecord;
if (this._firstInBinding(proto)) {
this.propertyBindingIndex = proto.propertyBindingIndex;
}
if (proto.isLifeCycleRecord()) {
if (proto.name === "onCheck" && !throwOnChange) {
this._getDirectiveFor(directiveRecord.directiveIndex).onCheck();
} else if (proto.name === "onInit" && !throwOnChange && !this.alreadyChecked) {
this._getDirectiveFor(directiveRecord.directiveIndex).onInit();
} else if (proto.name === "onChange" && isPresent(changes) && !throwOnChange) {
this._getDirectiveFor(directiveRecord.directiveIndex).onChange(changes);
}
} else {
var change = this._check(proto, throwOnChange, this.values, this.locals);
if (isPresent(change)) {
this._updateDirectiveOrElement(change, bindingRecord);
isChanged = true;
changes = this._addChange(bindingRecord, change, changes);
}
}
if (proto.lastInDirective) {
changes = null;
if (isChanged && !bindingRecord.isDefaultChangeDetection()) {
this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce();
}
isChanged = false;
}
}
this.alreadyChecked = true;
}
_firstInBinding(r: ProtoRecord): boolean {
var prev = ChangeDetectionUtil.protoByIndex(this.records, r.selfIndex - 1);
return isBlank(prev) || prev.bindingRecord !== r.bindingRecord;
}
callOnAllChangesDone() {
super.callOnAllChangesDone();
var dirs = this.directiveRecords;
for (var i = dirs.length - 1; i >= 0; --i) {
var dir = dirs[i];
if (dir.callOnAllChangesDone) {
this._getDirectiveFor(dir.directiveIndex).onAllChangesDone();
}
}
}
_updateDirectiveOrElement(change, bindingRecord) {
if (isBlank(bindingRecord.directiveRecord)) {
super.notifyDispatcher(change.currentValue);
} else {
var directiveIndex = bindingRecord.directiveRecord.directiveIndex;
bindingRecord.setter(this._getDirectiveFor(directiveIndex), change.currentValue);
}
if (this.genConfig.logBindingUpdate) {
super.logBindingUpdate(change.currentValue);
}
}
_addChange(bindingRecord: BindingRecord, change, changes) {
if (bindingRecord.callOnChange()) {
return super.addChange(changes, change.previousValue, change.currentValue);
} else {
return changes;
}
}
_getDirectiveFor(directiveIndex) { return this.directives.getDirectiveFor(directiveIndex); }
_getDetectorFor(directiveIndex) { return this.directives.getDetectorFor(directiveIndex); }
_check(proto: ProtoRecord, throwOnChange: boolean, values: any[], locals: Locals): SimpleChange {
if (proto.isPipeRecord()) {
return this._pipeCheck(proto, throwOnChange, values);
} else {
return this._referenceCheck(proto, throwOnChange, values, locals);
}
}
_referenceCheck(proto: ProtoRecord, throwOnChange: boolean, values: any[], locals: Locals) {
if (this._pureFuncAndArgsDidNotChange(proto)) {
this._setChanged(proto, false);
return null;
}
var currValue = this._calculateCurrValue(proto, values, locals);
if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) {
super.observeValue(currValue, proto.selfIndex);
}
if (proto.shouldBeChecked()) {
var prevValue = this._readSelf(proto, values);
if (!isSame(prevValue, currValue)) {
if (proto.lastInBinding) {
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
if (throwOnChange) this.throwOnChangeError(prevValue, currValue);
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return change;
} else {
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return null;
}
} else {
this._setChanged(proto, false);
return null;
}
} else {
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return null;
}
}
_calculateCurrValue(proto: ProtoRecord, values: any[], locals: Locals) {
switch (proto.mode) {
case RecordType.SELF:
return this._readContext(proto, values);
case RecordType.CONST:
return proto.funcOrValue;
case RecordType.PROPERTY_READ:
var context = this._readContext(proto, values);
return proto.funcOrValue(context);
case RecordType.SAFE_PROPERTY:
var context = this._readContext(proto, values);
return isBlank(context) ? null : proto.funcOrValue(context);
case RecordType.PROPERTY_WRITE:
var context = this._readContext(proto, values);
var value = this._readArgs(proto, values)[0];
proto.funcOrValue(context, value);
return value;
case RecordType.KEYED_WRITE:
var context = this._readContext(proto, values);
var key = this._readArgs(proto, values)[0];
var value = this._readArgs(proto, values)[1];
context[key] = value;
return value;
case RecordType.LOCAL:
return locals.get(proto.name);
case RecordType.INVOKE_METHOD:
var context = this._readContext(proto, values);
var args = this._readArgs(proto, values);
return proto.funcOrValue(context, args);
case RecordType.SAFE_INVOKE_METHOD:
var context = this._readContext(proto, values);
if (isBlank(context)) {
return null;
}
var args = this._readArgs(proto, values);
return proto.funcOrValue(context, args);
case RecordType.KEYED_READ:
var arg = this._readArgs(proto, values)[0];
return this._readContext(proto, values)[arg];
case RecordType.CHAIN:
var args = this._readArgs(proto, values);
return args[args.length - 1];
case RecordType.INVOKE_CLOSURE:
return FunctionWrapper.apply(this._readContext(proto, values),
this._readArgs(proto, values));
case RecordType.INTERPOLATE:
case RecordType.PRIMITIVE_OP:
case RecordType.COLLECTION_LITERAL:
return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto, values));
default:
throw new BaseException(`Unknown operation ${proto.mode}`);
}
}
_pipeCheck(proto: ProtoRecord, throwOnChange: boolean, values: any[]) {
var context = this._readContext(proto, values);
var args = this._readArgs(proto, values);
var pipe = this._pipeFor(proto, context);
var currValue = pipe.transform(context, args);
if (proto.shouldBeChecked()) {
var prevValue = this._readSelf(proto, values);
if (!isSame(prevValue, currValue)) {
currValue = ChangeDetectionUtil.unwrapValue(currValue);
if (proto.lastInBinding) {
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
if (throwOnChange) this.throwOnChangeError(prevValue, currValue);
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return change;
} else {
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return null;
}
} else {
this._setChanged(proto, false);
return null;
}
} else {
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return null;
}
}
_pipeFor(proto: ProtoRecord, context) {
var storedPipe = this._readPipe(proto);
if (isPresent(storedPipe)) return storedPipe;
var pipe = this.pipes.get(proto.name);
this._writePipe(proto, pipe);
return pipe;
}
_readContext(proto: ProtoRecord, values: any[]) {
if (proto.contextIndex == -1) {
return this._getDirectiveFor(proto.directiveIndex);
} else {
return values[proto.contextIndex];
}
return values[proto.contextIndex];
}
_readSelf(proto: ProtoRecord, values: any[]) { return values[proto.selfIndex]; }
_writeSelf(proto: ProtoRecord, value, values: any[]) { values[proto.selfIndex] = value; }
_readPipe(proto: ProtoRecord) { return this.localPipes[proto.selfIndex]; }
_writePipe(proto: ProtoRecord, value) { this.localPipes[proto.selfIndex] = value; }
_setChanged(proto: ProtoRecord, value: boolean) {
if (proto.argumentToPureFunction) this.changes[proto.selfIndex] = value;
}
_pureFuncAndArgsDidNotChange(proto: ProtoRecord): boolean {
return proto.isPureFunction() && !this._argsChanged(proto);
}
_argsChanged(proto: ProtoRecord): boolean {
var args = proto.args;
for (var i = 0; i < args.length; ++i) {
if (this.changes[args[i]]) {
return true;
}
}
return false;
}
_readArgs(proto: ProtoRecord, values: any[]) {
var res = ListWrapper.createFixedSize(proto.args.length);
var args = proto.args;
for (var i = 0; i < args.length; ++i) {
res[i] = values[args[i]];
}
return res;
}
}
function isSame(a, b): boolean {
if (a === b) return true;
if (a instanceof String && b instanceof String && a == b) return true;
if ((a !== a) && (b !== b)) return true;
return false;
}

View File

@ -0,0 +1,7 @@
import {DirectiveIndex} from './directive_record';
import {ProtoRecord} from './proto_record';
export class EventBinding {
constructor(public eventName: string, public elIndex: number, public dirIndex: DirectiveIndex,
public records: ProtoRecord[]) {}
}

View File

@ -0,0 +1,43 @@
import {BaseException} from "angular2/src/facade/lang";
/**
* An error thrown if application changes model breaking the top-down data flow.
*
* Angular expects that the data flows from top (root) component to child (leaf) components.
* This is known as directed acyclic graph. This allows Angular to only execute change detection
* once and prevents loops in change detection data flow.
*
* This exception is only thrown in dev mode.
*/
export class ExpressionChangedAfterItHasBeenCheckedException extends BaseException {
constructor(exp: string, oldValue: any, currValue: any, context: any) {
super(`Expression '${exp}' has changed after it was checked. ` +
`Previous value: '${oldValue}'. Current value: '${currValue}'`);
}
}
/**
* Thrown when an expression evaluation raises an exception.
*
* This error wraps the original exception, this is done to attach expression location information.
*/
export class ChangeDetectionError extends BaseException {
/**
* Location of the expression.
*/
location: string;
constructor(exp: string, originalException: any, originalStack: any, context: any) {
super(`${originalException} in [${exp}]`, originalException, originalStack, context);
this.location = exp;
}
}
/**
* Thrown when change detector executes on dehydrated view.
*
* This is angular internal error.
*/
export class DehydratedException extends BaseException {
constructor() { super('Attempt to detect changes on a dehydrated detector.'); }
}

View File

@ -0,0 +1,87 @@
import {List} from 'angular2/src/facade/collection';
import {CONST} from 'angular2/src/facade/lang';
import {Locals} from './parser/locals';
import {BindingTarget, BindingRecord} from './binding_record';
import {DirectiveIndex, DirectiveRecord} from './directive_record';
import {ChangeDetectorRef} from './change_detector_ref';
/**
* Interface used by Angular to control the change detection strategy for an application.
*
* Angular implements the following change detection strategies by default:
*
* - {@link DynamicChangeDetection}: slower, but does not require `eval()`.
* - {@link JitChangeDetection}: faster, but requires `eval()`.
*
* In JavaScript, you should always use `JitChangeDetection`, unless you are in an environment that
*has
* [CSP](https://developer.mozilla.org/en-US/docs/Web/Security/CSP), such as a Chrome Extension.
*
* In Dart, use `DynamicChangeDetection` during development. The Angular transformer generates an
*analog to the
* `JitChangeDetection` strategy at compile time.
*
*
* See: {@link DynamicChangeDetection}, {@link JitChangeDetection},
* {@link PreGeneratedChangeDetection}
*
* # Example
* ```javascript
* bootstrap(MyApp, [bind(ChangeDetection).toValue(new DynamicChangeDetection())]);
* ```
*/
@CONST()
export class ChangeDetection {
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
return null;
}
get generateDetectors(): boolean { return null; }
get genConfig(): ChangeDetectorGenConfig { return null; }
}
export class DebugContext {
constructor(public element: any, public componentElement: any, public directive: any,
public context: any, public locals: any, public injector: any) {}
}
export interface ChangeDispatcher {
getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): DebugContext;
notifyOnBinding(bindingTarget: BindingTarget, value: any): void;
logBindingUpdate(bindingTarget: BindingTarget, value: any): void;
notifyOnAllChangesDone(): void;
}
export interface ChangeDetector {
parent: ChangeDetector;
mode: string;
ref: ChangeDetectorRef;
addChild(cd: ChangeDetector): void;
addShadowDomChild(cd: ChangeDetector): void;
removeChild(cd: ChangeDetector): void;
removeShadowDomChild(cd: ChangeDetector): void;
remove(): void;
hydrate(context: any, locals: Locals, directives: any, pipes: any): void;
dehydrate(): void;
markPathToRootAsCheckOnce(): void;
handleEvent(eventName: string, elIndex: number, locals: Locals);
detectChanges(): void;
checkNoChanges(): void;
}
export interface ProtoChangeDetector { instantiate(dispatcher: ChangeDispatcher): ChangeDetector; }
export class ChangeDetectorGenConfig {
constructor(public genCheckNoChanges: boolean, public genDebugInfo: boolean,
public logBindingUpdate: boolean) {}
}
export class ChangeDetectorDefinition {
constructor(public id: string, public strategy: string, public variableNames: List<string>,
public bindingRecords: BindingRecord[], public eventRecords: BindingRecord[],
public directiveRecords: DirectiveRecord[],
public genConfig: ChangeDetectorGenConfig) {}
}

View File

@ -0,0 +1,13 @@
library change_detection.jit_proto_change_detector;
import 'interfaces.dart' show ChangeDetector, ProtoChangeDetector;
class JitProtoChangeDetector implements ProtoChangeDetector {
JitProtoChangeDetector(definition) : super();
static bool isSupported() => false;
ChangeDetector instantiate(dispatcher) {
throw "Jit Change Detection not supported in Dart";
}
}

View File

@ -0,0 +1,31 @@
import {ListWrapper} from 'angular2/src/facade/collection';
import {isPresent} from 'angular2/src/facade/lang';
import {ProtoChangeDetector, ChangeDetector, ChangeDetectorDefinition} from './interfaces';
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
import {coalesce} from './coalesce';
import {createPropertyRecords, createEventRecords} from './proto_change_detector';
export class JitProtoChangeDetector implements ProtoChangeDetector {
_factory: Function;
constructor(private definition: ChangeDetectorDefinition) {
this._factory = this._createFactory(definition);
}
static isSupported(): boolean { return true; }
instantiate(dispatcher: any): ChangeDetector { return this._factory(dispatcher); }
_createFactory(definition: ChangeDetectorDefinition) {
var propertyBindingRecords = createPropertyRecords(definition);
var eventBindingRecords = createEventRecords(definition);
var propertyBindingTargets = this.definition.bindingRecords.map(b => b.target);
return new ChangeDetectorJITGenerator(
definition.id, definition.strategy, propertyBindingRecords, propertyBindingTargets,
eventBindingRecords, this.definition.directiveRecords, this.definition.genConfig)
.generate();
}
}

View File

@ -0,0 +1,3 @@
import 'package:observe/observe.dart';
bool isObservable(value) => value is Observable;

View File

@ -0,0 +1,3 @@
export function isObservable(value: any): boolean {
return false;
}

View File

@ -0,0 +1,311 @@
import {isBlank, isPresent, FunctionWrapper, BaseException} from "angular2/src/facade/lang";
import {List, Map, ListWrapper, StringMapWrapper} from "angular2/src/facade/collection";
export class AST {
visit(visitor: AstVisitor): any { return null; }
toString(): string { return "AST"; }
}
export class EmptyExpr extends AST {
visit(visitor: AstVisitor) {
// do nothing
}
}
export class ImplicitReceiver extends AST {
visit(visitor: AstVisitor): any { return visitor.visitImplicitReceiver(this); }
}
/**
* Multiple expressions separated by a semicolon.
*/
export class Chain extends AST {
constructor(public expressions: List<any>) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitChain(this); }
}
export class Conditional extends AST {
constructor(public condition: AST, public trueExp: AST, public falseExp: AST) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitConditional(this); }
}
export class If extends AST {
constructor(public condition: AST, public trueExp: AST, public falseExp?: AST) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitIf(this); }
}
export class PropertyRead extends AST {
constructor(public receiver: AST, public name: string, public getter: Function) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitPropertyRead(this); }
}
export class PropertyWrite extends AST {
constructor(public receiver: AST, public name: string, public setter: Function,
public value: AST) {
super();
}
visit(visitor: AstVisitor): any { return visitor.visitPropertyWrite(this); }
}
export class SafePropertyRead extends AST {
constructor(public receiver: AST, public name: string, public getter: Function) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitSafePropertyRead(this); }
}
export class KeyedRead extends AST {
constructor(public obj: AST, public key: AST) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitKeyedRead(this); }
}
export class KeyedWrite extends AST {
constructor(public obj: AST, public key: AST, public value: AST) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitKeyedWrite(this); }
}
export class BindingPipe extends AST {
constructor(public exp: AST, public name: string, public args: List<any>) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitPipe(this); }
}
export class LiteralPrimitive extends AST {
constructor(public value) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitLiteralPrimitive(this); }
}
export class LiteralArray extends AST {
constructor(public expressions: List<any>) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitLiteralArray(this); }
}
export class LiteralMap extends AST {
constructor(public keys: List<any>, public values: List<any>) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitLiteralMap(this); }
}
export class Interpolation extends AST {
constructor(public strings: List<any>, public expressions: List<any>) { super(); }
visit(visitor: AstVisitor) { visitor.visitInterpolation(this); }
}
export class Binary extends AST {
constructor(public operation: string, public left: AST, public right: AST) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitBinary(this); }
}
export class PrefixNot extends AST {
constructor(public expression: AST) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitPrefixNot(this); }
}
export class MethodCall extends AST {
constructor(public receiver: AST, public name: string, public fn: Function,
public args: List<any>) {
super();
}
visit(visitor: AstVisitor): any { return visitor.visitMethodCall(this); }
}
export class SafeMethodCall extends AST {
constructor(public receiver: AST, public name: string, public fn: Function,
public args: List<any>) {
super();
}
visit(visitor: AstVisitor): any { return visitor.visitSafeMethodCall(this); }
}
export class FunctionCall extends AST {
constructor(public target: AST, public args: List<any>) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitFunctionCall(this); }
}
export class ASTWithSource extends AST {
constructor(public ast: AST, public source: string, public location: string) { super(); }
visit(visitor: AstVisitor): any { return this.ast.visit(visitor); }
toString(): string { return `${this.source} in ${this.location}`; }
}
export class TemplateBinding {
constructor(public key: string, public keyIsVar: boolean, public name: string,
public expression: ASTWithSource) {}
}
export interface AstVisitor {
visitBinary(ast: Binary): any;
visitChain(ast: Chain): any;
visitConditional(ast: Conditional): any;
visitFunctionCall(ast: FunctionCall): any;
visitIf(ast: If): any;
visitImplicitReceiver(ast: ImplicitReceiver): any;
visitInterpolation(ast: Interpolation): any;
visitKeyedRead(ast: KeyedRead): any;
visitKeyedWrite(ast: KeyedWrite): any;
visitLiteralArray(ast: LiteralArray): any;
visitLiteralMap(ast: LiteralMap): any;
visitLiteralPrimitive(ast: LiteralPrimitive): any;
visitMethodCall(ast: MethodCall): any;
visitPipe(ast: BindingPipe): any;
visitPrefixNot(ast: PrefixNot): any;
visitPropertyRead(ast: PropertyRead): any;
visitPropertyWrite(ast: PropertyWrite): any;
visitSafeMethodCall(ast: SafeMethodCall): any;
visitSafePropertyRead(ast: SafePropertyRead): any;
}
export class RecursiveAstVisitor implements AstVisitor {
visitBinary(ast: Binary): any {
ast.left.visit(this);
ast.right.visit(this);
return null;
}
visitChain(ast: Chain): any { return this.visitAll(ast.expressions); }
visitConditional(ast: Conditional): any {
ast.condition.visit(this);
ast.trueExp.visit(this);
ast.falseExp.visit(this);
return null;
}
visitIf(ast: If): any {
ast.condition.visit(this);
ast.trueExp.visit(this);
ast.falseExp.visit(this);
return null;
}
visitPipe(ast: BindingPipe): any {
ast.exp.visit(this);
this.visitAll(ast.args);
return null;
}
visitFunctionCall(ast: FunctionCall): any {
ast.target.visit(this);
this.visitAll(ast.args);
return null;
}
visitImplicitReceiver(ast: ImplicitReceiver): any { return null; }
visitInterpolation(ast: Interpolation): any { return this.visitAll(ast.expressions); }
visitKeyedRead(ast: KeyedRead): any {
ast.obj.visit(this);
ast.key.visit(this);
return null;
}
visitKeyedWrite(ast: KeyedWrite): any {
ast.obj.visit(this);
ast.key.visit(this);
ast.value.visit(this);
return null;
}
visitLiteralArray(ast: LiteralArray): any { return this.visitAll(ast.expressions); }
visitLiteralMap(ast: LiteralMap): any { return this.visitAll(ast.values); }
visitLiteralPrimitive(ast: LiteralPrimitive): any { return null; }
visitMethodCall(ast: MethodCall): any {
ast.receiver.visit(this);
return this.visitAll(ast.args);
}
visitPrefixNot(ast: PrefixNot): any {
ast.expression.visit(this);
return null;
}
visitPropertyRead(ast: PropertyRead): any {
ast.receiver.visit(this);
return null;
}
visitPropertyWrite(ast: PropertyWrite): any {
ast.receiver.visit(this);
ast.value.visit(this);
return null;
}
visitSafePropertyRead(ast: SafePropertyRead): any {
ast.receiver.visit(this);
return null;
}
visitSafeMethodCall(ast: SafeMethodCall): any {
ast.receiver.visit(this);
return this.visitAll(ast.args);
}
visitAll(asts: List<AST>): any {
ListWrapper.forEach(asts, (ast) => { ast.visit(this); });
return null;
}
}
export class AstTransformer implements AstVisitor {
visitImplicitReceiver(ast: ImplicitReceiver): ImplicitReceiver { return ast; }
visitInterpolation(ast: Interpolation): Interpolation {
return new Interpolation(ast.strings, this.visitAll(ast.expressions));
}
visitLiteralPrimitive(ast: LiteralPrimitive): LiteralPrimitive {
return new LiteralPrimitive(ast.value);
}
visitPropertyRead(ast: PropertyRead): PropertyRead {
return new PropertyRead(ast.receiver.visit(this), ast.name, ast.getter);
}
visitPropertyWrite(ast: PropertyWrite): PropertyWrite {
return new PropertyWrite(ast.receiver.visit(this), ast.name, ast.setter, ast.value);
}
visitSafePropertyRead(ast: SafePropertyRead): SafePropertyRead {
return new SafePropertyRead(ast.receiver.visit(this), ast.name, ast.getter);
}
visitMethodCall(ast: MethodCall): MethodCall {
return new MethodCall(ast.receiver.visit(this), ast.name, ast.fn, this.visitAll(ast.args));
}
visitSafeMethodCall(ast: SafeMethodCall): SafeMethodCall {
return new SafeMethodCall(ast.receiver.visit(this), ast.name, ast.fn, this.visitAll(ast.args));
}
visitFunctionCall(ast: FunctionCall): FunctionCall {
return new FunctionCall(ast.target.visit(this), this.visitAll(ast.args));
}
visitLiteralArray(ast: LiteralArray): LiteralArray {
return new LiteralArray(this.visitAll(ast.expressions));
}
visitLiteralMap(ast: LiteralMap): LiteralMap {
return new LiteralMap(ast.keys, this.visitAll(ast.values));
}
visitBinary(ast: Binary): Binary {
return new Binary(ast.operation, ast.left.visit(this), ast.right.visit(this));
}
visitPrefixNot(ast: PrefixNot): PrefixNot { return new PrefixNot(ast.expression.visit(this)); }
visitConditional(ast: Conditional): Conditional {
return new Conditional(ast.condition.visit(this), ast.trueExp.visit(this),
ast.falseExp.visit(this));
}
visitPipe(ast: BindingPipe): BindingPipe {
return new BindingPipe(ast.exp.visit(this), ast.name, this.visitAll(ast.args));
}
visitKeyedRead(ast: KeyedRead): KeyedRead {
return new KeyedRead(ast.obj.visit(this), ast.key.visit(this));
}
visitKeyedWrite(ast: KeyedWrite): KeyedWrite {
return new KeyedWrite(ast.obj.visit(this), ast.key.visit(this), ast.value.visit(this));
}
visitAll(asts: List<any>): List<any> {
var res = ListWrapper.createFixedSize(asts.length);
for (var i = 0; i < asts.length; ++i) {
res[i] = asts[i].visit(this);
}
return res;
}
visitChain(ast: Chain): Chain { return new Chain(this.visitAll(ast.expressions)); }
visitIf(ast: If): If {
let falseExp = isPresent(ast.falseExp) ? ast.falseExp.visit(this) : null;
return new If(ast.condition.visit(this), ast.trueExp.visit(this), falseExp);
}
}

View File

@ -0,0 +1,468 @@
import {Injectable} from 'angular2/src/di/decorators';
import {List, ListWrapper, SetWrapper} from "angular2/src/facade/collection";
import {
NumberWrapper,
StringJoiner,
StringWrapper,
BaseException,
isPresent
} from "angular2/src/facade/lang";
export enum TokenType {
CHARACTER,
IDENTIFIER,
KEYWORD,
STRING,
OPERATOR,
NUMBER
}
@Injectable()
export class Lexer {
tokenize(text: string): List<any> {
var scanner = new _Scanner(text);
var tokens = [];
var token = scanner.scanToken();
while (token != null) {
tokens.push(token);
token = scanner.scanToken();
}
return tokens;
}
}
export class Token {
constructor(public index: number, public type: TokenType, public numValue: number,
public strValue: string) {}
isCharacter(code: number): boolean {
return (this.type == TokenType.CHARACTER && this.numValue == code);
}
isNumber(): boolean { return (this.type == TokenType.NUMBER); }
isString(): boolean { return (this.type == TokenType.STRING); }
isOperator(operater: string): boolean {
return (this.type == TokenType.OPERATOR && this.strValue == operater);
}
isIdentifier(): boolean { return (this.type == TokenType.IDENTIFIER); }
isKeyword(): boolean { return (this.type == TokenType.KEYWORD); }
isKeywordVar(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "var"); }
isKeywordNull(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "null"); }
isKeywordUndefined(): boolean {
return (this.type == TokenType.KEYWORD && this.strValue == "undefined");
}
isKeywordTrue(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "true"); }
isKeywordIf(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "if"); }
isKeywordElse(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "else"); }
isKeywordFalse(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "false"); }
toNumber(): number {
// -1 instead of NULL ok?
return (this.type == TokenType.NUMBER) ? this.numValue : -1;
}
toString(): string {
switch (this.type) {
case TokenType.CHARACTER:
case TokenType.STRING:
case TokenType.IDENTIFIER:
case TokenType.KEYWORD:
return this.strValue;
case TokenType.NUMBER:
return this.numValue.toString();
default:
return null;
}
}
}
function newCharacterToken(index: number, code: number): Token {
return new Token(index, TokenType.CHARACTER, code, StringWrapper.fromCharCode(code));
}
function newIdentifierToken(index: number, text: string): Token {
return new Token(index, TokenType.IDENTIFIER, 0, text);
}
function newKeywordToken(index: number, text: string): Token {
return new Token(index, TokenType.KEYWORD, 0, text);
}
function newOperatorToken(index: number, text: string): Token {
return new Token(index, TokenType.OPERATOR, 0, text);
}
function newStringToken(index: number, text: string): Token {
return new Token(index, TokenType.STRING, 0, text);
}
function newNumberToken(index: number, n: number): Token {
return new Token(index, TokenType.NUMBER, n, "");
}
export var EOF: Token = new Token(-1, TokenType.CHARACTER, 0, "");
export const $EOF = 0;
export const $TAB = 9;
export const $LF = 10;
export const $VTAB = 11;
export const $FF = 12;
export const $CR = 13;
export const $SPACE = 32;
export const $BANG = 33;
export const $DQ = 34;
export const $HASH = 35;
export const $$ = 36;
export const $PERCENT = 37;
export const $AMPERSAND = 38;
export const $SQ = 39;
export const $LPAREN = 40;
export const $RPAREN = 41;
export const $STAR = 42;
export const $PLUS = 43;
export const $COMMA = 44;
export const $MINUS = 45;
export const $PERIOD = 46;
export const $SLASH = 47;
export const $COLON = 58;
export const $SEMICOLON = 59;
export const $LT = 60;
export const $EQ = 61;
export const $GT = 62;
export const $QUESTION = 63;
const $0 = 48;
const $9 = 57;
const $A = 65, $E = 69, $Z = 90;
export const $LBRACKET = 91;
export const $BACKSLASH = 92;
export const $RBRACKET = 93;
const $CARET = 94;
const $_ = 95;
const $a = 97, $e = 101, $f = 102, $n = 110, $r = 114, $t = 116, $u = 117, $v = 118, $z = 122;
export const $LBRACE = 123;
export const $BAR = 124;
export const $RBRACE = 125;
const $NBSP = 160;
export class ScannerError extends BaseException {
constructor(public message) { super(); }
toString(): string { return this.message; }
}
class _Scanner {
length: number;
peek: number = 0;
index: number = -1;
constructor(public input: string) {
this.length = input.length;
this.advance();
}
advance() {
this.peek =
++this.index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, this.index);
}
scanToken(): Token {
var input = this.input, length = this.length, peek = this.peek, index = this.index;
// Skip whitespace.
while (peek <= $SPACE) {
if (++index >= length) {
peek = $EOF;
break;
} else {
peek = StringWrapper.charCodeAt(input, index);
}
}
this.peek = peek;
this.index = index;
if (index >= length) {
return null;
}
// Handle identifiers and numbers.
if (isIdentifierStart(peek)) return this.scanIdentifier();
if (isDigit(peek)) return this.scanNumber(index);
var start: number = index;
switch (peek) {
case $PERIOD:
this.advance();
return isDigit(this.peek) ? this.scanNumber(start) : newCharacterToken(start, $PERIOD);
case $LPAREN:
case $RPAREN:
case $LBRACE:
case $RBRACE:
case $LBRACKET:
case $RBRACKET:
case $COMMA:
case $COLON:
case $SEMICOLON:
return this.scanCharacter(start, peek);
case $SQ:
case $DQ:
return this.scanString();
case $HASH:
case $PLUS:
case $MINUS:
case $STAR:
case $SLASH:
case $PERCENT:
case $CARET:
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
case $QUESTION:
return this.scanComplexOperator(start, '?', $PERIOD, '.');
case $LT:
case $GT:
return this.scanComplexOperator(start, StringWrapper.fromCharCode(peek), $EQ, '=');
case $BANG:
case $EQ:
return this.scanComplexOperator(start, StringWrapper.fromCharCode(peek), $EQ, '=', $EQ,
'=');
case $AMPERSAND:
return this.scanComplexOperator(start, '&', $AMPERSAND, '&');
case $BAR:
return this.scanComplexOperator(start, '|', $BAR, '|');
case $NBSP:
while (isWhitespace(this.peek)) this.advance();
return this.scanToken();
}
this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`, 0);
return null;
}
scanCharacter(start: number, code: number): Token {
assert(this.peek == code);
this.advance();
return newCharacterToken(start, code);
}
scanOperator(start: number, str: string): Token {
assert(this.peek == StringWrapper.charCodeAt(str, 0));
assert(SetWrapper.has(OPERATORS, str));
this.advance();
return newOperatorToken(start, str);
}
/**
* Tokenize a 2/3 char long operator
*
* @param start start index in the expression
* @param one first symbol (always part of the operator)
* @param twoCode code point for the second symbol
* @param two second symbol (part of the operator when the second code point matches)
* @param threeCode code point for the third symbol
* @param three third symbol (part of the operator when provided and matches source expression)
* @returns {Token}
*/
scanComplexOperator(start: number, one: string, twoCode: number, two: string, threeCode?: number,
three?: string): Token {
assert(this.peek == StringWrapper.charCodeAt(one, 0));
this.advance();
var str: string = one;
if (this.peek == twoCode) {
this.advance();
str += two;
}
if (isPresent(threeCode) && this.peek == threeCode) {
this.advance();
str += three;
}
assert(SetWrapper.has(OPERATORS, str));
return newOperatorToken(start, str);
}
scanIdentifier(): Token {
assert(isIdentifierStart(this.peek));
var start: number = this.index;
this.advance();
while (isIdentifierPart(this.peek)) this.advance();
var str: string = this.input.substring(start, this.index);
if (SetWrapper.has(KEYWORDS, str)) {
return newKeywordToken(start, str);
} else {
return newIdentifierToken(start, str);
}
}
scanNumber(start: number): Token {
assert(isDigit(this.peek));
var simple: boolean = (this.index === start);
this.advance(); // Skip initial digit.
while (true) {
if (isDigit(this.peek)) {
// Do nothing.
} else if (this.peek == $PERIOD) {
simple = false;
} else if (isExponentStart(this.peek)) {
this.advance();
if (isExponentSign(this.peek)) this.advance();
if (!isDigit(this.peek)) this.error('Invalid exponent', -1);
simple = false;
} else {
break;
}
this.advance();
}
var str: string = this.input.substring(start, this.index);
// TODO
var value: number =
simple ? NumberWrapper.parseIntAutoRadix(str) : NumberWrapper.parseFloat(str);
return newNumberToken(start, value);
}
scanString(): Token {
assert(this.peek == $SQ || this.peek == $DQ);
var start: number = this.index;
var quote: number = this.peek;
this.advance(); // Skip initial quote.
var buffer: StringJoiner;
var marker: number = this.index;
var input: string = this.input;
while (this.peek != quote) {
if (this.peek == $BACKSLASH) {
if (buffer == null) buffer = new StringJoiner();
buffer.add(input.substring(marker, this.index));
this.advance();
var unescapedCode: number;
if (this.peek == $u) {
// 4 character hex code for unicode character.
var hex: string = input.substring(this.index + 1, this.index + 5);
try {
unescapedCode = NumberWrapper.parseInt(hex, 16);
} catch (e) {
this.error(`Invalid unicode escape [\\u${hex}]`, 0);
}
for (var i: number = 0; i < 5; i++) {
this.advance();
}
} else {
unescapedCode = unescape(this.peek);
this.advance();
}
buffer.add(StringWrapper.fromCharCode(unescapedCode));
marker = this.index;
} else if (this.peek == $EOF) {
this.error('Unterminated quote', 0);
} else {
this.advance();
}
}
var last: string = input.substring(marker, this.index);
this.advance(); // Skip terminating quote.
// Compute the unescaped string value.
var unescaped: string = last;
if (buffer != null) {
buffer.add(last);
unescaped = buffer.toString();
}
return newStringToken(start, unescaped);
}
error(message: string, offset: number) {
var position: number = this.index + offset;
throw new ScannerError(
`Lexer Error: ${message} at column ${position} in expression [${this.input}]`);
}
}
function isWhitespace(code: number): boolean {
return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
}
function isIdentifierStart(code: number): boolean {
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) || (code == $_) || (code == $$);
}
function isIdentifierPart(code: number): boolean {
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) || ($0 <= code && code <= $9) ||
(code == $_) || (code == $$);
}
function isDigit(code: number): boolean {
return $0 <= code && code <= $9;
}
function isExponentStart(code: number): boolean {
return code == $e || code == $E;
}
function isExponentSign(code: number): boolean {
return code == $MINUS || code == $PLUS;
}
function unescape(code: number): number {
switch (code) {
case $n:
return $LF;
case $f:
return $FF;
case $r:
return $CR;
case $t:
return $TAB;
case $v:
return $VTAB;
default:
return code;
}
}
var OPERATORS = SetWrapper.createFromList([
'+',
'-',
'*',
'/',
'%',
'^',
'=',
'==',
'!=',
'===',
'!==',
'<',
'>',
'<=',
'>=',
'&&',
'||',
'&',
'|',
'!',
'?',
'#',
'?.'
]);
var KEYWORDS =
SetWrapper.createFromList(['var', 'null', 'undefined', 'true', 'false', 'if', 'else']);

View File

@ -0,0 +1,44 @@
import {isPresent, BaseException} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
export class Locals {
constructor(public parent: Locals, public current: Map<any, any>) {}
contains(name: string): boolean {
if (this.current.has(name)) {
return true;
}
if (isPresent(this.parent)) {
return this.parent.contains(name);
}
return false;
}
get(name: string): any {
if (this.current.has(name)) {
return this.current.get(name);
}
if (isPresent(this.parent)) {
return this.parent.get(name);
}
throw new BaseException(`Cannot find '${name}'`);
}
set(name: string, value: any): void {
// TODO(rado): consider removing this check if we can guarantee this is not
// exposed to the public API.
// TODO: vsavkin maybe it should check only the local map
if (this.current.has(name)) {
this.current.set(name, value);
} else {
throw new BaseException(
`Setting of new keys post-construction is not supported. Key: ${name}.`);
}
}
clearValues(): void { MapWrapper.clearValues(this.current); }
}

View File

@ -0,0 +1,691 @@
import {Injectable} from 'angular2/src/di/decorators';
import {isBlank, isPresent, BaseException, StringWrapper} from 'angular2/src/facade/lang';
import {ListWrapper, List} from 'angular2/src/facade/collection';
import {
Lexer,
EOF,
Token,
$PERIOD,
$COLON,
$SEMICOLON,
$LBRACKET,
$RBRACKET,
$COMMA,
$LBRACE,
$RBRACE,
$LPAREN,
$RPAREN
} from './lexer';
import {reflector, Reflector} from 'angular2/src/reflection/reflection';
import {
AST,
EmptyExpr,
ImplicitReceiver,
PropertyRead,
PropertyWrite,
SafePropertyRead,
LiteralPrimitive,
Binary,
PrefixNot,
Conditional,
If,
BindingPipe,
Chain,
KeyedRead,
KeyedWrite,
LiteralArray,
LiteralMap,
Interpolation,
MethodCall,
SafeMethodCall,
FunctionCall,
TemplateBinding,
ASTWithSource,
AstVisitor
} from './ast';
var _implicitReceiver = new ImplicitReceiver();
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
var INTERPOLATION_REGEXP = /\{\{(.*?)\}\}/g;
class ParseException extends BaseException {
constructor(message: string, input: string, errLocation: string, ctxLocation?: any) {
super(`Parser Error: ${message} ${errLocation} [${input}] in ${ctxLocation}`, null, null,
ctxLocation);
}
}
@Injectable()
export class Parser {
_reflector: Reflector;
constructor(public _lexer: Lexer, providedReflector: Reflector = null) {
this._reflector = isPresent(providedReflector) ? providedReflector : reflector;
}
parseAction(input: string, location: any): ASTWithSource {
this._checkNoInterpolation(input, location);
var tokens = this._lexer.tokenize(input);
var ast = new _ParseAST(input, location, tokens, this._reflector, true).parseChain();
return new ASTWithSource(ast, input, location);
}
parseBinding(input: string, location: any): ASTWithSource {
this._checkNoInterpolation(input, location);
var tokens = this._lexer.tokenize(input);
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
return new ASTWithSource(ast, input, location);
}
parseSimpleBinding(input: string, location: string): ASTWithSource {
this._checkNoInterpolation(input, location);
var tokens = this._lexer.tokenize(input);
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseSimpleBinding();
return new ASTWithSource(ast, input, location);
}
parseTemplateBindings(input: string, location: any): List<TemplateBinding> {
var tokens = this._lexer.tokenize(input);
return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings();
}
parseInterpolation(input: string, location: any): ASTWithSource {
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
if (parts.length <= 1) {
return null;
}
var strings = [];
var expressions = [];
for (var i = 0; i < parts.length; i++) {
var part: string = parts[i];
if (i % 2 === 0) {
// fixed string
strings.push(part);
} else if (part.trim().length > 0) {
var tokens = this._lexer.tokenize(part);
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
expressions.push(ast);
} else {
throw new ParseException('Blank expressions are not allowed in interpolated strings', input,
`at column ${this._findInterpolationErrorColumn(parts, i)} in`,
location);
}
}
return new ASTWithSource(new Interpolation(strings, expressions), input, location);
}
wrapLiteralPrimitive(input: string, location: any): ASTWithSource {
return new ASTWithSource(new LiteralPrimitive(input), input, location);
}
private _checkNoInterpolation(input: string, location: any): void {
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
if (parts.length > 1) {
throw new ParseException('Got interpolation ({{}}) where expression was expected', input,
`at column ${this._findInterpolationErrorColumn(parts, 1)} in`,
location);
}
}
private _findInterpolationErrorColumn(parts: string[], partInErrIdx: number): number {
var errLocation = '';
for (var j = 0; j < partInErrIdx; j++) {
errLocation += j % 2 === 0 ? parts[j] : `{{${parts[j]}}}`;
}
return errLocation.length;
}
}
export class _ParseAST {
index: number = 0;
constructor(public input: string, public location: any, public tokens: List<any>,
public reflector: Reflector, public parseAction: boolean) {}
peek(offset: number): Token {
var i = this.index + offset;
return i < this.tokens.length ? this.tokens[i] : EOF;
}
get next(): Token { return this.peek(0); }
get inputIndex(): number {
return (this.index < this.tokens.length) ? this.next.index : this.input.length;
}
advance() { this.index++; }
optionalCharacter(code: number): boolean {
if (this.next.isCharacter(code)) {
this.advance();
return true;
} else {
return false;
}
}
optionalKeywordVar(): boolean {
if (this.peekKeywordVar()) {
this.advance();
return true;
} else {
return false;
}
}
peekKeywordVar(): boolean { return this.next.isKeywordVar() || this.next.isOperator('#'); }
expectCharacter(code: number) {
if (this.optionalCharacter(code)) return;
this.error(`Missing expected ${StringWrapper.fromCharCode(code)}`);
}
optionalOperator(op: string): boolean {
if (this.next.isOperator(op)) {
this.advance();
return true;
} else {
return false;
}
}
expectOperator(operator: string) {
if (this.optionalOperator(operator)) return;
this.error(`Missing expected operator ${operator}`);
}
expectIdentifierOrKeyword(): string {
var n = this.next;
if (!n.isIdentifier() && !n.isKeyword()) {
this.error(`Unexpected token ${n}, expected identifier or keyword`);
}
this.advance();
return n.toString();
}
expectIdentifierOrKeywordOrString(): string {
var n = this.next;
if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) {
this.error(`Unexpected token ${n}, expected identifier, keyword, or string`);
}
this.advance();
return n.toString();
}
parseChain(): AST {
var exprs = [];
while (this.index < this.tokens.length) {
var expr = this.parsePipe();
exprs.push(expr);
if (this.optionalCharacter($SEMICOLON)) {
if (!this.parseAction) {
this.error("Binding expression cannot contain chained expression");
}
while (this.optionalCharacter($SEMICOLON)) {
} // read all semicolons
} else if (this.index < this.tokens.length) {
this.error(`Unexpected token '${this.next}'`);
}
}
if (exprs.length == 0) return new EmptyExpr();
if (exprs.length == 1) return exprs[0];
return new Chain(exprs);
}
parseSimpleBinding(): AST {
var ast = this.parseChain();
if (!SimpleExpressionChecker.check(ast)) {
this.error(`Simple binding expression can only contain field access and constants'`);
}
return ast;
}
parsePipe(): AST {
var result = this.parseExpression();
if (this.optionalOperator("|")) {
if (this.parseAction) {
this.error("Cannot have a pipe in an action expression");
}
do {
var name = this.expectIdentifierOrKeyword();
var args = [];
while (this.optionalCharacter($COLON)) {
args.push(this.parsePipe());
}
result = new BindingPipe(result, name, args);
} while (this.optionalOperator("|"));
}
return result;
}
parseExpression(): AST { return this.parseConditional(); }
parseConditional(): AST {
var start = this.inputIndex;
var result = this.parseLogicalOr();
if (this.optionalOperator('?')) {
var yes = this.parsePipe();
if (!this.optionalCharacter($COLON)) {
var end = this.inputIndex;
var expression = this.input.substring(start, end);
this.error(`Conditional expression ${expression} requires all 3 expressions`);
}
var no = this.parsePipe();
return new Conditional(result, yes, no);
} else {
return result;
}
}
parseLogicalOr(): AST {
// '||'
var result = this.parseLogicalAnd();
while (this.optionalOperator('||')) {
result = new Binary('||', result, this.parseLogicalAnd());
}
return result;
}
parseLogicalAnd(): AST {
// '&&'
var result = this.parseEquality();
while (this.optionalOperator('&&')) {
result = new Binary('&&', result, this.parseEquality());
}
return result;
}
parseEquality(): AST {
// '==','!=','===','!=='
var result = this.parseRelational();
while (true) {
if (this.optionalOperator('==')) {
result = new Binary('==', result, this.parseRelational());
} else if (this.optionalOperator('===')) {
result = new Binary('===', result, this.parseRelational());
} else if (this.optionalOperator('!=')) {
result = new Binary('!=', result, this.parseRelational());
} else if (this.optionalOperator('!==')) {
result = new Binary('!==', result, this.parseRelational());
} else {
return result;
}
}
}
parseRelational(): AST {
// '<', '>', '<=', '>='
var result = this.parseAdditive();
while (true) {
if (this.optionalOperator('<')) {
result = new Binary('<', result, this.parseAdditive());
} else if (this.optionalOperator('>')) {
result = new Binary('>', result, this.parseAdditive());
} else if (this.optionalOperator('<=')) {
result = new Binary('<=', result, this.parseAdditive());
} else if (this.optionalOperator('>=')) {
result = new Binary('>=', result, this.parseAdditive());
} else {
return result;
}
}
}
parseAdditive(): AST {
// '+', '-'
var result = this.parseMultiplicative();
while (true) {
if (this.optionalOperator('+')) {
result = new Binary('+', result, this.parseMultiplicative());
} else if (this.optionalOperator('-')) {
result = new Binary('-', result, this.parseMultiplicative());
} else {
return result;
}
}
}
parseMultiplicative(): AST {
// '*', '%', '/'
var result = this.parsePrefix();
while (true) {
if (this.optionalOperator('*')) {
result = new Binary('*', result, this.parsePrefix());
} else if (this.optionalOperator('%')) {
result = new Binary('%', result, this.parsePrefix());
} else if (this.optionalOperator('/')) {
result = new Binary('/', result, this.parsePrefix());
} else {
return result;
}
}
}
parsePrefix(): AST {
if (this.optionalOperator('+')) {
return this.parsePrefix();
} else if (this.optionalOperator('-')) {
return new Binary('-', new LiteralPrimitive(0), this.parsePrefix());
} else if (this.optionalOperator('!')) {
return new PrefixNot(this.parsePrefix());
} else {
return this.parseCallChain();
}
}
parseCallChain(): AST {
var result = this.parsePrimary();
while (true) {
if (this.optionalCharacter($PERIOD)) {
result = this.parseAccessMemberOrMethodCall(result, false);
} else if (this.optionalOperator('?.')) {
result = this.parseAccessMemberOrMethodCall(result, true);
} else if (this.optionalCharacter($LBRACKET)) {
var key = this.parsePipe();
this.expectCharacter($RBRACKET);
if (this.optionalOperator("=")) {
var value = this.parseConditional();
result = new KeyedWrite(result, key, value);
} else {
result = new KeyedRead(result, key);
}
} else if (this.optionalCharacter($LPAREN)) {
var args = this.parseCallArguments();
this.expectCharacter($RPAREN);
result = new FunctionCall(result, args);
} else {
return result;
}
}
}
parsePrimary(): AST {
if (this.optionalCharacter($LPAREN)) {
let result = this.parsePipe();
this.expectCharacter($RPAREN);
return result;
} else if (this.next.isKeywordNull() || this.next.isKeywordUndefined()) {
this.advance();
return new LiteralPrimitive(null);
} else if (this.next.isKeywordTrue()) {
this.advance();
return new LiteralPrimitive(true);
} else if (this.next.isKeywordFalse()) {
this.advance();
return new LiteralPrimitive(false);
} else if (this.parseAction && this.next.isKeywordIf()) {
this.advance();
this.expectCharacter($LPAREN);
let condition = this.parseExpression();
this.expectCharacter($RPAREN);
let ifExp = this.parseExpressionOrBlock();
let elseExp;
if (this.next.isKeywordElse()) {
this.advance();
elseExp = this.parseExpressionOrBlock();
}
return new If(condition, ifExp, elseExp);
} else if (this.optionalCharacter($LBRACKET)) {
var elements = this.parseExpressionList($RBRACKET);
this.expectCharacter($RBRACKET);
return new LiteralArray(elements);
} else if (this.next.isCharacter($LBRACE)) {
return this.parseLiteralMap();
} else if (this.next.isIdentifier()) {
return this.parseAccessMemberOrMethodCall(_implicitReceiver, false);
} else if (this.next.isNumber()) {
var value = this.next.toNumber();
this.advance();
return new LiteralPrimitive(value);
} else if (this.next.isString()) {
var literalValue = this.next.toString();
this.advance();
return new LiteralPrimitive(literalValue);
} else if (this.index >= this.tokens.length) {
this.error(`Unexpected end of expression: ${this.input}`);
} else {
this.error(`Unexpected token ${this.next}`);
}
// error() throws, so we don't reach here.
throw new BaseException("Fell through all cases in parsePrimary");
}
parseExpressionList(terminator: number): List<any> {
var result = [];
if (!this.next.isCharacter(terminator)) {
do {
result.push(this.parsePipe());
} while (this.optionalCharacter($COMMA));
}
return result;
}
parseLiteralMap(): LiteralMap {
var keys = [];
var values = [];
this.expectCharacter($LBRACE);
if (!this.optionalCharacter($RBRACE)) {
do {
var key = this.expectIdentifierOrKeywordOrString();
keys.push(key);
this.expectCharacter($COLON);
values.push(this.parsePipe());
} while (this.optionalCharacter($COMMA));
this.expectCharacter($RBRACE);
}
return new LiteralMap(keys, values);
}
parseAccessMemberOrMethodCall(receiver: AST, isSafe: boolean = false): AST {
let id = this.expectIdentifierOrKeyword();
if (this.optionalCharacter($LPAREN)) {
let args = this.parseCallArguments();
this.expectCharacter($RPAREN);
let fn = this.reflector.method(id);
return isSafe ? new SafeMethodCall(receiver, id, fn, args) :
new MethodCall(receiver, id, fn, args);
} else {
if (isSafe) {
if (this.optionalOperator("=")) {
this.error("The '?.' operator cannot be used in the assignment");
} else {
return new SafePropertyRead(receiver, id, this.reflector.getter(id));
}
} else {
if (this.optionalOperator("=")) {
if (!this.parseAction) {
this.error("Bindings cannot contain assignments");
}
let value = this.parseConditional();
return new PropertyWrite(receiver, id, this.reflector.setter(id), value);
} else {
return new PropertyRead(receiver, id, this.reflector.getter(id));
}
}
}
return null;
}
parseCallArguments(): BindingPipe[] {
if (this.next.isCharacter($RPAREN)) return [];
var positionals = [];
do {
positionals.push(this.parsePipe());
} while (this.optionalCharacter($COMMA));
return positionals;
}
parseExpressionOrBlock(): AST {
if (this.optionalCharacter($LBRACE)) {
let block = this.parseBlockContent();
this.expectCharacter($RBRACE);
return block;
}
return this.parseExpression();
}
parseBlockContent(): AST {
if (!this.parseAction) {
this.error("Binding expression cannot contain chained expression");
}
var exprs = [];
while (this.index < this.tokens.length && !this.next.isCharacter($RBRACE)) {
var expr = this.parseExpression();
exprs.push(expr);
if (this.optionalCharacter($SEMICOLON)) {
while (this.optionalCharacter($SEMICOLON)) {
} // read all semicolons
}
}
if (exprs.length == 0) return new EmptyExpr();
if (exprs.length == 1) return exprs[0];
return new Chain(exprs);
}
/**
* An identifier, a keyword, a string with an optional `-` inbetween.
*/
expectTemplateBindingKey(): string {
var result = '';
var operatorFound = false;
do {
result += this.expectIdentifierOrKeywordOrString();
operatorFound = this.optionalOperator('-');
if (operatorFound) {
result += '-';
}
} while (operatorFound);
return result.toString();
}
parseTemplateBindings(): any[] {
var bindings = [];
var prefix = null;
while (this.index < this.tokens.length) {
var keyIsVar: boolean = this.optionalKeywordVar();
var key = this.expectTemplateBindingKey();
if (!keyIsVar) {
if (prefix == null) {
prefix = key;
} else {
key = prefix + '-' + key;
}
}
this.optionalCharacter($COLON);
var name = null;
var expression = null;
if (keyIsVar) {
if (this.optionalOperator("=")) {
name = this.expectTemplateBindingKey();
} else {
name = '\$implicit';
}
} else if (this.next !== EOF && !this.peekKeywordVar()) {
var start = this.inputIndex;
var ast = this.parsePipe();
var source = this.input.substring(start, this.inputIndex);
expression = new ASTWithSource(ast, source, this.location);
}
bindings.push(new TemplateBinding(key, keyIsVar, name, expression));
if (!this.optionalCharacter($SEMICOLON)) {
this.optionalCharacter($COMMA);
}
}
return bindings;
}
error(message: string, index: number = null) {
if (isBlank(index)) index = this.index;
var location = (index < this.tokens.length) ? `at column ${this.tokens[index].index + 1} in` :
`at the end of the expression`;
throw new ParseException(message, this.input, location, this.location);
}
}
class SimpleExpressionChecker implements AstVisitor {
static check(ast: AST): boolean {
var s = new SimpleExpressionChecker();
ast.visit(s);
return s.simple;
}
simple = true;
visitImplicitReceiver(ast: ImplicitReceiver) {}
visitInterpolation(ast: Interpolation) { this.simple = false; }
visitLiteralPrimitive(ast: LiteralPrimitive) {}
visitPropertyRead(ast: PropertyRead) {}
visitPropertyWrite(ast: PropertyWrite) { this.simple = false; }
visitSafePropertyRead(ast: SafePropertyRead) { this.simple = false; }
visitMethodCall(ast: MethodCall) { this.simple = false; }
visitSafeMethodCall(ast: SafeMethodCall) { this.simple = false; }
visitFunctionCall(ast: FunctionCall) { this.simple = false; }
visitLiteralArray(ast: LiteralArray) { this.visitAll(ast.expressions); }
visitLiteralMap(ast: LiteralMap) { this.visitAll(ast.values); }
visitBinary(ast: Binary) { this.simple = false; }
visitPrefixNot(ast: PrefixNot) { this.simple = false; }
visitConditional(ast: Conditional) { this.simple = false; }
visitPipe(ast: BindingPipe) { this.simple = false; }
visitKeyedRead(ast: KeyedRead) { this.simple = false; }
visitKeyedWrite(ast: KeyedWrite) { this.simple = false; }
visitAll(asts: List<any>): List<any> {
var res = ListWrapper.createFixedSize(asts.length);
for (var i = 0; i < asts.length; ++i) {
res[i] = asts[i].visit(this);
}
return res;
}
visitChain(ast: Chain) { this.simple = false; }
visitIf(ast: If) { this.simple = false; }
}

View File

@ -0,0 +1,5 @@
library angular2.core.compiler.pipe_lifecycle_reflector;
import 'package:angular2/src/change_detection/pipe_transform.dart';
bool implementsOnDestroy(Object pipe) => pipe is PipeOnDestroy;

View File

@ -0,0 +1,3 @@
export function implementsOnDestroy(pipe: any): boolean {
return pipe.constructor.prototype.onDestroy;
}

View File

@ -0,0 +1,39 @@
import {ABSTRACT, BaseException, CONST, Type} from 'angular2/src/facade/lang';
/**
* An interface which all pipes must implement.
*
* #Example
*
* ```
* class DoublePipe implements PipeTransform {
* transform(value, args = []) {
* return `${value}${value}`;
* }
* }
* ```
*/
export interface PipeTransform { transform(value: any, args: List<any>): any; }
/**
* An interface that stateful pipes should implement.
*
* #Example
*
* ```
* class StatefulPipe implements PipeTransform, PipeOnDestroy {
* connection;
*
* onDestroy() {
* this.connection.release();
* }
*
* transform(value, args = []) {
* this.connection = createConnection();
* // ...
* return someValue;
* }
* }
* ```
*/
export interface PipeOnDestroy { onDestroy(): void; }

View File

@ -0,0 +1,3 @@
import {PipeTransform} from './pipe_transform';
export interface Pipes { get(name: string): PipeTransform; }

View File

@ -0,0 +1,56 @@
library angular2.src.change_detection.pregen_proto_change_detector;
import 'package:angular2/src/change_detection/interfaces.dart';
import 'package:angular2/src/facade/lang.dart' show looseIdentical;
export 'dart:core' show List;
export 'package:angular2/src/change_detection/abstract_change_detector.dart'
show AbstractChangeDetector;
export 'package:angular2/src/change_detection/change_detection.dart'
show preGeneratedProtoDetectors;
export 'package:angular2/src/change_detection/directive_record.dart'
show DirectiveIndex, DirectiveRecord;
export 'package:angular2/src/change_detection/interfaces.dart'
show ChangeDetector, ChangeDetectorDefinition, ProtoChangeDetector;
export 'package:angular2/src/change_detection/pipes.dart' show Pipes;
export 'package:angular2/src/change_detection/proto_record.dart'
show ProtoRecord;
export 'package:angular2/src/change_detection/change_detection_util.dart'
show ChangeDetectionUtil;
export 'package:angular2/src/facade/lang.dart' show looseIdentical;
typedef ProtoChangeDetector PregenProtoChangeDetectorFactory(
ChangeDetectorDefinition definition);
typedef ChangeDetector InstantiateMethod(dynamic dispatcher);
/// Implementation of [ProtoChangeDetector] for use by pre-generated change
/// detectors in Angular 2 Dart.
/// Classes generated by the `TemplateCompiler` use this. The `export`s above
/// allow the generated code to `import` a single library and get all
/// dependencies.
class PregenProtoChangeDetector extends ProtoChangeDetector {
/// The [ChangeDetectorDefinition#id]. Strictly informational.
final String id;
/// Closure used to generate an actual [ChangeDetector].
final InstantiateMethod _instantiateMethod;
/// Internal ctor.
PregenProtoChangeDetector._(this.id, this._instantiateMethod);
static bool isSupported() => true;
factory PregenProtoChangeDetector(
InstantiateMethod instantiateMethod, ChangeDetectorDefinition def) {
return new PregenProtoChangeDetector._(def.id, instantiateMethod);
}
@override
instantiate(dynamic dispatcher) => _instantiateMethod(dispatcher);
}
/// Provided as an optimization to cut down on '!' characters in generated
/// change detectors. See https://github.com/angular/angular/issues/3248 for
/// for details.
bool looseNotIdentical(a, b) => !looseIdentical(a, b);

View File

@ -0,0 +1,14 @@
import {BaseException} from 'angular2/src/facade/lang';
import {ProtoChangeDetector, ChangeDetector} from './interfaces';
import {coalesce} from './coalesce';
export {Function as PregenProtoChangeDetectorFactory};
export class PregenProtoChangeDetector implements ProtoChangeDetector {
static isSupported(): boolean { return false; }
instantiate(dispatcher: any): ChangeDetector {
throw new BaseException('Pregen change detection not supported in Js');
}
}

View File

@ -0,0 +1,435 @@
import {BaseException, Type, isBlank, isPresent, isString} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {
PropertyRead,
PropertyWrite,
KeyedWrite,
AST,
ASTWithSource,
AstVisitor,
Binary,
Chain,
Conditional,
If,
BindingPipe,
FunctionCall,
ImplicitReceiver,
Interpolation,
KeyedRead,
LiteralArray,
LiteralMap,
LiteralPrimitive,
MethodCall,
PrefixNot,
SafePropertyRead,
SafeMethodCall
} from './parser/ast';
import {ChangeDetector, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces';
import {ChangeDetectionUtil} from './change_detection_util';
import {DynamicChangeDetector} from './dynamic_change_detector';
import {BindingRecord, BindingTarget} from './binding_record';
import {DirectiveRecord, DirectiveIndex} from './directive_record';
import {EventBinding} from './event_binding';
import {coalesce} from './coalesce';
import {ProtoRecord, RecordType} from './proto_record';
export class DynamicProtoChangeDetector implements ProtoChangeDetector {
_propertyBindingRecords: ProtoRecord[];
_propertyBindingTargets: BindingTarget[];
_eventBindingRecords: EventBinding[];
_directiveIndices: DirectiveIndex[];
constructor(private definition: ChangeDetectorDefinition) {
this._propertyBindingRecords = createPropertyRecords(definition);
this._eventBindingRecords = createEventRecords(definition);
this._propertyBindingTargets = this.definition.bindingRecords.map(b => b.target);
this._directiveIndices = this.definition.directiveRecords.map(d => d.directiveIndex);
}
instantiate(dispatcher: any): ChangeDetector {
return new DynamicChangeDetector(
this.definition.id, dispatcher, this._propertyBindingRecords.length,
this._propertyBindingTargets, this._directiveIndices, this.definition.strategy,
this._propertyBindingRecords, this._eventBindingRecords, this.definition.directiveRecords,
this.definition.genConfig);
}
}
export function createPropertyRecords(definition: ChangeDetectorDefinition): ProtoRecord[] {
var recordBuilder = new ProtoRecordBuilder();
ListWrapper.forEachWithIndex(definition.bindingRecords,
(b, index) => recordBuilder.add(b, definition.variableNames, index));
return coalesce(recordBuilder.records);
}
export function createEventRecords(definition: ChangeDetectorDefinition): EventBinding[] {
// TODO: vsavkin: remove $event when the compiler handles render-side variables properly
var varNames = ListWrapper.concat(['$event'], definition.variableNames);
return definition.eventRecords.map(er => {
var records = _ConvertAstIntoProtoRecords.create(er, varNames);
var dirIndex = er.implicitReceiver instanceof DirectiveIndex ? er.implicitReceiver : null;
return new EventBinding(er.target.name, er.target.elementIndex, dirIndex, records);
});
}
export class ProtoRecordBuilder {
records: List<ProtoRecord>;
constructor() { this.records = []; }
add(b: BindingRecord, variableNames: string[], bindingIndex: number) {
var oldLast = ListWrapper.last(this.records);
if (isPresent(oldLast) && oldLast.bindingRecord.directiveRecord == b.directiveRecord) {
oldLast.lastInDirective = false;
}
var numberOfRecordsBefore = this.records.length;
this._appendRecords(b, variableNames, bindingIndex);
var newLast = ListWrapper.last(this.records);
if (isPresent(newLast) && newLast !== oldLast) {
newLast.lastInBinding = true;
newLast.lastInDirective = true;
this._setArgumentToPureFunction(numberOfRecordsBefore);
}
}
_setArgumentToPureFunction(startIndex: number): void {
for (var i = startIndex; i < this.records.length; ++i) {
var rec = this.records[i];
if (rec.isPureFunction()) {
rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction =
true);
}
}
}
_appendRecords(b: BindingRecord, variableNames: string[], bindingIndex: number) {
if (b.isDirectiveLifecycle()) {
this.records.push(new ProtoRecord(RecordType.DIRECTIVE_LIFECYCLE, b.lifecycleEvent, null, [],
[], -1, null, this.records.length + 1, b, false, false,
false, false, null));
} else {
_ConvertAstIntoProtoRecords.append(this.records, b, variableNames, bindingIndex);
}
}
}
class _ConvertAstIntoProtoRecords implements AstVisitor {
constructor(private _records: List<ProtoRecord>, private _bindingRecord: BindingRecord,
private _variableNames: string[], private _bindingIndex: number) {}
static append(records: List<ProtoRecord>, b: BindingRecord, variableNames: string[],
bindingIndex: number) {
var c = new _ConvertAstIntoProtoRecords(records, b, variableNames, bindingIndex);
b.ast.visit(c);
}
static create(b: BindingRecord, variableNames: List<any>): ProtoRecord[] {
var rec = [];
_ConvertAstIntoProtoRecords.append(rec, b, variableNames, null);
rec[rec.length - 1].lastInBinding = true;
return rec;
}
visitImplicitReceiver(ast: ImplicitReceiver): any { return this._bindingRecord.implicitReceiver; }
visitInterpolation(ast: Interpolation): number {
var args = this._visitAll(ast.expressions);
return this._addRecord(RecordType.INTERPOLATE, "interpolate", _interpolationFn(ast.strings),
args, ast.strings, 0);
}
visitLiteralPrimitive(ast: LiteralPrimitive): number {
return this._addRecord(RecordType.CONST, "literal", ast.value, [], null, 0);
}
visitPropertyRead(ast: PropertyRead): number {
var receiver = ast.receiver.visit(this);
if (isPresent(this._variableNames) && ListWrapper.contains(this._variableNames, ast.name) &&
ast.receiver instanceof ImplicitReceiver) {
return this._addRecord(RecordType.LOCAL, ast.name, ast.name, [], null, receiver);
} else {
return this._addRecord(RecordType.PROPERTY_READ, ast.name, ast.getter, [], null, receiver);
}
}
visitPropertyWrite(ast: PropertyWrite): number {
if (isPresent(this._variableNames) && ListWrapper.contains(this._variableNames, ast.name) &&
ast.receiver instanceof ImplicitReceiver) {
throw new BaseException(`Cannot reassign a variable binding ${ast.name}`);
} else {
var receiver = ast.receiver.visit(this);
var value = ast.value.visit(this);
return this._addRecord(RecordType.PROPERTY_WRITE, ast.name, ast.setter, [value], null,
receiver);
}
}
visitKeyedWrite(ast: KeyedWrite): number {
var obj = ast.obj.visit(this);
var key = ast.key.visit(this);
var value = ast.value.visit(this);
return this._addRecord(RecordType.KEYED_WRITE, null, null, [key, value], null, obj);
}
visitSafePropertyRead(ast: SafePropertyRead): number {
var receiver = ast.receiver.visit(this);
return this._addRecord(RecordType.SAFE_PROPERTY, ast.name, ast.getter, [], null, receiver);
}
visitMethodCall(ast: MethodCall): number {
var receiver = ast.receiver.visit(this);
var args = this._visitAll(ast.args);
if (isPresent(this._variableNames) && ListWrapper.contains(this._variableNames, ast.name)) {
var target = this._addRecord(RecordType.LOCAL, ast.name, ast.name, [], null, receiver);
return this._addRecord(RecordType.INVOKE_CLOSURE, "closure", null, args, null, target);
} else {
return this._addRecord(RecordType.INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
}
}
visitSafeMethodCall(ast: SafeMethodCall): number {
var receiver = ast.receiver.visit(this);
var args = this._visitAll(ast.args);
return this._addRecord(RecordType.SAFE_INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
}
visitFunctionCall(ast: FunctionCall): number {
var target = ast.target.visit(this);
var args = this._visitAll(ast.args);
return this._addRecord(RecordType.INVOKE_CLOSURE, "closure", null, args, null, target);
}
visitLiteralArray(ast: LiteralArray): number {
var primitiveName = `arrayFn${ast.expressions.length}`;
return this._addRecord(RecordType.COLLECTION_LITERAL, primitiveName,
_arrayFn(ast.expressions.length), this._visitAll(ast.expressions), null,
0);
}
visitLiteralMap(ast: LiteralMap): number {
return this._addRecord(RecordType.COLLECTION_LITERAL, _mapPrimitiveName(ast.keys),
ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null,
0);
}
visitBinary(ast: Binary): number {
var left = ast.left.visit(this);
var right = ast.right.visit(this);
return this._addRecord(RecordType.PRIMITIVE_OP, _operationToPrimitiveName(ast.operation),
_operationToFunction(ast.operation), [left, right], null, 0);
}
visitPrefixNot(ast: PrefixNot): number {
var exp = ast.expression.visit(this);
return this._addRecord(RecordType.PRIMITIVE_OP, "operation_negate",
ChangeDetectionUtil.operation_negate, [exp], null, 0);
}
visitConditional(ast: Conditional): number {
var c = ast.condition.visit(this);
var t = ast.trueExp.visit(this);
var f = ast.falseExp.visit(this);
return this._addRecord(RecordType.PRIMITIVE_OP, "cond", ChangeDetectionUtil.cond, [c, t, f],
null, 0);
}
visitPipe(ast: BindingPipe): number {
var value = ast.exp.visit(this);
var args = this._visitAll(ast.args);
return this._addRecord(RecordType.PIPE, ast.name, ast.name, args, null, value);
}
visitKeyedRead(ast: KeyedRead): number {
var obj = ast.obj.visit(this);
var key = ast.key.visit(this);
return this._addRecord(RecordType.KEYED_READ, "keyedAccess", ChangeDetectionUtil.keyedAccess,
[key], null, obj);
}
visitChain(ast: Chain): number {
var args = ast.expressions.map(e => e.visit(this));
return this._addRecord(RecordType.CHAIN, "chain", null, args, null, 0);
}
visitIf(ast: If) { throw new BaseException('Not supported'); }
_visitAll(asts: List<any>) {
var res = ListWrapper.createFixedSize(asts.length);
for (var i = 0; i < asts.length; ++i) {
res[i] = asts[i].visit(this);
}
return res;
}
_addRecord(type, name, funcOrValue, args, fixedArgs, context) {
var selfIndex = this._records.length + 1;
if (context instanceof DirectiveIndex) {
this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, -1, context,
selfIndex, this._bindingRecord, false, false, false, false,
this._bindingIndex));
} else {
this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, null,
selfIndex, this._bindingRecord, false, false, false, false,
this._bindingIndex));
}
return selfIndex;
}
}
function _arrayFn(length: number): Function {
switch (length) {
case 0:
return ChangeDetectionUtil.arrayFn0;
case 1:
return ChangeDetectionUtil.arrayFn1;
case 2:
return ChangeDetectionUtil.arrayFn2;
case 3:
return ChangeDetectionUtil.arrayFn3;
case 4:
return ChangeDetectionUtil.arrayFn4;
case 5:
return ChangeDetectionUtil.arrayFn5;
case 6:
return ChangeDetectionUtil.arrayFn6;
case 7:
return ChangeDetectionUtil.arrayFn7;
case 8:
return ChangeDetectionUtil.arrayFn8;
case 9:
return ChangeDetectionUtil.arrayFn9;
default:
throw new BaseException(`Does not support literal maps with more than 9 elements`);
}
}
function _mapPrimitiveName(keys: List<any>) {
var stringifiedKeys =
ListWrapper.join(ListWrapper.map(keys, (k) => isString(k) ? `"${k}"` : `${k}`), ", ");
return `mapFn([${stringifiedKeys}])`;
}
function _operationToPrimitiveName(operation: string): string {
switch (operation) {
case '+':
return "operation_add";
case '-':
return "operation_subtract";
case '*':
return "operation_multiply";
case '/':
return "operation_divide";
case '%':
return "operation_remainder";
case '==':
return "operation_equals";
case '!=':
return "operation_not_equals";
case '===':
return "operation_identical";
case '!==':
return "operation_not_identical";
case '<':
return "operation_less_then";
case '>':
return "operation_greater_then";
case '<=':
return "operation_less_or_equals_then";
case '>=':
return "operation_greater_or_equals_then";
case '&&':
return "operation_logical_and";
case '||':
return "operation_logical_or";
default:
throw new BaseException(`Unsupported operation ${operation}`);
}
}
function _operationToFunction(operation: string): Function {
switch (operation) {
case '+':
return ChangeDetectionUtil.operation_add;
case '-':
return ChangeDetectionUtil.operation_subtract;
case '*':
return ChangeDetectionUtil.operation_multiply;
case '/':
return ChangeDetectionUtil.operation_divide;
case '%':
return ChangeDetectionUtil.operation_remainder;
case '==':
return ChangeDetectionUtil.operation_equals;
case '!=':
return ChangeDetectionUtil.operation_not_equals;
case '===':
return ChangeDetectionUtil.operation_identical;
case '!==':
return ChangeDetectionUtil.operation_not_identical;
case '<':
return ChangeDetectionUtil.operation_less_then;
case '>':
return ChangeDetectionUtil.operation_greater_then;
case '<=':
return ChangeDetectionUtil.operation_less_or_equals_then;
case '>=':
return ChangeDetectionUtil.operation_greater_or_equals_then;
case '&&':
return ChangeDetectionUtil.operation_logical_and;
case '||':
return ChangeDetectionUtil.operation_logical_or;
default:
throw new BaseException(`Unsupported operation ${operation}`);
}
}
function s(v): string {
return isPresent(v) ? `${v}` : '';
}
function _interpolationFn(strings: List<any>) {
var length = strings.length;
var c0 = length > 0 ? strings[0] : null;
var c1 = length > 1 ? strings[1] : null;
var c2 = length > 2 ? strings[2] : null;
var c3 = length > 3 ? strings[3] : null;
var c4 = length > 4 ? strings[4] : null;
var c5 = length > 5 ? strings[5] : null;
var c6 = length > 6 ? strings[6] : null;
var c7 = length > 7 ? strings[7] : null;
var c8 = length > 8 ? strings[8] : null;
var c9 = length > 9 ? strings[9] : null;
switch (length - 1) {
case 1:
return (a1) => c0 + s(a1) + c1;
case 2:
return (a1, a2) => c0 + s(a1) + c1 + s(a2) + c2;
case 3:
return (a1, a2, a3) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3;
case 4:
return (a1, a2, a3, a4) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4;
case 5:
return (a1, a2, a3, a4, a5) =>
c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5;
case 6:
return (a1, a2, a3, a4, a5, a6) =>
c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6;
case 7:
return (a1, a2, a3, a4, a5, a6, a7) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) +
c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7;
case 8:
return (a1, a2, a3, a4, a5, a6, a7, a8) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) +
c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) +
c8;
case 9:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 +
s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) +
c7 + s(a8) + c8 + s(a9) + c9;
default:
throw new BaseException(`Does not support more than 9 expressions`);
}
}

View File

@ -0,0 +1,46 @@
import {List} from 'angular2/src/facade/collection';
import {BindingRecord} from './binding_record';
import {DirectiveIndex} from './directive_record';
export enum RecordType {
SELF,
CONST,
PRIMITIVE_OP,
PROPERTY_READ,
PROPERTY_WRITE,
LOCAL,
INVOKE_METHOD,
INVOKE_CLOSURE,
KEYED_READ,
KEYED_WRITE,
PIPE,
INTERPOLATE,
SAFE_PROPERTY,
COLLECTION_LITERAL,
SAFE_INVOKE_METHOD,
DIRECTIVE_LIFECYCLE,
CHAIN
}
export class ProtoRecord {
constructor(public mode: RecordType, public name: string, public funcOrValue,
public args: List<any>, public fixedArgs: List<any>, public contextIndex: number,
public directiveIndex: DirectiveIndex, public selfIndex: number,
public bindingRecord: BindingRecord, public lastInBinding: boolean,
public lastInDirective: boolean, public argumentToPureFunction: boolean,
public referencedBySelf: boolean, public propertyBindingIndex: number) {}
isPureFunction(): boolean {
return this.mode === RecordType.INTERPOLATE || this.mode === RecordType.COLLECTION_LITERAL;
}
isUsedByOtherRecord(): boolean { return !this.lastInBinding || this.referencedBySelf; }
shouldBeChecked(): boolean {
return this.argumentToPureFunction || this.lastInBinding || this.isPureFunction();
}
isPipeRecord(): boolean { return this.mode === RecordType.PIPE; }
isLifeCycleRecord(): boolean { return this.mode === RecordType.DIRECTIVE_LIFECYCLE; }
}

View File

@ -0,0 +1,194 @@
import {Type, isPresent, BaseException, isBlank} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, Predicate} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {ElementInjector} from 'angular2/src/core/compiler/element_injector';
import {AppView} from 'angular2/src/core/compiler/view';
import {internalView} from 'angular2/src/core/compiler/view_ref';
import {ElementRef} from 'angular2/src/core/compiler/element_ref';
/**
* A DebugElement contains information from the Angular compiler about an
* element and provides access to the corresponding ElementInjector and
* underlying dom Element, as well as a way to query for children.
*/
export class DebugElement {
_elementInjector: ElementInjector;
constructor(private _parentView: AppView, private _boundElementIndex: number) {
this._elementInjector = this._parentView.elementInjectors[this._boundElementIndex];
}
static create(elementRef: ElementRef): DebugElement {
return new DebugElement(internalView(elementRef.parentView), elementRef.boundElementIndex);
}
get componentInstance(): any {
if (!isPresent(this._elementInjector)) {
return null;
}
return this._elementInjector.getComponent();
}
get nativeElement(): any { return this.elementRef.nativeElement; }
get elementRef(): ElementRef { return this._parentView.elementRefs[this._boundElementIndex]; }
getDirectiveInstance(directiveIndex: number): any {
return this._elementInjector.getDirectiveAtIndex(directiveIndex);
}
/**
* Get child DebugElements from within the Light DOM.
*
* @return {List<DebugElement>}
*/
get children(): List<DebugElement> {
return this._getChildElements(this._parentView, this._boundElementIndex);
}
/**
* Get the root DebugElement children of a component. Returns an empty
* list if the current DebugElement is not a component root.
*
* @return {List<DebugElement>}
*/
get componentViewChildren(): List<DebugElement> {
var shadowView = this._parentView.getNestedView(this._boundElementIndex);
if (!isPresent(shadowView)) {
// The current element is not a component.
return [];
}
return this._getChildElements(shadowView, null);
}
triggerEventHandler(eventName: string, eventObj: Event): void {
this._parentView.triggerEventHandlers(eventName, eventObj, this._boundElementIndex);
}
hasDirective(type: Type): boolean {
if (!isPresent(this._elementInjector)) {
return false;
}
return this._elementInjector.hasDirective(type);
}
inject(type: Type): any {
if (!isPresent(this._elementInjector)) {
return null;
}
return this._elementInjector.get(type);
}
getLocal(name: string): any { return this._parentView.locals.get(name); }
/**
* Return the first descendant TestElement matching the given predicate
* and scope.
*
* @param {Function: boolean} predicate
* @param {Scope} scope
*
* @return {DebugElement}
*/
query(predicate: Predicate<DebugElement>, scope: Function = Scope.all): DebugElement {
var results = this.queryAll(predicate, scope);
return results.length > 0 ? results[0] : null;
}
/**
* Return descendant TestElememts matching the given predicate
* and scope.
*
* @param {Function: boolean} predicate
* @param {Scope} scope
*
* @return {List<DebugElement>}
*/
queryAll(predicate: Predicate<DebugElement>, scope: Function = Scope.all): List<DebugElement> {
var elementsInScope = scope(this);
return ListWrapper.filter(elementsInScope, predicate);
}
_getChildElements(view: AppView, parentBoundElementIndex: number): List<DebugElement> {
var els = [];
var parentElementBinder = null;
if (isPresent(parentBoundElementIndex)) {
parentElementBinder = view.proto.elementBinders[parentBoundElementIndex - view.elementOffset];
}
for (var i = 0; i < view.proto.elementBinders.length; ++i) {
var binder = view.proto.elementBinders[i];
if (binder.parent == parentElementBinder) {
els.push(new DebugElement(view, view.elementOffset + i));
var views = view.viewContainers[view.elementOffset + i];
if (isPresent(views)) {
ListWrapper.forEach(views.views, (nextView) => {
els = ListWrapper.concat(els, this._getChildElements(nextView, null));
});
}
}
}
return els;
}
}
export function inspectElement(elementRef: ElementRef): DebugElement {
return DebugElement.create(elementRef);
}
export function asNativeElements(arr: List<DebugElement>): List<any> {
return arr.map((debugEl) => debugEl.nativeElement);
}
export class Scope {
static all(debugElement: DebugElement): List<DebugElement> {
var scope = [];
scope.push(debugElement);
ListWrapper.forEach(debugElement.children,
(child) => { scope = ListWrapper.concat(scope, Scope.all(child)); });
ListWrapper.forEach(debugElement.componentViewChildren,
(child) => { scope = ListWrapper.concat(scope, Scope.all(child)); });
return scope;
}
static light(debugElement: DebugElement): List<DebugElement> {
var scope = [];
ListWrapper.forEach(debugElement.children, (child) => {
scope.push(child);
scope = ListWrapper.concat(scope, Scope.light(child));
});
return scope;
}
static view(debugElement: DebugElement): List<DebugElement> {
var scope = [];
ListWrapper.forEach(debugElement.componentViewChildren, (child) => {
scope.push(child);
scope = ListWrapper.concat(scope, Scope.light(child));
});
return scope;
}
}
export class By {
static all(): Function { return (debugElement) => true; }
static css(selector: string): Predicate<DebugElement> {
return (debugElement) => {
return isPresent(debugElement.nativeElement) ?
DOM.elementMatches(debugElement.nativeElement, selector) :
false;
};
}
static directive(type: Type): Predicate<DebugElement> {
return (debugElement) => { return debugElement.hasDirective(type); };
}
}

View File

@ -0,0 +1,74 @@
import {CONST_EXPR, isPresent, NumberWrapper, StringWrapper} from 'angular2/src/facade/lang';
import {MapWrapper, Map, ListWrapper, List} from 'angular2/src/facade/collection';
import {Injectable, bind, Binding} from 'angular2/di';
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
import {AppView} from 'angular2/src/core/compiler/view';
import {DOM} from 'angular2/src/dom/dom_adapter';
import {Renderer} from 'angular2/src/render/api';
import {DebugElement} from './debug_element';
const NG_ID_PROPERTY = 'ngid';
const INSPECT_GLOBAL_NAME = 'ngProbe';
var NG_ID_SEPARATOR = '#';
// Need to keep the views in a global Map so that multiple angular apps are supported
var _allIdsByView = new Map<AppView, number>();
var _allViewsById = new Map<number, AppView>();
var _nextId = 0;
function _setElementId(element, indices: List<number>) {
if (isPresent(element)) {
DOM.setData(element, NG_ID_PROPERTY, ListWrapper.join(indices, NG_ID_SEPARATOR));
}
}
function _getElementId(element): List<number> {
var elId = DOM.getData(element, NG_ID_PROPERTY);
if (isPresent(elId)) {
return ListWrapper.map(elId.split(NG_ID_SEPARATOR),
(partStr) => NumberWrapper.parseInt(partStr, 10));
} else {
return null;
}
}
export function inspectNativeElement(element): DebugElement {
var elId = _getElementId(element);
if (isPresent(elId)) {
var view = _allViewsById.get(elId[0]);
if (isPresent(view)) {
return new DebugElement(view, elId[1]);
}
}
return null;
}
@Injectable()
export class DebugElementViewListener implements AppViewListener {
constructor(private _renderer: Renderer) {
DOM.setGlobalVar(INSPECT_GLOBAL_NAME, inspectNativeElement);
}
viewCreated(view: AppView) {
var viewId = _nextId++;
_allViewsById.set(viewId, view);
_allIdsByView.set(view, viewId);
for (var i = 0; i < view.elementRefs.length; i++) {
var el = view.elementRefs[i];
_setElementId(this._renderer.getNativeElementSync(el), [viewId, i]);
}
}
viewDestroyed(view: AppView) {
var viewId = _allIdsByView.get(view);
MapWrapper.delete(_allIdsByView, view);
MapWrapper.delete(_allViewsById, viewId);
}
}
export const ELEMENT_PROBE_BINDINGS = CONST_EXPR([
DebugElementViewListener,
CONST_EXPR(new Binding(AppViewListener, {toAlias: DebugElementViewListener})),
]);

View File

@ -0,0 +1,447 @@
import {
Type,
isBlank,
isPresent,
CONST,
CONST_EXPR,
BaseException,
stringify,
isArray
} from 'angular2/src/facade/lang';
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {reflector} from 'angular2/src/reflection/reflection';
import {Key} from './key';
import {
InjectMetadata,
InjectableMetadata,
OptionalMetadata,
SelfMetadata,
HostMetadata,
SkipSelfMetadata,
DependencyMetadata
} from './metadata';
import {NoAnnotationError} from './exceptions';
import {resolveForwardRef} from './forward_ref';
/**
* @private
*/
export class Dependency {
constructor(public key: Key, public optional: boolean, public lowerBoundVisibility: any,
public upperBoundVisibility: any, public properties: List<any>) {}
static fromKey(key: Key): Dependency { return new Dependency(key, false, null, null, []); }
}
const _EMPTY_LIST = CONST_EXPR([]);
/**
* Describes how_ the {@link Injector} should instantiate a given token.
*
* See {@link bind}.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* new Binding(String, { toValue: 'Hello' })
* ]);
*
* expect(injector.get(String)).toEqual('Hello');
* ```
*/
@CONST()
export class Binding {
/**
* Token used when retrieving this binding. Usually the `Type`.
*/
token;
/**
* Binds an interface to an implementation / subclass.
*
* ## Example
*
* Becuse `toAlias` and `toClass` are often confused, the example contains both use cases for easy
* comparison.
*
* ```javascript
*
* class Vehicle {}
*
* class Car extends Vehicle {}
*
* var injectorClass = Injector.resolveAndCreate([
* Car,
* new Binding(Vehicle, { toClass: Car })
* ]);
* var injectorAlias = Injector.resolveAndCreate([
* Car,
* new Binding(Vehicle, { toAlias: Car })
* ]);
*
* expect(injectorClass.get(Vehicle)).not.toBe(injectorClass.get(Car));
* expect(injectorClass.get(Vehicle) instanceof Car).toBe(true);
*
* expect(injectorAlias.get(Vehicle)).toBe(injectorAlias.get(Car));
* expect(injectorAlias.get(Vehicle) instanceof Car).toBe(true);
* ```
*/
toClass: Type;
/**
* Binds a key to a value.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* new Binding(String, { toValue: 'Hello' })
* ]);
*
* expect(injector.get(String)).toEqual('Hello');
* ```
*/
toValue;
/**
* Binds a key to the alias for an existing key.
*
* An alias means that {@link Injector} returns the same instance as if the alias token was used.
* This is in contrast to `toClass` where a separate instance of `toClass` is returned.
*
* ## Example
*
* Becuse `toAlias` and `toClass` are often confused the example contains both use cases for easy
* comparison.
*
* ```javascript
*
* class Vehicle {}
*
* class Car extends Vehicle {}
*
* var injectorAlias = Injector.resolveAndCreate([
* Car,
* new Binding(Vehicle, { toAlias: Car })
* ]);
* var injectorClass = Injector.resolveAndCreate([
* Car,
* new Binding(Vehicle, { toClass: Car })
* ]);
*
* expect(injectorAlias.get(Vehicle)).toBe(injectorAlias.get(Car));
* expect(injectorAlias.get(Vehicle) instanceof Car).toBe(true);
*
* expect(injectorClass.get(Vehicle)).not.toBe(injectorClass.get(Car));
* expect(injectorClass.get(Vehicle) instanceof Car).toBe(true);
* ```
*/
toAlias;
/**
* Binds a key to a function which computes the value.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* new Binding(Number, { toFactory: () => { return 1+2; }}),
* new Binding(String, { toFactory: (value) => { return "Value: " + value; },
* dependencies: [Number] })
* ]);
*
* expect(injector.get(Number)).toEqual(3);
* expect(injector.get(String)).toEqual('Value: 3');
* ```
*/
toFactory: Function;
/**
* Used in conjunction with `toFactory` and specifies a set of dependencies
* (as `token`s) which should be injected into the factory function.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* new Binding(Number, { toFactory: () => { return 1+2; }}),
* new Binding(String, { toFactory: (value) => { return "Value: " + value; },
* dependencies: [Number] })
* ]);
*
* expect(injector.get(Number)).toEqual(3);
* expect(injector.get(String)).toEqual('Value: 3');
* ```
*/
dependencies: List<any>;
constructor(
token,
{toClass, toValue, toAlias, toFactory, deps}:
{toClass?: Type, toValue?: any, toAlias?: any, toFactory?: Function, deps?: List<any>}) {
this.token = token;
this.toClass = toClass;
this.toValue = toValue;
this.toAlias = toAlias;
this.toFactory = toFactory;
this.dependencies = deps;
}
/**
* Converts the {@link Binding} into {@link ResolvedBinding}.
*
* {@link Injector} internally only uses {@link ResolvedBinding}, {@link Binding} contains
* convenience binding syntax.
*/
resolve(): ResolvedBinding {
var factoryFn: Function;
var resolvedDeps;
if (isPresent(this.toClass)) {
var toClass = resolveForwardRef(this.toClass);
factoryFn = reflector.factory(toClass);
resolvedDeps = _dependenciesFor(toClass);
} else if (isPresent(this.toAlias)) {
factoryFn = (aliasInstance) => aliasInstance;
resolvedDeps = [Dependency.fromKey(Key.get(this.toAlias))];
} else if (isPresent(this.toFactory)) {
factoryFn = this.toFactory;
resolvedDeps = _constructDependencies(this.toFactory, this.dependencies);
} else {
factoryFn = () => this.toValue;
resolvedDeps = _EMPTY_LIST;
}
return new ResolvedBinding(Key.get(this.token), factoryFn, resolvedDeps);
}
}
/**
* An internal resolved representation of a {@link Binding} used by the {@link Injector}.
*
* A {@link Binding} is resolved when it has a factory function. Binding to a class, alias, or
* value, are just convenience methods, as {@link Injector} only operates on calling factory
* functions.
*/
export class ResolvedBinding {
constructor(
/**
* A key, usually a `Type`.
*/
public key: Key,
/**
* Factory function which can return an instance of an object represented by a key.
*/
public factory: Function,
/**
* Arguments (dependencies) to the `factory` function.
*/
public dependencies: List<Dependency>) {}
}
/**
* Provides an API for imperatively constructing {@link Binding}s.
*
* This is only relevant for JavaScript. See {@link BindingBuilder}.
*
* ## Example
*
* ```javascript
* bind(MyInterface).toClass(MyClass)
*
* ```
*/
export function bind(token): BindingBuilder {
return new BindingBuilder(token);
}
/**
* Helper class for the {@link bind} function.
*/
export class BindingBuilder {
constructor(public token) {}
/**
* Binds an interface to an implementation / subclass.
*
* ## Example
*
* Because `toAlias` and `toClass` are often confused, the example contains both use cases for
* easy comparison.
*
* ```javascript
*
* class Vehicle {}
*
* class Car extends Vehicle {}
*
* var injectorClass = Injector.resolveAndCreate([
* Car,
* bind(Vehicle).toClass(Car)
* ]);
* var injectorAlias = Injector.resolveAndCreate([
* Car,
* bind(Vehicle).toAlias(Car)
* ]);
*
* expect(injectorClass.get(Vehicle)).not.toBe(injectorClass.get(Car));
* expect(injectorClass.get(Vehicle) instanceof Car).toBe(true);
*
* expect(injectorAlias.get(Vehicle)).toBe(injectorAlias.get(Car));
* expect(injectorAlias.get(Vehicle) instanceof Car).toBe(true);
* ```
*/
toClass(type: Type): Binding { return new Binding(this.token, {toClass: type}); }
/**
* Binds a key to a value.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* bind(String).toValue('Hello')
* ]);
*
* expect(injector.get(String)).toEqual('Hello');
* ```
*/
toValue(value: any): Binding { return new Binding(this.token, {toValue: value}); }
/**
* Binds a key to the alias for an existing key.
*
* An alias means that we will return the same instance as if the alias token was used. (This is
* in contrast to `toClass` where a separate instance of `toClass` will be returned.)
*
* ## Example
*
* Becuse `toAlias` and `toClass` are often confused, the example contains both use cases for easy
* comparison.
*
* ```javascript
*
* class Vehicle {}
*
* class Car extends Vehicle {}
*
* var injectorAlias = Injector.resolveAndCreate([
* Car,
* bind(Vehicle).toAlias(Car)
* ]);
* var injectorClass = Injector.resolveAndCreate([
* Car,
* bind(Vehicle).toClass(Car)
* ]);
*
* expect(injectorAlias.get(Vehicle)).toBe(injectorAlias.get(Car));
* expect(injectorAlias.get(Vehicle) instanceof Car).toBe(true);
*
* expect(injectorClass.get(Vehicle)).not.toBe(injectorClass.get(Car));
* expect(injectorClass.get(Vehicle) instanceof Car).toBe(true);
* ```
*/
toAlias(aliasToken: /*Type*/ any): Binding {
if (isBlank(aliasToken)) {
throw new BaseException(`Can not alias ${stringify(this.token)} to a blank value!`);
}
return new Binding(this.token, {toAlias: aliasToken});
}
/**
* Binds a key to a function which computes the value.
*
* ## Example
*
* ```javascript
* var injector = Injector.resolveAndCreate([
* bind(Number).toFactory(() => { return 1+2; }),
* bind(String).toFactory((v) => { return "Value: " + v; }, [Number])
* ]);
*
* expect(injector.get(Number)).toEqual(3);
* expect(injector.get(String)).toEqual('Value: 3');
* ```
*/
toFactory(factoryFunction: Function, dependencies?: List<any>): Binding {
return new Binding(this.token, {toFactory: factoryFunction, deps: dependencies});
}
}
function _constructDependencies(factoryFunction: Function, dependencies: List<any>):
List<Dependency> {
if (isBlank(dependencies)) {
return _dependenciesFor(factoryFunction);
} else {
var params: List<List<any>> = ListWrapper.map(dependencies, (t) => [t]);
return ListWrapper.map(dependencies, (t) => _extractToken(factoryFunction, t, params));
}
}
function _dependenciesFor(typeOrFunc): List<Dependency> {
var params = reflector.parameters(typeOrFunc);
if (isBlank(params)) return [];
if (ListWrapper.any(params, (p) => isBlank(p))) {
throw new NoAnnotationError(typeOrFunc, params);
}
return ListWrapper.map(params, (p: List<any>) => _extractToken(typeOrFunc, p, params));
}
function _extractToken(typeOrFunc, metadata /*List<any> | any*/, params: List<List<any>>):
Dependency {
var depProps = [];
var token = null;
var optional = false;
if (!isArray(metadata)) {
return _createDependency(metadata, optional, null, null, depProps);
}
var lowerBoundVisibility = null;
var upperBoundVisibility = null;
for (var i = 0; i < metadata.length; ++i) {
var paramMetadata = metadata[i];
if (paramMetadata instanceof Type) {
token = paramMetadata;
} else if (paramMetadata instanceof InjectMetadata) {
token = paramMetadata.token;
} else if (paramMetadata instanceof OptionalMetadata) {
optional = true;
} else if (paramMetadata instanceof SelfMetadata) {
upperBoundVisibility = paramMetadata;
} else if (paramMetadata instanceof HostMetadata) {
upperBoundVisibility = paramMetadata;
} else if (paramMetadata instanceof SkipSelfMetadata) {
lowerBoundVisibility = paramMetadata;
} else if (paramMetadata instanceof DependencyMetadata) {
if (isPresent(paramMetadata.token)) {
token = paramMetadata.token;
}
depProps.push(paramMetadata);
}
}
token = resolveForwardRef(token);
if (isPresent(token)) {
return _createDependency(token, optional, lowerBoundVisibility, upperBoundVisibility, depProps);
} else {
throw new NoAnnotationError(typeOrFunc, params);
}
}
function _createDependency(token, optional, lowerBoundVisibility, upperBoundVisibility, depProps):
Dependency {
return new Dependency(Key.get(token), optional, lowerBoundVisibility, upperBoundVisibility,
depProps);
}

View File

@ -0,0 +1,46 @@
library angular2.di.decorators;
import 'metadata.dart';
export 'metadata.dart';
/**
* {@link InjectMetadata}.
*/
class Inject extends InjectMetadata {
const Inject(dynamic token) : super(token);
}
/**
* {@link OptionalMetadata}.
*/
class Optional extends OptionalMetadata {
const Optional() : super();
}
/**
* {@link InjectableMetadata}.
*/
class Injectable extends InjectableMetadata {
const Injectable() : super();
}
/**
* {@link SelfMetadata}.
*/
class Self extends SelfMetadata {
const Self() : super();
}
/**
* {@link HostMetadata}.
*/
class Host extends HostMetadata {
const Host() : super();
}
/**
* {@link SkipSelfMetadata}.
*/
class SkipSelf extends SkipSelfMetadata {
const SkipSelf() : super();
}

View File

@ -0,0 +1,87 @@
import {
InjectMetadata,
OptionalMetadata,
InjectableMetadata,
SelfMetadata,
HostMetadata,
SkipSelfMetadata
} from './metadata';
import {makeDecorator, makeParamDecorator, TypeDecorator} from '../util/decorators';
/**
* Factory for creating {@link InjectMetadata}.
*/
export interface InjectFactory {
(token: any): any;
new (token: any): InjectMetadata;
}
/**
* Factory for creating {@link OptionalMetadata}.
*/
export interface OptionalFactory {
(): any;
new (): OptionalMetadata;
}
/**
* Factory for creating {@link InjectableMetadata}.
*/
export interface InjectableFactory {
(): any;
new (): InjectableMetadata;
}
/**
* Factory for creating {@link SelfMetadata}.
*/
export interface SelfFactory {
(): any;
new (): SelfMetadata;
}
/**
* Factory for creating {@link HostMetadata}.
*/
export interface HostFactory {
(): any;
new (): HostMetadata;
}
/**
* Factory for creating {@link SkipSelfMetadata}.
*/
export interface SkipSelfFactory {
(): any;
new (): SkipSelfMetadata;
}
/**
* Factory for creating {@link InjectMetadata}.
*/
export var Inject: InjectFactory = makeParamDecorator(InjectMetadata);
/**
* Factory for creating {@link OptionalMetadata}.
*/
export var Optional: OptionalFactory = makeParamDecorator(OptionalMetadata);
/**
* Factory for creating {@link InjectableMetadata}.
*/
export var Injectable: InjectableFactory = <InjectableFactory>makeDecorator(InjectableMetadata);
/**
* Factory for creating {@link SelfMetadata}.
*/
export var Self: SelfFactory = makeParamDecorator(SelfMetadata);
/**
* Factory for creating {@link HostMetadata}.
*/
export var Host: HostFactory = makeParamDecorator(HostMetadata);
/**
* Factory for creating {@link SkipSelfMetadata}.
*/
export var SkipSelf: SkipSelfFactory = makeParamDecorator(SkipSelfMetadata);

View File

@ -0,0 +1,169 @@
import {ListWrapper, List} from 'angular2/src/facade/collection';
import {stringify, BaseException, isBlank} from 'angular2/src/facade/lang';
import {Key} from './key';
import {Injector} from './injector';
function findFirstClosedCycle(keys: List<any>): List<any> {
var res = [];
for (var i = 0; i < keys.length; ++i) {
if (ListWrapper.contains(res, keys[i])) {
res.push(keys[i]);
return res;
} else {
res.push(keys[i]);
}
}
return res;
}
function constructResolvingPath(keys: List<any>): string {
if (keys.length > 1) {
var reversed = findFirstClosedCycle(ListWrapper.reversed(keys));
var tokenStrs = ListWrapper.map(reversed, (k) => stringify(k.token));
return " (" + tokenStrs.join(' -> ') + ")";
} else {
return "";
}
}
/**
* Base class for all errors arising from misconfigured bindings.
*/
export class AbstractBindingError extends BaseException {
name: string;
message: string;
keys: List<Key>;
injectors: List<Injector>;
constructResolvingMessage: Function;
constructor(injector: Injector, key: Key, constructResolvingMessage: Function, originalException?,
originalStack?) {
super("DI Exception", originalException, originalStack, null);
this.keys = [key];
this.injectors = [injector];
this.constructResolvingMessage = constructResolvingMessage;
this.message = this.constructResolvingMessage(this.keys);
}
addKey(injector: Injector, key: Key): void {
this.injectors.push(injector);
this.keys.push(key);
this.message = this.constructResolvingMessage(this.keys);
}
get context() { return this.injectors[this.injectors.length - 1].debugContext(); }
toString(): string { return this.message; }
}
/**
* Thrown when trying to retrieve a dependency by `Key` from {@link Injector}, but the
* {@link Injector} does not have a {@link Binding} for {@link Key}.
*/
export class NoBindingError extends AbstractBindingError {
constructor(injector: Injector, key: Key) {
super(injector, key, function(keys: List<any>) {
var first = stringify(ListWrapper.first(keys).token);
return `No provider for ${first}!${constructResolvingPath(keys)}`;
});
}
}
/**
* Thrown when dependencies form a cycle.
*
* ## Example:
*
* ```javascript
* class A {
* constructor(b:B) {}
* }
* class B {
* constructor(a:A) {}
* }
* ```
*
* Retrieving `A` or `B` throws a `CyclicDependencyError` as the graph above cannot be constructed.
*/
export class CyclicDependencyError extends AbstractBindingError {
constructor(injector: Injector, key: Key) {
super(injector, key, function(keys: List<any>) {
return `Cannot instantiate cyclic dependency!${constructResolvingPath(keys)}`;
});
}
}
/**
* Thrown when a constructing type returns with an Error.
*
* The `InstantiationError` class contains the original error plus the dependency graph which caused
* this object to be instantiated.
*/
export class InstantiationError extends AbstractBindingError {
causeKey: Key;
constructor(injector: Injector, originalException, originalStack, key: Key) {
super(injector, key, function(keys: List<any>) {
var first = stringify(ListWrapper.first(keys).token);
return `Error during instantiation of ${first}!${constructResolvingPath(keys)}.`;
}, originalException, originalStack);
this.causeKey = key;
}
}
/**
* Thrown when an object other then {@link Binding} (or `Type`) is passed to {@link Injector}
* creation.
*/
export class InvalidBindingError extends BaseException {
message: string;
constructor(binding) {
super();
this.message = "Invalid binding - only instances of Binding and Type are allowed, got: " +
binding.toString();
}
toString(): string { return this.message; }
}
/**
* Thrown when the class has no annotation information.
*
* Lack of annotation information prevents the {@link Injector} from determining which dependencies
* need to be injected into the constructor.
*/
export class NoAnnotationError extends BaseException {
name: string;
message: string;
constructor(typeOrFunc, params: List<List<any>>) {
super();
var signature = [];
for (var i = 0, ii = params.length; i < ii; i++) {
var parameter = params[i];
if (isBlank(parameter) || parameter.length == 0) {
signature.push('?');
} else {
signature.push(ListWrapper.map(parameter, stringify).join(' '));
}
}
this.message = "Cannot resolve all parameters for " + stringify(typeOrFunc) + "(" +
signature.join(', ') + "). " +
'Make sure they all have valid type or annotations.';
}
toString(): string { return this.message; }
}
/**
* Thrown when getting an object by index.
*/
export class OutOfBoundsError extends BaseException {
message: string;
constructor(index) {
super();
this.message = `Index ${index} is out-of-bounds.`;
}
toString(): string { return this.message; }
}

View File

@ -0,0 +1,15 @@
library angular2.di.forward_ref;
typedef dynamic ForwardRefFn();
/**
* Dart does not have the forward ref problem, so this function is a noop.
*/
forwardRef(ForwardRefFn forwardRefFn) => forwardRefFn();
/**
* Lazily retrieve the reference value.
*
* See: {@link forwardRef}
*/
resolveForwardRef(type) => type;

View File

@ -0,0 +1,47 @@
import {Type, stringify, isFunction} from 'angular2/src/facade/lang';
export interface ForwardRefFn { (): any; }
/**
* Allows to refer to references which are not yet defined.
*
* This situation arises when the key which we need te refer to for the purposes of DI is declared,
* but not yet defined.
*
* ## Example:
*
* ```
* class Door {
* // Incorrect way to refer to a reference which is defined later.
* // This fails because `Lock` is undefined at this point.
* constructor(lock:Lock) { }
*
* // Correct way to refer to a reference which is defined later.
* // The reference needs to be captured in a closure.
* constructor(@Inject(forwardRef(() => Lock)) lock:Lock) { }
* }
*
* // Only at this point the lock is defined.
* class Lock {
* }
* ```
*/
export function forwardRef(forwardRefFn: ForwardRefFn): Type {
(<any>forwardRefFn).__forward_ref__ = forwardRef;
(<any>forwardRefFn).toString = function() { return stringify(this()); };
return (<Type><any>forwardRefFn);
}
/**
* Lazily retrieve the reference value.
*
* See: {@link forwardRef}
*/
export function resolveForwardRef(type: any): any {
if (isFunction(type) && type.hasOwnProperty('__forward_ref__') &&
type.__forward_ref__ === forwardRef) {
return (<ForwardRefFn>type)();
} else {
return type;
}
}

View File

@ -0,0 +1,850 @@
import {Map, List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {ResolvedBinding, Binding, Dependency, BindingBuilder, bind} from './binding';
import {
AbstractBindingError,
NoBindingError,
CyclicDependencyError,
InstantiationError,
InvalidBindingError,
OutOfBoundsError
} from './exceptions';
import {FunctionWrapper, Type, isPresent, isBlank, CONST_EXPR} from 'angular2/src/facade/lang';
import {Key} from './key';
import {resolveForwardRef} from './forward_ref';
import {SelfMetadata, HostMetadata, SkipSelfMetadata} from './metadata';
// Threshold for the dynamic version
const _MAX_CONSTRUCTION_COUNTER = 10;
export const UNDEFINED: Object = CONST_EXPR(new Object());
export enum Visibility {
Public,
Private,
PublicAndPrivate
}
function canSee(src: Visibility, dst: Visibility): boolean {
return (src === dst) ||
(dst === Visibility.PublicAndPrivate || src === Visibility.PublicAndPrivate);
}
export interface ProtoInjectorStrategy {
getBindingAtIndex(index: number): ResolvedBinding;
createInjectorStrategy(inj: Injector): InjectorStrategy;
}
export class ProtoInjectorInlineStrategy implements ProtoInjectorStrategy {
binding0: ResolvedBinding = null;
binding1: ResolvedBinding = null;
binding2: ResolvedBinding = null;
binding3: ResolvedBinding = null;
binding4: ResolvedBinding = null;
binding5: ResolvedBinding = null;
binding6: ResolvedBinding = null;
binding7: ResolvedBinding = null;
binding8: ResolvedBinding = null;
binding9: ResolvedBinding = null;
keyId0: number = null;
keyId1: number = null;
keyId2: number = null;
keyId3: number = null;
keyId4: number = null;
keyId5: number = null;
keyId6: number = null;
keyId7: number = null;
keyId8: number = null;
keyId9: number = null;
visibility0: Visibility = null;
visibility1: Visibility = null;
visibility2: Visibility = null;
visibility3: Visibility = null;
visibility4: Visibility = null;
visibility5: Visibility = null;
visibility6: Visibility = null;
visibility7: Visibility = null;
visibility8: Visibility = null;
visibility9: Visibility = null;
constructor(protoEI: ProtoInjector, bwv: BindingWithVisibility[]) {
var length = bwv.length;
if (length > 0) {
this.binding0 = bwv[0].binding;
this.keyId0 = bwv[0].getKeyId();
this.visibility0 = bwv[0].visibility;
}
if (length > 1) {
this.binding1 = bwv[1].binding;
this.keyId1 = bwv[1].getKeyId();
this.visibility1 = bwv[1].visibility;
}
if (length > 2) {
this.binding2 = bwv[2].binding;
this.keyId2 = bwv[2].getKeyId();
this.visibility2 = bwv[2].visibility;
}
if (length > 3) {
this.binding3 = bwv[3].binding;
this.keyId3 = bwv[3].getKeyId();
this.visibility3 = bwv[3].visibility;
}
if (length > 4) {
this.binding4 = bwv[4].binding;
this.keyId4 = bwv[4].getKeyId();
this.visibility4 = bwv[4].visibility;
}
if (length > 5) {
this.binding5 = bwv[5].binding;
this.keyId5 = bwv[5].getKeyId();
this.visibility5 = bwv[5].visibility;
}
if (length > 6) {
this.binding6 = bwv[6].binding;
this.keyId6 = bwv[6].getKeyId();
this.visibility6 = bwv[6].visibility;
}
if (length > 7) {
this.binding7 = bwv[7].binding;
this.keyId7 = bwv[7].getKeyId();
this.visibility7 = bwv[7].visibility;
}
if (length > 8) {
this.binding8 = bwv[8].binding;
this.keyId8 = bwv[8].getKeyId();
this.visibility8 = bwv[8].visibility;
}
if (length > 9) {
this.binding9 = bwv[9].binding;
this.keyId9 = bwv[9].getKeyId();
this.visibility9 = bwv[9].visibility;
}
}
getBindingAtIndex(index: number): any {
if (index == 0) return this.binding0;
if (index == 1) return this.binding1;
if (index == 2) return this.binding2;
if (index == 3) return this.binding3;
if (index == 4) return this.binding4;
if (index == 5) return this.binding5;
if (index == 6) return this.binding6;
if (index == 7) return this.binding7;
if (index == 8) return this.binding8;
if (index == 9) return this.binding9;
throw new OutOfBoundsError(index);
}
createInjectorStrategy(injector: Injector): InjectorStrategy {
return new InjectorInlineStrategy(injector, this);
}
}
export class ProtoInjectorDynamicStrategy implements ProtoInjectorStrategy {
bindings: ResolvedBinding[];
keyIds: number[];
visibilities: Visibility[];
constructor(protoInj: ProtoInjector, bwv: BindingWithVisibility[]) {
var len = bwv.length;
this.bindings = ListWrapper.createFixedSize(len);
this.keyIds = ListWrapper.createFixedSize(len);
this.visibilities = ListWrapper.createFixedSize(len);
for (var i = 0; i < len; i++) {
this.bindings[i] = bwv[i].binding;
this.keyIds[i] = bwv[i].getKeyId();
this.visibilities[i] = bwv[i].visibility;
}
}
getBindingAtIndex(index: number): any {
if (index < 0 || index >= this.bindings.length) {
throw new OutOfBoundsError(index);
}
return this.bindings[index];
}
createInjectorStrategy(ei: Injector): InjectorStrategy {
return new InjectorDynamicStrategy(this, ei);
}
}
export class ProtoInjector {
_strategy: ProtoInjectorStrategy;
numberOfBindings: number;
constructor(bwv: BindingWithVisibility[]) {
this.numberOfBindings = bwv.length;
this._strategy = bwv.length > _MAX_CONSTRUCTION_COUNTER ?
new ProtoInjectorDynamicStrategy(this, bwv) :
new ProtoInjectorInlineStrategy(this, bwv);
}
getBindingAtIndex(index: number): any { return this._strategy.getBindingAtIndex(index); }
}
export interface InjectorStrategy {
getObjByKeyId(keyId: number, visibility: Visibility): any;
getObjAtIndex(index: number): any;
getMaxNumberOfObjects(): number;
attach(parent: Injector, isHost: boolean): void;
resetConstructionCounter(): void;
instantiateBinding(binding: ResolvedBinding, visibility: Visibility): any;
}
export class InjectorInlineStrategy implements InjectorStrategy {
obj0: any = UNDEFINED;
obj1: any = UNDEFINED;
obj2: any = UNDEFINED;
obj3: any = UNDEFINED;
obj4: any = UNDEFINED;
obj5: any = UNDEFINED;
obj6: any = UNDEFINED;
obj7: any = UNDEFINED;
obj8: any = UNDEFINED;
obj9: any = UNDEFINED;
constructor(public injector: Injector, public protoStrategy: ProtoInjectorInlineStrategy) {}
resetConstructionCounter(): void { this.injector._constructionCounter = 0; }
instantiateBinding(binding: ResolvedBinding, visibility: Visibility): any {
return this.injector._new(binding, visibility);
}
attach(parent: Injector, isHost: boolean): void {
var inj = this.injector;
inj._parent = parent;
inj._isHost = isHost;
}
getObjByKeyId(keyId: number, visibility: Visibility): any {
var p = this.protoStrategy;
var inj = this.injector;
if (p.keyId0 === keyId && canSee(p.visibility0, visibility)) {
if (this.obj0 === UNDEFINED) {
this.obj0 = inj._new(p.binding0, p.visibility0);
}
return this.obj0;
}
if (p.keyId1 === keyId && canSee(p.visibility1, visibility)) {
if (this.obj1 === UNDEFINED) {
this.obj1 = inj._new(p.binding1, p.visibility1);
}
return this.obj1;
}
if (p.keyId2 === keyId && canSee(p.visibility2, visibility)) {
if (this.obj2 === UNDEFINED) {
this.obj2 = inj._new(p.binding2, p.visibility2);
}
return this.obj2;
}
if (p.keyId3 === keyId && canSee(p.visibility3, visibility)) {
if (this.obj3 === UNDEFINED) {
this.obj3 = inj._new(p.binding3, p.visibility3);
}
return this.obj3;
}
if (p.keyId4 === keyId && canSee(p.visibility4, visibility)) {
if (this.obj4 === UNDEFINED) {
this.obj4 = inj._new(p.binding4, p.visibility4);
}
return this.obj4;
}
if (p.keyId5 === keyId && canSee(p.visibility5, visibility)) {
if (this.obj5 === UNDEFINED) {
this.obj5 = inj._new(p.binding5, p.visibility5);
}
return this.obj5;
}
if (p.keyId6 === keyId && canSee(p.visibility6, visibility)) {
if (this.obj6 === UNDEFINED) {
this.obj6 = inj._new(p.binding6, p.visibility6);
}
return this.obj6;
}
if (p.keyId7 === keyId && canSee(p.visibility7, visibility)) {
if (this.obj7 === UNDEFINED) {
this.obj7 = inj._new(p.binding7, p.visibility7);
}
return this.obj7;
}
if (p.keyId8 === keyId && canSee(p.visibility8, visibility)) {
if (this.obj8 === UNDEFINED) {
this.obj8 = inj._new(p.binding8, p.visibility8);
}
return this.obj8;
}
if (p.keyId9 === keyId && canSee(p.visibility9, visibility)) {
if (this.obj9 === UNDEFINED) {
this.obj9 = inj._new(p.binding9, p.visibility9);
}
return this.obj9;
}
return UNDEFINED;
}
getObjAtIndex(index: number): any {
if (index == 0) return this.obj0;
if (index == 1) return this.obj1;
if (index == 2) return this.obj2;
if (index == 3) return this.obj3;
if (index == 4) return this.obj4;
if (index == 5) return this.obj5;
if (index == 6) return this.obj6;
if (index == 7) return this.obj7;
if (index == 8) return this.obj8;
if (index == 9) return this.obj9;
throw new OutOfBoundsError(index);
}
getMaxNumberOfObjects(): number { return _MAX_CONSTRUCTION_COUNTER; }
}
export class InjectorDynamicStrategy implements InjectorStrategy {
objs: any[];
constructor(public protoStrategy: ProtoInjectorDynamicStrategy, public injector: Injector) {
this.objs = ListWrapper.createFixedSize(protoStrategy.bindings.length);
ListWrapper.fill(this.objs, UNDEFINED);
}
resetConstructionCounter(): void { this.injector._constructionCounter = 0; }
instantiateBinding(binding: ResolvedBinding, visibility: Visibility): any {
return this.injector._new(binding, visibility);
}
attach(parent: Injector, isHost: boolean): void {
var inj = this.injector;
inj._parent = parent;
inj._isHost = isHost;
}
getObjByKeyId(keyId: number, visibility: Visibility): any {
var p = this.protoStrategy;
for (var i = 0; i < p.keyIds.length; i++) {
if (p.keyIds[i] === keyId && canSee(p.visibilities[i], visibility)) {
if (this.objs[i] === UNDEFINED) {
this.objs[i] = this.injector._new(p.bindings[i], p.visibilities[i]);
}
return this.objs[i];
}
}
return UNDEFINED;
}
getObjAtIndex(index: number): any {
if (index < 0 || index >= this.objs.length) {
throw new OutOfBoundsError(index);
}
return this.objs[index];
}
getMaxNumberOfObjects(): number { return this.objs.length; }
}
export class BindingWithVisibility {
constructor(public binding: ResolvedBinding, public visibility: Visibility){};
getKeyId(): number { return this.binding.key.id; }
}
/**
* Used to provide dependencies that cannot be easily expressed as bindings.
*/
export interface DependencyProvider {
getDependency(injector: Injector, binding: ResolvedBinding, dependency: Dependency): any;
}
/**
* A dependency injection container used for resolving dependencies.
*
* An `Injector` is a replacement for a `new` operator, which can automatically resolve the
* constructor dependencies.
* In typical use, application code asks for the dependencies in the constructor and they are
* resolved by the `Injector`.
*
* ## Example:
*
* Suppose that we want to inject an `Engine` into class `Car`, we would define it like this:
*
* ```javascript
* class Engine {
* }
*
* class Car {
* constructor(@Inject(Engine) engine) {
* }
* }
*
* ```
*
* Next we need to write the code that creates and instantiates the `Injector`. We then ask for the
* `root` object, `Car`, so that the `Injector` can recursively build all of that object's
*dependencies.
*
* ```javascript
* main() {
* var injector = Injector.resolveAndCreate([Car, Engine]);
*
* // Get a reference to the `root` object, which will recursively instantiate the tree.
* var car = injector.get(Car);
* }
* ```
* Notice that we don't use the `new` operator because we explicitly want to have the `Injector`
* resolve all of the object's dependencies automatically.
*/
export class Injector {
/**
* Turns a list of binding definitions into an internal resolved list of resolved bindings.
*
* A resolution is a process of flattening multiple nested lists and converting individual
* bindings into a list of {@link ResolvedBinding}s. The resolution can be cached by `resolve`
* for the {@link Injector} for performance-sensitive code.
*
* @param `bindings` can be a list of `Type`, {@link Binding}, {@link ResolvedBinding}, or a
* recursive list of more bindings.
*
* The returned list is sparse, indexed by `id` for the {@link Key}. It is generally not useful to
*application code
* other than for passing it to {@link Injector} functions that require resolved binding lists,
*such as
* `fromResolvedBindings` and `createChildFromResolved`.
*/
static resolve(bindings: List<Type | Binding | List<any>>): List<ResolvedBinding> {
var resolvedBindings = _resolveBindings(bindings);
var flatten = _flattenBindings(resolvedBindings, new Map());
return _createListOfBindings(flatten);
}
/**
* Resolves bindings and creates an injector based on those bindings. This function is slower than
* the corresponding `fromResolvedBindings` because it needs to resolve bindings first. See
*`resolve`
* for the {@link Injector}.
*
* Prefer `fromResolvedBindings` in performance-critical code that creates lots of injectors.
*
* @param `bindings` can be a list of `Type`, {@link Binding}, {@link ResolvedBinding}, or a
*recursive list of more
* bindings.
* @param `depProvider`
*/
static resolveAndCreate(bindings: List<Type | Binding | List<any>>,
depProvider: DependencyProvider = null): Injector {
var resolvedBindings = Injector.resolve(bindings);
return Injector.fromResolvedBindings(resolvedBindings, depProvider);
}
/**
* Creates an injector from previously resolved bindings. This bypasses resolution and flattening.
* This API is the recommended way to construct injectors in performance-sensitive parts.
*
* @param `bindings` A sparse list of {@link ResolvedBinding}s. See `resolve` for the
* {@link Injector}.
* @param `depProvider`
*/
static fromResolvedBindings(bindings: List<ResolvedBinding>,
depProvider: DependencyProvider = null): Injector {
var bd = bindings.map(b => new BindingWithVisibility(b, Visibility.Public));
var proto = new ProtoInjector(bd);
var inj = new Injector(proto, null, depProvider);
return inj;
}
_strategy: InjectorStrategy;
_isHost: boolean = false;
_constructionCounter: number = 0;
constructor(public _proto: ProtoInjector, public _parent: Injector = null,
private _depProvider: DependencyProvider = null,
private _debugContext: Function = null) {
this._strategy = _proto._strategy.createInjectorStrategy(this);
}
/**
* Returns debug information about the injector.
*
* This information is included into exceptions thrown by the injector.
*/
debugContext(): any { return this._debugContext(); }
/**
* Retrieves an instance from the injector.
*
* @param `token`: usually the `Type` of an object. (Same as the token used while setting up a
*binding).
* @returns an instance represented by the token. Throws if not found.
*/
get(token: any): any {
return this._getByKey(Key.get(token), null, null, false, Visibility.PublicAndPrivate);
}
/**
* Retrieves an instance from the injector.
*
* @param `token`: usually a `Type`. (Same as the token used while setting up a binding).
* @returns an instance represented by the token. Returns `null` if not found.
*/
getOptional(token: any): any {
return this._getByKey(Key.get(token), null, null, true, Visibility.PublicAndPrivate);
}
/**
* Retrieves an instance from the injector.
*
* @param `index`: index of an instance.
* @returns an instance represented by the index. Throws if not found.
*/
getAt(index: number): any { return this._strategy.getObjAtIndex(index); }
/**
* Direct parent of this injector.
*/
get parent(): Injector { return this._parent; }
/**
* Internal. Do not use.
*
* We return `any` not to export the InjectorStrategy type.
*/
get internalStrategy(): any { return this._strategy; }
/**
* Creates a child injector and loads a new set of bindings into it.
*
* A resolution is a process of flattening multiple nested lists and converting individual
* bindings into a list of {@link ResolvedBinding}s. The resolution can be cached by `resolve`
* for the {@link Injector} for performance-sensitive code.
*
* @param `bindings` can be a list of `Type`, {@link Binding}, {@link ResolvedBinding}, or a
* recursive list of more bindings.
* @param `depProvider`
*/
resolveAndCreateChild(bindings: List<Type | Binding | List<any>>,
depProvider: DependencyProvider = null): Injector {
var resovledBindings = Injector.resolve(bindings);
return this.createChildFromResolved(resovledBindings, depProvider);
}
/**
* Creates a child injector and loads a new set of {@link ResolvedBinding}s into it.
*
* @param `bindings`: A sparse list of {@link ResolvedBinding}s.
* See `resolve` for the {@link Injector}.
* @param `depProvider`
* @returns a new child {@link Injector}.
*/
createChildFromResolved(bindings: List<ResolvedBinding>,
depProvider: DependencyProvider = null): Injector {
var bd = bindings.map(b => new BindingWithVisibility(b, Visibility.Public));
var proto = new ProtoInjector(bd);
var inj = new Injector(proto, null, depProvider);
inj._parent = this;
return inj;
}
/**
* Resolves a binding and instantiates an object in the context of the injector.
*
* @param `binding`: either a type or a binding.
* @returns an object created using binding.
*/
resolveAndInstantiate(binding: Type | Binding): any {
return this.instantiateResolved(Injector.resolve([binding])[0]);
}
/**
* Instantiates an object using a resolved bindin in the context of the injector.
*
* @param `binding`: a resolved binding
* @returns an object created using binding.
*/
instantiateResolved(binding: ResolvedBinding): any {
return this._instantiate(binding, Visibility.PublicAndPrivate);
}
_new(binding: ResolvedBinding, visibility: Visibility): any {
if (this._constructionCounter++ > this._strategy.getMaxNumberOfObjects()) {
throw new CyclicDependencyError(this, binding.key);
}
return this._instantiate(binding, visibility);
}
private _instantiate(binding: ResolvedBinding, visibility: Visibility): any {
var factory = binding.factory;
var deps = binding.dependencies;
var length = deps.length;
var d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17, d18, d19;
try {
d0 = length > 0 ? this._getByDependency(binding, deps[0], visibility) : null;
d1 = length > 1 ? this._getByDependency(binding, deps[1], visibility) : null;
d2 = length > 2 ? this._getByDependency(binding, deps[2], visibility) : null;
d3 = length > 3 ? this._getByDependency(binding, deps[3], visibility) : null;
d4 = length > 4 ? this._getByDependency(binding, deps[4], visibility) : null;
d5 = length > 5 ? this._getByDependency(binding, deps[5], visibility) : null;
d6 = length > 6 ? this._getByDependency(binding, deps[6], visibility) : null;
d7 = length > 7 ? this._getByDependency(binding, deps[7], visibility) : null;
d8 = length > 8 ? this._getByDependency(binding, deps[8], visibility) : null;
d9 = length > 9 ? this._getByDependency(binding, deps[9], visibility) : null;
d10 = length > 10 ? this._getByDependency(binding, deps[10], visibility) : null;
d11 = length > 11 ? this._getByDependency(binding, deps[11], visibility) : null;
d12 = length > 12 ? this._getByDependency(binding, deps[12], visibility) : null;
d13 = length > 13 ? this._getByDependency(binding, deps[13], visibility) : null;
d14 = length > 14 ? this._getByDependency(binding, deps[14], visibility) : null;
d15 = length > 15 ? this._getByDependency(binding, deps[15], visibility) : null;
d16 = length > 16 ? this._getByDependency(binding, deps[16], visibility) : null;
d17 = length > 17 ? this._getByDependency(binding, deps[17], visibility) : null;
d18 = length > 18 ? this._getByDependency(binding, deps[18], visibility) : null;
d19 = length > 19 ? this._getByDependency(binding, deps[19], visibility) : null;
} catch (e) {
if (e instanceof AbstractBindingError) {
e.addKey(this, binding.key);
}
throw e;
}
var obj;
try {
switch (length) {
case 0:
obj = factory();
break;
case 1:
obj = factory(d0);
break;
case 2:
obj = factory(d0, d1);
break;
case 3:
obj = factory(d0, d1, d2);
break;
case 4:
obj = factory(d0, d1, d2, d3);
break;
case 5:
obj = factory(d0, d1, d2, d3, d4);
break;
case 6:
obj = factory(d0, d1, d2, d3, d4, d5);
break;
case 7:
obj = factory(d0, d1, d2, d3, d4, d5, d6);
break;
case 8:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7);
break;
case 9:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8);
break;
case 10:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9);
break;
case 11:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10);
break;
case 12:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11);
break;
case 13:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12);
break;
case 14:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13);
break;
case 15:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14);
break;
case 16:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15);
break;
case 17:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16);
break;
case 18:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16,
d17);
break;
case 19:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16,
d17, d18);
break;
case 20:
obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16,
d17, d18, d19);
break;
}
} catch (e) {
throw new InstantiationError(this, e, e.stack, binding.key);
}
return obj;
}
private _getByDependency(binding: ResolvedBinding, dep: Dependency,
bindingVisibility: Visibility): any {
var special = isPresent(this._depProvider) ?
this._depProvider.getDependency(this, binding, dep) :
UNDEFINED;
if (special !== UNDEFINED) {
return special;
} else {
return this._getByKey(dep.key, dep.lowerBoundVisibility, dep.upperBoundVisibility,
dep.optional, bindingVisibility);
}
}
private _getByKey(key: Key, lowerBoundVisibility: Object, upperBoundVisibility: Object,
optional: boolean, bindingVisibility: Visibility): any {
if (key === INJECTOR_KEY) {
return this;
}
if (upperBoundVisibility instanceof SelfMetadata) {
return this._getByKeySelf(key, optional, bindingVisibility);
} else if (upperBoundVisibility instanceof HostMetadata) {
return this._getByKeyHost(key, optional, bindingVisibility, lowerBoundVisibility);
} else {
return this._getByKeyDefault(key, optional, bindingVisibility, lowerBoundVisibility);
}
}
_throwOrNull(key: Key, optional: boolean): any {
if (optional) {
return null;
} else {
throw new NoBindingError(this, key);
}
}
_getByKeySelf(key: Key, optional: boolean, bindingVisibility: Visibility): any {
var obj = this._strategy.getObjByKeyId(key.id, bindingVisibility);
return (obj !== UNDEFINED) ? obj : this._throwOrNull(key, optional);
}
_getByKeyHost(key: Key, optional: boolean, bindingVisibility: Visibility,
lowerBoundVisibility: Object): any {
var inj = this;
if (lowerBoundVisibility instanceof SkipSelfMetadata) {
if (inj._isHost) {
return this._getPrivateDependency(key, optional, inj);
} else {
inj = inj._parent;
}
}
while (inj != null) {
var obj = inj._strategy.getObjByKeyId(key.id, bindingVisibility);
if (obj !== UNDEFINED) return obj;
if (isPresent(inj._parent) && inj._isHost) {
return this._getPrivateDependency(key, optional, inj);
} else {
inj = inj._parent;
}
}
return this._throwOrNull(key, optional);
}
_getPrivateDependency(key: Key, optional: boolean, inj: Injector): any {
var obj = inj._parent._strategy.getObjByKeyId(key.id, Visibility.Private);
return (obj !== UNDEFINED) ? obj : this._throwOrNull(key, optional);
}
_getByKeyDefault(key: Key, optional: boolean, bindingVisibility: Visibility,
lowerBoundVisibility: Object): any {
var inj = this;
if (lowerBoundVisibility instanceof SkipSelfMetadata) {
bindingVisibility = inj._isHost ? Visibility.PublicAndPrivate : Visibility.Public;
inj = inj._parent;
}
while (inj != null) {
var obj = inj._strategy.getObjByKeyId(key.id, bindingVisibility);
if (obj !== UNDEFINED) return obj;
bindingVisibility = inj._isHost ? Visibility.PublicAndPrivate : Visibility.Public;
inj = inj._parent;
}
return this._throwOrNull(key, optional);
}
get displayName(): string {
return `Injector(bindings: [${_mapBindings(this, b => ` "${b.key.displayName}" `).join(", ")}])`;
}
toString(): string { return this.displayName; }
}
var INJECTOR_KEY = Key.get(Injector);
function _resolveBindings(bindings: List<Type | Binding | List<any>>): List<ResolvedBinding> {
var resolvedList = ListWrapper.createFixedSize(bindings.length);
for (var i = 0; i < bindings.length; i++) {
var unresolved = resolveForwardRef(bindings[i]);
var resolved;
if (unresolved instanceof ResolvedBinding) {
resolved = unresolved; // ha-ha! I'm easily amused
} else if (unresolved instanceof Type) {
resolved = bind(unresolved).toClass(unresolved).resolve();
} else if (unresolved instanceof Binding) {
resolved = unresolved.resolve();
} else if (unresolved instanceof List) {
resolved = _resolveBindings(unresolved);
} else if (unresolved instanceof BindingBuilder) {
throw new InvalidBindingError(unresolved.token);
} else {
throw new InvalidBindingError(unresolved);
}
resolvedList[i] = resolved;
}
return resolvedList;
}
function _createListOfBindings(flattenedBindings: Map<number, ResolvedBinding>):
List<ResolvedBinding> {
return MapWrapper.values(flattenedBindings);
}
function _flattenBindings(bindings: List<ResolvedBinding | List<any>>,
res: Map<number, ResolvedBinding>): Map<number, ResolvedBinding> {
ListWrapper.forEach(bindings, function(b) {
if (b instanceof ResolvedBinding) {
res.set(b.key.id, b);
} else if (b instanceof List) {
_flattenBindings(b, res);
}
});
return res;
}
function _mapBindings(injector: Injector, fn: Function): any[] {
var res = [];
for (var i = 0; i < injector._proto.numberOfBindings; ++i) {
res.push(fn(injector._proto.getBindingAtIndex(i)));
}
return res;
}

View File

@ -0,0 +1,66 @@
import {MapWrapper} from 'angular2/src/facade/collection';
import {stringify, CONST, Type, isBlank, BaseException} from 'angular2/src/facade/lang';
import {TypeLiteral} from './type_literal';
import {resolveForwardRef} from './forward_ref';
export {TypeLiteral} from './type_literal';
/**
* A unique object used for retrieving items from the {@link Injector}.
*
* Keys have:
* - a system-wide unique `id`.
* - a `token`, usually the `Type` of the instance.
*
* Keys are used internally by the {@link Injector} because their system-wide unique `id`s allow the
* injector to index in arrays rather than looking up items in maps.
*/
export class Key {
constructor(public token: Object, public id: number) {
if (isBlank(token)) {
throw new BaseException('Token must be defined!');
}
}
get displayName(): string { return stringify(this.token); }
/**
* Retrieves a `Key` for a token.
*/
static get(token: Object): Key { return _globalKeyRegistry.get(resolveForwardRef(token)); }
/**
* @returns the number of keys registered in the system.
*/
static get numberOfKeys(): number { return _globalKeyRegistry.numberOfKeys; }
}
/**
* @private
*/
export class KeyRegistry {
private _allKeys: Map<Object, Key> = new Map();
get(token: Object): Key {
if (token instanceof Key) return token;
// TODO: workaround for https://github.com/Microsoft/TypeScript/issues/3123
var theToken = token;
if (token instanceof TypeLiteral) {
theToken = token.type;
}
token = theToken;
if (this._allKeys.has(token)) {
return this._allKeys.get(token);
}
var newKey = new Key(token, Key.numberOfKeys);
this._allKeys.set(token, newKey);
return newKey;
}
get numberOfKeys(): number { return MapWrapper.size(this._allKeys); }
}
var _globalKeyRegistry = new KeyRegistry();

View File

@ -0,0 +1,166 @@
import {CONST, CONST_EXPR, stringify, isBlank, isPresent} from "angular2/src/facade/lang";
/**
* A parameter metadata that specifies a dependency.
*
* ```
* class AComponent {
* constructor(@Inject(MyService) aService:MyService) {}
* }
* ```
*/
@CONST()
export class InjectMetadata {
constructor(public token) {}
toString(): string { return `@Inject(${stringify(this.token)})`; }
}
/**
* A parameter metadata that marks a dependency as optional. {@link Injector} provides `null` if
* the dependency is not found.
*
* ```
* class AComponent {
* constructor(@Optional() aService:MyService) {
* this.aService = aService;
* }
* }
* ```
*/
@CONST()
export class OptionalMetadata {
toString(): string { return `@Optional()`; }
}
/**
* `DependencyMetadata is used by the framework to extend DI.
*
* Only metadata implementing `DependencyMetadata` are added to the list of dependency
* properties.
*
* For example:
*
* ```
* class Exclude extends DependencyMetadata {}
* class NotDependencyProperty {}
*
* class AComponent {
* constructor(@Exclude @NotDependencyProperty aService:AService) {}
* }
* ```
*
* will create the following dependency:
*
* ```
* new Dependency(Key.get(AService), [new Exclude()])
* ```
*
* The framework can use `new Exclude()` to handle the `aService` dependency
* in a specific way.
*/
@CONST()
export class DependencyMetadata {
get token() { return null; }
}
/**
* A marker metadata that marks a class as available to `Injector` for creation. Used by tooling
* for generating constructor stubs.
*
* ```
* class NeedsService {
* constructor(svc:UsefulService) {}
* }
*
* @Injectable
* class UsefulService {}
* ```
*/
@CONST()
export class InjectableMetadata {
constructor() {}
}
/**
* Specifies that an injector should retrieve a dependency from itself.
*
* ## Example
*
* ```
* class Dependency {
* }
*
* class NeedsDependency {
* constructor(public @Self() dependency:Dependency) {}
* }
*
* var inj = Injector.resolveAndCreate([Dependency, NeedsDependency]);
* var nd = inj.get(NeedsDependency);
* expect(nd.dependency).toBeAnInstanceOf(Dependency);
* ```
*/
@CONST()
export class SelfMetadata {
toString(): string { return `@Self()`; }
}
/**
* Specifies that the dependency resolution should start from the parent injector.
*
* ## Example
*
*
* ```
* class Service {}
*
* class ParentService implements Service {
* }
*
* class ChildService implements Service {
* constructor(public @SkipSelf() parentService:Service) {}
* }
*
* var parent = Injector.resolveAndCreate([
* bind(Service).toClass(ParentService)
* ]);
* var child = parent.resolveAndCreateChild([
* bind(Service).toClass(ChildSerice)
* ]);
* var s = child.get(Service);
* expect(s).toBeAnInstanceOf(ChildService);
* expect(s.parentService).toBeAnInstanceOf(ParentService);
* ```
*/
@CONST()
export class SkipSelfMetadata {
toString(): string { return `@SkipSelf()`; }
}
/**
* Specifies that an injector should retrieve a dependency from any injector until reaching the
* closest host.
*
* ## Example
*
* ```
* class Dependency {
* }
*
* class NeedsDependency {
* constructor(public @Host() dependency:Dependency) {}
* }
*
* var parent = Injector.resolveAndCreate([
* bind(Dependency).toClass(HostDependency)
* ]);
* var child = parent.resolveAndCreateChild([]);
* var grandChild = child.resolveAndCreateChild([NeedsDependency, Depedency]);
* var nd = grandChild.get(NeedsDependency);
* expect(nd.dependency).toBeAnInstanceOf(HostDependency);
* ```
*/
@CONST()
export class HostMetadata {
toString(): string { return `@Host()`; }
}

View File

@ -0,0 +1,10 @@
import {CONST} from 'angular2/src/facade/lang';
@CONST()
export class OpaqueToken {
_desc: string;
constructor(desc: string) { this._desc = 'Token(' + desc + ')'; }
toString(): string { return this._desc; }
}

View File

@ -0,0 +1,4 @@
library angular2.di.type_info;
// In dart always return empty, as we can get the co
argsLength(Type type) => 0;

View File

@ -0,0 +1,26 @@
library angular2.di.type_literal;
/**
* Use type literals as DI keys corresponding to generic types.
*
* Example:
*
* ```
* Injector.resolveAndCreate([
* bind(new TypeLiteral<List<int>>()).toValue([1, 2, 3])
* ]);
*
* class Foo {
* // Delend on `List<int>` normally.
* Foo(List<int> list) { ... }
* }
* ```
*
* This capability might be added to the language one day. See:
*
* https://code.google.com/p/dart/issues/detail?id=11923
*/
class TypeLiteral<T> {
const TypeLiteral();
Type get type => T;
}

View File

@ -0,0 +1,7 @@
/**
* Type literals is a Dart-only feature. This is here only so we can x-compile
* to multiple languages.
*/
export class TypeLiteral {
get type(): any { throw new Error("Type literals are only supported in Dart"); }
}

View File

@ -0,0 +1,130 @@
import {isPresent, isString, StringWrapper, isBlank} from 'angular2/src/facade/lang';
import {Directive, LifecycleEvent} from 'angular2/metadata';
import {ElementRef} from 'angular2/core';
import {Renderer} from 'angular2/src/render/api';
import {
KeyValueDiffer,
IterableDiffer,
IterableDiffers,
KeyValueDiffers
} from 'angular2/change_detection';
import {ListWrapper, StringMapWrapper, isListLikeIterable} from 'angular2/src/facade/collection';
/**
* Adds and removes CSS classes based on an {expression} value.
*
* The result of expression is used to add and remove CSS classes using the following logic,
* based on expression's value type:
* - {string} - all the CSS classes (space - separated) are added
* - {Array} - all the CSS classes (Array elements) are added
* - {Object} - each key corresponds to a CSS class name while values
* are interpreted as {boolean} expression. If a given expression
* evaluates to {true} a corresponding CSS class is added - otherwise
* it is removed.
*
* # Example:
*
* ```
* <div class="message" [ng-class]="{error: errorCount > 0}">
* Please check errors.
* </div>
* ```
*/
@Directive({
selector: '[ng-class]',
lifecycle: [LifecycleEvent.onCheck, LifecycleEvent.onDestroy],
properties: ['rawClass: ng-class', 'initialClasses: class']
})
export class NgClass {
private _differ: any;
private _mode: string;
private _initialClasses = [];
private _rawClass;
constructor(private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
private _ngEl: ElementRef, private _renderer: Renderer) {}
set initialClasses(v) {
this._applyInitialClasses(true);
this._initialClasses = isPresent(v) && isString(v) ? v.split(' ') : [];
this._applyInitialClasses(false);
this._applyClasses(this._rawClass, false);
}
set rawClass(v) {
this._cleanupClasses(this._rawClass);
if (isString(v)) {
v = v.split(' ');
}
this._rawClass = v;
if (isPresent(v)) {
if (isListLikeIterable(v)) {
this._differ = this._iterableDiffers.find(v).create(null);
this._mode = 'iterable';
} else {
this._differ = this._keyValueDiffers.find(v).create(null);
this._mode = 'keyValue';
}
} else {
this._differ = null;
}
}
onCheck(): void {
if (isPresent(this._differ)) {
var changes = this._differ.diff(this._rawClass);
if (isPresent(changes)) {
if (this._mode == 'iterable') {
this._applyIterableChanges(changes);
} else {
this._applyKeyValueChanges(changes);
}
}
}
}
onDestroy(): void { this._cleanupClasses(this._rawClass); }
private _cleanupClasses(rawClassVal): void {
this._applyClasses(rawClassVal, true);
this._applyInitialClasses(false);
}
private _applyKeyValueChanges(changes: any): void {
changes.forEachAddedItem((record) => { this._toggleClass(record.key, record.currentValue); });
changes.forEachChangedItem((record) => { this._toggleClass(record.key, record.currentValue); });
changes.forEachRemovedItem((record) => {
if (record.previousValue) {
this._toggleClass(record.key, false);
}
});
}
private _applyIterableChanges(changes: any): void {
changes.forEachAddedItem((record) => { this._toggleClass(record.item, true); });
changes.forEachRemovedItem((record) => { this._toggleClass(record.item, false); });
}
private _applyInitialClasses(isCleanup: boolean) {
ListWrapper.forEach(this._initialClasses,
(className) => { this._toggleClass(className, !isCleanup); });
}
private _applyClasses(rawClassVal, isCleanup: boolean) {
if (isPresent(rawClassVal)) {
if (isListLikeIterable(rawClassVal)) {
ListWrapper.forEach(rawClassVal, (className) => this._toggleClass(className, !isCleanup));
} else {
StringMapWrapper.forEach(rawClassVal, (expVal, className) => {
if (expVal) this._toggleClass(className, !isCleanup);
});
}
}
}
private _toggleClass(className: string, enabled): void {
this._renderer.setElementClass(this._ngEl, className, enabled);
}
}

View File

@ -0,0 +1,125 @@
import {Directive, LifecycleEvent} from 'angular2/metadata';
import {ViewContainerRef, ViewRef, TemplateRef} from 'angular2/core';
import {ChangeDetectorRef, IterableDiffer, IterableDiffers} from 'angular2/change_detection';
import {isPresent, isBlank} from 'angular2/src/facade/lang';
/**
* The `NgFor` directive instantiates a template once per item from an iterable. The context for
* each instantiated template inherits from the outer context with the given loop variable set
* to the current item from the iterable.
*
* It is possible to alias the `index` to a local variable that will be set to the current loop
* iteration in the template context.
*
* When the contents of the iterator changes, `NgFor` makes the corresponding changes to the DOM:
*
* * When an item is added, a new instance of the template is added to the DOM.
* * When an item is removed, its template instance is removed from the DOM.
* * When items are reordered, their respective templates are reordered in the DOM.
*
* # Example
*
* ```
* <ul>
* <li *ng-for="#error of errors; #i = index">
* Error {{i}} of {{errors.length}}: {{error.message}}
* </li>
* </ul>
* ```
*
* # Syntax
*
* - `<li *ng-for="#item of items; #i = index">...</li>`
* - `<li template="ng-for #item of items; #i = index">...</li>`
* - `<template ng-for #item [ng-for-of]="items" #i="index"><li>...</li></template>`
*/
@Directive(
{selector: '[ng-for][ng-for-of]', properties: ['ngForOf'], lifecycle: [LifecycleEvent.onCheck]})
export class NgFor {
_ngForOf: any;
private _differ: IterableDiffer;
constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef,
private iterableDiffers: IterableDiffers, private cdr: ChangeDetectorRef) {}
set ngForOf(value: any) {
this._ngForOf = value;
if (isBlank(this._differ) && isPresent(value)) {
this._differ = this.iterableDiffers.find(value).create(this.cdr);
}
}
onCheck() {
if (isPresent(this._differ)) {
var changes = this._differ.diff(this._ngForOf);
if (isPresent(changes)) this._applyChanges(changes);
}
}
private _applyChanges(changes) {
// TODO(rado): check if change detection can produce a change record that is
// easier to consume than current.
var recordViewTuples = [];
changes.forEachRemovedItem((removedRecord) =>
recordViewTuples.push(new RecordViewTuple(removedRecord, null)));
changes.forEachMovedItem((movedRecord) =>
recordViewTuples.push(new RecordViewTuple(movedRecord, null)));
var insertTuples = NgFor.bulkRemove(recordViewTuples, this.viewContainer);
changes.forEachAddedItem((addedRecord) =>
insertTuples.push(new RecordViewTuple(addedRecord, null)));
NgFor.bulkInsert(insertTuples, this.viewContainer, this.templateRef);
for (var i = 0; i < insertTuples.length; i++) {
this._perViewChange(insertTuples[i].view, insertTuples[i].record);
}
}
private _perViewChange(view, record) {
view.setLocal('\$implicit', record.item);
view.setLocal('index', record.currentIndex);
}
static bulkRemove(tuples: List<RecordViewTuple>,
viewContainer: ViewContainerRef): List<RecordViewTuple> {
tuples.sort((a, b) => a.record.previousIndex - b.record.previousIndex);
var movedTuples = [];
for (var i = tuples.length - 1; i >= 0; i--) {
var tuple = tuples[i];
// separate moved views from removed views.
if (isPresent(tuple.record.currentIndex)) {
tuple.view = viewContainer.detach(tuple.record.previousIndex);
movedTuples.push(tuple);
} else {
viewContainer.remove(tuple.record.previousIndex);
}
}
return movedTuples;
}
static bulkInsert(tuples: List<RecordViewTuple>, viewContainer: ViewContainerRef,
templateRef: TemplateRef): List<RecordViewTuple> {
tuples.sort((a, b) => a.record.currentIndex - b.record.currentIndex);
for (var i = 0; i < tuples.length; i++) {
var tuple = tuples[i];
if (isPresent(tuple.view)) {
viewContainer.insert(tuple.view, tuple.record.currentIndex);
} else {
tuple.view = viewContainer.createEmbeddedView(templateRef, tuple.record.currentIndex);
}
}
return tuples;
}
}
export class RecordViewTuple {
view: ViewRef;
record: any;
constructor(record, view) {
this.record = record;
this.view = view;
}
}

View File

@ -0,0 +1,42 @@
import {Directive} from 'angular2/metadata';
import {ViewContainerRef, TemplateRef} from 'angular2/core';
import {isBlank} from 'angular2/src/facade/lang';
/**
* Removes or recreates a portion of the DOM tree based on an {expression}.
*
* If the expression assigned to `ng-if` evaluates to a false value then the element
* is removed from the DOM, otherwise a clone of the element is reinserted into the DOM.
*
* # Example:
*
* ```
* <div *ng-if="errorCount > 0" class="error">
* <!-- Error message displayed when the errorCount property on the current context is greater
* than 0. -->
* {{errorCount}} errors detected
* </div>
* ```
*
* # Syntax
*
* - `<div *ng-if="condition">...</div>`
* - `<div template="ng-if condition">...</div>`
* - `<template [ng-if]="condition"><div>...</div></template>`
*/
@Directive({selector: '[ng-if]', properties: ['ngIf']})
export class NgIf {
private _prevCondition: boolean = null;
constructor(private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef) {}
set ngIf(newCondition /* boolean */) {
if (newCondition && (isBlank(this._prevCondition) || !this._prevCondition)) {
this._prevCondition = true;
this._viewContainer.createEmbeddedView(this._templateRef);
} else if (!newCondition && (isBlank(this._prevCondition) || this._prevCondition)) {
this._prevCondition = false;
this._viewContainer.clear();
}
}
}

View File

@ -0,0 +1,18 @@
import {Directive} from 'angular2/metadata';
/**
* The `NgNonBindable` directive tells Angular not to compile or bind the contents of the current
* DOM element. This is useful if the element contains what appears to be Angular directives and
* bindings but which should be ignored by Angular. This could be the case if you have a site that
* displays snippets of code, for instance.
*
* Example:
*
* ```
* <div>Normal: {{1 + 2}}</div> // output "Normal: 3"
* <div ng-non-bindable>Ignored: {{1 + 2}}</div> // output "Ignored: {{1 + 2}}"
* ```
*/
@Directive({selector: '[ng-non-bindable]', compileChildren: false})
export class NgNonBindable {
}

View File

@ -0,0 +1,65 @@
import {Directive, LifecycleEvent} from 'angular2/metadata';
import {ElementRef} from 'angular2/core';
import {KeyValueDiffer, KeyValueDiffers} from 'angular2/change_detection';
import {isPresent, isBlank, print} from 'angular2/src/facade/lang';
import {Renderer} from 'angular2/src/render/api';
/**
* Adds or removes styles based on an {expression}.
*
* When the expression assigned to `ng-style` evaluates to an object, the corresponding element
* styles are updated. Style names to update are taken from the object keys and values - from the
* corresponding object values.
*
* # Example:
*
* ```
* <div [ng-style]="{'text-align': alignExp}"></div>
* ```
*
* In the above example the `text-align` style will be updated based on the `alignExp` value
* changes.
*
* # Syntax
*
* - `<div [ng-style]="{'text-align': alignExp}"></div>`
* - `<div [ng-style]="styleExp"></div>`
*/
@Directive({
selector: '[ng-style]',
lifecycle: [LifecycleEvent.onCheck],
properties: ['rawStyle: ng-style']
})
export class NgStyle {
_rawStyle;
_differ: KeyValueDiffer;
constructor(private _differs: KeyValueDiffers, private _ngEl: ElementRef,
private _renderer: Renderer) {}
set rawStyle(v) {
this._rawStyle = v;
if (isBlank(this._differ) && isPresent(v)) {
this._differ = this._differs.find(this._rawStyle).create(null);
}
}
onCheck() {
if (isPresent(this._differ)) {
var changes = this._differ.diff(this._rawStyle);
if (isPresent(changes)) {
this._applyChanges(changes);
}
}
}
private _applyChanges(changes: any): void {
changes.forEachAddedItem((record) => { this._setStyle(record.key, record.currentValue); });
changes.forEachChangedItem((record) => { this._setStyle(record.key, record.currentValue); });
changes.forEachRemovedItem((record) => { this._setStyle(record.key, null); });
}
private _setStyle(name: string, val: string): void {
this._renderer.setElementStyle(this._ngEl, name, val);
}
}

View File

@ -0,0 +1,176 @@
import {Directive} from 'angular2/metadata';
import {Host} from 'angular2/di';
import {ViewContainerRef, TemplateRef} from 'angular2/core';
import {isPresent, isBlank, normalizeBlank, CONST_EXPR} from 'angular2/src/facade/lang';
import {ListWrapper, List, Map} from 'angular2/src/facade/collection';
const _WHEN_DEFAULT = CONST_EXPR(new Object());
export class SwitchView {
constructor(private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef) {}
create(): void { this._viewContainerRef.createEmbeddedView(this._templateRef); }
destroy(): void { this._viewContainerRef.clear(); }
}
/**
* The `NgSwitch` directive is used to conditionally swap DOM structure on your template based on a
* scope expression.
* Elements within `NgSwitch` but without `NgSwitchWhen` or `NgSwitchDefault` directives will be
* preserved at the location as specified in the template.
*
* `NgSwitch` simply chooses nested elements and makes them visible based on which element matches
* the value obtained from the evaluated expression. In other words, you define a container element
* (where you place the directive), place an expression on the **`[ng-switch]="..."` attribute**),
* define any inner elements inside of the directive and place a `[ng-switch-when]` attribute per
* element.
* The when attribute is used to inform NgSwitch which element to display when the expression is
* evaluated. If a matching expression is not found via a when attribute then an element with the
* default attribute is displayed.
*
* # Example:
*
* ```
* <ANY [ng-switch]="expression">
* <template [ng-switch-when]="whenExpression1">...</template>
* <template [ng-switch-when]="whenExpression1">...</template>
* <template ng-switch-default>...</template>
* </ANY>
* ```
*/
@Directive({selector: '[ng-switch]', properties: ['ngSwitch']})
export class NgSwitch {
private _switchValue: any;
private _useDefault: boolean = false;
private _valueViews: Map<any, List<SwitchView>> = new Map();
private _activeViews: List<SwitchView> = [];
set ngSwitch(value) {
// Empty the currently active ViewContainers
this._emptyAllActiveViews();
// Add the ViewContainers matching the value (with a fallback to default)
this._useDefault = false;
var views = this._valueViews.get(value);
if (isBlank(views)) {
this._useDefault = true;
views = normalizeBlank(this._valueViews.get(_WHEN_DEFAULT));
}
this._activateViews(views);
this._switchValue = value;
}
_onWhenValueChanged(oldWhen, newWhen, view: SwitchView): void {
this._deregisterView(oldWhen, view);
this._registerView(newWhen, view);
if (oldWhen === this._switchValue) {
view.destroy();
ListWrapper.remove(this._activeViews, view);
} else if (newWhen === this._switchValue) {
if (this._useDefault) {
this._useDefault = false;
this._emptyAllActiveViews();
}
view.create();
this._activeViews.push(view);
}
// Switch to default when there is no more active ViewContainers
if (this._activeViews.length === 0 && !this._useDefault) {
this._useDefault = true;
this._activateViews(this._valueViews.get(_WHEN_DEFAULT));
}
}
_emptyAllActiveViews(): void {
var activeContainers = this._activeViews;
for (var i = 0; i < activeContainers.length; i++) {
activeContainers[i].destroy();
}
this._activeViews = [];
}
_activateViews(views: List<SwitchView>): void {
// TODO(vicb): assert(this._activeViews.length === 0);
if (isPresent(views)) {
for (var i = 0; i < views.length; i++) {
views[i].create();
}
this._activeViews = views;
}
}
_registerView(value, view: SwitchView): void {
var views = this._valueViews.get(value);
if (isBlank(views)) {
views = [];
this._valueViews.set(value, views);
}
views.push(view);
}
_deregisterView(value, view: SwitchView): void {
// `_WHEN_DEFAULT` is used a marker for non-registered whens
if (value === _WHEN_DEFAULT) return;
var views = this._valueViews.get(value);
if (views.length == 1) {
this._valueViews.delete(value);
} else {
ListWrapper.remove(views, view);
}
}
}
/**
* Defines a case statement as an expression.
*
* If multiple `NgSwitchWhen` match the `NgSwitch` value, all of them are displayed.
*
* Example:
*
* ```
* // match against a context variable
* <template [ng-switch-when]="contextVariable">...</template>
*
* // match against a constant string
* <template ng-switch-when="stringValue">...</template>
* ```
*/
@Directive({selector: '[ng-switch-when]', properties: ['ngSwitchWhen']})
export class NgSwitchWhen {
// `_WHEN_DEFAULT` is used as a marker for a not yet initialized value
_value: any = _WHEN_DEFAULT;
_view: SwitchView;
constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef,
@Host() private _switch: NgSwitch) {
this._view = new SwitchView(viewContainer, templateRef);
}
set ngSwitchWhen(value) {
this._switch._onWhenValueChanged(this._value, value, this._view);
this._value = value;
}
}
/**
* Defines a default case statement.
*
* Default case statements are displayed when no `NgSwitchWhen` match the `ng-switch` value.
*
* Example:
*
* ```
* <template ng-switch-default>...</template>
* ```
*/
@Directive({selector: '[ng-switch-default]'})
export class NgSwitchDefault {
constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef,
@Host() sswitch: NgSwitch) {
sswitch._registerView(_WHEN_DEFAULT, new SwitchView(viewContainer, templateRef));
}
}

View File

@ -0,0 +1,63 @@
library angular2.directives.observable_list_iterable_diff;
import 'package:observe/observe.dart' show ObservableList;
import 'package:angular2/change_detection.dart';
import 'package:angular2/src/change_detection/differs/default_iterable_differ.dart';
import 'dart:async';
class ObservableListDiff extends DefaultIterableDiffer {
ChangeDetectorRef _ref;
ObservableListDiff(this._ref);
bool _updated = true;
ObservableList _collection;
StreamSubscription _subscription;
onDestroy() {
if (this._subscription != null) {
this._subscription.cancel();
this._subscription = null;
this._collection = null;
}
}
dynamic diff(ObservableList collection) {
if (collection is! ObservableList) {
throw "Cannot change the type of a collection";
}
// A new collection instance is passed in.
// - We need to set up a listener.
// - We need to diff collection.
if (!identical(_collection, collection)) {
_collection = collection;
if (_subscription != null) _subscription.cancel();
_subscription = collection.changes.listen((_) {
_updated = true;
_ref.requestCheck();
});
_updated = false;
return super.diff(collection);
// An update has been registered since the last change detection check.
// - We reset the flag.
// - We diff the collection.
} else if (_updated) {
_updated = false;
return super.diff(collection);
// No updates has been registered.
} else {
return null;
}
}
}
class ObservableListDiffFactory implements IterableDifferFactory {
const ObservableListDiffFactory();
bool supports(obj) => obj is ObservableList;
IterableDiffer create(ChangeDetectorRef cdRef) {
return new ObservableListDiff(cdRef);
}
}

View File

@ -0,0 +1,468 @@
library angular.core.facade.dom;
import 'dart:html';
import 'dom_adapter.dart' show setRootDomAdapter;
import 'generic_browser_adapter.dart' show GenericBrowserDomAdapter;
import '../facade/browser.dart';
import 'dart:js' as js;
// WARNING: Do not expose outside this class. Parsing HTML using this
// sanitizer is a security risk.
class _IdentitySanitizer implements NodeTreeSanitizer {
void sanitizeTree(Node node) {}
}
final _identitySanitizer = new _IdentitySanitizer();
final _keyCodeToKeyMap = const {
8: 'Backspace',
9: 'Tab',
12: 'Clear',
13: 'Enter',
16: 'Shift',
17: 'Control',
18: 'Alt',
19: 'Pause',
20: 'CapsLock',
27: 'Escape',
32: ' ',
33: 'PageUp',
34: 'PageDown',
35: 'End',
36: 'Home',
37: 'ArrowLeft',
38: 'ArrowUp',
39: 'ArrowRight',
40: 'ArrowDown',
45: 'Insert',
46: 'Delete',
65: 'a',
66: 'b',
67: 'c',
68: 'd',
69: 'e',
70: 'f',
71: 'g',
72: 'h',
73: 'i',
74: 'j',
75: 'k',
76: 'l',
77: 'm',
78: 'n',
79: 'o',
80: 'p',
81: 'q',
82: 'r',
83: 's',
84: 't',
85: 'u',
86: 'v',
87: 'w',
88: 'x',
89: 'y',
90: 'z',
91: 'OS',
93: 'ContextMenu',
96: '0',
97: '1',
98: '2',
99: '3',
100: '4',
101: '5',
102: '6',
103: '7',
104: '8',
105: '9',
106: '*',
107: '+',
109: '-',
110: '.',
111: '/',
112: 'F1',
113: 'F2',
114: 'F3',
115: 'F4',
116: 'F5',
117: 'F6',
118: 'F7',
119: 'F8',
120: 'F9',
121: 'F10',
122: 'F11',
123: 'F12',
144: 'NumLock',
145: 'ScrollLock'
};
final bool _supportsTemplateElement = () {
try {
return new TemplateElement().content != null;
} catch(_) {
return false;
}
}();
class BrowserDomAdapter extends GenericBrowserDomAdapter {
js.JsFunction _setProperty;
js.JsFunction _getProperty;
js.JsFunction _hasProperty;
Map<String, bool> _hasPropertyCache;
BrowserDomAdapter() {
_hasPropertyCache = new Map();
_setProperty = js.context.callMethod(
'eval', ['(function(el, prop, value) { el[prop] = value; })']);
_getProperty = js.context
.callMethod('eval', ['(function(el, prop) { return el[prop]; })']);
_hasProperty = js.context
.callMethod('eval', ['(function(el, prop) { return prop in el; })']);
}
static void makeCurrent() {
setRootDomAdapter(new BrowserDomAdapter());
}
bool hasProperty(Element element, String name) {
// Always return true as the serverside version html_adapter.dart does so.
// TODO: change this once we have schema support.
// Note: This nees to kept in sync with html_adapter.dart!
return true;
}
void setProperty(Element element, String name, Object value) {
var cacheKey = "${element.tagName}.${name}";
var hasProperty = this._hasPropertyCache[cacheKey];
if (hasProperty == null) {
hasProperty = this._hasProperty.apply([element, name]);
this._hasPropertyCache[cacheKey] = hasProperty;
}
if (hasProperty) {
_setProperty.apply([element, name, value]);
}
}
getProperty(Element element, String name) =>
_getProperty.apply([element, name]);
invoke(Element element, String methodName, List args) =>
this.getProperty(element, methodName).apply(args, thisArg: element);
// TODO(tbosch): move this into a separate environment class once we have it
logError(error) {
window.console.error(error);
}
log(error) {
window.console.log(error);
}
logGroup(error) {
window.console.group(error);
}
logGroupEnd() {
window.console.groupEnd();
}
@override
Map<String, String> get attrToPropMap => const <String, String>{
'class': 'className',
'innerHtml': 'innerHTML',
'readonly': 'readOnly',
'tabindex': 'tabIndex',
};
Element query(String selector) => document.querySelector(selector);
Element querySelector(el, String selector) => el.querySelector(selector);
ElementList querySelectorAll(el, String selector) =>
el.querySelectorAll(selector);
void on(EventTarget element, String event, callback(arg)) {
// due to https://code.google.com/p/dart/issues/detail?id=17406
// addEventListener misses zones so we use element.on.
element.on[event].listen(callback);
}
Function onAndCancel(EventTarget element, String event, callback(arg)) {
// due to https://code.google.com/p/dart/issues/detail?id=17406
// addEventListener misses zones so we use element.on.
var subscription = element.on[event].listen(callback);
return subscription.cancel;
}
void dispatchEvent(EventTarget el, Event evt) {
el.dispatchEvent(evt);
}
MouseEvent createMouseEvent(String eventType) =>
new MouseEvent(eventType, canBubble: true);
Event createEvent(String eventType) => new Event(eventType, canBubble: true);
void preventDefault(Event evt) {
evt.preventDefault();
}
bool isPrevented(Event evt) {
return evt.defaultPrevented;
}
String getInnerHTML(Element el) => el.innerHtml;
String getOuterHTML(Element el) => el.outerHtml;
void setInnerHTML(Element el, String value) {
el.innerHtml = value;
}
String nodeName(Node el) => el.nodeName;
String nodeValue(Node el) => el.nodeValue;
String type(InputElement el) => el.type;
Node content(TemplateElement el) => _supportsTemplateElement ? el.content : el;
Node firstChild(el) => el.firstChild;
Node nextSibling(Node el) => el.nextNode;
Element parentElement(Node el) => el.parent;
List<Node> childNodes(Node el) => el.childNodes;
List childNodesAsList(Node el) => childNodes(el).toList();
void clearNodes(Node el) {
el.nodes = const [];
}
void appendChild(Node el, Node node) {
el.append(node);
}
void removeChild(el, Node node) {
node.remove();
}
void replaceChild(Node el, Node newNode, Node oldNode) {
oldNode.replaceWith(newNode);
}
ChildNode remove(ChildNode el) {
return el..remove();
}
void insertBefore(Node el, node) {
el.parentNode.insertBefore(node, el);
}
void insertAllBefore(Node el, Iterable<Node> nodes) {
el.parentNode.insertAllBefore(nodes, el);
}
void insertAfter(Node el, Node node) {
el.parentNode.insertBefore(node, el.nextNode);
}
String getText(Node el) => el.text;
void setText(Node el, String value) {
el.text = value;
}
String getValue(el) => el.value;
void setValue(el, String value) {
el.value = value;
}
bool getChecked(InputElement el) => el.checked;
void setChecked(InputElement el, bool isChecked) {
el.checked = isChecked;
}
Comment createComment(String text) {
return new Comment(text);
}
TemplateElement createTemplate(String html) {
var t = new TemplateElement();
// We do not sanitize because templates are part of the application code
// not user code.
t.setInnerHtml(html, treeSanitizer: _identitySanitizer);
return t;
}
Element createElement(String tagName, [HtmlDocument doc = null]) {
if (doc == null) doc = document;
return doc.createElement(tagName);
}
Text createTextNode(String text, [HtmlDocument doc = null]) {
return new Text(text);
}
createScriptTag(String attrName, String attrValue,
[HtmlDocument doc = null]) {
if (doc == null) doc = document;
var el = doc.createElement('SCRIPT');
el.setAttribute(attrName, attrValue);
return el;
}
StyleElement createStyleElement(String css, [HtmlDocument doc = null]) {
if (doc == null) doc = document;
var el = doc.createElement('STYLE');
el.text = css;
return el;
}
ShadowRoot createShadowRoot(Element el) => el.createShadowRoot();
ShadowRoot getShadowRoot(Element el) => el.shadowRoot;
Element getHost(Element el) => (el as ShadowRoot).host;
clone(Node node) => node.clone(true);
List<Node> getElementsByClassName(Element element, String name) =>
element.getElementsByClassName(name);
List<Node> getElementsByTagName(Element element, String name) =>
element.querySelectorAll(name);
List<String> classList(Element element) => element.classes.toList();
void addClass(Element element, String classname) {
element.classes.add(classname);
}
void removeClass(Element element, String classname) {
element.classes.remove(classname);
}
bool hasClass(Element element, String classname) =>
element.classes.contains(classname);
void setStyle(Element element, String stylename, String stylevalue) {
element.style.setProperty(stylename, stylevalue);
}
void removeStyle(Element element, String stylename) {
element.style.removeProperty(stylename);
}
String getStyle(Element element, String stylename) {
return element.style.getPropertyValue(stylename);
}
String tagName(Element element) => element.tagName;
Map<String, String> attributeMap(Element element) {
return new Map.from(element.attributes);
}
bool hasAttribute(Element element, String attribute) =>
element.attributes.containsKey(attribute);
String getAttribute(Element element, String attribute) =>
element.getAttribute(attribute);
void setAttribute(Element element, String name, String value) {
element.setAttribute(name, value);
}
void removeAttribute(Element element, String name) {
//there is no removeAttribute method as of now in Dart:
//https://code.google.com/p/dart/issues/detail?id=19934
element.attributes.remove(name);
}
Node templateAwareRoot(Element el) => el is TemplateElement ? el.content : el;
HtmlDocument createHtmlDocument() =>
document.implementation.createHtmlDocument('fakeTitle');
HtmlDocument defaultDoc() => document;
Rectangle getBoundingClientRect(el) => el.getBoundingClientRect();
String getTitle() => document.title;
void setTitle(String newTitle) {
document.title = newTitle;
}
bool elementMatches(n, String selector) =>
n is Element && n.matches(selector);
bool isTemplateElement(Element el) => el is TemplateElement;
bool isTextNode(Node node) => node.nodeType == Node.TEXT_NODE;
bool isCommentNode(Node node) => node.nodeType == Node.COMMENT_NODE;
bool isElementNode(Node node) => node.nodeType == Node.ELEMENT_NODE;
bool hasShadowRoot(Node node) {
return node is Element && node.shadowRoot != null;
}
bool isShadowRoot(Node node) {
return node is ShadowRoot;
}
Node importIntoDoc(Node node) {
return document.importNode(node, true);
}
Node adoptNode(Node node) {
return document.adoptNode(node);
}
bool isPageRule(CssRule rule) => rule is CssPageRule;
bool isStyleRule(CssRule rule) => rule is CssStyleRule;
bool isMediaRule(CssRule rule) => rule is CssMediaRule;
bool isKeyframesRule(CssRule rule) => rule is CssKeyframesRule;
String getHref(AnchorElement element) {
return element.href;
}
String getEventKey(KeyboardEvent event) {
int keyCode = event.keyCode;
return _keyCodeToKeyMap.containsKey(keyCode)
? _keyCodeToKeyMap[keyCode]
: 'Unidentified';
}
getGlobalEventTarget(String target) {
if (target == "window") {
return window;
} else if (target == "document") {
return document;
} else if (target == "body") {
return document.body;
}
}
getHistory() {
return window.history;
}
getLocation() {
return window.location;
}
getBaseHref() {
var href = getBaseElementHref();
if (href == null) {
return null;
}
var baseUri = Uri.parse(href);
return baseUri.path[0] == '/' ? baseUri.path : ('/' + baseUri.path);
}
resetBaseElement() {
baseElement = null;
}
String getUserAgent() {
return window.navigator.userAgent;
}
void setData(Element element, String name, String value) {
element.dataset[name] = value;
}
String getData(Element element, String name) {
return element.dataset[name];
}
// TODO(tbosch): move this into a separate environment class once we have it
setGlobalVar(String name, value) {
js.context[name] = value;
}
}
var baseElement = null;
String getBaseElementHref() {
if (baseElement == null) {
baseElement = document.querySelector('base');
if (baseElement == null) {
return null;
}
}
return baseElement.getAttribute('href');
}

View File

@ -0,0 +1,340 @@
import {List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent, global} from 'angular2/src/facade/lang';
import {setRootDomAdapter} from './dom_adapter';
import {GenericBrowserDomAdapter} from './generic_browser_adapter';
var _attrToPropMap = {
'class': 'className',
'innerHtml': 'innerHTML',
'readonly': 'readOnly',
'tabindex': 'tabIndex'
};
const DOM_KEY_LOCATION_NUMPAD = 3;
// Map to convert some key or keyIdentifier values to what will be returned by getEventKey
var _keyMap = {
// The following values are here for cross-browser compatibility and to match the W3C standard
// cf http://www.w3.org/TR/DOM-Level-3-Events-key/
'\b': 'Backspace',
'\t': 'Tab',
'\x7F': 'Delete',
'\x1B': 'Escape',
'Del': 'Delete',
'Esc': 'Escape',
'Left': 'ArrowLeft',
'Right': 'ArrowRight',
'Up': 'ArrowUp',
'Down': 'ArrowDown',
'Menu': 'ContextMenu',
'Scroll': 'ScrollLock',
'Win': 'OS'
};
// There is a bug in Chrome for numeric keypad keys:
// https://code.google.com/p/chromium/issues/detail?id=155654
// 1, 2, 3 ... are reported as A, B, C ...
var _chromeNumKeyPadMap = {
'A': '1',
'B': '2',
'C': '3',
'D': '4',
'E': '5',
'F': '6',
'G': '7',
'H': '8',
'I': '9',
'J': '*',
'K': '+',
'M': '-',
'N': '.',
'O': '/',
'\x60': '0',
'\x90': 'NumLock'
};
/* tslint:disable:requireParameterType */
export class BrowserDomAdapter extends GenericBrowserDomAdapter {
static makeCurrent() { setRootDomAdapter(new BrowserDomAdapter()); }
hasProperty(element, name: string): boolean { return name in element; }
setProperty(el: /*element*/ any, name: string, value: any) { el[name] = value; }
getProperty(el: /*element*/ any, name: string): any { return el[name]; }
invoke(el: /*element*/ any, methodName: string, args: List<any>): any {
el[methodName].apply(el, args);
}
// TODO(tbosch): move this into a separate environment class once we have it
logError(error) { window.console.error(error); }
log(error) { window.console.log(error); }
logGroup(error) {
if (window.console.group) {
window.console.group(error);
} else {
window.console.log(error);
}
}
logGroupEnd() {
if (window.console.groupEnd) {
window.console.groupEnd();
}
}
get attrToPropMap(): any { return _attrToPropMap; }
query(selector: string): any { return document.querySelector(selector); }
querySelector(el, selector: string): HTMLElement { return el.querySelector(selector); }
querySelectorAll(el, selector: string): List<any> { return el.querySelectorAll(selector); }
on(el, evt, listener) { el.addEventListener(evt, listener, false); }
onAndCancel(el, evt, listener): Function {
el.addEventListener(evt, listener, false);
// Needed to follow Dart's subscription semantic, until fix of
// https://code.google.com/p/dart/issues/detail?id=17406
return () => { el.removeEventListener(evt, listener, false); };
}
dispatchEvent(el, evt) { el.dispatchEvent(evt); }
createMouseEvent(eventType: string): MouseEvent {
var evt: MouseEvent = document.createEvent('MouseEvent');
evt.initEvent(eventType, true, true);
return evt;
}
createEvent(eventType): Event {
var evt: Event = document.createEvent('Event');
evt.initEvent(eventType, true, true);
return evt;
}
preventDefault(evt: Event) {
evt.preventDefault();
evt.returnValue = false;
}
isPrevented(evt: Event): boolean {
return evt.defaultPrevented || isPresent(evt.returnValue) && !evt.returnValue;
}
getInnerHTML(el): string { return el.innerHTML; }
getOuterHTML(el): string { return el.outerHTML; }
nodeName(node: Node): string { return node.nodeName; }
nodeValue(node: Node): string { return node.nodeValue; }
type(node: HTMLInputElement): string { return node.type; }
content(node: Node): Node {
if (this.hasProperty(node, "content")) {
return (<any>node).content;
} else {
return node;
}
}
firstChild(el): Node { return el.firstChild; }
nextSibling(el): Node { return el.nextSibling; }
parentElement(el): Node { return el.parentNode; }
childNodes(el): List<Node> { return el.childNodes; }
childNodesAsList(el): List<any> {
var childNodes = el.childNodes;
var res = ListWrapper.createFixedSize(childNodes.length);
for (var i = 0; i < childNodes.length; i++) {
res[i] = childNodes[i];
}
return res;
}
clearNodes(el) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
}
appendChild(el, node) { el.appendChild(node); }
removeChild(el, node) { el.removeChild(node); }
replaceChild(el: Node, newChild, oldChild) { el.replaceChild(newChild, oldChild); }
remove(node): Node {
node.parentNode.removeChild(node);
return node;
}
insertBefore(el, node) { el.parentNode.insertBefore(node, el); }
insertAllBefore(el, nodes) {
ListWrapper.forEach(nodes, (n) => { el.parentNode.insertBefore(n, el); });
}
insertAfter(el, node) { el.parentNode.insertBefore(node, el.nextSibling); }
setInnerHTML(el, value) { el.innerHTML = value; }
getText(el): string { return el.textContent; }
// TODO(vicb): removed Element type because it does not support StyleElement
setText(el, value: string) { el.textContent = value; }
getValue(el): string { return el.value; }
setValue(el, value: string) { el.value = value; }
getChecked(el): boolean { return el.checked; }
setChecked(el, value: boolean) { el.checked = value; }
createComment(text: string): Comment { return document.createComment(text); }
createTemplate(html): HTMLElement {
var t = document.createElement('template');
t.innerHTML = html;
return t;
}
createElement(tagName, doc = document): HTMLElement { return doc.createElement(tagName); }
createTextNode(text: string, doc = document): Text { return doc.createTextNode(text); }
createScriptTag(attrName: string, attrValue: string, doc = document): HTMLScriptElement {
var el = <HTMLScriptElement>doc.createElement('SCRIPT');
el.setAttribute(attrName, attrValue);
return el;
}
createStyleElement(css: string, doc = document): HTMLStyleElement {
var style = <HTMLStyleElement>doc.createElement('style');
this.appendChild(style, this.createTextNode(css));
return style;
}
createShadowRoot(el: HTMLElement): DocumentFragment { return (<any>el).createShadowRoot(); }
getShadowRoot(el: HTMLElement): DocumentFragment { return (<any>el).shadowRoot; }
getHost(el: HTMLElement): HTMLElement { return (<any>el).host; }
clone(node: Node): Node { return node.cloneNode(true); }
getElementsByClassName(element, name: string): List<HTMLElement> {
return element.getElementsByClassName(name);
}
getElementsByTagName(element, name: string): List<HTMLElement> {
return element.getElementsByTagName(name);
}
classList(element): List<any> {
return <List<any>>Array.prototype.slice.call(element.classList, 0);
}
addClass(element, classname: string) { element.classList.add(classname); }
removeClass(element, classname: string) { element.classList.remove(classname); }
hasClass(element, classname: string): boolean { return element.classList.contains(classname); }
setStyle(element, stylename: string, stylevalue: string) {
element.style[stylename] = stylevalue;
}
removeStyle(element, stylename: string) { element.style[stylename] = null; }
getStyle(element, stylename: string): string { return element.style[stylename]; }
tagName(element): string { return element.tagName; }
attributeMap(element): Map<string, string> {
var res = new Map();
var elAttrs = element.attributes;
for (var i = 0; i < elAttrs.length; i++) {
var attrib = elAttrs[i];
res.set(attrib.name, attrib.value);
}
return res;
}
hasAttribute(element, attribute: string): boolean { return element.hasAttribute(attribute); }
getAttribute(element, attribute: string): string { return element.getAttribute(attribute); }
setAttribute(element, name: string, value: string) { element.setAttribute(name, value); }
removeAttribute(element, attribute: string) { element.removeAttribute(attribute); }
templateAwareRoot(el): any { return this.isTemplateElement(el) ? this.content(el) : el; }
createHtmlDocument(): HTMLDocument {
return document.implementation.createHTMLDocument('fakeTitle');
}
defaultDoc(): HTMLDocument { return document; }
getBoundingClientRect(el): any {
try {
return el.getBoundingClientRect();
} catch (e) {
return {top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0};
}
}
getTitle(): string { return document.title; }
setTitle(newTitle: string) { document.title = newTitle || ''; }
elementMatches(n, selector: string): boolean {
var matches = false;
if (n instanceof HTMLElement) {
if (n.matches) {
matches = n.matches(selector);
} else if (n.msMatchesSelector) {
matches = n.msMatchesSelector(selector);
} else if (n.webkitMatchesSelector) {
matches = n.webkitMatchesSelector(selector);
}
}
return matches;
}
isTemplateElement(el: any): boolean {
return el instanceof HTMLElement && el.nodeName == "TEMPLATE";
}
isTextNode(node: Node): boolean { return node.nodeType === Node.TEXT_NODE; }
isCommentNode(node: Node): boolean { return node.nodeType === Node.COMMENT_NODE; }
isElementNode(node: Node): boolean { return node.nodeType === Node.ELEMENT_NODE; }
hasShadowRoot(node): boolean { return node instanceof HTMLElement && isPresent(node.shadowRoot); }
isShadowRoot(node): boolean { return node instanceof DocumentFragment; }
importIntoDoc(node: Node): any {
var toImport = node;
if (this.isTemplateElement(node)) {
toImport = this.content(node);
}
return document.importNode(toImport, true);
}
adoptNode(node: Node): any { return document.adoptNode(node); }
isPageRule(rule): boolean { return rule.type === CSSRule.PAGE_RULE; }
isStyleRule(rule): boolean { return rule.type === CSSRule.STYLE_RULE; }
isMediaRule(rule): boolean { return rule.type === CSSRule.MEDIA_RULE; }
isKeyframesRule(rule): boolean { return rule.type === CSSRule.KEYFRAMES_RULE; }
getHref(el: Element): string { return (<any>el).href; }
getEventKey(event): string {
var key = event.key;
if (isBlank(key)) {
key = event.keyIdentifier;
// keyIdentifier is defined in the old draft of DOM Level 3 Events implemented by Chrome and
// Safari
// cf
// http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/events.html#Events-KeyboardEvents-Interfaces
if (isBlank(key)) {
return 'Unidentified';
}
if (key.startsWith('U+')) {
key = String.fromCharCode(parseInt(key.substring(2), 16));
if (event.location === DOM_KEY_LOCATION_NUMPAD && _chromeNumKeyPadMap.hasOwnProperty(key)) {
// There is a bug in Chrome for numeric keypad keys:
// https://code.google.com/p/chromium/issues/detail?id=155654
// 1, 2, 3 ... are reported as A, B, C ...
key = _chromeNumKeyPadMap[key];
}
}
}
if (_keyMap.hasOwnProperty(key)) {
key = _keyMap[key];
}
return key;
}
getGlobalEventTarget(target: string): EventTarget {
if (target == "window") {
return window;
} else if (target == "document") {
return document;
} else if (target == "body") {
return document.body;
}
}
getHistory(): History { return window.history; }
getLocation(): Location { return window.location; }
getBaseHref(): string {
var href = getBaseElementHref();
if (isBlank(href)) {
return null;
}
return relativePath(href);
}
resetBaseElement(): void { baseElement = null; }
getUserAgent(): string { return window.navigator.userAgent; }
setData(element, name: string, value: string) {
this.setAttribute(element, 'data-' + name, value);
}
getData(element, name: string): string { return this.getAttribute(element, 'data-' + name); }
// TODO(tbosch): move this into a separate environment class once we have it
setGlobalVar(name: string, value: any) { global[name] = value; }
}
var baseElement = null;
function getBaseElementHref(): string {
if (isBlank(baseElement)) {
baseElement = document.querySelector('base');
if (isBlank(baseElement)) {
return null;
}
}
return baseElement.getAttribute('href');
}
// based on urlUtils.js in AngularJS 1
var urlParsingNode = null;
function relativePath(url): string {
if (isBlank(urlParsingNode)) {
urlParsingNode = document.createElement("a");
}
urlParsingNode.setAttribute('href', url);
return (urlParsingNode.pathname.charAt(0) === '/') ? urlParsingNode.pathname :
'/' + urlParsingNode.pathname;
}

View File

@ -0,0 +1,135 @@
import {BaseException, isBlank} from 'angular2/src/facade/lang';
export var DOM: DomAdapter;
export function setRootDomAdapter(adapter: DomAdapter) {
if (isBlank(DOM)) {
DOM = adapter;
}
}
function _abstract() {
return new BaseException('This method is abstract');
}
/* tslint:disable:requireParameterType */
/**
* Provides DOM operations in an environment-agnostic way.
*/
export class DomAdapter {
hasProperty(element, name: string): boolean { throw _abstract(); }
setProperty(el: Element, name: string, value: any) { throw _abstract(); }
getProperty(el: Element, name: string): any { throw _abstract(); }
invoke(el: Element, methodName: string, args: List<any>): any { throw _abstract(); }
logError(error) { throw _abstract(); }
log(error) { throw _abstract(); }
logGroup(error) { throw _abstract(); }
logGroupEnd() { throw _abstract(); }
/**
* Maps attribute names to their corresponding property names for cases
* where attribute name doesn't match property name.
*/
get attrToPropMap(): StringMap<string, string> { throw _abstract(); }
parse(templateHtml: string) { throw _abstract(); }
query(selector: string): any { throw _abstract(); }
querySelector(el, selector: string): HTMLElement { throw _abstract(); }
querySelectorAll(el, selector: string): List<any> { throw _abstract(); }
on(el, evt, listener) { throw _abstract(); }
onAndCancel(el, evt, listener): Function { throw _abstract(); }
dispatchEvent(el, evt) { throw _abstract(); }
createMouseEvent(eventType): any { throw _abstract(); }
createEvent(eventType: string): any { throw _abstract(); }
preventDefault(evt) { throw _abstract(); }
isPrevented(evt): boolean { throw _abstract(); }
getInnerHTML(el): string { throw _abstract(); }
getOuterHTML(el): string { throw _abstract(); }
nodeName(node): string { throw _abstract(); }
nodeValue(node): string { throw _abstract(); }
type(node): string { throw _abstract(); }
content(node): any { throw _abstract(); }
firstChild(el): Node { throw _abstract(); }
nextSibling(el): Node { throw _abstract(); }
parentElement(el): Node { throw _abstract(); }
childNodes(el): List<Node> { throw _abstract(); }
childNodesAsList(el): List<Node> { throw _abstract(); }
clearNodes(el) { throw _abstract(); }
appendChild(el, node) { throw _abstract(); }
removeChild(el, node) { throw _abstract(); }
replaceChild(el, newNode, oldNode) { throw _abstract(); }
remove(el): Node { throw _abstract(); }
insertBefore(el, node) { throw _abstract(); }
insertAllBefore(el, nodes) { throw _abstract(); }
insertAfter(el, node) { throw _abstract(); }
setInnerHTML(el, value) { throw _abstract(); }
getText(el): string { throw _abstract(); }
setText(el, value: string) { throw _abstract(); }
getValue(el): string { throw _abstract(); }
setValue(el, value: string) { throw _abstract(); }
getChecked(el): boolean { throw _abstract(); }
setChecked(el, value: boolean) { throw _abstract(); }
createComment(text: string): any { throw _abstract(); }
createTemplate(html): HTMLElement { throw _abstract(); }
createElement(tagName, doc = null): HTMLElement { throw _abstract(); }
createTextNode(text: string, doc = null): Text { throw _abstract(); }
createScriptTag(attrName: string, attrValue: string, doc = null): HTMLElement {
throw _abstract();
}
createStyleElement(css: string, doc = null): HTMLStyleElement { throw _abstract(); }
createShadowRoot(el): any { throw _abstract(); }
getShadowRoot(el): any { throw _abstract(); }
getHost(el): any { throw _abstract(); }
getDistributedNodes(el): List<Node> { throw _abstract(); }
clone /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/ { throw _abstract(); }
getElementsByClassName(element, name: string): List<HTMLElement> { throw _abstract(); }
getElementsByTagName(element, name: string): List<HTMLElement> { throw _abstract(); }
classList(element): List<any> { throw _abstract(); }
addClass(element, classname: string) { throw _abstract(); }
removeClass(element, classname: string) { throw _abstract(); }
hasClass(element, classname: string): boolean { throw _abstract(); }
setStyle(element, stylename: string, stylevalue: string) { throw _abstract(); }
removeStyle(element, stylename: string) { throw _abstract(); }
getStyle(element, stylename: string): string { throw _abstract(); }
tagName(element): string { throw _abstract(); }
attributeMap(element): Map<string, string> { throw _abstract(); }
hasAttribute(element, attribute: string): boolean { throw _abstract(); }
getAttribute(element, attribute: string): string { throw _abstract(); }
setAttribute(element, name: string, value: string) { throw _abstract(); }
removeAttribute(element, attribute: string) { throw _abstract(); }
templateAwareRoot(el) { throw _abstract(); }
createHtmlDocument(): HTMLDocument { throw _abstract(); }
defaultDoc(): HTMLDocument { throw _abstract(); }
getBoundingClientRect(el) { throw _abstract(); }
getTitle(): string { throw _abstract(); }
setTitle(newTitle: string) { throw _abstract(); }
elementMatches(n, selector: string): boolean { throw _abstract(); }
isTemplateElement(el: any): boolean { throw _abstract(); }
isTextNode(node): boolean { throw _abstract(); }
isCommentNode(node): boolean { throw _abstract(); }
isElementNode(node): boolean { throw _abstract(); }
hasShadowRoot(node): boolean { throw _abstract(); }
isShadowRoot(node): boolean { throw _abstract(); }
importIntoDoc /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/ { throw _abstract(); }
adoptNode /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/ { throw _abstract(); }
isPageRule(rule): boolean { throw _abstract(); }
isStyleRule(rule): boolean { throw _abstract(); }
isMediaRule(rule): boolean { throw _abstract(); }
isKeyframesRule(rule): boolean { throw _abstract(); }
getHref(element): string { throw _abstract(); }
getEventKey(event): string { throw _abstract(); }
resolveAndSetHref(element, baseUrl: string, href: string) { throw _abstract(); }
cssToRules(css: string): List<any> { throw _abstract(); }
supportsDOMEvents(): boolean { throw _abstract(); }
supportsNativeShadowDOM(): boolean { throw _abstract(); }
getGlobalEventTarget(target: string): any { throw _abstract(); }
getHistory(): History { throw _abstract(); }
getLocation(): Location { throw _abstract(); }
getBaseHref(): string { throw _abstract(); }
resetBaseElement(): void { throw _abstract(); }
getUserAgent(): string { throw _abstract(); }
setData(element, name: string, value: string) { throw _abstract(); }
getData(element, name: string): string { throw _abstract(); }
setGlobalVar(name: string, value: any) { throw _abstract(); }
}

View File

@ -0,0 +1,40 @@
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isPresent, isFunction} from 'angular2/src/facade/lang';
import {DomAdapter} from './dom_adapter';
/**
* Provides DOM operations in any browser environment.
*/
export class GenericBrowserDomAdapter extends DomAdapter {
getDistributedNodes(el: HTMLElement): List<Node> { return (<any>el).getDistributedNodes(); }
resolveAndSetHref(el: HTMLAnchorElement, baseUrl: string, href: string) {
el.href = href == null ? baseUrl : baseUrl + '/../' + href;
}
cssToRules(css: string): List<any> {
var style = this.createStyleElement(css);
this.appendChild(this.defaultDoc().head, style);
var rules = [];
if (isPresent(style.sheet)) {
// TODO(sorvell): Firefox throws when accessing the rules of a stylesheet
// with an @import
// https://bugzilla.mozilla.org/show_bug.cgi?id=625013
try {
var rawRules = (<any>style.sheet).cssRules;
rules = ListWrapper.createFixedSize(rawRules.length);
for (var i = 0; i < rawRules.length; i++) {
rules[i] = rawRules[i];
}
} catch (e) {
//
}
} else {
// console.warn('sheet not found', style);
}
this.remove(style);
return rules;
}
supportsDOMEvents(): boolean { return true; }
supportsNativeShadowDOM(): boolean {
return isFunction((<any>this.defaultDoc().body).createShadowRoot);
}
}

View File

@ -0,0 +1,423 @@
library angular2.dom.htmlAdapter;
import 'dom_adapter.dart';
import 'package:html/parser.dart' as parser;
import 'package:html/dom.dart';
import 'dart:io';
class Html5LibDomAdapter implements DomAdapter {
static void makeCurrent() {
setRootDomAdapter(new Html5LibDomAdapter());
}
hasProperty(element, String name) {
// This is needed for serverside compile to generate the right getters/setters.
// TODO: change this once we have property schema support.
// Attention: Keep this in sync with browser_adapter.dart!
return true;
}
void setProperty(Element element, String name, Object value) =>
throw 'not implemented';
getProperty(Element element, String name) => throw 'not implemented';
invoke(Element element, String methodName, List args) =>
throw 'not implemented';
logError(error) {
stderr.writeln('${error}');
}
log(error) {
stdout.writeln('${error}');
}
logGroup(error) {
stdout.writeln('${error}');
}
logGroupEnd() {}
@override
final attrToPropMap = const {
'innerHtml': 'innerHTML',
'readonly': 'readOnly',
'tabindex': 'tabIndex',
};
@override
getGlobalEventTarget(String target) {
throw 'not implemented';
}
@override
getTitle() {
throw 'not implemented';
}
@override
setTitle(String newTitle) {
throw 'not implemented';
}
@override
String getEventKey(event) {
throw 'not implemented';
}
@override
void replaceChild(el, newNode, oldNode) {
throw 'not implemented';
}
@override
dynamic getBoundingClientRect(el) {
throw 'not implemented';
}
Element parse(String templateHtml) => parser.parse(templateHtml).firstChild;
query(selector) {
throw 'not implemented';
}
querySelector(el, String selector) {
return el.querySelector(selector);
}
List querySelectorAll(el, String selector) {
return el.querySelectorAll(selector);
}
on(el, evt, listener) {
throw 'not implemented';
}
Function onAndCancel(el, evt, listener) {
throw 'not implemented';
}
dispatchEvent(el, evt) {
throw 'not implemented';
}
createMouseEvent(eventType) {
throw 'not implemented';
}
createEvent(eventType) {
throw 'not implemented';
}
preventDefault(evt) {
throw 'not implemented';
}
isPrevented(evt) {
throw 'not implemented';
}
getInnerHTML(el) {
return el.innerHtml;
}
getOuterHTML(el) {
return el.outerHtml;
}
String nodeName(node) {
switch (node.nodeType) {
case Node.ELEMENT_NODE:
return (node as Element).localName;
case Node.TEXT_NODE:
return '#text';
default:
throw 'not implemented for type ${node.nodeType}. '
'See http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1950641247'
' for node types definitions.';
}
}
String nodeValue(node) => node.data;
String type(node) {
throw 'not implemented';
}
content(node) {
return node;
}
firstChild(el) => el is NodeList ? el.first : el.firstChild;
nextSibling(el) {
final parentNode = el.parentNode;
if (parentNode == null) return null;
final siblings = parentNode.nodes;
final index = siblings.indexOf(el);
if (index < siblings.length - 1) {
return siblings[index + 1];
}
return null;
}
parentElement(el) {
return el.parent;
}
List childNodes(el) => el.nodes;
List childNodesAsList(el) => el.nodes;
clearNodes(el) {
el.nodes.forEach((e) => e.remove());
}
appendChild(el, node) => el.append(node.remove());
removeChild(el, node) {
throw 'not implemented';
}
remove(el) => el.remove();
insertBefore(el, node) {
if (el.parent == null) throw '$el must have a parent';
el.parent.insertBefore(node, el);
}
insertAllBefore(el, nodes) {
throw 'not implemented';
}
insertAfter(el, node) {
throw 'not implemented';
}
setInnerHTML(el, value) {
el.innerHtml = value;
}
getText(el) {
return el.text;
}
setText(el, String value) => el.text = value;
getValue(el) {
throw 'not implemented';
}
setValue(el, String value) {
throw 'not implemented';
}
getChecked(el) {
throw 'not implemented';
}
setChecked(el, bool value) {
throw 'not implemented';
}
createComment(String text) => new Comment(text);
createTemplate(String html) => createElement('template')..innerHtml = html;
createElement(tagName, [doc]) {
return new Element.tag(tagName);
}
createTextNode(String text, [doc]) {
throw 'not implemented';
}
createScriptTag(String attrName, String attrValue, [doc]) {
throw 'not implemented';
}
createStyleElement(String css, [doc]) {
throw 'not implemented';
}
createShadowRoot(el) {
throw 'not implemented';
}
getShadowRoot(el) {
throw 'not implemented';
}
getHost(el) {
throw 'not implemented';
}
clone(node) => node.clone(true);
getElementsByClassName(element, String name) {
throw 'not implemented';
}
getElementsByTagName(element, String name) {
throw 'not implemented';
}
List classList(element) => element.classes.toList();
addClass(element, String classname) {
element.classes.add(classname);
}
removeClass(element, String classname) {
throw 'not implemented';
}
hasClass(element, String classname) => element.classes.contains(classname);
setStyle(element, String stylename, String stylevalue) {
throw 'not implemented';
}
removeStyle(element, String stylename) {
throw 'not implemented';
}
getStyle(element, String stylename) {
throw 'not implemented';
}
String tagName(element) => element.localName;
attributeMap(element) {
// `attributes` keys can be {@link AttributeName}s.
var map = <String, String>{};
element.attributes.forEach((key, value) {
map['$key'] = value;
});
return map;
}
hasAttribute(element, String attribute) {
// `attributes` keys can be {@link AttributeName}s.
return element.attributes.keys.any((key) => '$key' == attribute);
}
getAttribute(element, String attribute) {
// `attributes` keys can be {@link AttributeName}s.
var key = element.attributes.keys.firstWhere((key) => '$key' == attribute,
orElse: () {});
return element.attributes[key];
}
setAttribute(element, String name, String value) {
element.attributes[name] = value;
}
removeAttribute(element, String attribute) {
element.attributes.remove(attribute);
}
templateAwareRoot(el) => el;
createHtmlDocument() {
throw 'not implemented';
}
defaultDoc() {
throw 'not implemented';
}
bool elementMatches(n, String selector) {
throw 'not implemented';
}
bool isTemplateElement(Element el) {
return el != null && el.localName.toLowerCase() == 'template';
}
bool isTextNode(node) => node.nodeType == Node.TEXT_NODE;
bool isCommentNode(node) => node.nodeType == Node.COMMENT_NODE;
bool isElementNode(node) => node.nodeType == Node.ELEMENT_NODE;
bool hasShadowRoot(node) {
throw 'not implemented';
}
bool isShadowRoot(node) {
throw 'not implemented';
}
importIntoDoc(node) {
throw 'not implemented';
}
adoptNode(node) {
throw 'not implemented';
}
bool isPageRule(rule) {
throw 'not implemented';
}
bool isStyleRule(rule) {
throw 'not implemented';
}
bool isMediaRule(rule) {
throw 'not implemented';
}
bool isKeyframesRule(rule) {
throw 'not implemented';
}
String getHref(element) {
throw 'not implemented';
}
void resolveAndSetHref(element, baseUrl, href) {
throw 'not implemented';
}
List cssToRules(String css) {
throw 'not implemented';
}
List getDistributedNodes(Node) {
throw 'not implemented';
}
bool supportsDOMEvents() {
return false;
}
bool supportsNativeShadowDOM() {
return false;
}
getHistory() {
throw 'not implemented';
}
getLocation() {
throw 'not implemented';
}
getBaseHref() {
throw 'not implemented';
}
resetBaseElement() {
throw 'not implemented';
}
String getUserAgent() {
throw 'not implemented';
}
void setData(Element element, String name, String value) {
this.setAttribute(element, 'data-${name}', value);
}
String getData(Element element, String name) {
return this.getAttribute(element, 'data-${name}');
}
// TODO(tbosch): move this into a separate environment class once we have it
setGlobalVar(String name, value) {
// noop on the server
}
}

View File

@ -0,0 +1,3 @@
library angular2.src.dom.parse5_adapter;
// no dart implementation

View File

@ -0,0 +1,730 @@
var parse5 = require('parse5');
var parser = new parse5.Parser(parse5.TreeAdapters.htmlparser2);
var serializer = new parse5.Serializer(parse5.TreeAdapters.htmlparser2);
var treeAdapter = parser.treeAdapter;
var cssParse = require('css').parse;
var url = require('url');
import {List, MapWrapper, ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {DomAdapter, setRootDomAdapter} from './dom_adapter';
import {BaseException, isPresent, isBlank, global} from 'angular2/src/facade/lang';
import {SelectorMatcher, CssSelector} from 'angular2/src/render/dom/compiler/selector';
var _attrToPropMap = {
'class': 'className',
'innerHtml': 'innerHTML',
'readonly': 'readOnly',
'tabindex': 'tabIndex',
};
var defDoc = null;
var mapProps = ['attribs', 'x-attribsNamespace', 'x-attribsPrefix'];
function _notImplemented(methodName) {
return new BaseException('This method is not implemented in Parse5DomAdapter: ' + methodName);
}
/* tslint:disable:requireParameterType */
export class Parse5DomAdapter extends DomAdapter {
static makeCurrent() { setRootDomAdapter(new Parse5DomAdapter()); }
hasProperty(element, name: string): boolean {
return _HTMLElementPropertyList.indexOf(name) > -1;
}
// TODO(tbosch): don't even call this method when we run the tests on server side
// by not using the DomRenderer in tests. Keeping this for now to make tests happy...
setProperty(el: /*element*/ any, name: string, value: any) {
if (name === 'innerHTML') {
this.setInnerHTML(el, value);
} else if (name === 'className') {
el.attribs["class"] = el.className = value;
} else {
el[name] = value;
}
}
// TODO(tbosch): don't even call this method when we run the tests on server side
// by not using the DomRenderer in tests. Keeping this for now to make tests happy...
getProperty(el: /*element*/ any, name: string): any { return el[name]; }
logError(error) { console.error(error); }
log(error) { console.log(error); }
logGroup(error) { console.log(error); }
logGroupEnd() {}
get attrToPropMap() { return _attrToPropMap; }
query(selector) { throw _notImplemented('query'); }
querySelector(el, selector: string): any { return this.querySelectorAll(el, selector)[0]; }
querySelectorAll(el, selector: string): List<any> {
var res = [];
var _recursive = (result, node, selector, matcher) => {
var cNodes = node.childNodes;
if (cNodes && cNodes.length > 0) {
for (var i = 0; i < cNodes.length; i++) {
var childNode = cNodes[i];
if (this.elementMatches(childNode, selector, matcher)) {
result.push(childNode);
}
_recursive(result, childNode, selector, matcher);
}
}
};
var matcher = new SelectorMatcher();
matcher.addSelectables(CssSelector.parse(selector));
_recursive(res, el, selector, matcher);
return res;
}
elementMatches(node, selector: string, matcher = null): boolean {
if (this.isElementNode(node) && selector === '*') {
return true;
}
var result = false;
if (selector && selector.charAt(0) == "#") {
result = this.getAttribute(node, 'id') == selector.substring(1);
} else if (selector) {
var result = false;
if (matcher == null) {
matcher = new SelectorMatcher();
matcher.addSelectables(CssSelector.parse(selector));
}
var cssSelector = new CssSelector();
cssSelector.setElement(this.tagName(node));
if (node.attribs) {
for (var attrName in node.attribs) {
cssSelector.addAttribute(attrName, node.attribs[attrName]);
}
}
var classList = this.classList(node);
for (var i = 0; i < classList.length; i++) {
cssSelector.addClassName(classList[i]);
}
matcher.match(cssSelector, function(selector, cb) { result = true; });
}
return result;
}
on(el, evt, listener) {
var listenersMap: StringMap<any, any> = el._eventListenersMap;
if (isBlank(listenersMap)) {
var listenersMap: StringMap<any, any> = StringMapWrapper.create();
el._eventListenersMap = listenersMap;
}
var listeners = StringMapWrapper.get(listenersMap, evt);
if (isBlank(listeners)) {
listeners = [];
}
listeners.push(listener);
StringMapWrapper.set(listenersMap, evt, listeners);
}
onAndCancel(el, evt, listener): Function {
this.on(el, evt, listener);
return () => {
ListWrapper.remove(StringMapWrapper.get<List<any>>(el._eventListenersMap, evt), listener);
};
}
dispatchEvent(el, evt) {
if (isBlank(evt.target)) {
evt.target = el;
}
if (isPresent(el._eventListenersMap)) {
var listeners: any = StringMapWrapper.get(el._eventListenersMap, evt.type);
if (isPresent(listeners)) {
for (var i = 0; i < listeners.length; i++) {
listeners[i](evt);
}
}
}
if (isPresent(el.parent)) {
this.dispatchEvent(el.parent, evt);
}
if (isPresent(el._window)) {
this.dispatchEvent(el._window, evt);
}
}
createMouseEvent(eventType): Event { return this.createEvent(eventType); }
createEvent(eventType: string): Event {
var evt = <Event>{
type: eventType,
defaultPrevented: false,
preventDefault: () => { evt.defaultPrevented = true; }
};
return evt;
}
preventDefault(evt) { evt.returnValue = false; }
isPrevented(evt): boolean { return isPresent(evt.returnValue) && !evt.returnValue; }
getInnerHTML(el): string { return serializer.serialize(this.templateAwareRoot(el)); }
getOuterHTML(el): string {
serializer.html = '';
serializer._serializeElement(el);
return serializer.html;
}
nodeName(node): string { return node.tagName; }
nodeValue(node): string { return node.nodeValue; }
type(node: any): string { throw _notImplemented('type'); }
content(node): string { return node.childNodes[0]; }
firstChild(el): Node { return el.firstChild; }
nextSibling(el): Node { return el.nextSibling; }
parentElement(el): Node { return el.parent; }
childNodes(el): Node[] { return el.childNodes; }
childNodesAsList(el): List<any> {
var childNodes = el.childNodes;
var res = ListWrapper.createFixedSize(childNodes.length);
for (var i = 0; i < childNodes.length; i++) {
res[i] = childNodes[i];
}
return res;
}
clearNodes(el) {
while (el.childNodes.length > 0) {
this.remove(el.childNodes[0]);
}
}
appendChild(el, node) {
this.remove(node);
treeAdapter.appendChild(this.templateAwareRoot(el), node);
}
removeChild(el, node) {
if (ListWrapper.contains(el.childNodes, node)) {
this.remove(node);
}
}
remove(el): HTMLElement {
var parent = el.parent;
if (parent) {
var index = parent.childNodes.indexOf(el);
parent.childNodes.splice(index, 1);
}
var prev = el.previousSibling;
var next = el.nextSibling;
if (prev) {
prev.next = next;
}
if (next) {
next.prev = prev;
}
el.prev = null;
el.next = null;
el.parent = null;
return el;
}
insertBefore(el, node) {
this.remove(node);
treeAdapter.insertBefore(el.parent, node, el);
}
insertAllBefore(el, nodes) {
ListWrapper.forEach(nodes, (n) => { this.insertBefore(el, n); });
}
insertAfter(el, node) {
if (el.nextSibling) {
this.insertBefore(el.nextSibling, node);
} else {
this.appendChild(el.parent, node);
}
}
setInnerHTML(el, value) {
this.clearNodes(el);
var content = parser.parseFragment(value);
for (var i = 0; i < content.childNodes.length; i++) {
treeAdapter.appendChild(el, content.childNodes[i]);
}
}
getText(el): string {
if (this.isTextNode(el)) {
return el.data;
} else if (isBlank(el.childNodes) || el.childNodes.length == 0) {
return "";
} else {
var textContent = "";
for (var i = 0; i < el.childNodes.length; i++) {
textContent += this.getText(el.childNodes[i]);
}
return textContent;
}
}
setText(el, value: string) {
if (this.isTextNode(el)) {
el.data = value;
} else {
this.clearNodes(el);
if (value !== '') treeAdapter.insertText(el, value);
}
}
getValue(el): string { return el.value; }
setValue(el, value: string) { el.value = value; }
getChecked(el): boolean { return el.checked; }
setChecked(el, value: boolean) { el.checked = value; }
createComment(text: string): Comment { return treeAdapter.createCommentNode(text); }
createTemplate(html): HTMLElement {
var template = treeAdapter.createElement("template", 'http://www.w3.org/1999/xhtml', []);
var content = parser.parseFragment(html);
treeAdapter.appendChild(template, content);
return template;
}
createElement(tagName): HTMLElement {
return treeAdapter.createElement(tagName, 'http://www.w3.org/1999/xhtml', []);
}
createTextNode(text: string): Text { throw _notImplemented('createTextNode'); }
createScriptTag(attrName: string, attrValue: string): HTMLElement {
return treeAdapter.createElement("script", 'http://www.w3.org/1999/xhtml',
[{name: attrName, value: attrValue}]);
}
createStyleElement(css: string): HTMLStyleElement {
var style = this.createElement('style');
this.setText(style, css);
return <HTMLStyleElement>style;
}
createShadowRoot(el): HTMLElement {
el.shadowRoot = treeAdapter.createDocumentFragment();
el.shadowRoot.parent = el;
return el.shadowRoot;
}
getShadowRoot(el): Element { return el.shadowRoot; }
getHost(el): string { return el.host; }
getDistributedNodes(el: any): List<Node> { throw _notImplemented('getDistributedNodes'); }
clone(node: Node): Node {
var _recursive = (node) => {
var nodeClone = Object.create(Object.getPrototypeOf(node));
for (var prop in node) {
var desc = Object.getOwnPropertyDescriptor(node, prop);
if (desc && 'value' in desc && typeof desc.value !== 'object') {
nodeClone[prop] = node[prop];
}
}
nodeClone.parent = null;
nodeClone.prev = null;
nodeClone.next = null;
nodeClone.children = null;
mapProps.forEach(mapName => {
if (isPresent(node[mapName])) {
nodeClone[mapName] = {};
for (var prop in node[mapName]) {
nodeClone[mapName][prop] = node[mapName][prop];
}
}
});
var cNodes = node.children;
if (cNodes) {
var cNodesClone = new Array(cNodes.length);
for (var i = 0; i < cNodes.length; i++) {
var childNode = cNodes[i];
var childNodeClone = _recursive(childNode);
cNodesClone[i] = childNodeClone;
if (i > 0) {
childNodeClone.prev = cNodesClone[i - 1];
cNodesClone[i - 1].next = childNodeClone;
}
childNodeClone.parent = nodeClone;
}
nodeClone.children = cNodesClone;
}
return nodeClone;
};
return _recursive(node);
}
getElementsByClassName(element, name: string): List<HTMLElement> {
return this.querySelectorAll(element, "." + name);
}
getElementsByTagName(element: any, name: string): List<HTMLElement> {
throw _notImplemented('getElementsByTagName');
}
classList(element): List<string> {
var classAttrValue = null;
var attributes = element.attribs;
if (attributes && attributes.hasOwnProperty("class")) {
classAttrValue = attributes["class"];
}
return classAttrValue ? classAttrValue.trim().split(/\s+/g) : [];
}
addClass(element, classname: string) {
var classList = this.classList(element);
var index = classList.indexOf(classname);
if (index == -1) {
classList.push(classname);
element.attribs["class"] = element.className = ListWrapper.join(classList, " ");
}
}
removeClass(element, classname: string) {
var classList = this.classList(element);
var index = classList.indexOf(classname);
if (index > -1) {
classList.splice(index, 1);
element.attribs["class"] = element.className = ListWrapper.join(classList, " ");
}
}
hasClass(element, classname: string): boolean {
return ListWrapper.contains(this.classList(element), classname);
}
_readStyleAttribute(element) {
var styleMap = {};
var attributes = element.attribs;
if (attributes && attributes.hasOwnProperty("style")) {
var styleAttrValue = attributes["style"];
var styleList = styleAttrValue.split(/;+/g);
for (var i = 0; i < styleList.length; i++) {
if (styleList[i].length > 0) {
var elems = styleList[i].split(/:+/g);
styleMap[elems[0].trim()] = elems[1].trim();
}
}
}
return styleMap;
}
_writeStyleAttribute(element, styleMap) {
var styleAttrValue = "";
for (var key in styleMap) {
var newValue = styleMap[key];
if (newValue && newValue.length > 0) {
styleAttrValue += key + ":" + styleMap[key] + ";";
}
}
element.attribs["style"] = styleAttrValue;
}
setStyle(element, stylename: string, stylevalue: string) {
var styleMap = this._readStyleAttribute(element);
styleMap[stylename] = stylevalue;
this._writeStyleAttribute(element, styleMap);
}
removeStyle(element, stylename: string) { this.setStyle(element, stylename, null); }
getStyle(element, stylename: string): string {
var styleMap = this._readStyleAttribute(element);
return styleMap.hasOwnProperty(stylename) ? styleMap[stylename] : "";
}
tagName(element): string { return element.tagName == "style" ? "STYLE" : element.tagName; }
attributeMap(element): Map<string, string> {
var res = new Map();
var elAttrs = treeAdapter.getAttrList(element);
for (var i = 0; i < elAttrs.length; i++) {
var attrib = elAttrs[i];
res.set(attrib.name, attrib.value);
}
return res;
}
hasAttribute(element, attribute: string): boolean {
return element.attribs && element.attribs.hasOwnProperty(attribute);
}
getAttribute(element, attribute: string): string {
return element.attribs && element.attribs.hasOwnProperty(attribute) ?
element.attribs[attribute] :
null;
}
setAttribute(element, attribute: string, value: string) {
if (attribute) {
element.attribs[attribute] = value;
}
}
removeAttribute(element, attribute: string) {
if (attribute) {
StringMapWrapper.delete(element.attribs, attribute);
}
}
templateAwareRoot(el): any { return this.isTemplateElement(el) ? this.content(el) : el; }
createHtmlDocument(): Document {
var newDoc = treeAdapter.createDocument();
newDoc.title = "fake title";
var head = treeAdapter.createElement("head", null, []);
var body = treeAdapter.createElement("body", 'http://www.w3.org/1999/xhtml', []);
this.appendChild(newDoc, head);
this.appendChild(newDoc, body);
StringMapWrapper.set(newDoc, "head", head);
StringMapWrapper.set(newDoc, "body", body);
StringMapWrapper.set(newDoc, "_window", StringMapWrapper.create());
return newDoc;
}
defaultDoc(): Document {
if (defDoc === null) {
defDoc = this.createHtmlDocument();
}
return defDoc;
}
getBoundingClientRect(el): any { return {left: 0, top: 0, width: 0, height: 0}; }
getTitle(): string { return this.defaultDoc().title || ""; }
setTitle(newTitle: string) { this.defaultDoc().title = newTitle; }
isTemplateElement(el: any): boolean {
return this.isElementNode(el) && this.tagName(el) === "template";
}
isTextNode(node): boolean { return treeAdapter.isTextNode(node); }
isCommentNode(node): boolean { return treeAdapter.isCommentNode(node); }
isElementNode(node): boolean { return node ? treeAdapter.isElementNode(node) : false; }
hasShadowRoot(node): boolean { return isPresent(node.shadowRoot); }
isShadowRoot(node): boolean { return this.getShadowRoot(node) == node; }
importIntoDoc(node): any { return this.clone(node); }
adoptNode(node): any { return node; }
isPageRule(rule): boolean {
return rule.type === 6; // CSSRule.PAGE_RULE
}
isStyleRule(rule): boolean {
return rule.type === 1; // CSSRule.MEDIA_RULE
}
isMediaRule(rule): boolean {
return rule.type === 4; // CSSRule.MEDIA_RULE
}
isKeyframesRule(rule): boolean {
return rule.type === 7; // CSSRule.KEYFRAMES_RULE
}
getHref(el): string { return el.href; }
resolveAndSetHref(el, baseUrl: string, href: string) {
if (href == null) {
el.href = baseUrl;
} else {
el.href = url.resolve(baseUrl, href);
}
}
_buildRules(parsedRules, css?) {
var rules = [];
for (var i = 0; i < parsedRules.length; i++) {
var parsedRule = parsedRules[i];
var rule: StringMap<string, any> = StringMapWrapper.create();
StringMapWrapper.set(rule, "cssText", css);
StringMapWrapper.set(rule, "style", {content: "", cssText: ""});
if (parsedRule.type == "rule") {
StringMapWrapper.set(rule, "type", 1);
StringMapWrapper.set(rule, "selectorText", parsedRule.selectors.join(", ")
.replace(/\s{2,}/g, " ")
.replace(/\s*~\s*/g, " ~ ")
.replace(/\s*\+\s*/g, " + ")
.replace(/\s*>\s*/g, " > ")
.replace(/\[(\w+)=(\w+)\]/g, '[$1="$2"]'));
if (isBlank(parsedRule.declarations)) {
continue;
}
for (var j = 0; j < parsedRule.declarations.length; j++) {
var declaration = parsedRule.declarations[j];
StringMapWrapper.set(StringMapWrapper.get(rule, "style"), declaration.property,
declaration.value);
StringMapWrapper.get(rule, "style").cssText +=
declaration.property + ": " + declaration.value + ";";
}
} else if (parsedRule.type == "media") {
StringMapWrapper.set(rule, "type", 4);
StringMapWrapper.set(rule, "media", {mediaText: parsedRule.media});
if (parsedRule.rules) {
StringMapWrapper.set(rule, "cssRules", this._buildRules(parsedRule.rules));
}
}
rules.push(rule);
}
return rules;
}
cssToRules(css: string): List<any> {
css = css.replace(/url\(\'(.+)\'\)/g, 'url($1)');
var rules = [];
var parsedCSS = cssParse(css, {silent: true});
if (parsedCSS.stylesheet && parsedCSS.stylesheet.rules) {
rules = this._buildRules(parsedCSS.stylesheet.rules, css);
}
return rules;
}
supportsDOMEvents(): boolean { return false; }
supportsNativeShadowDOM(): boolean { return false; }
getGlobalEventTarget(target: string): any {
if (target == "window") {
return (<any>this.defaultDoc())._window;
} else if (target == "document") {
return this.defaultDoc();
} else if (target == "body") {
return this.defaultDoc().body;
}
}
getBaseHref(): string { throw 'not implemented'; }
resetBaseElement(): void { throw 'not implemented'; }
getHistory(): History { throw 'not implemented'; }
getLocation(): Location { throw 'not implemented'; }
getUserAgent(): string { return "Fake user agent"; }
getData(el, name: string): string { return this.getAttribute(el, 'data-' + name); }
setData(el, name: string, value: string) { this.setAttribute(el, 'data-' + name, value); }
// TODO(tbosch): move this into a separate environment class once we have it
setGlobalVar(name: string, value: any) { global[name] = value; }
}
// TODO: build a proper list, this one is all the keys of a HTMLInputElement
var _HTMLElementPropertyList = [
"webkitEntries",
"incremental",
"webkitdirectory",
"selectionDirection",
"selectionEnd",
"selectionStart",
"labels",
"validationMessage",
"validity",
"willValidate",
"width",
"valueAsNumber",
"valueAsDate",
"value",
"useMap",
"defaultValue",
"type",
"step",
"src",
"size",
"required",
"readOnly",
"placeholder",
"pattern",
"name",
"multiple",
"min",
"minLength",
"maxLength",
"max",
"list",
"indeterminate",
"height",
"formTarget",
"formNoValidate",
"formMethod",
"formEnctype",
"formAction",
"files",
"form",
"disabled",
"dirName",
"checked",
"defaultChecked",
"autofocus",
"autocomplete",
"alt",
"align",
"accept",
"onautocompleteerror",
"onautocomplete",
"onwaiting",
"onvolumechange",
"ontoggle",
"ontimeupdate",
"onsuspend",
"onsubmit",
"onstalled",
"onshow",
"onselect",
"onseeking",
"onseeked",
"onscroll",
"onresize",
"onreset",
"onratechange",
"onprogress",
"onplaying",
"onplay",
"onpause",
"onmousewheel",
"onmouseup",
"onmouseover",
"onmouseout",
"onmousemove",
"onmouseleave",
"onmouseenter",
"onmousedown",
"onloadstart",
"onloadedmetadata",
"onloadeddata",
"onload",
"onkeyup",
"onkeypress",
"onkeydown",
"oninvalid",
"oninput",
"onfocus",
"onerror",
"onended",
"onemptied",
"ondurationchange",
"ondrop",
"ondragstart",
"ondragover",
"ondragleave",
"ondragenter",
"ondragend",
"ondrag",
"ondblclick",
"oncuechange",
"oncontextmenu",
"onclose",
"onclick",
"onchange",
"oncanplaythrough",
"oncanplay",
"oncancel",
"onblur",
"onabort",
"spellcheck",
"isContentEditable",
"contentEditable",
"outerText",
"innerText",
"accessKey",
"hidden",
"webkitdropzone",
"draggable",
"tabIndex",
"dir",
"translate",
"lang",
"title",
"childElementCount",
"lastElementChild",
"firstElementChild",
"children",
"onwebkitfullscreenerror",
"onwebkitfullscreenchange",
"nextElementSibling",
"previousElementSibling",
"onwheel",
"onselectstart",
"onsearch",
"onpaste",
"oncut",
"oncopy",
"onbeforepaste",
"onbeforecut",
"onbeforecopy",
"shadowRoot",
"dataset",
"classList",
"className",
"outerHTML",
"innerHTML",
"scrollHeight",
"scrollWidth",
"scrollTop",
"scrollLeft",
"clientHeight",
"clientWidth",
"clientTop",
"clientLeft",
"offsetParent",
"offsetHeight",
"offsetWidth",
"offsetTop",
"offsetLeft",
"localName",
"prefix",
"namespaceURI",
"id",
"style",
"attributes",
"tagName",
"parentElement",
"textContent",
"baseURI",
"ownerDocument",
"nextSibling",
"previousSibling",
"lastChild",
"firstChild",
"childNodes",
"parentNode",
"nodeType",
"nodeValue",
"nodeName",
"closure_lm_714617",
"__jsaction"
];

View File

@ -0,0 +1,126 @@
library angular2.core.facade.async;
import 'dart:async';
export 'dart:async' show Future, Stream, StreamController, StreamSubscription;
class PromiseWrapper {
static Future resolve(obj) => new Future.value(obj);
static Future reject(obj, stackTrace) => new Future.error(obj,
stackTrace != null ? stackTrace : obj is Error ? obj.stackTrace : null);
static Future<List> all(List<dynamic> promises) {
return Future
.wait(promises.map((p) => p is Future ? p : new Future.value(p)));
}
static Future then(Future promise, success(value), [Function onError]) {
if (success == null) return promise.catchError(onError);
return promise.then(success, onError: onError);
}
static Future wrap(Function fn) {
return new Future(fn);
}
// Note: We can't rename this method to `catch`, as this is not a valid
// method name in Dart.
static Future catchError(Future promise, Function onError) {
return promise.catchError(onError);
}
static PromiseCompleter<dynamic> completer() =>
new PromiseCompleter(new Completer());
}
class TimerWrapper {
static Timer setTimeout(fn(), int millis) =>
new Timer(new Duration(milliseconds: millis), fn);
static void clearTimeout(Timer timer) {
timer.cancel();
}
static Timer setInterval(fn(), int millis) {
var interval = new Duration(milliseconds: millis);
return new Timer.periodic(interval, (Timer timer) {
fn();
});
}
static void clearInterval(Timer timer) {
timer.cancel();
}
}
class ObservableWrapper {
static StreamSubscription subscribe(Stream s, Function onNext,
[onError, onComplete]) {
return s.listen(onNext,
onError: onError, onDone: onComplete, cancelOnError: true);
}
static bool isObservable(obs) {
return obs is Stream;
}
static void dispose(StreamSubscription s) {
s.cancel();
}
static void callNext(EventEmitter emitter, value) {
emitter.add(value);
}
static void callThrow(EventEmitter emitter, error) {
emitter.addError(error);
}
static void callReturn(EventEmitter emitter) {
emitter.close();
}
}
class EventEmitter extends Stream {
StreamController<dynamic> _controller;
EventEmitter() {
_controller = new StreamController.broadcast();
}
StreamSubscription listen(void onData(dynamic line),
{void onError(Error error), void onDone(), bool cancelOnError}) {
return _controller.stream.listen(onData,
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
}
void add(value) {
_controller.add(value);
}
void addError(error) {
_controller.addError(error);
}
void close() {
_controller.close();
}
}
class PromiseCompleter<T> {
final Completer<T> c;
PromiseCompleter(this.c);
Future get promise => c.future;
void resolve(v) {
c.complete(v);
}
void reject(error, stack) {
if (stack == null && error is Error) {
stack = error.stackTrace;
}
c.completeError(error, stack);
}
}

View File

@ -0,0 +1,124 @@
/// <reference path="../../typings/rx/rx.d.ts" />
import {global, isPresent} from 'angular2/src/facade/lang';
import {List} from 'angular2/src/facade/collection';
import * as Rx from 'rx';
export {Promise};
export interface PromiseCompleter<R> {
promise: Promise<R>;
resolve: (value?: R | PromiseLike<R>) => void;
reject: (error?: any, stackTrace?: string) => void;
}
export class PromiseWrapper {
static resolve<T>(obj: T): Promise<T> { return Promise.resolve(obj); }
static reject(obj: any, _): Promise<any> { return Promise.reject(obj); }
// Note: We can't rename this method into `catch`, as this is not a valid
// method name in Dart.
static catchError<T>(promise: Promise<T>,
onError: (error: any) => T | PromiseLike<T>): Promise<T> {
return promise.catch(onError);
}
static all(promises: List<any>): Promise<any> {
if (promises.length == 0) return Promise.resolve([]);
return Promise.all(promises);
}
static then<T, U>(promise: Promise<T>, success: (value: T) => U | PromiseLike<U>,
rejection?: (error: any, stack?: any) => U | PromiseLike<U>): Promise<U> {
return promise.then(success, rejection);
}
static wrap<T>(computation: () => T): Promise<T> {
return new Promise((res, rej) => {
try {
res(computation());
} catch (e) {
rej(e);
}
});
}
static completer(): PromiseCompleter<any> {
var resolve;
var reject;
var p = new Promise(function(res, rej) {
resolve = res;
reject = rej;
});
return {promise: p, resolve: resolve, reject: reject};
}
}
export class TimerWrapper {
static setTimeout(fn: Function, millis: number): number { return global.setTimeout(fn, millis); }
static clearTimeout(id: number): void { global.clearTimeout(id); }
static setInterval(fn: Function, millis: number): number {
return global.setInterval(fn, millis);
}
static clearInterval(id: number): void { global.clearInterval(id); }
}
export class ObservableWrapper {
// TODO(vsavkin): when we use rxnext, try inferring the generic type from the first arg
static subscribe<T>(emitter: Observable, onNext: (value: T) => void,
onThrow: (exception: any) => void = null,
onReturn: () => void = null): Object {
return emitter.observer({next: onNext, throw: onThrow, return: onReturn});
}
static isObservable(obs: any): boolean { return obs instanceof Observable; }
static dispose(subscription: any) { subscription.dispose(); }
static callNext(emitter: EventEmitter, value: any) { emitter.next(value); }
static callThrow(emitter: EventEmitter, error: any) { emitter.throw(error); }
static callReturn(emitter: EventEmitter) { emitter.return (null); }
}
// TODO: vsavkin change to interface
export class Observable {
observer(generator: any): Object { return null; }
}
/**
* Use Rx.Observable but provides an adapter to make it work as specified here:
* https://github.com/jhusain/observable-spec
*
* Once a reference implementation of the spec is available, switch to it.
*/
export class EventEmitter extends Observable {
_subject: Rx.Subject<any>;
_immediateScheduler;
constructor() {
super();
this._subject = new Rx.Subject<any>();
this._immediateScheduler = (<any>Rx.Scheduler).immediate;
}
observer(generator: any): Rx.IDisposable {
return this._subject.observeOn(this._immediateScheduler)
.subscribe((value) => { setTimeout(() => generator.next(value)); },
(error) => generator.throw ? generator.throw(error) : null,
() => generator.return ? generator.return () : null);
}
toRx(): Rx.Observable<any> { return this._subject; }
next(value: any) { this._subject.onNext(value); }
throw(error: any) { this._subject.onError(error); }
return (value?: any) { this._subject.onCompleted(); }
}

View File

@ -0,0 +1,30 @@
/**
* Dart version of browser APIs. This library depends on 'dart:html' and
* therefore can only run in the browser.
*/
library angular2.src.facade.browser;
import 'dart:js' show context;
export 'dart:html'
show
document,
location,
window,
Element,
Node,
MouseEvent,
KeyboardEvent,
Event,
EventTarget,
History,
Location,
EventListener;
final _gc = context['gc'];
void gc() {
if (_gc != null) {
_gc.apply(const []);
}
}

View File

@ -0,0 +1,16 @@
/**
* JS version of browser APIs. This library can only run in the browser.
*/
var win = window;
export {win as window};
export var document = window.document;
export var location = window.location;
export var gc = window['gc'] ? () => window['gc']() : () => null;
export const Event = Event;
export const MouseEvent = MouseEvent;
export const KeyboardEvent = KeyboardEvent;
export const EventTarget = EventTarget;
export const History = History;
export const Location = Location;
export const EventListener = EventListener;

View File

@ -0,0 +1,264 @@
library facade.collection;
import 'dart:collection' show IterableBase, Iterator;
import 'dart:convert' show JsonEncoder;
export 'dart:core' show Map, List, Set;
import 'dart:math' show max, min;
var jsonEncoder = new JsonEncoder();
class MapIterator extends Iterator<List> {
final Iterator _iterator;
final Map _map;
MapIterator(Map map)
: _map = map,
_iterator = map.keys.iterator;
bool moveNext() => _iterator.moveNext();
List get current {
return _iterator.current != null
? [_iterator.current, _map[_iterator.current]]
: null;
}
}
class IterableMap extends IterableBase<List> {
final Map _map;
IterableMap(Map map) : _map = map;
Iterator<List> get iterator => new MapIterator(_map);
}
class MapWrapper {
static Map clone(Map m) => new Map.from(m);
// in opposite to JS, Dart does not create a new map
static Map createFromStringMap(Map m) => m;
// in opposite to JS, Dart does not create a new map
static Map toStringMap(Map m) => m;
static Map createFromPairs(List pairs) => pairs.fold({}, (m, p) {
m[p[0]] = p[1];
return m;
});
static forEach(Map m, fn(v, k)) {
m.forEach((k, v) => fn(v, k));
}
static get(Map map, key) => map[key];
static int size(Map m) => m.length;
static void delete(Map m, k) {
m.remove(k);
}
static void clearValues(Map m) {
for (var k in m.keys) {
m[k] = null;
}
}
static Iterable iterable(Map m) => new IterableMap(m);
static List keys(Map m) => m.keys.toList();
static List values(Map m) => m.values.toList();
}
class StringMapWrapper {
static Map create() => {};
static bool contains(Map map, key) => map.containsKey(key);
static get(Map map, key) => map[key];
static void set(Map map, key, value) {
map[key] = value;
}
static void delete(Map m, k) {
m.remove(k);
}
static void forEach(Map m, fn(v, k)) {
m.forEach((k, v) => fn(v, k));
}
static Map merge(Map a, Map b) {
var m = new Map.from(a);
if (b != null) {
b.forEach((k, v) => m[k] = v);
}
return m;
}
static List<String> keys(Map<String, dynamic> a) {
return a.keys.toList();
}
static bool isEmpty(Map m) => m.isEmpty;
static bool equals(Map m1, Map m2) {
if (m1.length != m2.length) {
return false;
}
for (var key in m1.keys) {
if (m1[key] != m2[key]) {
return false;
}
}
return true;
}
}
typedef bool Predicate<T>(T item);
class ListWrapper {
static List clone(Iterable l) => new List.from(l);
static List createFixedSize(int size) => new List(size);
static List createGrowableSize(int size) =>
new List.generate(size, (_) => null, growable: true);
static get(List m, int k) => m[k];
static void set(List m, int k, v) {
m[k] = v;
}
static bool contains(List m, k) => m.contains(k);
static List map(list, fn(item)) => list.map(fn).toList();
static List filter(List list, bool fn(item)) => list.where(fn).toList();
static int indexOf(List list, value, [int startIndex = 0]) =>
list.indexOf(value, startIndex);
static int lastIndexOf(List list, value, [int startIndex = null]) =>
list.lastIndexOf(value, startIndex == null ? list.length : startIndex);
static find(List list, bool fn(item)) =>
list.firstWhere(fn, orElse: () => null);
static bool any(List list, bool fn(item)) => list.any(fn);
static void forEach(Iterable list, fn(item)) {
list.forEach(fn);
}
static void forEachWithIndex(List list, fn(item, index)) {
for (var i = 0; i < list.length; ++i) {
fn(list[i], i);
}
}
static reduce(List list, fn(a, b), init) {
return list.fold(init, fn);
}
static first(List list) => list.isEmpty ? null : list.first;
static last(List list) => list.isEmpty ? null : list.last;
static List reversed(List list) => list.reversed.toList();
static List concat(List a, List b) {
return new List()
..length = a.length + b.length
..setRange(0, a.length, a)
..setRange(a.length, a.length + b.length, b);
}
static void insert(List l, int index, value) {
l.insert(index, value);
}
static removeAt(List l, int index) => l.removeAt(index);
static void removeAll(List list, List items) {
for (var i = 0; i < items.length; ++i) {
list.remove(items[i]);
}
}
static removeLast(List list) => list.removeLast();
static bool remove(List list, item) => list.remove(item);
static void clear(List l) {
l.clear();
}
static String join(List l, String s) => l.join(s);
static bool isEmpty(Iterable list) => list.isEmpty;
static void fill(List l, value, [int start = 0, int end]) {
l.fillRange(_startOffset(l, start), _endOffset(l, end), value);
}
static bool equals(List a, List b) {
if (a.length != b.length) return false;
for (var i = 0; i < a.length; ++i) {
if (a[i] != b[i]) return false;
}
return true;
}
static List slice(List l, [int from = 0, int to]) {
return l.sublist(_startOffset(l, from), _endOffset(l, to));
}
static List splice(List l, int from, int length) {
from = _startOffset(l, from);
var to = from + length;
var sub = l.sublist(from, to);
l.removeRange(from, to);
return sub;
}
static void sort(List l, [compareFn(a, b) = null]) {
if (compareFn == null) {
l.sort();
} else {
l.sort(compareFn);
}
}
static String toJSON(List l) {
return jsonEncoder.convert(l);
}
// JS splice, slice, fill functions can take start < 0 which indicates a position relative to
// the end of the list
static int _startOffset(List l, int start) {
int len = l.length;
return start = start < 0 ? max(len + start, 0) : min(start, len);
}
// JS splice, slice, fill functions can take end < 0 which indicates a position relative to
// the end of the list
static int _endOffset(List l, int end) {
int len = l.length;
if (end == null) return len;
return end < 0 ? max(len + end, 0) : min(end, len);
}
static maximum(List l, fn(item)) {
if (l.length == 0) {
return null;
}
var solution = null;
var maxValue = double.NEGATIVE_INFINITY;
for (var index = 0; index < l.length; index++) {
var candidate = l[index];
if (candidate == null) {
continue;
}
var candidateValue = fn(candidate);
if (candidateValue > maxValue) {
solution = candidate;
maxValue = candidateValue;
}
}
return solution;
}
}
bool isListLikeIterable(obj) => obj is Iterable;
void iterateListLike(iter, fn(item)) {
assert(iter is Iterable);
for (var item in iter) {
fn(item);
}
}
class SetWrapper {
static Set createFromList(List l) => new Set.from(l);
static bool has(Set s, key) => s.contains(key);
static void delete(Set m, k) {
m.remove(k);
}
}

View File

@ -0,0 +1,339 @@
import {isJsObject, global, isPresent, isBlank, isArray} from 'angular2/src/facade/lang';
export var List = global.Array;
export var Map = global.Map;
export var Set = global.Set;
export var StringMap = global.Object;
// Safari and Internet Explorer do not support the iterable parameter to the
// Map constructor. We work around that by manually adding the items.
var createMapFromPairs: {(pairs: List<any>): Map<any, any>} = (function() {
try {
if (new Map([[1, 2]]).size === 1) {
return function createMapFromPairs(pairs: List<any>):
Map<any, any> { return new Map(pairs); };
}
} catch (e) {
}
return function createMapAndPopulateFromPairs(pairs: List<any>): Map<any, any> {
var map = new Map();
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i];
map.set(pair[0], pair[1]);
}
return map;
};
})();
var createMapFromMap: {(m: Map<any, any>): Map<any, any>} = (function() {
try {
if (new Map(new Map())) {
return function createMapFromMap(m: Map<any, any>): Map<any, any> { return new Map(m); };
}
} catch (e) {
}
return function createMapAndPopulateFromMap(m: Map<any, any>): Map<any, any> {
var map = new Map();
m.forEach((v, k) => { map.set(k, v); });
return map;
};
})();
var _clearValues: {(m: Map<any, any>)} = (function() {
if ((<any>(new Map()).keys()).next) {
return function _clearValues(m: Map<any, any>) {
var keyIterator = m.keys();
var k;
while (!((k = (<any>keyIterator).next()).done)) {
m.set(k.value, null);
}
};
} else {
return function _clearValuesWithForeEach(m: Map<any, any>) {
m.forEach((v, k) => { m.set(k, null); });
};
}
})();
// Safari doesn't implement MapIterator.next(), which is used is Traceur's polyfill of Array.from
// TODO(mlaval): remove the work around once we have a working polyfill of Array.from
var _arrayFromMap: {(m: Map<any, any>, getValues: boolean): List<any>} = (function() {
try {
if ((<any>(new Map()).values()).next) {
return function createArrayFromMap(m: Map<any, any>, getValues: boolean): List<any> {
return getValues ? (<any>Array).from(m.values()) : (<any>Array).from(m.keys());
};
}
} catch (e) {
}
return function createArrayFromMapWithForeach(m: Map<any, any>, getValues: boolean): List<any> {
var res = ListWrapper.createFixedSize(m.size), i = 0;
m.forEach((v, k) => {
ListWrapper.set(res, i, getValues ? v : k);
i++;
});
return res;
};
})();
export class MapWrapper {
static clone<K, V>(m: Map<K, V>): Map<K, V> { return createMapFromMap(m); }
static createFromStringMap<T>(stringMap: StringMap<string, T>): Map<string, T> {
var result = new Map();
for (var prop in stringMap) {
result.set(prop, stringMap[prop]);
}
return result;
}
static toStringMap<T>(m: Map<string, T>): StringMap<string, T> {
var r = {};
m.forEach((v, k) => r[k] = v);
return r;
}
static createFromPairs(pairs: List<any>): Map<any, any> { return createMapFromPairs(pairs); }
static forEach<K, V>(m: Map<K, V>, fn: /*(V, K) => void*/ Function) { m.forEach(<any>fn); }
static get<K, V>(map: Map<K, V>, key: K): V { return map.get(key); }
static size(m: Map<any, any>): number { return m.size; }
static delete<K>(m: Map<K, any>, k: K) { m.delete(k); }
static clearValues(m: Map<any, any>) { _clearValues(m); }
static iterable<T>(m: T): T { return m; }
static keys<K>(m: Map<K, any>): List<K> { return _arrayFromMap(m, false); }
static values<V>(m: Map<any, V>): List<V> { return _arrayFromMap(m, true); }
}
/**
* Wraps Javascript Objects
*/
export class StringMapWrapper {
static create(): StringMap<any, any> {
// Note: We are not using Object.create(null) here due to
// performance!
// http://jsperf.com/ng2-object-create-null
return {};
}
static contains(map: StringMap<string, any>, key: string): boolean {
return map.hasOwnProperty(key);
}
static get<V>(map: StringMap<string, V>, key: string): V {
return map.hasOwnProperty(key) ? map[key] : undefined;
}
static set<V>(map: StringMap<string, V>, key: string, value: V) { map[key] = value; }
static keys(map: StringMap<string, any>): List<string> { return Object.keys(map); }
static isEmpty(map: StringMap<string, any>): boolean {
for (var prop in map) {
return false;
}
return true;
}
static delete (map: StringMap<string, any>, key: string) { delete map[key]; }
static forEach<K, V>(map: StringMap<string, V>, callback: /*(V, K) => void*/ Function) {
for (var prop in map) {
if (map.hasOwnProperty(prop)) {
callback(map[prop], prop);
}
}
}
static merge<V>(m1: StringMap<string, V>, m2: StringMap<string, V>): StringMap<string, V> {
var m = {};
for (var attr in m1) {
if (m1.hasOwnProperty(attr)) {
m[attr] = m1[attr];
}
}
for (var attr in m2) {
if (m2.hasOwnProperty(attr)) {
m[attr] = m2[attr];
}
}
return m;
}
static equals<V>(m1: StringMap<string, V>, m2: StringMap<string, V>): boolean {
var k1 = Object.keys(m1);
var k2 = Object.keys(m2);
if (k1.length != k2.length) {
return false;
}
var key;
for (var i = 0; i < k1.length; i++) {
key = k1[i];
if (m1[key] !== m2[key]) {
return false;
}
}
return true;
}
}
export interface Predicate<T> { (value: T, index?: number, array?: T[]): boolean; }
export class ListWrapper {
// JS has no way to express a staticly fixed size list, but dart does so we
// keep both methods.
static createFixedSize(size: number): List<any> { return new List(size); }
static createGrowableSize(size: number): List<any> { return new List(size); }
static get<T>(m: List<T>, k: number): T { return m[k]; }
static set<T>(m: List<T>, k: number, v: T) { m[k] = v; }
static clone<T>(array: List<T>): T[] { return array.slice(0); }
static map<T, V>(array: List<T>, fn: (T) => V): List<V> { return array.map(fn); }
static forEach<T>(array: List<T>, fn: (T) => void) {
for (var i = 0; i < array.length; i++) {
fn(array[i]);
}
}
static forEachWithIndex<T>(array: List<T>, fn: (T, number) => void) {
for (var i = 0; i < array.length; i++) {
fn(array[i], i);
}
}
static first<T>(array: List<T>): T {
if (!array) return null;
return array[0];
}
static last<T>(array: List<T>): T {
if (!array || array.length == 0) return null;
return array[array.length - 1];
}
static find<T>(list: List<T>, pred: Predicate<T>): T {
for (var i = 0; i < list.length; ++i) {
if (pred(list[i])) return list[i];
}
return null;
}
static indexOf<T>(array: List<T>, value: T, startIndex: number = 0): number {
return array.indexOf(value, startIndex);
}
static reduce<T, E>(list: List<T>,
fn: (accumValue: E, currentValue: T, currentIndex: number, array: T[]) => E,
init: E): E {
return list.reduce(fn, init);
}
static filter<T>(array: List<T>, pred: Predicate<T>): T[] { return array.filter(pred); }
static any(list: List<any>, pred: Function): boolean {
for (var i = 0; i < list.length; ++i) {
if (pred(list[i])) return true;
}
return false;
}
static contains<T>(list: List<T>, el: T): boolean { return list.indexOf(el) !== -1; }
static reversed<T>(array: List<T>): T[] {
var a = ListWrapper.clone(array);
return a.reverse();
}
static concat(a: List<any>, b: List<any>): List<any> { return a.concat(b); }
static insert<T>(list: List<T>, index: number, value: T) { list.splice(index, 0, value); }
static removeAt<T>(list: List<T>, index: number): T {
var res = list[index];
list.splice(index, 1);
return res;
}
static removeAll<T>(list: List<T>, items: List<T>) {
for (var i = 0; i < items.length; ++i) {
var index = list.indexOf(items[i]);
list.splice(index, 1);
}
}
static removeLast<T>(list: List<T>): T { return list.pop(); }
static remove<T>(list: List<T>, el: T): boolean {
var index = list.indexOf(el);
if (index > -1) {
list.splice(index, 1);
return true;
}
return false;
}
static clear(list: List<any>) { list.splice(0, list.length); }
static join(list: List<any>, s: string): string { return list.join(s); }
static isEmpty(list: List<any>): boolean { return list.length == 0; }
static fill(list: List<any>, value: any, start: number = 0, end: number = null) {
list.fill(value, start, end === null ? list.length : end);
}
static equals(a: List<any>, b: List<any>): boolean {
if (a.length != b.length) return false;
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
static slice<T>(l: List<T>, from: number = 0, to: number = null): List<T> {
return l.slice(from, to === null ? undefined : to);
}
static splice<T>(l: List<T>, from: number, length: number): List<T> {
return l.splice(from, length);
}
static sort<T>(l: List<T>, compareFn?: (a: T, b: T) => number) {
if (isPresent(compareFn)) {
l.sort(compareFn);
} else {
l.sort();
}
}
static toString<T>(l: List<T>): string { return l.toString(); }
static toJSON<T>(l: List<T>): string { return JSON.stringify(l); }
static maximum<T>(list: List<T>, predicate: (T) => number): T {
if (list.length == 0) {
return null;
}
var solution = null;
var maxValue = -Infinity;
for (var index = 0; index < list.length; index++) {
var candidate = list[index];
if (isBlank(candidate)) {
continue;
}
var candidateValue = predicate(candidate);
if (candidateValue > maxValue) {
solution = candidate;
maxValue = candidateValue;
}
}
return solution;
}
}
export function isListLikeIterable(obj: any): boolean {
if (!isJsObject(obj)) return false;
return isArray(obj) ||
(!(obj instanceof Map) && // JS Map are iterables but return entries as [k, v]
Symbol.iterator in obj); // JS Iterable have a Symbol.iterator prop
}
export function iterateListLike(obj: any, fn: Function) {
if (isArray(obj)) {
for (var i = 0; i < obj.length; i++) {
fn(obj[i]);
}
} else {
var iterator = obj[Symbol.iterator]();
var item;
while (!((item = iterator.next()).done)) {
fn(item.value);
}
}
}
// Safari and Internet Explorer do not support the iterable parameter to the
// Set constructor. We work around that by manually adding the items.
var createSetFromList: {(lst: List<any>): Set<any>} = (function() {
var test = new Set([1, 2, 3]);
if (test.size === 3) {
return function createSetFromList(lst: List<any>): Set<any> { return new Set(lst); };
} else {
return function createSetAndPopulateFromList(lst: List<any>): Set<any> {
var res = new Set(lst);
if (res.size !== lst.length) {
for (var i = 0; i < lst.length; i++) {
res.add(lst[i]);
}
}
return res;
};
}
})();
export class SetWrapper {
static createFromList<T>(lst: List<T>): Set<T> { return createSetFromList(lst); }
static has<T>(s: Set<T>, key: T): boolean { return s.has(key); }
static delete<K>(m: Set<K>, k: K) { m.delete(k); }
}

View File

@ -0,0 +1,57 @@
library facade.intl;
import 'package:intl/intl.dart';
String _normalizeLocale(String locale) => locale.replaceAll('-', '_');
enum NumberFormatStyle { DECIMAL, PERCENT, CURRENCY }
class NumberFormatter {
static String format(num number, String locale, NumberFormatStyle style,
{int minimumIntegerDigits: 1,
int minimumFractionDigits: 0,
int maximumFractionDigits: 3,
String currency,
bool currencyAsSymbol: false}) {
locale = _normalizeLocale(locale);
NumberFormat formatter;
switch (style) {
case NumberFormatStyle.DECIMAL:
formatter = new NumberFormat.decimalPattern(locale);
break;
case NumberFormatStyle.PERCENT:
formatter = new NumberFormat.percentPattern(locale);
break;
case NumberFormatStyle.CURRENCY:
if (currencyAsSymbol) {
// See https://github.com/dart-lang/intl/issues/59.
throw new Exception(
'Displaying currency as symbol is not supported.');
}
formatter = new NumberFormat.currencyPattern(locale, currency);
break;
}
formatter.minimumIntegerDigits = minimumIntegerDigits;
formatter.minimumFractionDigits = minimumFractionDigits;
formatter.maximumFractionDigits = maximumFractionDigits;
return formatter.format(number);
}
}
class DateFormatter {
static RegExp _multiPartRegExp = new RegExp(r'^([yMdE]+)([Hjms]+)$');
static String format(DateTime date, String locale, String pattern) {
locale = _normalizeLocale(locale);
var formatter = new DateFormat(null, locale);
var matches = _multiPartRegExp.firstMatch(pattern);
if (matches != null) {
// Support for patterns which have known date and time components.
formatter.addPattern(matches[1]);
formatter.addPattern(matches[2], ', ');
} else {
formatter.addPattern(pattern);
}
return formatter.format(date);
}
}

View File

@ -0,0 +1,147 @@
// Modified version of internal Typescript intl.d.ts.
// TODO(piloopin): remove when https://github.com/Microsoft/TypeScript/issues/3521 is shipped.
declare module Intl {
interface NumberFormatOptions {
localeMatcher?: string;
style?: string;
currency?: string;
currencyDisplay?: string;
useGrouping?: boolean;
minimumIntegerDigits?: number;
minimumFractionDigits?: number;
maximumFractionDigits?: number;
}
interface NumberFormat {
format(value: number): string;
}
var NumberFormat: {new (locale?: string, options?: NumberFormatOptions): NumberFormat};
interface DateTimeFormatOptions {
localeMatcher?: string;
weekday?: string;
era?: string;
year?: string;
month?: string;
day?: string;
hour?: string;
minute?: string;
second?: string;
timeZoneName?: string;
formatMatcher?: string;
hour12?: boolean;
}
interface DateTimeFormat {
format(date?: Date | number): string;
}
var DateTimeFormat: {new (locale?: string, options?: DateTimeFormatOptions): DateTimeFormat};
}
export enum NumberFormatStyle {
DECIMAL,
PERCENT,
CURRENCY
}
export class NumberFormatter {
static format(number: number, locale: string, style: NumberFormatStyle,
{minimumIntegerDigits = 1, minimumFractionDigits = 0, maximumFractionDigits = 3,
currency, currencyAsSymbol = false}: {
minimumIntegerDigits?: number,
minimumFractionDigits?: number,
maximumFractionDigits?: number,
currency?: string,
currencyAsSymbol?: boolean
} = {}): string {
var intlOptions: Intl.NumberFormatOptions = {
minimumIntegerDigits: minimumIntegerDigits,
minimumFractionDigits: minimumFractionDigits,
maximumFractionDigits: maximumFractionDigits
};
intlOptions.style = NumberFormatStyle[style].toLowerCase();
if (style == NumberFormatStyle.CURRENCY) {
intlOptions.currency = currency;
intlOptions.currencyDisplay = currencyAsSymbol ? 'symbol' : 'code';
}
return new Intl.NumberFormat(locale, intlOptions).format(number);
}
}
function digitCondition(len: number): string {
return len == 2 ? '2-digit' : 'numeric';
}
function nameCondition(len: number): string {
return len < 4 ? 'short' : 'long';
}
function extractComponents(pattern: string): Intl.DateTimeFormatOptions {
var ret: Intl.DateTimeFormatOptions = {};
var i = 0, j;
while (i < pattern.length) {
j = i;
while (j < pattern.length && pattern[j] == pattern[i]) j++;
let len = j - i;
switch (pattern[i]) {
case 'G':
ret.era = nameCondition(len);
break;
case 'y':
ret.year = digitCondition(len);
break;
case 'M':
if (len >= 3)
ret.month = nameCondition(len);
else
ret.month = digitCondition(len);
break;
case 'd':
ret.day = digitCondition(len);
break;
case 'E':
ret.weekday = nameCondition(len);
break;
case 'j':
ret.hour = digitCondition(len);
break;
case 'h':
ret.hour = digitCondition(len);
ret.hour12 = true;
break;
case 'H':
ret.hour = digitCondition(len);
ret.hour12 = false;
break;
case 'm':
ret.minute = digitCondition(len);
break;
case 's':
ret.second = digitCondition(len);
break;
case 'z':
ret.timeZoneName = 'long';
break;
case 'Z':
ret.timeZoneName = 'short';
break;
}
i = j;
}
return ret;
}
var dateFormatterCache: Map<string, Intl.DateTimeFormat> = new Map<string, Intl.DateTimeFormat>();
export class DateFormatter {
static format(date: Date, locale: string, pattern: string): string {
var key = locale + pattern;
if (dateFormatterCache.has(key)) {
return dateFormatterCache.get(key).format(date);
}
var formatter = new Intl.DateTimeFormat(locale, extractComponents(pattern));
dateFormatterCache.set(key, formatter);
return formatter.format(date);
}
}

View File

@ -0,0 +1,297 @@
library angular.core.facade.lang;
export 'dart:core' show Type, RegExp, print, DateTime;
import 'dart:math' as math;
import 'dart:convert' as convert;
import 'dart:async' show Future;
String getTypeNameForDebugging(Type type) => type.toString();
class Math {
static final _random = new math.Random();
static int floor(num n) => n.floor();
static double random() => _random.nextDouble();
}
int ENUM_INDEX(value) => value.index;
class CONST {
const CONST();
}
class ABSTRACT {
const ABSTRACT();
}
class IMPLEMENTS {
final interfaceClass;
const IMPLEMENTS(this.interfaceClass);
}
bool isPresent(obj) => obj != null;
bool isBlank(obj) => obj == null;
bool isString(obj) => obj is String;
bool isFunction(obj) => obj is Function;
bool isType(obj) => obj is Type;
bool isStringMap(obj) => obj is Map;
bool isArray(obj) => obj is List;
bool isPromise(obj) => obj is Future;
bool isNumber(obj) => obj is num;
bool isDate(obj) => obj is DateTime;
String stringify(obj) => obj.toString();
int serializeEnum(val) {
return val.index;
}
/**
* Deserializes an enum
* val should be the indexed value of the enum (sa returned from @Link{serializeEnum})
* values should be a map from indexes to values for the enum that you want to deserialize.
*/
dynamic deserializeEnum(num val, Map<num, dynamic> values) {
return values[val];
}
class StringWrapper {
static String fromCharCode(int code) {
return new String.fromCharCode(code);
}
static int charCodeAt(String s, int index) {
return s.codeUnitAt(index);
}
static List<String> split(String s, RegExp regExp) {
var parts = <String>[];
var lastEnd = 0;
regExp.allMatches(s).forEach((match) {
parts.add(s.substring(lastEnd, match.start));
lastEnd = match.end;
for (var i = 0; i < match.groupCount; i++) {
parts.add(match.group(i + 1));
}
});
parts.add(s.substring(lastEnd));
return parts;
}
static bool equals(String s, String s2) {
return s == s2;
}
static String replace(String s, Pattern from, String replace) {
return s.replaceFirst(from, replace);
}
static String replaceAll(String s, RegExp from, String replace) {
return s.replaceAll(from, replace);
}
static String toUpperCase(String s) {
return s.toUpperCase();
}
static String toLowerCase(String s) {
return s.toLowerCase();
}
static startsWith(String s, String start) {
return s.startsWith(start);
}
static String substring(String s, int start, [int end]) {
return s.substring(start, end);
}
static String replaceAllMapped(String s, RegExp from, Function cb) {
return s.replaceAllMapped(from, cb);
}
static bool contains(String s, String substr) {
return s.contains(substr);
}
static int compare(String a, String b) => a.compareTo(b);
}
class StringJoiner {
final List<String> _parts = <String>[];
void add(String part) {
_parts.add(part);
}
String toString() => _parts.join("");
}
class NumberWrapper {
static String toFixed(num n, int fractionDigits) {
return n.toStringAsFixed(fractionDigits);
}
static bool equal(num a, num b) {
return a == b;
}
static int parseIntAutoRadix(String text) {
return int.parse(text);
}
static int parseInt(String text, int radix) {
return int.parse(text, radix: radix);
}
static double parseFloat(String text) {
return double.parse(text);
}
static double get NaN => double.NAN;
static bool isNaN(num value) => value.isNaN;
static bool isInteger(value) => value is int;
}
class RegExpWrapper {
static RegExp create(regExpStr, [String flags = '']) {
bool multiLine = flags.contains('m');
bool caseSensitive = !flags.contains('i');
return new RegExp(regExpStr,
multiLine: multiLine, caseSensitive: caseSensitive);
}
static Match firstMatch(RegExp regExp, String input) {
return regExp.firstMatch(input);
}
static bool test(RegExp regExp, String input) {
return regExp.hasMatch(input);
}
static Iterator<Match> matcher(RegExp regExp, String input) {
return regExp.allMatches(input).iterator;
}
}
class RegExpMatcherWrapper {
static _JSLikeMatch next(Iterator<Match> matcher) {
if (matcher.moveNext()) {
return new _JSLikeMatch(matcher.current);
}
return null;
}
}
class _JSLikeMatch {
Match _m;
_JSLikeMatch(this._m);
String operator [](index) => _m[index];
int get index => _m.start;
int get length => _m.groupCount + 1;
}
class FunctionWrapper {
static apply(Function fn, posArgs) {
return Function.apply(fn, posArgs);
}
}
class BaseException extends Error {
final dynamic context;
final String message;
final originalException;
final originalStack;
BaseException(
[this.message, this.originalException, this.originalStack, this.context]);
String toString() {
return this.message;
}
}
Error makeTypeError([String message = ""]) {
return new BaseException(message);
}
const _NAN_KEY = const Object();
// Dart can have identical(str1, str2) == false while str1 == str2. Moreover,
// after compiling with dart2js identical(str1, str2) might return true.
// (see dartbug.com/22496 for details).
bool looseIdentical(a, b) =>
a is String && b is String ? a == b : identical(a, b);
// Dart compare map keys by equality and we can have NaN != NaN
dynamic getMapKey(value) {
if (value is! num) return value;
return value.isNaN ? _NAN_KEY : value;
}
// TODO: remove with https://github.com/angular/angular/issues/3055
dynamic normalizeBlank(obj) => obj;
bool normalizeBool(bool obj) {
return isBlank(obj) ? false : obj;
}
bool isJsObject(o) {
return false;
}
var _assertionsEnabled = null;
bool assertionsEnabled() {
if (_assertionsEnabled == null) {
try {
assert(false);
_assertionsEnabled = false;
} catch (e) {
_assertionsEnabled = true;
}
}
return _assertionsEnabled;
}
// Can't be all uppercase as our transpiler would think it is a special directive...
class Json {
static parse(String s) => convert.JSON.decode(s);
static String stringify(data) {
var encoder = new convert.JsonEncoder.withIndent(" ");
return encoder.convert(data);
}
}
class DateWrapper {
static DateTime create(int year,
[int month = 1,
int day = 1,
int hour = 0,
int minutes = 0,
int seconds = 0,
int milliseconds = 0]) {
return new DateTime(year, month, day, hour, minutes, seconds, milliseconds);
}
static DateTime fromMillis(int ms) {
return new DateTime.fromMillisecondsSinceEpoch(ms, isUtc: true);
}
static int toMillis(DateTime date) {
return date.millisecondsSinceEpoch;
}
static DateTime now() {
return new DateTime.now();
}
static String toJson(DateTime date) {
return date.toUtc().toIso8601String();
}
}
// needed to match the exports from lang.js
var global = null;

View File

@ -0,0 +1,349 @@
/// <reference path="../../globals.d.ts" />
var _global: BrowserNodeGlobal = <any>(typeof window === 'undefined' ? global : window);
export {_global as global};
export var Type = Function;
/**
* Runtime representation of a type.
*
* In JavaScript a Type is a constructor function.
*/
export interface Type extends Function { new (...args): any; }
export function getTypeNameForDebugging(type: Type): string {
return type['name'];
}
export class BaseException extends Error {
stack;
constructor(public message?: string, private _originalException?, private _originalStack?,
private _context?) {
super(message);
this.stack = (<any>new Error(message)).stack;
}
get originalException(): any { return this._originalException; }
get originalStack(): any { return this._originalStack; }
get context(): any { return this._context; }
toString(): string { return this.message; }
}
export function makeTypeError(message?: string): Error {
return new TypeError(message);
}
export var Math = _global.Math;
export var Date = _global.Date;
var assertionsEnabled_ = typeof _global['assert'] !== 'undefined';
export function assertionsEnabled(): boolean {
return assertionsEnabled_;
}
// TODO: remove calls to assert in production environment
// Note: Can't just export this and import in in other files
// as `assert` is a reserved keyword in Dart
_global.assert = function assert(condition) {
if (assertionsEnabled_) {
_global['assert'].call(condition);
}
};
export function ENUM_INDEX(value: number): number {
return value;
}
// This function is needed only to properly support Dart's const expressions
// see https://github.com/angular/ts2dart/pull/151 for more info
export function CONST_EXPR<T>(expr: T): T {
return expr;
}
export function CONST(): ClassDecorator {
return (target) => target;
}
export function ABSTRACT(): ClassDecorator {
return (t) => t;
}
// Note: This is only a marker annotation needed for ts2dart.
// This is written so that is can be used as a Traceur annotation
// or a Typescript decorator.
export function IMPLEMENTS(_): ClassDecorator {
return (t) => t;
}
export function isPresent(obj: any): boolean {
return obj !== undefined && obj !== null;
}
export function isBlank(obj: any): boolean {
return obj === undefined || obj === null;
}
export function isString(obj: any): boolean {
return typeof obj === "string";
}
export function isFunction(obj: any): boolean {
return typeof obj === "function";
}
export function isType(obj: any): boolean {
return isFunction(obj);
}
export function isStringMap(obj: any): boolean {
return typeof obj === 'object' && obj !== null;
}
export function isPromise(obj: any): boolean {
return obj instanceof (<any>_global).Promise;
}
export function isArray(obj: any): boolean {
return Array.isArray(obj);
}
export function isNumber(obj): boolean {
return typeof obj === 'number';
}
export function isDate(obj): boolean {
return obj instanceof Date && !isNaN(obj.valueOf());
}
export function stringify(token): string {
if (typeof token === 'string') {
return token;
}
if (token === undefined || token === null) {
return '' + token;
}
if (token.name) {
return token.name;
}
var res = token.toString();
var newLineIndex = res.indexOf("\n");
return (newLineIndex === -1) ? res : res.substring(0, newLineIndex);
}
// serialize / deserialize enum exist only for consistency with dart API
// enums in typescript don't need to be serialized
export function serializeEnum(val): number {
return val;
}
export function deserializeEnum(val, values: Map<number, any>): any {
return val;
}
export class StringWrapper {
static fromCharCode(code: number): string { return String.fromCharCode(code); }
static charCodeAt(s: string, index: number): number { return s.charCodeAt(index); }
static split(s: string, regExp: RegExp): List<string> { return s.split(regExp); }
static equals(s: string, s2: string): boolean { return s === s2; }
static replace(s: string, from: string, replace: string): string {
return s.replace(from, replace);
}
static replaceAll(s: string, from: RegExp, replace: string): string {
return s.replace(from, replace);
}
static toUpperCase(s: string): string { return s.toUpperCase(); }
static toLowerCase(s: string): string { return s.toLowerCase(); }
static startsWith(s: string, start: string): boolean { return s.startsWith(start); }
static substring(s: string, start: number, end: number = null): string {
return s.substring(start, end === null ? undefined : end);
}
static replaceAllMapped(s: string, from: RegExp, cb: Function): string {
return s.replace(from, function(...matches) {
// Remove offset & string from the result array
matches.splice(-2, 2);
// The callback receives match, p1, ..., pn
return cb(matches);
});
}
static contains(s: string, substr: string): boolean { return s.indexOf(substr) != -1; }
static compare(a: string, b: string): number {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
}
}
export class StringJoiner {
constructor(public parts = []) {}
add(part: string): void { this.parts.push(part); }
toString(): string { return this.parts.join(""); }
}
export class NumberParseError extends BaseException {
name: string;
constructor(public message: string) { super(); }
toString(): string { return this.message; }
}
export class NumberWrapper {
static toFixed(n: number, fractionDigits: number): string { return n.toFixed(fractionDigits); }
static equal(a: number, b: number): boolean { return a === b; }
static parseIntAutoRadix(text: string): number {
var result: number = parseInt(text);
if (isNaN(result)) {
throw new NumberParseError("Invalid integer literal when parsing " + text);
}
return result;
}
static parseInt(text: string, radix: number): number {
if (radix == 10) {
if (/^(\-|\+)?[0-9]+$/.test(text)) {
return parseInt(text, radix);
}
} else if (radix == 16) {
if (/^(\-|\+)?[0-9ABCDEFabcdef]+$/.test(text)) {
return parseInt(text, radix);
}
} else {
var result: number = parseInt(text, radix);
if (!isNaN(result)) {
return result;
}
}
throw new NumberParseError("Invalid integer literal when parsing " + text + " in base " +
radix);
}
// TODO: NaN is a valid literal but is returned by parseFloat to indicate an error.
static parseFloat(text: string): number { return parseFloat(text); }
static get NaN(): number { return NaN; }
static isNaN(value: any): boolean { return isNaN(value); }
static isInteger(value: any): boolean { return Number.isInteger(value); }
}
export var RegExp = _global.RegExp;
export class RegExpWrapper {
static create(regExpStr: string, flags: string = ''): RegExp {
flags = flags.replace(/g/g, '');
return new _global.RegExp(regExpStr, flags + 'g');
}
static firstMatch(regExp: RegExp, input: string): List<string> {
// Reset multimatch regex state
regExp.lastIndex = 0;
return regExp.exec(input);
}
static test(regExp: RegExp, input: string): boolean {
regExp.lastIndex = 0;
return regExp.test(input);
}
static matcher(regExp: RegExp, input: string): {
re: RegExp;
input: string
}
{
// Reset regex state for the case
// someone did not loop over all matches
// last time.
regExp.lastIndex = 0;
return {re: regExp, input: input};
}
}
export class RegExpMatcherWrapper {
static next(matcher: {
re: RegExp;
input: string
}): string[] {
return matcher.re.exec(matcher.input);
}
}
export class FunctionWrapper {
static apply(fn: Function, posArgs: any): any { return fn.apply(null, posArgs); }
}
// JS has NaN !== NaN
export function looseIdentical(a, b): boolean {
return a === b || typeof a === "number" && typeof b === "number" && isNaN(a) && isNaN(b);
}
// JS considers NaN is the same as NaN for map Key (while NaN !== NaN otherwise)
// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
export function getMapKey<T>(value: T): T {
return value;
}
export function normalizeBlank(obj: Object): any {
return isBlank(obj) ? null : obj;
}
export function normalizeBool(obj: boolean): boolean {
return isBlank(obj) ? false : obj;
}
export function isJsObject(o: any): boolean {
return o !== null && (typeof o === "function" || typeof o === "object");
}
export function print(obj: Error | Object) {
if (obj instanceof BaseException) {
console.log(obj.stack);
} else {
console.log(obj);
}
}
// Can't be all uppercase as our transpiler would think it is a special directive...
export class Json {
static parse(s: string): Object { return _global.JSON.parse(s); }
static stringify(data: Object): string {
// Dart doesn't take 3 arguments
return _global.JSON.stringify(data, null, 2);
}
}
export class DateWrapper {
static create(year: number, month: number = 1, day: number = 1, hour: number = 0,
minutes: number = 0, seconds: number = 0, milliseconds: number = 0): Date {
return new Date(year, month - 1, day, hour, minutes, seconds, milliseconds);
}
static fromMillis(ms: number): Date { return new Date(ms); }
static toMillis(date: Date): number { return date.getTime(); }
static now(): Date { return new Date(); }
static toJson(date: Date): string { return date.toJSON(); }
}

View File

@ -0,0 +1,22 @@
library angular.core.facade.math;
import 'dart:core' show double, num;
import 'dart:math' as math;
const NaN = double.NAN;
class Math {
static num pow(num x, num exponent) {
return math.pow(x, exponent);
}
static num max(num a, num b) => math.max(a, b);
static num min(num a, num b) => math.min(a, b);
static num floor(num a) => a.floor();
static num ceil(num a) => a.ceil();
static num sqrt(num x) => math.sqrt(x);
}

View File

@ -0,0 +1,4 @@
import {global} from 'angular2/src/facade/lang';
export var Math = global.Math;
export var NaN = typeof NaN;

View File

@ -0,0 +1,130 @@
import {isBlank, isPresent, isPromise, CONST, BaseException} from 'angular2/src/facade/lang';
import {Observable, Promise, ObservableWrapper} from 'angular2/src/facade/async';
import {Injectable} from 'angular2/di';
import {PipeTransform, PipeOnDestroy, WrappedValue} from 'angular2/change_detection';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
import {ChangeDetectorRef} from 'angular2/change_detection';
import {Pipe} from '../core/metadata';
class ObservableStrategy {
createSubscription(async: any, updateLatestValue: any): any {
return ObservableWrapper.subscribe(async, updateLatestValue, e => { throw e; });
}
dispose(subscription: any): void { ObservableWrapper.dispose(subscription); }
onDestroy(subscription: any): void { ObservableWrapper.dispose(subscription); }
}
class PromiseStrategy {
createSubscription(async: any, updateLatestValue: any): any {
return async.then(updateLatestValue);
}
dispose(subscription: any): void {}
onDestroy(subscription: any): void {}
}
var _promiseStrategy = new PromiseStrategy();
var _observableStrategy = new ObservableStrategy();
/**
* Implements async bindings to Observable and Promise.
*
* # Example
*
* In this example we bind the description observable to the DOM. The async pipe will convert an
*observable to the
* latest value it emitted. It will also request a change detection check when a new value is
*emitted.
*
* ```
* @Component({
* selector: "task-cmp",
* changeDetection: ON_PUSH
* })
* @View({
* template: "Task Description {{ description | async }}"
* })
* class Task {
* description:Observable<string>;
* }
*
* ```
*/
@Pipe({name: 'async'})
@Injectable()
export class AsyncPipe implements PipeTransform, PipeOnDestroy {
_latestValue: Object = null;
_latestReturnedValue: Object = null;
_subscription: Object = null;
_obj: Observable | Promise<any> = null;
private _strategy: any = null;
constructor(public _ref: ChangeDetectorRef) {}
onDestroy(): void {
if (isPresent(this._subscription)) {
this._dispose();
}
}
transform(obj: Observable | Promise<any>, args?: any[]): any {
if (isBlank(this._obj)) {
if (isPresent(obj)) {
this._subscribe(obj);
}
return null;
}
if (obj !== this._obj) {
this._dispose();
return this.transform(obj);
}
if (this._latestValue === this._latestReturnedValue) {
return this._latestReturnedValue;
} else {
this._latestReturnedValue = this._latestValue;
return WrappedValue.wrap(this._latestValue);
}
}
_subscribe(obj: Observable | Promise<any>): void {
this._obj = obj;
this._strategy = this._selectStrategy(obj);
this._subscription =
this._strategy.createSubscription(obj, value => this._updateLatestValue(obj, value));
}
_selectStrategy(obj: Observable | Promise<any>): any {
if (isPromise(obj)) {
return _promiseStrategy;
} else if (ObservableWrapper.isObservable(obj)) {
return _observableStrategy;
} else {
throw new InvalidPipeArgumentException(AsyncPipe, obj);
}
}
_dispose(): void {
this._strategy.dispose(this._subscription);
this._latestValue = null;
this._latestReturnedValue = null;
this._subscription = null;
this._obj = null;
}
_updateLatestValue(async: any, value: Object) {
if (async === this._obj) {
this._latestValue = value;
this._ref.requestCheck();
}
}
}

View File

@ -0,0 +1,114 @@
import {
isDate,
isNumber,
isPresent,
Date,
DateWrapper,
CONST,
isBlank,
FunctionWrapper
} from 'angular2/src/facade/lang';
import {DateFormatter} from 'angular2/src/facade/intl';
import {Injectable} from 'angular2/di';
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
import {PipeTransform, WrappedValue} from 'angular2/change_detection';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
import {Pipe} from '../core/metadata';
// TODO: move to a global configable location along with other i18n components.
var defaultLocale: string = 'en-US';
/**
* WARNING: this pipe uses the Internationalization API.
* Therefore it is only reliable in Chrome and Opera browsers.
*
* Formats a date value to a string based on the requested format.
*
* # Usage
*
* expression | date[:format]
*
* where `expression` is a date object or a number (milliseconds since UTC epoch) and
* `format` indicates which date/time components to include:
*
* | Component | Symbol | Short Form | Long Form | Numeric | 2-digit |
* |-----------|:------:|--------------|-------------------|-----------|-----------|
* | era | G | G (AD) | GGGG (Anno Domini)| - | - |
* | year | y | - | - | y (2015) | yy (15) |
* | month | M | MMM (Sep) | MMMM (September) | M (9) | MM (09) |
* | day | d | - | - | d (3) | dd (03) |
* | weekday | E | EEE (Sun) | EEEE (Sunday) | - | - |
* | hour | j | - | - | j (13) | jj (13) |
* | hour12 | h | - | - | h (1 PM) | hh (01 PM)|
* | hour24 | H | - | - | H (13) | HH (13) |
* | minute | m | - | - | m (5) | mm (05) |
* | second | s | - | - | s (9) | ss (09) |
* | timezone | z | - | z (Pacific Standard Time)| - | - |
* | timezone | Z | Z (GMT-8:00) | - | - | - |
*
* In javascript, only the components specified will be respected (not the ordering,
* punctuations, ...) and details of the the formatting will be dependent on the locale.
* On the other hand in Dart version, you can also include quoted text as well as some extra
* date/time components such as quarter. For more information see:
* https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/intl/intl.DateFormat.
*
* `format` can also be one of the following predefined formats:
*
* - `'medium'`: equivalent to `'yMMMdjms'` (e.g. Sep 3, 2010, 12:05:08 PM for en-US)
* - `'short'`: equivalent to `'yMdjm'` (e.g. 9/3/2010, 12:05 PM for en-US)
* - `'fullDate'`: equivalent to `'yMMMMEEEEd'` (e.g. Friday, September 3, 2010 for en-US)
* - `'longDate'`: equivalent to `'yMMMMd'` (e.g. September 3, 2010)
* - `'mediumDate'`: equivalent to `'yMMMd'` (e.g. Sep 3, 2010 for en-US)
* - `'shortDate'`: equivalent to `'yMd'` (e.g. 9/3/2010 for en-US)
* - `'mediumTime'`: equivalent to `'jms'` (e.g. 12:05:08 PM for en-US)
* - `'shortTime'`: equivalent to `'jm'` (e.g. 12:05 PM for en-US)
*
* Timezone of the formatted text will be the local system timezone of the end-users machine.
*
* # Examples
*
* Assuming `dateObj` is (year: 2015, month: 6, day: 15, hour: 21, minute: 43, second: 11)
* in the _local_ time and locale is 'en-US':
*
* {{ dateObj | date }} // output is 'Jun 15, 2015'
* {{ dateObj | date:'medium' }} // output is 'Jun 15, 2015, 9:43:11 PM'
* {{ dateObj | date:'shortTime' }} // output is '9:43 PM'
* {{ dateObj | date:'mmss' }} // output is '43:11'
*/
@CONST()
@Pipe({name: 'date'})
@Injectable()
export class DatePipe implements PipeTransform {
static _ALIASES = {
'medium': 'yMMMdjms',
'short': 'yMdjm',
'fullDate': 'yMMMMEEEEd',
'longDate': 'yMMMMd',
'mediumDate': 'yMMMd',
'shortDate': 'yMd',
'mediumTime': 'jms',
'shortTime': 'jm'
};
transform(value: any, args: List<any>): string {
if (isBlank(value)) return null;
if (!this.supports(value)) {
throw new InvalidPipeArgumentException(DatePipe, value);
}
var pattern: string = isPresent(args) && args.length > 0 ? args[0] : 'mediumDate';
if (isNumber(value)) {
value = DateWrapper.fromMillis(value);
}
if (StringMapWrapper.contains(DatePipe._ALIASES, pattern)) {
pattern = <string>StringMapWrapper.get(DatePipe._ALIASES, pattern);
}
return DateFormatter.format(value, defaultLocale, pattern);
}
private supports(obj: any): boolean { return isDate(obj) || isNumber(obj); }
}

View File

@ -0,0 +1,27 @@
import {AsyncPipe} from './async_pipe';
import {UpperCasePipe} from './uppercase_pipe';
import {LowerCasePipe} from './lowercase_pipe';
import {JsonPipe} from './json_pipe';
import {LimitToPipe} from './limit_to_pipe';
import {DatePipe} from './date_pipe';
import {DecimalPipe, PercentPipe, CurrencyPipe} from './number_pipe';
import {CONST_EXPR} from 'angular2/src/facade/lang';
import {Binding, OpaqueToken} from 'angular2/di';
const DEFAULT_PIPES_LIST = CONST_EXPR([
AsyncPipe,
UpperCasePipe,
LowerCasePipe,
JsonPipe,
LimitToPipe,
DecimalPipe,
PercentPipe,
CurrencyPipe,
DatePipe
]);
export const DEFAULT_PIPES_TOKEN = CONST_EXPR(new OpaqueToken("Default Pipes"));
export const DEFAULT_PIPES =
CONST_EXPR(new Binding(DEFAULT_PIPES_TOKEN, {toValue: DEFAULT_PIPES_LIST}));

View File

@ -0,0 +1,7 @@
import {ABSTRACT, BaseException, CONST, Type} from 'angular2/src/facade/lang';
export class InvalidPipeArgumentException extends BaseException {
constructor(type: Type, value: Object) {
super(`Invalid argument '${value}' for pipe '${type}'`);
}
}

View File

@ -0,0 +1,36 @@
import {isBlank, isPresent, Json, CONST} from 'angular2/src/facade/lang';
import {Injectable} from 'angular2/di';
import {PipeTransform, WrappedValue} from 'angular2/change_detection';
import {Pipe} from '../core/metadata';
/**
* Implements json transforms to any object.
*
* # Example
*
* In this example we transform the user object to json.
*
* ```
* @Component({
* selector: "user-cmp"
* })
* @View({
* template: "User: {{ user | json }}"
* })
* class Username {
* user:Object
* constructor() {
* this.user = { name: "PatrickJS" };
* }
* }
*
* ```
*/
@CONST()
@Pipe({name: 'json'})
@Injectable()
export class JsonPipe implements PipeTransform {
transform(value: any, args: List<any> = null): string { return Json.stringify(value); }
}

View File

@ -0,0 +1,81 @@
import {
isBlank,
isString,
isArray,
StringWrapper,
BaseException,
CONST
} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {Math} from 'angular2/src/facade/math';
import {Injectable} from 'angular2/di';
import {PipeTransform, WrappedValue} from 'angular2/change_detection';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
import {Pipe} from '../core/metadata';
/**
* Creates a new List or String containing only a prefix/suffix of the
* elements.
*
* The number of elements to return is specified by the `limitTo` parameter.
*
* # Usage
*
* expression | limitTo:number
*
* Where the input expression is a [List] or [String], and `limitTo` is:
*
* - **a positive integer**: return _number_ items from the beginning of the list or string
* expression.
* - **a negative integer**: return _number_ items from the end of the list or string expression.
* - **`|limitTo|` greater than the size of the expression**: return the entire expression.
*
* When operating on a [List], the returned list is always a copy even when all
* the elements are being returned.
*
* # Examples
*
* ## List Example
*
* Assuming `var collection = ['a', 'b', 'c']`, this `ng-for` directive:
*
* <li *ng-for="var i in collection | limitTo:2">{{i}}</li>
*
* produces the following:
*
* <li>a</li>
* <li>b</li>
*
* ## String Examples
*
* {{ 'abcdefghij' | limitTo: 4 }} // output is 'abcd'
* {{ 'abcdefghij' | limitTo: -4 }} // output is 'ghij'
* {{ 'abcdefghij' | limitTo: -100 }} // output is 'abcdefghij'
*/
@Pipe({name: 'limitTo'})
@Injectable()
export class LimitToPipe implements PipeTransform {
supports(obj: any): boolean { return isString(obj) || isArray(obj); }
transform(value: any, args: List<any> = null): any {
if (isBlank(args) || args.length == 0) {
throw new BaseException('limitTo pipe requires one argument');
}
if (!this.supports(value)) {
throw new InvalidPipeArgumentException(LimitToPipe, value);
}
if (isBlank(value)) return value;
var limit: number = args[0];
var left = 0, right = Math.min(limit, value.length);
if (limit < 0) {
left = Math.max(0, value.length + limit);
right = value.length;
}
if (isString(value)) {
return StringWrapper.substring(value, left, right);
}
return ListWrapper.slice(value, left, right);
}
}

View File

@ -0,0 +1,41 @@
import {isString, StringWrapper, CONST, isBlank} from 'angular2/src/facade/lang';
import {Injectable} from 'angular2/di';
import {PipeTransform, WrappedValue} from 'angular2/change_detection';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
import {Pipe} from '../core/metadata';
/**
* Implements lowercase transforms to text.
*
* # Example
*
* In this example we transform the user text lowercase.
*
* ```
* @Component({
* selector: "username-cmp"
* })
* @View({
* template: "Username: {{ user | lowercase }}"
* })
* class Username {
* user:string;
* }
*
* ```
*/
@CONST()
@Pipe({name: 'lowercase'})
@Injectable()
export class LowerCasePipe implements PipeTransform {
transform(value: string, args: List<any> = null): string {
if (isBlank(value)) return value;
if (!isString(value)) {
throw new InvalidPipeArgumentException(LowerCasePipe, value);
}
return StringWrapper.toLowerCase(value);
}
}

View File

@ -0,0 +1,146 @@
import {
isNumber,
isPresent,
isBlank,
StringWrapper,
NumberWrapper,
RegExpWrapper,
BaseException,
CONST,
FunctionWrapper
} from 'angular2/src/facade/lang';
import {NumberFormatter, NumberFormatStyle} from 'angular2/src/facade/intl';
import {Injectable} from 'angular2/di';
import {ListWrapper} from 'angular2/src/facade/collection';
import {PipeTransform, WrappedValue} from 'angular2/change_detection';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
import {Pipe} from '../core/metadata';
var defaultLocale: string = 'en-US';
var _re = RegExpWrapper.create('^(\\d+)?\\.((\\d+)(\\-(\\d+))?)?$');
@CONST()
@Injectable()
export class NumberPipe {
static _format(value: number, style: NumberFormatStyle, digits: string, currency: string = null,
currencyAsSymbol: boolean = false): string {
if (isBlank(value)) return null;
if (!isNumber(value)) {
throw new InvalidPipeArgumentException(NumberPipe, value);
}
var minInt = 1, minFraction = 0, maxFraction = 3;
if (isPresent(digits)) {
var parts = RegExpWrapper.firstMatch(_re, digits);
if (isBlank(parts)) {
throw new BaseException(`${digits} is not a valid digit info for number pipes`);
}
if (isPresent(parts[1])) { // min integer digits
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
}
if (isPresent(parts[3])) { // min fraction digits
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
}
if (isPresent(parts[5])) { // max fraction digits
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
}
}
return NumberFormatter.format(value, defaultLocale, style, {
minimumIntegerDigits: minInt,
minimumFractionDigits: minFraction,
maximumFractionDigits: maxFraction,
currency: currency,
currencyAsSymbol: currencyAsSymbol
});
}
}
/**
* WARNING: this pipe uses the Internationalization API.
* Therefore it is only reliable in Chrome and Opera browsers.
*
* Formats a number as local text. i.e. group sizing and seperator and other locale-specific
* configurations are based on the active locale.
*
* # Usage
*
* expression | number[:digitInfo]
*
* where `expression` is a number and `digitInfo` has the following format:
*
* {minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
*
* - minIntegerDigits is the minimum number of integer digits to use. Defaults to 1.
* - minFractionDigits is the minimum number of digits after fraction. Defaults to 0.
* - maxFractionDigits is the maximum number of digits after fraction. Defaults to 3.
*
* For more information on the acceptable range for each of these numbers and other
* details see your native internationalization library.
*
* # Examples
*
* {{ 123 | number }} // output is 123
* {{ 123.1 | number: '.2-3' }} // output is 123.10
* {{ 1 | number: '2.2' }} // output is 01.00
*/
@CONST()
@Pipe({name: 'number'})
@Injectable()
export class DecimalPipe extends NumberPipe implements PipeTransform {
transform(value: any, args: any[]): string {
var digits: string = ListWrapper.first(args);
return NumberPipe._format(value, NumberFormatStyle.DECIMAL, digits);
}
}
/**
* WARNING: this pipe uses the Internationalization API.
* Therefore it is only reliable in Chrome and Opera browsers.
*
* Formats a number as local percent.
*
* # Usage
*
* expression | percent[:digitInfo]
*
* For more information about `digitInfo` see {@link DecimalPipe}
*/
@CONST()
@Pipe({name: 'percent'})
@Injectable()
export class PercentPipe extends NumberPipe implements PipeTransform {
transform(value: any, args: any[]): string {
var digits: string = ListWrapper.first(args);
return NumberPipe._format(value, NumberFormatStyle.PERCENT, digits);
}
}
/**
* WARNING: this pipe uses the Internationalization API.
* Therefore it is only reliable in Chrome and Opera browsers.
*
* Formats a number as local currency.
*
* # Usage
*
* expression | currency[:currencyCode[:symbolDisplay[:digitInfo]]]
*
* where `currencyCode` is the ISO 4217 currency code, such as "USD" for the US dollar and
* "EUR" for the euro. `symbolDisplay` is a boolean indicating whether to use the currency
* symbol (e.g. $) or the currency code (e.g. USD) in the output. The default for this value
* is `false`.
* For more information about `digitInfo` see {@link DecimalPipe}
*/
@CONST()
@Pipe({name: 'currency'})
@Injectable()
export class CurrencyPipe extends NumberPipe implements PipeTransform {
transform(value: any, args: any[]): string {
var currencyCode: string = isPresent(args) && args.length > 0 ? args[0] : 'USD';
var symbolDisplay: boolean = isPresent(args) && args.length > 1 ? args[1] : false;
var digits: string = isPresent(args) && args.length > 2 ? args[2] : null;
return NumberPipe._format(value, NumberFormatStyle.CURRENCY, digits, currencyCode,
symbolDisplay);
}
}

View File

@ -0,0 +1,40 @@
import {isString, StringWrapper, CONST, isBlank} from 'angular2/src/facade/lang';
import {Injectable} from 'angular2/di';
import {PipeTransform, WrappedValue} from 'angular2/change_detection';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
import {Pipe} from '../core/metadata';
/**
* Implements uppercase transforms to text.
*
* # Example
*
* In this example we transform the user text uppercase.
*
* ```
* @Component({
* selector: "username-cmp"
* })
* @View({
* template: "Username: {{ user | uppercase }}"
* })
* class Username {
* user:string;
* }
*
* ```
*/
@CONST()
@Pipe({name: 'uppercase'})
@Injectable()
export class UpperCasePipe implements PipeTransform {
transform(value: string, args: List<any> = null): string {
if (isBlank(value)) return value;
if (!isString(value)) {
throw new InvalidPipeArgumentException(UpperCasePipe, value);
}
return StringWrapper.toUpperCase(value);
}
}

View File

@ -0,0 +1,80 @@
export {WtfScopeFn} from './wtf_impl';
import * as impl from "./wtf_impl";
// Change exports to const once https://github.com/angular/ts2dart/issues/150
/**
* True if WTF is enabled.
*/
export var wtfEnabled = impl.detectWTF();
function noopScope(arg0?: any, arg1?: any): any {
return null;
}
/**
* Create trace scope.
*
* Scopes must be strictly nested and are analogous to stack frames, but
* do not have to follow the stack frames. Instead it is recommended that they follow logical
* nesting. You may want to use
* [Event
* Signatures](http://google.github.io/tracing-framework/instrumenting-code.html#custom-events)
* as they are defined in WTF.
*
* Used to mark scope entry. The return value is used to leave the scope.
*
* final myScope = wtfCreateScope('MyClass#myMethod(ascii someVal)');
*
* someMethod() {
* var s = myScope('Foo'); // 'Foo' gets stored in tracing UI
* // DO SOME WORK HERE
* return wtfLeave(s, 123); // Return value 123
* }
*
* Note, adding try-finally block around the work to ensure that `wtfLeave` gets called can
* negatively impact the performance of your application. For this reason we recommend that
* you don't add them to ensure that `wtfLeave` gets called. In production `wtfLeave` is a noop and
* so try-finally block has no value. When debugging perf issues, skipping `wtfLeave`, do to
* exception, will produce incorrect trace, but presence of exception signifies logic error which
* needs to be fixed before the app should be profiled. Add try-finally only when you expect that
* an exception is expected during normal execution while profiling.
*
*/
export var wtfCreateScope: (signature: string, flags?: any) => impl.WtfScopeFn =
wtfEnabled ? impl.createScope : (signature: string, flags?: any) => noopScope;
/**
* Used to mark end of Scope.
*
* - `scope` to end.
* - `returnValue` (optional) to be passed to the WTF.
*
* Returns the `returnValue for easy chaining.
*/
export var wtfLeave:<T>(scope: any, returnValue?: T) => T =
wtfEnabled ? impl.leave : (s: any, r?: any) => r;
/**
* Used to mark Async start. Async are similar to scope but they don't have to be strictly nested.
* The return value is used in the call to [endAsync]. Async ranges only work if WTF has been
* enabled.
*
* someMethod() {
* var s = wtfStartTimeRange('HTTP:GET', 'some.url');
* var future = new Future.delay(5).then((_) {
* wtfEndTimeRange(s);
* });
* }
*/
export var wtfStartTimeRange: (rangeType: string, action: string) => any =
wtfEnabled ? impl.startTimeRange : (rangeType: string, action: string) => null;
/**
* Ends a async time range operation.
* [range] is the return value from [wtfStartTimeRange] Async ranges only work if WTF has been
* enabled.
*/
export var wtfEndTimeRange: (range: any) => void = wtfEnabled ? impl.endTimeRange : (r: any) =>
null;

View File

@ -0,0 +1,96 @@
/**
* Tracing for Dart applications.
*
* The tracing API hooks up to either [WTF](http://google.github.io/tracing-framework/) or
* [Dart Observatory](https://www.dartlang.org/tools/observatory/).
*/
library angular2.src.core.wtf_impl;
typedef dynamic WtfScopeFn([arg0, arg1]);
var context = null;
var _trace;
var _events;
var _createScope;
var _leaveScope;
var _beginTimeRange;
var _endTimeRange;
final List _arg1 = [null];
final List _arg2 = [null, null];
bool detectWTF() {
if (context != null && context.hasProperty('wtf')) {
var wtf = context['wtf'];
if (wtf.hasProperty('trace')) {
_trace = wtf['trace'];
_events = _trace['events'];
_createScope = _events['createScope'];
_leaveScope = _trace['leaveScope'];
_beginTimeRange = _trace['beginTimeRange'];
_endTimeRange = _trace['endTimeRange'];
return true;
}
}
return false;
}
int getArgSize(String signature) {
int start = signature.indexOf('(') + 1;
int end = signature.indexOf(')', start);
bool found = false;
int count = 0;
for (var i = start; i < end; i++) {
var ch = signature[i];
if (identical(ch, ',')) {
found = false;
}
if (!found) {
found = true;
count++;
}
}
return count;
}
dynamic createScope(String signature, [flags]) {
_arg2[0] = signature;
_arg2[1] = flags;
var jsScope = _createScope.apply(_arg2, thisArg: _events);
switch (getArgSize(signature)) {
case 0:
return ([arg0, arg1]) {
return jsScope.apply(const []);
};
case 1:
return ([arg0, arg1]) {
_arg1[0] = arg0;
return jsScope.apply(_arg1);
};
case 2:
return ([arg0, arg1]) {
_arg2[0] = arg0;
_arg2[1] = arg1;
return jsScope.apply(_arg2);
};
default:
throw "Max 2 arguments are supported.";
}
}
void leave(scope, [returnValue]) {
_arg2[0] = scope;
_arg2[1] = returnValue;
_leaveScope.apply(_arg2, thisArg: _trace);
return returnValue;
}
dynamic startTimeRange(String rangeType, String action) {
_arg2[0] = rangeType;
_arg2[1] = action;
return _beginTimeRange.apply(_arg2, thisArg: _trace);
}
void endTimeRange(dynamic range) {
_arg1[0] = range;
_endTimeRange.apply(_arg1, thisArg: _trace);
}

View File

@ -0,0 +1,56 @@
import {global} from '../facade/lang';
export interface WtfScopeFn { (arg0?: any, arg1?: any): any; }
interface WTF {
trace: Trace;
}
interface Trace {
events: Events;
leaveScope(scope: Scope, returnValue: any);
beginTimeRange(rangeType: string, action: string): Range;
endTimeRange(range: Range);
}
interface Range {}
interface Events {
createScope(signature: string, flags: any): Scope;
}
interface Scope {
(...args): any;
}
var trace: Trace;
var events: Events;
export function detectWTF(): boolean {
var wtf: WTF = global['wtf'];
if (wtf) {
trace = wtf['trace'];
if (trace) {
events = trace['events'];
return true;
}
}
return false;
}
export function createScope(signature: string, flags: any = null): any {
return events.createScope(signature, flags);
}
export function leave<T>(scope: Scope, returnValue?: T): T {
trace.leaveScope(scope, returnValue);
return returnValue;
}
export function startTimeRange(rangeType: string, action: string): Range {
return trace.beginTimeRange(rangeType, action);
}
export function endTimeRange(range: Range): void {
trace.endTimeRange(range);
}

View File

@ -0,0 +1,14 @@
library angular2.src.core.wtf_init;
import 'dart:js' as js;
import 'wtf_impl.dart' as impl;
/**
* Must be executed explicitly in Dart to set the JS Context.
*
* NOTE: this is done explicitly to allow WTF api not to depend on
* JS context and possible to run the noop WTF stubs outside the browser.
*/
wtfInit() {
impl.context = js.context;
}

View File

@ -0,0 +1,4 @@
/**
* This is here because DART requires it. It is noop in JS.
*/
export function wtfInit() {}

View File

@ -0,0 +1,58 @@
library reflection.debug_reflection_capabilities;
import 'dart:mirrors';
import 'package:logging/logging.dart' as log;
import 'package:stack_trace/stack_trace.dart';
import 'types.dart';
import 'reflection_capabilities.dart' as standard;
class ReflectionCapabilities extends standard.ReflectionCapabilities {
final bool _verbose;
final log.Logger _log = new log.Logger('ReflectionCapabilities');
ReflectionCapabilities({bool verbose: false})
: _verbose = verbose,
super() {
log.hierarchicalLoggingEnabled = true;
_log.level = _verbose ? log.Level.ALL : log.Level.INFO;
_log.onRecord.listen((log.LogRecord rec) {
print('[${rec.loggerName}(${rec.level.name})]: ${rec.message}');
});
}
void _notify(String methodName, param) {
var trace = _verbose ? ' ${Trace.format(new Trace.current())}' : '';
_log.info('"$methodName" requested for "$param".$trace');
}
Function factory(Type type) {
ClassMirror classMirror = reflectType(type);
_notify('factory', '${classMirror.qualifiedName}');
return super.factory(type);
}
List<List> parameters(typeOrFunc) {
_notify('parameters', typeOrFunc);
return super.parameters(typeOrFunc);
}
List annotations(typeOrFunc) {
_notify('annotations', typeOrFunc);
return super.annotations(typeOrFunc);
}
GetterFn getter(String name) {
_notify('getter', name);
return super.getter(name);
}
SetterFn setter(String name) {
_notify('setter', name);
return super.setter(name);
}
MethodFn method(String name) {
_notify('method', name);
return super.method(name);
}
}

View File

@ -0,0 +1,14 @@
import {Type} from 'angular2/src/facade/lang';
import {GetterFn, SetterFn, MethodFn} from './types';
import {List} from 'angular2/src/facade/collection';
export interface PlatformReflectionCapabilities {
isReflectionEnabled(): boolean;
factory(type: Type): Function;
interfaces(type: Type): List<any>;
parameters(type: Type): List<List<any>>;
annotations(type: Type): List<any>;
getter(name: string): GetterFn;
setter(name: string): SetterFn;
method(name: string): MethodFn;
}

View File

@ -0,0 +1,43 @@
library reflection.reflection;
import 'reflector.dart';
import 'types.dart';
export 'reflector.dart';
import 'platform_reflection_capabilities.dart';
import 'package:angular2/src/facade/lang.dart';
class NoReflectionCapabilities implements PlatformReflectionCapabilities {
bool isReflectionEnabled() {
return false;
}
Function factory(Type type) {
throw "Cannot find reflection information on ${stringify(type)}";
}
List interfaces(Type type) {
throw "Cannot find reflection information on ${stringify(type)}";
}
List parameters(Type type) {
throw "Cannot find reflection information on ${stringify(type)}";
}
List annotations(Type type) {
throw "Cannot find reflection information on ${stringify(type)}";
}
GetterFn getter(String name) {
throw "Cannot find getter ${name}";
}
SetterFn setter(String name) {
throw "Cannot find setter ${name}";
}
MethodFn method(String name) {
throw "Cannot find method ${name}";
}
}
final Reflector reflector = new Reflector(new NoReflectionCapabilities());

View File

@ -0,0 +1,7 @@
import {Type, isPresent} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {Reflector} from './reflector';
export {Reflector, ReflectionInfo} from './reflector';
import {ReflectionCapabilities} from './reflection_capabilities';
export var reflector = new Reflector(new ReflectionCapabilities());

View File

@ -0,0 +1,300 @@
library reflection.reflection_capabilities;
import 'package:angular2/src/facade/lang.dart';
import 'types.dart';
import 'dart:mirrors';
import 'platform_reflection_capabilities.dart';
class ReflectionCapabilities implements PlatformReflectionCapabilities {
ReflectionCapabilities([metadataReader]) {}
bool isReflectionEnabled() {
return true;
}
Function factory(Type type) {
ClassMirror classMirror = reflectType(type);
MethodMirror ctor = classMirror.declarations[classMirror.simpleName];
Function create = classMirror.newInstance;
Symbol name = ctor.constructorName;
int length = ctor.parameters.length;
switch (length) {
case 0:
return () => create(name, []).reflectee;
case 1:
return (a1) => create(name, [a1]).reflectee;
case 2:
return (a1, a2) => create(name, [a1, a2]).reflectee;
case 3:
return (a1, a2, a3) => create(name, [a1, a2, a3]).reflectee;
case 4:
return (a1, a2, a3, a4) => create(name, [a1, a2, a3, a4]).reflectee;
case 5:
return (a1, a2, a3, a4, a5) =>
create(name, [a1, a2, a3, a4, a5]).reflectee;
case 6:
return (a1, a2, a3, a4, a5, a6) =>
create(name, [a1, a2, a3, a4, a5, a6]).reflectee;
case 7:
return (a1, a2, a3, a4, a5, a6, a7) =>
create(name, [a1, a2, a3, a4, a5, a6, a7]).reflectee;
case 8:
return (a1, a2, a3, a4, a5, a6, a7, a8) =>
create(name, [a1, a2, a3, a4, a5, a6, a7, a8]).reflectee;
case 9:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9) =>
create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9]).reflectee;
case 10:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) =>
create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]).reflectee;
case 11:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) =>
create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11])
.reflectee;
case 12:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) =>
create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12])
.reflectee;
case 13:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) =>
create(name, [
a1,
a2,
a3,
a4,
a5,
a6,
a7,
a8,
a9,
a10,
a11,
a12,
a13
]).reflectee;
case 14:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) =>
create(name, [
a1,
a2,
a3,
a4,
a5,
a6,
a7,
a8,
a9,
a10,
a11,
a12,
a13,
a14
]).reflectee;
case 15:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14,
a15) =>
create(name, [
a1,
a2,
a3,
a4,
a5,
a6,
a7,
a8,
a9,
a10,
a11,
a12,
a13,
a14,
a15
]).reflectee;
case 16:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14,
a15, a16) =>
create(name, [
a1,
a2,
a3,
a4,
a5,
a6,
a7,
a8,
a9,
a10,
a11,
a12,
a13,
a14,
a15,
a16
]).reflectee;
case 17:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14,
a15, a16, a17) =>
create(name, [
a1,
a2,
a3,
a4,
a5,
a6,
a7,
a8,
a9,
a10,
a11,
a12,
a13,
a14,
a15,
a16,
a17
]).reflectee;
case 18:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14,
a15, a16, a17, a18) =>
create(name, [
a1,
a2,
a3,
a4,
a5,
a6,
a7,
a8,
a9,
a10,
a11,
a12,
a13,
a14,
a15,
a16,
a17,
a18
]).reflectee;
case 19:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14,
a15, a16, a17, a18, a19) =>
create(name, [
a1,
a2,
a3,
a4,
a5,
a6,
a7,
a8,
a9,
a10,
a11,
a12,
a13,
a14,
a15,
a16,
a17,
a18,
a19
]).reflectee;
case 20:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14,
a15, a16, a17, a18, a19, a20) =>
create(name, [
a1,
a2,
a3,
a4,
a5,
a6,
a7,
a8,
a9,
a10,
a11,
a12,
a13,
a14,
a15,
a16,
a17,
a18,
a19,
a20
]).reflectee;
}
throw "Cannot create a factory for '${stringify(type)}' because its constructor has more than 20 arguments";
}
List<List> parameters(typeOrFunc) {
final parameters = typeOrFunc is Type
? _constructorParameters(typeOrFunc)
: _functionParameters(typeOrFunc);
return parameters.map(_convertParameter).toList();
}
List _convertParameter(ParameterMirror p) {
var t = p.type;
var res = (!t.hasReflectedType || t.reflectedType == dynamic)
? []
: [t.reflectedType];
res.addAll(p.metadata.map((m) => m.reflectee));
return res;
}
List annotations(typeOrFunc) {
final meta = typeOrFunc is Type
? _constructorMetadata(typeOrFunc)
: _functionMetadata(typeOrFunc);
return meta.map((m) => m.reflectee).toList();
}
List interfaces(type) {
ClassMirror classMirror = reflectType(type);
return classMirror.superinterfaces.map((si) => si.reflectedType).toList();
}
GetterFn getter(String name) {
var symbol = new Symbol(name);
return (receiver) => reflect(receiver).getField(symbol).reflectee;
}
SetterFn setter(String name) {
var symbol = new Symbol(name);
return (receiver, value) =>
reflect(receiver).setField(symbol, value).reflectee;
}
MethodFn method(String name) {
var symbol = new Symbol(name);
return (receiver, posArgs) =>
reflect(receiver).invoke(symbol, posArgs).reflectee;
}
List _functionParameters(Function func) {
var closureMirror = reflect(func);
return closureMirror.function.parameters;
}
List _constructorParameters(Type type) {
ClassMirror classMirror = reflectType(type);
MethodMirror ctor = classMirror.declarations[classMirror.simpleName];
return ctor.parameters;
}
List _functionMetadata(Function func) {
var closureMirror = reflect(func);
return closureMirror.function.metadata;
}
List _constructorMetadata(Type type) {
ClassMirror classMirror = reflectType(type);
return classMirror.metadata;
}
}

View File

@ -0,0 +1,158 @@
import {
Type,
isPresent,
isFunction,
global,
stringify,
BaseException
} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {GetterFn, SetterFn, MethodFn} from './types';
import {PlatformReflectionCapabilities} from 'platform_reflection_capabilities';
export class ReflectionCapabilities implements PlatformReflectionCapabilities {
private _reflect: any;
constructor(reflect?: any) { this._reflect = isPresent(reflect) ? reflect : global.Reflect; }
isReflectionEnabled(): boolean { return true; }
factory(t: Type): Function {
switch (t.length) {
case 0:
return () => new t();
case 1:
return (a1) => new t(a1);
case 2:
return (a1, a2) => new t(a1, a2);
case 3:
return (a1, a2, a3) => new t(a1, a2, a3);
case 4:
return (a1, a2, a3, a4) => new t(a1, a2, a3, a4);
case 5:
return (a1, a2, a3, a4, a5) => new t(a1, a2, a3, a4, a5);
case 6:
return (a1, a2, a3, a4, a5, a6) => new t(a1, a2, a3, a4, a5, a6);
case 7:
return (a1, a2, a3, a4, a5, a6, a7) => new t(a1, a2, a3, a4, a5, a6, a7);
case 8:
return (a1, a2, a3, a4, a5, a6, a7, a8) => new t(a1, a2, a3, a4, a5, a6, a7, a8);
case 9:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => new t(a1, a2, a3, a4, a5, a6, a7, a8, a9);
case 10:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) =>
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
case 11:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) =>
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11);
case 12:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) =>
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);
case 13:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) =>
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13);
case 14:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) =>
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14);
case 15:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) =>
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15);
case 16:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) =>
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16);
case 17:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17) =>
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16,
a17);
case 18:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18) =>
new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17,
a18);
case 19:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18,
a19) => new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16,
a17, a18, a19);
case 20:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18,
a19, a20) => new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15,
a16, a17, a18, a19, a20);
};
throw new Error(
`Cannot create a factory for '${stringify(t)}' because its constructor has more than 20 arguments`);
}
_zipTypesAndAnnotaions(paramTypes, paramAnnotations): List<List<any>> {
var result;
if (typeof paramTypes === 'undefined') {
result = ListWrapper.createFixedSize(paramAnnotations.length);
} else {
result = ListWrapper.createFixedSize(paramTypes.length);
}
for (var i = 0; i < result.length; i++) {
// TS outputs Object for parameters without types, while Traceur omits
// the annotations. For now we preserve the Traceur behavior to aid
// migration, but this can be revisited.
if (typeof paramTypes === 'undefined') {
result[i] = [];
} else if (paramTypes[i] != Object) {
result[i] = [paramTypes[i]];
} else {
result[i] = [];
}
if (isPresent(paramAnnotations) && isPresent(paramAnnotations[i])) {
result[i] = result[i].concat(paramAnnotations[i]);
}
}
return result;
}
parameters(typeOfFunc: Type): List<List<any>> {
// Prefer the direct API.
if (isPresent((<any>typeOfFunc).parameters)) {
return (<any>typeOfFunc).parameters;
}
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
var paramAnnotations = this._reflect.getMetadata('parameters', typeOfFunc);
var paramTypes = this._reflect.getMetadata('design:paramtypes', typeOfFunc);
if (isPresent(paramTypes) || isPresent(paramAnnotations)) {
return this._zipTypesAndAnnotaions(paramTypes, paramAnnotations);
}
}
return ListWrapper.createFixedSize((<any>typeOfFunc).length);
}
annotations(typeOfFunc: Type): List<any> {
// Prefer the direct API.
if (isPresent((<any>typeOfFunc).annotations)) {
var annotations = (<any>typeOfFunc).annotations;
if (isFunction(annotations) && annotations.annotations) {
annotations = annotations.annotations;
}
return annotations;
}
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
var annotations = this._reflect.getMetadata('annotations', typeOfFunc);
if (isPresent(annotations)) return annotations;
}
return [];
}
interfaces(type: Type): List<any> {
throw new BaseException("JavaScript does not support interfaces");
}
getter(name: string): GetterFn { return <GetterFn>new Function('o', 'return o.' + name + ';'); }
setter(name: string): SetterFn {
return <SetterFn>new Function('o', 'v', 'return o.' + name + ' = v;');
}
method(name: string): MethodFn {
let functionBody = `if (!o.${name}) throw new Error('"${name}" is undefined');
return o.${name}.apply(o, args);`;
return <MethodFn>new Function('o', 'args', functionBody);
}
}

Some files were not shown because too many files have changed in this diff Show More