feat(elementBinder): introduce element binder.

It is a plain-old-data class to seperate the protoInjector from the
textNodes and elementBinding data.
This commit is contained in:
Rado Kirov 2014-10-30 14:41:19 -07:00
parent ec7e8534c2
commit 8c566dcfb5
8 changed files with 71 additions and 57 deletions

@ -8,7 +8,7 @@ export function run () {
var appInjector = new Injector([]); var appInjector = new Injector([]);
var bindings = [A, B, C]; var bindings = [A, B, C];
var proto = new ProtoElementInjector(null, bindings, [], false); var proto = new ProtoElementInjector(null, bindings);
for (var i = 0; i < ITERATIONS; ++i) { for (var i = 0; i < ITERATIONS; ++i) {
var ei = proto.instantiate({view:null}); var ei = proto.instantiate({view:null});
ei.instantiateDirectives(appInjector); ei.instantiateDirectives(appInjector);

@ -16,7 +16,7 @@ export function run () {
], false)]; ], false)];
var proto = new ProtoElementInjector(null, bindings, [], false); var proto = new ProtoElementInjector(null, bindings);
for (var i = 0; i < ITERATIONS; ++i) { for (var i = 0; i < ITERATIONS; ++i) {
var ei = proto.instantiate({view:null}); var ei = proto.instantiate({view:null});
ei.instantiateDirectives(appInjector); ei.instantiateDirectives(appInjector);
@ -39,4 +39,4 @@ class C {
constructor(a:A, b:B) { constructor(a:A, b:B) {
count++; count++;
} }
} }

@ -8,7 +8,7 @@ export function run () {
var appInjector = new Injector([]); var appInjector = new Injector([]);
var bindings = [A, B, C]; var bindings = [A, B, C];
var proto = new ProtoElementInjector(null, bindings, [], false); var proto = new ProtoElementInjector(null, bindings);
var ei = proto.instantiate({view:null}); var ei = proto.instantiate({view:null});
for (var i = 0; i < ITERATIONS; ++i) { for (var i = 0; i < ITERATIONS; ++i) {
@ -33,4 +33,4 @@ class C {
constructor(a:A, b:B) { constructor(a:A, b:B) {
count++; count++;
} }
} }

@ -0,0 +1,15 @@
import {ProtoElementInjector} from './element_injector';
import {FIELD} from 'facade/lang';
import {List} from 'facade/collection';
export class ElementBinder {
@FIELD('final protoElementInjector:ProtoElementInjector')
@FIELD('final textNodeIndices:List<int>')
@FIELD('final hasElementPropertyBindings:bool')
constructor(protoElementInjector: ProtoElementInjector,
textNodeIndices:List, hasElementPropertyBindings:boolean) {
this.protoElementInjector = protoElementInjector;
this.textNodeIndices = textNodeIndices;
this.hasElementPropertyBindings = hasElementPropertyBindings;
}
}

@ -146,10 +146,7 @@ export class ProtoElementInjector extends TreeNode {
@FIELD('_key7:int') @FIELD('_key7:int')
@FIELD('_key8:int') @FIELD('_key8:int')
@FIELD('_key9:int') @FIELD('_key9:int')
@FIELD('textNodeIndices:List<int>') constructor(parent:ProtoElementInjector, bindings:List) {
@FIELD('hasElementPropertyBindings:bool')
constructor(parent:ProtoElementInjector, bindings:List, textNodeIndices:List,
hasElementPropertyBindings:boolean) {
super(parent); super(parent);
this._elementInjector = null; this._elementInjector = null;
@ -180,9 +177,6 @@ export class ProtoElementInjector extends TreeNode {
if (length > 10) { if (length > 10) {
throw 'Maximum number of directives per element has been reached.'; throw 'Maximum number of directives per element has been reached.';
} }
this.textNodeIndices = textNodeIndices;
this.hasElementPropertyBindings = hasElementPropertyBindings;
} }
instantiate({view}):ElementInjector { instantiate({view}):ElementInjector {

@ -3,6 +3,7 @@ import {ListWrapper} from 'facade/collection';
import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detection/watch_group'; import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detection/watch_group';
import {Record} from 'change_detection/record'; import {Record} from 'change_detection/record';
import {ProtoElementInjector, ElementInjector} from './element_injector'; import {ProtoElementInjector, ElementInjector} from './element_injector';
import {ElementBinder} from './element_binder';
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';
@ -57,19 +58,16 @@ export class View {
export class ProtoView { export class ProtoView {
@FIELD('final _template:TemplateElement') @FIELD('final _template:TemplateElement')
@FIELD('final _bindings:List') @FIELD('final _elementBinders:List<ElementBinder>')
@FIELD('final _protoElementInjectors:List<ProtoElementInjector>')
@FIELD('final _protoWatchGroup:ProtoWatchGroup') @FIELD('final _protoWatchGroup:ProtoWatchGroup')
@FIELD('final _useRootElement:bool') @FIELD('final _useRootElement:bool')
constructor( constructor(
template:TemplateElement, template:TemplateElement,
bindings:List, elementBinders:List,
protoElementInjectors:List,
protoWatchGroup:ProtoWatchGroup, protoWatchGroup:ProtoWatchGroup,
useRootElement:boolean) { useRootElement:boolean) {
this._template = template; this._template = template;
this._bindings = bindings; this._elementBinders = elementBinders;
this._protoElementInjectors = protoElementInjectors;
this._protoWatchGroup = protoWatchGroup; this._protoWatchGroup = protoWatchGroup;
// not implemented // not implemented
@ -79,31 +77,32 @@ export class ProtoView {
instantiate(context, appInjector:Injector):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 binders = this._elementBinders;
/** /**
* TODO: vsavkin: benchmark * TODO: vsavkin: benchmark
* If this performs poorly, the five 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, binders);
var rootElementInjectors = ProtoView._rootElementInjectors(elementInjectors); var rootElementInjectors = ProtoView._rootElementInjectors(elementInjectors);
var textNodes = ProtoView._textNodes(elements, protos); var textNodes = ProtoView._textNodes(elements, binders);
var bindElements = ProtoView._bindElements(elements, protos); var bindElements = ProtoView._bindElements(elements, binders);
ProtoView._instantiateDirectives(elementInjectors, appInjector); ProtoView._instantiateDirectives(elementInjectors, appInjector);
return new View(fragment, elementInjectors, rootElementInjectors, textNodes, return new View(fragment, elementInjectors, rootElementInjectors, textNodes,
bindElements, this._protoWatchGroup, context); bindElements, this._protoWatchGroup, context);
} }
static _createElementInjectors(elements, protos) { static _createElementInjectors(elements, binders) {
var injectors = ListWrapper.createFixedSize(protos.length); var injectors = ListWrapper.createFixedSize(binders.length);
for (var i = 0; i < protos.length; ++i) { for (var i = 0; i < binders.length; ++i) {
injectors[i] = ProtoView._createElementInjector(elements[i], protos[i]); injectors[i] = ProtoView._createElementInjector(
elements[i], binders[i].protoElementInjector);
} }
// Cannot be rolled into loop above, because parentInjector pointers need // Cannot be rolled into loop above, because parentInjector pointers need
// to be set on the children. // to be set on the children.
for (var i = 0; i < protos.length; ++i) { for (var i = 0; i < binders.length; ++i) {
protos[i].clearElementInjector(); binders[i].protoElementInjector.clearElementInjector();
} }
return injectors; return injectors;
} }
@ -124,19 +123,19 @@ export class ProtoView {
return ListWrapper.filter(injectors, inj => isPresent(inj) && isBlank(inj.parent)); return ListWrapper.filter(injectors, inj => isPresent(inj) && isBlank(inj.parent));
} }
static _textNodes(elements, protos) { static _textNodes(elements, binders) {
var textNodes = []; var textNodes = [];
for (var i = 0; i < protos.length; ++i) { for (var i = 0; i < binders.length; ++i) {
ProtoView._collectTextNodes(textNodes, elements[i], ProtoView._collectTextNodes(textNodes, elements[i],
protos[i].textNodeIndices); binders[i].textNodeIndices);
} }
return textNodes; return textNodes;
} }
static _bindElements(elements, protos):List<Element> { static _bindElements(elements, binders):List<Element> {
var bindElements = []; var bindElements = [];
for (var i = 0; i < protos.length; ++i) { for (var i = 0; i < binders.length; ++i) {
if (protos[i].hasElementPropertyBindings) ListWrapper.push( if (binders[i].hasElementPropertyBindings) ListWrapper.push(
bindElements, elements[i]); bindElements, elements[i]);
} }
return bindElements; return bindElements;

@ -70,7 +70,7 @@ export function main() {
if (isBlank(appInjector)) appInjector = new Injector([]); if (isBlank(appInjector)) appInjector = new Injector([]);
if (isBlank(props)) props = {}; if (isBlank(props)) props = {};
var proto = new ProtoElementInjector(null, bindings, [], false); var proto = new ProtoElementInjector(null, bindings);
var inj = proto.instantiate({view: props["view"]}); var inj = proto.instantiate({view: props["view"]});
inj.instantiateDirectives(appInjector); inj.instantiateDirectives(appInjector);
return inj; return inj;
@ -79,12 +79,11 @@ export function main() {
function parentChildInjectors(parentBindings, childBindings) { function parentChildInjectors(parentBindings, childBindings) {
var inj = new Injector([]); var inj = new Injector([]);
var protoParent = new ProtoElementInjector(null, parentBindings, [], false); var protoParent = new ProtoElementInjector(null, parentBindings);
var parent = protoParent.instantiate({view: null}); var parent = protoParent.instantiate({view: null});
parent.instantiateDirectives(inj); parent.instantiateDirectives(inj);
var protoChild = new ProtoElementInjector(protoParent, childBindings, [], var protoChild = new ProtoElementInjector(protoParent, childBindings);
false);
var child = protoChild.instantiate({view: null}); var child = protoChild.instantiate({view: null});
child.instantiateDirectives(inj); child.instantiateDirectives(inj);
@ -94,9 +93,9 @@ export function main() {
describe("ElementInjector", function () { describe("ElementInjector", function () {
describe("proto injectors", function () { describe("proto injectors", function () {
it("should construct a proto tree", function () { it("should construct a proto tree", function () {
var p = new ProtoElementInjector(null, [], [], false); var p = new ProtoElementInjector(null, []);
var c1 = new ProtoElementInjector(p, [], [], false); var c1 = new ProtoElementInjector(p, []);
var c2 = new ProtoElementInjector(p, [], [], false); var c2 = new ProtoElementInjector(p, []);
expect(humanize(p, [ expect(humanize(p, [
[p, 'parent'], [p, 'parent'],
@ -108,9 +107,9 @@ export function main() {
describe("instantiate", function () { describe("instantiate", function () {
it("should create an element injector", function () { it("should create an element injector", function () {
var protoParent = new ProtoElementInjector(null, [], [], false); var protoParent = new ProtoElementInjector(null, []);
var protoChild1 = new ProtoElementInjector(protoParent, [], [], false); var protoChild1 = new ProtoElementInjector(protoParent, []);
var protoChild2 = new ProtoElementInjector(protoParent, [], [], false); var protoChild2 = new ProtoElementInjector(protoParent, []);
var p = protoParent.instantiate({view: null}); var p = protoParent.instantiate({view: null});
var c1 = protoChild1.instantiate({view: null}); var c1 = protoChild1.instantiate({view: null});
@ -126,12 +125,12 @@ export function main() {
describe("hasBindings", function () { describe("hasBindings", function () {
it("should be true when there are bindings", function () { it("should be true when there are bindings", function () {
var p = new ProtoElementInjector(null, [Directive], [], false); var p = new ProtoElementInjector(null, [Directive]);
expect(p.hasBindings).toBeTruthy(); expect(p.hasBindings).toBeTruthy();
}); });
it("should be false otherwise", function () { it("should be false otherwise", function () {
var p = new ProtoElementInjector(null, [], [], false); var p = new ProtoElementInjector(null, []);
expect(p.hasBindings).toBeFalsy(); expect(p.hasBindings).toBeFalsy();
}); });
}); });

@ -8,6 +8,7 @@ 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 {ImplicitReceiver, FieldRead} from 'change_detection/parser/ast';
import {ClosureMap} from 'change_detection/parser/closure_map'; import {ClosureMap} from 'change_detection/parser/closure_map';
import {ElementBinder} from 'core/compiler/element_binder';
class Directive { class Directive {
@FIELD('prop') @FIELD('prop')
@ -30,10 +31,15 @@ export function main() {
'</div>' + '</div>' +
'</section>'; '</section>';
function templateElInj() { function templateElementBinders() {
var sectionPI = new ProtoElementInjector(null, [], [0], false); var sectionPI = new ElementBinder(new ProtoElementInjector(null, []),
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false); [0], false);
var spanPI = new ProtoElementInjector(divPI, [], [], true);
var divPI = new ElementBinder(new ProtoElementInjector(
sectionPI.protoElementInjector, [Directive]), [], false);
var spanPI = new ElementBinder(new ProtoElementInjector(
divPI.protoElementInjector, []), [], true);
return [sectionPI, divPI, spanPI]; return [sectionPI, divPI, spanPI];
} }
@ -41,9 +47,8 @@ export function main() {
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 hasSingleRoot = false; var hasSingleRoot = false;
var pv = new ProtoView(template, diBindings, templateElInj(), var pv = new ProtoView(template, templateElementBinders(),
new ProtoWatchGroup(), hasSingleRoot); new ProtoWatchGroup(), hasSingleRoot);
var view = pv.instantiate(null, null); var view = pv.instantiate(null, null);
@ -68,10 +73,12 @@ export function main() {
'<div directive class="ng-binding"></div>' + '<div directive class="ng-binding"></div>' +
'</section>'); '</section>');
var sectionPI = new ProtoElementInjector(null, [Directive], [], false); var sectionPI = new ElementBinder(new ProtoElementInjector(
var divPI = new ProtoElementInjector(sectionPI, [Directive], [], false); null, [Directive]), [], false);
var divPI = new ElementBinder(new ProtoElementInjector(
sectionPI.protoElementInjector, [Directive]), [], false);
var pv = new ProtoView(template, [], [sectionPI, divPI], var pv = new ProtoView(template, [sectionPI, divPI],
new ProtoWatchGroup(), false); new ProtoWatchGroup(), false);
var view = pv.instantiate(null, null); var view = pv.instantiate(null, null);
@ -82,7 +89,7 @@ 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 pv = new ProtoView(template, templateElementBinders(),
new ProtoWatchGroup(), false); new ProtoWatchGroup(), false);
view = pv.instantiate(null, null); view = pv.instantiate(null, null);
}); });
@ -125,7 +132,7 @@ export function main() {
var protoWatchGroup = new ProtoWatchGroup(); var protoWatchGroup = new ProtoWatchGroup();
protoWatchGroup.watch(oneFieldAst('foo'), memento); protoWatchGroup.watch(oneFieldAst('foo'), memento);
var pv = new ProtoView(template, [], templateElInj(), var pv = new ProtoView(template, templateElementBinders(),
protoWatchGroup, false); protoWatchGroup, false);
ctx = new MyEvaluationContext(); ctx = new MyEvaluationContext();