feat(view): hook watch group instantiation in the view.
This commit is contained in:
parent
01e6c7b70c
commit
91f50b67b7
|
@ -39,10 +39,12 @@ export class ProtoWatchGroup {
|
||||||
this.tailRecord = tail;
|
this.tailRecord = tail;
|
||||||
}
|
}
|
||||||
|
|
||||||
instantiate(dispatcher:WatchGroupDispatcher):WatchGroup {
|
// TODO(rado): the type annotation should be dispatcher:WatchGroupDispatcher.
|
||||||
|
// but @Implements is not ready yet.
|
||||||
|
instantiate(dispatcher):WatchGroup {
|
||||||
var watchGroup:WatchGroup = new WatchGroup(this, dispatcher);
|
var watchGroup:WatchGroup = new WatchGroup(this, dispatcher);
|
||||||
var tail:Record = null;
|
var tail:Record = null;
|
||||||
var proto:ProtoRecord;
|
var proto:ProtoRecord = null;
|
||||||
var prevRecord:Record = null;
|
var prevRecord:Record = null;
|
||||||
|
|
||||||
if (this.headRecord !== null) {
|
if (this.headRecord !== null) {
|
||||||
|
@ -70,7 +72,9 @@ export class WatchGroup {
|
||||||
@FIELD('final dispatcher:WatchGroupDispatcher')
|
@FIELD('final dispatcher:WatchGroupDispatcher')
|
||||||
@FIELD('final headRecord:Record')
|
@FIELD('final headRecord:Record')
|
||||||
@FIELD('final tailRecord:Record')
|
@FIELD('final tailRecord:Record')
|
||||||
constructor(protoWatchGroup:ProtoWatchGroup, dispatcher:WatchGroupDispatcher) {
|
// TODO(rado): the type annotation should be dispatcher:WatchGroupDispatcher.
|
||||||
|
// but @Implements is not ready yet.
|
||||||
|
constructor(protoWatchGroup:ProtoWatchGroup, dispatcher) {
|
||||||
this.protoWatchGroup = protoWatchGroup;
|
this.protoWatchGroup = protoWatchGroup;
|
||||||
this.dispatcher = dispatcher;
|
this.dispatcher = dispatcher;
|
||||||
this.headRecord = null;
|
this.headRecord = null;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {ProtoElementInjector, ElementInjector} from './element_injector';
|
||||||
import {SetterFn} from 'change_detection/facade';
|
import {SetterFn} from 'change_detection/facade';
|
||||||
import {FIELD, IMPLEMENTS, int, isPresent, isBlank} from 'facade/lang';
|
import {FIELD, IMPLEMENTS, int, isPresent, isBlank} from 'facade/lang';
|
||||||
import {List} from 'facade/collection';
|
import {List} from 'facade/collection';
|
||||||
|
import {Injector} from 'di/di';
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Const of making objects: http://jsperf.com/instantiate-size-of-object
|
* Const of making objects: http://jsperf.com/instantiate-size-of-object
|
||||||
|
@ -24,7 +25,8 @@ export class View {
|
||||||
@FIELD('final nodes:List<Node>')
|
@FIELD('final nodes:List<Node>')
|
||||||
@FIELD('final onChangeDispatcher:OnChangeDispatcher')
|
@FIELD('final onChangeDispatcher:OnChangeDispatcher')
|
||||||
constructor(fragment:DocumentFragment, elementInjector:List,
|
constructor(fragment:DocumentFragment, elementInjector:List,
|
||||||
rootElementInjectors:List, textNodes:List, bindElements:List) {
|
rootElementInjectors:List, textNodes:List, bindElements:List,
|
||||||
|
protoWatchGroup:ProtoWatchGroup, context) {
|
||||||
this.fragment = fragment;
|
this.fragment = fragment;
|
||||||
this.nodes = ListWrapper.clone(fragment.childNodes);
|
this.nodes = ListWrapper.clone(fragment.childNodes);
|
||||||
this.elementInjectors = elementInjector;
|
this.elementInjectors = elementInjector;
|
||||||
|
@ -32,6 +34,8 @@ export class View {
|
||||||
this.onChangeDispatcher = null;
|
this.onChangeDispatcher = null;
|
||||||
this.textNodes = textNodes;
|
this.textNodes = textNodes;
|
||||||
this.bindElements = bindElements;
|
this.bindElements = bindElements;
|
||||||
|
this.watchGroup = protoWatchGroup.instantiate(this);
|
||||||
|
this.watchGroup.setContext(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
onRecordChange(record:Record, target) {
|
onRecordChange(record:Record, target) {
|
||||||
|
@ -66,28 +70,29 @@ export class ProtoView {
|
||||||
this._template = template;
|
this._template = template;
|
||||||
this._bindings = bindings;
|
this._bindings = bindings;
|
||||||
this._protoElementInjectors = protoElementInjectors;
|
this._protoElementInjectors = protoElementInjectors;
|
||||||
|
this._protoWatchGroup = protoWatchGroup;
|
||||||
|
|
||||||
// not implemented
|
// not implemented
|
||||||
this._protoWatchGroup = protoWatchGroup;
|
|
||||||
this._useRootElement = useRootElement;
|
this._useRootElement = useRootElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
instantiate():View {
|
instantiate(context, appInjector:Injector):View {
|
||||||
var fragment = DOM.clone(this._template.content);
|
var fragment = DOM.clone(this._template.content);
|
||||||
var elements = DOM.querySelectorAll(fragment, ".ng-binding");
|
var elements = DOM.querySelectorAll(fragment, ".ng-binding");
|
||||||
var protos = this._protoElementInjectors;
|
var protos = this._protoElementInjectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: vsavkin: benchmark
|
* TODO: vsavkin: benchmark
|
||||||
* If this performs poorly, the three loops can be collapsed into one.
|
* If this performs poorly, the five loops can be collapsed into one.
|
||||||
*/
|
*/
|
||||||
var elementInjectors = ProtoView._createElementInjectors(elements, protos);
|
var elementInjectors = ProtoView._createElementInjectors(elements, protos);
|
||||||
var rootElementInjectors = ProtoView._rootElementInjectors(elementInjectors);
|
var rootElementInjectors = ProtoView._rootElementInjectors(elementInjectors);
|
||||||
var textNodes = ProtoView._textNodes(elements, protos);
|
var textNodes = ProtoView._textNodes(elements, protos);
|
||||||
var bindElements = ProtoView._bindElements(elements, protos);
|
var bindElements = ProtoView._bindElements(elements, protos);
|
||||||
|
ProtoView._instantiateDirectives(elementInjectors, appInjector);
|
||||||
|
|
||||||
return new View(fragment, elementInjectors, rootElementInjectors, textNodes,
|
return new View(fragment, elementInjectors, rootElementInjectors, textNodes,
|
||||||
bindElements);
|
bindElements, this._protoWatchGroup, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static _createElementInjectors(elements, protos) {
|
static _createElementInjectors(elements, protos) {
|
||||||
|
@ -103,6 +108,13 @@ export class ProtoView {
|
||||||
return injectors;
|
return injectors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static _instantiateDirectives(
|
||||||
|
injectors:List<ElementInjectors>, appInjector:Injector) {
|
||||||
|
for (var i = 0; i < injectors.length; ++i) {
|
||||||
|
if (injectors[i] != null) injectors[i].instantiateDirectives(appInjector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static _createElementInjector(element, proto) {
|
static _createElementInjector(element, proto) {
|
||||||
//TODO: vsavkin: pass element to `proto.instantiate()` once https://github.com/angular/angular/pull/98 is merged
|
//TODO: vsavkin: pass element to `proto.instantiate()` once https://github.com/angular/angular/pull/98 is merged
|
||||||
return proto.hasBindings ? proto.instantiate({view:null}) : null;
|
return proto.hasBindings ? proto.instantiate({view:null}) : null;
|
||||||
|
|
|
@ -2,8 +2,12 @@ import {describe, xit, it, expect, beforeEach} from 'test_lib/test_lib';
|
||||||
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'core/compiler/view';
|
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'core/compiler/view';
|
||||||
import {Record} from 'change_detection/record';
|
import {Record} from 'change_detection/record';
|
||||||
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
|
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
|
||||||
|
import {ProtoWatchGroup} from 'change_detection/watch_group';
|
||||||
|
import {ChangeDetector} from 'change_detection/change_detector';
|
||||||
import {DOM, Element} from 'facade/dom';
|
import {DOM, Element} from 'facade/dom';
|
||||||
import {FIELD} from 'facade/lang';
|
import {FIELD} from 'facade/lang';
|
||||||
|
import {ImplicitReceiver, FieldRead} from 'change_detection/parser/ast';
|
||||||
|
import {ClosureMap} from 'change_detection/parser/closure_map';
|
||||||
|
|
||||||
class Directive {
|
class Directive {
|
||||||
@FIELD('prop')
|
@FIELD('prop')
|
||||||
|
@ -13,32 +17,36 @@ class Directive {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
|
var oneFieldAst = (fieldName) =>
|
||||||
|
new FieldRead(new ImplicitReceiver(), fieldName,
|
||||||
|
(new ClosureMap()).getter(fieldName));
|
||||||
|
|
||||||
describe('view', function() {
|
describe('view', function() {
|
||||||
var tempalteWithThreeTypesOfBindings =
|
var tempalteWithThreeTypesOfBindings =
|
||||||
'<section class="ng-binding">' +
|
'<section class="ng-binding">' +
|
||||||
'Hello {}!' +
|
'Hello {}!' +
|
||||||
'<div directive class="ng-binding">' +
|
'<div directive class="ng-binding">' +
|
||||||
'<span class="ng-binding" [hidden]="exp">don\'t show me</span>' +
|
'<span class="ng-binding" [id]="exp">don\'t show me</span>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</section>';
|
'</section>';
|
||||||
|
|
||||||
|
function templateElInj() {
|
||||||
|
var sectionPI = new ProtoElementInjector(null, [], [0], false);
|
||||||
|
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false);
|
||||||
|
var spanPI = new ProtoElementInjector(divPI, [], [], true);
|
||||||
|
return [sectionPI, divPI, spanPI];
|
||||||
|
}
|
||||||
|
|
||||||
describe('ProtoView', function() {
|
describe('ProtoView', function() {
|
||||||
it('should create view instance and locate basic parts', function() {
|
it('should create view instance and locate basic parts', function() {
|
||||||
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
|
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
|
||||||
|
|
||||||
var diBindings = [];
|
var diBindings = [];
|
||||||
|
|
||||||
var sectionPI = new ProtoElementInjector(null, [], [0], false);
|
|
||||||
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false);
|
|
||||||
var spanPI = new ProtoElementInjector(divPI, [], [], true);
|
|
||||||
var protoElementInjectors = [sectionPI, divPI, spanPI];
|
|
||||||
|
|
||||||
var protoWatchGroup = null;
|
|
||||||
var hasSingleRoot = false;
|
var hasSingleRoot = false;
|
||||||
var pv = new ProtoView(template, diBindings, protoElementInjectors,
|
var pv = new ProtoView(template, diBindings, templateElInj(),
|
||||||
protoWatchGroup, hasSingleRoot);
|
new ProtoWatchGroup(), hasSingleRoot);
|
||||||
|
|
||||||
var view = pv.instantiate();
|
var view = pv.instantiate(null, null);
|
||||||
|
|
||||||
var section = DOM.firstChild(template.content);
|
var section = DOM.firstChild(template.content);
|
||||||
|
|
||||||
|
@ -63,8 +71,9 @@ export function main() {
|
||||||
var sectionPI = new ProtoElementInjector(null, [Directive], [], false);
|
var sectionPI = new ProtoElementInjector(null, [Directive], [], false);
|
||||||
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false);
|
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false);
|
||||||
|
|
||||||
var pv = new ProtoView(template, [], [sectionPI, divPI], null, false);
|
var pv = new ProtoView(template, [], [sectionPI, divPI],
|
||||||
var view = pv.instantiate();
|
new ProtoWatchGroup(), false);
|
||||||
|
var view = pv.instantiate(null, null);
|
||||||
|
|
||||||
expect(view.rootElementInjectors.length).toEqual(1);
|
expect(view.rootElementInjectors.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
@ -73,20 +82,9 @@ export function main() {
|
||||||
var view;
|
var view;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
|
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
|
||||||
|
var pv = new ProtoView(template, [], templateElInj(),
|
||||||
var diBindings = [];
|
new ProtoWatchGroup(), false);
|
||||||
|
view = pv.instantiate(null, null);
|
||||||
var sectionPI = new ProtoElementInjector(null, [], [0], false);
|
|
||||||
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false);
|
|
||||||
var spanPI = new ProtoElementInjector(divPI, [], [], true);
|
|
||||||
var protoElementInjectors = [sectionPI, divPI, spanPI];
|
|
||||||
|
|
||||||
var protoWatchGroup = null;
|
|
||||||
var hasSingleRoot = false;
|
|
||||||
var pv = new ProtoView(template, diBindings, protoElementInjectors,
|
|
||||||
protoWatchGroup, hasSingleRoot);
|
|
||||||
|
|
||||||
view = pv.instantiate();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should consume text node changes', () => {
|
it('should consume text node changes', () => {
|
||||||
|
@ -98,20 +96,17 @@ export function main() {
|
||||||
|
|
||||||
it('should consume element binding changes', () => {
|
it('should consume element binding changes', () => {
|
||||||
var elementWithBinding = view.bindElements[0];
|
var elementWithBinding = view.bindElements[0];
|
||||||
expect(elementWithBinding.hidden).toEqual(false);
|
expect(elementWithBinding.id).toEqual('');
|
||||||
var record = new Record(null, null);
|
var record = new Record(null, null);
|
||||||
var memento = new ElementPropertyMemento(0, 'hidden');
|
var memento = new ElementPropertyMemento(0, 'id');
|
||||||
record.currentValue = true;
|
record.currentValue = 'foo';
|
||||||
view.onRecordChange(record, memento);
|
view.onRecordChange(record, memento);
|
||||||
expect(elementWithBinding.hidden).toEqual(true);
|
expect(elementWithBinding.id).toEqual('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should consume directive watch expression change.', () => {
|
it('should consume directive watch expression change.', () => {
|
||||||
var elInj = view.elementInjectors[1];
|
var elInj = view.elementInjectors[1];
|
||||||
|
|
||||||
// TODO(rado): hook-up instantiateDirectives in implementation and
|
|
||||||
// remove from here.
|
|
||||||
elInj.instantiateDirectives(null);
|
|
||||||
expect(elInj.get(Directive).prop).toEqual('foo');
|
expect(elInj.get(Directive).prop).toEqual('foo');
|
||||||
var record = new Record(null, null);
|
var record = new Record(null, null);
|
||||||
var memento = new DirectivePropertyMemento(1, 0, 'prop',
|
var memento = new DirectivePropertyMemento(1, 0, 'prop',
|
||||||
|
@ -121,6 +116,64 @@ export function main() {
|
||||||
expect(elInj.get(Directive).prop).toEqual('bar');
|
expect(elInj.get(Directive).prop).toEqual('bar');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('integration view update with change detector', () => {
|
||||||
|
var view, cd, ctx;
|
||||||
|
function setUp(memento) {
|
||||||
|
var template = DOM.createTemplate(tempalteWithThreeTypesOfBindings);
|
||||||
|
|
||||||
|
var protoWatchGroup = new ProtoWatchGroup();
|
||||||
|
protoWatchGroup.watch(oneFieldAst('foo'), memento);
|
||||||
|
|
||||||
|
var pv = new ProtoView(template, [], templateElInj(),
|
||||||
|
protoWatchGroup, false);
|
||||||
|
|
||||||
|
ctx = new MyEvaluationContext();
|
||||||
|
view = pv.instantiate(ctx, null);
|
||||||
|
|
||||||
|
cd = new ChangeDetector(view.watchGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should consume text node changes', () => {
|
||||||
|
setUp(0);
|
||||||
|
|
||||||
|
ctx.foo = 'buz';
|
||||||
|
cd.detectChanges();
|
||||||
|
expect(view.textNodes[0].nodeValue).toEqual('buz');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should consume element binding changes', () => {
|
||||||
|
setUp(new ElementPropertyMemento(0, 'id'));
|
||||||
|
|
||||||
|
var elementWithBinding = view.bindElements[0];
|
||||||
|
expect(elementWithBinding.id).toEqual('');
|
||||||
|
|
||||||
|
ctx.foo = 'buz';
|
||||||
|
cd.detectChanges();
|
||||||
|
expect(elementWithBinding.id).toEqual('buz');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should consume directive watch expression change.', () => {
|
||||||
|
var memento = new DirectivePropertyMemento(1, 0, 'prop',
|
||||||
|
(o, v) => o.prop = v);
|
||||||
|
setUp(memento);
|
||||||
|
|
||||||
|
var elInj = view.elementInjectors[1];
|
||||||
|
expect(elInj.get(Directive).prop).toEqual('foo');
|
||||||
|
|
||||||
|
ctx.foo = 'buz';
|
||||||
|
cd.detectChanges();
|
||||||
|
expect(elInj.get(Directive).prop).toEqual('buz');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MyEvaluationContext {
|
||||||
|
@FIELD('foo')
|
||||||
|
constructor() {
|
||||||
|
this.foo = 'bar';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue