design: view instantiation test
This commit is contained in:
parent
2d19e7122b
commit
a9896ed391
|
@ -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):
|
di.Injector (di.Module):
|
||||||
- imperative based (can create child injectors imperativly)
|
- imperative based (can create child injectors imperativly)
|
||||||
|
@ -16,14 +16,47 @@ ElementInjector (ElementModule):
|
||||||
- Fast
|
- Fast
|
||||||
- Query mechanism for children
|
- Query mechanism for children
|
||||||
- 1:1 to DOM structure.
|
- 1:1 to DOM structure.
|
||||||
|
|
||||||
|
PERF BENCHMARK: http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class ProtoElementInjector {
|
export class ProtoElementInjector {
|
||||||
@FIELD('final _parent:ProtoElementInjector')
|
/**
|
||||||
/// Temporory instance while instantiating
|
parent:ProtoDirectiveInjector;
|
||||||
@FIELD('_clone:ElementInjector')
|
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) {
|
constructor(parent:ProtoElementInjector) {
|
||||||
this._parent = parent;
|
this.hasProperties = false;
|
||||||
|
this.textNodes = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
instantiate():ElementInjector {
|
instantiate():ElementInjector {
|
||||||
|
@ -32,6 +65,48 @@ export class ProtoElementInjector {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ElementInjector {
|
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')
|
@FIELD('final protoInjector:ProtoElementInjector')
|
||||||
constructor(protoInjector:ProtoElementInjector) {
|
constructor(protoInjector:ProtoElementInjector) {
|
||||||
this.protoInjector = protoInjector;
|
this.protoInjector = protoInjector;
|
||||||
|
|
|
@ -13,7 +13,7 @@ export class Selector {
|
||||||
* @param elementName Name of the element
|
* @param elementName Name of the element
|
||||||
* @param attributes Attributes on 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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {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';
|
||||||
|
@ -8,56 +8,66 @@ import {SetterFn} from 'change_detection/facade';
|
||||||
import {FIELD, IMPLEMENTS} from 'facade/lang';
|
import {FIELD, IMPLEMENTS} from 'facade/lang';
|
||||||
import {List} from 'facade/collection';
|
import {List} from 'facade/collection';
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Const of making objects: http://jsperf.com/instantiate-size-of-object
|
||||||
|
*/
|
||||||
@IMPLEMENTS(WatchGroupDispatcher)
|
@IMPLEMENTS(WatchGroupDispatcher)
|
||||||
export class View {
|
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
|
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
|
||||||
@FIELD('final _rootElementInjectors:List<ElementInjector>')
|
@FIELD('final rootElementInjectors:List<ElementInjector>')
|
||||||
@FIELD('final _elementInjectors:List<ElementInjector>')
|
@FIELD('final elementInjectors:List<ElementInjector>')
|
||||||
@FIELD('final _textNodes:List<Text>')
|
@FIELD('final bindElements:List<Element>')
|
||||||
@FIELD('final _watchGroup:WatchGroup')
|
@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
|
/// When the view is part of render tree, the DocumentFragment is empty, which is why we need
|
||||||
/// to keep track of the nodes.
|
/// to keep track of the nodes.
|
||||||
@FIELD('final _nodes:List<Node>')
|
@FIELD('final nodes:List<Node>')
|
||||||
@FIELD('final _onChangeDispatcher:OnChangeDispatcher')
|
@FIELD('final onChangeDispatcher:OnChangeDispatcher')
|
||||||
constructor(fragment:DocumentFragment) {
|
constructor(fragment:DocumentFragment) {
|
||||||
this._fragment = fragment;
|
this.fragment = fragment;
|
||||||
this._nodes = ListWrapper.clone(fragment.childNodes);
|
this.nodes = ListWrapper.clone(fragment.childNodes);
|
||||||
this._onChangeDispatcher = null;
|
this.onChangeDispatcher = null;
|
||||||
this._elementInjectors = null;
|
this.elementInjectors = null;
|
||||||
this._textNodes = null;
|
this.textNodes = null;
|
||||||
|
this.bindElements = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
onRecordChange(record:Record, target) {
|
onRecordChange(record:Record, target) {
|
||||||
// dispatch to element injector or text nodes based on context
|
// dispatch to element injector or text nodes based on context
|
||||||
if (target instanceof ElementInjectorTarget) {
|
if (target instanceof DirectivePropertyMemento) {
|
||||||
// we know that it is ElementInjectorTarget
|
// we know that it is DirectivePropertyMemento
|
||||||
var eTarget:ElementInjectorTarget = target;
|
var directiveMemento:DirectivePropertyMemento = target;
|
||||||
this._onChangeDispatcher.notify(this, eTarget);
|
directiveMemento.invoke(record, this.elementInjectors);
|
||||||
eTarget.invoke(record, this._elementInjectors);
|
} else if (target instanceof ElementPropertyMemento) {
|
||||||
|
var elementMemento:ElementPropertyMemento = target;
|
||||||
|
elementMemento.invoke(record, this.bindElements);
|
||||||
} else {
|
} else {
|
||||||
// we know it refferst to _textNodes.
|
// we know it refers to _textNodes.
|
||||||
var textNodeIndex:number = target;
|
var textNodeIndex:number = target;
|
||||||
DOM.setText(this._textNodes[textNodeIndex], record.currentValue);
|
DOM.setText(this.textNodes[textNodeIndex], record.currentValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProtoView {
|
export class ProtoView {
|
||||||
@FIELD('final _template:TemplateElement')
|
@FIELD('final _template:TemplateElement')
|
||||||
@FIELD('final _module:Module')
|
@FIELD('final _module:Module')
|
||||||
@FIELD('final _protoElementInjectors:List<ProtoElementInjector>')
|
@FIELD('final _protoElementInjectors:List<ProtoElementInjector>')
|
||||||
@FIELD('final _protoWatchGroup:ProtoWatchGroup')
|
@FIELD('final _protoWatchGroup:ProtoWatchGroup')
|
||||||
|
@FIELD('final _useRootElement:bool')
|
||||||
constructor(
|
constructor(
|
||||||
template:TemplateElement,
|
template:TemplateElement,
|
||||||
module:Module,
|
module:Module,
|
||||||
protoElementInjector:ProtoElementInjector,
|
protoElementInjector:List<ProtoElementInjector>,
|
||||||
protoWatchGroup:ProtoWatchGroup)
|
protoWatchGroup:ProtoWatchGroup,
|
||||||
|
useRootElement:bool)
|
||||||
{
|
{
|
||||||
this._template = template;
|
this._template = template;
|
||||||
this._module = module;
|
this._module = module;
|
||||||
this._protoElementInjectors = protoElementInjector;
|
this._protoElementInjectors = protoElementInjector;
|
||||||
this._protoWatchGroup = protoWatchGroup;
|
this._protoWatchGroup = protoWatchGroup;
|
||||||
|
this._useRootElement = useRootElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
instantiate():View {
|
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 _elementInjectorIndex:int')
|
||||||
@FIELD('final _directiveIndex:int')
|
@FIELD('final _directiveIndex:int')
|
||||||
@FIELD('final _setterName:String')
|
@FIELD('final _setterName:String')
|
||||||
|
@ -97,13 +120,13 @@ export class ElementInjectorTarget {
|
||||||
export class OnChangeDispatcher {
|
export class OnChangeDispatcher {
|
||||||
|
|
||||||
@FIELD('_lastView:View')
|
@FIELD('_lastView:View')
|
||||||
@FIELD('_lastTarget:ElementInjectorTarget')
|
@FIELD('_lastTarget:DirectivePropertyMemento')
|
||||||
constructor() {
|
constructor() {
|
||||||
this._lastView = null;
|
this._lastView = null;
|
||||||
this._lastTarget = null;
|
this._lastTarget = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
notify(view:View, eTarget:ElementInjectorTarget) {
|
notify(view:View, eTarget:DirectivePropertyMemento) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {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() {
|
export function main() {
|
||||||
describe('view', function() {
|
describe('view', function() {
|
||||||
describe('ProtoView', function() {
|
describe('ProtoView', function() {
|
||||||
it('should create an instance of view', function() {
|
it('should create an instance of view', function() {
|
||||||
var template = DOM.createTemplate('Hello <b>world</b>!');
|
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();
|
var view:View = pv.instantiate();
|
||||||
expect(view instanceof View).toBe(true);
|
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]]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
library angular.core.facade.dom;
|
library angular.core.facade.dom;
|
||||||
|
|
||||||
import 'dart:html';
|
import 'dart:html';
|
||||||
|
import 'dart:js' show JsObject;
|
||||||
|
|
||||||
export 'dart:html' show DocumentFragment, Node, Element, TemplateElement, Text;
|
export 'dart:html' show DocumentFragment, Node, Element, TemplateElement, Text;
|
||||||
|
|
||||||
|
@ -28,4 +29,13 @@ class DOM {
|
||||||
static clone(Node node) {
|
static clone(Node node) {
|
||||||
return node.clone(true);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,4 +28,13 @@ export class DOM {
|
||||||
static clone(node:Node) {
|
static clone(node:Node) {
|
||||||
return node.cloneNode(true);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,3 +57,4 @@ class NumberWrapper {
|
||||||
return double.parse(text);
|
return double.parse(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ export class Parser extends TraceurParser {
|
||||||
parseTypeName_() {
|
parseTypeName_() {
|
||||||
// Copy of original implementation
|
// Copy of original implementation
|
||||||
var typeName = super.parseTypeName_();
|
var typeName = super.parseTypeName_();
|
||||||
var next = this.peekType_();
|
|
||||||
// Generics support
|
// Generics support
|
||||||
if (this.eatIf_(OPEN_ANGLE)) {
|
if (this.eatIf_(OPEN_ANGLE)) {
|
||||||
var generics = [];
|
var generics = [];
|
||||||
|
@ -34,4 +33,4 @@ export class Parser extends TraceurParser {
|
||||||
} while (this.eatIf_(COMMA));
|
} while (this.eatIf_(COMMA));
|
||||||
this.eat_(CLOSE_CURLY);
|
this.eat_(CLOSE_CURLY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue