perf(view): inline and refactor view instantiation and hydration

- Don’t convert DOM array into JS array via `Array.slice`
- Inline static methods for instantiation and hydration
- Misc cleanup

Closes #291
This commit is contained in:
Tobias Bosch 2014-12-09 10:31:19 -08:00
parent 8acf9fb609
commit 3ec3d5e084
11 changed files with 223 additions and 254 deletions

View File

@ -54,7 +54,7 @@ export function documentDependentBindings(appComponentType) {
// The light Dom of the app element is not considered part of // The light Dom of the app element is not considered part of
// the angular application. Thus the context and lightDomInjector are // the angular application. Thus the context and lightDomInjector are
// empty. // empty.
var view = appProtoView.instantiate(null, true); var view = appProtoView.instantiate(null);
view.hydrate(injector, null, new Object()); view.hydrate(injector, null, new Object());
return view; return view;
}); });

View File

@ -1,5 +1,5 @@
import {int, isPresent, isBlank, Type, BaseException, stringify} from 'facade/lang'; import {int, isPresent, isBlank, Type, BaseException, stringify} from 'facade/lang';
import {Element} from 'facade/dom'; import {Element, DOM} from 'facade/dom';
import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'facade/collection'; import {ListWrapper, List, MapWrapper, StringMapWrapper} from 'facade/collection';
import {reflector} from 'reflection/reflection'; import {reflector} from 'reflection/reflection';
@ -74,7 +74,9 @@ export class ElementBinderBuilder extends CompileStep {
_bindElementProperties(protoView, compileElement) { _bindElementProperties(protoView, compileElement) {
MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => { MapWrapper.forEach(compileElement.propertyBindings, (expression, property) => {
protoView.bindElementProperty(property, expression); if (DOM.hasProperty(compileElement.element, property)) {
protoView.bindElementProperty(expression.ast, property, reflector.setter(property));
}
}); });
} }

View File

@ -16,6 +16,9 @@ import {OnChange} from './interfaces';
import {ContextWithVariableBindings} from 'change_detection/parser/context_with_variable_bindings'; import {ContextWithVariableBindings} from 'change_detection/parser/context_with_variable_bindings';
const NG_BINDING_CLASS = 'ng-binding'; const NG_BINDING_CLASS = 'ng-binding';
const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
// TODO(tbosch): Cannot use `const` because of Dart.
var NO_FORMATTERS = MapWrapper.create();
/** /**
* Const of making objects: http://jsperf.com/instantiate-size-of-object * Const of making objects: http://jsperf.com/instantiate-size-of-object
@ -36,34 +39,36 @@ export class View {
preBuiltObjects: List<PreBuiltObjects>; preBuiltObjects: List<PreBuiltObjects>;
proto: ProtoView; proto: ProtoView;
context: Object; context: Object;
_localBindings: Map; contextWithLocals:ContextWithVariableBindings;
constructor(proto:ProtoView, nodes:List<Node>, elementInjectors:List, constructor(proto:ProtoView, nodes:List<Node>, protoRecordRange:ProtoRecordRange, protoContextLocals:Map) {
rootElementInjectors:List, textNodes:List, bindElements:List,
protoRecordRange:ProtoRecordRange) {
this.proto = proto; this.proto = proto;
this.nodes = nodes; this.nodes = nodes;
this.elementInjectors = elementInjectors; this.recordRange = protoRecordRange.instantiate(this, NO_FORMATTERS);
this.rootElementInjectors = rootElementInjectors; this.elementInjectors = null;
this.textNodes = textNodes; this.rootElementInjectors = null;
this.bindElements = bindElements; this.textNodes = null;
this.recordRange = protoRecordRange.instantiate(this, MapWrapper.create()); this.bindElements = null;
this.componentChildViews = null; this.componentChildViews = null;
this.viewPorts = null; this.viewPorts = null;
this.preBuiltObjects = null; this.preBuiltObjects = null;
this.context = null; this.context = null;
// used to persist the locals part of context inbetween hydrations. // contextWithLocals
this._localBindings = null; if (MapWrapper.size(protoContextLocals) > 0) {
if (isPresent(this.proto) && MapWrapper.size(this.proto.variableBindings) > 0) { this.contextWithLocals = new ContextWithVariableBindings(null, MapWrapper.clone(protoContextLocals));
this._createLocalContext(); } else {
this.contextWithLocals = null;
} }
} }
_createLocalContext() { init(elementInjectors:List, rootElementInjectors:List, textNodes: List, bindElements:List, viewPorts:List, preBuiltObjects:List, componentChildViews:List) {
this._localBindings = MapWrapper.create(); this.elementInjectors = elementInjectors;
for (var [ctxName, tmplName] of MapWrapper.iterable(this.proto.variableBindings)) { this.rootElementInjectors = rootElementInjectors;
MapWrapper.set(this._localBindings, tmplName, null); this.textNodes = textNodes;
} this.bindElements = bindElements;
this.viewPorts = viewPorts;
this.preBuiltObjects = preBuiltObjects;
this.componentChildViews = componentChildViews;
} }
setLocal(contextName: string, value) { setLocal(contextName: string, value) {
@ -81,16 +86,21 @@ export class View {
} }
_hydrateContext(newContext) { _hydrateContext(newContext) {
if (isPresent(this._localBindings)) { if (isPresent(this.contextWithLocals)) {
newContext = new ContextWithVariableBindings(newContext, this._localBindings); this.contextWithLocals.parent = newContext;
} this.context = this.contextWithLocals;
this.recordRange.setContext(newContext); } else {
this.context = newContext; this.context = newContext;
} }
// TODO(tbosch): if we have a contextWithLocals we actually only need to
// set the contextWithLocals once. Would it be faster to always use a contextWithLocals
// even if we don't have locals and not update the recordRange here?
this.recordRange.setContext(this.context);
}
_dehydrateContext() { _dehydrateContext() {
if (isPresent(this._localBindings)) { if (isPresent(this.contextWithLocals)) {
this.context.clearValues(); this.contextWithLocals.clearValues();
} }
this.context = null; this.context = null;
} }
@ -113,54 +123,67 @@ export class View {
*/ */
hydrate(appInjector: Injector, hostElementInjector: ElementInjector, hydrate(appInjector: Injector, hostElementInjector: ElementInjector,
context: Object) { context: Object) {
if (isBlank(this.preBuiltObjects)) { if (this.hydrated()) throw new BaseException('The view is already hydrated.');
throw new BaseException('Cannot hydrate a view without pre-built objects.');
}
this._hydrateContext(context); this._hydrateContext(context);
var shadowDomAppInjectors = View._createShadowDomInjectors( // viewPorts
this.proto, appInjector); for (var i = 0; i < this.viewPorts.length; i++) {
this.viewPorts[i].hydrate(appInjector, hostElementInjector);
}
this._hydrateViewPorts(appInjector, hostElementInjector); var binders = this.proto.elementBinders;
this._instantiateDirectives(appInjector, shadowDomAppInjectors); var componentChildViewIndex = 0;
this._hydrateChildComponentViews(appInjector, shadowDomAppInjectors); for (var i = 0; i < binders.length; ++i) {
var componentDirective = binders[i].componentDirective;
var shadowDomAppInjector = null;
// shadowDomAppInjector
if (isPresent(componentDirective)) {
var services = componentDirective.annotation.componentServices;
if (isPresent(services))
shadowDomAppInjector = appInjector.createChild(services);
else {
shadowDomAppInjector = appInjector;
}
} else {
shadowDomAppInjector = null;
}
// elementInjectors
var elementInjector = this.elementInjectors[i];
if (isPresent(elementInjector)) {
elementInjector.instantiateDirectives(appInjector, shadowDomAppInjector, this.preBuiltObjects[i]);
}
// componentChildViews
if (isPresent(shadowDomAppInjector)) {
this.componentChildViews[componentChildViewIndex++].hydrate(shadowDomAppInjector,
elementInjector, elementInjector.getComponent());
}
}
} }
dehydrate() { dehydrate() {
// preserve the opposite order of the hydration process. // Note: preserve the opposite order of the hydration process.
if (isPresent(this.componentChildViews)) {
// componentChildViews
for (var i = 0; i < this.componentChildViews.length; i++) { for (var i = 0; i < this.componentChildViews.length; i++) {
this.componentChildViews[i].dehydrate(); this.componentChildViews[i].dehydrate();
} }
}
// elementInjectors
for (var i = 0; i < this.elementInjectors.length; i++) { for (var i = 0; i < this.elementInjectors.length; i++) {
this.elementInjectors[i].clearDirectives(); this.elementInjectors[i].clearDirectives();
} }
// viewPorts
if (isPresent(this.viewPorts)) { if (isPresent(this.viewPorts)) {
for (var i = 0; i < this.viewPorts.length; i++) { for (var i = 0; i < this.viewPorts.length; i++) {
this.viewPorts[i].dehydrate(); this.viewPorts[i].dehydrate();
} }
} }
this._dehydrateContext();
}
static _createShadowDomInjectors(protoView, defaultInjector) { this._dehydrateContext();
var binders = protoView.elementBinders;
var shadowDomAppInjectors = ListWrapper.createFixedSize(binders.length);
for (var i = 0; i < binders.length; ++i) {
var componentDirective = binders[i].componentDirective;
if (isPresent(componentDirective)) {
var services = componentDirective.annotation.componentServices;
if (isPresent(services))
shadowDomAppInjectors[i] = defaultInjector.createChild(services);
else {
shadowDomAppInjectors[i] = defaultInjector;
}
} else {
shadowDomAppInjectors[i] = null;
}
}
return shadowDomAppInjectors;
} }
onRecordChange(groupMemento, records:List<Record>) { onRecordChange(groupMemento, records:List<Record>) {
@ -211,48 +234,6 @@ export class View {
} }
return changes; return changes;
} }
addViewPort(viewPort: ViewPort) {
if (isBlank(this.viewPorts)) this.viewPorts = [];
ListWrapper.push(this.viewPorts, viewPort);
}
addComponentChildView(childView: View) {
if (isBlank(this.componentChildViews)) this.componentChildViews = [];
ListWrapper.push(this.componentChildViews, childView);
this.recordRange.addRange(childView.recordRange);
}
_instantiateDirectives(
lightDomAppInjector: Injector, shadowDomAppInjectors) {
for (var i = 0; i < this.elementInjectors.length; ++i) {
var injector = this.elementInjectors[i];
if (injector != null) {
injector.instantiateDirectives(
lightDomAppInjector, shadowDomAppInjectors[i], this.preBuiltObjects[i]);
}
}
}
_hydrateViewPorts(appInjector, hostElementInjector) {
if (isBlank(this.viewPorts)) return;
for (var i = 0; i < this.viewPorts.length; i++) {
this.viewPorts[i].hydrate(appInjector, hostElementInjector);
}
}
_hydrateChildComponentViews(appInjector, shadowDomAppInjectors) {
var count = 0;
for (var i = 0; i < shadowDomAppInjectors.length; i++) {
var shadowDomInjector = shadowDomAppInjectors[i];
var injector = this.elementInjectors[i];
// replace with protoView.binder.
if (isPresent(shadowDomAppInjectors[i])) {
this.componentChildViews[count++].hydrate(shadowDomInjector,
injector, injector.getComponent());
}
}
}
} }
export class ProtoView { export class ProtoView {
@ -260,64 +241,129 @@ export class ProtoView {
elementBinders:List<ElementBinder>; elementBinders:List<ElementBinder>;
protoRecordRange:ProtoRecordRange; protoRecordRange:ProtoRecordRange;
variableBindings: Map; variableBindings: Map;
protoContextLocals:Map;
textNodesWithBindingCount:int; textNodesWithBindingCount:int;
elementsWithBindingCount:int; elementsWithBindingCount:int;
instantiateInPlace:boolean;
rootBindingOffset:int;
isTemplateElement:boolean;
constructor( constructor(
template:Element, template:Element,
protoRecordRange:ProtoRecordRange) { protoRecordRange:ProtoRecordRange) {
this.element = template; this.element = template;
this.elementBinders = []; this.elementBinders = [];
this.variableBindings = MapWrapper.create(); this.variableBindings = MapWrapper.create();
this.protoContextLocals = MapWrapper.create();
this.protoRecordRange = protoRecordRange; this.protoRecordRange = protoRecordRange;
this.textNodesWithBindingCount = 0; this.textNodesWithBindingCount = 0;
this.elementsWithBindingCount = 0; this.elementsWithBindingCount = 0;
this.instantiateInPlace = false;
if (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) {
this.rootBindingOffset = 1;
} else {
this.rootBindingOffset = 0;
}
this.isTemplateElement = this.element instanceof TemplateElement;
} }
// TODO(rado): hostElementInjector should be moved to hydrate phase. // TODO(rado): hostElementInjector should be moved to hydrate phase.
// TODO(rado): inPlace is only used for bootstrapping, invastigate whether we can bootstrap without instantiate(hostElementInjector: ElementInjector):View {
// rootProtoView. var rootElementClone = this.instantiateInPlace ? this.element : DOM.clone(this.element);
instantiate(hostElementInjector: ElementInjector, inPlace:boolean = false):View { var elementsWithBindings;
var clone = inPlace ? this.element : DOM.clone(this.element); if (this.isTemplateElement) {
var elements; elementsWithBindings = DOM.querySelectorAll(rootElementClone.content, NG_BINDING_CLASS_SELECTOR);
if (clone instanceof TemplateElement) {
elements = ListWrapper.clone(DOM.querySelectorAll(clone.content, `.${NG_BINDING_CLASS}`));
} else { } else {
elements = ListWrapper.clone(DOM.getElementsByClassName(clone, NG_BINDING_CLASS)); elementsWithBindings = DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
} }
if (DOM.hasClass(clone, NG_BINDING_CLASS)) {
ListWrapper.insert(elements, 0, clone);
}
var binders = this.elementBinders;
/**
* TODO: vsavkin: benchmark
* If this performs poorly, the seven loops can be collapsed into one.
*/
var elementInjectors = ProtoView._createElementInjectors(elements, binders, hostElementInjector);
var rootElementInjectors = ProtoView._rootElementInjectors(elementInjectors);
var textNodes = ProtoView._textNodes(elements, binders);
var bindElements = ProtoView._bindElements(elements, binders);
var viewNodes; var viewNodes;
if (this.isTemplateElement) {
if (clone instanceof TemplateElement) { var childNodes = rootElementClone.content.childNodes;
viewNodes = ListWrapper.clone(clone.content.childNodes); // Note: An explicit loop is the fastes way to convert a DOM array into a JS array!
} else { viewNodes = ListWrapper.createFixedSize(childNodes.length);
viewNodes = [clone]; for (var i=0; i<childNodes.length; i++) {
viewNodes[i] = childNodes[i];
} }
var view = new View(this, viewNodes, elementInjectors, rootElementInjectors, textNodes, } else {
bindElements, this.protoRecordRange); viewNodes = [rootElementClone];
}
var view = new View(this, viewNodes, this.protoRecordRange, this.protoContextLocals);
view.preBuiltObjects = ProtoView._createPreBuiltObjects(view, elementInjectors, elements, binders); var binders = this.elementBinders;
var elementInjectors = ListWrapper.createFixedSize(binders.length);
var rootElementInjectors = [];
var textNodes = [];
var elementsWithPropertyBindings = [];
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
var viewPorts = [];
var componentChildViews = [];
ProtoView._instantiateChildComponentViews(view, elements, binders, for (var i = 0; i < binders.length; i++) {
elementInjectors); var binder = binders[i];
var element;
if (i === 0 && this.rootBindingOffset === 1) {
element = rootElementClone;
} else {
element = elementsWithBindings[i - this.rootBindingOffset];
}
var elementInjector = null;
// elementInjectors and rootElementInjectors
var protoElementInjector = binder.protoElementInjector;
if (isPresent(protoElementInjector)) {
var parentElementInjector = isPresent(protoElementInjector.parent) ? elementInjectors[protoElementInjector.parent.index] : null;
elementInjector = protoElementInjector.instantiate(parentElementInjector, hostElementInjector);
if (isBlank(parentElementInjector)) {
ListWrapper.push(rootElementInjectors, elementInjector);
}
}
elementInjectors[i] = elementInjector;
// viewPorts
var viewPort = null;
if (isPresent(binder.templateDirective)) {
viewPort = new ViewPort(view, element, binder.nestedProtoView, elementInjector);
ListWrapper.push(viewPorts, viewPort);
}
// preBuiltObjects
var preBuiltObject = null;
if (isPresent(elementInjector)) {
preBuiltObject = new PreBuiltObjects(view, new NgElement(element), viewPort);
}
preBuiltObjects[i] = preBuiltObject;
// elementsWithPropertyBindings
if (binder.hasElementPropertyBindings) {
ListWrapper.push(elementsWithPropertyBindings, element);
}
// textNodes
var textNodeIndices = binder.textNodeIndices;
if (isPresent(textNodeIndices)) {
var childNodes = DOM.templateAwareRoot(element).childNodes;
for (var j = 0; j < textNodeIndices.length; j++) {
ListWrapper.push(textNodes, childNodes[textNodeIndices[j]]);
}
}
// componentChildViews
if (isPresent(binder.componentDirective)) {
var childView = binder.nestedProtoView.instantiate(elementInjector);
view.recordRange.addRange(childView.recordRange);
ViewPort.moveViewNodesIntoParent(element.createShadowRoot(), childView);
ListWrapper.push(componentChildViews, childView);
}
}
view.init(elementInjectors, rootElementInjectors, textNodes, elementsWithPropertyBindings,
viewPorts, preBuiltObjects, componentChildViews);
return view; return view;
} }
bindVariable(contextName:string, templateName:string) { bindVariable(contextName:string, templateName:string) {
MapWrapper.set(this.variableBindings, contextName, templateName); MapWrapper.set(this.variableBindings, contextName, templateName);
MapWrapper.set(this.protoContextLocals, templateName, null);
} }
bindElement(protoElementInjector:ProtoElementInjector, bindElement(protoElementInjector:ProtoElementInjector,
@ -343,13 +389,13 @@ export class ProtoView {
/** /**
* Adds an element property binding for the last created ElementBinder via bindElement * Adds an element property binding for the last created ElementBinder via bindElement
*/ */
bindElementProperty(propertyName:string, expression:AST) { bindElementProperty(expression:AST, setterName:string, setter:SetterFn) {
var elBinder = this.elementBinders[this.elementBinders.length-1]; var elBinder = this.elementBinders[this.elementBinders.length-1];
if (!elBinder.hasElementPropertyBindings) { if (!elBinder.hasElementPropertyBindings) {
elBinder.hasElementPropertyBindings = true; elBinder.hasElementPropertyBindings = true;
this.elementsWithBindingCount++; this.elementsWithBindingCount++;
} }
var memento = new ElementPropertyMemento(this.elementsWithBindingCount-1, propertyName); var memento = new ElementPropertyMemento(this.elementsWithBindingCount-1, setterName, setter);
this.protoRecordRange.addRecordsFromAST(expression, memento, memento); this.protoRecordRange.addRecordsFromAST(expression, memento, memento);
} }
@ -383,113 +429,35 @@ export class ProtoView {
this.protoRecordRange.addRecordsFromAST(expression, expMemento, groupMemento, false); this.protoRecordRange.addRecordsFromAST(expression, expMemento, groupMemento, false);
} }
static _createElementInjectors(elements, binders, hostElementInjector) {
var injectors = ListWrapper.createFixedSize(binders.length);
for (var i = 0; i < binders.length; ++i) {
var proto = binders[i].protoElementInjector;
if (isPresent(proto)) {
var parentElementInjector = isPresent(proto.parent) ? injectors[proto.parent.index] : null;
injectors[i] = proto.instantiate(parentElementInjector, hostElementInjector);
} else {
injectors[i] = null;
}
}
return injectors;
}
static _createPreBuiltObjects(view, injectors, elements, binders) {
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
for (var i = 0; i < injectors.length; ++i) {
var injector = injectors[i];
if (injector != null) {
var binder = binders[i];
var element = elements[i];
var ngElement = new NgElement(element);
var viewPort = null;
if (isPresent(binder.templateDirective)) {
viewPort = new ViewPort(view, element, binder.nestedProtoView, injector);
view.addViewPort(viewPort);
}
preBuiltObjects[i] = new PreBuiltObjects(view, ngElement, viewPort);
} else {
preBuiltObjects[i] = null;
}
}
return preBuiltObjects;
}
static _rootElementInjectors(injectors) {
return ListWrapper.filter(injectors, inj => isPresent(inj) && isBlank(inj.parent));
}
static _textNodes(elements, binders) {
var textNodes = [];
for (var i = 0; i < binders.length; ++i) {
ProtoView._collectTextNodes(textNodes, elements[i],
binders[i].textNodeIndices);
}
return textNodes;
}
static _bindElements(elements, binders):List<Element> {
var bindElements = [];
for (var i = 0; i < binders.length; ++i) {
if (binders[i].hasElementPropertyBindings) ListWrapper.push(
bindElements, elements[i]);
}
return bindElements;
}
static _collectTextNodes(allTextNodes, element, indices) {
if (isPresent(indices)) {
var childNodes = DOM.templateAwareRoot(element).childNodes;
for (var i = 0; i < indices.length; ++i) {
ListWrapper.push(allTextNodes, childNodes[indices[i]]);
}
}
}
static _instantiateChildComponentViews(view: View, elements, binders,
injectors) {
for (var i = 0; i < binders.length; ++i) {
var binder = binders[i];
if (isPresent(binder.componentDirective)) {
var injector = injectors[i];
var childView = binder.nestedProtoView.instantiate(injectors[i]);
view.addComponentChildView(childView);
var shadowRoot = elements[i].createShadowRoot();
ViewPort.moveViewNodesIntoParent(shadowRoot, childView);
}
}
}
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>, // Create a rootView as if the compiler encountered <rootcmp></rootcmp>,
// and the component template is already compiled into protoView. // and the component template is already compiled into protoView.
// Used for bootstrapping. // Used for bootstrapping.
static createRootProtoView(protoView: ProtoView, static createRootProtoView(protoView: ProtoView,
insertionElement, rootComponentAnnotatedType: AnnotatedType): ProtoView { insertionElement, rootComponentAnnotatedType: AnnotatedType): ProtoView {
DOM.addClass(insertionElement, 'ng-binding');
var rootProtoView = new ProtoView(insertionElement, new ProtoRecordRange()); var rootProtoView = new ProtoView(insertionElement, new ProtoRecordRange());
rootProtoView.instantiateInPlace = true;
var binder = rootProtoView.bindElement( var binder = rootProtoView.bindElement(
new ProtoElementInjector(null, 0, [rootComponentAnnotatedType.type], true)); new ProtoElementInjector(null, 0, [rootComponentAnnotatedType.type], true));
binder.componentDirective = rootComponentAnnotatedType; binder.componentDirective = rootComponentAnnotatedType;
binder.nestedProtoView = protoView; binder.nestedProtoView = protoView;
DOM.addClass(insertionElement, 'ng-binding');
return rootProtoView; return rootProtoView;
} }
} }
export class ElementPropertyMemento { export class ElementPropertyMemento {
_elementIndex:int; _elementIndex:int;
_propertyName:string; _setterName:string;
constructor(elementIndex:int, propertyName:string) { _setter:SetterFn;
constructor(elementIndex:int, setterName:string, setter:SetterFn) {
this._elementIndex = elementIndex; this._elementIndex = elementIndex;
this._propertyName = propertyName; this._setterName = setterName;
this._setter = setter;
} }
invoke(record:Record, bindElements:List<Element>) { invoke(record:Record, bindElements:List<Element>) {
var element:Element = bindElements[this._elementIndex]; var element:Element = bindElements[this._elementIndex];
DOM.setProperty(element, this._propertyName, record.currentValue); this._setter(element, record.currentValue);
} }
} }

View File

@ -12,7 +12,7 @@ import {NgElement} from 'core/dom/element';
//TODO: vsavkin: use a spy object //TODO: vsavkin: use a spy object
class DummyView extends View { class DummyView extends View {
constructor() { constructor() {
super(null, null, null, null, null, null, new ProtoRecordRange()); super(null, null, new ProtoRecordRange(), MapWrapper.create());
} }
} }

View File

@ -32,12 +32,6 @@ export function main() {
var parser = new Parser(new Lexer()); var parser = new Parser(new Lexer());
return new CompilePipeline([ return new CompilePipeline([
new MockStep((parent, current, control) => { new MockStep((parent, current, control) => {
if (isPresent(current.element.getAttribute('viewroot'))) {
current.isViewRoot = true;
current.inheritedProtoView = new ProtoView(current.element, new ProtoRecordRange());
} else if (isPresent(parent)) {
current.inheritedProtoView = parent.inheritedProtoView;
}
var hasBinding = false; var hasBinding = false;
if (isPresent(current.element.getAttribute('text-binding'))) { if (isPresent(current.element.getAttribute('text-binding'))) {
MapWrapper.forEach(textNodeBindings, (v,k) => { MapWrapper.forEach(textNodeBindings, (v,k) => {
@ -72,6 +66,12 @@ export function main() {
current.hasBindings = true; current.hasBindings = true;
DOM.addClass(current.element, 'ng-binding'); DOM.addClass(current.element, 'ng-binding');
} }
if (isPresent(current.element.getAttribute('viewroot'))) {
current.isViewRoot = true;
current.inheritedProtoView = new ProtoView(current.element, new ProtoRecordRange());
} else if (isPresent(parent)) {
current.inheritedProtoView = parent.inheritedProtoView;
}
}), new ElementBinderBuilder() }), new ElementBinderBuilder()
]); ]);
} }
@ -159,22 +159,22 @@ export function main() {
it('should bind element properties', () => { it('should bind element properties', () => {
var propertyBindings = MapWrapper.createFromStringMap({ var propertyBindings = MapWrapper.createFromStringMap({
'elprop1': 'prop1', 'value': 'prop1',
'elprop2': 'prop2' 'hidden': 'prop2'
}); });
var pipeline = createPipeline({propertyBindings: propertyBindings}); var pipeline = createPipeline({propertyBindings: propertyBindings});
var results = pipeline.process(createElement('<div viewroot prop-binding></div>')); var results = pipeline.process(createElement('<input viewroot prop-binding>'));
var pv = results[0].inheritedProtoView; var pv = results[0].inheritedProtoView;
expect(pv.elementBinders[0].hasElementPropertyBindings).toBe(true); expect(pv.elementBinders[0].hasElementPropertyBindings).toBe(true);
instantiateView(pv); instantiateView(pv);
evalContext.prop1 = 'a'; evalContext.prop1 = 'a';
evalContext.prop2 = 'b'; evalContext.prop2 = false;
changeDetector.detectChanges(); changeDetector.detectChanges();
expect(DOM.getProperty(view.nodes[0], 'elprop1')).toEqual('a'); expect(view.nodes[0].value).toEqual('a');
expect(DOM.getProperty(view.nodes[0], 'elprop2')).toEqual('b'); expect(view.nodes[0].hidden).toEqual(false);
}); });
it('should bind events', () => { it('should bind events', () => {

View File

@ -105,7 +105,7 @@ export function main() {
it('should collect property bindings on the root element if it has the ng-binding class', () => { it('should collect property bindings on the root element if it has the ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('<div [prop]="a" class="ng-binding"></div>'), new ProtoRecordRange()); var pv = new ProtoView(templateAwareCreateElement('<div [prop]="a" class="ng-binding"></div>'), new ProtoRecordRange());
pv.bindElement(null); pv.bindElement(null);
pv.bindElementProperty('prop', parser.parseBinding('a')); pv.bindElementProperty(parser.parseBinding('a').ast, 'prop', reflector.setter('prop'));
var view = pv.instantiate(null); var view = pv.instantiate(null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
@ -117,7 +117,7 @@ export function main() {
var pv = new ProtoView(templateAwareCreateElement('<div><span></span><span class="ng-binding"></span></div>'), var pv = new ProtoView(templateAwareCreateElement('<div><span></span><span class="ng-binding"></span></div>'),
new ProtoRecordRange()); new ProtoRecordRange());
pv.bindElement(null); pv.bindElement(null);
pv.bindElementProperty('a', parser.parseBinding('b')); pv.bindElementProperty(parser.parseBinding('b').ast, 'a', reflector.setter('a'));
var view = pv.instantiate(null); var view = pv.instantiate(null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
@ -159,9 +159,10 @@ export function main() {
describe('inplace instantiation', () => { describe('inplace instantiation', () => {
it('should be supported.', () => { it('should be supported.', () => {
var template = createElement('<div></div>') var template = createElement('<div></div>');
var view = new ProtoView(template, new ProtoRecordRange()) var pv = new ProtoView(template, new ProtoRecordRange());
.instantiate(null, true); pv.instantiateInPlace = true;
var view = pv.instantiate(null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.nodes[0]).toBe(template); expect(view.nodes[0]).toBe(template);
}); });
@ -369,7 +370,7 @@ export function main() {
var pv = new ProtoView(createElement('<div class="ng-binding"></div>'), var pv = new ProtoView(createElement('<div class="ng-binding"></div>'),
new ProtoRecordRange()); new ProtoRecordRange());
pv.bindElement(null); pv.bindElement(null);
pv.bindElementProperty('id', parser.parseBinding('foo')); pv.bindElementProperty(parser.parseBinding('foo').ast, 'id', reflector.setter('id'));
createViewAndChangeDetector(pv); createViewAndChangeDetector(pv);
ctx.foo = 'buz'; ctx.foo = 'buz';
@ -437,14 +438,14 @@ export function main() {
it('should create the root component when instantiated', () => { it('should create the root component when instantiated', () => {
var rootProtoView = ProtoView.createRootProtoView(pv, el, someComponentDirective); var rootProtoView = ProtoView.createRootProtoView(pv, el, someComponentDirective);
var view = rootProtoView.instantiate(null, true); var view = rootProtoView.instantiate(null);
view.hydrate(new Injector([]), null, null); view.hydrate(new Injector([]), null, null);
expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null); expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null);
}); });
it('should inject the protoView into the shadowDom', () => { it('should inject the protoView into the shadowDom', () => {
var rootProtoView = ProtoView.createRootProtoView(pv, el, someComponentDirective); var rootProtoView = ProtoView.createRootProtoView(pv, el, someComponentDirective);
var view = rootProtoView.instantiate(null, true); var view = rootProtoView.instantiate(null);
view.hydrate(new Injector([]), null, null); view.hydrate(new Injector([]), null, null);
expect(el.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi'); expect(el.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi');
}); });

View File

@ -14,7 +14,9 @@ function createElement(html) {
} }
function createView(nodes) { function createView(nodes) {
return new View(null, nodes, [], [], [], [], new ProtoRecordRange()); var view = new View(null, nodes, new ProtoRecordRange(), MapWrapper.create());
view.init([], [], [], [], [], [], []);
return view;
} }
export function main() { export function main() {

View File

@ -32,6 +32,7 @@ class IterableMap extends IterableBase<List> {
class MapWrapper { class MapWrapper {
static HashMap create() => new HashMap(); static HashMap create() => new HashMap();
static HashMap clone(Map m) => new HashMap.from(m);
static HashMap createFromStringMap(m) => m; static HashMap createFromStringMap(m) => m;
static HashMap createFromPairs(List pairs) { static HashMap createFromPairs(List pairs) {
return pairs.fold({}, (m, p){ return pairs.fold({}, (m, p){

View File

@ -6,6 +6,7 @@ export var Set = window.Set;
export class MapWrapper { export class MapWrapper {
static create():Map { return new Map(); } static create():Map { return new Map(); }
static clone(m:Map):Map { return new Map(m); }
static createFromStringMap(stringMap):Map { static createFromStringMap(stringMap):Map {
var result = MapWrapper.create(); var result = MapWrapper.create();
for (var prop in stringMap) { for (var prop in stringMap) {
@ -63,8 +64,8 @@ export class ListWrapper {
static createFixedSize(size):List { return new List(size); } static createFixedSize(size):List { return new List(size); }
static get(m, k) { return m[k]; } static get(m, k) { return m[k]; }
static set(m, k, v) { m[k] = v; } static set(m, k, v) { m[k] = v; }
static clone(array) { static clone(array:List) {
return Array.prototype.slice.call(array, 0); return array.slice(0);
} }
static map(array, fn) { static map(array, fn) {
return array.map(fn); return array.map(fn);

View File

@ -68,11 +68,8 @@ 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) { static hasProperty(Element element, String name) {
new JsObject.fromBrowserObject(element)[name] = value; return new JsObject.fromBrowserObject(element).hasProperty(name);
}
static getProperty(Element element, String name) {
return new JsObject.fromBrowserObject(element)[name];
} }
static getElementsByClassName(Element element, String name) { static getElementsByClassName(Element element, String name) {
return element.getElementsByClassName(name); return element.getElementsByClassName(name);

View File

@ -63,11 +63,8 @@ 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) { static hasProperty(element:Element, name:string) {
element[name] = value; return name in element;
}
static getProperty(element:Element, name:string) {
return element[name];
} }
static getElementsByClassName(element:Element, name:string) { static getElementsByClassName(element:Element, name:string) {
return element.getElementsByClassName(name); return element.getElementsByClassName(name);