design: view instantiation test

This commit is contained in:
Misko Hevery 2014-10-10 20:44:55 -07:00
parent 2d19e7122b
commit a9896ed391
8 changed files with 191 additions and 40 deletions

View File

@ -2,7 +2,7 @@ import {FIELD} from 'facade/lang';
/**
Difference beteween di.Injector and ElementInjector
Difference between di.Injector and ElementInjector
di.Injector (di.Module):
- imperative based (can create child injectors imperativly)
@ -16,14 +16,47 @@ ElementInjector (ElementModule):
- Fast
- Query mechanism for children
- 1:1 to DOM structure.
PERF BENCHMARK: http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/
*/
export class ProtoElementInjector {
@FIELD('final _parent:ProtoElementInjector')
/// Temporory instance while instantiating
@FIELD('_clone:ElementInjector')
/**
parent:ProtoDirectiveInjector;
next:ProtoDirectiveInjector;
prev:ProtoDirectiveInjector;
head:ProtoDirectiveInjector;
tail:ProtoDirectiveInjector;
DirectiveInjector cloneingInstance;
KeyMap keyMap;
/// Because DI tree is sparse, this shows how far away is the Parent DI
parentDistance:int = 1; /// 1 for non-sparse/normal depth.
cKey:int; cFactory:Function; cParams:List<int>;
keyId0:int; factory0:Function; params0:List<int>;
keyId1:int; factory1:Function; params1:List<int>;
keyId2:int; factory2:Function; params2:List<int>;
keyId3:int; factory3:Function; params3:List<int>;
keyId4:int; factory4:Function; params4:List<int>;
keyId5:int; factory5:Function; params5:List<int>;
keyId6:int; factory6:Function; params6:List<int>;
keyId7:int; factory7:Function; params7:List<int>;
keyId8:int; factory8:Function; params8:List<int>;
keyId9:int; factory9:Function; params9:List<int>;
queryKeyId0:int;
queryKeyId1:int;
textNodes:List<int>;
hasProperties:bool;
events:Map<string, Expression>;
elementInjector:ElementInjector;
*/
constructor(parent:ProtoElementInjector) {
this._parent = parent;
this.hasProperties = false;
this.textNodes = null;
}
instantiate():ElementInjector {
@ -32,6 +65,48 @@ export class ProtoElementInjector {
}
export class ElementInjector {
/*
_protoInjector:ProtoElementInjector;
injector:Injector;
_parent:ElementInjector;
_next:ElementInjector;
_prev:ElementInjector;
_head:ElementInjector;
_tail:ElementInjector;
// For performance reasons the Injector only supports 10 directives per element.
// NOTE: linear search over fields is faster than HashMap lookup.
_cObj; // Component only
_obj0;
_obj1;
_obj2;
_obj3;
_obj4;
_obj5;
_obj6;
_obj7;
_obj8;
_obj9;
element:Element;
ngElement:NgElement;
shadowRoot:ShadowRoot;
elementProbe:ElementProbe;
view:View;
viewPort:ViewPort;
viewFactory:ViewFactory;
animate:Animate;
destinationLightDom:DestinationLightDom;
sourceLightDom:SourceLightDom;
// For performance reasons the Injector only supports 2 [Query] per element.
// NOTE: linear search over fields is faster than HashMap lookup.
_query0:Query;
_query1:Query;
*/
@FIELD('final protoInjector:ProtoElementInjector')
constructor(protoInjector:ProtoElementInjector) {
this.protoInjector = protoInjector;

View File

@ -13,7 +13,7 @@ export class Selector {
* @param elementName Name of the element
* @param attributes Attributes on the Element.
*/
visitElement(elementName:String, attributes:Map<string, string>):List<AnnotatedType> {
visitElement(elementName:string, attributes:Map<string, string>):List<AnnotatedType> {
return null;
}
}

View File

@ -1,4 +1,4 @@
import {DOM, Node, DocumentFragment, TemplateElement} from 'facade/dom';
import {DOM, Element, Node, Text, DocumentFragment, TemplateElement} from 'facade/dom';
import {ListWrapper} from 'facade/collection';
import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detection/watch_group';
import {Record} from 'change_detection/record';
@ -8,56 +8,66 @@ import {SetterFn} from 'change_detection/facade';
import {FIELD, IMPLEMENTS} from 'facade/lang';
import {List} from 'facade/collection';
/***
* Const of making objects: http://jsperf.com/instantiate-size-of-object
*/
@IMPLEMENTS(WatchGroupDispatcher)
export class View {
@FIELD('final _fragment:DocumentFragment')
@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')
@FIELD('final rootElementInjectors:List<ElementInjector>')
@FIELD('final elementInjectors:List<ElementInjector>')
@FIELD('final bindElements:List<Element>')
@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')
@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;
this.fragment = fragment;
this.nodes = ListWrapper.clone(fragment.childNodes);
this.onChangeDispatcher = null;
this.elementInjectors = null;
this.textNodes = null;
this.bindElements = 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);
if (target instanceof DirectivePropertyMemento) {
// we know that it is DirectivePropertyMemento
var directiveMemento:DirectivePropertyMemento = target;
directiveMemento.invoke(record, this.elementInjectors);
} else if (target instanceof ElementPropertyMemento) {
var elementMemento:ElementPropertyMemento = target;
elementMemento.invoke(record, this.bindElements);
} else {
// we know it refferst to _textNodes.
// we know it refers to _textNodes.
var textNodeIndex:number = target;
DOM.setText(this._textNodes[textNodeIndex], record.currentValue);
DOM.setText(this.textNodes[textNodeIndex], record.currentValue);
}
}
}
export class ProtoView {
@FIELD('final _template:TemplateElement')
@FIELD('final _module:Module')
@FIELD('final _protoElementInjectors:List<ProtoElementInjector>')
@FIELD('final _protoWatchGroup:ProtoWatchGroup')
@FIELD('final _template:TemplateElement')
@FIELD('final _module:Module')
@FIELD('final _protoElementInjectors:List<ProtoElementInjector>')
@FIELD('final _protoWatchGroup:ProtoWatchGroup')
@FIELD('final _useRootElement:bool')
constructor(
template:TemplateElement,
module:Module,
protoElementInjector:ProtoElementInjector,
protoWatchGroup:ProtoWatchGroup)
protoElementInjector:List<ProtoElementInjector>,
protoWatchGroup:ProtoWatchGroup,
useRootElement:bool)
{
this._template = template;
this._module = module;
this._protoElementInjectors = protoElementInjector;
this._protoWatchGroup = protoWatchGroup;
this._useRootElement = useRootElement;
}
instantiate():View {
@ -65,8 +75,21 @@ export class ProtoView {
}
}
export class ElementPropertyMemento {
@FIELD('final _elementIndex:int')
@FIELD('final _propertyIndex:string')
constructor(elementIndex:int, propertyName:string) {
this._elementIndex = elementIndex;
this._propertyName = propertyName;
}
export class ElementInjectorTarget {
invoke(record:Record, elementInjectors:List<Element>) {
var element:Element = elementInjectors[this._elementIndex];
DOM.setProperty(element, this._propertyName, record.currentValue);
}
}
export class DirectivePropertyMemento {
@FIELD('final _elementInjectorIndex:int')
@FIELD('final _directiveIndex:int')
@FIELD('final _setterName:String')
@ -97,13 +120,13 @@ export class ElementInjectorTarget {
export class OnChangeDispatcher {
@FIELD('_lastView:View')
@FIELD('_lastTarget:ElementInjectorTarget')
@FIELD('_lastTarget:DirectivePropertyMemento')
constructor() {
this._lastView = null;
this._lastTarget = null;
}
notify(view:View, eTarget:ElementInjectorTarget) {
notify(view:View, eTarget:DirectivePropertyMemento) {
}

View File

@ -1,16 +1,50 @@
import {describe, it, expect} from 'test_lib/test_lib';
import {describe, xit, it, expect} from 'test_lib/test_lib';
import {ProtoWatchGroup} from 'change_detection/watch_group';
import {ProtoView, View} from 'core/compiler/view';
import {DOM} from 'facade/dom';
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
import {DOM, Element} from 'facade/dom';
import {Module} from 'di/di';
export function main() {
describe('view', function() {
describe('ProtoView', function() {
it('should create an instance of view', function() {
var template = DOM.createTemplate('Hello <b>world</b>!');
var pv = new ProtoView(template, null, null, null);
var pv = new ProtoView(template, null, null, null, false);
var view:View = pv.instantiate();
expect(view instanceof View).toBe(true);
});
xit('should create view instance and locate basic parts', function() {
var template = DOM.createTemplate(
'<section class="ng-binding" no-injector>' +
'Hello {}!' +
'<div directive class="ng-binding" injector>' +
'<span class="ng-binding" [hidden]="exp" no-injector>don\'t show me</span>' +
'</div>' +
'</section>');
var module:Module = null;
var sectionPI = new ProtoElementInjector(null);
sectionPI.textNodes = [0];
var divPI = new ProtoElementInjector(null);
var spanPI = new ProtoElementInjector(null);
spanPI.hasProperties = true;
var protoElementInjector:List<ProtoElementInjector> = [sectionPI, divPI, spanPI];
var protoWatchGroup:ProtoWatchGroup = null;
var hasSingleRoot:bool = false;
var pv = new ProtoView(template, module, protoElementInjector, protoWatchGroup, hasSingleRoot);
var view:View = pv.instantiate();
var section:Element = template.content.firstChild;
var div:Element = DOM.getElementsByTagName(section, 'div');
var span:Element = DOM.getElementsByTagName(div, 'span');
expect(DOM.getInnerHTML(view.fragment)).toEqual(DOM.getInnerHTML(section)); // exclude top level <section>
expect(view.nodes).toEqual([view.fragment.firstChild.childNodes]); // TextNode(Hello...), <div>
var elementInjector:ElementInjector = view.elementInjectors[1];
expect(view.elementInjectors).toEqual([null, elementInjector, null]); // only second one has directive
expect(view.bindElements).toEqual([span]);
expect(view.textNodes).toEqual([section.childNodes[0]]);
});
});
});
}

View File

@ -1,6 +1,7 @@
library angular.core.facade.dom;
import 'dart:html';
import 'dart:js' show JsObject;
export 'dart:html' show DocumentFragment, Node, Element, TemplateElement, Text;
@ -28,4 +29,13 @@ class DOM {
static clone(Node node) {
return node.clone(true);
}
static setProperty(Element element, String name, value) {
new JsObject.fromBrowserObject(element)[name] = value;
}
static getElementsByClassName(Element element, String name) {
return element.getElementsByClassName(name);
}
static getElementsByTagName(Element element, String name) {
return element.querySelectorAll(name);
}
}

View File

@ -28,4 +28,13 @@ export class DOM {
static clone(node:Node) {
return node.cloneNode(true);
}
static setProperty(element:Element, name:string, value) {
element[name] = value;
}
static getElementsByClassName(element:Element, name:string) {
return element.getElementsByClassName(name);
}
static getElementsByTagName(element:Element, name:string) {
return element.getElementsByTagName(name);
}
}

View File

@ -57,3 +57,4 @@ class NumberWrapper {
return double.parse(text);
}
}

View File

@ -11,7 +11,6 @@ export class Parser extends TraceurParser {
parseTypeName_() {
// Copy of original implementation
var typeName = super.parseTypeName_();
var next = this.peekType_();
// Generics support
if (this.eatIf_(OPEN_ANGLE)) {
var generics = [];
@ -34,4 +33,4 @@ export class Parser extends TraceurParser {
} while (this.eatIf_(COMMA));
this.eat_(CLOSE_CURLY);
}
}
}