chore(packaging): move files to match target file structure

This commit is contained in:
Yegor Jbanov
2015-02-04 23:05:13 -08:00
parent 7ce4f66cdc
commit 3820609f24
149 changed files with 0 additions and 77 deletions

View File

@ -0,0 +1,53 @@
import {isPresent} from 'facade/src/lang';
import {List, ListWrapper} from 'facade/src/collection';
import {ChangeDetector, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './interfaces';
export class AbstractChangeDetector extends ChangeDetector {
children:List;
parent:ChangeDetector;
status:string;
constructor() {
this.children = [];
this.status = CHECK_ALWAYS;
}
addChild(cd:ChangeDetector) {
ListWrapper.push(this.children, cd);
cd.parent = this;
}
removeChild(cd:ChangeDetector) {
ListWrapper.remove(this.children, cd);
}
remove() {
this.parent.removeChild(this);
}
detectChanges() {
this._detectChanges(false);
}
checkNoChanges() {
this._detectChanges(true);
}
_detectChanges(throwOnChange:boolean) {
if (this.mode === DETACHED || this.mode === CHECKED) return;
this.detectChangesInRecords(throwOnChange);
this._detectChangesInChildren(throwOnChange);
if (this.mode === CHECK_ONCE) this.mode = CHECKED;
}
detectChangesInRecords(throwOnChange:boolean){}
_detectChangesInChildren(throwOnChange:boolean) {
var children = this.children;
for(var i = 0; i < children.length; ++i) {
children[i]._detectChanges(throwOnChange);
}
}
}

View File

@ -0,0 +1,653 @@
import {
isListLikeIterable,
iterateListLike,
ListWrapper,
MapWrapper
} from 'facade/src/collection';
import {
int,
isBlank,
isPresent,
stringify,
getMapKey,
looseIdentical,
} from 'facade/src/lang';
export class ArrayChanges {
_collection;
_length:int;
_linkedRecords:_DuplicateMap;
_unlinkedRecords:_DuplicateMap;
_previousItHead:CollectionChangeRecord<V>;
_itHead:CollectionChangeRecord<V>;
_itTail:CollectionChangeRecord<V>;
_additionsHead:CollectionChangeRecord<V>;
_additionsTail:CollectionChangeRecord<V>;
_movesHead:CollectionChangeRecord<V>;
_movesTail:CollectionChangeRecord<V> ;
_removalsHead:CollectionChangeRecord<V>;
_removalsTail:CollectionChangeRecord<V>;
constructor() {
this._collection = null;
this._length = null;
/// Keeps track of the used records at any point in time (during & across `_check()` calls)
this._linkedRecords = null;
/// Keeps track of the removed records at any point in time during `_check()` calls.
this._unlinkedRecords = null;
this._previousItHead = null;
this._itHead = null;
this._itTail = null;
this._additionsHead = null;
this._additionsTail = null;
this._movesHead = null;
this._movesTail = null;
this._removalsHead = null;
this._removalsTail = null;
}
static supports(obj):boolean {
return isListLikeIterable(obj);
}
supportsObj(obj):boolean {
return ArrayChanges.supports(obj);
}
get collection() {
return this._collection;
}
get length():int {
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);
}
}
// todo(vicb): optim for UnmodifiableListView (frozen arrays)
check(collection):boolean {
this._reset();
var record:CollectionChangeRecord = this._itHead;
var mayBeDirty:boolean = false;
var index:int, item;
if (ListWrapper.isList(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:int):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:int):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 [CollectionChangeRecord]s from the previous collection
*
* - [record] The first excess [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:int):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:int):CollectionChangeRecord {
this._unlink(record);
this._insertAfter(record, prevRecord, index);
this._addToMoves(record, index);
return record;
}
_addAfter(record:CollectionChangeRecord, prevRecord:CollectionChangeRecord,
index:int):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:int):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:int):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) {
ListWrapper.push(list, record);
}
var previous = [];
for (record = this._previousItHead; record !== null; record = record._nextPrevious) {
ListWrapper.push(previous, record);
}
var additions = [];
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
ListWrapper.push(additions, record);
}
var moves = [];
for (record = this._movesHead; record !== null; record = record._nextMoved) {
ListWrapper.push(moves, record);
}
var removals = [];
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
ListWrapper.push(removals, 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:int;
previousIndex:int;
item;
_nextPrevious:CollectionChangeRecord;
_prev:CollectionChangeRecord; _next:CollectionChangeRecord;
_prevDup:CollectionChangeRecord; _nextDup:CollectionChangeRecord;
_prevRemoved:CollectionChangeRecord; _nextRemoved:CollectionChangeRecord;
_nextAdded:CollectionChangeRecord;
_nextMoved:CollectionChangeRecord;
constructor(item) {
this.currentIndex = null;
this.previousIndex = null;
this.item = item;
this._nextPrevious = null;
this._prev = null;
this._next = null;
this._prevDup = null;
this._nextDup = null;
this._prevRemoved = null;
this._nextRemoved = null;
this._nextAdded = null;
this._nextMoved = null;
}
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;
_tail:CollectionChangeRecord;
constructor() {
this._head = null;
this._tail = 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) {
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, afterIndex:int):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 [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;
constructor() {
this.map = MapWrapper.create();
}
put(record:CollectionChangeRecord) {
// todo(vicb) handle corner cases
var key = getMapKey(record.item);
var duplicates = MapWrapper.get(this.map, key);
if (!isPresent(duplicates)) {
duplicates = new _DuplicateItemRecordList();
MapWrapper.set(this.map, 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, afterIndex = null):CollectionChangeRecord {
var key = getMapKey(value);
var recordList = MapWrapper.get(this.map, key);
return isBlank(recordList) ? null : recordList.get(value, afterIndex);
}
/**
* Removes an [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 = MapWrapper.get(this.map, 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() {
MapWrapper.clear(this.map);
}
toString():string {
return '_DuplicateMap(' + stringify(this.map) + ')';
}
}

View File

@ -0,0 +1,10 @@
library change_detectoin.change_detection_jit_generator;
class ChangeDetectorJITGenerator {
ChangeDetectorJITGenerator(typeName, records) {
}
generate() {
throw "Not supported in Dart";
}
}

View File

@ -0,0 +1,401 @@
import {isPresent, isBlank, BaseException, Type} from 'facade/src/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/src/collection';
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
import {AbstractChangeDetector} from './abstract_change_detector';
import {ChangeDetectionUtil} from './change_detection_util';
import {
ProtoRecord,
RECORD_TYPE_SELF,
RECORD_TYPE_PROPERTY,
RECORD_TYPE_INVOKE_METHOD,
RECORD_TYPE_CONST,
RECORD_TYPE_INVOKE_CLOSURE,
RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_INVOKE_FORMATTER,
RECORD_TYPE_STRUCTURAL_CHECK,
RECORD_TYPE_INTERPOLATE,
ProtoChangeDetector
} from './proto_change_detector';
/**
* 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.
*
* For example: An expression `address.city` will result in the following class:
*
* var ChangeDetector0 = function ChangeDetector0(dispatcher, formatters, protos) {
* AbstractChangeDetector.call(this);
* this.dispatcher = dispatcher;
* this.formatters = formatters;
* this.protos = protos;
*
* this.context = null;
* this.address0 = null;
* this.city1 = null;
* }
* ChangeDetector0.prototype = Object.create(AbstractChangeDetector.prototype);
*
* ChangeDetector0.prototype.detectChangesInRecords = function(throwOnChange) {
* var address0;
* var city1;
* var change;
* var changes = null;
* var temp;
* var context = this.context;
*
* temp = ChangeDetectionUtil.findContext("address", context);
* if (temp instanceof ContextWithVariableBindings) {
* address0 = temp.get('address');
* } else {
* address0 = temp.address;
* }
*
* if (address0 !== this.address0) {
* this.address0 = address0;
* }
*
* city1 = address0.city;
* if (city1 !== this.city1) {
* changes = ChangeDetectionUtil.addRecord(changes,
* ChangeDetectionUtil.simpleChangeRecord(this.protos[1].bindingMemento, this.city1, city1));
* this.city1 = city1;
* }
*
* if (changes.length > 0) {
* if(throwOnChange) ChangeDetectionUtil.throwOnChange(this.protos[1], changes[0]);
* this.dispatcher.onRecordChange('address.city', changes);
* changes = null;
* }
* }
*
*
* ChangeDetector0.prototype.setContext = function(context) {
* this.context = context;
* }
*
* return ChangeDetector0;
*
*
* The only thing the generated class depends on is the super class AbstractChangeDetector.
*
* The implementation comprises two parts:
* * ChangeDetectorJITGenerator has the logic of how everything fits together.
* * template functions (e.g., constructorTemplate) define what code is generated.
*/
var ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
var UTIL = "ChangeDetectionUtil";
var DISPATCHER_ACCESSOR = "this.dispatcher";
var FORMATTERS_ACCESSOR = "this.formatters";
var PROTOS_ACCESSOR = "this.protos";
var CHANGE_LOCAL = "change";
var CHANGES_LOCAL = "changes";
var TEMP_LOCAL = "temp";
function typeTemplate(type:string, cons:string, detectChanges:string, setContext:string):string {
return `
${cons}
${detectChanges}
${setContext};
return function(dispatcher, formatters) {
return new ${type}(dispatcher, formatters, protos);
}
`;
}
function constructorTemplate(type:string, fieldsDefinitions:string):string {
return `
var ${type} = function ${type}(dispatcher, formatters, protos) {
${ABSTRACT_CHANGE_DETECTOR}.call(this);
${DISPATCHER_ACCESSOR} = dispatcher;
${FORMATTERS_ACCESSOR} = formatters;
${PROTOS_ACCESSOR} = protos;
${fieldsDefinitions}
}
${type}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype);
`;
}
function setContextTemplate(type:string):string {
return `
${type}.prototype.setContext = function(context) {
this.context = context;
}
`;
}
function detectChangesTemplate(type:string, body:string):string {
return `
${type}.prototype.detectChangesInRecords = function(throwOnChange) {
${body}
}
`;
}
function bodyTemplate(localDefinitions:string, changeDefinitions:string, records:string):string {
return `
${localDefinitions}
${changeDefinitions}
var ${TEMP_LOCAL};
var ${CHANGE_LOCAL};
var ${CHANGES_LOCAL} = null;
context = this.context;
${records}
`;
}
function notifyTemplate(index:number):string{
return `
if (${CHANGES_LOCAL} && ${CHANGES_LOCAL}.length > 0) {
if(throwOnChange) ${UTIL}.throwOnChange(${PROTOS_ACCESSOR}[${index}], ${CHANGES_LOCAL}[0]);
${DISPATCHER_ACCESSOR}.onRecordChange(${PROTOS_ACCESSOR}[${index}].groupMemento, ${CHANGES_LOCAL});
${CHANGES_LOCAL} = null;
}
`;
}
function structuralCheckTemplate(selfIndex:number, field:string, context:string, notify:string):string{
return `
${CHANGE_LOCAL} = ${UTIL}.structuralCheck(${field}, ${context});
if (${CHANGE_LOCAL}) {
${CHANGES_LOCAL} = ${UTIL}.addRecord(${CHANGES_LOCAL},
${UTIL}.changeRecord(${PROTOS_ACCESSOR}[${selfIndex}].bindingMemento, ${CHANGE_LOCAL}));
${field} = ${CHANGE_LOCAL}.currentValue;
}
${notify}
`;
}
function referenceCheckTemplate(assignment, newValue, oldValue, change, addRecord, notify) {
return `
${assignment}
if (${newValue} !== ${oldValue} || (${newValue} !== ${newValue}) && (${oldValue} !== ${oldValue})) {
${change} = true;
${addRecord}
${oldValue} = ${newValue};
}
${notify}
`;
}
function assignmentTemplate(field:string, value:string) {
return `${field} = ${value};`;
}
function propertyReadTemplate(name:string, context:string, newValue:string) {
return `
${TEMP_LOCAL} = ${UTIL}.findContext("${name}", ${context});
if (${TEMP_LOCAL} instanceof ContextWithVariableBindings) {
${newValue} = ${TEMP_LOCAL}.get('${name}');
} else {
${newValue} = ${TEMP_LOCAL}.${name};
}
`;
}
function localDefinitionsTemplate(names:List):string {
return names.map((n) => `var ${n};`).join("\n");
}
function changeDefinitionsTemplate(names:List):string {
return names.map((n) => `var ${n} = false;`).join("\n");
}
function fieldDefinitionsTemplate(names:List):string {
return names.map((n) => `${n} = ${UTIL}.unitialized();`).join("\n");
}
function ifChangedGuardTemplate(changeNames:List, body:string):string {
var cond = changeNames.join(" || ");
return `
if (${cond}) {
${body}
}
`;
}
function addSimpleChangeRecordTemplate(protoIndex:number, oldValue:string, newValue:string) {
return `${CHANGES_LOCAL} = ${UTIL}.addRecord(${CHANGES_LOCAL},
${UTIL}.simpleChangeRecord(${PROTOS_ACCESSOR}[${protoIndex}].bindingMemento, ${oldValue}, ${newValue}));`;
}
export class ChangeDetectorJITGenerator {
typeName:string;
records:List<ProtoRecord>;
localNames:List<String>;
changeNames:List<String>;
fieldNames:List<String>;
constructor(typeName:string, records:List<ProtoRecord>) {
this.typeName = typeName;
this.records = records;
this.localNames = this.getLocalNames(records);
this.changeNames = this.getChangeNames(this.localNames);
this.fieldNames = this.getFieldNames(this.localNames);
}
getLocalNames(records:List<ProtoRecord>):List<String> {
var index = 0;
var names = records.map((r) => {
var sanitizedName = r.name.replace(new RegExp("\\W", "g"), '');
return `${sanitizedName}${index++}`
});
return ["context"].concat(names);
}
getChangeNames(localNames:List<String>):List<String> {
return localNames.map((n) => `change_${n}`);
}
getFieldNames(localNames:List<String>):List<String> {
return localNames.map((n) => `this.${n}`);
}
generate():Function {
var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(), this.genSetContext());
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'ContextWithVariableBindings', 'protos', text)(AbstractChangeDetector, ChangeDetectionUtil, ContextWithVariableBindings, this.records);
}
genConstructor():string {
return constructorTemplate(this.typeName, fieldDefinitionsTemplate(this.fieldNames));
}
genSetContext():string {
return setContextTemplate(this.typeName);
}
genDetectChanges():string {
var body = this.genBody();
return detectChangesTemplate(this.typeName, body);
}
genBody():string {
var rec = this.records.map((r) => this.genRecord(r)).join("\n");
return bodyTemplate(this.genLocalDefinitions(), this.genChangeDefinitions(), rec);
}
genLocalDefinitions():string {
return localDefinitionsTemplate(this.localNames);
}
genChangeDefinitions():string {
return changeDefinitionsTemplate(this.changeNames);
}
genRecord(r:ProtoRecord):string {
if (r.mode == RECORD_TYPE_STRUCTURAL_CHECK) {
return this.getStructuralCheck(r);
} else {
return this.genReferenceCheck(r);
}
}
getStructuralCheck(r:ProtoRecord):string {
var field = this.fieldNames[r.selfIndex];
var context = this.localNames[r.contextIndex];
return structuralCheckTemplate(r.selfIndex - 1, field, context, this.genNotify(r));
}
genReferenceCheck(r:ProtoRecord):string {
var newValue = this.localNames[r.selfIndex];
var oldValue = this.fieldNames[r.selfIndex];
var change = this.changeNames[r.selfIndex];
var assignment = this.genUpdateCurrentValue(r);
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
var notify = this.genNotify(r);
var check = referenceCheckTemplate(assignment, newValue, oldValue, change, r.lastInBinding ? addRecord : '', notify);;
if (r.isPureFunction()) {
return this.ifChangedGuard(r, check);
} else {
return check;
}
}
genUpdateCurrentValue(r:ProtoRecord):string {
var context = this.localNames[r.contextIndex];
var newValue = this.localNames[r.selfIndex];
var args = this.genArgs(r);
switch (r.mode) {
case RECORD_TYPE_SELF:
return assignmentTemplate(newValue, context);
case RECORD_TYPE_CONST:
return `${newValue} = ${this.genLiteral(r.funcOrValue)}`;
case RECORD_TYPE_PROPERTY:
if (r.contextIndex == 0) { // only the first property read can be a local
return propertyReadTemplate(r.name, context, newValue);
} else {
return assignmentTemplate(newValue, `${context}.${r.name}`);
}
case RECORD_TYPE_INVOKE_METHOD:
return assignmentTemplate(newValue, `${context}.${r.name}(${args})`);
case RECORD_TYPE_INVOKE_CLOSURE:
return assignmentTemplate(newValue, `${context}(${args})`);
case RECORD_TYPE_PRIMITIVE_OP:
return assignmentTemplate(newValue, `${UTIL}.${r.name}(${args})`);
case RECORD_TYPE_INTERPOLATE:
return assignmentTemplate(newValue, this.genInterpolation(r));
case RECORD_TYPE_INVOKE_FORMATTER:
return assignmentTemplate(newValue, `${FORMATTERS_ACCESSOR}.get("${r.name}")(${args})`);
case RECORD_TYPE_KEYED_ACCESS:
var key = this.localNames[r.args[0]];
return assignmentTemplate(newValue, `${context}[${key}]`);
default:
throw new BaseException(`Unknown operation ${r.mode}`);
}
}
ifChangedGuard(r:ProtoRecord, body:string):string {
return ifChangedGuardTemplate(r.args.map((a) => this.changeNames[a]), body);
}
genInterpolation(r:ProtoRecord):string{
var res = "";
for (var i = 0; i < r.args.length; ++i) {
res += this.genLiteral(r.fixedArgs[i]);
res += " + ";
res += this.localNames[r.args[i]];
res += " + ";
}
res += this.genLiteral(r.fixedArgs[r.args.length]);
return res;
}
genLiteral(value):string {
return JSON.stringify(value);
}
genNotify(r):string{
return r.lastInGroup ? notifyTemplate(r.selfIndex - 1) : '';
}
genArgs(r:ProtoRecord):string {
return r.args.map((arg) => this.localNames[arg]).join(", ");
}
}

View File

@ -0,0 +1,225 @@
import {isPresent, isBlank, BaseException, Type} from 'facade/src/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/src/collection';
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
import {ArrayChanges} from './array_changes';
import {KeyValueChanges} from './keyvalue_changes';
import {ProtoRecord} from './proto_change_detector';
import {ExpressionChangedAfterItHasBeenChecked} from './exceptions';
import {ChangeRecord, ChangeDetector, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './interfaces';
export var uninitialized = new Object();
export class SimpleChange {
previousValue:any;
currentValue:any;
constructor(previousValue:any, currentValue:any) {
this.previousValue = previousValue;
this.currentValue = currentValue;
}
}
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)
]
var _changeRecordsIndex = 0;
var _changeRecords = [
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null)
]
function _simpleChange(previousValue, currentValue) {
var index = _simpleChangesIndex++ % 20;
var s = _simpleChanges[index];
s.previousValue = previousValue;
s.currentValue = currentValue;
return s;
}
function _changeRecord(bindingMemento, change) {
var index = _changeRecordsIndex++ % 20;
var s = _changeRecords[index];
s.bindingMemento = bindingMemento;
s.change = change;
return s;
}
var _singleElementList = [null];
function _isBlank(val):boolean {
return isBlank(val) || val === uninitialized;
}
export class ChangeDetectionUtil {
static unitialized() {
return uninitialized;
}
static arrayFn0() { return []; }
static arrayFn1(a1) { return [a1]; }
static arrayFn2(a1, a2) { return [a1, a2]; }
static arrayFn3(a1, a2, a3) { return [a1, a2, a3]; }
static arrayFn4(a1, a2, a3, a4) { return [a1, a2, a3, a4]; }
static arrayFn5(a1, a2, a3, a4, a5) { return [a1, a2, a3, a4, a5]; }
static arrayFn6(a1, a2, a3, a4, a5, a6) { return [a1, a2, a3, a4, a5, a6]; }
static arrayFn7(a1, a2, a3, a4, a5, a6, a7) { return [a1, a2, a3, a4, a5, a6, a7]; }
static arrayFn8(a1, a2, a3, a4, a5, a6, a7, a8) { return [a1, a2, a3, a4, a5, a6, a7, a8]; }
static arrayFn9(a1, a2, a3, a4, a5, a6, a7, a8, a9) { return [a1, a2, a3, a4, a5, a6, a7, a8, a9]; }
static operation_negate(value) {return !value;}
static operation_add(left, right) {return left + right;}
static operation_subtract(left, right) {return left - right;}
static operation_multiply(left, right) {return left * right;}
static operation_divide(left, right) {return left / right;}
static operation_remainder(left, right) {return left % right;}
static operation_equals(left, right) {return left == right;}
static operation_not_equals(left, right) {return left != right;}
static operation_less_then(left, right) {return left < right;}
static operation_greater_then(left, right) {return left > right;}
static operation_less_or_equals_then(left, right) {return left <= right;}
static operation_greater_or_equals_then(left, right) {return left >= right;}
static operation_logical_and(left, right) {return left && right;}
static operation_logical_or(left, right) {return left || right;}
static cond(cond, trueVal, falseVal) {return cond ? trueVal : falseVal;}
static mapFn(keys:List) {
function buildMap(values) {
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) {
return obj[args[0]];
}
static structuralCheck(self, context) {
if (_isBlank(self) && _isBlank(context)) {
return null;
} else if (_isBlank(context)) {
return new SimpleChange(null, null);
}
if (_isBlank(self)) {
if (ArrayChanges.supports(context)) {
self = new ArrayChanges();
} else if (KeyValueChanges.supports(context)) {
self = new KeyValueChanges();
}
}
if (isBlank(self) || !self.supportsObj(context)) {
throw new BaseException(`Unsupported type (${context})`);
}
if (self.check(context)) {
return new SimpleChange(null, self); // TODO: don't wrap and return self instead
} else {
return null;
}
}
static findContext(name:string, c){
while (c instanceof ContextWithVariableBindings) {
if (c.hasBinding(name)) {
return c;
}
c = c.parent;
}
return c;
}
static throwOnChange(proto:ProtoRecord, change) {
throw new ExpressionChangedAfterItHasBeenChecked(proto, change);
}
static simpleChange(previousValue:any, currentValue:any):SimpleChange {
return _simpleChange(previousValue, currentValue);
}
static changeRecord(memento:any, change:any):ChangeRecord {
return _changeRecord(memento, change);
}
static simpleChangeRecord(memento:any, previousValue:any, currentValue:any):ChangeRecord {
return _changeRecord(memento, _simpleChange(previousValue, currentValue));
}
static addRecord(updatedRecords:List, changeRecord:ChangeRecord):List {
if (isBlank(updatedRecords)) {
updatedRecords = _singleElementList;
updatedRecords[0] = changeRecord;
} else if (updatedRecords === _singleElementList) {
updatedRecords = [_singleElementList[0], changeRecord];
} else {
ListWrapper.push(updatedRecords, changeRecord);
}
return updatedRecords;
}
static markPathToRootAsCheckOnce(cd:ChangeDetector) {
var c = cd;
while(isPresent(c) && c.mode != DETACHED) {
if (c.mode === CHECKED) c.mode = CHECK_ONCE;
c = c.parent;
}
}
}

View File

@ -0,0 +1,88 @@
import {isPresent} from 'facade/src/lang';
import {List, ListWrapper, Map, MapWrapper} from 'facade/src/collection';
import {RECORD_TYPE_SELF, ProtoRecord} from './proto_change_detector';
/**
* 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:List<ProtoRecord>):List<ProtoRecord> {
var res = ListWrapper.create();
var indexMap = MapWrapper.create();
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) {
ListWrapper.push(res, _selfRecord(record, matchingRecord.selfIndex, res.length + 1));
MapWrapper.set(indexMap, r.selfIndex, matchingRecord.selfIndex);
} else if (isPresent(matchingRecord) && !record.lastInBinding) {
MapWrapper.set(indexMap, r.selfIndex, matchingRecord.selfIndex);
} else {
ListWrapper.push(res, record);
MapWrapper.set(indexMap, r.selfIndex, record.selfIndex);
}
}
return res;
}
function _selfRecord(r:ProtoRecord, contextIndex:number, selfIndex:number):ProtoRecord {
return new ProtoRecord(
RECORD_TYPE_SELF,
"self",
null,
[],
r.fixedArgs,
contextIndex,
selfIndex,
r.bindingMemento,
r.groupMemento,
r.expressionAsString,
r.lastInBinding,
r.lastInGroup
);
}
function _findMatching(r:ProtoRecord, rs:List<ProtoRecord>){
return ListWrapper.find(rs, (rr) =>
rr.mode === r.mode &&
rr.funcOrValue === r.funcOrValue &&
rr.contextIndex === r.contextIndex &&
ListWrapper.equals(rr.args, r.args)
);
}
function _replaceIndices(r:ProtoRecord, selfIndex:number, indexMap:Map) {
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,
selfIndex,
r.bindingMemento,
r.groupMemento,
r.expressionAsString,
r.lastInBinding,
r.lastInGroup
);
}
function _map(indexMap:Map, value:number) {
var r = MapWrapper.get(indexMap, value)
return isPresent(r) ? r : value;
}

View File

@ -0,0 +1,215 @@
import {isPresent, isBlank, BaseException, FunctionWrapper} from 'facade/src/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/src/collection';
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
import {AbstractChangeDetector} from './abstract_change_detector';
import {ChangeDetectionUtil, SimpleChange, uninitialized} from './change_detection_util';
import {ArrayChanges} from './array_changes';
import {KeyValueChanges} from './keyvalue_changes';
import {
ProtoRecord,
RECORD_TYPE_SELF,
RECORD_TYPE_PROPERTY,
RECORD_TYPE_INVOKE_METHOD,
RECORD_TYPE_CONST,
RECORD_TYPE_INVOKE_CLOSURE,
RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_INVOKE_FORMATTER,
RECORD_TYPE_STRUCTURAL_CHECK,
RECORD_TYPE_INTERPOLATE,
ProtoChangeDetector
} from './proto_change_detector';
import {ChangeDetector, ChangeDispatcher} from './interfaces';
import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
export class DynamicChangeDetector extends AbstractChangeDetector {
dispatcher:any;
formatters:Map;
values:List;
changes:List;
protos:List<ProtoRecord>;
constructor(dispatcher:any, formatters:Map, protoRecords:List<ProtoRecord>) {
super();
this.dispatcher = dispatcher;
this.formatters = formatters;
this.values = ListWrapper.createFixedSize(protoRecords.length + 1);
ListWrapper.fill(this.values, uninitialized);
this.changes = ListWrapper.createFixedSize(protoRecords.length + 1);
this.protos = protoRecords;
}
setContext(context:any) {
this.values[0] = context;
}
detectChangesInRecords(throwOnChange:boolean) {
var protos:List<ProtoRecord> = this.protos;
var updatedRecords = null;
var currentGroup = null;
for (var i = 0; i < protos.length; ++i) {
var proto:ProtoRecord = protos[i];
var change = this._check(proto);
if (isPresent(change)) {
currentGroup = proto.groupMemento;
var record = ChangeDetectionUtil.changeRecord(proto.bindingMemento, change);
updatedRecords = ChangeDetectionUtil.addRecord(updatedRecords, record);
}
if (proto.lastInGroup && isPresent(updatedRecords)) {
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, updatedRecords[0]);
this.dispatcher.onRecordChange(currentGroup, updatedRecords);
updatedRecords = null;
}
}
}
_check(proto:ProtoRecord) {
try {
if (proto.mode == RECORD_TYPE_STRUCTURAL_CHECK) {
return this._structuralCheck(proto);
} else {
return this._referenceCheck(proto);
}
} catch (e) {
throw new ChangeDetectionError(proto, e);
}
}
_referenceCheck(proto:ProtoRecord) {
if (this._pureFuncAndArgsDidNotChange(proto)) {
this._setChanged(proto, false);
return null;
}
var prevValue = this._readSelf(proto);
var currValue = this._calculateCurrValue(proto);
if (!isSame(prevValue, currValue)) {
this._writeSelf(proto, currValue);
this._setChanged(proto, true);
if (proto.lastInBinding) {
return ChangeDetectionUtil.simpleChange(prevValue, currValue);
} else {
return null;
}
} else {
this._setChanged(proto, false);
return null;
}
}
_calculateCurrValue(proto:ProtoRecord) {
switch (proto.mode) {
case RECORD_TYPE_SELF:
return this._readContext(proto);
case RECORD_TYPE_CONST:
return proto.funcOrValue;
case RECORD_TYPE_PROPERTY:
var context = this._readContext(proto);
var c = ChangeDetectionUtil.findContext(proto.name, context);
if (c instanceof ContextWithVariableBindings) {
return c.get(proto.name);
} else {
var propertyGetter:Function = proto.funcOrValue;
return propertyGetter(c);
}
break;
case RECORD_TYPE_INVOKE_METHOD:
var methodInvoker:Function = proto.funcOrValue;
return methodInvoker(this._readContext(proto), this._readArgs(proto));
case RECORD_TYPE_KEYED_ACCESS:
var arg = this._readArgs(proto)[0];
return this._readContext(proto)[arg];
case RECORD_TYPE_INVOKE_CLOSURE:
return FunctionWrapper.apply(this._readContext(proto), this._readArgs(proto));
case RECORD_TYPE_INTERPOLATE:
case RECORD_TYPE_PRIMITIVE_OP:
return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto));
case RECORD_TYPE_INVOKE_FORMATTER:
var formatter = MapWrapper.get(this.formatters, proto.funcOrValue);
return FunctionWrapper.apply(formatter, this._readArgs(proto));
default:
throw new BaseException(`Unknown operation ${proto.mode}`);
}
}
_structuralCheck(proto:ProtoRecord) {
var self = this._readSelf(proto);
var context = this._readContext(proto);
var change = ChangeDetectionUtil.structuralCheck(self, context);
if (isPresent(change)) {
this._writeSelf(proto, change.currentValue);
}
return change;
}
_readContext(proto:ProtoRecord) {
return this.values[proto.contextIndex];
}
_readSelf(proto:ProtoRecord) {
return this.values[proto.selfIndex];
}
_writeSelf(proto:ProtoRecord, value) {
this.values[proto.selfIndex] = value;
}
_setChanged(proto:ProtoRecord, value:boolean) {
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) {
var res = ListWrapper.createFixedSize(proto.args.length);
var args = proto.args;
for (var i = 0; i < args.length; ++i) {
res[i] = this.values[args[i]];
}
return res;
}
}
var _singleElementList = [null];
function isSame(a, b) {
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,30 @@
import {ProtoRecord} from './proto_change_detector';
export class ExpressionChangedAfterItHasBeenChecked extends Error {
message:string;
constructor(proto:ProtoRecord, change:any) {
this.message = `Expression '${proto.expressionAsString}' has changed after it was checked. ` +
`Previous value: '${change.previousValue}'. Current value: '${change.currentValue}'`;
}
toString():string {
return this.message;
}
}
export class ChangeDetectionError extends Error {
message:string;
originalException:any;
location:string;
constructor(proto:ProtoRecord, originalException:any) {
this.originalException = originalException;
this.location = proto.expressionAsString;
this.message = `${this.originalException} in [${this.location}]`;
}
toString():string {
return this.message;
}
}

View File

@ -0,0 +1,63 @@
import {List} from 'facade/src/collection';
export class ChangeRecord {
bindingMemento:any;
change:any;
constructor(bindingMemento, change) {
this.bindingMemento = bindingMemento;
this.change = change;
}
//REMOVE IT
get currentValue() {
return this.change.currentValue;
}
get previousValue() {
return this.change.previousValue;
}
}
/**
* CHECK_ONCE means that after calling detectChanges the mode of the change detector
* will become CHECKED.
*/
export const CHECK_ONCE="CHECK_ONCE";
/**
* CHECKED means that the change detector should be skipped until its mode changes to
* CHECK_ONCE or CHECK_ALWAYS.
*/
export const CHECKED="CHECKED";
/**
* CHECK_ALWAYS means that after calling detectChanges the mode of the change detector
* will remain CHECK_ALWAYS.
*/
export const CHECK_ALWAYS="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="DETACHED";
export class ChangeDispatcher {
onRecordChange(groupMemento, records:List<ChangeRecord>) {}
}
export class ChangeDetector {
parent:ChangeDetector;
mode:string;
addChild(cd:ChangeDetector) {}
removeChild(cd:ChangeDetector) {}
remove() {}
setContext(context:any) {}
markPathToRootAsCheckOnce() {}
detectChanges() {}
checkNoChanges() {}
}

View File

@ -0,0 +1,363 @@
import {ListWrapper, MapWrapper, StringMapWrapper} from 'facade/src/collection';
import {stringify, looseIdentical, isJsObject} from 'facade/src/lang';
export class KeyValueChanges {
_records:Map;
_mapHead:KVChangeRecord;
_previousMapHead:KVChangeRecord;
_changesHead:KVChangeRecord;
_changesTail:KVChangeRecord;
_additionsHead:KVChangeRecord;
_additionsTail:KVChangeRecord;
_removalsHead:KVChangeRecord;
_removalsTail:KVChangeRecord;
constructor() {
this._records = MapWrapper.create();
this._mapHead = null;
this._previousMapHead = null;
this._changesHead = null;
this._changesTail = null;
this._additionsHead = null;
this._additionsTail = null;
this._removalsHead = null;
this._removalsTail = null;
}
static supports(obj):boolean {
return obj instanceof Map || isJsObject(obj);
}
supportsObj(obj):boolean {
return KeyValueChanges.supports(obj);
}
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);
}
}
check(map):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 (MapWrapper.contains(records, key)) {
newSeqRecord = MapWrapper.get(records, key);
} else {
newSeqRecord = new KVChangeRecord(key);
MapWrapper.set(records, 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) {
ListWrapper.push(items, stringify(record));
}
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
ListWrapper.push(previous, stringify(record));
}
for (record = this._changesHead; record !== null; record = record._nextChanged) {
ListWrapper.push(changes, stringify(record));
}
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
ListWrapper.push(additions, stringify(record));
}
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
ListWrapper.push(removals, 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 {
key;
_previousValue;
_currentValue;
_nextPrevious:KVChangeRecord;
_next:KVChangeRecord;
_nextAdded:KVChangeRecord;
_nextRemoved:KVChangeRecord;
_prevRemoved:KVChangeRecord;
_nextChanged:KVChangeRecord;
constructor(key) {
this.key = key;
this._previousValue = null;
this._currentValue = null;
this._nextPrevious = null;
this._next = null;
this._nextAdded = null;
this._nextRemoved = null;
this._prevRemoved = null;
this._nextChanged = null;
}
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,467 @@
import {FIELD, autoConvertAdd, isBlank, isPresent, FunctionWrapper, BaseException} from "facade/src/lang";
import {List, Map, ListWrapper, StringMapWrapper} from "facade/src/collection";
import {ContextWithVariableBindings} from "./context_with_variable_bindings";
export class AST {
eval(context) {
throw new BaseException("Not supported");
}
get isAssignable() {
return false;
}
assign(context, value) {
throw new BaseException("Not supported");
}
visit(visitor) {
}
toString():string {
return "AST";
}
}
export class EmptyExpr extends AST {
eval(context) {
return null;
}
visit(visitor) {
//do nothing
}
}
export class Structural extends AST {
value:AST;
constructor(value:AST) {
this.value = value;
}
eval(context) {
return value.eval(context);
}
visit(visitor) {
return visitor.visitStructural(this);
}
}
export class ImplicitReceiver extends AST {
eval(context) {
return context;
}
visit(visitor) {
return visitor.visitImplicitReceiver(this);
}
}
/**
* Multiple expressions separated by a semicolon.
*/
export class Chain extends AST {
expressions:List;
constructor(expressions:List) {
this.expressions = expressions;
}
eval(context) {
var result;
for (var i = 0; i < this.expressions.length; i++) {
var last = this.expressions[i].eval(context);
if (isPresent(last)) result = last;
}
return result;
}
visit(visitor) {
return visitor.visitChain(this);
}
}
export class Conditional extends AST {
condition:AST;
trueExp:AST;
falseExp:AST;
constructor(condition:AST, trueExp:AST, falseExp:AST){
this.condition = condition;
this.trueExp = trueExp;
this.falseExp = falseExp;
}
eval(context) {
if(this.condition.eval(context)) {
return this.trueExp.eval(context);
} else {
return this.falseExp.eval(context);
}
}
visit(visitor) {
return visitor.visitConditional(this);
}
}
export class AccessMember extends AST {
receiver:AST;
name:string;
getter:Function;
setter:Function;
constructor(receiver:AST, name:string, getter:Function, setter:Function) {
this.receiver = receiver;
this.name = name;
this.getter = getter;
this.setter = setter;
}
eval(context) {
var evaluatedContext = this.receiver.eval(context);
while (evaluatedContext instanceof ContextWithVariableBindings) {
if (evaluatedContext.hasBinding(this.name)) {
return evaluatedContext.get(this.name);
}
evaluatedContext = evaluatedContext.parent;
}
return this.getter(evaluatedContext);
}
get isAssignable() {
return true;
}
assign(context, value) {
var evaluatedContext = this.receiver.eval(context);
while (evaluatedContext instanceof ContextWithVariableBindings) {
if (evaluatedContext.hasBinding(this.name)) {
throw new BaseException(`Cannot reassign a variable binding ${this.name}`)
}
evaluatedContext = evaluatedContext.parent;
}
return this.setter(evaluatedContext, value);
}
visit(visitor) {
return visitor.visitAccessMember(this);
}
}
export class KeyedAccess extends AST {
obj:AST;
key:AST;
constructor(obj:AST, key:AST) {
this.obj = obj;
this.key = key;
}
eval(context) {
var obj = this.obj.eval(context);
var key = this.key.eval(context);
return obj[key];
}
get isAssignable() {
return true;
}
assign(context, value) {
var obj = this.obj.eval(context);
var key = this.key.eval(context);
obj[key] = value;
return value;
}
visit(visitor) {
return visitor.visitKeyedAccess(this);
}
}
export class Formatter extends AST {
exp:AST;
name:string;
args:List<AST>;
allArgs:List<AST>;
constructor(exp:AST, name:string, args:List) {
this.exp = exp;
this.name = name;
this.args = args;
this.allArgs = ListWrapper.concat([exp], args);
}
visit(visitor) {
return visitor.visitFormatter(this);
}
}
export class LiteralPrimitive extends AST {
value;
constructor(value) {
this.value = value;
}
eval(context) {
return this.value;
}
visit(visitor) {
return visitor.visitLiteralPrimitive(this);
}
}
export class LiteralArray extends AST {
expressions:List;
constructor(expressions:List) {
this.expressions = expressions;
}
eval(context) {
return ListWrapper.map(this.expressions, (e) => e.eval(context));
}
visit(visitor) {
return visitor.visitLiteralArray(this);
}
}
export class LiteralMap extends AST {
keys:List;
values:List;
constructor(keys:List, values:List) {
this.keys = keys;
this.values = values;
}
eval(context) {
var res = StringMapWrapper.create();
for(var i = 0; i < this.keys.length; ++i) {
StringMapWrapper.set(res, this.keys[i], this.values[i].eval(context));
}
return res;
}
visit(visitor) {
return visitor.visitLiteralMap(this);
}
}
export class Interpolation extends AST {
strings:List;
expressions:List;
constructor(strings:List, expressions:List) {
this.strings = strings;
this.expressions = expressions;
}
eval(context) {
throw new BaseException("evaluating an Interpolation is not supported");
}
visit(visitor) {
visitor.visitInterpolation(this);
}
}
export class Binary extends AST {
operation:string;
left:AST;
right:AST;
constructor(operation:string, left:AST, right:AST) {
this.operation = operation;
this.left = left;
this.right = right;
}
eval(context) {
var left = this.left.eval(context);
switch (this.operation) {
case '&&': return left && this.right.eval(context);
case '||': return left || this.right.eval(context);
}
var right = this.right.eval(context);
switch (this.operation) {
case '+' : return left + right;
case '-' : return left - right;
case '*' : return left * right;
case '/' : return left / right;
case '%' : return left % right;
case '==' : return left == right;
case '!=' : return left != right;
case '<' : return left < right;
case '>' : return left > right;
case '<=' : return left <= right;
case '>=' : return left >= right;
case '^' : return left ^ right;
case '&' : return left & right;
}
throw 'Internal error [$operation] not handled';
}
visit(visitor) {
return visitor.visitBinary(this);
}
}
export class PrefixNot extends AST {
expression:AST;
constructor(expression:AST) {
this.expression = expression;
}
eval(context) {
return !this.expression.eval(context);
}
visit(visitor) {
return visitor.visitPrefixNot(this);
}
}
export class Assignment extends AST {
target:AST;
value:AST;
constructor(target:AST, value:AST) {
this.target = target;
this.value = value;
}
eval(context) {
return this.target.assign(context, this.value.eval(context));
}
visit(visitor) {
return visitor.visitAssignment(this);
}
}
export class MethodCall extends AST {
receiver:AST;
fn:Function;
args:List;
name:string;
constructor(receiver:AST, name:string, fn:Function, args:List) {
this.receiver = receiver;
this.fn = fn;
this.args = args;
this.name = name;
}
eval(context) {
var evaluatedContext = this.receiver.eval(context);
var evaluatedArgs = evalList(context, this.args);
while (evaluatedContext instanceof ContextWithVariableBindings) {
if (evaluatedContext.hasBinding(this.name)) {
var fn = evaluatedContext.get(this.name);
return FunctionWrapper.apply(fn, evaluatedArgs);
}
evaluatedContext = evaluatedContext.parent;
}
return this.fn(evaluatedContext, evaluatedArgs);
}
visit(visitor) {
return visitor.visitMethodCall(this);
}
}
export class FunctionCall extends AST {
target:AST;
args:List;
constructor(target:AST, args:List) {
this.target = target;
this.args = args;
}
eval(context) {
var obj = this.target.eval(context);
if (! (obj instanceof Function)) {
throw new BaseException(`${obj} is not a function`);
}
return FunctionWrapper.apply(obj, evalList(context, this.args));
}
visit(visitor) {
return visitor.visitFunctionCall(this);
}
}
export class ASTWithSource extends AST {
ast:AST;
source:string;
location:string;
constructor(ast:AST, source:string, location:string) {
this.source = source;
this.location = location;
this.ast = ast;
}
eval(context) {
return this.ast.eval(context);
}
get isAssignable() {
return this.ast.isAssignable;
}
assign(context, value) {
return this.ast.assign(context, value);
}
visit(visitor) {
return this.ast.visit(visitor);
}
toString():string {
return `${this.source} in ${this.location}`;
}
}
export class TemplateBinding {
key:string;
keyIsVar:boolean;
name:string;
expression:ASTWithSource;
constructor(key:string, keyIsVar:boolean, name:string, expression:ASTWithSource) {
this.key = key;
this.keyIsVar = keyIsVar;
// only either name or expression will be filled.
this.name = name;
this.expression = expression;
}
}
//INTERFACE
export class AstVisitor {
visitAccessMember(ast:AccessMember) {}
visitAssignment(ast:Assignment) {}
visitBinary(ast:Binary) {}
visitChain(ast:Chain){}
visitStructural(ast:Structural) {}
visitConditional(ast:Conditional) {}
visitFormatter(ast:Formatter) {}
visitFunctionCall(ast:FunctionCall) {}
visitImplicitReceiver(ast:ImplicitReceiver) {}
visitKeyedAccess(ast:KeyedAccess) {}
visitLiteralArray(ast:LiteralArray) {}
visitLiteralMap(ast:LiteralMap) {}
visitLiteralPrimitive(ast:LiteralPrimitive) {}
visitMethodCall(ast:MethodCall) {}
visitPrefixNot(ast:PrefixNot) {}
}
var _evalListCache = [[],[0],[0,0],[0,0,0],[0,0,0,0],[0,0,0,0,0]];
function evalList(context, exps:List){
var length = exps.length;
var result = _evalListCache[length];
for (var i = 0; i < length; i++) {
result[i] = exps[i].eval(context);
}
return result;
}

View File

@ -0,0 +1,38 @@
import {MapWrapper} from 'facade/src/collection';
import {BaseException} from 'facade/src/lang';
export class ContextWithVariableBindings {
parent:any;
/// varBindings' keys are read-only. adding/removing keys is not supported.
varBindings:Map;
constructor(parent:any, varBindings:Map) {
this.parent = parent;
this.varBindings = varBindings;
}
hasBinding(name:string):boolean {
return MapWrapper.contains(this.varBindings, name);
}
get(name:string) {
return MapWrapper.get(this.varBindings, name);
}
set(name:string, value) {
// TODO(rado): consider removing this check if we can guarantee this is not
// exposed to the public API.
if (this.hasBinding(name)) {
MapWrapper.set(this.varBindings, name, value);
} else {
throw new BaseException(
'VariableBindings do not support setting of new keys post-construction.');
}
}
clearValues() {
for (var k of MapWrapper.keys(this.varBindings)) {
MapWrapper.set(this.varBindings, k, null);
}
}
}

View File

@ -0,0 +1,481 @@
import {List, ListWrapper, SetWrapper} from "facade/src/collection";
import {int, FIELD, NumberWrapper, StringJoiner, StringWrapper} from "facade/src/lang";
export const TOKEN_TYPE_CHARACTER = 1;
export const TOKEN_TYPE_IDENTIFIER = 2;
export const TOKEN_TYPE_KEYWORD = 3;
export const TOKEN_TYPE_STRING = 4;
export const TOKEN_TYPE_OPERATOR = 5;
export const TOKEN_TYPE_NUMBER = 6;
export class Lexer {
text:string;
tokenize(text:string):List {
var scanner = new _Scanner(text);
var tokens = [];
var token = scanner.scanToken();
while (token != null) {
ListWrapper.push(tokens, token);
token = scanner.scanToken();
}
return tokens;
}
}
export class Token {
index:int;
type:int;
_numValue:number;
_strValue:string;
constructor(index:int, type:int, numValue:number, strValue:string) {
/**
* NOTE: To ensure that this constructor creates the same hidden class each time, ensure that
* all the fields are assigned to in the exact same order in each run of this constructor.
*/
this.index = index;
this.type = type;
this._numValue = numValue;
this._strValue = strValue;
}
isCharacter(code:int):boolean {
return (this.type == TOKEN_TYPE_CHARACTER && this._numValue == code);
}
isNumber():boolean {
return (this.type == TOKEN_TYPE_NUMBER);
}
isString():boolean {
return (this.type == TOKEN_TYPE_STRING);
}
isOperator(operater:string):boolean {
return (this.type == TOKEN_TYPE_OPERATOR && this._strValue == operater);
}
isIdentifier():boolean {
return (this.type == TOKEN_TYPE_IDENTIFIER);
}
isKeyword():boolean {
return (this.type == TOKEN_TYPE_KEYWORD);
}
isKeywordVar():boolean {
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "var");
}
isKeywordNull():boolean {
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "null");
}
isKeywordUndefined():boolean {
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "undefined");
}
isKeywordTrue():boolean {
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "true");
}
isKeywordFalse():boolean {
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "false");
}
toNumber():number {
// -1 instead of NULL ok?
return (this.type == TOKEN_TYPE_NUMBER) ? this._numValue : -1;
}
toString():string {
var type:int = this.type;
if (type >= TOKEN_TYPE_CHARACTER && type <= TOKEN_TYPE_STRING) {
return this._strValue;
} else if (type == TOKEN_TYPE_NUMBER) {
return this._numValue.toString();
} else {
return null;
}
}
}
function newCharacterToken(index:int, code:int):Token {
return new Token(index, TOKEN_TYPE_CHARACTER, code, StringWrapper.fromCharCode(code));
}
function newIdentifierToken(index:int, text:string):Token {
return new Token(index, TOKEN_TYPE_IDENTIFIER, 0, text);
}
function newKeywordToken(index:int, text:string):Token {
return new Token(index, TOKEN_TYPE_KEYWORD, 0, text);
}
function newOperatorToken(index:int, text:string):Token {
return new Token(index, TOKEN_TYPE_OPERATOR, 0, text);
}
function newStringToken(index:int, text:string):Token {
return new Token(index, TOKEN_TYPE_STRING, 0, text);
}
function newNumberToken(index:int, n:number):Token {
return new Token(index, TOKEN_TYPE_NUMBER, n, "");
}
export var EOF:Token = new Token(-1, 0, 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, $B = 66, $C = 67, $D = 68, $E = 69, $F = 70, $G = 71, $H = 72,
$I = 73, $J = 74, $K = 75, $L = 76, $M = 77, $N = 78, $O = 79, $P = 80,
$Q = 81, $R = 82, $S = 83, $T = 84, $U = 85, $V = 86, $W = 87, $X = 88,
$Y = 89, $Z = 90;
export const $LBRACKET = 91;
export const $BACKSLASH = 92;
export const $RBRACKET = 93;
const $CARET = 94;
const $_ = 95;
const $a = 97, $b = 98, $c = 99, $d = 100, $e = 101, $f = 102, $g = 103,
$h = 104, $i = 105, $j = 106, $k = 107, $l = 108, $m = 109, $n = 110,
$o = 111, $p = 112, $q = 113, $r = 114, $s = 115, $t = 116, $u = 117,
$v = 118, $w = 119, $x = 120, $y = 121, $z = 122;
export const $LBRACE = 123;
export const $BAR = 124;
export const $RBRACE = 125;
const $TILDE = 126;
const $NBSP = 160;
export class ScannerError extends Error {
message:string;
constructor(message) {
this.message = message;
}
toString() {
return this.message;
}
}
class _Scanner {
input:string;
length:int;
peek:int;
index:int;
constructor(input:string) {
this.input = input;
this.length = input.length;
this.peek = 0;
this.index = -1;
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:int = 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:
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
case $PLUS:
case $MINUS:
case $STAR:
case $SLASH:
case $PERCENT:
case $CARET:
case $QUESTION:
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
case $LT:
case $GT:
case $BANG:
case $EQ:
return this.scanComplexOperator(start, $EQ, StringWrapper.fromCharCode(peek), '=');
case $AMPERSAND:
return this.scanComplexOperator(start, $AMPERSAND, '&', '&');
case $BAR:
return this.scanComplexOperator(start, $BAR, '|', '|');
case $TILDE:
return this.scanComplexOperator(start, $SLASH, '~', '/');
case $NBSP:
while (isWhitespace(this.peek)) this.advance();
return this.scanToken();
}
this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`, 0);
return null;
}
scanCharacter(start:int, code:int):Token {
assert(this.peek == code);
this.advance();
return newCharacterToken(start, code);
}
scanOperator(start:int, str:string):Token {
assert(this.peek == StringWrapper.charCodeAt(str, 0));
assert(SetWrapper.has(OPERATORS, str));
this.advance();
return newOperatorToken(start, str);
}
scanComplexOperator(start:int, code:int, one:string, two:string):Token {
assert(this.peek == StringWrapper.charCodeAt(one, 0));
this.advance();
var str:string = one;
if (this.peek == code) {
this.advance();
str += two;
}
assert(SetWrapper.has(OPERATORS, str));
return newOperatorToken(start, str);
}
scanIdentifier():Token {
assert(isIdentifierStart(this.peek));
var start:int = 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:int):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:int = this.index;
var quote:int = this.peek;
this.advance(); // Skip initial quote.
var buffer:StringJoiner;
var marker:int = 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:int;
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:int = 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:int) {
var position:int = this.index + offset;
throw new ScannerError(`Lexer Error: ${message} at column ${position} in expression [${this.input}]`);
}
}
function isWhitespace(code:int):boolean {
return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
}
function isIdentifierStart(code:int):boolean {
return ($a <= code && code <= $z) ||
($A <= code && code <= $Z) ||
(code == $_) ||
(code == $$);
}
function isIdentifierPart(code:int):boolean {
return ($a <= code && code <= $z) ||
($A <= code && code <= $Z) ||
($0 <= code && code <= $9) ||
(code == $_) ||
(code == $$);
}
function isDigit(code:int):boolean {
return $0 <= code && code <= $9;
}
function isExponentStart(code:int):boolean {
return code == $e || code == $E;
}
function isExponentSign(code:int):boolean {
return code == $MINUS || code == $PLUS;
}
function unescape(code:int):int {
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',
]);

View File

@ -0,0 +1,521 @@
import {FIELD, int, isBlank, isPresent, BaseException, StringWrapper, RegExpWrapper} from 'facade/src/lang';
import {ListWrapper, List} from 'facade/src/collection';
import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET,
$COMMA, $LBRACE, $RBRACE, $LPAREN, $RPAREN} from './lexer';
import {reflector, Reflector} from 'reflection/src/reflection';
import {
AST,
EmptyExpr,
ImplicitReceiver,
AccessMember,
LiteralPrimitive,
Expression,
Binary,
PrefixNot,
Conditional,
Formatter,
Assignment,
Chain,
KeyedAccess,
LiteralArray,
LiteralMap,
Interpolation,
MethodCall,
FunctionCall,
TemplateBindings,
TemplateBinding,
ASTWithSource
} from './ast';
var _implicitReceiver = new ImplicitReceiver();
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
var INTERPOLATION_REGEXP = RegExpWrapper.create('\\{\\{(.*?)\\}\\}');
var QUOTE_REGEXP = RegExpWrapper.create("'");
export class Parser {
_lexer:Lexer;
_reflector:Reflector;
constructor(lexer:Lexer, providedReflector:Reflector = null){
this._lexer = lexer;
this._reflector = isPresent(providedReflector) ? providedReflector : reflector;
}
parseAction(input:string, location:any):ASTWithSource {
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 {
var tokens = this._lexer.tokenize(input);
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
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 = parts[i];
if (i%2 === 0) {
// fixed string
ListWrapper.push(strings, part);
} else {
var tokens = this._lexer.tokenize(part);
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
ListWrapper.push(expressions, ast);
}
}
return new ASTWithSource(new Interpolation(strings, expressions), input, location);
}
}
class _ParseAST {
input:string;
location:any;
tokens:List<Token>;
reflector:Reflector;
parseAction:boolean;
index:int;
constructor(input:string, location:any, tokens:List, reflector:Reflector, parseAction:boolean) {
this.input = input;
this.location = location;
this.tokens = tokens;
this.index = 0;
this.reflector = reflector;
this.parseAction = parseAction;
}
peek(offset:int):Token {
var i = this.index + offset;
return i < this.tokens.length ? this.tokens[i] : EOF;
}
get next():Token {
return this.peek(0);
}
get inputIndex():int {
return (this.index < this.tokens.length) ? this.next.index : this.input.length;
}
advance() {
this.index ++;
}
optionalCharacter(code:int):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:int) {
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.parseFormatter();
ListWrapper.push(exprs, 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);
}
parseFormatter() {
var result = this.parseExpression();
while (this.optionalOperator("|")) {
if (this.parseAction) {
this.error("Cannot have a formatter in an action expression");
}
var name = this.expectIdentifierOrKeyword();
var args = ListWrapper.create();
while (this.optionalCharacter($COLON)) {
ListWrapper.push(args, this.parseExpression());
}
result = new Formatter(result, name, args);
}
return result;
}
parseExpression() {
var start = this.inputIndex;
var result = this.parseConditional();
while (this.next.isOperator('=')) {
if (!result.isAssignable) {
var end = this.inputIndex;
var expression = this.input.substring(start, end);
this.error(`Expression ${expression} is not assignable`);
}
if (!this.parseAction) {
this.error("Binding expression cannot contain assignments");
}
this.expectOperator('=');
result = new Assignment(result, this.parseConditional());
}
return result;
}
parseConditional() {
var start = this.inputIndex;
var result = this.parseLogicalOr();
if (this.optionalOperator('?')) {
var yes = this.parseExpression();
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.parseExpression();
return new Conditional(result, yes, no);
} else {
return result;
}
}
parseLogicalOr() {
// '||'
var result = this.parseLogicalAnd();
while (this.optionalOperator('||')) {
result = new Binary('||', result, this.parseLogicalAnd());
}
return result;
}
parseLogicalAnd() {
// '&&'
var result = this.parseEquality();
while (this.optionalOperator('&&')) {
result = new Binary('&&', result, this.parseEquality());
}
return result;
}
parseEquality() {
// '==','!='
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 {
return result;
}
}
}
parseRelational() {
// '<', '>', '<=', '>='
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() {
// '+', '-'
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() {
// '*', '%', '/'
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() {
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);
} else if (this.optionalCharacter($LBRACKET)) {
var key = this.parseExpression();
this.expectCharacter($RBRACKET);
result = new KeyedAccess(result, key);
} else if (this.optionalCharacter($LPAREN)) {
var args = this.parseCallArguments();
this.expectCharacter($RPAREN);
result = new FunctionCall(result, args);
} else {
return result;
}
}
}
parsePrimary() {
if (this.optionalCharacter($LPAREN)) {
var result = this.parseFormatter();
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.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);
} else if (this.next.isNumber()) {
var value = this.next.toNumber();
this.advance();
return new LiteralPrimitive(value);
} else if (this.next.isString()) {
var value = this.next.toString();
this.advance();
return new LiteralPrimitive(value);
} else if (this.index >= this.tokens.length) {
this.error(`Unexpected end of expression: ${this.input}`);
} else {
this.error(`Unexpected token ${this.next}`);
}
}
parseExpressionList(terminator:int):List {
var result = [];
if (!this.next.isCharacter(terminator)) {
do {
ListWrapper.push(result, this.parseExpression());
} while (this.optionalCharacter($COMMA));
}
return result;
}
parseLiteralMap() {
var keys = [];
var values = [];
this.expectCharacter($LBRACE);
if (!this.optionalCharacter($RBRACE)) {
do {
var key = this.expectIdentifierOrKeywordOrString();
ListWrapper.push(keys, key);
this.expectCharacter($COLON);
ListWrapper.push(values, this.parseExpression());
} while (this.optionalCharacter($COMMA));
this.expectCharacter($RBRACE);
}
return new LiteralMap(keys, values);
}
parseAccessMemberOrMethodCall(receiver):AST {
var id = this.expectIdentifierOrKeyword();
if (this.optionalCharacter($LPAREN)) {
var args = this.parseCallArguments();
this.expectCharacter($RPAREN);
var fn = this.reflector.method(id);
return new MethodCall(receiver, id, fn, args);
} else {
var getter = this.reflector.getter(id);
var setter = this.reflector.setter(id);
return new AccessMember(receiver, id, getter, setter);
}
}
parseCallArguments() {
if (this.next.isCharacter($RPAREN)) return [];
var positionals = [];
do {
ListWrapper.push(positionals, this.parseExpression());
} while (this.optionalCharacter($COMMA))
return positionals;
}
/**
* An identifier, a keyword, a string with an optional `-` inbetween.
*/
expectTemplateBindingKey() {
var result = '';
var operatorFound = false;
do {
result += this.expectIdentifierOrKeywordOrString();
operatorFound = this.optionalOperator('-');
if (operatorFound) {
result += '-';
}
} while (operatorFound);
return result.toString();
}
parseTemplateBindings() {
var bindings = [];
while (this.index < this.tokens.length) {
var keyIsVar:boolean = this.optionalKeywordVar();
var key = this.expectTemplateBindingKey();
this.optionalCharacter($COLON);
var name = null;
var expression = null;
if (this.next !== EOF) {
if (keyIsVar) {
if (this.optionalOperator("=")) {
name = this.expectTemplateBindingKey();
} else {
name = '\$implicit';
}
} else if (!this.peekKeywordVar()) {
var start = this.inputIndex;
var ast = this.parseExpression();
var source = this.input.substring(start, this.inputIndex);
expression = new ASTWithSource(ast, source, this.location);
}
}
ListWrapper.push(bindings, new TemplateBinding(key, keyIsVar, name, expression));
if (!this.optionalCharacter($SEMICOLON)) {
this.optionalCharacter($COMMA);
};
}
return bindings;
}
error(message:string, index:int = 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 BaseException(`Parser Error: ${message} ${location} [${this.input}] in ${this.location}`);
}
}

View File

@ -0,0 +1,390 @@
import {isPresent, isBlank, BaseException, Type, isString} from 'facade/src/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/src/collection';
import {
AccessMember,
Assignment,
AST,
ASTWithSource,
AstVisitor,
Binary,
Chain,
Structural,
Conditional,
Formatter,
FunctionCall,
ImplicitReceiver,
Interpolation,
KeyedAccess,
LiteralArray,
LiteralMap,
LiteralPrimitive,
MethodCall,
PrefixNot
} from './parser/ast';
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
import {ChangeRecord, ChangeDispatcher, ChangeDetector} from './interfaces';
import {ChangeDetectionUtil} from './change_detection_util';
import {DynamicChangeDetector} from './dynamic_change_detector';
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
import {ArrayChanges} from './array_changes';
import {KeyValueChanges} from './keyvalue_changes';
import {coalesce} from './coalesce';
export const RECORD_TYPE_SELF = 0;
export const RECORD_TYPE_CONST = 1;
export const RECORD_TYPE_PRIMITIVE_OP = 2;
export const RECORD_TYPE_PROPERTY = 3;
export const RECORD_TYPE_INVOKE_METHOD = 4;
export const RECORD_TYPE_INVOKE_CLOSURE = 5;
export const RECORD_TYPE_KEYED_ACCESS = 6;
export const RECORD_TYPE_INVOKE_FORMATTER = 7;
export const RECORD_TYPE_STRUCTURAL_CHECK = 8;
export const RECORD_TYPE_INTERPOLATE = 9;
export class ProtoRecord {
mode:number;
name:string;
funcOrValue:any;
args:List;
fixedArgs:List;
contextIndex:number;
selfIndex:number;
bindingMemento:any;
groupMemento:any;
lastInBinding:boolean;
lastInGroup:boolean;
expressionAsString:string;
constructor(mode:number,
name:string,
funcOrValue,
args:List,
fixedArgs:List,
contextIndex:number,
selfIndex:number,
bindingMemento:any,
groupMemento:any,
expressionAsString:string,
lastInBinding:boolean,
lastInGroup:boolean) {
this.mode = mode;
this.name = name;
this.funcOrValue = funcOrValue;
this.args = args;
this.fixedArgs = fixedArgs;
this.contextIndex = contextIndex;
this.selfIndex = selfIndex;
this.bindingMemento = bindingMemento;
this.groupMemento = groupMemento;
this.lastInBinding = lastInBinding;
this.lastInGroup = lastInGroup;
this.expressionAsString = expressionAsString;
}
isPureFunction():boolean {
return this.mode === RECORD_TYPE_INTERPOLATE ||
this.mode === RECORD_TYPE_INVOKE_FORMATTER ||
this.mode === RECORD_TYPE_PRIMITIVE_OP;
}
}
export class ProtoChangeDetector {
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false){}
instantiate(dispatcher:any, formatters:Map):ChangeDetector{
return null;
}
}
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
_records:List<ProtoRecord>;
_recordBuilder:ProtoRecordBuilder;
constructor() {
this._records = null;
this._recordBuilder = new ProtoRecordBuilder();
}
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
this._recordBuilder.addAst(ast, bindingMemento, groupMemento, structural);
}
instantiate(dispatcher:any, formatters:Map) {
this._createRecordsIfNecessary();
return new DynamicChangeDetector(dispatcher, formatters, this._records);
}
_createRecordsIfNecessary() {
if (isBlank(this._records)) {
var records = this._recordBuilder.records;
this._records = coalesce(records);
}
}
}
var _jitProtoChangeDetectorClassCounter:number = 0;
export class JitProtoChangeDetector extends ProtoChangeDetector {
_factory:Function;
_recordBuilder:ProtoRecordBuilder;
constructor() {
this._factory = null;
this._recordBuilder = new ProtoRecordBuilder();
}
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
this._recordBuilder.addAst(ast, bindingMemento, groupMemento, structural);
}
instantiate(dispatcher:any, formatters:Map) {
this._createFactoryIfNecessary();
return this._factory(dispatcher, formatters);
}
_createFactoryIfNecessary() {
if (isBlank(this._factory)) {
var c = _jitProtoChangeDetectorClassCounter++;
var records = coalesce(this._recordBuilder.records);
var typeName = `ChangeDetector${c}`;
this._factory = new ChangeDetectorJITGenerator(typeName, records).generate();
}
}
}
class ProtoRecordBuilder {
records:List<ProtoRecord>;
constructor() {
this.records = [];
}
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
if (structural) ast = new Structural(ast);
var last = ListWrapper.last(this.records);
if (isPresent(last) && last.groupMemento == groupMemento) {
last.lastInGroup = false;
}
var pr = _ConvertAstIntoProtoRecords.convert(ast, bindingMemento, groupMemento, this.records.length);
if (! ListWrapper.isEmpty(pr)) {
var last = ListWrapper.last(pr);
last.lastInBinding = true;
last.lastInGroup = true;
this.records = ListWrapper.concat(this.records, pr);
}
}
}
class _ConvertAstIntoProtoRecords {
protoRecords:List;
bindingMemento:any;
groupMemento:any;
contextIndex:number;
expressionAsString:string;
constructor(bindingMemento:any, groupMemento:any, contextIndex:number, expressionAsString:string) {
this.protoRecords = [];
this.bindingMemento = bindingMemento;
this.groupMemento = groupMemento;
this.contextIndex = contextIndex;
this.expressionAsString = expressionAsString;
}
static convert(ast:AST, bindingMemento:any, groupMemento:any, contextIndex:number) {
var c = new _ConvertAstIntoProtoRecords(bindingMemento, groupMemento, contextIndex, ast.toString());
ast.visit(c);
return c.protoRecords;
}
visitImplicitReceiver(ast:ImplicitReceiver) {
return 0;
}
visitInterpolation(ast:Interpolation) {
var args = this._visitAll(ast.expressions);
return this._addRecord(RECORD_TYPE_INTERPOLATE, "interpolate", _interpolationFn(ast.strings),
args, ast.strings, 0);
}
visitLiteralPrimitive(ast:LiteralPrimitive) {
return this._addRecord(RECORD_TYPE_CONST, "literal", ast.value, [], null, 0);
}
visitAccessMember(ast:AccessMember) {
var receiver = ast.receiver.visit(this);
return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], null, receiver);
}
visitFormatter(ast:Formatter) {
return this._addRecord(RECORD_TYPE_INVOKE_FORMATTER, ast.name, ast.name, this._visitAll(ast.allArgs), null, 0);
}
visitMethodCall(ast:MethodCall) {
var receiver = ast.receiver.visit(this);
var args = this._visitAll(ast.args);
return this._addRecord(RECORD_TYPE_INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
}
visitFunctionCall(ast:FunctionCall) {
var target = ast.target.visit(this);
var args = this._visitAll(ast.args);
return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, "closure", null, args, null, target);
}
visitLiteralArray(ast:LiteralArray) {
var primitiveName = `arrayFn${ast.expressions.length}`;
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, primitiveName, _arrayFn(ast.expressions.length),
this._visitAll(ast.expressions), null, 0);
}
visitLiteralMap(ast:LiteralMap) {
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _mapPrimitiveName(ast.keys),
ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null, 0);
}
visitBinary(ast:Binary) {
var left = ast.left.visit(this);
var right = ast.right.visit(this);
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _operationToPrimitiveName(ast.operation),
_operationToFunction(ast.operation), [left, right], null, 0);
}
visitPrefixNot(ast:PrefixNot) {
var exp = ast.expression.visit(this)
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, "operation_negate",
ChangeDetectionUtil.operation_negate, [exp], null, 0);
}
visitConditional(ast:Conditional) {
var c = ast.condition.visit(this);
var t = ast.trueExp.visit(this);
var f = ast.falseExp.visit(this);
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, "cond",
ChangeDetectionUtil.cond, [c,t,f], null, 0);
}
visitStructural(ast:Structural) {
var value = ast.value.visit(this);
return this._addRecord(RECORD_TYPE_STRUCTURAL_CHECK, "structural", null, [], null, value);
}
visitKeyedAccess(ast:KeyedAccess) {
var obj = ast.obj.visit(this);
var key = ast.key.visit(this);
return this._addRecord(RECORD_TYPE_KEYED_ACCESS, "keyedAccess",
ChangeDetectionUtil.keyedAccess, [key], null, obj);
}
_visitAll(asts:List) {
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.contextIndex;
ListWrapper.push(this.protoRecords,
new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, selfIndex,
this.bindingMemento, this.groupMemento, this.expressionAsString, false, false));
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) {
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_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_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) {
return isPresent(v) ? '' + v : '';
}
function _interpolationFn(strings:List) {
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,142 @@
import {ABSTRACT, CONST, normalizeBlank} from 'facade/src/lang';
import {List} from 'facade/src/collection';
import {TemplateConfig} from './template_config';
import {ShadowDomStrategy} from '../compiler/shadow_dom';
@ABSTRACT()
export class Directive {
selector:any; //string;
bind:any;
lightDomServices:any; //List;
implementsTypes:any; //List;
lifecycle:any; //List
@CONST()
constructor({
selector,
bind,
lightDomServices,
implementsTypes,
lifecycle
}:{
selector:string,
bind:any,
lightDomServices:List,
implementsTypes:List,
lifecycle:List
}={})
{
this.selector = selector;
this.lightDomServices = lightDomServices;
this.implementsTypes = implementsTypes;
this.bind = bind;
this.lifecycle = lifecycle;
}
}
export class Component extends Directive {
//TODO: vsavkin: uncomment it once the issue with defining fields in a sublass works
template:any; //TemplateConfig;
lightDomServices:any; //List;
shadowDomServices:any; //List;
componentServices:any; //List;
shadowDom:any; //ShadowDomStrategy;
lifecycle:any; //List
@CONST()
constructor({
selector,
bind,
template,
lightDomServices,
shadowDomServices,
componentServices,
implementsTypes,
shadowDom,
lifecycle
}:{
selector:String,
bind:Object,
template:TemplateConfig,
lightDomServices:List,
shadowDomServices:List,
componentServices:List,
implementsTypes:List,
shadowDom:ShadowDomStrategy,
lifecycle:List
}={})
{
super({
selector: selector,
bind: bind,
lightDomServices: lightDomServices,
implementsTypes: implementsTypes,
lifecycle: lifecycle
});
this.template = template;
this.lightDomServices = lightDomServices;
this.shadowDomServices = shadowDomServices;
this.componentServices = componentServices;
this.shadowDom = shadowDom;
this.lifecycle = lifecycle;
}
}
export class Decorator extends Directive {
compileChildren: boolean;
@CONST()
constructor({
selector,
bind,
lightDomServices,
implementsTypes,
lifecycle,
compileChildren = true,
}:{
selector:string,
bind:any,
lightDomServices:List,
implementsTypes:List,
lifecycle:List,
compileChildren:boolean
}={})
{
this.compileChildren = compileChildren;
super({
selector: selector,
bind: bind,
lightDomServices: lightDomServices,
implementsTypes: implementsTypes,
lifecycle: lifecycle
});
}
}
export class Template extends Directive {
@CONST()
constructor({
selector,
bind,
lightDomServices,
implementsTypes,
lifecycle
}:{
selector:string,
bind:any,
lightDomServices:List,
implementsTypes:List,
lifecycle:List
}={})
{
super({
selector: selector,
bind: bind,
lightDomServices: lightDomServices,
implementsTypes: implementsTypes,
lifecycle: lifecycle
});
}
}
export var onDestroy = "onDestroy";

View File

@ -0,0 +1,14 @@
import {CONST} from 'facade/src/lang';
import {DependencyAnnotation} from 'di/di';
/**
* The directive can inject an emitter function that would emit events onto the
* directive host element.
*/
export class EventEmitter extends DependencyAnnotation {
eventName: string;
@CONST()
constructor(eventName) {
this.eventName = eventName;
}
}

View File

@ -0,0 +1,31 @@
import {ABSTRACT, CONST, Type} from 'facade/src/lang';
import {List} from 'facade/src/collection';
export class TemplateConfig {
url:any; //string;
inline:any; //string;
directives:any; //List<Type>;
formatters:any; //List<Type>;
source:any;//List<TemplateConfig>;
@CONST()
constructor({
url,
inline,
directives,
formatters,
source
}: {
url: string,
inline: string,
directives: List<Type>,
formatters: List<Type>,
source: List<TemplateConfig>
})
{
this.url = url;
this.inline = inline;
this.directives = directives;
this.formatters = formatters;
this.source = source;
}
}

View File

@ -0,0 +1,22 @@
import {CONST} from 'facade/src/lang';
import {DependencyAnnotation} from 'di/di';
/**
* The directive can only be injected from the current element
* or from its parent.
*/
export class Parent extends DependencyAnnotation {
@CONST()
constructor() {
}
}
/**
* The directive can only be injected from the current element
* or from its ancestor.
*/
export class Ancestor extends DependencyAnnotation {
@CONST()
constructor() {
}
}

126
modules/angular2/src/core/application.js vendored Normal file
View File

@ -0,0 +1,126 @@
import {Injector, bind, OpaqueToken} from 'di/di';
import {Type, FIELD, isBlank, isPresent, BaseException, assertionsEnabled, print} from 'facade/src/lang';
import {DOM, Element} from 'facade/src/dom';
import {Compiler, CompilerCache} from './compiler/compiler';
import {ProtoView} from './compiler/view';
import {Reflector, reflector} from 'reflection/src/reflection';
import {Parser, Lexer, ChangeDetection, dynamicChangeDetection, jitChangeDetection} from 'change_detection/change_detection';
import {TemplateLoader} from './compiler/template_loader';
import {DirectiveMetadataReader} from './compiler/directive_metadata_reader';
import {DirectiveMetadata} from './compiler/directive_metadata';
import {List, ListWrapper} from 'facade/src/collection';
import {PromiseWrapper} from 'facade/src/async';
import {VmTurnZone} from 'core/src/zone/vm_turn_zone';
import {LifeCycle} from 'core/src/life_cycle/life_cycle';
var _rootInjector: Injector;
// Contains everything that is safe to share between applications.
var _rootBindings = [
bind(Reflector).toValue(reflector),
bind(ChangeDetection).toValue(dynamicChangeDetection),
Compiler,
CompilerCache,
TemplateLoader,
DirectiveMetadataReader,
Parser,
Lexer
];
export var appViewToken = new OpaqueToken('AppView');
export var appChangeDetectorToken = new OpaqueToken('AppChangeDetector');
export var appElementToken = new OpaqueToken('AppElement');
export var appComponentAnnotatedTypeToken = new OpaqueToken('AppComponentAnnotatedType');
export var appDocumentToken = new OpaqueToken('AppDocument');
function _injectorBindings(appComponentType) {
return [
bind(appDocumentToken).toValue(DOM.defaultDoc()),
bind(appComponentAnnotatedTypeToken).toFactory((reader) => {
// TODO(rado): inspect annotation here and warn if there are bindings,
// lightDomServices, and other component annotations that are skipped
// for bootstrapping components.
return reader.read(appComponentType);
}, [DirectiveMetadataReader]),
bind(appElementToken).toFactory((appComponentAnnotatedType, appDocument) => {
var selector = appComponentAnnotatedType.annotation.selector;
var element = DOM.querySelector(appDocument, selector);
if (isBlank(element)) {
throw new BaseException(`The app selector "${selector}" did not match any elements`);
}
return element;
}, [appComponentAnnotatedTypeToken, appDocumentToken]),
bind(appViewToken).toAsyncFactory((changeDetection, compiler, injector, appElement,
appComponentAnnotatedType) => {
return compiler.compile(appComponentAnnotatedType.type, null).then(
(protoView) => {
var appProtoView = ProtoView.createRootProtoView(protoView,
appElement, appComponentAnnotatedType, changeDetection.createProtoChangeDetector('root'));
// The light Dom of the app element is not considered part of
// the angular application. Thus the context and lightDomInjector are
// empty.
var view = appProtoView.instantiate(null);
view.hydrate(injector, null, new Object());
return view;
});
}, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken]),
bind(appChangeDetectorToken).toFactory((rootView) => rootView.changeDetector,
[appViewToken]),
bind(appComponentType).toFactory((rootView) => rootView.elementInjectors[0].getComponent(),
[appViewToken]),
bind(LifeCycle).toFactory((cd) => new LifeCycle(cd, assertionsEnabled()), [appChangeDetectorToken])
];
}
function _createVmZone(givenReporter:Function){
var defaultErrorReporter = (exception, stackTrace) => {
var longStackTrace = ListWrapper.join(stackTrace, "\n\n-----async gap-----\n");
print(`${exception}\n\n${longStackTrace}`);
throw exception;
};
var reporter = isPresent(givenReporter) ? givenReporter : defaultErrorReporter;
var zone = new VmTurnZone({enableLongStackTrace: assertionsEnabled()});
zone.initCallbacks({onErrorHandler: reporter});
return zone;
}
// Multiple calls to this method are allowed. Each application would only share
// _rootInjector, which is not user-configurable by design, thus safe to share.
export function bootstrap(appComponentType: Type, bindings=null, givenBootstrapErrorReporter=null) {
var bootstrapProcess = PromiseWrapper.completer();
var zone = _createVmZone(givenBootstrapErrorReporter);
zone.run(() => {
// TODO(rado): prepopulate template cache, so applications with only
// index.html and main.js are possible.
var appInjector = _createAppInjector(appComponentType, bindings);
PromiseWrapper.then(appInjector.asyncGet(LifeCycle),
(lc) => {
lc.registerWith(zone);
lc.tick(); //the first tick that will bootstrap the app
bootstrapProcess.complete(appInjector);
},
(err) => {
bootstrapProcess.reject(err)
});
});
return bootstrapProcess.promise;
}
function _createAppInjector(appComponentType: Type, bindings: List): Injector {
if (isBlank(_rootInjector)) _rootInjector = new Injector(_rootBindings);
var mergedBindings = isPresent(bindings) ?
ListWrapper.concat(_injectorBindings(appComponentType), bindings) :
_injectorBindings(appComponentType);
return _rootInjector.createChild(mergedBindings);
}

View File

@ -0,0 +1,25 @@
import {ChangeDetector, CHECK_ONCE, DETACHED, CHECK_ALWAYS} from 'change_detection/change_detection';
export class BindingPropagationConfig {
_cd:ChangeDetector;
constructor(cd:ChangeDetector) {
this._cd = cd;
}
shouldBePropagated() {
this._cd.mode = CHECK_ONCE;
}
shouldBePropagatedFromRoot() {
this._cd.markPathToRootAsCheckOnce();
}
shouldNotPropagate() {
this._cd.mode = DETACHED;
}
shouldAlwaysPropagate() {
this._cd.mode = CHECK_ALWAYS;
}
}

View File

@ -0,0 +1,111 @@
import {Type, FIELD, isBlank, isPresent, BaseException, stringify} from 'facade/src/lang';
import {Promise, PromiseWrapper} from 'facade/src/async';
import {List, ListWrapper, MapWrapper} from 'facade/src/collection';
import {DOM, Element} from 'facade/src/dom';
import {ChangeDetection, Parser} from 'change_detection/change_detection';
import {DirectiveMetadataReader} from './directive_metadata_reader';
import {ProtoView} from './view';
import {CompilePipeline} from './pipeline/compile_pipeline';
import {CompileElement} from './pipeline/compile_element';
import {createDefaultSteps} from './pipeline/default_steps';
import {TemplateLoader} from './template_loader';
import {DirectiveMetadata} from './directive_metadata';
import {Component} from '../annotations/annotations';
import {Content} from './shadow_dom_emulation/content_tag';
/**
* Cache that stores the ProtoView of the template of a component.
* Used to prevent duplicate work and resolve cyclic dependencies.
*/
export class CompilerCache {
_cache:Map;
constructor() {
this._cache = MapWrapper.create();
}
set(component:Type, protoView:ProtoView) {
MapWrapper.set(this._cache, component, protoView);
}
get(component:Type):ProtoView {
var result = MapWrapper.get(this._cache, component);
if (isBlank(result)) {
// need to normalize undefined to null so that type checking passes :-(
return null;
}
return result;
}
clear() {
this._cache = MapWrapper.create();
}
}
/**
* The compiler loads and translates the html templates of components into
* nested ProtoViews. To decompose its functionality it uses
* the CompilePipeline and the CompileSteps.
*/
export class Compiler {
_reader: DirectiveMetadataReader;
_parser:Parser;
_compilerCache:CompilerCache;
_changeDetection:ChangeDetection;
constructor(changeDetection:ChangeDetection, templateLoader:TemplateLoader, reader: DirectiveMetadataReader, parser:Parser, cache:CompilerCache) {
this._changeDetection = changeDetection;
this._reader = reader;
this._parser = parser;
this._compilerCache = cache;
}
createSteps(component:DirectiveMetadata):List<CompileStep> {
var dirs = ListWrapper.map(component.componentDirectives, (d) => this._reader.read(d));
return createDefaultSteps(this._changeDetection, this._parser, component, dirs);
}
compile(component:Type, templateRoot:Element = null):Promise<ProtoView> {
var templateCache = null;
// TODO load all components that have urls
// transitively via the _templateLoader and store them in templateCache
return PromiseWrapper.resolve(this.compileAllLoaded(
templateCache, this._reader.read(component), templateRoot)
);
}
// public so that we can compile in sync in performance tests.
compileAllLoaded(templateCache, component:DirectiveMetadata, templateRoot:Element = null):ProtoView {
var rootProtoView = this._compilerCache.get(component.type);
if (isPresent(rootProtoView)) {
return rootProtoView;
}
if (isBlank(templateRoot)) {
// TODO: read out the cache if templateRoot = null. Could contain:
// - templateRoot string
// - precompiled template
// - ProtoView
var annotation:any = component.annotation;
templateRoot = DOM.createTemplate(annotation.template.inline);
}
var pipeline = new CompilePipeline(this.createSteps(component));
var compileElements = pipeline.process(templateRoot);
rootProtoView = compileElements[0].inheritedProtoView;
// Save the rootProtoView before we recurse so that we are able
// to compile components that use themselves in their template.
this._compilerCache.set(component.type, rootProtoView);
for (var i=0; i<compileElements.length; i++) {
var ce = compileElements[i];
if (isPresent(ce.componentDirective)) {
ce.inheritedElementBinder.nestedProtoView = this.compileAllLoaded(templateCache, ce.componentDirective, null);
}
}
return rootProtoView;
}
}

View File

@ -0,0 +1,22 @@
import {Type} from 'facade/src/lang';
import {Directive} from '../annotations/annotations'
import {List} from 'facade/src/collection'
import {ShadowDomStrategy} from './shadow_dom';
/**
* Combination of a type with the Directive annotation
*/
export class DirectiveMetadata {
type:Type;
annotation:Directive;
shadowDomStrategy:ShadowDomStrategy;
componentDirectives:List<Type>;
constructor(type:Type, annotation:Directive, shadowDomStrategy:ShadowDomStrategy,
componentDirectives:List<Type>) {
this.annotation = annotation;
this.type = type;
this.shadowDomStrategy = shadowDomStrategy;
this.componentDirectives = componentDirectives;
}
}

View File

@ -0,0 +1,48 @@
import {Type, isPresent, BaseException, stringify} from 'facade/src/lang';
import {List, ListWrapper} from 'facade/src/collection';
import {Directive, Component} from '../annotations/annotations';
import {DirectiveMetadata} from './directive_metadata';
import {reflector} from 'reflection/src/reflection';
import {ShadowDom, ShadowDomStrategy, ShadowDomNative} from './shadow_dom';
export class DirectiveMetadataReader {
read(type:Type):DirectiveMetadata {
var annotations = reflector.annotations(type);
if (isPresent(annotations)) {
for (var i=0; i<annotations.length; i++) {
var annotation = annotations[i];
if (annotation instanceof Component) {
var shadowDomStrategy = this.parseShadowDomStrategy(annotation);
return new DirectiveMetadata(
type,
annotation,
shadowDomStrategy,
this.componentDirectivesMetadata(annotation, shadowDomStrategy)
);
}
if (annotation instanceof Directive) {
return new DirectiveMetadata(type, annotation, null, null);
}
}
}
throw new BaseException(`No Directive annotation found on ${stringify(type)}`);
}
parseShadowDomStrategy(annotation:Component):ShadowDomStrategy{
return isPresent(annotation.shadowDom) ? annotation.shadowDom : ShadowDomNative;
}
componentDirectivesMetadata(annotation:Component, shadowDomStrategy:ShadowDomStrategy):List<Type> {
var polyDirs = shadowDomStrategy.polyfillDirectives();
var template = annotation.template;
var templateDirs = isPresent(template) && isPresent(template.directives) ? template.directives : [];
var res = [];
res = ListWrapper.concat(res, templateDirs)
res = ListWrapper.concat(res, polyDirs)
return res;
}
}

View File

@ -0,0 +1,30 @@
import {ProtoElementInjector} from './element_injector';
import {FIELD} from 'facade/src/lang';
import {MapWrapper} from 'facade/src/collection';
import {DirectiveMetadata} from './directive_metadata';
import {List, Map} from 'facade/src/collection';
import {ProtoView} from './view';
export class ElementBinder {
protoElementInjector:ProtoElementInjector;
componentDirective:DirectiveMetadata;
templateDirective:DirectiveMetadata;
textNodeIndices:List<int>;
hasElementPropertyBindings:boolean;
nestedProtoView: ProtoView;
events:Map;
constructor(
protoElementInjector: ProtoElementInjector, componentDirective:DirectiveMetadata, templateDirective:DirectiveMetadata) {
this.protoElementInjector = protoElementInjector;
this.componentDirective = componentDirective;
this.templateDirective = templateDirective;
// updated later when events are bound
this.events = null;
// updated later when text nodes are bound
this.textNodeIndices = null;
// updated later when element properties are bound
this.hasElementPropertyBindings = false;
// updated later, so we are able to resolve cycles
this.nestedProtoView = null;
}
}

View File

@ -0,0 +1,602 @@
import {FIELD, isPresent, isBlank, Type, int, BaseException} from 'facade/src/lang';
import {Math} from 'facade/src/math';
import {List, ListWrapper, MapWrapper} from 'facade/src/collection';
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'di/di';
import {Parent, Ancestor} from 'core/src/annotations/visibility';
import {EventEmitter} from 'core/src/annotations/events';
import {onDestroy} from 'core/src/annotations/annotations';
import {View, ProtoView} from 'core/src/compiler/view';
import {LightDom, SourceLightDom, DestinationLightDom} from 'core/src/compiler/shadow_dom_emulation/light_dom';
import {ViewPort} from 'core/src/compiler/viewport';
import {NgElement} from 'core/src/dom/element';
import {Directive} from 'core/src/annotations/annotations'
import {BindingPropagationConfig} from 'core/src/compiler/binding_propagation_config'
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
var MAX_DEPTH = Math.pow(2, 30) - 1;
var _undefined = new Object();
var _staticKeys;
class StaticKeys {
viewId:number;
ngElementId:number;
viewPortId:number;
destinationLightDomId:number;
sourceLightDomId:number;
bindingPropagationConfigId:number;
constructor() {
//TODO: vsavkin Key.annotate(Key.get(View), 'static')
this.viewId = Key.get(View).id;
this.ngElementId = Key.get(NgElement).id;
this.viewPortId = Key.get(ViewPort).id;
this.destinationLightDomId = Key.get(DestinationLightDom).id;
this.sourceLightDomId = Key.get(SourceLightDom).id;
this.bindingPropagationConfigId = Key.get(BindingPropagationConfig).id;
}
static instance() {
if (isBlank(_staticKeys)) _staticKeys = new StaticKeys();
return _staticKeys;
}
}
class TreeNode {
_parent:TreeNode;
_head:TreeNode;
_tail:TreeNode;
_next:TreeNode;
constructor(parent:TreeNode) {
this._parent = parent;
this._head = null;
this._tail = null;
this._next = null;
if (isPresent(parent)) parent._addChild(this);
}
/**
* Adds a child to the parent node. The child MUST NOT be a part of a tree.
*/
_addChild(child:TreeNode) {
if (isPresent(this._tail)) {
this._tail._next = child;
this._tail = child;
} else {
this._tail = this._head = child;
}
}
get parent() {
return this._parent;
}
set parent(node:TreeNode) {
this._parent = node;
}
get children() {
var res = [];
var child = this._head;
while (child != null) {
ListWrapper.push(res, child);
child = child._next;
}
return res;
}
}
export class DirectiveDependency extends Dependency {
depth:int;
eventEmitterName:string;
constructor(key:Key, asPromise:boolean, lazy:boolean, properties:List, depth:int, eventEmitterName: string) {
super(key, asPromise, lazy, properties);
this.depth = depth;
this.eventEmitterName = eventEmitterName;
}
static createFrom(d:Dependency):Dependency {
return new DirectiveDependency(d.key, d.asPromise, d.lazy,
d.properties, DirectiveDependency._depth(d.properties),
DirectiveDependency._eventEmitterName(d.properties));
}
static _depth(properties):int {
if (properties.length == 0) return 0;
if (ListWrapper.any(properties, p => p instanceof Parent)) return 1;
if (ListWrapper.any(properties, p => p instanceof Ancestor)) return MAX_DEPTH;
return 0;
}
static _eventEmitterName(properties):string {
for (var i = 0; i < properties.length; i++) {
if (properties[i] instanceof EventEmitter) {
return properties[i].eventName;
}
}
return null;
}
}
export class DirectiveBinding extends Binding {
callOnDestroy:boolean;
constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean, callOnDestroy:boolean) {
super(key, factory, dependencies, providedAsPromise);
this.callOnDestroy = callOnDestroy;
}
static createFromBinding(b:Binding, annotation:Directive):Binding {
var deps = ListWrapper.map(b.dependencies, DirectiveDependency.createFrom);
var callOnDestroy = isPresent(annotation) && isPresent(annotation.lifecycle) ?
ListWrapper.contains(annotation.lifecycle, onDestroy) :
false;
return new DirectiveBinding(b.key, b.factory, deps, b.providedAsPromise, callOnDestroy);
}
static createFromType(type:Type, annotation:Directive):Binding {
var binding = bind(type).toClass(type);
return DirectiveBinding.createFromBinding(binding, annotation);
}
static _hasEventEmitter(eventName: string, binding: DirectiveBinding) {
return ListWrapper.any(binding.dependencies, (d) => (d.eventEmitterName == eventName));
}
}
// TODO(rado): benchmark and consider rolling in as ElementInjector fields.
export class PreBuiltObjects {
view:View;
element:NgElement;
viewPort:ViewPort;
lightDom:LightDom;
bindingPropagationConfig:BindingPropagationConfig;
constructor(view, element:NgElement, viewPort:ViewPort, lightDom:LightDom,
bindingPropagationConfig:BindingPropagationConfig) {
this.view = view;
this.element = element;
this.viewPort = viewPort;
this.lightDom = lightDom;
this.bindingPropagationConfig = bindingPropagationConfig;
}
}
/**
Difference between di.Injector and ElementInjector
di.Injector:
- imperative based (can create child injectors imperativly)
- Lazy loading of code
- Component/App Level services which are usually not DOM Related.
ElementInjector:
- ProtoBased (Injector structure fixed at compile time)
- understands @Ancestor, @Parent, @Child, @Descendent
- Fast
- Query mechanism for children
- 1:1 to DOM structure.
PERF BENCHMARK: http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/
*/
export class ProtoElementInjector {
_binding0:Binding;
_binding1:Binding;
_binding2:Binding;
_binding3:Binding;
_binding4:Binding;
_binding5:Binding;
_binding6:Binding;
_binding7:Binding;
_binding8:Binding;
_binding9:Binding;
_binding0IsComponent:boolean;
_keyId0:int;
_keyId1:int;
_keyId2:int;
_keyId3:int;
_keyId4:int;
_keyId5:int;
_keyId6:int;
_keyId7:int;
_keyId8:int;
_keyId9:int;
parent:ProtoElementInjector;
index:int;
view:View;
distanceToParent:number;
constructor(parent:ProtoElementInjector, index:int, bindings:List, firstBindingIsComponent:boolean = false, distanceToParent:number = 0) {
this.parent = parent;
this.index = index;
this.distanceToParent = distanceToParent;
this._binding0IsComponent = firstBindingIsComponent;
this._binding0 = null; this._keyId0 = null;
this._binding1 = null; this._keyId1 = null;
this._binding2 = null; this._keyId2 = null;
this._binding3 = null; this._keyId3 = null;
this._binding4 = null; this._keyId4 = null;
this._binding5 = null; this._keyId5 = null;
this._binding6 = null; this._keyId6 = null;
this._binding7 = null; this._keyId7 = null;
this._binding8 = null; this._keyId8 = null;
this._binding9 = null; this._keyId9 = null;
var length = bindings.length;
if (length > 0) {this._binding0 = this._createBinding(bindings[0]); this._keyId0 = this._binding0.key.id;}
if (length > 1) {this._binding1 = this._createBinding(bindings[1]); this._keyId1 = this._binding1.key.id;}
if (length > 2) {this._binding2 = this._createBinding(bindings[2]); this._keyId2 = this._binding2.key.id;}
if (length > 3) {this._binding3 = this._createBinding(bindings[3]); this._keyId3 = this._binding3.key.id;}
if (length > 4) {this._binding4 = this._createBinding(bindings[4]); this._keyId4 = this._binding4.key.id;}
if (length > 5) {this._binding5 = this._createBinding(bindings[5]); this._keyId5 = this._binding5.key.id;}
if (length > 6) {this._binding6 = this._createBinding(bindings[6]); this._keyId6 = this._binding6.key.id;}
if (length > 7) {this._binding7 = this._createBinding(bindings[7]); this._keyId7 = this._binding7.key.id;}
if (length > 8) {this._binding8 = this._createBinding(bindings[8]); this._keyId8 = this._binding8.key.id;}
if (length > 9) {this._binding9 = this._createBinding(bindings[9]); this._keyId9 = this._binding9.key.id;}
if (length > 10) {
throw 'Maximum number of directives per element has been reached.';
}
}
instantiate(parent:ElementInjector, host:ElementInjector, eventCallbacks):ElementInjector {
return new ElementInjector(this, parent, host, eventCallbacks);
}
_createBinding(bindingOrType) {
if (bindingOrType instanceof DirectiveBinding) {
return bindingOrType;
} else {
var b = bind(bindingOrType).toClass(bindingOrType);
return DirectiveBinding.createFromBinding(b, null);
}
}
get hasBindings():boolean {
return isPresent(this._binding0);
}
hasEventEmitter(eventName: string) {
var p = this;
if (isPresent(p._binding0) && DirectiveBinding._hasEventEmitter(eventName, p._binding0)) return true;
if (isPresent(p._binding1) && DirectiveBinding._hasEventEmitter(eventName, p._binding1)) return true;
if (isPresent(p._binding2) && DirectiveBinding._hasEventEmitter(eventName, p._binding2)) return true;
if (isPresent(p._binding3) && DirectiveBinding._hasEventEmitter(eventName, p._binding3)) return true;
if (isPresent(p._binding4) && DirectiveBinding._hasEventEmitter(eventName, p._binding4)) return true;
if (isPresent(p._binding5) && DirectiveBinding._hasEventEmitter(eventName, p._binding5)) return true;
if (isPresent(p._binding6) && DirectiveBinding._hasEventEmitter(eventName, p._binding6)) return true;
if (isPresent(p._binding7) && DirectiveBinding._hasEventEmitter(eventName, p._binding7)) return true;
if (isPresent(p._binding8) && DirectiveBinding._hasEventEmitter(eventName, p._binding8)) return true;
if (isPresent(p._binding9) && DirectiveBinding._hasEventEmitter(eventName, p._binding9)) return true;
return false;
}
}
export class ElementInjector extends TreeNode {
_proto:ProtoElementInjector;
_lightDomAppInjector:Injector;
_shadowDomAppInjector:Injector;
_host:ElementInjector;
_obj0:any;
_obj1:any;
_obj2:any;
_obj3:any;
_obj4:any;
_obj5:any;
_obj6:any;
_obj7:any;
_obj8:any;
_obj9:any;
_preBuiltObjects;
_constructionCounter;
_eventCallbacks;
constructor(proto:ProtoElementInjector, parent:ElementInjector, host:ElementInjector, eventCallbacks: Map) {
super(parent);
if (isPresent(parent) && isPresent(host)) {
throw new BaseException('Only either parent or host is allowed');
}
this._host = null; // needed to satisfy Dart
if (isPresent(parent)) {
this._host = parent._host;
} else {
this._host = host;
}
this._proto = proto;
//we cannot call clearDirectives because fields won't be detected
this._preBuiltObjects = null;
this._lightDomAppInjector = null;
this._shadowDomAppInjector = null;
this._eventCallbacks = eventCallbacks;
this._obj0 = null;
this._obj1 = null;
this._obj2 = null;
this._obj3 = null;
this._obj4 = null;
this._obj5 = null;
this._obj6 = null;
this._obj7 = null;
this._obj8 = null;
this._obj9 = null;
this._constructionCounter = 0;
}
clearDirectives() {
this._preBuiltObjects = null;
this._lightDomAppInjector = null;
this._shadowDomAppInjector = null;
var p = this._proto;
if (isPresent(p._binding0) && p._binding0.callOnDestroy) {this._obj0.onDestroy();}
if (isPresent(p._binding1) && p._binding1.callOnDestroy) {this._obj1.onDestroy();}
if (isPresent(p._binding2) && p._binding2.callOnDestroy) {this._obj2.onDestroy();}
if (isPresent(p._binding3) && p._binding3.callOnDestroy) {this._obj3.onDestroy();}
if (isPresent(p._binding4) && p._binding4.callOnDestroy) {this._obj4.onDestroy();}
if (isPresent(p._binding5) && p._binding5.callOnDestroy) {this._obj5.onDestroy();}
if (isPresent(p._binding6) && p._binding6.callOnDestroy) {this._obj6.onDestroy();}
if (isPresent(p._binding7) && p._binding7.callOnDestroy) {this._obj7.onDestroy();}
if (isPresent(p._binding8) && p._binding8.callOnDestroy) {this._obj8.onDestroy();}
if (isPresent(p._binding9) && p._binding9.callOnDestroy) {this._obj9.onDestroy();}
this._obj0 = null;
this._obj1 = null;
this._obj2 = null;
this._obj3 = null;
this._obj4 = null;
this._obj5 = null;
this._obj6 = null;
this._obj7 = null;
this._obj8 = null;
this._obj9 = null;
this._constructionCounter = 0;
}
instantiateDirectives(lightDomAppInjector:Injector, shadowDomAppInjector:Injector, preBuiltObjects:PreBuiltObjects) {
this._checkShadowDomAppInjector(shadowDomAppInjector);
this._preBuiltObjects = preBuiltObjects;
this._lightDomAppInjector = lightDomAppInjector;
this._shadowDomAppInjector = shadowDomAppInjector;
var p = this._proto;
if (isPresent(p._keyId0)) this._getDirectiveByKeyId(p._keyId0);
if (isPresent(p._keyId1)) this._getDirectiveByKeyId(p._keyId1);
if (isPresent(p._keyId2)) this._getDirectiveByKeyId(p._keyId2);
if (isPresent(p._keyId3)) this._getDirectiveByKeyId(p._keyId3);
if (isPresent(p._keyId4)) this._getDirectiveByKeyId(p._keyId4);
if (isPresent(p._keyId5)) this._getDirectiveByKeyId(p._keyId5);
if (isPresent(p._keyId6)) this._getDirectiveByKeyId(p._keyId6);
if (isPresent(p._keyId7)) this._getDirectiveByKeyId(p._keyId7);
if (isPresent(p._keyId8)) this._getDirectiveByKeyId(p._keyId8);
if (isPresent(p._keyId9)) this._getDirectiveByKeyId(p._keyId9);
}
_checkShadowDomAppInjector(shadowDomAppInjector:Injector) {
if (this._proto._binding0IsComponent && isBlank(shadowDomAppInjector)) {
throw new BaseException('A shadowDomAppInjector is required as this ElementInjector contains a component');
} else if (!this._proto._binding0IsComponent && isPresent(shadowDomAppInjector)) {
throw new BaseException('No shadowDomAppInjector allowed as there is not component stored in this ElementInjector');
}
}
get(token) {
return this._getByKey(Key.get(token), 0, null);
}
hasDirective(type:Type):boolean {
return this._getDirectiveByKeyId(Key.get(type).id) !== _undefined;
}
hasPreBuiltObject(type:Type):boolean {
var pb = this._getPreBuiltObjectByKeyId(Key.get(type).id);
return pb !== _undefined && isPresent(pb);
}
forElement(el):boolean {
return this._preBuiltObjects.element.domElement === el;
}
getComponent() {
if (this._proto._binding0IsComponent) {
return this._obj0;
} else {
throw new BaseException('There is not component stored in this ElementInjector');
}
}
directParent(): ElementInjector {
return this._proto.distanceToParent < 2 ? this.parent : null;
}
_isComponentKey(key:Key) {
return this._proto._binding0IsComponent && key.id === this._proto._keyId0;
}
_new(binding:Binding) {
if (this._constructionCounter++ > _MAX_DIRECTIVE_CONSTRUCTION_COUNTER) {
throw new CyclicDependencyError(binding.key);
}
var factory = binding.factory;
var deps = binding.dependencies;
var length = deps.length;
var d0,d1,d2,d3,d4,d5,d6,d7,d8,d9;
try {
d0 = length > 0 ? this._getByDependency(deps[0], binding.key) : null;
d1 = length > 1 ? this._getByDependency(deps[1], binding.key) : null;
d2 = length > 2 ? this._getByDependency(deps[2], binding.key) : null;
d3 = length > 3 ? this._getByDependency(deps[3], binding.key) : null;
d4 = length > 4 ? this._getByDependency(deps[4], binding.key) : null;
d5 = length > 5 ? this._getByDependency(deps[5], binding.key) : null;
d6 = length > 6 ? this._getByDependency(deps[6], binding.key) : null;
d7 = length > 7 ? this._getByDependency(deps[7], binding.key) : null;
d8 = length > 8 ? this._getByDependency(deps[8], binding.key) : null;
d9 = length > 9 ? this._getByDependency(deps[9], binding.key) : null;
} catch(e) {
if (e instanceof ProviderError) e.addKey(binding.key);
throw e;
}
var obj;
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;
default: throw `Directive ${binding.key.token} can only have up to 10 dependencies.`;
}
return obj;
}
_getByDependency(dep:DirectiveDependency, requestor:Key) {
if (isPresent(dep.eventEmitterName)) return this._buildEventEmitter(dep);
return this._getByKey(dep.key, dep.depth, requestor);
}
_buildEventEmitter(dep) {
var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId);
if (isPresent(this._eventCallbacks)) {
var callback = MapWrapper.get(this._eventCallbacks, dep.eventEmitterName);
if (isPresent(callback)) {
var locals = MapWrapper.create();
return ProtoView.buildInnerCallback(callback, view, locals);
}
}
return (_) => {};
}
/*
* It is fairly easy to annotate keys with metadata.
* For example, key.metadata = 'directive'.
*
* This would allows to do the lookup more efficiently.
*
* for example
* we would lookup pre built objects only when metadata = 'preBuilt'
* we would lookup directives only when metadata = 'directive'
*
* Write benchmarks before doing this optimization.
*/
_getByKey(key:Key, depth:number, requestor:Key) {
var ei = this;
if (! this._shouldIncludeSelf(depth)) {
depth -= ei._proto.distanceToParent;
ei = ei._parent;
}
while (ei != null && depth >= 0) {
var preBuiltObj = ei._getPreBuiltObjectByKeyId(key.id);
if (preBuiltObj !== _undefined) return preBuiltObj;
var dir = ei._getDirectiveByKeyId(key.id);
if (dir !== _undefined) return dir;
depth -= ei._proto.distanceToParent;
ei = ei._parent;
}
if (isPresent(this._host) && this._host._isComponentKey(key)) {
return this._host.getComponent();
} else {
return this._appInjector(requestor).get(key);
}
}
_appInjector(requestor:Key) {
if (isPresent(requestor) && this._isComponentKey(requestor)) {
return this._shadowDomAppInjector;
} else {
return this._lightDomAppInjector;
}
}
_shouldIncludeSelf(depth:int) {
return depth === 0;
}
_getPreBuiltObjectByKeyId(keyId:int) {
var staticKeys = StaticKeys.instance();
if (keyId === staticKeys.viewId) return this._preBuiltObjects.view;
if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element;
if (keyId === staticKeys.viewPortId) return this._preBuiltObjects.viewPort;
if (keyId === staticKeys.bindingPropagationConfigId) return this._preBuiltObjects.bindingPropagationConfig;
if (keyId === staticKeys.destinationLightDomId) {
var p:ElementInjector = this.directParent();
return isPresent(p) ? p._preBuiltObjects.lightDom : null;
}
if (keyId === staticKeys.sourceLightDomId) {
return this._host._preBuiltObjects.lightDom;
}
//TODO add other objects as needed
return _undefined;
}
_getDirectiveByKeyId(keyId:int) {
var p = this._proto;
if (p._keyId0 === keyId) {if (isBlank(this._obj0)){this._obj0 = this._new(p._binding0);} return this._obj0;}
if (p._keyId1 === keyId) {if (isBlank(this._obj1)){this._obj1 = this._new(p._binding1);} return this._obj1;}
if (p._keyId2 === keyId) {if (isBlank(this._obj2)){this._obj2 = this._new(p._binding2);} return this._obj2;}
if (p._keyId3 === keyId) {if (isBlank(this._obj3)){this._obj3 = this._new(p._binding3);} return this._obj3;}
if (p._keyId4 === keyId) {if (isBlank(this._obj4)){this._obj4 = this._new(p._binding4);} return this._obj4;}
if (p._keyId5 === keyId) {if (isBlank(this._obj5)){this._obj5 = this._new(p._binding5);} return this._obj5;}
if (p._keyId6 === keyId) {if (isBlank(this._obj6)){this._obj6 = this._new(p._binding6);} return this._obj6;}
if (p._keyId7 === keyId) {if (isBlank(this._obj7)){this._obj7 = this._new(p._binding7);} return this._obj7;}
if (p._keyId8 === keyId) {if (isBlank(this._obj8)){this._obj8 = this._new(p._binding8);} return this._obj8;}
if (p._keyId9 === keyId) {if (isBlank(this._obj9)){this._obj9 = this._new(p._binding9);} return this._obj9;}
return _undefined;
}
getAtIndex(index:int) {
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 OutOfBoundsAccess(index);
}
hasInstances() {
return this._constructionCounter > 0;
}
hasEventEmitter(eventName: string) {
return this._proto.hasEventEmitter(eventName);
}
}
class OutOfBoundsAccess extends Error {
message:string;
constructor(index) {
this.message = `Index ${index} is out-of-bounds.`;
}
toString() {
return this.message;
}
}

View File

@ -0,0 +1,5 @@
export class OnChange {
onChange(changes) {
throw "OnChange.onChange is not implemented";
}
}

View File

@ -0,0 +1,59 @@
import {isBlank} from 'facade/src/lang';
import {List, ListWrapper} from 'facade/src/collection';
import {DOM, Element} from 'facade/src/dom';
import {CompileElement} from './compile_element';
import {CompileStep} from './compile_step';
/**
* Controls the processing order of elements.
* Right now it only allows to add a parent element.
*/
export class CompileControl {
_steps:List<CompileStep>;
_currentStepIndex:number;
_parent:CompileElement;
_results;
_additionalChildren;
constructor(steps) {
this._steps = steps;
this._currentStepIndex = 0;
this._parent = null;
this._results = null;
this._additionalChildren = null;
}
// only public so that it can be used by compile_pipeline
internalProcess(results, startStepIndex, parent:CompileElement, current:CompileElement) {
this._results = results;
var previousStepIndex = this._currentStepIndex;
var previousParent = this._parent;
for (var i=startStepIndex; i<this._steps.length; i++) {
var step = this._steps[i];
this._parent = parent;
this._currentStepIndex = i;
step.process(parent, current, this);
parent = this._parent;
}
ListWrapper.push(results, current);
this._currentStepIndex = previousStepIndex;
this._parent = previousParent;
var localAdditionalChildren = this._additionalChildren;
this._additionalChildren = null;
return localAdditionalChildren;
}
addParent(newElement:CompileElement) {
this.internalProcess(this._results, this._currentStepIndex+1, this._parent, newElement);
this._parent = newElement;
}
addChild(element:CompileElement) {
if (isBlank(this._additionalChildren)) {
this._additionalChildren = ListWrapper.create();
}
ListWrapper.push(this._additionalChildren, element);
}
}

View File

@ -0,0 +1,159 @@
import {List, Map, ListWrapper, MapWrapper} from 'facade/src/collection';
import {Element, DOM} from 'facade/src/dom';
import {int, isBlank, isPresent, Type} from 'facade/src/lang';
import {DirectiveMetadata} from '../directive_metadata';
import {Decorator} from '../../annotations/annotations';
import {Component} from '../../annotations/annotations';
import {Template} from '../../annotations/annotations';
import {ElementBinder} from '../element_binder';
import {ProtoElementInjector} from '../element_injector';
import {ProtoView} from '../view';
import {AST} from 'change_detection/change_detection';
/**
* Collects all data that is needed to process an element
* in the compile process. Fields are filled
* by the CompileSteps starting out with the pure HTMLElement.
*/
export class CompileElement {
element:Element;
_attrs:Map;
_classList:List;
textNodeBindings:Map;
propertyBindings:Map;
eventBindings:Map;
/// Store directive name to template name mapping.
/// Directive name is what the directive exports the variable as
/// Template name is how it is reffered to it in template
variableBindings:Map;
decoratorDirectives:List<DirectiveMetadata>;
templateDirective:DirectiveMetadata;
componentDirective:DirectiveMetadata;
_allDirectives:List<DirectiveMetadata>;
isViewRoot:boolean;
hasBindings:boolean;
inheritedProtoView:ProtoView;
inheritedProtoElementInjector:ProtoElementInjector;
inheritedElementBinder:ElementBinder;
distanceToParentInjector:number;
compileChildren: boolean;
constructor(element:Element) {
this.element = element;
this._attrs = null;
this._classList = null;
this.textNodeBindings = null;
this.propertyBindings = null;
this.eventBindings = null;
this.variableBindings = null;
this.decoratorDirectives = null;
this.templateDirective = null;
this.componentDirective = null;
this._allDirectives = null;
this.isViewRoot = false;
this.hasBindings = false;
// inherited down to children if they don't have
// an own protoView
this.inheritedProtoView = null;
// inherited down to children if they don't have
// an own protoElementInjector
this.inheritedProtoElementInjector = null;
// inherited down to children if they don't have
// an own elementBinder
this.inheritedElementBinder = null;
this.distanceToParentInjector = 0;
this.compileChildren = true;
}
refreshAttrs() {
this._attrs = null;
}
attrs():Map<string,string> {
if (isBlank(this._attrs)) {
this._attrs = DOM.attributeMap(this.element);
}
return this._attrs;
}
refreshClassList() {
this._classList = null;
}
classList():List<string> {
if (isBlank(this._classList)) {
this._classList = ListWrapper.create();
var elClassList = DOM.classList(this.element);
for (var i = 0; i < elClassList.length; i++) {
ListWrapper.push(this._classList, elClassList[i]);
}
}
return this._classList;
}
addTextNodeBinding(indexInParent:int, expression:AST) {
if (isBlank(this.textNodeBindings)) {
this.textNodeBindings = MapWrapper.create();
}
MapWrapper.set(this.textNodeBindings, indexInParent, expression);
}
addPropertyBinding(property:string, expression:AST) {
if (isBlank(this.propertyBindings)) {
this.propertyBindings = MapWrapper.create();
}
MapWrapper.set(this.propertyBindings, property, expression);
}
addVariableBinding(directiveName:string, templateName:string) {
if (isBlank(this.variableBindings)) {
this.variableBindings = MapWrapper.create();
}
MapWrapper.set(this.variableBindings, templateName, directiveName);
}
addEventBinding(eventName:string, expression:AST) {
if (isBlank(this.eventBindings)) {
this.eventBindings = MapWrapper.create();
}
MapWrapper.set(this.eventBindings, eventName, expression);
}
addDirective(directive:DirectiveMetadata) {
var annotation = directive.annotation;
this._allDirectives = null;
if (annotation instanceof Decorator) {
if (isBlank(this.decoratorDirectives)) {
this.decoratorDirectives = ListWrapper.create();
}
ListWrapper.push(this.decoratorDirectives, directive);
if (!annotation.compileChildren) {
this.compileChildren = false;
}
} else if (annotation instanceof Template) {
this.templateDirective = directive;
} else if (annotation instanceof Component) {
this.componentDirective = directive;
}
}
getAllDirectives(): List<DirectiveMetadata> {
if (this._allDirectives === null) {
// Collect all the directives
// When present the component directive must be first
var directives = ListWrapper.create();
if (isPresent(this.componentDirective)) {
ListWrapper.push(directives, this.componentDirective);
}
if (isPresent(this.templateDirective)) {
ListWrapper.push(directives, this.templateDirective);
}
if (isPresent(this.decoratorDirectives)) {
directives = ListWrapper.concat(directives, this.decoratorDirectives);
}
this._allDirectives = directives;
}
return this._allDirectives;
}
}

View File

@ -0,0 +1,47 @@
import {isPresent} from 'facade/src/lang';
import {List, ListWrapper} from 'facade/src/collection';
import {Element, Node, DOM} from 'facade/src/dom';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {CompileStep} from './compile_step';
import {DirectiveMetadata} from '../directive_metadata';
/**
* CompilePipeline for executing CompileSteps recursively for
* all elements in a template.
*/
export class CompilePipeline {
_control:CompileControl;
constructor(steps:List<CompileStep>) {
this._control = new CompileControl(steps);
}
process(rootElement:Element):List {
var results = ListWrapper.create();
this._process(results, null, new CompileElement(rootElement));
return results;
}
_process(results, parent:CompileElement, current:CompileElement) {
var additionalChildren = this._control.internalProcess(results, 0, parent, current);
if (current.compileChildren) {
var node = DOM.templateAwareRoot(current.element).firstChild;
while (isPresent(node)) {
// compiliation can potentially move the node, so we need to store the
// next sibling before recursing.
var nextNode = DOM.nextSibling(node);
if (node.nodeType === Node.ELEMENT_NODE) {
this._process(results, current, new CompileElement(node));
}
node = nextNode;
}
}
if (isPresent(additionalChildren)) {
for (var i=0; i<additionalChildren.length; i++) {
this._process(results, current, additionalChildren[i]);
}
}
}
}

View File

@ -0,0 +1,11 @@
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {DirectiveMetadata} from '../directive_metadata';
/**
* One part of the compile process.
* Is guaranteed to be called in depth first order
*/
export class CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {}
}

View File

@ -0,0 +1,38 @@
import {ChangeDetection, Parser} from 'change_detection/change_detection';
import {List} from 'facade/src/collection';
import {PropertyBindingParser} from './property_binding_parser';
import {TextInterpolationParser} from './text_interpolation_parser';
import {DirectiveParser} from './directive_parser';
import {ViewSplitter} from './view_splitter';
import {ElementBindingMarker} from './element_binding_marker';
import {ProtoViewBuilder} from './proto_view_builder';
import {ProtoElementInjectorBuilder} from './proto_element_injector_builder';
import {ElementBinderBuilder} from './element_binder_builder';
import {DirectiveMetadata} from 'core/src/compiler/directive_metadata';
import {stringify} from 'facade/src/lang';
/**
* Default steps used for compiling a template.
* Takes in an HTMLElement and produces the ProtoViews,
* ProtoElementInjectors and ElementBinders in the end.
*/
export function createDefaultSteps(
changeDetection:ChangeDetection,
parser:Parser,
compiledComponent: DirectiveMetadata,
directives: List<DirectiveMetadata>) {
var compilationUnit = stringify(compiledComponent.type);
return [
new ViewSplitter(parser, compilationUnit),
new PropertyBindingParser(parser, compilationUnit),
new DirectiveParser(directives),
new TextInterpolationParser(parser, compilationUnit),
new ElementBindingMarker(),
new ProtoViewBuilder(changeDetection),
new ProtoElementInjectorBuilder(),
new ElementBinderBuilder()
];
}

View File

@ -0,0 +1,82 @@
import {isPresent, BaseException} from 'facade/src/lang';
import {List, MapWrapper} from 'facade/src/collection';
import {TemplateElement} from 'facade/src/dom';
import {SelectorMatcher} from '../selector';
import {CssSelector} from '../selector';
import {DirectiveMetadata} from '../directive_metadata';
import {Template} from '../../annotations/annotations';
import {Component} from '../../annotations/annotations';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
/**
* Parses the directives on a single element. Assumes ViewSplitter has already created
* <template> elements for template directives.
*
* Fills:
* - CompileElement#decoratorDirectives
* - CompileElement#templateDirecitve
* - CompileElement#componentDirective.
*
* Reads:
* - CompileElement#propertyBindings (to find directives contained
* in the property bindings)
* - CompileElement#variableBindings (to find directives contained
* in the variable bindings)
*/
export class DirectiveParser extends CompileStep {
_selectorMatcher:SelectorMatcher;
constructor(directives:List<DirectiveMetadata>) {
this._selectorMatcher = new SelectorMatcher();
for (var i=0; i<directives.length; i++) {
var directiveMetadata = directives[i];
this._selectorMatcher.addSelectable(
CssSelector.parse(directiveMetadata.annotation.selector),
directiveMetadata
);
}
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var attrs = current.attrs();
var classList = current.classList();
var cssSelector = new CssSelector();
cssSelector.setElement(current.element.nodeName);
for (var i=0; i < classList.length; i++) {
cssSelector.addClassName(classList[i]);
}
MapWrapper.forEach(attrs, (attrValue, attrName) => {
cssSelector.addAttribute(attrName, attrValue);
});
if (isPresent(current.propertyBindings)) {
MapWrapper.forEach(current.propertyBindings, (expression, prop) => {
cssSelector.addAttribute(prop, expression.source);
});
}
if (isPresent(current.variableBindings)) {
MapWrapper.forEach(current.variableBindings, (value, name) => {
cssSelector.addAttribute(name, value);
});
}
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
// only be present on <template> elements any more!
var isTemplateElement = current.element instanceof TemplateElement;
this._selectorMatcher.match(cssSelector, (directive) => {
if (directive.annotation instanceof Template) {
if (!isTemplateElement) {
throw new BaseException('Template directives need to be placed on <template> elements or elements with template attribute!');
} else if (isPresent(current.templateDirective)) {
throw new BaseException('Only one template directive per element is allowed!');
}
} else if (isTemplateElement) {
throw new BaseException('Only template directives are allowed on <template> elements!');
} else if ((directive.annotation instanceof Component) && isPresent(current.componentDirective)) {
throw new BaseException('Only one component directive per element is allowed!');
}
current.addDirective(directive);
});
}
}

View File

@ -0,0 +1,145 @@
import {int, isPresent, isBlank, Type, BaseException, StringWrapper, stringify} from 'facade/src/lang';
import {Element, DOM} from 'facade/src/dom';
import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'facade/src/collection';
import {reflector} from 'reflection/src/reflection';
import {Parser, ProtoChangeDetector} from 'change_detection/change_detection';
import {Component, Directive} from '../../annotations/annotations';
import {DirectiveMetadata} from '../directive_metadata';
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from '../view';
import {ProtoElementInjector} from '../element_injector';
import {ElementBinder} from '../element_binder';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
const CLASS_PREFIX = 'class.';
var classSettersCache = StringMapWrapper.create();
function classSetterFactory(className:string) {
var setterFn = StringMapWrapper.get(classSettersCache, className);
if (isBlank(setterFn)) {
setterFn = function(element:Element, value) {
if (value) {
DOM.addClass(element, className);
} else {
DOM.removeClass(element, className);
}
};
StringMapWrapper.set(classSettersCache, className, setterFn);
}
return setterFn;
}
/**
* Creates the ElementBinders and adds watches to the
* ProtoChangeDetector.
*
* Fills:
* - CompileElement#inheritedElementBinder
*
* Reads:
* - (in parent) CompileElement#inheritedElementBinder
* - CompileElement#hasBindings
* - CompileElement#inheritedProtoView
* - CompileElement#inheritedProtoElementInjector
* - CompileElement#textNodeBindings
* - CompileElement#propertyBindings
* - CompileElement#eventBindings
* - CompileElement#decoratorDirectives
* - CompileElement#componentDirective
* - CompileElement#templateDirective
*
* Note: This actually only needs the CompileElements with the flags
* `hasBindings` and `isViewRoot`,
* and only needs the actual HTMLElement for the ones
* with the flag `isViewRoot`.
*/
export class ElementBinderBuilder extends CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var elementBinder = null;
if (current.hasBindings) {
var protoView = current.inheritedProtoView;
elementBinder = protoView.bindElement(current.inheritedProtoElementInjector,
current.componentDirective, current.templateDirective);
if (isPresent(current.textNodeBindings)) {
this._bindTextNodes(protoView, current);
}
if (isPresent(current.propertyBindings)) {
this._bindElementProperties(protoView, current);
}
if (isPresent(current.eventBindings)) {
this._bindEvents(protoView, current);
}
this._bindDirectiveProperties(current.getAllDirectives(), current);
} else if (isPresent(parent)) {
elementBinder = parent.inheritedElementBinder;
}
current.inheritedElementBinder = elementBinder;
}
_bindTextNodes(protoView, compileElement) {
MapWrapper.forEach(compileElement.textNodeBindings, (expression, indexInParent) => {
protoView.bindTextNode(indexInParent, expression);
});
}
_bindElementProperties(protoView, compileElement) {
MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => {
var setterFn;
if (StringWrapper.startsWith(property, CLASS_PREFIX)) {
setterFn = classSetterFactory(StringWrapper.substring(property, CLASS_PREFIX.length));
} else if (DOM.hasProperty(compileElement.element, property)) {
setterFn = reflector.setter(property);
}
if (isPresent(setterFn)) {
protoView.bindElementProperty(expression.ast, property, setterFn);
}
});
}
_bindEvents(protoView, compileElement) {
MapWrapper.forEach(compileElement.eventBindings, (expression, eventName) => {
protoView.bindEvent(eventName, expression);
});
}
_bindDirectiveProperties(directives: List<DirectiveMetadata>,
compileElement: CompileElement) {
var protoView = compileElement.inheritedProtoView;
for (var directiveIndex = 0; directiveIndex < directives.length; directiveIndex++) {
var directive = ListWrapper.get(directives, directiveIndex);
var annotation = directive.annotation;
if (isBlank(annotation.bind)) continue;
StringMapWrapper.forEach(annotation.bind, function (dirProp, elProp) {
var expression = isPresent(compileElement.propertyBindings) ?
MapWrapper.get(compileElement.propertyBindings, elProp) :
null;
if (isBlank(expression)) {
throw new BaseException("No element binding found for property '" + elProp
+ "' which is required by directive '" + stringify(directive.type) + "'");
}
var len = dirProp.length;
var dirBindingName = dirProp;
var isContentWatch = dirProp[len - 2] === '[' && dirProp[len - 1] === ']';
if (isContentWatch) dirBindingName = dirProp.substring(0, len - 2);
protoView.bindDirectiveProperty(
directiveIndex,
expression,
dirBindingName,
reflector.setter(dirBindingName),
isContentWatch
);
});
}
}
}

View File

@ -0,0 +1,44 @@
import {isPresent} from 'facade/src/lang';
import {MapWrapper} from 'facade/src/collection';
import {DOM} from 'facade/src/dom';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
const NG_BINDING_CLASS = 'ng-binding';
/**
* Marks elements that have bindings with a css class
* and sets the CompileElement.hasBindings flag.
*
* Fills:
* - CompileElement#hasBindings
*
* Reads:
* - CompileElement#textNodeBindings
* - CompileElement#propertyBindings
* - CompileElement#variableBindings
* - CompileElement#eventBindings
* - CompileElement#decoratorDirectives
* - CompileElement#componentDirective
* - CompileElement#templateDirective
*/
export class ElementBindingMarker extends CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var hasBindings =
(isPresent(current.textNodeBindings) && MapWrapper.size(current.textNodeBindings)>0) ||
(isPresent(current.propertyBindings) && MapWrapper.size(current.propertyBindings)>0) ||
(isPresent(current.variableBindings) && MapWrapper.size(current.variableBindings)>0) ||
(isPresent(current.eventBindings) && MapWrapper.size(current.eventBindings)>0) ||
(isPresent(current.decoratorDirectives) && current.decoratorDirectives.length > 0) ||
isPresent(current.templateDirective) ||
isPresent(current.componentDirective);
if (hasBindings) {
var element = current.element;
DOM.addClass(element, NG_BINDING_CLASS);
current.hasBindings = true;
}
}
}

View File

@ -0,0 +1,76 @@
import {isPresent, isBlank, RegExpWrapper, BaseException} from 'facade/src/lang';
import {MapWrapper} from 'facade/src/collection';
import {TemplateElement} from 'facade/src/dom';
import {Parser, AST, ExpressionWithSource} from 'change_detection/change_detection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
var BIND_NAME_REGEXP = RegExpWrapper.create('^(?:(?:(bind)|(var)|(on))-(.+))|\\[([^\\]]+)\\]|\\(([^\\)]+)\\)');
/**
* Parses the property bindings on a single element.
*
* Fills:
* - CompileElement#propertyBindings
* - CompileElement#eventBindings
* - CompileElement#variableBindings
*/
export class PropertyBindingParser extends CompileStep {
_parser:Parser;
_compilationUnit:any;
constructor(parser:Parser, compilationUnit:any) {
this._parser = parser;
this._compilationUnit = compilationUnit;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var attrs = current.attrs();
MapWrapper.forEach(attrs, (attrValue, attrName) => {
var bindParts = RegExpWrapper.firstMatch(BIND_NAME_REGEXP, attrName);
if (isPresent(bindParts)) {
if (isPresent(bindParts[1])) {
// match: bind-prop
current.addPropertyBinding(bindParts[4], this._parseBinding(attrValue));
} else if (isPresent(bindParts[2])) {
// match: let-prop
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should
// only be present on <template> elements any more!
if (!(current.element instanceof TemplateElement)) {
throw new BaseException('var-* is only allowed on <template> elements!');
}
current.addVariableBinding(bindParts[4], attrValue);
} else if (isPresent(bindParts[3])) {
// match: on-prop
current.addEventBinding(bindParts[4], this._parseAction(attrValue));
} else if (isPresent(bindParts[5])) {
// match: [prop]
current.addPropertyBinding(bindParts[5], this._parseBinding(attrValue));
} else if (isPresent(bindParts[6])) {
// match: (prop)
current.addEventBinding(bindParts[6], this._parseBinding(attrValue));
}
} else {
var ast = this._parseInterpolation(attrValue);
if (isPresent(ast)) {
current.addPropertyBinding(attrName, ast);
}
}
});
}
_parseInterpolation(input:string):AST {
return this._parser.parseInterpolation(input, this._compilationUnit);
}
_parseBinding(input:string):AST {
return this._parser.parseBinding(input, this._compilationUnit);
}
_parseAction(input:string):AST {
return this._parser.parseAction(input, this._compilationUnit);
}
}

View File

@ -0,0 +1,72 @@
import {isPresent, isBlank} from 'facade/src/lang';
import {ListWrapper} from 'facade/src/collection';
import {Key} from 'di/di';
import {ProtoElementInjector, ComponentKeyMetaData, DirectiveBinding} from '../element_injector';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {DirectiveMetadata} from '../directive_metadata';
/**
* Creates the ProtoElementInjectors.
*
* Fills:
* - CompileElement#inheritedProtoElementInjector
* - CompileElement#distanceToParentInjector
*
* Reads:
* - (in parent) CompileElement#inheritedProtoElementInjector
* - (in parent) CompileElement#distanceToParentInjector
* - CompileElement#isViewRoot
* - CompileElement#inheritedProtoView
* - CompileElement#decoratorDirectives
* - CompileElement#componentDirective
* - CompileElement#templateDirective
*/
export class ProtoElementInjectorBuilder extends CompileStep {
// public so that we can overwrite it in tests
internalCreateProtoElementInjector(parent, index, directives, firstBindingIsComponent, distance) {
return new ProtoElementInjector(parent, index, directives, firstBindingIsComponent, distance);
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var distanceToParentInjector = this._getDistanceToParentInjector(parent, current);
var parentProtoElementInjector = this._getParentProtoElementInjector(parent, current);
var injectorBindings = ListWrapper.map(current.getAllDirectives(), this._createBinding);
// TODO: add lightDomServices as well,
// but after the directives as we rely on that order
// in the element_binder_builder.
if (injectorBindings.length > 0) {
var protoView = current.inheritedProtoView;
var hasComponent = isPresent(current.componentDirective);
current.inheritedProtoElementInjector = this.internalCreateProtoElementInjector(
parentProtoElementInjector, protoView.elementBinders.length, injectorBindings,
hasComponent, distanceToParentInjector
);
current.distanceToParentInjector = 0;
} else {
current.inheritedProtoElementInjector = parentProtoElementInjector;
current.distanceToParentInjector = distanceToParentInjector;
}
}
_getDistanceToParentInjector(parent, current) {
return isPresent(parent) ? parent.distanceToParentInjector + 1 : 0;
}
_getParentProtoElementInjector(parent, current) {
if (isPresent(parent) && !current.isViewRoot) {
return parent.inheritedProtoElementInjector;
}
return null;
}
_createBinding(d:DirectiveMetadata): DirectiveBinding {
return DirectiveBinding.createFromType(d.type, d.annotation);
}
}

View File

@ -0,0 +1,50 @@
import {isPresent, BaseException} from 'facade/src/lang';
import {ListWrapper, MapWrapper} from 'facade/src/collection';
import {ProtoView} from '../view';
import {ChangeDetection} from 'change_detection/change_detection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
/**
* Creates ProtoViews and forwards variable bindings from parent to children.
*
* Fills:
* - (in parent): CompileElement#inheritedElementBinder.nestedProtoView
* - CompileElement#inheritedProtoView
*
* Reads:
* - (in parent): CompileElement#inheritedProtoView
* - (in parent): CompileElement#variableBindings
* - CompileElement#isViewRoot
*/
export class ProtoViewBuilder extends CompileStep {
changeDetection:ChangeDetection;
constructor(changeDetection:ChangeDetection) {
this.changeDetection = changeDetection;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var inheritedProtoView = null;
if (current.isViewRoot) {
var protoChangeDetector = this.changeDetection.createProtoChangeDetector('dummy');
inheritedProtoView = new ProtoView(current.element, protoChangeDetector);
if (isPresent(parent)) {
if (isPresent(parent.inheritedElementBinder.nestedProtoView)) {
throw new BaseException('Only one nested view per element is allowed');
}
parent.inheritedElementBinder.nestedProtoView = inheritedProtoView;
if (isPresent(parent.variableBindings)) {
MapWrapper.forEach(parent.variableBindings, (mappedName, varName) => {
inheritedProtoView.bindVariable(varName, mappedName);
});
}
}
} else if (isPresent(parent)) {
inheritedProtoView = parent.inheritedProtoView;
}
current.inheritedProtoView = inheritedProtoView;
}
}

View File

@ -0,0 +1,45 @@
import {RegExpWrapper, StringWrapper, isPresent} from 'facade/src/lang';
import {Node, DOM} from 'facade/src/dom';
import {Parser} from 'change_detection/change_detection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
/**
* Parses interpolations in direct text child nodes of the current element.
*
* Fills:
* - CompileElement#textNodeBindings
*/
export class TextInterpolationParser extends CompileStep {
_parser:Parser;
_compilationUnit:any;
constructor(parser:Parser, compilationUnit:any) {
this._parser = parser;
this._compilationUnit = compilationUnit;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
if (!current.compileChildren) {
return;
}
var element = current.element;
var childNodes = DOM.templateAwareRoot(element).childNodes;
for (var i=0; i<childNodes.length; i++) {
var node = childNodes[i];
if (node.nodeType === Node.TEXT_NODE) {
this._parseTextNode(current, node, i);
}
}
}
_parseTextNode(pipelineElement, node, nodeIndex) {
var ast = this._parser.parseInterpolation(node.nodeValue, this._compilationUnit);
if (isPresent(ast)) {
DOM.setText(node, ' ');
pipelineElement.addTextNodeBinding(nodeIndex, ast);
}
}
}

View File

@ -0,0 +1,113 @@
import {isBlank, isPresent, BaseException} from 'facade/src/lang';
import {DOM, TemplateElement} from 'facade/src/dom';
import {MapWrapper, ListWrapper} from 'facade/src/collection';
import {Parser} from 'change_detection/change_detection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {StringWrapper} from 'facade/src/lang';
import {$BANG} from 'change_detection/src/parser/lexer';
/**
* Splits views at `<template>` elements or elements with `template` attribute:
* For `<template>` elements:
* - moves the content into a new and disconnected `<template>` element
* that is marked as view root.
*
* For elements with a `template` attribute:
* - replaces the element with an empty `<template>` element,
* parses the content of the `template` attribute and adds the information to that
* `<template>` element. Marks the elements as view root.
*
* Note: In both cases the root of the nested view is disconnected from its parent element.
* This is needed for browsers that don't support the `<template>` element
* as we want to do locate elements with bindings using `getElementsByClassName` later on,
* which should not descend into the nested view.
*
* Fills:
* - CompileElement#isViewRoot
* - CompileElement#variableBindings
* - CompileElement#propertyBindings
*/
export class ViewSplitter extends CompileStep {
_parser:Parser;
_compilationUnit:any;
constructor(parser:Parser, compilationUnit:any) {
this._parser = parser;
this._compilationUnit = compilationUnit;
}
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
if (isBlank(parent)) {
current.isViewRoot = true;
} else {
if (current.element instanceof TemplateElement) {
if (!current.isViewRoot) {
var viewRoot = new CompileElement(DOM.createTemplate(''));
var currentElement:TemplateElement = current.element;
var viewRootElement:TemplateElement = viewRoot.element;
this._moveChildNodes(currentElement.content, viewRootElement.content);
viewRoot.isViewRoot = true;
control.addChild(viewRoot);
}
} else {
var attrs = current.attrs();
var templateBindings = MapWrapper.get(attrs, 'template');
var hasTemplateBinding = isPresent(templateBindings);
// look for template shortcuts such as !if="condition" and treat them as template="if condition"
MapWrapper.forEach(attrs, (attrValue, attrName) => {
if (StringWrapper.charCodeAt(attrName, 0) == $BANG) {
var key = StringWrapper.substring(attrName, 1); // remove the bang
if (hasTemplateBinding) {
// 2nd template binding detected
throw new BaseException(`Only one template directive per element is allowed: ` +
`${templateBindings} and ${key} cannot be used simultaneously!`);
} else {
templateBindings = (attrValue.length == 0) ? key : key + ' ' + attrValue;
hasTemplateBinding = true;
}
}
});
if (hasTemplateBinding) {
var newParent = new CompileElement(DOM.createTemplate(''));
current.isViewRoot = true;
this._parseTemplateBindings(templateBindings, newParent);
this._addParentElement(current.element, newParent.element);
control.addParent(newParent);
current.element.remove();
}
}
}
}
_moveChildNodes(source, target) {
while (isPresent(source.firstChild)) {
DOM.appendChild(target, source.firstChild);
}
}
_addParentElement(currentElement, newParentElement) {
DOM.insertBefore(currentElement, newParentElement);
DOM.appendChild(newParentElement, currentElement);
}
_parseTemplateBindings(templateBindings:string, compileElement:CompileElement) {
var bindings = this._parser.parseTemplateBindings(templateBindings, this._compilationUnit);
for (var i=0; i<bindings.length; i++) {
var binding = bindings[i];
if (binding.keyIsVar) {
compileElement.addVariableBinding(binding.key, binding.name);
} else if (isPresent(binding.expression)) {
compileElement.addPropertyBinding(binding.key, binding.expression);
} else {
compileElement.element.setAttribute(binding.key, '');
}
}
}
}

View File

@ -0,0 +1,249 @@
import {List, Map, ListWrapper, MapWrapper} from 'facade/src/collection';
import {isPresent, isBlank, RegExpWrapper, RegExpMatcherWrapper, StringWrapper} from 'facade/src/lang';
const _EMPTY_ATTR_VALUE = '';
// TODO: Can't use `const` here as
// in Dart this is not transpiled into `final` yet...
var _SELECTOR_REGEXP =
RegExpWrapper.create('^([-\\w]+)|' + // "tag"
'(?:\\.([-\\w]+))|' + // ".class"
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])'); // "[name]", "[name=value]" or "[name*=value]"
/**
* A css selector contains an element name,
* css classes and attribute/value pairs with the purpose
* of selecting subsets out of them.
*/
export class CssSelector {
element:string;
classNames:List;
attrs:List;
static parse(selector:string):CssSelector {
var cssSelector = new CssSelector();
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
var match;
while (isPresent(match = RegExpMatcherWrapper.next(matcher))) {
if (isPresent(match[1])) {
cssSelector.setElement(match[1]);
}
if (isPresent(match[2])) {
cssSelector.addClassName(match[2]);
}
if (isPresent(match[3])) {
cssSelector.addAttribute(match[3], match[4]);
}
}
return cssSelector;
}
constructor() {
this.element = null;
this.classNames = ListWrapper.create();
this.attrs = ListWrapper.create();
}
setElement(element:string = null) {
if (isPresent(element)) {
element = element.toLowerCase();
}
this.element = element;
}
addAttribute(name:string, value:string = _EMPTY_ATTR_VALUE) {
ListWrapper.push(this.attrs, name.toLowerCase());
if (isPresent(value)) {
value = value.toLowerCase();
} else {
value = _EMPTY_ATTR_VALUE;
}
ListWrapper.push(this.attrs, value);
}
addClassName(name:string) {
ListWrapper.push(this.classNames, name.toLowerCase());
}
toString():string {
var res = '';
if (isPresent(this.element)) {
res += this.element;
}
if (isPresent(this.classNames)) {
for (var i=0; i<this.classNames.length; i++) {
res += '.' + this.classNames[i];
}
}
if (isPresent(this.attrs)) {
for (var i=0; i<this.attrs.length;) {
var attrName = this.attrs[i++];
var attrValue = this.attrs[i++]
res += '[' + attrName;
if (attrValue.length > 0) {
res += '=' + attrValue;
}
res += ']';
}
}
return res;
}
}
/**
* Reads a list of CssSelectors and allows to calculate which ones
* are contained in a given CssSelector.
*/
export class SelectorMatcher {
_elementMap:Map;
_elementPartialMap:Map;
_classMap:Map;
_classPartialMap:Map;
_attrValueMap:Map;
_attrValuePartialMap:Map;
constructor() {
this._elementMap = MapWrapper.create();
this._elementPartialMap = MapWrapper.create();
this._classMap = MapWrapper.create();
this._classPartialMap = MapWrapper.create();
this._attrValueMap = MapWrapper.create();
this._attrValuePartialMap = MapWrapper.create();
}
/**
* Add an object that can be found later on by calling `match`.
* @param cssSelector A css selector
* @param selectable An opaque object that will be given to the callback of the `match` function
*/
addSelectable(cssSelector:CssSelector, selectable) {
var matcher = this;
var element = cssSelector.element;
var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs;
if (isPresent(element)) {
var isTerminal = attrs.length === 0 && classNames.length === 0;
if (isTerminal) {
this._addTerminal(matcher._elementMap, element, selectable);
} else {
matcher = this._addPartial(matcher._elementPartialMap, element);
}
}
if (isPresent(classNames)) {
for (var index = 0; index<classNames.length; index++) {
var isTerminal = attrs.length === 0 && index === classNames.length - 1;
var className = classNames[index];
if (isTerminal) {
this._addTerminal(matcher._classMap, className, selectable);
} else {
matcher = this._addPartial(matcher._classPartialMap, className);
}
}
}
if (isPresent(attrs)) {
for (var index = 0; index<attrs.length; ) {
var isTerminal = index === attrs.length - 2;
var attrName = attrs[index++];
var attrValue = attrs[index++];
var map = isTerminal ? matcher._attrValueMap : matcher._attrValuePartialMap;
var valuesMap = MapWrapper.get(map, attrName)
if (isBlank(valuesMap)) {
valuesMap = MapWrapper.create();
MapWrapper.set(map, attrName, valuesMap);
}
if (isTerminal) {
this._addTerminal(valuesMap, attrValue, selectable);
} else {
matcher = this._addPartial(valuesMap, attrValue);
}
}
}
}
_addTerminal(map:Map<string,string>, name:string, selectable) {
var terminalList = MapWrapper.get(map, name)
if (isBlank(terminalList)) {
terminalList = ListWrapper.create();
MapWrapper.set(map, name, terminalList);
}
ListWrapper.push(terminalList, selectable);
}
_addPartial(map:Map<string,string>, name:string) {
var matcher = MapWrapper.get(map, name)
if (isBlank(matcher)) {
matcher = new SelectorMatcher();
MapWrapper.set(map, name, matcher);
}
return matcher;
}
/**
* Find the objects that have been added via `addSelectable`
* whose css selector is contained in the given css selector.
* @param cssSelector A css selector
* @param matchedCallback This callback will be called with the object handed into `addSelectable`
*/
match(cssSelector:CssSelector, matchedCallback:Function) {
var element = cssSelector.element;
var classNames = cssSelector.classNames;
var attrs = cssSelector.attrs;
this._matchTerminal(this._elementMap, element, matchedCallback);
this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback);
if (isPresent(classNames)) {
for (var index = 0; index<classNames.length; index++) {
var className = classNames[index];
this._matchTerminal(this._classMap, className, matchedCallback);
this._matchPartial(this._classPartialMap, className, cssSelector, matchedCallback);
}
}
if (isPresent(attrs)) {
for (var index = 0; index<attrs.length;) {
var attrName = attrs[index++];
var attrValue = attrs[index++];
var valuesMap = MapWrapper.get(this._attrValueMap, attrName);
if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
this._matchTerminal(valuesMap, _EMPTY_ATTR_VALUE, matchedCallback);
}
this._matchTerminal(valuesMap, attrValue, matchedCallback);
valuesMap = MapWrapper.get(this._attrValuePartialMap, attrName)
this._matchPartial(valuesMap, attrValue, cssSelector, matchedCallback);
}
}
}
_matchTerminal(map:Map<string,string> = null, name, matchedCallback) {
if (isBlank(map) || isBlank(name)) {
return;
}
var selectables = MapWrapper.get(map, name)
if (isBlank(selectables)) {
return;
}
for (var index=0; index<selectables.length; index++) {
matchedCallback(selectables[index]);
}
}
_matchPartial(map:Map<string,string> = null, name, cssSelector, matchedCallback) {
if (isBlank(map) || isBlank(name)) {
return;
}
var nestedSelector = MapWrapper.get(map, name)
if (isBlank(nestedSelector)) {
return;
}
// TODO(perf): get rid of recursion and measure again
// TODO(perf): don't pass the whole selector into the recursion,
// but only the not processed parts
nestedSelector.match(cssSelector, matchedCallback);
}
}

View File

@ -0,0 +1,9 @@
library angular.core.compiler.shadow_dom;
//TODO: merge this file with shadow_dom.es6 when the traspiler support creating const globals
import './shadow_dom_strategy.dart';
export './shadow_dom_strategy.dart';
const ShadowDomEmulated = const EmulatedShadowDomStrategy();
const ShadowDomNative = const NativeShadowDomStrategy();

View File

@ -0,0 +1,5 @@
import {EmulatedShadowDomStrategy, NativeShadowDomStrategy} from './shadow_dom_strategy';
export * from './shadow_dom_strategy';
export var ShadowDomEmulated = new EmulatedShadowDomStrategy();
export var ShadowDomNative = new NativeShadowDomStrategy();

View File

@ -0,0 +1,99 @@
import {Decorator} from '../../annotations/annotations';
import {SourceLightDom, DestinationLightDom, LightDom} from './light_dom';
import {Inject} from 'di/di';
import {Element, Node, DOM} from 'facade/src/dom';
import {isPresent} from 'facade/src/lang';
import {List, ListWrapper} from 'facade/src/collection';
import {NgElement} from 'core/src/dom/element';
var _scriptTemplate = DOM.createScriptTag('type', 'ng/content')
class ContentStrategy {
nodes: List<Node>;
insert(nodes:List<Nodes>){}
}
/**
* An implementation of the content tag that is used by transcluding components.
* It is used when the content tag is not a direct child of another component,
* and thus does not affect redistribution.
*/
class RenderedContent extends ContentStrategy {
beginScript:Element;
endScript:Element;
constructor(contentEl:Element) {
this._replaceContentElementWithScriptTags(contentEl);
this.nodes = [];
}
// Inserts the nodes in between the start and end scripts.
// Previous content is removed.
insert(nodes:List<Node>) {
this.nodes = nodes;
DOM.insertAllBefore(this.endScript, nodes);
this._removeNodesUntil(ListWrapper.isEmpty(nodes) ? this.endScript : nodes[0]);
}
// Replaces the content tag with a pair of script tags
_replaceContentElementWithScriptTags(contentEl:Element) {
this.beginScript = DOM.clone(_scriptTemplate);
this.endScript = DOM.clone(_scriptTemplate);
DOM.insertBefore(contentEl, this.beginScript);
DOM.insertBefore(contentEl, this.endScript);
DOM.removeChild(DOM.parentElement(contentEl), contentEl);
}
_removeNodesUntil(node:Node) {
var p = DOM.parentElement(this.beginScript);
for (var next = DOM.nextSibling(this.beginScript);
next !== node;
next = DOM.nextSibling(this.beginScript)) {
DOM.removeChild(p, next);
}
}
}
/**
* An implementation of the content tag that is used by transcluding components.
* It is used when the content tag is a direct child of another component,
* and thus does not get rendered but only affect the distribution of its parent component.
*/
class IntermediateContent extends ContentStrategy {
destinationLightDom:LightDom;
constructor(destinationLightDom:LightDom) {
this.destinationLightDom = destinationLightDom;
this.nodes = [];
}
insert(nodes:List<Node>) {
this.nodes = nodes;
this.destinationLightDom.redistribute();
}
}
@Decorator({
selector: 'content'
})
export class Content {
select:string;
_strategy:ContentStrategy;
constructor(@Inject(DestinationLightDom) destinationLightDom, contentEl:NgElement) {
this.select = contentEl.getAttribute('select');
this._strategy = isPresent(destinationLightDom) ?
new IntermediateContent(destinationLightDom) :
new RenderedContent(contentEl.domElement);
}
nodes():List<Node> {
return this._strategy.nodes;
}
insert(nodes:List<Node>) {
this._strategy.insert(nodes);
}
}

View File

@ -0,0 +1,131 @@
import {Element, Node, DOM} from 'facade/src/dom';
import {List, ListWrapper} from 'facade/src/collection';
import {isBlank, isPresent} from 'facade/src/lang';
import {View} from '../view';
import {ElementInjector} from '../element_injector';
import {ViewPort} from '../viewport';
import {Content} from './content_tag';
export class SourceLightDom {}
export class DestinationLightDom {}
class _Root {
node:Node;
injector:ElementInjector;
constructor(node, injector) {
this.node = node;
this.injector = injector;
}
}
// TODO: LightDom should implement SourceLightDom and DestinationLightDom
// once interfaces are supported
export class LightDom {
// The light DOM of the element is enclosed inside the lightDomView
lightDomView:View;
// The shadow DOM
shadowDomView:View;
// The nodes of the light DOM
nodes:List<Node>;
roots:List<_Root>;
constructor(lightDomView:View, shadowDomView:View, element:Element) {
this.lightDomView = lightDomView;
this.shadowDomView = shadowDomView;
this.nodes = DOM.childNodesAsList(element);
this.roots = null;
}
redistribute() {
var tags = this.contentTags();
if (tags.length > 0) {
redistributeNodes(tags, this.expandedDomNodes());
}
}
contentTags(): List<Content> {
return this._collectAllContentTags(this.shadowDomView, []);
}
// Collects the Content directives from the view and all its child views
_collectAllContentTags(view: View, acc:List<Content>):List<Content> {
var eis = view.elementInjectors;
for (var i = 0; i < eis.length; ++i) {
var ei = eis[i];
if (isBlank(ei)) continue;
if (ei.hasDirective(Content)) {
ListWrapper.push(acc, ei.get(Content));
} else if (ei.hasPreBuiltObject(ViewPort)) {
var vp = ei.get(ViewPort);
ListWrapper.forEach(vp.contentTagContainers(), (view) => {
this._collectAllContentTags(view, acc);
});
}
}
return acc;
}
// Collects the nodes of the light DOM by merging:
// - nodes from enclosed ViewPorts,
// - nodes from enclosed content tags,
// - plain DOM nodes
expandedDomNodes():List {
var res = [];
var roots = this._roots();
for (var i = 0; i < roots.length; ++i) {
var root = roots[i];
var ei = root.injector;
if (isPresent(ei) && ei.hasPreBuiltObject(ViewPort)) {
var vp = root.injector.get(ViewPort);
res = ListWrapper.concat(res, vp.nodes());
} else if (isPresent(ei) && ei.hasDirective(Content)) {
var content = root.injector.get(Content);
res = ListWrapper.concat(res, content.nodes());
} else {
ListWrapper.push(res, root.node);
}
}
return res;
}
// Returns a list of Roots for all the nodes of the light DOM.
// The Root object contains the DOM node and its corresponding injector (could be null).
_roots() {
if (isPresent(this.roots)) return this.roots;
var viewInj = this.lightDomView.elementInjectors;
this.roots = ListWrapper.map(this.nodes, (n) =>
new _Root(n, ListWrapper.find(viewInj, (inj) => inj.forElement(n))));
return this.roots;
}
}
// Projects the light DOM into the shadow DOM
function redistributeNodes(contents:List<Content>, nodes:List<Node>) {
for (var i = 0; i < contents.length; ++i) {
var content = contents[i];
var select = content.select;
var matchSelector = (n) => DOM.elementMatches(n, select);
if (isBlank(select)) {
content.insert(nodes);
ListWrapper.clear(nodes);
} else {
var matchingNodes = ListWrapper.filter(nodes, matchSelector);
content.insert(matchingNodes);
ListWrapper.removeAll(nodes, matchingNodes);
}
}
}

View File

@ -0,0 +1,50 @@
import {CONST} from 'facade/src/lang';
import {DOM, Element} from 'facade/src/dom';
import {List} from 'facade/src/collection';
import {View} from './view';
import {Content} from './shadow_dom_emulation/content_tag';
import {LightDom} from './shadow_dom_emulation/light_dom';
export class ShadowDomStrategy {
@CONST() constructor() {}
attachTemplate(el:Element, view:View){}
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){}
polyfillDirectives():List<Type>{ return null; };
}
export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
@CONST() constructor() {}
attachTemplate(el:Element, view:View){
DOM.clearNodes(el);
moveViewNodesIntoParent(el, view);
}
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){
return new LightDom(lightDomView, shadowDomView, el);
}
polyfillDirectives():List<Type> {
return [Content];
}
}
export class NativeShadowDomStrategy extends ShadowDomStrategy {
@CONST() constructor() {}
attachTemplate(el:Element, view:View){
moveViewNodesIntoParent(el.createShadowRoot(), view);
}
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){
return null;
}
polyfillDirectives():List<Type> {
return [];
}
}
function moveViewNodesIntoParent(parent, view) {
for (var i = 0; i < view.nodes.length; ++i) {
DOM.appendChild(parent, view.nodes[i]);
}
}

View File

@ -0,0 +1,11 @@
import {Promise} from 'facade/src/async';
//import {Document} from 'facade/src/dom';
/**
* Strategy to load component templates.
*/
export class TemplateLoader {
load(url:string):Promise<Document> {
return null;
}
}

View File

@ -0,0 +1,589 @@
import {DOM, Element, Node, Text, DocumentFragment, TemplateElement} from 'facade/src/dom';
import {ListWrapper, MapWrapper, StringMapWrapper, List} from 'facade/src/collection';
import {AST, ContextWithVariableBindings, ChangeDispatcher, ProtoChangeDetector, ChangeDetector, ChangeRecord}
from 'change_detection/change_detection';
import {ProtoElementInjector, ElementInjector, PreBuiltObjects} from './element_injector';
import {BindingPropagationConfig} from './binding_propagation_config';
import {ElementBinder} from './element_binder';
import {DirectiveMetadata} from './directive_metadata';
import {SetterFn} from 'reflection/src/types';
import {FIELD, IMPLEMENTS, int, isPresent, isBlank, BaseException} from 'facade/src/lang';
import {Injector} from 'di/di';
import {NgElement} from 'core/src/dom/element';
import {ViewPort} from './viewport';
import {OnChange} from './interfaces';
import {Content} from './shadow_dom_emulation/content_tag';
import {LightDom, DestinationLightDom} from './shadow_dom_emulation/light_dom';
const NG_BINDING_CLASS = 'ng-binding';
const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
// TODO(tbosch): Cannot use `const` because of Dart.
var NO_FORMATTERS = MapWrapper.create();
/**
* Const of making objects: http://jsperf.com/instantiate-size-of-object
*/
@IMPLEMENTS(ChangeDispatcher)
export class View {
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
rootElementInjectors:List<ElementInjector>;
elementInjectors:List<ElementInjector>;
bindElements:List<Element>;
textNodes:List<Text>;
changeDetector:ChangeDetector;
/// When the view is part of render tree, the DocumentFragment is empty, which is why we need
/// to keep track of the nodes.
nodes:List<Node>;
componentChildViews: List<View>;
viewPorts: List<ViewPort>;
preBuiltObjects: List<PreBuiltObjects>;
proto: ProtoView;
context: any;
contextWithLocals:ContextWithVariableBindings;
constructor(proto:ProtoView, nodes:List<Node>, protoChangeDetector:ProtoChangeDetector, protoContextLocals:Map) {
this.proto = proto;
this.nodes = nodes;
this.changeDetector = protoChangeDetector.instantiate(this, NO_FORMATTERS);
this.elementInjectors = null;
this.rootElementInjectors = null;
this.textNodes = null;
this.bindElements = null;
this.componentChildViews = null;
this.viewPorts = null;
this.preBuiltObjects = null;
this.context = null;
this.contextWithLocals = (MapWrapper.size(protoContextLocals) > 0)
? new ContextWithVariableBindings(null, MapWrapper.clone(protoContextLocals))
: null;
}
init(elementInjectors:List, rootElementInjectors:List, textNodes: List, bindElements:List, viewPorts:List, preBuiltObjects:List, componentChildViews:List) {
this.elementInjectors = elementInjectors;
this.rootElementInjectors = rootElementInjectors;
this.textNodes = textNodes;
this.bindElements = bindElements;
this.viewPorts = viewPorts;
this.preBuiltObjects = preBuiltObjects;
this.componentChildViews = componentChildViews;
}
setLocal(contextName: string, value) {
if (!this.hydrated()) throw new BaseException('Cannot set locals on dehydrated view.');
if (!MapWrapper.contains(this.proto.variableBindings, contextName)) {
return;
}
var templateName = MapWrapper.get(this.proto.variableBindings, contextName);
this.context.set(templateName, value);
}
hydrated() {
return isPresent(this.context);
}
_hydrateContext(newContext) {
if (isPresent(this.contextWithLocals)) {
this.contextWithLocals.parent = newContext;
this.context = this.contextWithLocals;
} else {
this.context = newContext;
}
// TODO(tbosch): if we have a contextWithLocals we actually only need to
// set the contextWithLocals once. Would it be faster to always use a contextWithLocals
// even if we don't have locals and not update the recordRange here?
this.changeDetector.setContext(this.context);
}
_dehydrateContext() {
if (isPresent(this.contextWithLocals)) {
this.contextWithLocals.clearValues();
}
this.context = null;
}
/**
* A dehydrated view is a state of the view that allows it to be moved around
* the view tree, without incurring the cost of recreating the underlying
* injectors and watch records.
*
* A dehydrated view has the following properties:
*
* - all element injectors are empty.
* - all appInjectors are released.
* - all viewports are empty.
* - all context locals are set to null.
* - the view context is null.
*
* A call to hydrate/dehydrate does not attach/detach the view from the view
* tree.
*/
hydrate(appInjector: Injector, hostElementInjector: ElementInjector,
context: Object) {
if (this.hydrated()) throw new BaseException('The view is already hydrated.');
this._hydrateContext(context);
// viewPorts
for (var i = 0; i < this.viewPorts.length; i++) {
this.viewPorts[i].hydrate(appInjector, hostElementInjector);
}
var binders = this.proto.elementBinders;
var componentChildViewIndex = 0;
for (var i = 0; i < binders.length; ++i) {
var componentDirective = binders[i].componentDirective;
var shadowDomAppInjector = null;
// shadowDomAppInjector
if (isPresent(componentDirective)) {
var services = componentDirective.annotation.componentServices;
if (isPresent(services))
shadowDomAppInjector = appInjector.createChild(services);
else {
shadowDomAppInjector = appInjector;
}
} else {
shadowDomAppInjector = null;
}
// elementInjectors
var elementInjector = this.elementInjectors[i];
if (isPresent(elementInjector)) {
elementInjector.instantiateDirectives(appInjector, shadowDomAppInjector, this.preBuiltObjects[i]);
}
if (isPresent(componentDirective)) {
this.componentChildViews[componentChildViewIndex++].hydrate(shadowDomAppInjector,
elementInjector, elementInjector.getComponent());
}
}
// this should be moved into DOM write queue
for (var i = 0; i < binders.length; ++i) {
var componentDirective = binders[i].componentDirective;
if (isPresent(componentDirective)) {
var lightDom = this.preBuiltObjects[i].lightDom;
if (isPresent(lightDom)) {
lightDom.redistribute();
}
}
}
}
dehydrate() {
// Note: preserve the opposite order of the hydration process.
// componentChildViews
for (var i = 0; i < this.componentChildViews.length; i++) {
this.componentChildViews[i].dehydrate();
}
// elementInjectors
for (var i = 0; i < this.elementInjectors.length; i++) {
if (isPresent(this.elementInjectors[i])) {
this.elementInjectors[i].clearDirectives();
}
}
// viewPorts
if (isPresent(this.viewPorts)) {
for (var i = 0; i < this.viewPorts.length; i++) {
this.viewPorts[i].dehydrate();
}
}
this._dehydrateContext();
}
onRecordChange(groupMemento, records:List) {
this._invokeMementos(records);
if (groupMemento instanceof DirectivePropertyGroupMemento) {
this._notifyDirectiveAboutChanges(groupMemento, records);
}
}
_invokeMementos(records:List) {
for(var i = 0; i < records.length; ++i) {
this._invokeMementoFor(records[i]);
}
}
_notifyDirectiveAboutChanges(groupMemento, records:List) {
var dir = groupMemento.directive(this.elementInjectors);
if (dir instanceof OnChange) {
dir.onChange(this._collectChanges(records));
}
}
// dispatch to element injector or text nodes based on context
_invokeMementoFor(record:ChangeRecord) {
var memento = record.bindingMemento;
if (memento instanceof DirectivePropertyMemento) {
// we know that it is DirectivePropertyMemento
var directiveMemento:DirectivePropertyMemento = memento;
directiveMemento.invoke(record, this.elementInjectors);
} else if (memento instanceof ElementPropertyMemento) {
var elementMemento:ElementPropertyMemento = memento;
elementMemento.invoke(record, this.bindElements);
} else {
// we know it refers to _textNodes.
var textNodeIndex:number = memento;
DOM.setText(this.textNodes[textNodeIndex], record.currentValue);
}
}
_collectChanges(records:List) {
var changes = StringMapWrapper.create();
for(var i = 0; i < records.length; ++i) {
var record = records[i];
var propertyUpdate = new PropertyUpdate(record.currentValue, record.previousValue);
StringMapWrapper.set(changes, record.bindingMemento._setterName, propertyUpdate);
}
return changes;
}
}
export class ProtoView {
element:Element;
elementBinders:List<ElementBinder>;
protoChangeDetector:ProtoChangeDetector;
variableBindings: Map;
protoContextLocals:Map;
textNodesWithBindingCount:int;
elementsWithBindingCount:int;
instantiateInPlace:boolean;
rootBindingOffset:int;
isTemplateElement:boolean;
constructor(
template:Element,
protoChangeDetector:ProtoChangeDetector) {
this.element = template;
this.elementBinders = [];
this.variableBindings = MapWrapper.create();
this.protoContextLocals = MapWrapper.create();
this.protoChangeDetector = protoChangeDetector;
this.textNodesWithBindingCount = 0;
this.elementsWithBindingCount = 0;
this.instantiateInPlace = false;
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS))
? 1 : 0;
this.isTemplateElement = this.element instanceof TemplateElement;
}
// TODO(rado): hostElementInjector should be moved to hydrate phase.
instantiate(hostElementInjector: ElementInjector):View {
var rootElementClone = this.instantiateInPlace ? this.element : DOM.clone(this.element);
var elementsWithBindingsDynamic;
if (this.isTemplateElement) {
elementsWithBindingsDynamic = DOM.querySelectorAll(rootElementClone.content, NG_BINDING_CLASS_SELECTOR);
} else {
elementsWithBindingsDynamic= DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
}
var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length);
for (var i = 0; i < elementsWithBindingsDynamic.length; ++i) {
elementsWithBindings[i] = elementsWithBindingsDynamic[i];
}
var viewNodes;
if (this.isTemplateElement) {
var childNode = DOM.firstChild(rootElementClone.content);
viewNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in ProtoView
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
while(childNode != null) {
ListWrapper.push(viewNodes, childNode);
childNode = DOM.nextSibling(childNode);
}
} else {
viewNodes = [rootElementClone];
}
var view = new View(this, viewNodes, this.protoChangeDetector, this.protoContextLocals);
var binders = this.elementBinders;
var elementInjectors = ListWrapper.createFixedSize(binders.length);
var rootElementInjectors = [];
var textNodes = [];
var elementsWithPropertyBindings = [];
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
var viewPorts = [];
var componentChildViews = [];
for (var i = 0; i < binders.length; i++) {
var binder = binders[i];
var element;
if (i === 0 && this.rootBindingOffset === 1) {
element = rootElementClone;
} else {
element = elementsWithBindings[i - this.rootBindingOffset];
}
var elementInjector = null;
// elementInjectors and rootElementInjectors
var protoElementInjector = binder.protoElementInjector;
if (isPresent(protoElementInjector)) {
if (isPresent(protoElementInjector.parent)) {
var parentElementInjector = elementInjectors[protoElementInjector.parent.index];
elementInjector = protoElementInjector.instantiate(parentElementInjector, null, binder.events);
} else {
elementInjector = protoElementInjector.instantiate(null, hostElementInjector, binder.events);
ListWrapper.push(rootElementInjectors, elementInjector);
}
}
elementInjectors[i] = elementInjector;
if (binder.hasElementPropertyBindings) {
ListWrapper.push(elementsWithPropertyBindings, element);
}
// textNodes
var textNodeIndices = binder.textNodeIndices;
if (isPresent(textNodeIndices)) {
var childNode = DOM.firstChild(DOM.templateAwareRoot(element));
for (var j = 0, k = 0; j < textNodeIndices.length; j++) {
for(var index = textNodeIndices[j]; k < index; k++) {
childNode = DOM.nextSibling(childNode);
}
ListWrapper.push(textNodes, childNode);
}
}
// componentChildViews
var lightDom = null;
var bindingPropagationConfig = null;
if (isPresent(binder.componentDirective)) {
var childView = binder.nestedProtoView.instantiate(elementInjector);
view.changeDetector.addChild(childView.changeDetector);
lightDom = binder.componentDirective.shadowDomStrategy.constructLightDom(view, childView, element);
binder.componentDirective.shadowDomStrategy.attachTemplate(element, childView);
bindingPropagationConfig = new BindingPropagationConfig(view.changeDetector);
ListWrapper.push(componentChildViews, childView);
}
// viewPorts
var viewPort = null;
if (isPresent(binder.templateDirective)) {
var destLightDom = this._parentElementLightDom(protoElementInjector, preBuiltObjects);
viewPort = new ViewPort(view, element, binder.nestedProtoView, elementInjector, destLightDom);
ListWrapper.push(viewPorts, viewPort);
}
// preBuiltObjects
if (isPresent(elementInjector)) {
preBuiltObjects[i] = new PreBuiltObjects(view, new NgElement(element), viewPort,
lightDom, bindingPropagationConfig);
}
// events
if (isPresent(binder.events)) {
MapWrapper.forEach(binder.events, (expr, eventName) => {
if (isBlank(elementInjector) || !elementInjector.hasEventEmitter(eventName)) {
ProtoView._addNativeEventListener(element, eventName, expr, view);
}
});
}
}
view.init(elementInjectors, rootElementInjectors, textNodes, elementsWithPropertyBindings,
viewPorts, preBuiltObjects, componentChildViews);
return view;
}
static _addNativeEventListener(element: Element, eventName: string, expr: AST, view: View) {
var locals = MapWrapper.create();
var innerCallback = ProtoView.buildInnerCallback(expr, view, locals);
DOM.on(element, eventName, (event) => {
if (event.target === element) {
innerCallback(event);
}
});
}
static buildInnerCallback(expr:AST, view:View, locals: Map) {
return (event) => {
// Most of the time the event will be fired only when the view is
// in the live document. However, in a rare circumstance the
// view might get dehydrated, in between the event queuing up and
// firing.
if (view.hydrated()) {
MapWrapper.set(locals, '\$event', event);
var context = new ContextWithVariableBindings(view.context, locals);
expr.eval(context);
}
}
}
_parentElementLightDom(protoElementInjector:ProtoElementInjector, preBuiltObjects:List):LightDom {
var p = protoElementInjector.parent;
return isPresent(p) ? preBuiltObjects[p.index].lightDom : null;
}
bindVariable(contextName:string, templateName:string) {
MapWrapper.set(this.variableBindings, contextName, templateName);
MapWrapper.set(this.protoContextLocals, templateName, null);
}
bindElement(protoElementInjector:ProtoElementInjector,
componentDirective:DirectiveMetadata = null, templateDirective:DirectiveMetadata = null):ElementBinder {
var elBinder = new ElementBinder(protoElementInjector, componentDirective, templateDirective);
ListWrapper.push(this.elementBinders, elBinder);
return elBinder;
}
/**
* Adds a text node binding for the last created ElementBinder via bindElement
*/
bindTextNode(indexInParent:int, expression:AST) {
var elBinder = this.elementBinders[this.elementBinders.length-1];
if (isBlank(elBinder.textNodeIndices)) {
elBinder.textNodeIndices = ListWrapper.create();
}
ListWrapper.push(elBinder.textNodeIndices, indexInParent);
var memento = this.textNodesWithBindingCount++;
this.protoChangeDetector.addAst(expression, memento, memento);
}
/**
* Adds an element property binding for the last created ElementBinder via bindElement
*/
bindElementProperty(expression:AST, setterName:string, setter:SetterFn) {
var elBinder = this.elementBinders[this.elementBinders.length-1];
if (!elBinder.hasElementPropertyBindings) {
elBinder.hasElementPropertyBindings = true;
this.elementsWithBindingCount++;
}
var memento = new ElementPropertyMemento(this.elementsWithBindingCount-1, setterName, setter);
this.protoChangeDetector.addAst(expression, memento, memento);
}
/**
* Adds an event binding for the last created ElementBinder via bindElement
*/
bindEvent(eventName:string, expression:AST) {
var elBinder = this.elementBinders[this.elementBinders.length-1];
if (isBlank(elBinder.events)) {
elBinder.events = MapWrapper.create();
}
MapWrapper.set(elBinder.events, eventName, expression);
}
/**
* Adds a directive property binding for the last created ElementBinder via bindElement
*/
bindDirectiveProperty(
directiveIndex:number,
expression:AST,
setterName:string,
setter:SetterFn,
isContentWatch: boolean) {
var expMemento = new DirectivePropertyMemento(
this.elementBinders.length-1,
directiveIndex,
setterName,
setter
);
var groupMemento = DirectivePropertyGroupMemento.get(expMemento);
this.protoChangeDetector.addAst(expression, expMemento, groupMemento, isContentWatch);
}
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>,
// and the component template is already compiled into protoView.
// Used for bootstrapping.
static createRootProtoView(protoView: ProtoView,
insertionElement, rootComponentAnnotatedType: DirectiveMetadata,
protoChangeDetector:ProtoChangeDetector
): ProtoView {
DOM.addClass(insertionElement, 'ng-binding');
var rootProtoView = new ProtoView(insertionElement, protoChangeDetector);
rootProtoView.instantiateInPlace = true;
var binder = rootProtoView.bindElement(
new ProtoElementInjector(null, 0, [rootComponentAnnotatedType.type], true));
binder.componentDirective = rootComponentAnnotatedType;
binder.nestedProtoView = protoView;
return rootProtoView;
}
}
export class ElementPropertyMemento {
_elementIndex:int;
_setterName:string;
_setter:SetterFn;
constructor(elementIndex:int, setterName:string, setter:SetterFn) {
this._elementIndex = elementIndex;
this._setterName = setterName;
this._setter = setter;
}
invoke(record:ChangeRecord, bindElements:List<Element>) {
var element:Element = bindElements[this._elementIndex];
this._setter(element, record.currentValue);
}
}
export class DirectivePropertyMemento {
_elementInjectorIndex:int;
_directiveIndex:int;
_setterName:string;
_setter:SetterFn;
constructor(
elementInjectorIndex:number,
directiveIndex:number,
setterName:string,
setter:SetterFn) {
this._elementInjectorIndex = elementInjectorIndex;
this._directiveIndex = directiveIndex;
this._setterName = setterName;
this._setter = setter;
}
invoke(record:ChangeRecord, elementInjectors:List<ElementInjector>) {
var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex];
var directive = elementInjector.getAtIndex(this._directiveIndex);
this._setter(directive, record.currentValue);
}
}
var _groups = MapWrapper.create();
class DirectivePropertyGroupMemento {
_elementInjectorIndex:number;
_directiveIndex:number;
constructor(elementInjectorIndex:number, directiveIndex:number) {
this._elementInjectorIndex = elementInjectorIndex;
this._directiveIndex = directiveIndex;
}
static get(memento:DirectivePropertyMemento) {
var elementInjectorIndex = memento._elementInjectorIndex;
var directiveIndex = memento._directiveIndex;
var id = elementInjectorIndex * 100 + directiveIndex;
if (!MapWrapper.contains(_groups, id)) {
MapWrapper.set(_groups, id, new DirectivePropertyGroupMemento(elementInjectorIndex, directiveIndex));
}
return MapWrapper.get(_groups, id);
}
directive(elementInjectors:List<ElementInjector>) {
var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex];
return elementInjector.getAtIndex(this._directiveIndex);
}
}
class PropertyUpdate {
currentValue;
previousValue;
constructor(currentValue, previousValue) {
this.currentValue = currentValue;
this.previousValue = previousValue;
}
}

View File

@ -0,0 +1,151 @@
import {View, ProtoView} from './view';
import {DOM, Node, Element} from 'facade/src/dom';
import {ListWrapper, MapWrapper, List} from 'facade/src/collection';
import {BaseException} from 'facade/src/lang';
import {Injector} from 'di/di';
import {ElementInjector} from 'core/src/compiler/element_injector';
import {isPresent, isBlank} from 'facade/src/lang';
export class ViewPort {
parentView: View;
templateElement: Element;
defaultProtoView: ProtoView;
_views: List<View>;
_lightDom: any;
elementInjector: ElementInjector;
appInjector: Injector;
hostElementInjector: ElementInjector;
constructor(parentView: View, templateElement: Element, defaultProtoView: ProtoView,
elementInjector: ElementInjector, lightDom = null) {
this.parentView = parentView;
this.templateElement = templateElement;
this.defaultProtoView = defaultProtoView;
this.elementInjector = elementInjector;
this._lightDom = lightDom;
// The order in this list matches the DOM order.
this._views = [];
this.appInjector = null;
this.hostElementInjector = null;
}
hydrate(appInjector: Injector, hostElementInjector: ElementInjector) {
this.appInjector = appInjector;
this.hostElementInjector = hostElementInjector;
}
dehydrate() {
this.appInjector = null;
this.hostElementInjector = null;
this.clear();
}
clear() {
for (var i = this._views.length - 1; i >= 0; i--) {
this.remove(i);
}
}
get(index: number): View {
return this._views[index];
}
get length() {
return this._views.length;
}
_siblingToInsertAfter(index: number) {
if (index == 0) return this.templateElement;
return ListWrapper.last(this._views[index - 1].nodes);
}
hydrated() {
return isPresent(this.appInjector);
}
// TODO(rado): profile and decide whether bounds checks should be added
// to the methods below.
create(atIndex=-1): View {
if (!this.hydrated()) throw new BaseException(
'Cannot create views on a dehydrated view port');
// TODO(rado): replace with viewFactory.
var newView = this.defaultProtoView.instantiate(this.hostElementInjector);
newView.hydrate(this.appInjector, this.hostElementInjector, this.parentView.context);
return this.insert(newView, atIndex);
}
insert(view, atIndex=-1): View {
if (atIndex == -1) atIndex = this._views.length;
ListWrapper.insert(this._views, atIndex, view);
if (isBlank(this._lightDom)) {
ViewPort.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view);
} else {
this._lightDom.redistribute();
}
this.parentView.changeDetector.addChild(view.changeDetector);
this._linkElementInjectors(view);
return view;
}
remove(atIndex=-1) {
if (atIndex == -1) atIndex = this._views.length - 1;
var view = this.detach(atIndex);
view.dehydrate();
// view is intentionally not returned to the client.
}
/**
* The method can be used together with insert to implement a view move, i.e.
* moving the dom nodes while the directives in the view stay intact.
*/
detach(atIndex=-1): View {
if (atIndex == -1) atIndex = this._views.length - 1;
var detachedView = this.get(atIndex);
ListWrapper.removeAt(this._views, atIndex);
if (isBlank(this._lightDom)) {
ViewPort.removeViewNodesFromParent(this.templateElement.parentNode, detachedView);
} else {
this._lightDom.redistribute();
}
detachedView.changeDetector.remove();
this._unlinkElementInjectors(detachedView);
return detachedView;
}
contentTagContainers() {
return this._views;
}
nodes():List<Node> {
var r = [];
for (var i = 0; i < this._views.length; ++i) {
r = ListWrapper.concat(r, this._views[i].nodes);
}
return r;
}
_linkElementInjectors(view) {
for (var i = 0; i < view.rootElementInjectors.length; ++i) {
view.rootElementInjectors[i].parent = this.elementInjector;
}
}
_unlinkElementInjectors(view) {
for (var i = 0; i < view.rootElementInjectors.length; ++i) {
view.rootElementInjectors[i].parent = null;
}
}
static moveViewNodesAfterSibling(sibling, view) {
for (var i = view.nodes.length - 1; i >= 0; --i) {
DOM.insertAfter(sibling, view.nodes[i]);
}
}
static removeViewNodesFromParent(parent, view) {
for (var i = view.nodes.length - 1; i >= 0; --i) {
DOM.removeChild(parent, view.nodes[i]);
}
}
}

View File

@ -0,0 +1,13 @@
import {DOM, Element} from 'facade/src/dom';
import {normalizeBlank} from 'facade/src/lang';
export class NgElement {
domElement:Element;
constructor(domElement:Element) {
this.domElement = domElement;
}
getAttribute(name:string) {
return normalizeBlank(DOM.getAttribute(this.domElement, name));
}
}

View File

@ -0,0 +1,35 @@
import {FIELD, print} from 'facade/src/lang';
import {ChangeDetector} from 'change_detection/change_detection';
import {VmTurnZone} from 'core/src/zone/vm_turn_zone';
import {ListWrapper} from 'facade/src/collection';
export class LifeCycle {
_changeDetector:ChangeDetector;
_enforceNoNewChanges:boolean;
constructor(changeDetector:ChangeDetector, enforceNoNewChanges:boolean = false) {
this._changeDetector = changeDetector;
this._enforceNoNewChanges = enforceNoNewChanges;
}
registerWith(zone:VmTurnZone) {
// temporary error handler, we should inject one
var errorHandler = (exception, stackTrace) => {
var longStackTrace = ListWrapper.join(stackTrace, "\n\n-----async gap-----\n");
print(`${exception}\n\n${longStackTrace}`);
throw exception;
};
zone.initCallbacks({
onErrorHandler: errorHandler,
onTurnDone: () => this.tick()
});
}
tick() {
this._changeDetector.detectChanges();
if (this._enforceNoNewChanges) {
this._changeDetector.checkNoChanges();
}
}
}

View File

@ -0,0 +1,105 @@
library angular.zone;
import 'dart:async' as async;
import 'package:stack_trace/stack_trace.dart' show Chain;
class VmTurnZone {
Function _onTurnStart;
Function _onTurnDone;
Function _onScheduleMicrotask;
Function _onErrorHandler;
async.Zone _outerZone;
async.Zone _innerZone;
int _nestedRunCounter;
VmTurnZone({bool enableLongStackTrace}) {
_nestedRunCounter = 0;
_outerZone = async.Zone.current;
_innerZone = _createInnerZoneWithErrorHandling(enableLongStackTrace);
}
initCallbacks({Function onTurnStart, Function onTurnDone, Function onScheduleMicrotask, Function onErrorHandler}) {
this._onTurnStart = onTurnStart;
this._onTurnDone = onTurnDone;
this._onScheduleMicrotask = onScheduleMicrotask;
this._onErrorHandler = onErrorHandler;
}
dynamic run(fn()) => _innerZone.run(fn);
dynamic runOutsideAngular(fn()) => _outerZone.run(fn);
async.Zone _createInnerZoneWithErrorHandling(bool enableLongStackTrace) {
if (enableLongStackTrace) {
return Chain.capture(() {
return _createInnerZone(async.Zone.current);
}, onError: this._onErrorWithLongStackTrace);
} else {
return async.runZoned(() {
return _createInnerZone(async.Zone.current);
}, onError: this._onErrorWithoutLongStackTrace);
}
}
async.Zone _createInnerZone(async.Zone zone) {
return zone.fork(specification: new async.ZoneSpecification(
run: _onRun,
runUnary: _onRunUnary,
scheduleMicrotask: _onMicrotask
));
}
dynamic _onRunBase(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) {
_nestedRunCounter++;
try {
if (_nestedRunCounter == 1 && _onTurnStart != null) delegate.run(zone, _onTurnStart);
return fn();
} catch (e, s) {
if (_onErrorHandler != null && _nestedRunCounter == 1) {
_onErrorHandler(e, [s.toString()]);
} else {
rethrow;
}
} finally {
_nestedRunCounter--;
if (_nestedRunCounter == 0 && _onTurnDone != null) _finishTurn(zone, delegate);
}
}
dynamic _onRun(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn()) =>
_onRunBase(self, delegate, zone, () => delegate.run(zone, fn));
dynamic _onRunUnary(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn(args), args) =>
_onRunBase(self, delegate, zone, () => delegate.runUnary(zone, fn, args));
void _finishTurn(zone, delegate) {
delegate.run(zone, _onTurnDone);
}
_onMicrotask(async.Zone self, async.ZoneDelegate delegate, async.Zone zone, fn) {
if (this._onScheduleMicrotask != null) {
_onScheduleMicrotask(fn);
} else {
delegate.scheduleMicrotask(zone, fn);
}
}
_onErrorWithLongStackTrace(exception, Chain chain) {
final traces = chain.terse.traces.map((t) => t.toString()).toList();
_onError(exception, traces, chain.traces[0]);
}
_onErrorWithoutLongStackTrace(exception, StackTrace trace) {
_onError(exception, [trace.toString()], trace);
}
_onError(exception, List<String> traces, StackTrace singleTrace) {
if (_onErrorHandler != null) {
_onErrorHandler(exception, traces);
} else {
_outerZone.handleUncaughtError(exception, singleTrace);
}
}
}

View File

@ -0,0 +1,89 @@
import {List, ListWrapper, StringMapWrapper} from 'facade/src/collection';
import {normalizeBlank, isPresent} from 'facade/src/lang';
export class VmTurnZone {
_outerZone;
_innerZone;
_onTurnStart:Function;
_onTurnDone:Function;
_onErrorHandler:Function;
_nestedRunCounter:number;
constructor({enableLongStackTrace}) {
this._nestedRunCounter = 0;
this._onTurnStart = null;
this._onTurnDone = null;
this._onErrorHandler = null;
this._outerZone = window.zone;
this._innerZone = this._createInnerZone(this._outerZone, enableLongStackTrace);
}
initCallbacks({onTurnStart, onTurnDone, onScheduleMicrotask, onErrorHandler} = {}) {
this._onTurnStart = normalizeBlank(onTurnStart);
this._onTurnDone = normalizeBlank(onTurnDone);
this._onErrorHandler = normalizeBlank(onErrorHandler);
}
run(fn) {
return this._innerZone.run(fn);
}
runOutsideAngular(fn) {
return this._outerZone.run(fn);
}
_createInnerZone(zone, enableLongStackTrace) {
var vmTurnZone = this;
var errorHandling;
if (enableLongStackTrace) {
errorHandling = StringMapWrapper.merge(Zone.longStackTraceZone, {
onError: function (e) {
vmTurnZone._onError(this, e)
}
});
} else {
errorHandling = {
onError: function (e) {
vmTurnZone._onError(this, e)
}
};
}
return zone.fork(errorHandling).fork({
beforeTask: () => {this._beforeTask()},
afterTask: () => {this._afterTask()}
});
}
_beforeTask(){
this._nestedRunCounter ++;
if(this._nestedRunCounter === 1 && this._onTurnStart) {
this._onTurnStart();
}
}
_afterTask(){
this._nestedRunCounter --;
if(this._nestedRunCounter === 0 && this._onTurnDone) {
this._onTurnDone();
}
}
_onError(zone, e) {
if (isPresent(this._onErrorHandler)) {
var trace = [normalizeBlank(e.stack)];
while (zone && zone.constructedAtException) {
trace.push(zone.constructedAtException.get());
zone = zone.parent;
}
this._onErrorHandler(e, trace);
} else {
throw e;
}
}
}

92
modules/angular2/src/di/annotations.js vendored Normal file
View File

@ -0,0 +1,92 @@
import {CONST} from "facade/src/lang";
/**
* A parameter annotation that creates a synchronous eager dependency.
*
* ```
* class AComponent {
* constructor(@Inject('aServiceToken') aService) {}
* }
* ```
*
*/
export class Inject {
token;
@CONST()
constructor(token) {
this.token = token;
}
}
/**
* A parameter annotation that creates an asynchronous eager dependency.
*
* ```
* class AComponent {
* constructor(@InjectPromise('aServiceToken') aServicePromise) {
* aServicePromise.then(aService => ...);
* }
* }
* ```
*
*/
export class InjectPromise {
token;
@CONST()
constructor(token) {
this.token = token;
}
}
/**
* A parameter annotation that creates a synchronous lazy dependency.
*
* ```
* class AComponent {
* constructor(@InjectLazy('aServiceToken') aServiceFn) {
* aService = aServiceFn();
* }
* }
* ```
*
*/
export class InjectLazy {
token;
@CONST()
constructor(token) {
this.token = token;
}
}
/**
* `DependencyAnnotation` is used by the framework to extend DI.
*
* Only annotations implementing `DependencyAnnotation` will be added
* to the list of dependency properties.
*
* For example:
*
* ```
* class Parent extends DependencyAnnotation {}
* class NotDependencyProperty {}
*
* class AComponent {
* constructor(@Parent @NotDependencyProperty aService:AService) {}
* }
* ```
*
* will create the following dependency:
*
* ```
* new Dependency(Key.get(AService), [new Parent()])
* ```
*
* The framework can use `new Parent()` to handle the `aService` dependency
* in a specific way.
*
*/
export class DependencyAnnotation {
@CONST()
constructor() {
}
}

128
modules/angular2/src/di/binding.js vendored Normal file
View File

@ -0,0 +1,128 @@
import {FIELD, Type, isBlank, isPresent} from 'facade/src/lang';
import {List, MapWrapper, ListWrapper} from 'facade/src/collection';
import {reflector} from 'reflection/src/reflection';
import {Key} from './key';
import {Inject, InjectLazy, InjectPromise, DependencyAnnotation} from './annotations';
import {NoAnnotationError} from './exceptions';
export class Dependency {
key:Key;
asPromise:boolean;
lazy:boolean;
properties:List;
constructor(key:Key, asPromise:boolean, lazy:boolean, properties:List) {
this.key = key;
this.asPromise = asPromise;
this.lazy = lazy;
this.properties = properties;
}
}
export class Binding {
key:Key;
factory:Function;
dependencies:List;
providedAsPromise:boolean;
constructor(key:Key, factory:Function, dependencies:List, providedAsPromise:boolean) {
this.key = key;
this.factory = factory;
this.dependencies = dependencies;
this.providedAsPromise = providedAsPromise;
}
}
export function bind(token):BindingBuilder {
return new BindingBuilder(token);
}
export class BindingBuilder {
token;
constructor(token) {
this.token = token;
}
toClass(type:Type):Binding {
return new Binding(
Key.get(this.token),
reflector.factory(type),
_dependenciesFor(type),
false
);
}
toValue(value):Binding {
return new Binding(
Key.get(this.token),
() => value,
[],
false
);
}
toFactory(factoryFunction:Function, dependencies:List = null):Binding {
return new Binding(
Key.get(this.token),
factoryFunction,
this._constructDependencies(factoryFunction, dependencies),
false
);
}
toAsyncFactory(factoryFunction:Function, dependencies:List = null):Binding {
return new Binding(
Key.get(this.token),
factoryFunction,
this._constructDependencies(factoryFunction, dependencies),
true
);
}
_constructDependencies(factoryFunction:Function, dependencies:List) {
return isBlank(dependencies) ?
_dependenciesFor(factoryFunction) :
ListWrapper.map(dependencies, (t) => new Dependency(Key.get(t), false, false, []));
}
}
function _dependenciesFor(typeOrFunc):List {
var params = reflector.parameters(typeOrFunc);
if (isBlank(params)) return [];
if (ListWrapper.any(params, (p) => isBlank(p))) throw new NoAnnotationError(typeOrFunc);
return ListWrapper.map(params, (p) => _extractToken(typeOrFunc, p));
}
function _extractToken(typeOrFunc, annotations) {
var type;
var depProps = [];
for (var i = 0; i < annotations.length; ++i) {
var paramAnnotation = annotations[i];
if (paramAnnotation instanceof Type) {
type = paramAnnotation;
} else if (paramAnnotation instanceof Inject) {
return _createDependency(paramAnnotation.token, false, false, []);
} else if (paramAnnotation instanceof InjectPromise) {
return _createDependency(paramAnnotation.token, true, false, []);
} else if (paramAnnotation instanceof InjectLazy) {
return _createDependency(paramAnnotation.token, false, true, []);
} else if (paramAnnotation instanceof DependencyAnnotation) {
ListWrapper.push(depProps, paramAnnotation);
}
}
if (isPresent(type)) {
return _createDependency(type, false, false, depProps);
} else {
throw new NoAnnotationError(typeOrFunc);
}
}
function _createDependency(token, asPromise, lazy, depProps):Dependency {
return new Dependency(Key.get(token), asPromise, lazy, depProps);
}

107
modules/angular2/src/di/exceptions.js vendored Normal file
View File

@ -0,0 +1,107 @@
import {ListWrapper, List} from 'facade/src/collection';
import {stringify} from 'facade/src/lang';
import {Key} from './key';
function findFirstClosedCycle(keys:List) {
var res = [];
for(var i = 0; i < keys.length; ++i) {
if (ListWrapper.contains(res, keys[i])) {
ListWrapper.push(res, keys[i]);
return res;
} else {
ListWrapper.push(res, keys[i]);
}
}
return res;
}
function constructResolvingPath(keys:List) {
if (keys.length > 1) {
var reversed = findFirstClosedCycle(ListWrapper.reversed(keys));
var tokenStrs = ListWrapper.map(reversed, (k) => stringify(k.token));
return " (" + tokenStrs.join(' -> ') + ")";
} else {
return "";
}
}
export class KeyMetadataError extends Error {}
export class ProviderError extends Error {
keys:List;
constructResolvingMessage:Function;
message;
constructor(key:Key, constructResolvingMessage:Function) {
this.keys = [key];
this.constructResolvingMessage = constructResolvingMessage;
this.message = this.constructResolvingMessage(this.keys);
}
addKey(key:Key) {
ListWrapper.push(this.keys, key);
this.message = this.constructResolvingMessage(this.keys);
}
toString() {
return this.message;
}
}
export class NoProviderError extends ProviderError {
constructor(key:Key) {
super(key, function (keys:List) {
var first = stringify(ListWrapper.first(keys).token);
return `No provider for ${first}!${constructResolvingPath(keys)}`;
});
}
}
export class AsyncBindingError extends ProviderError {
constructor(key:Key) {
super(key, function (keys:List) {
var first = stringify(ListWrapper.first(keys).token);
return `Cannot instantiate ${first} synchronously. ` +
`It is provided as a promise!${constructResolvingPath(keys)}`;
});
}
}
export class CyclicDependencyError extends ProviderError {
constructor(key:Key) {
super(key, function (keys:List) {
return `Cannot instantiate cyclic dependency!${constructResolvingPath(keys)}`;
});
}
}
export class InstantiationError extends ProviderError {
constructor(originalException, key:Key) {
super(key, function (keys:List) {
var first = stringify(ListWrapper.first(keys).token);
return `Error during instantiation of ${first}!${constructResolvingPath(keys)}.` +
` ORIGINAL ERROR: ${originalException}`;
});
}
}
export class InvalidBindingError extends Error {
message:string;
constructor(binding) {
this.message = `Invalid binding ${binding}`;
}
toString() {
return this.message;
}
}
export class NoAnnotationError extends Error {
message:string;
constructor(typeOrFunc) {
this.message = `Cannot resolve all parameters for ${stringify(typeOrFunc)}`;
}
toString() {
return this.message;
}
}

258
modules/angular2/src/di/injector.js vendored Normal file
View File

@ -0,0 +1,258 @@
import {Map, List, MapWrapper, ListWrapper} from 'facade/src/collection';
import {Binding, BindingBuilder, bind} from './binding';
import {ProviderError, NoProviderError, InvalidBindingError,
AsyncBindingError, CyclicDependencyError, InstantiationError} from './exceptions';
import {FunctionWrapper, Type, isPresent, isBlank} from 'facade/src/lang';
import {Promise, PromiseWrapper} from 'facade/src/async';
import {Key} from './key';
var _constructing = new Object();
class _Waiting {
promise:Promise;
constructor(promise:Promise) {
this.promise = promise;
}
}
function _isWaiting(obj):boolean {
return obj instanceof _Waiting;
}
export class Injector {
_bindings:List;
_instances:List;
_parent:Injector;
_defaultBindings:boolean;
_asyncStrategy: _AsyncInjectorStrategy;
_syncStrategy:_SyncInjectorStrategy;
constructor(bindings:List, {parent=null, defaultBindings=false}={}) {
var flatten = _flattenBindings(bindings, MapWrapper.create());
this._bindings = this._createListOfBindings(flatten);
this._instances = this._createInstances();
this._parent = parent;
this._defaultBindings = defaultBindings;
this._asyncStrategy = new _AsyncInjectorStrategy(this);
this._syncStrategy = new _SyncInjectorStrategy(this);
}
get(token) {
return this._getByKey(Key.get(token), false, false);
}
asyncGet(token) {
return this._getByKey(Key.get(token), true, false);
}
createChild(bindings:List):Injector {
return new Injector(bindings, {parent: this});
}
_createListOfBindings(flattenBindings):List {
var bindings = ListWrapper.createFixedSize(Key.numberOfKeys + 1);
MapWrapper.forEach(flattenBindings, (v, keyId) => bindings[keyId] = v);
return bindings;
}
_createInstances():List {
return ListWrapper.createFixedSize(Key.numberOfKeys + 1);
}
_getByKey(key:Key, returnPromise:boolean, returnLazy:boolean) {
if (returnLazy) {
return () => this._getByKey(key, returnPromise, false);
}
var strategy = returnPromise ? this._asyncStrategy : this._syncStrategy;
var instance = strategy.readFromCache(key);
if (isPresent(instance)) return instance;
instance = strategy.instantiate(key);
if (isPresent(instance)) return instance;
if (isPresent(this._parent)) {
return this._parent._getByKey(key, returnPromise, returnLazy);
}
throw new NoProviderError(key);
}
_resolveDependencies(key:Key, binding:Binding, forceAsync:boolean):List {
try {
var getDependency = d => this._getByKey(d.key, forceAsync || d.asPromise, d.lazy);
return ListWrapper.map(binding.dependencies, getDependency);
} catch (e) {
this._clear(key);
if (e instanceof ProviderError) e.addKey(key);
throw e;
}
}
_getInstance(key:Key) {
if (this._instances.length <= key.id) return null;
return ListWrapper.get(this._instances, key.id);
}
_setInstance(key:Key, obj) {
ListWrapper.set(this._instances, key.id, obj);
}
_getBinding(key:Key) {
var binding = this._bindings.length <= key.id ?
null :
ListWrapper.get(this._bindings, key.id);
if (isBlank(binding) && this._defaultBindings) {
return bind(key.token).toClass(key.token);
} else {
return binding;
}
}
_markAsConstructing(key:Key) {
this._setInstance(key, _constructing);
}
_clear(key:Key) {
this._setInstance(key, null);
}
}
class _SyncInjectorStrategy {
injector:Injector;
constructor(injector:Injector) {
this.injector = injector;
}
readFromCache(key:Key) {
if (key.token === Injector) {
return this.injector;
}
var instance = this.injector._getInstance(key);
if (instance === _constructing) {
throw new CyclicDependencyError(key);
} else if (isPresent(instance) && !_isWaiting(instance)) {
return instance;
} else {
return null;
}
}
instantiate(key:Key) {
var binding = this.injector._getBinding(key);
if (isBlank(binding)) return null;
if (binding.providedAsPromise) throw new AsyncBindingError(key);
//add a marker so we can detect cyclic dependencies
this.injector._markAsConstructing(key);
var deps = this.injector._resolveDependencies(key, binding, false);
return this._createInstance(key, binding, deps);
}
_createInstance(key:Key, binding:Binding, deps:List) {
try {
var instance = FunctionWrapper.apply(binding.factory, deps);
this.injector._setInstance(key, instance);
return instance;
} catch (e) {
this.injector._clear(key);
throw new InstantiationError(e, key);
}
}
}
class _AsyncInjectorStrategy {
injector:Injector;
constructor(injector:Injector) {
this.injector = injector;
}
readFromCache(key:Key) {
if (key.token === Injector) {
return PromiseWrapper.resolve(this.injector);
}
var instance = this.injector._getInstance(key);
if (instance === _constructing) {
throw new CyclicDependencyError(key);
} else if (_isWaiting(instance)) {
return instance.promise;
} else if (isPresent(instance)) {
return PromiseWrapper.resolve(instance);
} else {
return null;
}
}
instantiate(key:Key) {
var binding = this.injector._getBinding(key);
if (isBlank(binding)) return null;
//add a marker so we can detect cyclic dependencies
this.injector._markAsConstructing(key);
var deps = this.injector._resolveDependencies(key, binding, true);
var depsPromise = PromiseWrapper.all(deps);
var promise = PromiseWrapper
.then(depsPromise, null, (e) => this._errorHandler(key, e))
.then(deps => this._findOrCreate(key, binding, deps))
.then(instance => this._cacheInstance(key, instance));
this.injector._setInstance(key, new _Waiting(promise));
return promise;
}
_errorHandler(key:Key, e):Promise {
if (e instanceof ProviderError) e.addKey(key);
return PromiseWrapper.reject(e);
}
_findOrCreate(key:Key, binding:Binding, deps:List) {
try {
var instance = this.injector._getInstance(key);
if (!_isWaiting(instance)) return instance;
return FunctionWrapper.apply(binding.factory, deps);
} catch (e) {
this.injector._clear(key);
throw new InstantiationError(e, key);
}
}
_cacheInstance(key, instance) {
this.injector._setInstance(key, instance);
return instance
}
}
function _flattenBindings(bindings:List, res:Map) {
ListWrapper.forEach(bindings, function (b) {
if (b instanceof Binding) {
MapWrapper.set(res, b.key.id, b);
} else if (b instanceof Type) {
var s = bind(b).toClass(b);
MapWrapper.set(res, s.key.id, s);
} else if (b instanceof List) {
_flattenBindings(b, res);
} else if (b instanceof BindingBuilder) {
throw new InvalidBindingError(b.token);
} else {
throw new InvalidBindingError(b);
}
});
return res;
}

55
modules/angular2/src/di/key.js vendored Normal file
View File

@ -0,0 +1,55 @@
import {KeyMetadataError} from './exceptions';
import {MapWrapper, Map} from 'facade/src/collection';
import {FIELD, int, isPresent} from 'facade/src/lang';
export class Key {
token;
id:int;
metadata:any;
constructor(token, id:int) {
this.token = token;
this.id = id;
this.metadata = null;
}
static setMetadata(key:Key, metadata):Key {
if (isPresent(key.metadata) && key.metadata !== metadata) {
throw new KeyMetadataError();
}
key.metadata = metadata;
return key;
}
static get(token):Key {
return _globalKeyRegistry.get(token);
}
static get numberOfKeys():int {
return _globalKeyRegistry.numberOfKeys;
}
}
export class KeyRegistry {
_allKeys:Map;
constructor() {
this._allKeys = MapWrapper.create();
}
get(token):Key {
if (token instanceof Key) return token;
if (MapWrapper.contains(this._allKeys, token)) {
return MapWrapper.get(this._allKeys, token);
}
var newKey = new Key(token, Key.numberOfKeys);
MapWrapper.set(this._allKeys, token, newKey);
return newKey;
}
get numberOfKeys():int {
return MapWrapper.size(this._allKeys);
}
}
var _globalKeyRegistry = new KeyRegistry();

11
modules/angular2/src/di/opaque_token.js vendored Normal file
View File

@ -0,0 +1,11 @@
export class OpaqueToken {
_desc:string;
constructor(desc:string){
this._desc = `Token(${desc})`;
}
toString() {
return this._desc;
}
}

View File

@ -0,0 +1,29 @@
import {Template} from 'core/src/annotations/annotations';
import {ViewPort} from 'core/src/compiler/viewport';
import {isBlank} from 'facade/src/lang';
@Template({
selector: '[ng-if]',
bind: {
'ng-if': 'condition'
}
})
export class NgIf {
viewPort: ViewPort;
prevCondition: boolean;
constructor(viewPort: ViewPort) {
this.viewPort = viewPort;
this.prevCondition = null;
}
set condition(newCondition) {
if (newCondition && (isBlank(this.prevCondition) || !this.prevCondition)) {
this.prevCondition = true;
this.viewPort.create();
} else if (!newCondition && (isBlank(this.prevCondition) || this.prevCondition)) {
this.prevCondition = false;
this.viewPort.clear();
}
}
}

View File

@ -0,0 +1,8 @@
import {Decorator} from 'core/src/annotations/annotations';
@Decorator({
selector: '[ng-non-bindable]',
compileChildren: false
})
export class NgNonBindable {
}

View File

@ -0,0 +1,93 @@
import {Template} from 'core/src/annotations/annotations';
import {OnChange} from 'core/src/compiler/interfaces';
import {ViewPort} from 'core/src/compiler/viewport';
import {View} from 'core/src/compiler/view';
import {isPresent, isBlank} from 'facade/src/lang';
import {ListWrapper} from 'facade/src/collection';
@Template({
selector: '[ng-repeat]',
bind: {
'in': 'iterable[]'
}
})
export class NgRepeat extends OnChange {
viewPort: ViewPort;
iterable;
constructor(viewPort: ViewPort) {
this.viewPort = viewPort;
}
onChange(changes) {
var iteratorChanges = changes['iterable'];
if (isBlank(iteratorChanges) || isBlank(iteratorChanges.currentValue)) {
this.viewPort.clear();
return;
}
// TODO(rado): check if change detection can produce a change record that is
// easier to consume than current.
var recordViewTuples = [];
iteratorChanges.currentValue.forEachRemovedItem(
(removedRecord) => ListWrapper.push(recordViewTuples, new RecordViewTuple(removedRecord, null))
);
iteratorChanges.currentValue.forEachMovedItem(
(movedRecord) => ListWrapper.push(recordViewTuples, new RecordViewTuple(movedRecord, null))
);
var insertTuples = NgRepeat.bulkRemove(recordViewTuples, this.viewPort);
iteratorChanges.currentValue.forEachAddedItem(
(addedRecord) => ListWrapper.push(insertTuples, new RecordViewTuple(addedRecord, null))
);
NgRepeat.bulkInsert(insertTuples, this.viewPort);
for (var i = 0; i < insertTuples.length; i++) {
this.perViewChange(insertTuples[i].view, insertTuples[i].record);
}
}
perViewChange(view, record) {
view.setLocal('\$implicit', record.item);
view.setLocal('index', record.currentIndex);
}
static bulkRemove(tuples, viewPort) {
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 = viewPort.detach(tuple.record.previousIndex);
ListWrapper.push(movedTuples, tuple);
} else {
viewPort.remove(tuple.record.previousIndex);
}
}
return movedTuples;
}
static bulkInsert(tuples, viewPort) {
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)) {
viewPort.insert(tuple.view, tuple.record.currentIndex);
} else {
tuple.view = viewPort.create(tuple.record.currentIndex);
}
}
return tuples;
}
}
class RecordViewTuple {
view: View;
record: any;
constructor(record, view) {
this.record = record;
this.view = view;
}
}

View File

@ -0,0 +1,190 @@
import {Decorator, Template} from 'core/src/annotations/annotations';
import {ViewPort} from 'core/src/compiler/viewport';
import {NgElement} from 'core/src/dom/element';
import {DOM} from 'facade/src/dom';
import {isPresent, isBlank} from 'facade/src/lang';
import {ListWrapper, List, MapWrapper, Map} from 'facade/src/collection';
import {Parent} from 'core/src/annotations/visibility';
/**
* 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>
* ```
*/
@Decorator({
selector: '[ng-switch]',
bind: {
'ng-switch': 'value'
}
})
export class NgSwitch {
_switchValue: any;
_useDefault: boolean;
_valueViewPorts: Map;
_activeViewPorts: List;
constructor() {
this._valueViewPorts = MapWrapper.create();
this._activeViewPorts = ListWrapper.create();
this._useDefault = false;
}
set value(value) {
// Remove the currently active viewports
this._removeAllActiveViewPorts();
// Add the viewports matching the value (with a fallback to default)
this._useDefault = false;
var viewPorts = MapWrapper.get(this._valueViewPorts, value);
if (isBlank(viewPorts)) {
this._useDefault = true;
viewPorts = MapWrapper.get(this._valueViewPorts, _whenDefault);
}
this._activateViewPorts(viewPorts);
this._switchValue = value;
}
_onWhenValueChanged(oldWhen, newWhen, viewPort: ViewPort) {
this._deregisterViewPort(oldWhen, viewPort);
this._registerViewPort(newWhen, viewPort);
if (oldWhen === this._switchValue) {
viewPort.remove();
ListWrapper.remove(this._activeViewPorts, viewPort);
} else if (newWhen === this._switchValue) {
if (this._useDefault) {
this._useDefault = false;
this._removeAllActiveViewPorts();
}
viewPort.create();
ListWrapper.push(this._activeViewPorts, viewPort);
}
// Switch to default when there is no more active viewports
if (this._activeViewPorts.length === 0 && !this._useDefault) {
this._useDefault = true;
this._activateViewPorts(MapWrapper.get(this._valueViewPorts, _whenDefault));
}
}
_removeAllActiveViewPorts() {
var activeViewPorts = this._activeViewPorts;
for (var i = 0; i < activeViewPorts.length; i++) {
activeViewPorts[i].remove();
}
this._activeViewPorts = ListWrapper.create();
}
_activateViewPorts(viewPorts) {
// TODO(vicb): assert(this._activeViewPorts.length === 0);
if (isPresent(viewPorts)) {
for (var i = 0; i < viewPorts.length; i++) {
viewPorts[i].create();
}
this._activeViewPorts = viewPorts;
}
}
_registerViewPort(value, viewPort: ViewPort) {
var viewPorts = MapWrapper.get(this._valueViewPorts, value);
if (isBlank(viewPorts)) {
viewPorts = ListWrapper.create();
MapWrapper.set(this._valueViewPorts, value, viewPorts);
}
ListWrapper.push(viewPorts, viewPort);
}
_deregisterViewPort(value, viewPort: ViewPort) {
// `_whenDefault` is used a marker for non-registered whens
if (value == _whenDefault) return;
var viewPorts = MapWrapper.get(this._valueViewPorts, value);
if (viewPorts.length == 1) {
MapWrapper.delete(this._valueViewPorts, value);
} else {
ListWrapper.remove(viewPorts, viewPort);
}
}
}
/**
* 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>
* ```
*/
@Template({
selector: '[ng-switch-when]',
bind: {
'ng-switch-when' : 'when'
}
})
export class NgSwitchWhen {
_value: any;
_ngSwitch: NgSwitch;
_viewPort: ViewPort;
constructor(el: NgElement, viewPort: ViewPort, @Parent() ngSwitch: NgSwitch) {
// `_whenDefault` is used as a marker for a not yet initialized value
this._value = _whenDefault;
this._ngSwitch = ngSwitch;
this._viewPort = viewPort;
}
set when(value) {
this._ngSwitch._onWhenValueChanged(this._value, value, this._viewPort);
this._value = value;
}
}
/**
* Defines a default case statement.
*
* Default case statements are displayed when no `NgSwitchWhen` match the `ngSwitch` value.
*
* Example:
*
* ```
* <template [ng-switch-default]>...</template>
* ```
*/
@Template({
selector: '[ng-switch-default]'
})
export class NgSwitchDefault {
constructor(viewPort: ViewPort, @Parent() ngSwitch: NgSwitch) {
ngSwitch._registerViewPort(_whenDefault, viewPort);
}
}
var _whenDefault = new Object();

View File

@ -0,0 +1,39 @@
library angular.core.facade.async;
import 'dart:async';
export 'dart:async' show Future;
class PromiseWrapper {
static Future resolve(obj) => new Future.value(obj);
static Future reject(obj) => new Future.error(obj);
static Future<List> all(List<Future> promises) => Future.wait(promises);
static Future then(Future promise, success(value), Function onError) {
if (success == null) return promise.catchError(onError);
return promise.then(success, onError: onError);
}
static _Completer completer() => new _Completer(new Completer());
static void setTimeout(fn(), int millis) {
new Timer(new Duration(milliseconds: millis), fn);
}
}
class _Completer {
final Completer c;
_Completer(this.c);
Future get promise => c.future;
void complete(v) {
c.complete(v);
}
void reject(v) {
c.completeError(v);
}
}

View File

@ -0,0 +1,42 @@
import {int} from 'facade/src/lang';
import {List} from 'facade/src/collection';
export var Promise = window.Promise;
export class PromiseWrapper {
static resolve(obj):Promise {
return Promise.resolve(obj);
}
static reject(obj):Promise {
return Promise.reject(obj);
}
static all(promises:List):Promise {
if (promises.length == 0) return Promise.resolve([]);
return Promise.all(promises);
}
static then(promise:Promise, success:Function, rejection:Function):Promise {
return promise.then(success, rejection);
}
static completer() {
var resolve;
var reject;
var p = new Promise(function(res, rej) {
resolve = res;
reject = rej;
});
return {
promise: p,
complete: resolve,
reject: reject
};
}
static setTimeout(fn:Function, millis:int) {
window.setTimeout(fn, millis);
}
}

View File

@ -0,0 +1,154 @@
library facade.collection;
import 'dart:collection' show HashMap, IterableBase, Iterator;
export 'dart:core' show Map, List, Set;
import 'dart:math' show max, min;
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 HashMap create() => new HashMap();
static HashMap clone(Map m) => new HashMap.from(m);
static HashMap createFromStringMap(HashMap m) => m;
static HashMap createFromPairs(List pairs) => pairs.fold({}, (m, p) {
m[p[0]] = p[1];
return m;
});
static get(Map m, k) => m[k];
static void set(Map m, k, v) {
m[k] = v;
}
static contains(Map m, k) => m.containsKey(k);
static forEach(Map m, fn(v, k)) {
m.forEach((k, v) => fn(v, k));
}
static int size(Map m) => m.length;
static void delete(Map m, k) {
m.remove(k);
}
static void clear(Map m) {
m.clear();
}
static Iterable iterable(Map m) => new IterableMap(m);
static Iterable keys(Map m) => m.keys;
static Iterable values(Map m) => m.values;
}
// TODO: how to export StringMap=Map as a type?
class StringMapWrapper {
static HashMap create() => new HashMap();
static get(Map map, key) => map[key];
static void set(Map map, key, value) {
map[key] = value;
}
static void forEach(Map m, fn(v, k)) {
m.forEach((k, v) => fn(v, k));
}
static bool isEmpty(Map m) => m.isEmpty;
}
class ListWrapper {
static List clone(List l) => new List.from(l);
static List create() => new List();
static List createFixedSize(int size) => new List(size);
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 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 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 void push(List l, e) {
l.add(e);
}
static List concat(List a, List b) {
a.addAll(b);
return a;
}
static bool isList(l) => l is List;
static void insert(List l, int index, value) {
l.insert(index, value);
}
static void 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 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]) {
// JS semantics
// see https://github.com/google/traceur-compiler/blob/81880cd3f17bac7de90a4cd0339e9f1a9f61d24c/src/runtime/polyfills/Array.js#L94
int len = l.length;
start = start < 0 ? max(len + start, 0) : min(start, len);
if (end == null) {
end = len;
} else {
end = end < 0 ? max(len + end, 0) : min(end, len);
}
l.fillRange(start, 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;
}
}
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);
}

View File

@ -0,0 +1,197 @@
import {int, isJsObject} from 'facade/src/lang';
export var List = window.Array;
export var Map = window.Map;
export var Set = window.Set;
export class MapWrapper {
static create():Map { return new Map(); }
static clone(m:Map):Map { return new Map(m); }
static createFromStringMap(stringMap):Map {
var result = MapWrapper.create();
for (var prop in stringMap) {
MapWrapper.set(result, prop, stringMap[prop]);
}
return result;
}
static createFromPairs(pairs:List):Map {return new Map(pairs);}
static get(m, k) { return m.get(k); }
static set(m, k, v) { m.set(k,v); }
static contains(m, k) { return m.has(k); }
static forEach(m, fn) {
m.forEach(fn);
}
static size(m) {return m.size;}
static delete(m, k) { m.delete(k); }
static clear(m) { m.clear(); }
static iterable(m) { return m; }
static keys(m) { return m.keys(); }
static values(m) { return m.values(); }
}
// TODO: cannot export StringMap as a type as Dart does not support renaming types...
/**
* Wraps Javascript Objects
*/
export class StringMapWrapper {
static create():Object {
// Note: We are not using Object.create(null) here due to
// performance!
// http://jsperf.com/ng2-object-create-null
return { };
}
static get(map, key) {
return map.hasOwnProperty(key) ? map[key] : undefined;
}
static set(map, key, value) {
map[key] = value;
}
static isEmpty(map) {
for (var prop in map) {
return false;
}
return true;
}
static forEach(map, callback) {
for (var prop in map) {
if (map.hasOwnProperty(prop)) {
callback(map[prop], prop);
}
}
}
static merge(m1, m2) {
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;
}
}
export class ListWrapper {
static create():List { return new List(); }
static createFixedSize(size):List { return new List(size); }
static get(m, k) { return m[k]; }
static set(m, k, v) { m[k] = v; }
static clone(array:List) {
return array.slice(0);
}
static map(array, fn) {
return array.map(fn);
}
static forEach(array, fn) {
for(var p of array) {
fn(p);
}
}
static push(array, el) {
array.push(el);
}
static first(array) {
if (!array) return null;
return array[0];
}
static last(array) {
if (!array || array.length == 0) return null;
return array[array.length - 1];
}
static find(list:List, pred:Function) {
for (var i = 0 ; i < list.length; ++i) {
if (pred(list[i])) return list[i];
}
return null;
}
static reduce(list:List, fn:Function, init) {
return list.reduce(fn, init);
}
static filter(array, pred:Function) {
return array.filter(pred);
}
static any(list:List, pred:Function) {
for (var i = 0 ; i < list.length; ++i) {
if (pred(list[i])) return true;
}
return false;
}
static contains(list:List, el) {
return list.indexOf(el) !== -1;
}
static reversed(array) {
var a = ListWrapper.clone(array);
return a.reverse();
}
static concat(a, b) {return a.concat(b);}
static isList(list) {
return Array.isArray(list);
}
static insert(list, index:int, value) {
list.splice(index, 0, value);
}
static removeAt(list, index:int) {
var res = list[index];
list.splice(index, 1);
return res;
}
static removeAll(list, items) {
for (var i = 0; i < items.length; ++i) {
var index = list.indexOf(items[i]);
list.splice(index, 1);
}
}
static remove(list, el): boolean {
var index = list.indexOf(el);
if (index > -1) {
list.splice(index, 1);
return true;
}
return false;
}
static clear(list) {
list.splice(0, list.length);
}
static join(list, s) {
return list.join(s);
}
static isEmpty(list) {
return list.length == 0;
}
static fill(list:List, value, start:int = 0, end:int = undefined) {
list.fill(value, start, end);
}
static equals(a:List, b:List):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;
}
}
export function isListLikeIterable(obj):boolean {
if (!isJsObject(obj)) return false;
return ListWrapper.isList(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, fn:Function) {
for (var item of obj) {
fn(item);
}
}
export class SetWrapper {
static createFromList(lst:List) { return new Set(lst); }
static has(s:Set, key):boolean { return s.has(key); }
}

View File

@ -0,0 +1,128 @@
library angular.core.facade.dom;
import 'dart:html';
import 'dart:js' show JsObject, context;
export 'dart:html' show DocumentFragment, Node, Element, TemplateElement, Text, document, location, window;
// TODO(tbosch): Is there a builtin one? Why is Dart
// removing unknown elements by default?
class IdentitySanitizer implements NodeTreeSanitizer {
void sanitizeTree(Node node) {}
}
final _window = context['window'];
final _gc = context['gc'];
void gc() {
if (_gc != null) {
_gc.apply(const []);
}
}
final identitySanitizer = new IdentitySanitizer();
class DOM {
static query(String selector) => document.querySelector(selector);
static Element querySelector(el, String selector) =>
el.querySelector(selector);
static ElementList querySelectorAll(el, String selector) =>
el.querySelectorAll(selector);
static 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);
}
static void dispatchEvent(EventTarget el, Event evt) {
el.dispatchEvent(evt);
}
static MouseEvent createMouseEvent(String eventType) =>
new MouseEvent(eventType, canBubble: true);
static createEvent(eventType) =>
new Event(eventType, canBubble: true);
static String getInnerHTML(Element el) => el.innerHtml;
static String getOuterHTML(Element el) => el.outerHtml;
static void setInnerHTML(Element el, String value) {
el.innerHtml = value;
}
static Node firstChild(el) => el.firstChild;
static Node nextSibling(Node el) => el.nextNode;
static Element parentElement(Node el) => el.parent;
static List<Node> childNodes(Node el) => el.childNodes;
static List childNodesAsList(Node el) => childNodes(el).toList();
static void clearNodes(Node el) {
el.nodes = const [];
}
static void appendChild(Node el, Node node) {
el.append(node);
}
static void removeChild(Element el, Node node) {
node.remove();
}
static void insertBefore(Node el, Node node) {
el.parentNode.insertBefore(node, el);
}
static void insertAllBefore(Node el, Iterable<Node> nodes) {
el.parentNode.insertAllBefore(nodes, el);
}
static void insertAfter(Node el, Node node) {
el.parentNode.insertBefore(node, el.nextNode);
}
static String getText(Node el) => el.text;
static void setText(Text text, String value) {
text.text = value;
}
static TemplateElement createTemplate(String html) {
var t = new TemplateElement();
t.setInnerHtml(html, treeSanitizer: identitySanitizer);
return t;
}
static Element createElement(String tagName, [HtmlDocument doc = null]) {
if (doc == null) doc = document;
return doc.createElement(tagName);
}
static createScriptTag(String attrName, String attrValue,
[HtmlDocument doc = null]) {
if (doc == null) doc = document;
var el = doc.createElement("SCRIPT");
el.setAttribute(attrName, attrValue);
return el;
}
static clone(Node node) => node.clone(true);
static bool hasProperty(Element element, String name) =>
new JsObject.fromBrowserObject(element).hasProperty(name);
static List<Node> getElementsByClassName(Element element, String name) =>
element.getElementsByClassName(name);
static List<Node> getElementsByTagName(Element element, String name) =>
element.querySelectorAll(name);
static List<String> classList(Element element) => element.classes.toList();
static void addClass(Element element, String classname) {
element.classes.add(classname);
}
static void removeClass(Element element, String classname) {
element.classes.remove(classname);
}
static bool hasClass(Element element, String classname) =>
element.classes.contains(classname);
static String tagName(Element element) => element.tagName;
static Map<String, String> attributeMap(Element element) =>
element.attributes;
static String getAttribute(Element element, String attribute) =>
element.getAttribute(attribute);
static Node templateAwareRoot(Element el) =>
el is TemplateElement ? el.content : el;
static HtmlDocument createHtmlDocument() =>
document.implementation.createHtmlDocument('fakeTitle');
static HtmlDocument defaultDoc() => document;
static bool elementMatches(n, String selector) =>
n is Element && n.matches(selector);
}

View File

@ -0,0 +1,157 @@
export var window = frames.window;
export var DocumentFragment = window.DocumentFragment;
export var Node = window.Node;
export var NodeList = window.NodeList;
export var Text = window.Text;
export var Element = window.HTMLElement;
export var TemplateElement = window.HTMLTemplateElement;
export var document = window.document;
export var location = window.location;
export var gc = window.gc ? () => window.gc() : () => null;
import {List, MapWrapper, ListWrapper} from 'facade/src/collection';
export class DOM {
static query(selector) {
return document.querySelector(selector);
}
static querySelector(el, selector:string):Node {
return el.querySelector(selector);
}
static querySelectorAll(el, selector:string):NodeList {
return el.querySelectorAll(selector);
}
static on(el, evt, listener) {
el.addEventListener(evt, listener, false);
}
static dispatchEvent(el, evt) {
el.dispatchEvent(evt);
}
static createMouseEvent(eventType) {
var evt = new MouseEvent(eventType);
evt.initEvent(eventType, true, true);
return evt;
}
static createEvent(eventType) {
return new Event(eventType, true);
}
static getInnerHTML(el) {
return el.innerHTML;
}
static getOuterHTML(el) {
return el.outerHTML;
}
static firstChild(el):Node {
return el.firstChild;
}
static nextSibling(el):Node {
return el.nextSibling;
}
static parentElement(el) {
return el.parentElement;
}
static childNodes(el):NodeList {
return el.childNodes;
}
static childNodesAsList(el):List {
var childNodes = el.childNodes;
var res = ListWrapper.createFixedSize(childNodes.length);
for (var i=0; i<childNodes.length; i++) {
res[i] = childNodes[i];
}
return res;
}
static clearNodes(el) {
el.innerHTML = "";
}
static appendChild(el, node) {
el.appendChild(node);
}
static removeChild(el, node) {
el.removeChild(node);
}
static insertBefore(el, node) {
el.parentNode.insertBefore(node, el);
}
static insertAllBefore(el, nodes) {
ListWrapper.forEach(nodes, (n) => {
el.parentNode.insertBefore(n, el);
});
}
static insertAfter(el, node) {
el.parentNode.insertBefore(node, el.nextSibling);
}
static setInnerHTML(el, value) {
el.innerHTML = value;
}
static getText(el: Element) {
return el.textContent;
}
static setText(text:Text, value:string) {
text.nodeValue = value;
}
static createTemplate(html) {
var t = document.createElement('template');
t.innerHTML = html;
return t;
}
static createElement(tagName, doc=document) {
return doc.createElement(tagName);
}
static createScriptTag(attrName:string, attrValue:string, doc=document) {
var el = doc.createElement("SCRIPT");
el.setAttribute(attrName, attrValue);
return el;
}
static clone(node:Node) {
return node.cloneNode(true);
}
static hasProperty(element:Element, name:string) {
return name in element;
}
static getElementsByClassName(element:Element, name:string) {
return element.getElementsByClassName(name);
}
static getElementsByTagName(element:Element, name:string) {
return element.getElementsByTagName(name);
}
static classList(element:Element):List {
return Array.prototype.slice.call(element.classList, 0);
}
static addClass(element:Element, classname:string) {
element.classList.add(classname);
}
static removeClass(element:Element, classname:string) {
element.classList.remove(classname);
}
static hasClass(element:Element, classname:string) {
return element.classList.contains(classname);
}
static tagName(element:Element):string {
return element.tagName;
}
static attributeMap(element:Element) {
var res = MapWrapper.create();
var elAttrs = element.attributes;
for (var i = 0; i < elAttrs.length; i++) {
var attrib = elAttrs[i];
MapWrapper.set(res, attrib.name, attrib.value);
}
return res;
}
static getAttribute(element:Element, attribute:string) {
return element.getAttribute(attribute);
}
static templateAwareRoot(el:Element):Node {
return el instanceof TemplateElement ? el.content : el;
}
static createHtmlDocument() {
return document.implementation.createHTMLDocument();
}
static defaultDoc() {
return document;
}
static elementMatches(n, selector:string):boolean {
return n instanceof Element && n.matches(selector);
}
}

