design: add changed detection API
This commit is contained in:
parent
7e3005e705
commit
6335fc407c
|
@ -1,5 +1,22 @@
|
||||||
|
import {WatchGroup} from './watch_group';
|
||||||
|
import {Record} from './record';
|
||||||
|
|
||||||
export class ChangeDetection {
|
export class ChangeDetection {
|
||||||
|
|
||||||
|
@FIELD('final _rootWatchGroup:WatchGroup')
|
||||||
|
constructor(watchGroup:WatchGroup) {
|
||||||
|
this._rootWatchGroup = watchGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
detectChanges():int {
|
||||||
|
var current:Record = _rootWatchGroup._headRecord;
|
||||||
|
var count:number = 0;
|
||||||
|
while(current != null) {
|
||||||
|
if(current.check()) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
detectChanges():int {}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export class ProtoRecord {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
export class ProtoWatchGroup {
|
|
||||||
watch(
|
|
||||||
expression:String,
|
|
||||||
context:dynamic,
|
|
||||||
{isCollection})
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
@Component(
|
|
||||||
bind: {
|
|
||||||
'title': 'title',
|
|
||||||
'name': 'name'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
class MyComponent implements ChangeListener {
|
|
||||||
String name;
|
|
||||||
String title;
|
|
||||||
|
|
||||||
onChange(List<Record> changes) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
|
@ -1,4 +1,54 @@
|
||||||
import {WatchGroup} from './watch_group';
|
import {ProtoWatchGroup, WatchGroup} from './watch_group';
|
||||||
|
|
||||||
|
export class ProtoRecord {
|
||||||
|
|
||||||
|
@FIELD('final watchGroup:ProtoWatchGroup')
|
||||||
|
@FIELD('final fieldName:String')
|
||||||
|
/// order list of all records. Including head/tail markers
|
||||||
|
@FIELD('next:ProtoRecord')
|
||||||
|
@FIELD('prev:ProtoRecord')
|
||||||
|
/// next record to dirty check
|
||||||
|
@FIELD('_checkNext:ProtoRecord')
|
||||||
|
@FIELD('_checkPrev:ProtoRecord')
|
||||||
|
// next notifier
|
||||||
|
@FIELD('_notifierNext:ProtoRecord')
|
||||||
|
// Opeque data which will be presented to WatchGroupDispatcher
|
||||||
|
@FIELD('dispatcherContext')
|
||||||
|
// IF we detect change, we have to update the _context of the
|
||||||
|
// next record.
|
||||||
|
@FIELD('_updateContext:ProtoRecord')
|
||||||
|
// May be removed if we don't support coelsence.
|
||||||
|
@FIELD('_updateContextNext:ProtoRecord')
|
||||||
|
@FIELD('_clone')
|
||||||
|
constructor(watchGroup:ProtoWatchGroup, fieldName:String) {
|
||||||
|
this.watchGroup = watchGroup;
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
this._next = null;
|
||||||
|
this._prev = null;
|
||||||
|
this._checkNext = null;
|
||||||
|
this._checkPrev = null;
|
||||||
|
this._notifierNext = null;
|
||||||
|
this.dispatcherContext = null;
|
||||||
|
this._updateContext = null;
|
||||||
|
this._updateContextNext = null;
|
||||||
|
this._clone = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
instantiate(watchGroup:WatchGroup):Record {
|
||||||
|
var record = this._clone = new Record(watchGroup, this);
|
||||||
|
record._prev = this._prev._clone;
|
||||||
|
record._checkPrev = this._checkPrev._clone;
|
||||||
|
return _clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
instantiateComplete():Record {
|
||||||
|
var record = this._clone;
|
||||||
|
record._next = this._next._clone;
|
||||||
|
record._checkNext = this._checkNext._clone;
|
||||||
|
this._clone = null;
|
||||||
|
return this._next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,13 +69,8 @@ import {WatchGroup} from './watch_group';
|
||||||
*/
|
*/
|
||||||
export class Record {
|
export class Record {
|
||||||
|
|
||||||
@FIELD('final _watchGroup:WatchGroup')
|
@FIELD('final watchGroup:WatchGroup')
|
||||||
@FIELD('final _protoRecord:ProtoRecord')
|
@FIELD('final protoRecord:ProtoRecord')
|
||||||
@FIELD('_context')
|
|
||||||
@FIELD('_getter')
|
|
||||||
@FIELD('_arguments')
|
|
||||||
@FIELD('_previousValue')
|
|
||||||
@FIELD('_mode:int')
|
|
||||||
/// order list of all records. Including head/tail markers
|
/// order list of all records. Including head/tail markers
|
||||||
@FIELD('_next:Record')
|
@FIELD('_next:Record')
|
||||||
@FIELD('_prev:Record')
|
@FIELD('_prev:Record')
|
||||||
|
@ -37,18 +82,40 @@ export class Record {
|
||||||
// notifier context will be present to the notifier to release
|
// notifier context will be present to the notifier to release
|
||||||
// the object from notification/watching.
|
// the object from notification/watching.
|
||||||
@FIELD('_notifierContext')
|
@FIELD('_notifierContext')
|
||||||
// Opeque data which will be presented to WatchGroupDispatcher
|
|
||||||
@FIELD('_watchContext')
|
|
||||||
// IF we detect change, we have to update the _context of the
|
// IF we detect change, we have to update the _context of the
|
||||||
// next record.
|
// next record.
|
||||||
@FIELD('_updateContext:Record')
|
@FIELD('_updateContext:Record')
|
||||||
// May be removed if we don't support coelsence.
|
// May be removed if we don't support coelsence.
|
||||||
@FIELD('_updateContextNext:Record')
|
@FIELD('_updateContextNext:Record')
|
||||||
constructor() {
|
|
||||||
|
@FIELD('_mode:int')
|
||||||
|
@FIELD('_context')
|
||||||
|
@FIELD('_getter')
|
||||||
|
@FIELD('_arguments')
|
||||||
|
@FIELD('currentValue')
|
||||||
|
@FIELD('previousValue')
|
||||||
|
constructor(watchGroup:WatchGroup, protoRecord:ProtoRecord) {
|
||||||
|
this.protoRecord = protoRecord;
|
||||||
|
this.watchGroup = watchGroup;
|
||||||
|
this._next = null;
|
||||||
|
this._prev = null;
|
||||||
|
this._checkNext = null;
|
||||||
|
this._checkPrev = null;
|
||||||
|
this._notifierNext = null;
|
||||||
|
this._notifierContext = null;
|
||||||
|
this._updateContext = null;
|
||||||
|
this._updateContextNext = null;
|
||||||
|
|
||||||
|
this._mode = MODE_STATE_MARKER;
|
||||||
|
this._context = null;
|
||||||
|
this._getter = null;
|
||||||
|
this._arguments = null;
|
||||||
|
this.currentValue = null;
|
||||||
|
this.previousValue = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
check():bool {
|
check():bool {
|
||||||
var mode = this.mode;
|
var mode = this._mode;
|
||||||
var state = mode & MODE_MASK_STATE;
|
var state = mode & MODE_MASK_STATE;
|
||||||
var notify = mode & MODE_MASK_NOTIFY;
|
var notify = mode & MODE_MASK_NOTIFY;
|
||||||
var currentValue;
|
var currentValue;
|
||||||
|
@ -67,14 +134,15 @@ export class Record {
|
||||||
case MODE_STATE_MAP:
|
case MODE_STATE_MAP:
|
||||||
case MODE_STATE_LIST:
|
case MODE_STATE_LIST:
|
||||||
}
|
}
|
||||||
var previousValue = this._previousValue;
|
var previousValue = this.previousValue;
|
||||||
if (isSame(previousValue, currentValue)) return false;
|
if (isSame(previousValue, currentValue)) return false;
|
||||||
if (previousValue instanceof String && currentValue instanceof String
|
if (previousValue instanceof String && currentValue instanceof String
|
||||||
&& previousValue == currentValue) {
|
&& previousValue == currentValue) {
|
||||||
this._previousValue = currentValue;
|
this.previousValue = currentValue;
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
this.previousValue = previousValue;
|
this.previousValue = currentValue;
|
||||||
|
this.watchGroup.dispatcher.onRecordChange(this, this.protoRecord.dispatcherContext);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,24 +151,24 @@ export class Record {
|
||||||
// to use and which dereference mode to execute.
|
// to use and which dereference mode to execute.
|
||||||
|
|
||||||
// We use dirty checking aka no notification
|
// We use dirty checking aka no notification
|
||||||
var MODE_MASK_NOTIFY:number = 0xFF00;
|
const MODE_MASK_NOTIFY = 0xFF00;
|
||||||
// Encodes the state of dereference
|
// Encodes the state of dereference
|
||||||
var MODE_MASK_STATE:int = 0x00FF;
|
const MODE_MASK_STATE = 0x00FF;
|
||||||
|
|
||||||
var MODE_PLUGIN_DIRTY_CHECK:int = 0x0000;
|
const MODE_PLUGIN_DIRTY_CHECK = 0x0000;
|
||||||
var MODE_STATE_MARKER:int = 0x0000;
|
const MODE_STATE_MARKER = 0x0000;
|
||||||
|
|
||||||
/// _context[_protoRecord.propname] => _getter(_context)
|
/// _context[_protoRecord.propname] => _getter(_context)
|
||||||
var MODE_STATE_PROPERTY:int = 0x0001;
|
const MODE_STATE_PROPERTY = 0x0001;
|
||||||
/// _context(_arguments)
|
/// _context(_arguments)
|
||||||
var MODE_STATE_INVOKE_CLOSURE:int = 0x0002;
|
const MODE_STATE_INVOKE_CLOSURE = 0x0002;
|
||||||
/// _getter(_context, _arguments)
|
/// _getter(_context, _arguments)
|
||||||
var MODE_STATE_INVOKE_METHOD:int = 0x0003;
|
const MODE_STATE_INVOKE_METHOD = 0x0003;
|
||||||
|
|
||||||
/// _context is Map => _previousValue is MapChangeRecord
|
/// _context is Map => _previousValue is MapChangeRecord
|
||||||
var MODE_STATE_MAP:int = 0x0004;
|
const MODE_STATE_MAP = 0x0004;
|
||||||
/// _context is Array/List/Iterable => _previousValue = ListChangeRecord
|
/// _context is Array/List/Iterable => _previousValue = ListChangeRecord
|
||||||
var MODE_STATE_LIST:int = 0x0005;
|
const MODE_STATE_LIST = 0x0005;
|
||||||
|
|
||||||
function isSame(a, b) {
|
function isSame(a, b) {
|
||||||
if (a === b) {
|
if (a === b) {
|
||||||
|
|
|
@ -1,4 +1,63 @@
|
||||||
|
import {ProtoRecord, Record} from './record';
|
||||||
|
import {WatchGroupDispatcher} from './watch_group_dispatcher';
|
||||||
|
|
||||||
|
export class ProtoWatchGroup {
|
||||||
|
@FIELD('final _headRecord:ProtoRecord')
|
||||||
|
@FIELD('final _tailRecord:ProtoRecord')
|
||||||
|
constructor() {
|
||||||
|
this._headRecord = null;
|
||||||
|
this._tailRecord = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
expression:String,
|
||||||
|
context,
|
||||||
|
{isCollection})
|
||||||
|
{
|
||||||
|
/// IMPREMENT
|
||||||
|
}
|
||||||
|
|
||||||
|
instantiate(dispatcher:WatchGroupDispatcher):WatchGroup {
|
||||||
|
var watchGroup:WatchGroup = new WatchGroup(this, dispatcher);
|
||||||
|
var head:Record = null;
|
||||||
|
var tail:Record = null;
|
||||||
|
var proto:ProtoRecord = this._headRecord;
|
||||||
|
|
||||||
|
while(proto != null) {
|
||||||
|
tail = proto.instantiate(watchGroup);
|
||||||
|
if (head == null) head = tail;
|
||||||
|
proto = proto.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
proto = this._headRecord;
|
||||||
|
while(proto != null) {
|
||||||
|
proto = proto.instantiateComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
watchGroup._headRecord = head;
|
||||||
|
watchGroup._tailRecord = tail;
|
||||||
|
return watchGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class WatchGroup {
|
export class WatchGroup {
|
||||||
|
@FIELD('final protoWatchGroup:ProtoWatchGroup')
|
||||||
@FIELD('final dispatcher:WatchGroupDispatcher')
|
@FIELD('final dispatcher:WatchGroupDispatcher')
|
||||||
constructor() {}
|
@FIELD('final _headRecord:Record')
|
||||||
}
|
@FIELD('final _tailRecord:Record')
|
||||||
|
constructor(protoWatchGroup:ProtoWatchGroup, dispatcher:WatchGroupDispatcher) {
|
||||||
|
this.protoWatchGroup = protoWatchGroup;
|
||||||
|
this.dispatcher = dispatcher;
|
||||||
|
this._headRecord = null;
|
||||||
|
this._tailRecord = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
insertChildGroup(newChild:WatchGroup, insertAfter:WatchGroup) {
|
||||||
|
/// IMPLEMENT
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
/// IMPLEMENT
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
|
import {Record} from './record';
|
||||||
|
|
||||||
export class WatchGroupDispatcher {
|
export class WatchGroupDispatcher {
|
||||||
notify(record:Record, context) {}
|
onRecordChange(record:Record, context) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ export * from './annotations/component';
|
||||||
export * from './annotations/template_config';
|
export * from './annotations/template_config';
|
||||||
|
|
||||||
export * from 'change_detection/change_detection';
|
export * from 'change_detection/change_detection';
|
||||||
|
export * from 'change_detection/watch_group';
|
||||||
export * from 'change_detection/record';
|
export * from 'change_detection/record';
|
||||||
|
|
||||||
export * from './compiler/compiler';
|
export * from './compiler/compiler';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {Node, DocumentFragment} from 'facade/dom';
|
import {Node, DocumentFragment} from 'facade/dom';
|
||||||
import {ListWrapper wraps List} from 'facade/collection';
|
import {ListWrapper wraps List} from 'facade/collection';
|
||||||
|
import {WatchGroupDispatcher} from 'change_detection/watch_group_dispatcher';
|
||||||
import {Record} from 'change_detection/record';
|
import {Record} from 'change_detection/record';
|
||||||
|
|
||||||
@IMPLEMENTS(WatchGroupDispatcher)
|
@IMPLEMENTS(WatchGroupDispatcher)
|
||||||
|
@ -18,17 +19,17 @@ export class View {
|
||||||
this._nodes = ListWrapper.clone(fragment.childNodes);
|
this._nodes = ListWrapper.clone(fragment.childNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
notify(record:Record, target) {
|
onRecordChange(record:Record, target) {
|
||||||
/*
|
|
||||||
// dispatch to element injector or text nodes based on context
|
// dispatch to element injector or text nodes based on context
|
||||||
if (Number.is(target)) {
|
if (target is ElementInjectorTarge) {
|
||||||
// we know it refferst to _textNodes.
|
|
||||||
} else {
|
|
||||||
// we know that it is ElementInjectorTarge
|
// we know that it is ElementInjectorTarge
|
||||||
var eTarget:ElementInjectorTarget = target;
|
var eTarget:ElementInjectorTarget = target;
|
||||||
onChangeDispatcher.notify(this, eTarget);
|
onChangeDispatcher.notify(this, eTarget);
|
||||||
eTarget.invoke(record, _elementInjectors);
|
eTarget.invoke(record, _elementInjectors);
|
||||||
|
} else {
|
||||||
|
// we know it refferst to _textNodes.
|
||||||
|
var textNodeIndex:number = target;
|
||||||
|
DOM.setText(this._textNodes[textNodeIndex], record.currentValue);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ library angular.core.facade.dom;
|
||||||
|
|
||||||
import 'dart:html';
|
import 'dart:html';
|
||||||
|
|
||||||
export 'dart:html' show DocumentFragment, Node, Element, TemplateElement;
|
export 'dart:html' show DocumentFragment, Node, Element, TemplateElement, Text;
|
||||||
|
|
||||||
class DOM {
|
class DOM {
|
||||||
static query(selector) {
|
static query(selector) {
|
||||||
|
@ -14,7 +14,10 @@ class DOM {
|
||||||
static getInnerHTML(el) {
|
static getInnerHTML(el) {
|
||||||
return el.innerHtml;
|
return el.innerHtml;
|
||||||
}
|
}
|
||||||
static setInnerHTML(el, value) {
|
static setInnerHTML(el:, value) {
|
||||||
el.innerHtml = value;
|
el.innerHtml = value;
|
||||||
}
|
}
|
||||||
}
|
static setText(Text text, String value) {
|
||||||
|
text.text = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export var DocumentFragment = window.DocumentFragment;
|
export var DocumentFragment = window.DocumentFragment;
|
||||||
export var Node = window.Node;
|
export var Node = window.Node;
|
||||||
|
export var Text = window.Text;
|
||||||
export var Element = window.HTMLElement;
|
export var Element = window.HTMLElement;
|
||||||
export var TemplateElement = window.HTMLTemplateElement;
|
export var TemplateElement = window.HTMLTemplateElement;
|
||||||
|
|
||||||
|
@ -16,4 +17,7 @@ export class DOM {
|
||||||
static setInnerHTML(el, value) {
|
static setInnerHTML(el, value) {
|
||||||
el.innerHTML = value;
|
el.innerHTML = value;
|
||||||
}
|
}
|
||||||
}
|
static setText(text:Text, value:String) {
|
||||||
|
text.nodeValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue