design: simplified view interfaces
This commit is contained in:
parent
39c03e67e6
commit
9c7c7e8acf
|
@ -18,5 +18,4 @@ export class ChangeDetection {
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
library change_detection.facade;
|
||||||
|
|
||||||
|
typedef SetterFn(Object obj, value);
|
|
@ -0,0 +1 @@
|
||||||
|
export var SetterFn = Function;
|
|
@ -23,8 +23,8 @@ export class ProtoRecord {
|
||||||
constructor(watchGroup:ProtoWatchGroup, fieldName:String) {
|
constructor(watchGroup:ProtoWatchGroup, fieldName:String) {
|
||||||
this.watchGroup = watchGroup;
|
this.watchGroup = watchGroup;
|
||||||
this.fieldName = fieldName;
|
this.fieldName = fieldName;
|
||||||
this._next = null;
|
this.next = null;
|
||||||
this._prev = null;
|
this.prev = null;
|
||||||
this._checkNext = null;
|
this._checkNext = null;
|
||||||
this._checkPrev = null;
|
this._checkPrev = null;
|
||||||
this._notifierNext = null;
|
this._notifierNext = null;
|
||||||
|
@ -36,17 +36,17 @@ export class ProtoRecord {
|
||||||
|
|
||||||
instantiate(watchGroup:WatchGroup):Record {
|
instantiate(watchGroup:WatchGroup):Record {
|
||||||
var record = this._clone = new Record(watchGroup, this);
|
var record = this._clone = new Record(watchGroup, this);
|
||||||
record._prev = this._prev._clone;
|
record.prev = this.prev._clone;
|
||||||
record._checkPrev = this._checkPrev._clone;
|
record._checkPrev = this._checkPrev._clone;
|
||||||
return _clone;
|
return _clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
instantiateComplete():Record {
|
instantiateComplete():Record {
|
||||||
var record = this._clone;
|
var record = this._clone;
|
||||||
record._next = this._next._clone;
|
record.next = this.next._clone;
|
||||||
record._checkNext = this._checkNext._clone;
|
record._checkNext = this._checkNext._clone;
|
||||||
this._clone = null;
|
this._clone = null;
|
||||||
return this._next;
|
return this.next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,8 +97,8 @@ export class Record {
|
||||||
constructor(watchGroup:WatchGroup, protoRecord:ProtoRecord) {
|
constructor(watchGroup:WatchGroup, protoRecord:ProtoRecord) {
|
||||||
this.protoRecord = protoRecord;
|
this.protoRecord = protoRecord;
|
||||||
this.watchGroup = watchGroup;
|
this.watchGroup = watchGroup;
|
||||||
this._next = null;
|
this.next = null;
|
||||||
this._prev = null;
|
this.prev = null;
|
||||||
this._checkNext = null;
|
this._checkNext = null;
|
||||||
this._checkPrev = null;
|
this._checkPrev = null;
|
||||||
this._notifierNext = null;
|
this._notifierNext = null;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {ProtoRecord, Record} from './record';
|
import {ProtoRecord, Record} from './record';
|
||||||
import {WatchGroupDispatcher} from './watch_group_dispatcher';
|
|
||||||
|
|
||||||
export class ProtoWatchGroup {
|
export class ProtoWatchGroup {
|
||||||
@FIELD('final _headRecord:ProtoRecord')
|
@FIELD('final _headRecord:ProtoRecord')
|
||||||
|
@ -31,7 +30,8 @@ export class ProtoWatchGroup {
|
||||||
|
|
||||||
proto = this._headRecord;
|
proto = this._headRecord;
|
||||||
while(proto != null) {
|
while(proto != null) {
|
||||||
proto = proto.instantiateComplete();
|
proto.instantiateComplete();
|
||||||
|
proto = proto.next;
|
||||||
}
|
}
|
||||||
|
|
||||||
watchGroup._headRecord = head;
|
watchGroup._headRecord = head;
|
||||||
|
@ -61,3 +61,7 @@ export class WatchGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class WatchGroupDispatcher {
|
||||||
|
onRecordChange(record:Record, context) {}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import {Record} from './record';
|
|
||||||
|
|
||||||
export class WatchGroupDispatcher {
|
|
||||||
onRecordChange(record:Record, context) {}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:di/di.dart' show Module;
|
import 'package:di/di.dart' show Module;
|
||||||
import '../view/element_module.dart' show ElementModule;
|
import '../compiler/element_module.dart' show ElementModule;
|
||||||
|
|
||||||
typedef DomServicesFunction(ElementModule m);
|
typedef DomServicesFunction(ElementModule m);
|
||||||
typedef ComponentServicesFunction(Module m);
|
typedef ComponentServicesFunction(Module m);
|
|
@ -1,6 +1,6 @@
|
||||||
import {Future, Type} from 'facade/lang';
|
import {Future, Type} from 'facade/lang';
|
||||||
import {Element} from 'facade/dom';
|
import {Element} from 'facade/dom';
|
||||||
import {ProtoView} from '../view/proto_view';
|
import {ProtoView} from './view';
|
||||||
import {TemplateLoader} from './template_loader';
|
import {TemplateLoader} from './template_loader';
|
||||||
|
|
||||||
export class Compiler {
|
export class Compiler {
|
||||||
|
|
|
@ -21,7 +21,21 @@ ElementInjector (ElementModule):
|
||||||
export class ProtoElementInjector {
|
export class ProtoElementInjector {
|
||||||
@FIELD('final _parent:ProtoElementInjector')
|
@FIELD('final _parent:ProtoElementInjector')
|
||||||
/// Temporory instance while instantiating
|
/// Temporory instance while instantiating
|
||||||
@FIELD('_instance:ElementInjector')
|
@FIELD('_clone:ElementInjector')
|
||||||
constructor() {}
|
constructor(parent:ProtoElementInjector) {
|
||||||
|
this._parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
instantiate():ElementInjector {
|
||||||
|
return new ElementInjector(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ElementInjector {
|
||||||
|
@FIELD('final protoInjector:ProtoElementInjector')
|
||||||
|
constructor(protoInjector:ProtoElementInjector) {
|
||||||
|
this.protoInjector = protoInjector;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
import {DOM, Node, DocumentFragment, TemplateElement} from 'facade/dom';
|
||||||
|
import {ListWrapper wraps List} from 'facade/collection';
|
||||||
|
import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detection/watch_group';
|
||||||
|
import {Record} from 'change_detection/record';
|
||||||
|
import {Module} from 'di/di';
|
||||||
|
import {ProtoElementInjector, ElementInjector} from './element_injector';
|
||||||
|
import {SetterFn} from 'change_detection/facade';
|
||||||
|
|
||||||
|
export class ProtoView {
|
||||||
|
@FIELD('final _template:TemplateElement')
|
||||||
|
@FIELD('final _module:Module')
|
||||||
|
@FIELD('final _protoElementInjectors:List<ProtoElementInjector>')
|
||||||
|
@FIELD('final _protoWatchGroup:ProtoWatchGroup')
|
||||||
|
constructor(
|
||||||
|
template:TemplateElement,
|
||||||
|
module:Module,
|
||||||
|
protoElementInjector:ProtoElementInjector,
|
||||||
|
protoWatchGroup:ProtoWatchGroup)
|
||||||
|
{
|
||||||
|
this._template = template;
|
||||||
|
this._module = module;
|
||||||
|
this._protoElementInjectors = protoElementInjector;
|
||||||
|
this._protoWatchGroup = protoWatchGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IMPLEMENTS(WatchGroupDispatcher)
|
||||||
|
export class View {
|
||||||
|
@FIELD('final _fragment:DocumentFragment')
|
||||||
|
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
|
||||||
|
@FIELD('final _rootElementInjectors:List<ElementInjector>')
|
||||||
|
@FIELD('final _elementInjectors:List<ElementInjector>')
|
||||||
|
@FIELD('final _textNodes:List<Text>')
|
||||||
|
@FIELD('final _watchGroup:WatchGroup')
|
||||||
|
/// When the view is part of render tree, the DocumentFragment is empty, which is why we need
|
||||||
|
/// to keep track of the nodes.
|
||||||
|
@FIELD('final _nodes:List<Node>')
|
||||||
|
@FIELD('final _onChangeDispatcher:OnChangeDispatcher')
|
||||||
|
constructor(fragment:DocumentFragment) {
|
||||||
|
this._fragment = fragment;
|
||||||
|
this._nodes = ListWrapper.clone(fragment.childNodes);
|
||||||
|
this._onChangeDispatcher = null;
|
||||||
|
this._elementInjectors = null;
|
||||||
|
this._textNodes = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
onRecordChange(record:Record, target) {
|
||||||
|
// dispatch to element injector or text nodes based on context
|
||||||
|
if (target instanceof ElementInjectorTarget) {
|
||||||
|
// we know that it is ElementInjectorTarget
|
||||||
|
var eTarget:ElementInjectorTarget = target;
|
||||||
|
this._onChangeDispatcher.notify(this, eTarget);
|
||||||
|
eTarget.invoke(record, this._elementInjectors);
|
||||||
|
} else {
|
||||||
|
// we know it refferst to _textNodes.
|
||||||
|
var textNodeIndex:number = target;
|
||||||
|
DOM.setText(this._textNodes[textNodeIndex], record.currentValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class ElementInjectorTarget {
|
||||||
|
@FIELD('final _elementInjectorIndex:int')
|
||||||
|
@FIELD('final _directiveIndex:int')
|
||||||
|
@FIELD('final _setterName:String')
|
||||||
|
@FIELD('final _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:Record, elementInjectors:List<ElementInjector>) {
|
||||||
|
var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex];
|
||||||
|
var directive = elementInjectors[this._directiveIndex];
|
||||||
|
this._setter(directive, record.currentValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//TODO(tbosch): I don't like to have done be called from a different place than notify
|
||||||
|
// notify is called by change detection, but done is called by our wrapper on detect changes.
|
||||||
|
export class OnChangeDispatcher {
|
||||||
|
|
||||||
|
@FIELD('_lastView:View')
|
||||||
|
@FIELD('_lastTarget:ElementInjectorTarget')
|
||||||
|
constructor() {
|
||||||
|
this._lastView = null;
|
||||||
|
this._lastTarget = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(view:View, eTarget:ElementInjectorTarget) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
done() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,5 @@ export * from 'change_detection/record';
|
||||||
|
|
||||||
export * from './compiler/compiler';
|
export * from './compiler/compiler';
|
||||||
export * from './compiler/template_loader';
|
export * from './compiler/template_loader';
|
||||||
|
export * from './compiler/view';
|
||||||
|
|
||||||
export * from './view/proto_view';
|
|
||||||
export * from './view/view';
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
export class ElementInjectorTarget {
|
|
||||||
@FIELD('final _elementInjectorIndex:int')
|
|
||||||
@FIELD('final _directiveIndex:int')
|
|
||||||
@FIELD('final _setterName:String')
|
|
||||||
@FIELD('final _setter:SetterFn')
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
invoke(record:Record, elementInjectors:List<ElementInjector>) {
|
|
||||||
var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex];
|
|
||||||
var directive = elementInjectors.getByIndex(this._directiveIndex);
|
|
||||||
this._setter(directive, record.currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
|
|
||||||
//TODO(tbosch): I don't like to have done be called from a different place than notify
|
|
||||||
// notify is called by change detection, but done is called by our wrapper on detect changes.
|
|
||||||
export class OnChangeDispatcher {
|
|
||||||
|
|
||||||
@FIELD('_lastView:View')
|
|
||||||
@FIELD('_lastTarget:ElementInjectorTarget')
|
|
||||||
constructor() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
notify(view:View, eTarget:ElementInjectorTarget) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
done() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
import {Module} from 'di/di';
|
|
||||||
import {TemplateElement} from 'facade/dom';
|
|
||||||
|
|
||||||
export class ProtoView {
|
|
||||||
@FIELD('final _template:TemplateElement')
|
|
||||||
@FIELD('final _module:Module')
|
|
||||||
@FIELD('final _protoElementInjectors:List<ProtoElementInjector>')
|
|
||||||
@FIELD('final _protoWatchGroup:ProtoWatchGroup')
|
|
||||||
constructor() { }
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
import {Node, DocumentFragment} from 'facade/dom';
|
|
||||||
import {ListWrapper wraps List} from 'facade/collection';
|
|
||||||
import {WatchGroupDispatcher} from 'change_detection/watch_group_dispatcher';
|
|
||||||
import {Record} from 'change_detection/record';
|
|
||||||
|
|
||||||
@IMPLEMENTS(WatchGroupDispatcher)
|
|
||||||
export class View {
|
|
||||||
@FIELD('final _fragment:DocumentFragment')
|
|
||||||
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
|
|
||||||
@FIELD('final _rootElementInjectors:List<ElementInjector>')
|
|
||||||
@FIELD('final _elementInjectors:List<ElementInjector>')
|
|
||||||
@FIELD('final _textNodes:List<Text>')
|
|
||||||
@FIELD('final _watchGroup:WatchGroup')
|
|
||||||
/// When the view is part of render tree, the DocumentFragment is empty, which is why we need
|
|
||||||
/// to keep track of the nodes.
|
|
||||||
@FIELD('final _nodes:List<Node>')
|
|
||||||
constructor(fragment:DocumentFragment) {
|
|
||||||
this._fragment = fragment;
|
|
||||||
this._nodes = ListWrapper.clone(fragment.childNodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
onRecordChange(record:Record, target) {
|
|
||||||
// dispatch to element injector or text nodes based on context
|
|
||||||
if (target instanceof ElementInjectorTarge) {
|
|
||||||
// we know that it is ElementInjectorTarge
|
|
||||||
var eTarget:ElementInjectorTarget = target;
|
|
||||||
onChangeDispatcher.notify(this, eTarget);
|
|
||||||
eTarget.invoke(record, _elementInjectors);
|
|
||||||
} else {
|
|
||||||
// we know it refferst to _textNodes.
|
|
||||||
var textNodeIndex:number = target;
|
|
||||||
DOM.setText(this._textNodes[textNodeIndex], record.currentValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue