repackaging: all the file moves

This commit is contained in:
Igor Minar
2016-04-28 08:02:15 -07:00
committed by Misko Hevery
parent 4fe0f1fa65
commit 505da6c0a8
739 changed files with 0 additions and 52 deletions

View File

@ -0,0 +1,58 @@
import {IterableDiffers, IterableDifferFactory} from './differs/iterable_differs';
import {DefaultIterableDifferFactory} from './differs/default_iterable_differ';
import {KeyValueDiffers, KeyValueDifferFactory} from './differs/keyvalue_differs';
import {
DefaultKeyValueDifferFactory,
KeyValueChangeRecord
} from './differs/default_keyvalue_differ';
export {
DefaultKeyValueDifferFactory,
KeyValueChangeRecord
} from './differs/default_keyvalue_differ';
export {
DefaultIterableDifferFactory,
CollectionChangeRecord
} from './differs/default_iterable_differ';
export {
ChangeDetectionStrategy,
CHANGE_DETECTION_STRATEGY_VALUES,
ChangeDetectorState,
CHANGE_DETECTOR_STATE_VALUES,
isDefaultChangeDetectionStrategy
} from './constants';
export {ChangeDetectorRef} from './change_detector_ref';
export {
IterableDiffers,
IterableDiffer,
IterableDifferFactory,
TrackByFn
} from './differs/iterable_differs';
export {KeyValueDiffers, KeyValueDiffer, KeyValueDifferFactory} from './differs/keyvalue_differs';
export {PipeTransform} from './pipe_transform';
export {
WrappedValue,
ValueUnwrapper,
SimpleChange,
devModeEqual,
looseIdentical,
uninitialized
} from './change_detection_util';
/**
* Structural diffing for `Object`s and `Map`s.
*/
export const keyValDiff: KeyValueDifferFactory[] =
/*@ts2dart_const*/[new DefaultKeyValueDifferFactory()];
/**
* Structural diffing for `Iterable` types such as `Array`s.
*/
export const iterableDiff: IterableDifferFactory[] =
/*@ts2dart_const*/[new DefaultIterableDifferFactory()];
export const defaultIterableDiffers = /*@ts2dart_const*/ new IterableDiffers(iterableDiff);
export const defaultKeyValueDiffers = /*@ts2dart_const*/ new KeyValueDiffers(keyValDiff);

View File

@ -0,0 +1,75 @@
import {isBlank, looseIdentical, isPrimitive} from 'angular2/src/facade/lang';
import {
StringMapWrapper,
isListLikeIterable,
areIterablesEqual
} from 'angular2/src/facade/collection';
export {looseIdentical} from 'angular2/src/facade/lang';
export var uninitialized: Object = /*@ts2dart_const*/ new Object();
export function devModeEqual(a: any, b: any): boolean {
if (isListLikeIterable(a) && isListLikeIterable(b)) {
return areIterablesEqual(a, b, devModeEqual);
} else if (!isListLikeIterable(a) && !isPrimitive(a) && !isListLikeIterable(b) &&
!isPrimitive(b)) {
return true;
} else {
return looseIdentical(a, b);
}
}
/**
* Indicates that the result of a {@link PipeMetadata} transformation has changed even though the
* reference
* has not changed.
*
* The wrapped value will be unwrapped by change detection, and the unwrapped value will be stored.
*
* Example:
*
* ```
* if (this._latestValue === this._latestReturnedValue) {
* return this._latestReturnedValue;
* } else {
* this._latestReturnedValue = this._latestValue;
* return WrappedValue.wrap(this._latestValue); // this will force update
* }
* ```
*/
export class WrappedValue {
constructor(public wrapped: any) {}
static wrap(value: any): WrappedValue { return new WrappedValue(value); }
}
/**
* Helper class for unwrapping WrappedValue s
*/
export class ValueUnwrapper {
public hasWrappedValue = false;
unwrap(value: any): any {
if (value instanceof WrappedValue) {
this.hasWrappedValue = true;
return value.wrapped;
}
return value;
}
reset() { this.hasWrappedValue = false; }
}
/**
* Represents a basic change from a previous to a new value.
*/
export class SimpleChange {
constructor(public previousValue: any, public currentValue: any) {}
/**
* Check whether the new value is the first value assigned.
*/
isFirstChange(): boolean { return this.previousValue === uninitialized; }
}

View File

@ -0,0 +1,192 @@
export abstract class ChangeDetectorRef {
/**
* Marks all {@link ChangeDetectionStrategy#OnPush} ancestors as to be checked.
*
* <!-- TODO: Add a link to a chapter on OnPush components -->
*
* ### Example ([live demo](http://plnkr.co/edit/GC512b?p=preview))
*
* ```typescript
* @Component({
* selector: 'cmp',
* changeDetection: ChangeDetectionStrategy.OnPush,
* template: `Number of ticks: {{numberOfTicks}}`
* })
* class Cmp {
* numberOfTicks = 0;
*
* constructor(ref: ChangeDetectorRef) {
* setInterval(() => {
* this.numberOfTicks ++
* // the following is required, otherwise the view will not be updated
* this.ref.markForCheck();
* }, 1000);
* }
* }
*
* @Component({
* selector: 'app',
* changeDetection: ChangeDetectionStrategy.OnPush,
* template: `
* <cmp><cmp>
* `,
* directives: [Cmp]
* })
* class App {
* }
*
* bootstrap(App);
* ```
*/
abstract markForCheck(): void;
/**
* Detaches the change detector from the change detector tree.
*
* The detached change detector will not be checked until it is reattached.
*
* This can also be used in combination with {@link ChangeDetectorRef#detectChanges} to implement
* local change
* detection checks.
*
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
* <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->
*
* ### Example
*
* The following example defines a component with a large list of readonly data.
* Imagine the data changes constantly, many times per second. For performance reasons,
* we want to check and update the list every five seconds. We can do that by detaching
* the component's change detector and doing a local check every five seconds.
*
* ```typescript
* class DataProvider {
* // in a real application the returned data will be different every time
* get data() {
* return [1,2,3,4,5];
* }
* }
*
* @Component({
* selector: 'giant-list',
* template: `
* <li *ngFor="let d of dataProvider.data">Data {{d}}</lig>
* `,
* directives: [NgFor]
* })
* class GiantList {
* constructor(private ref: ChangeDetectorRef, private dataProvider:DataProvider) {
* ref.detach();
* setInterval(() => {
* this.ref.detectChanges();
* }, 5000);
* }
* }
*
* @Component({
* selector: 'app',
* providers: [DataProvider],
* template: `
* <giant-list><giant-list>
* `,
* directives: [GiantList]
* })
* class App {
* }
*
* bootstrap(App);
* ```
*/
abstract detach(): void;
/**
* Checks the change detector and its children.
*
* This can also be used in combination with {@link ChangeDetectorRef#detach} to implement local
* change detection
* checks.
*
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
* <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->
*
* ### Example
*
* The following example defines a component with a large list of readonly data.
* Imagine, the data changes constantly, many times per second. For performance reasons,
* we want to check and update the list every five seconds.
*
* We can do that by detaching the component's change detector and doing a local change detection
* check
* every five seconds.
*
* See {@link ChangeDetectorRef#detach} for more information.
*/
abstract detectChanges(): void;
/**
* Checks the change detector and its children, and throws if any changes are detected.
*
* This is used in development mode to verify that running change detection doesn't introduce
* other changes.
*/
abstract checkNoChanges(): void;
/**
* Reattach the change detector to the change detector tree.
*
* This also marks OnPush ancestors as to be checked. This reattached change detector will be
* checked during the next change detection run.
*
* <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
*
* ### Example ([live demo](http://plnkr.co/edit/aUhZha?p=preview))
*
* The following example creates a component displaying `live` data. The component will detach
* its change detector from the main change detector tree when the component's live property
* is set to false.
*
* ```typescript
* class DataProvider {
* data = 1;
*
* constructor() {
* setInterval(() => {
* this.data = this.data * 2;
* }, 500);
* }
* }
*
* @Component({
* selector: 'live-data',
* inputs: ['live'],
* template: `Data: {{dataProvider.data}}`
* })
* class LiveData {
* constructor(private ref: ChangeDetectorRef, private dataProvider:DataProvider) {}
*
* set live(value) {
* if (value)
* this.ref.reattach();
* else
* this.ref.detach();
* }
* }
*
* @Component({
* selector: 'app',
* providers: [DataProvider],
* template: `
* Live Update: <input type="checkbox" [(ngModel)]="live">
* <live-data [live]="live"><live-data>
* `,
* directives: [LiveData, FORM_DIRECTIVES]
* })
* class App {
* live = true;
* }
*
* bootstrap(App);
* ```
*/
abstract reattach(): void;
}

View File

@ -0,0 +1,93 @@
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/facade/lang';
/**
* Describes the current state of the change detector.
*/
export enum ChangeDetectorState {
/**
* `NeverChecked` means that the change detector has not been checked yet, and
* initialization methods should be called during detection.
*/
NeverChecked,
/**
* `CheckedBefore` means that the change detector has successfully completed at least
* one detection previously.
*/
CheckedBefore,
/**
* `Errored` means that the change detector encountered an error checking a binding
* or calling a directive lifecycle method and is now in an inconsistent state. Change
* detectors in this state will no longer detect changes.
*/
Errored
}
/**
* Describes within the change detector which strategy will be used the next time change
* detection is triggered.
*/
export enum ChangeDetectionStrategy {
/**
* `CheckedOnce` means that after calling detectChanges the mode of the change detector
* will become `Checked`.
*/
CheckOnce,
/**
* `Checked` means that the change detector should be skipped until its mode changes to
* `CheckOnce`.
*/
Checked,
/**
* `CheckAlways` means that after calling detectChanges the mode of the change detector
* will remain `CheckAlways`.
*/
CheckAlways,
/**
* `Detached` means that the change detector sub tree is not a part of the main tree and
* should be skipped.
*/
Detached,
/**
* `OnPush` means that the change detector's mode will be set to `CheckOnce` during hydration.
*/
OnPush,
/**
* `Default` means that the change detector's mode will be set to `CheckAlways` during hydration.
*/
Default,
}
/**
* List of possible {@link ChangeDetectionStrategy} values.
*/
export var CHANGE_DETECTION_STRATEGY_VALUES = [
ChangeDetectionStrategy.CheckOnce,
ChangeDetectionStrategy.Checked,
ChangeDetectionStrategy.CheckAlways,
ChangeDetectionStrategy.Detached,
ChangeDetectionStrategy.OnPush,
ChangeDetectionStrategy.Default
];
/**
* List of possible {@link ChangeDetectorState} values.
*/
export var CHANGE_DETECTOR_STATE_VALUES = [
ChangeDetectorState.NeverChecked,
ChangeDetectorState.CheckedBefore,
ChangeDetectorState.Errored
];
export function isDefaultChangeDetectionStrategy(
changeDetectionStrategy: ChangeDetectionStrategy): boolean {
return isBlank(changeDetectionStrategy) ||
changeDetectionStrategy === ChangeDetectionStrategy.Default;
}

View File

@ -0,0 +1,694 @@
import {BaseException} from 'angular2/src/facade/exceptions';
import {isListLikeIterable, iterateListLike, ListWrapper} from 'angular2/src/facade/collection';
import {
isBlank,
isPresent,
stringify,
getMapKey,
looseIdentical,
isArray
} from 'angular2/src/facade/lang';
import {ChangeDetectorRef} from '../change_detector_ref';
import {IterableDiffer, IterableDifferFactory, TrackByFn} from '../differs/iterable_differs';
/* @ts2dart_const */
export class DefaultIterableDifferFactory implements IterableDifferFactory {
constructor() {}
supports(obj: Object): boolean { return isListLikeIterable(obj); }
create(cdRef: ChangeDetectorRef, trackByFn?: TrackByFn): DefaultIterableDiffer {
return new DefaultIterableDiffer(trackByFn);
}
}
var trackByIdentity = (index: number, item: any) => item;
export class DefaultIterableDiffer implements IterableDiffer {
private _length: number = null;
private _collection = null;
// Keeps track of the used records at any point in time (during & across `_check()` calls)
private _linkedRecords: _DuplicateMap = null;
// Keeps track of the removed records at any point in time during `_check()` calls.
private _unlinkedRecords: _DuplicateMap = null;
private _previousItHead: CollectionChangeRecord = null;
private _itHead: CollectionChangeRecord = null;
private _itTail: CollectionChangeRecord = null;
private _additionsHead: CollectionChangeRecord = null;
private _additionsTail: CollectionChangeRecord = null;
private _movesHead: CollectionChangeRecord = null;
private _movesTail: CollectionChangeRecord = null;
private _removalsHead: CollectionChangeRecord = null;
private _removalsTail: CollectionChangeRecord = null;
// Keeps track of records where custom track by is the same, but item identity has changed
private _identityChangesHead: CollectionChangeRecord = null;
private _identityChangesTail: CollectionChangeRecord = null;
constructor(private _trackByFn?: TrackByFn) {
this._trackByFn = isPresent(this._trackByFn) ? this._trackByFn : trackByIdentity;
}
get collection() { return this._collection; }
get length(): number { return this._length; }
forEachItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._itHead; record !== null; record = record._next) {
fn(record);
}
}
forEachPreviousItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._previousItHead; record !== null; record = record._nextPrevious) {
fn(record);
}
}
forEachAddedItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
fn(record);
}
}
forEachMovedItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._movesHead; record !== null; record = record._nextMoved) {
fn(record);
}
}
forEachRemovedItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
fn(record);
}
}
forEachIdentityChange(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._identityChangesHead; record !== null; record = record._nextIdentityChange) {
fn(record);
}
}
diff(collection: any): DefaultIterableDiffer {
if (isBlank(collection)) collection = [];
if (!isListLikeIterable(collection)) {
throw new BaseException(`Error trying to diff '${collection}'`);
}
if (this.check(collection)) {
return this;
} else {
return null;
}
}
onDestroy() {}
// todo(vicb): optim for UnmodifiableListView (frozen arrays)
check(collection: any): boolean {
this._reset();
var record: CollectionChangeRecord = this._itHead;
var mayBeDirty: boolean = false;
var index: number;
var item;
var itemTrackBy;
if (isArray(collection)) {
var list = collection;
this._length = collection.length;
for (index = 0; index < this._length; index++) {
item = list[index];
itemTrackBy = this._trackByFn(index, item);
if (record === null || !looseIdentical(record.trackById, itemTrackBy)) {
record = this._mismatch(record, item, itemTrackBy, index);
mayBeDirty = true;
} else {
if (mayBeDirty) {
// TODO(misko): can we limit this to duplicates only?
record = this._verifyReinsertion(record, item, itemTrackBy, index);
}
if (!looseIdentical(record.item, item)) this._addIdentityChange(record, item);
}
record = record._next;
}
} else {
index = 0;
iterateListLike(collection, (item) => {
itemTrackBy = this._trackByFn(index, item);
if (record === null || !looseIdentical(record.trackById, itemTrackBy)) {
record = this._mismatch(record, item, itemTrackBy, index);
mayBeDirty = true;
} else {
if (mayBeDirty) {
// TODO(misko): can we limit this to duplicates only?
record = this._verifyReinsertion(record, item, itemTrackBy, index);
}
if (!looseIdentical(record.item, item)) this._addIdentityChange(record, item);
}
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, removals, or identity
* changes.
*/
get isDirty(): boolean {
return this._additionsHead !== null || this._movesHead !== null ||
this._removalsHead !== null || this._identityChangesHead !== 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
*
* @internal
*/
_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;
this._identityChangesHead = this._identityChangesTail = 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
*
* @internal
*/
_mismatch(record: CollectionChangeRecord, item: any, itemTrackBy: any,
index: number): CollectionChangeRecord {
// The previous record after which we will append the current one.
var previousRecord: CollectionChangeRecord;
if (record === null) {
previousRecord = this._itTail;
} else {
previousRecord = record._prev;
// Remove the record from the collection since we know it does not match the item.
this._remove(record);
}
// Attempt to see if we have seen the item before.
record = this._linkedRecords === null ? null : this._linkedRecords.get(itemTrackBy, index);
if (record !== null) {
// We have seen this before, we need to move it forward in the collection.
// But first we need to check if identity changed, so we can update in view if necessary
if (!looseIdentical(record.item, item)) this._addIdentityChange(record, item);
this._moveAfter(record, previousRecord, index);
} else {
// Never seen it, check evicted list.
record = this._unlinkedRecords === null ? null : this._unlinkedRecords.get(itemTrackBy);
if (record !== null) {
// It is an item which we have evicted earlier: reinsert it back into the list.
// But first we need to check if identity changed, so we can update in view if necessary
if (!looseIdentical(record.item, item)) this._addIdentityChange(record, item);
this._reinsertAfter(record, previousRecord, index);
} else {
// It is a new item: add it.
record =
this._addAfter(new CollectionChangeRecord(item, itemTrackBy), 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.
*
* @internal
*/
_verifyReinsertion(record: CollectionChangeRecord, item: any, itemTrackBy: any,
index: number): CollectionChangeRecord {
var reinsertRecord: CollectionChangeRecord =
this._unlinkedRecords === null ? null : this._unlinkedRecords.get(itemTrackBy);
if (reinsertRecord !== null) {
record = this._reinsertAfter(reinsertRecord, record._prev, index);
} else if (record.currentIndex != index) {
record.currentIndex = index;
this._addToMoves(record, index);
}
return record;
}
/**
* Get rid of any excess {@link CollectionChangeRecord}s from the previous collection
*
* - `record` The first excess {@link CollectionChangeRecord}.
*
* @internal
*/
_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;
}
if (this._identityChangesTail !== null) {
this._identityChangesTail._nextIdentityChange = null;
}
}
/** @internal */
_reinsertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index: number): CollectionChangeRecord {
if (this._unlinkedRecords !== null) {
this._unlinkedRecords.remove(record);
}
var prev = record._prevRemoved;
var next = record._nextRemoved;
if (prev === null) {
this._removalsHead = next;
} else {
prev._nextRemoved = next;
}
if (next === null) {
this._removalsTail = prev;
} else {
next._prevRemoved = prev;
}
this._insertAfter(record, prevRecord, index);
this._addToMoves(record, index);
return record;
}
/** @internal */
_moveAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index: number): CollectionChangeRecord {
this._unlink(record);
this._insertAfter(record, prevRecord, index);
this._addToMoves(record, index);
return record;
}
/** @internal */
_addAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index: number): CollectionChangeRecord {
this._insertAfter(record, prevRecord, index);
if (this._additionsTail === null) {
// todo(vicb)
// assert(this._additionsHead === null);
this._additionsTail = this._additionsHead = record;
} else {
// todo(vicb)
// assert(_additionsTail._nextAdded === null);
// assert(record._nextAdded === null);
this._additionsTail = this._additionsTail._nextAdded = record;
}
return record;
}
/** @internal */
_insertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index: number): CollectionChangeRecord {
// todo(vicb)
// assert(record != prevRecord);
// assert(record._next === null);
// assert(record._prev === null);
var next: CollectionChangeRecord = prevRecord === null ? this._itHead : prevRecord._next;
// todo(vicb)
// assert(next != record);
// assert(prevRecord != record);
record._next = next;
record._prev = prevRecord;
if (next === null) {
this._itTail = record;
} else {
next._prev = record;
}
if (prevRecord === null) {
this._itHead = record;
} else {
prevRecord._next = record;
}
if (this._linkedRecords === null) {
this._linkedRecords = new _DuplicateMap();
}
this._linkedRecords.put(record);
record.currentIndex = index;
return record;
}
/** @internal */
_remove(record: CollectionChangeRecord): CollectionChangeRecord {
return this._addToRemovals(this._unlink(record));
}
/** @internal */
_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;
}
/** @internal */
_addToMoves(record: CollectionChangeRecord, toIndex: number): CollectionChangeRecord {
// todo(vicb)
// assert(record._nextMoved === null);
if (record.previousIndex === toIndex) {
return record;
}
if (this._movesTail === null) {
// todo(vicb)
// assert(_movesHead === null);
this._movesTail = this._movesHead = record;
} else {
// todo(vicb)
// assert(_movesTail._nextMoved === null);
this._movesTail = this._movesTail._nextMoved = record;
}
return record;
}
/** @internal */
_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;
}
/** @internal */
_addIdentityChange(record: CollectionChangeRecord, item: any) {
record.item = item;
if (this._identityChangesTail === null) {
this._identityChangesTail = this._identityChangesHead = record;
} else {
this._identityChangesTail = this._identityChangesTail._nextIdentityChange = record;
}
return record;
}
toString(): string {
var list = [];
this.forEachItem((record) => list.push(record));
var previous = [];
this.forEachPreviousItem((record) => previous.push(record));
var additions = [];
this.forEachAddedItem((record) => additions.push(record));
var moves = [];
this.forEachMovedItem((record) => moves.push(record));
var removals = [];
this.forEachRemovedItem((record) => removals.push(record));
var identityChanges = [];
this.forEachIdentityChange((record) => identityChanges.push(record));
return "collection: " + list.join(', ') + "\n" + "previous: " + previous.join(', ') + "\n" +
"additions: " + additions.join(', ') + "\n" + "moves: " + moves.join(', ') + "\n" +
"removals: " + removals.join(', ') + "\n" + "identityChanges: " +
identityChanges.join(', ') + "\n";
}
}
export class CollectionChangeRecord {
currentIndex: number = null;
previousIndex: number = null;
/** @internal */
_nextPrevious: CollectionChangeRecord = null;
/** @internal */
_prev: CollectionChangeRecord = null;
/** @internal */
_next: CollectionChangeRecord = null;
/** @internal */
_prevDup: CollectionChangeRecord = null;
/** @internal */
_nextDup: CollectionChangeRecord = null;
/** @internal */
_prevRemoved: CollectionChangeRecord = null;
/** @internal */
_nextRemoved: CollectionChangeRecord = null;
/** @internal */
_nextAdded: CollectionChangeRecord = null;
/** @internal */
_nextMoved: CollectionChangeRecord = null;
/** @internal */
_nextIdentityChange: CollectionChangeRecord = null;
constructor(public item: any, public trackById: any) {}
toString(): string {
return this.previousIndex === this.currentIndex ?
stringify(this.item) :
stringify(this.item) + '[' + stringify(this.previousIndex) + '->' +
stringify(this.currentIndex) + ']';
}
}
// A linked list of CollectionChangeRecords with the same CollectionChangeRecord.item
class _DuplicateItemRecordList {
/** @internal */
_head: CollectionChangeRecord = null;
/** @internal */
_tail: CollectionChangeRecord = null;
/**
* Append the record to the list of duplicates.
*
* Note: by design all records in the list of duplicates hold the same value in record.item.
*/
add(record: CollectionChangeRecord): void {
if (this._head === null) {
this._head = this._tail = record;
record._nextDup = null;
record._prevDup = null;
} else {
// todo(vicb)
// assert(record.item == _head.item ||
// record.item is num && record.item.isNaN && _head.item is num && _head.item.isNaN);
this._tail._nextDup = record;
record._prevDup = this._tail;
record._nextDup = null;
this._tail = record;
}
}
// Returns a CollectionChangeRecord having CollectionChangeRecord.trackById == trackById and
// CollectionChangeRecord.currentIndex >= afterIndex
get(trackById: any, afterIndex: number): CollectionChangeRecord {
var record: CollectionChangeRecord;
for (record = this._head; record !== null; record = record._nextDup) {
if ((afterIndex === null || afterIndex < record.currentIndex) &&
looseIdentical(record.trackById, trackById)) {
return record;
}
}
return null;
}
/**
* Remove one {@link CollectionChangeRecord} from the list of duplicates.
*
* Returns whether the list of duplicates is empty.
*/
remove(record: CollectionChangeRecord): boolean {
// todo(vicb)
// assert(() {
// // verify that the record being removed is in the list.
// for (CollectionChangeRecord cursor = _head; cursor != null; cursor = cursor._nextDup) {
// if (identical(cursor, record)) return true;
// }
// return false;
//});
var prev: CollectionChangeRecord = record._prevDup;
var next: CollectionChangeRecord = record._nextDup;
if (prev === null) {
this._head = next;
} else {
prev._nextDup = next;
}
if (next === null) {
this._tail = prev;
} else {
next._prevDup = prev;
}
return this._head === null;
}
}
class _DuplicateMap {
map = new Map<any, _DuplicateItemRecordList>();
put(record: CollectionChangeRecord) {
// todo(vicb) handle corner cases
var key = getMapKey(record.trackById);
var duplicates = this.map.get(key);
if (!isPresent(duplicates)) {
duplicates = new _DuplicateItemRecordList();
this.map.set(key, duplicates);
}
duplicates.add(record);
}
/**
* Retrieve the `value` using key. Because the CollectionChangeRecord value may be 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(trackById: any, afterIndex: number = null): CollectionChangeRecord {
var key = getMapKey(trackById);
var recordList = this.map.get(key);
return isBlank(recordList) ? null : recordList.get(trackById, afterIndex);
}
/**
* Removes a {@link CollectionChangeRecord} from the list of duplicates.
*
* The list of duplicates also is removed from the map if it gets empty.
*/
remove(record: CollectionChangeRecord): CollectionChangeRecord {
var key = getMapKey(record.trackById);
// todo(vicb)
// assert(this.map.containsKey(key));
var recordList: _DuplicateItemRecordList = this.map.get(key);
// Remove the list of duplicates when it gets empty
if (recordList.remove(record)) {
this.map.delete(key);
}
return record;
}
get isEmpty(): boolean { return this.map.size === 0; }
clear() { this.map.clear(); }
toString(): string { return '_DuplicateMap(' + stringify(this.map) + ')'; }
}

View File

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

View File

@ -0,0 +1,93 @@
import {isBlank, isPresent, getTypeNameForDebugging} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {ListWrapper} from 'angular2/src/facade/collection';
import {ChangeDetectorRef} from '../change_detector_ref';
import {Provider, SkipSelfMetadata, OptionalMetadata, Injectable} from 'angular2/src/core/di';
/**
* A strategy for tracking changes over time to an iterable. Used for {@link NgFor} to
* respond to changes in an iterable by effecting equivalent changes in the DOM.
*/
export interface IterableDiffer {
diff(object: any): any;
onDestroy();
}
/**
* An optional function passed into {@link NgFor} that defines how to track
* items in an iterable (e.g. by index or id)
*/
export interface TrackByFn { (index: number, item: any): any; }
/**
* Provides a factory for {@link IterableDiffer}.
*/
export interface IterableDifferFactory {
supports(objects: any): boolean;
create(cdRef: ChangeDetectorRef, trackByFn?: TrackByFn): IterableDiffer;
}
/**
* A repository of different iterable diffing strategies used by NgFor, NgClass, and others.
* @ts2dart_const
*/
export class IterableDiffers {
/*@ts2dart_const*/
constructor(public factories: IterableDifferFactory[]) {}
static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers {
if (isPresent(parent)) {
var copied = ListWrapper.clone(parent.factories);
factories = factories.concat(copied);
return new IterableDiffers(factories);
} else {
return new IterableDiffers(factories);
}
}
/**
* Takes an array of {@link IterableDifferFactory} and returns a provider used to extend the
* inherited {@link IterableDiffers} instance with the provided factories and return a new
* {@link IterableDiffers} instance.
*
* The following example shows how to extend an existing list of factories,
* which will only be applied to the injector for this component and its children.
* This step is all that's required to make a new {@link IterableDiffer} available.
*
* ### Example
*
* ```
* @Component({
* viewProviders: [
* IterableDiffers.extend([new ImmutableListDiffer()])
* ]
* })
* ```
*/
static extend(factories: IterableDifferFactory[]): Provider {
return new Provider(IterableDiffers, {
useFactory: (parent: IterableDiffers) => {
if (isBlank(parent)) {
// Typically would occur when calling IterableDiffers.extend inside of dependencies passed
// to
// bootstrap(), which would override default pipes instead of extending them.
throw new BaseException('Cannot extend IterableDiffers without a parent injector');
}
return IterableDiffers.create(factories, parent);
},
// Dependency technically isn't optional, but we can provide a better error message this way.
deps: [[IterableDiffers, new SkipSelfMetadata(), new OptionalMetadata()]]
});
}
find(iterable: any): IterableDifferFactory {
var factory = this.factories.find(f => f.supports(iterable));
if (isPresent(factory)) {
return factory;
} else {
throw new BaseException(
`Cannot find a differ supporting object '${iterable}' of type '${getTypeNameForDebugging(iterable)}'`);
}
}
}

