From 9c7c7e8acffda77d0ee46423e802678d8f415627 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sun, 28 Sep 2014 16:29:11 -0700 Subject: [PATCH] design: simplified view interfaces --- .../change_detection/src/change_detection.js | 5 +- modules/change_detection/src/facade.dart | 3 + modules/change_detection/src/facade.es6 | 1 + modules/change_detection/src/record.js | 14 +-- modules/change_detection/src/watch_group.js | 8 +- .../src/watch_group_dispatcher.js | 5 - modules/core/src/annotations/facade.dart | 4 +- modules/core/src/compiler/compiler.js | 4 +- .../element_injector.js} | 22 +++- .../src/{view => compiler}/element_module.js | 0 modules/core/src/compiler/view.js | 107 ++++++++++++++++++ modules/core/src/core.js | 3 +- .../core/src/view/element_injector_target.js | 13 --- modules/core/src/view/on_change_dispatcher.js | 19 ---- modules/core/src/view/proto_view.js | 10 -- modules/core/src/view/view.js | 35 ------ 16 files changed, 149 insertions(+), 104 deletions(-) create mode 100644 modules/change_detection/src/facade.dart create mode 100644 modules/change_detection/src/facade.es6 delete mode 100644 modules/change_detection/src/watch_group_dispatcher.js rename modules/core/src/{view/proto_element_injector.js => compiler/element_injector.js} (61%) rename modules/core/src/{view => compiler}/element_module.js (100%) create mode 100644 modules/core/src/compiler/view.js delete mode 100644 modules/core/src/view/element_injector_target.js delete mode 100644 modules/core/src/view/on_change_dispatcher.js delete mode 100644 modules/core/src/view/proto_view.js delete mode 100644 modules/core/src/view/view.js diff --git a/modules/change_detection/src/change_detection.js b/modules/change_detection/src/change_detection.js index a03fdd770c..e96fbe4e6b 100644 --- a/modules/change_detection/src/change_detection.js +++ b/modules/change_detection/src/change_detection.js @@ -11,12 +11,11 @@ export class ChangeDetection { detectChanges():int { var current:Record = _rootWatchGroup._headRecord; var count:number = 0; - while(current != null) { - if(current.check()) { + while (current != null) { + if (current.check()) { count++; } } return count; } - } diff --git a/modules/change_detection/src/facade.dart b/modules/change_detection/src/facade.dart new file mode 100644 index 0000000000..41f3bcc83d --- /dev/null +++ b/modules/change_detection/src/facade.dart @@ -0,0 +1,3 @@ +library change_detection.facade; + +typedef SetterFn(Object obj, value); diff --git a/modules/change_detection/src/facade.es6 b/modules/change_detection/src/facade.es6 new file mode 100644 index 0000000000..35d97b6d2e --- /dev/null +++ b/modules/change_detection/src/facade.es6 @@ -0,0 +1 @@ +export var SetterFn = Function; diff --git a/modules/change_detection/src/record.js b/modules/change_detection/src/record.js index 53fd496a37..7a353e48f3 100644 --- a/modules/change_detection/src/record.js +++ b/modules/change_detection/src/record.js @@ -23,8 +23,8 @@ export class ProtoRecord { constructor(watchGroup:ProtoWatchGroup, fieldName:String) { this.watchGroup = watchGroup; this.fieldName = fieldName; - this._next = null; - this._prev = null; + this.next = null; + this.prev = null; this._checkNext = null; this._checkPrev = null; this._notifierNext = null; @@ -36,17 +36,17 @@ export class ProtoRecord { instantiate(watchGroup:WatchGroup):Record { var record = this._clone = new Record(watchGroup, this); - record._prev = this._prev._clone; + record.prev = this.prev._clone; record._checkPrev = this._checkPrev._clone; return _clone; } instantiateComplete():Record { var record = this._clone; - record._next = this._next._clone; + record.next = this.next._clone; record._checkNext = this._checkNext._clone; this._clone = null; - return this._next; + return this.next; } } @@ -97,8 +97,8 @@ export class Record { constructor(watchGroup:WatchGroup, protoRecord:ProtoRecord) { this.protoRecord = protoRecord; this.watchGroup = watchGroup; - this._next = null; - this._prev = null; + this.next = null; + this.prev = null; this._checkNext = null; this._checkPrev = null; this._notifierNext = null; diff --git a/modules/change_detection/src/watch_group.js b/modules/change_detection/src/watch_group.js index dcc87dc4cd..05655bb8a0 100644 --- a/modules/change_detection/src/watch_group.js +++ b/modules/change_detection/src/watch_group.js @@ -1,5 +1,4 @@ import {ProtoRecord, Record} from './record'; -import {WatchGroupDispatcher} from './watch_group_dispatcher'; export class ProtoWatchGroup { @FIELD('final _headRecord:ProtoRecord') @@ -31,7 +30,8 @@ export class ProtoWatchGroup { proto = this._headRecord; while(proto != null) { - proto = proto.instantiateComplete(); + proto.instantiateComplete(); + proto = proto.next; } watchGroup._headRecord = head; @@ -61,3 +61,7 @@ export class WatchGroup { } } + +export class WatchGroupDispatcher { + onRecordChange(record:Record, context) {} +} diff --git a/modules/change_detection/src/watch_group_dispatcher.js b/modules/change_detection/src/watch_group_dispatcher.js deleted file mode 100644 index 1685cff545..0000000000 --- a/modules/change_detection/src/watch_group_dispatcher.js +++ /dev/null @@ -1,5 +0,0 @@ -import {Record} from './record'; - -export class WatchGroupDispatcher { - onRecordChange(record:Record, context) {} -} diff --git a/modules/core/src/annotations/facade.dart b/modules/core/src/annotations/facade.dart index 56ac0c7744..fcb9ee3718 100644 --- a/modules/core/src/annotations/facade.dart +++ b/modules/core/src/annotations/facade.dart @@ -1,5 +1,5 @@ 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 ComponentServicesFunction(Module m); \ No newline at end of file +typedef ComponentServicesFunction(Module m); diff --git a/modules/core/src/compiler/compiler.js b/modules/core/src/compiler/compiler.js index 0c4290a1e6..0fa574baf6 100644 --- a/modules/core/src/compiler/compiler.js +++ b/modules/core/src/compiler/compiler.js @@ -1,6 +1,6 @@ import {Future, Type} from 'facade/lang'; import {Element} from 'facade/dom'; -import {ProtoView} from '../view/proto_view'; +import {ProtoView} from './view'; import {TemplateLoader} from './template_loader'; export class Compiler { @@ -23,4 +23,4 @@ export class Compiler { } -} \ No newline at end of file +} diff --git a/modules/core/src/view/proto_element_injector.js b/modules/core/src/compiler/element_injector.js similarity index 61% rename from modules/core/src/view/proto_element_injector.js rename to modules/core/src/compiler/element_injector.js index 9c3029bc17..135edb2a86 100644 --- a/modules/core/src/view/proto_element_injector.js +++ b/modules/core/src/compiler/element_injector.js @@ -17,11 +17,25 @@ ElementInjector (ElementModule): - Query mechanism for children - 1:1 to DOM structure. */ - + export class ProtoElementInjector { @FIELD('final _parent:ProtoElementInjector') /// Temporory instance while instantiating - @FIELD('_instance:ElementInjector') - constructor() {} + @FIELD('_clone:ElementInjector') + 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; + } + +} + diff --git a/modules/core/src/view/element_module.js b/modules/core/src/compiler/element_module.js similarity index 100% rename from modules/core/src/view/element_module.js rename to modules/core/src/compiler/element_module.js diff --git a/modules/core/src/compiler/view.js b/modules/core/src/compiler/view.js new file mode 100644 index 0000000000..7e51af4f9b --- /dev/null +++ b/modules/core/src/compiler/view.js @@ -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') + @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') + @FIELD('final _elementInjectors:List') + @FIELD('final _textNodes:List') + @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') + @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) { + 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() { + + } +} diff --git a/modules/core/src/core.js b/modules/core/src/core.js index fe4021a636..9163021684 100644 --- a/modules/core/src/core.js +++ b/modules/core/src/core.js @@ -11,6 +11,5 @@ export * from 'change_detection/record'; export * from './compiler/compiler'; export * from './compiler/template_loader'; +export * from './compiler/view'; -export * from './view/proto_view'; -export * from './view/view'; diff --git a/modules/core/src/view/element_injector_target.js b/modules/core/src/view/element_injector_target.js deleted file mode 100644 index 67392367cf..0000000000 --- a/modules/core/src/view/element_injector_target.js +++ /dev/null @@ -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) { - var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex]; - var directive = elementInjectors.getByIndex(this._directiveIndex); - this._setter(directive, record.currentValue); - } -} diff --git a/modules/core/src/view/on_change_dispatcher.js b/modules/core/src/view/on_change_dispatcher.js deleted file mode 100644 index 5fdc017fa3..0000000000 --- a/modules/core/src/view/on_change_dispatcher.js +++ /dev/null @@ -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() { - - } -} \ No newline at end of file diff --git a/modules/core/src/view/proto_view.js b/modules/core/src/view/proto_view.js deleted file mode 100644 index 576f2e1ec9..0000000000 --- a/modules/core/src/view/proto_view.js +++ /dev/null @@ -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') - @FIELD('final _protoWatchGroup:ProtoWatchGroup') - constructor() { } -} \ No newline at end of file diff --git a/modules/core/src/view/view.js b/modules/core/src/view/view.js deleted file mode 100644 index ddf7c9949f..0000000000 --- a/modules/core/src/view/view.js +++ /dev/null @@ -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') - @FIELD('final _elementInjectors:List') - @FIELD('final _textNodes:List') - @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') - 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); - } - } -}