View File

@ -0,0 +1,168 @@
library angular.core.facade.lang;
export 'dart:core' show Type, RegExp;
import 'dart:math' as math;
class Math {
static final _random = new math.Random();
static int floor(num n) => n.floor();
static double random() => _random.nextDouble();
}
class FIELD {
final String definition;
const FIELD(this.definition);
}
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;
String stringify(obj) => obj.toString();
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 replaceAll(String s, RegExp from, String replace) {
return s.replaceAll(from, replace);
}
static startsWith(String s, String start) {
return s.startsWith(start);
}
static String substring(String s, int start, [int end]) {
return s.substring(start, end);
}
}
class StringJoiner {
final List<String> _parts = <String>[];
void add(String part) {
_parts.add(part);
}
String toString() => _parts.join("");
}
class NumberWrapper {
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(String regExpStr) {
return new RegExp(regExpStr);
}
static Match firstMatch(RegExp regExp, String input) {
return regExp.firstMatch(input);
}
static Iterator<Match> matcher(RegExp regExp, String input) {
return regExp.allMatches(input).iterator;
}
}
class RegExpMatcherWrapper {
static Match next(Iterator<Match> matcher) {
if (matcher.moveNext()) {
return matcher.current;
}
return null;
}
}
class FunctionWrapper {
static apply(Function fn, posArgs) {
return Function.apply(fn, posArgs);
}
}
class BaseException extends Error {
final String message;
BaseException(this.message);
String toString() {
return this.message;
}
}
const _NAN_KEY = const Object();
// Dart can have identical(str1, str2) == false while str1 == str2
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;
}
dynamic normalizeBlank(obj) {
return isBlank(obj) ? null : obj;
}
bool isJsObject(o) {
return false;
}
bool assertionsEnabled() {
try {
assert(false);
return false;
} catch (e) {
return true;
}
}

View File

@ -0,0 +1,226 @@
import {assert} from 'rtts_assert/rtts_assert';
export {proxy} from 'rtts_assert/rtts_assert';
export var Type = Function;
export var Math = window.Math;
// global assert support, as Dart has it...
// TODO: `assert` calls need to be removed in production code!
window.assert = assert;
export class FIELD {
constructor(definition) {
this.definition = definition;
}
}
export class CONST {}
export class ABSTRACT {}
export class IMPLEMENTS {}
export function isPresent(obj):boolean {
return obj !== undefined && obj !== null;
}
export function isBlank(obj):boolean {
return obj === undefined || obj === null;
}
export function isString(obj):boolean {
return typeof obj === "string";
}
export function stringify(token):string {
if (typeof token === 'string') {
return token;
}
if (token === undefined || token === null) {
return '' + token;
}
if (token.name) {
return token.name;
}
return token.toString();
}
export class StringWrapper {
static fromCharCode(code:int):string {
return String.fromCharCode(code);
}
static charCodeAt(s:string, index:int) {
return s.charCodeAt(index);
}
static split(s:string, regExp:RegExp) {
return s.split(regExp.multiple);
}
static equals(s:string, s2:string):boolean {
return s === s2;
}
static replaceAll(s:string, from:RegExp, replace:string):string {
return s.replace(from.multiple, replace);
}
static startsWith(s:string, start:string) {
return s.startsWith(start);
}
static substring(s:string, start:int, end:int = undefined) {
return s.substring(start, end);
}
}
export class StringJoiner {
constructor() {
this.parts = [];
}
add(part:string) {
this.parts.push(part);
}
toString():string {
return this.parts.join("");
}
}
export class NumberParseError extends Error {
constructor(message) {
this.message = message;
}
toString() {
return this.message;
}
}
export class NumberWrapper {
static parseIntAutoRadix(text:string):int {
var result:int = parseInt(text);
if (isNaN(result)) {
throw new NumberParseError("Invalid integer literal when parsing " + text);
}
return result;
}
static parseInt(text:string, radix:int):int {
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:int = 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):boolean {
return isNaN(value);
}
static isInteger(value):boolean {
return Number.isInteger(value);
}
}
export function int() {};
int.assert = function(value) {
return value == null || typeof value == 'number' && value === Math.floor(value);
}
export var RegExp = assert.define('RegExp', function(obj) {
assert(obj).is(assert.structure({
single: window.RegExp,
multiple: window.RegExp
}));
});
export class RegExpWrapper {
static create(regExpStr):RegExp {
return {
multiple: new window.RegExp(regExpStr, 'g'),
single: new window.RegExp(regExpStr)
};
}
static firstMatch(regExp, input) {
return input.match(regExp.single);
}
static matcher(regExp, input) {
return {
re: regExp.multiple,
input: input
};
}
}
export class RegExpMatcherWrapper {
static next(matcher) {
return matcher.re.exec(matcher.input);
}
}
export class FunctionWrapper {
static apply(fn:Function, posArgs) {
return fn.apply(null, posArgs);
}
}
// No subclass so that we preserve error stack.
export var BaseException = Error;
// 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(value) {
return value;
}
export function normalizeBlank(obj) {
return isBlank(obj) ? null : obj;
}
export function isJsObject(o):boolean {
return o !== null && (typeof o === "function" || typeof o === "object");
}
export function assertionsEnabled():boolean {
try {
var x:int = "string";
return false;
} catch (e) {
return true;
}
}
export function print(obj) {
console.log(obj);
}

View File

@ -0,0 +1,9 @@
library angular.core.facade.math;
import 'dart:math' as math;
class Math {
static num pow(num x, num exponent) {
return math.pow(x, exponent);
}
}

View File

@ -0,0 +1 @@
export var Math = window.Math;

View File

@ -0,0 +1,34 @@
library reflection.reflection;
import 'reflector.dart';
import 'types.dart';
export 'reflector.dart';
import 'package:facade/src/lang.dart';
class NoReflectionCapabilities {
Function factory(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 'facade/src/lang';
import {List, ListWrapper} from 'facade/src/collection';
import {Reflector} from './reflector';
export {Reflector} from './reflector';
import {ReflectionCapabilities} from './reflection_capabilities';
export var reflector = new Reflector(new ReflectionCapabilities());

View File

@ -0,0 +1,89 @@
library reflection.reflection_capabilities;
import 'reflection.dart';
import 'types.dart';
import 'dart:mirrors';
class ReflectionCapabilities {
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;
};
throw "Factory cannot take more than 10 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.reflectedType;
var res = t == dynamic ? [] : [t];
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();
}
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,59 @@
import {Type, isPresent} from 'facade/src/lang';
import {List, ListWrapper} from 'facade/src/collection';
import {GetterFn, SetterFn, MethodFn} from './types';
export class ReflectionCapabilities {
factory(type:Type):Function {
switch (type.length) {
case 0:
return function(){return new type();};
case 1:
return function(a1){return new type(a1);};
case 2:
return function(a1, a2){return new type(a1, a2);};
case 3:
return function(a1, a2, a3){return new type(a1, a2, a3);};
case 4:
return function(a1, a2, a3, a4){return new type(a1, a2, a3, a4);};
case 5:
return function(a1, a2, a3, a4, a5){return new type(a1, a2, a3, a4, a5);};
case 6:
return function(a1, a2, a3, a4, a5, a6){return new type(a1, a2, a3, a4, a5, a6);};
case 7:
return function(a1, a2, a3, a4, a5, a6, a7){return new type(a1, a2, a3, a4, a5, a6, a7);};
case 8:
return function(a1, a2, a3, a4, a5, a6, a7, a8){return new type(a1, a2, a3, a4, a5, a6, a7, a8);};
case 9:
return function(a1, a2, a3, a4, a5, a6, a7, a8, a9){return new type(a1, a2, a3, a4, a5, a6, a7, a8, a9);};
case 10:
return function(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10){return new type(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);};
};
throw new Error("Factory cannot take more than 10 arguments");
}
parameters(typeOfFunc):List<List> {
return isPresent(typeOfFunc.parameters) ?
typeOfFunc.parameters :
ListWrapper.createFixedSize(typeOfFunc.length);
}
annotations(typeOfFunc):List {
return isPresent(typeOfFunc.annotations) ? typeOfFunc.annotations : [];
}
getter(name:string):GetterFn {
return new Function('o', 'return o.' + name + ';');
}
setter(name:string):SetterFn {
return new Function('o', 'v', 'return o.' + name + ' = v;');
}
method(name:string):MethodFn {
var method = `o.${name}`;
return new Function('o', 'args',
`if (!${method}) throw new Error('"${name}" is undefined');` +
`return ${method}.apply(o, args);`);
}
}

View File

@ -0,0 +1,88 @@
import {Type, isPresent, stringify, BaseException} from 'facade/src/lang';
import {List, ListWrapper, Map, MapWrapper, StringMapWrapper} from 'facade/src/collection';
import {SetterFn, GetterFn, MethodFn} from './types';
export {SetterFn, GetterFn, MethodFn} from './types';
export class Reflector {
_typeInfo:Map;
_getters:Map;
_setters:Map;
_methods:Map;
reflectionCapabilities:any;
constructor(reflectionCapabilities) {
this._typeInfo = MapWrapper.create();
this._getters = MapWrapper.create();
this._setters = MapWrapper.create();
this._methods = MapWrapper.create();
this.reflectionCapabilities = reflectionCapabilities;
}
registerType(type, typeInfo){
MapWrapper.set(this._typeInfo, type, typeInfo);
}
registerGetters(getters){
_mergeMaps(this._getters, getters);
}
registerSetters(setters){
_mergeMaps(this._setters, setters);
}
registerMethods(methods){
_mergeMaps(this._methods, methods);
}
factory(type:Type):Function {
if(MapWrapper.contains(this._typeInfo, type)) {
return MapWrapper.get(this._typeInfo, type)["factory"];
} else {
return this.reflectionCapabilities.factory(type);
}
}
parameters(typeOfFunc):List {
if(MapWrapper.contains(this._typeInfo, typeOfFunc)) {
return MapWrapper.get(this._typeInfo, typeOfFunc)["parameters"];
} else {
return this.reflectionCapabilities.parameters(typeOfFunc);
}
}
annotations(typeOfFunc):List {
if(MapWrapper.contains(this._typeInfo, typeOfFunc)) {
return MapWrapper.get(this._typeInfo, typeOfFunc)["annotations"];
} else {
return this.reflectionCapabilities.annotations(typeOfFunc);
}
}
getter(name:string):GetterFn {
if(MapWrapper.contains(this._getters, name)) {
return MapWrapper.get(this._getters, name);
} else {
return this.reflectionCapabilities.getter(name);
}
}
setter(name:string):SetterFn {
if(MapWrapper.contains(this._setters, name)) {
return MapWrapper.get(this._setters, name);
} else {
return this.reflectionCapabilities.setter(name);
}
}
method(name:string):MethodFn {
if(MapWrapper.contains(this._methods, name)) {
return MapWrapper.get(this._methods, name);
} else {
return this.reflectionCapabilities.method(name);
}
}
}
function _mergeMaps(target:Map, config) {
StringMapWrapper.forEach(config, (v, k) => MapWrapper.set(target, k, v));
}

View File

@ -0,0 +1,5 @@
library reflection.types;
typedef SetterFn(Object obj, value);
typedef GetterFn(Object obj);
typedef MethodFn(Object obj, List args);

View File

@ -0,0 +1,3 @@
export var SetterFn = Function;
export var GetterFn = Function;
export var MethodFn = Function;