View File

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

View File

@ -0,0 +1,33 @@
/**
* To create a Pipe, you must implement this interface.
*
* Angular invokes the `transform` method with the value of a binding
* as the first argument, and any parameters as the second argument in list form.
*
* ## Syntax
*
* `value | pipeName[:arg0[:arg1...]]`
*
* ### Example ([live demo](http://plnkr.co/edit/f5oyIked9M2cKzvZNKHV?p=preview))
*
* The `RepeatPipe` below repeats the value as many times as indicated by the first argument:
*
* ```
* import {Pipe, PipeTransform} from 'angular2/core';
*
* @Pipe({name: 'repeat'})
* export class RepeatPipe implements PipeTransform {
* transform(value: any, times: number) {
* return value.repeat(times);
* }
* }
* ```
*
* Invoking `{{ 'ok' | repeat:3 }}` in a template produces `okokok`.
*
*/
abstract class PipeTransform {
// Note: Dart does not support varargs,
// so we can't type the `transform` method...
// dynamic transform(dynamic value, List<dynamic> ...args): any;
}

View File

@ -0,0 +1,29 @@
/**
* To create a Pipe, you must implement this interface.
*
* Angular invokes the `transform` method with the value of a binding
* as the first argument, and any parameters as the second argument in list form.
*
* ## Syntax
*
* `value | pipeName[:arg0[:arg1...]]`
*
* ### Example ([live demo](http://plnkr.co/edit/f5oyIked9M2cKzvZNKHV?p=preview))
*
* The `RepeatPipe` below repeats the value as many times as indicated by the first argument:
*
* ```
* import {Pipe, PipeTransform} from 'angular2/core';
*
* @Pipe({name: 'repeat'})
* export class RepeatPipe implements PipeTransform {
* transform(value: any, times: number) {
* return value.repeat(times);
* }
* }
* ```
*
* Invoking `{{ 'ok' | repeat:3 }}` in a template produces `okokok`.
*
*/
export interface PipeTransform { transform(value: any, ...args: any[]): any; }