2014-10-10 20:44:55 -07:00
|
|
|
import {DOM, Element, Node, Text, DocumentFragment, TemplateElement} from 'facade/dom';
|
2014-12-02 17:09:46 -08:00
|
|
|
import {ListWrapper, MapWrapper, StringMapWrapper, List} from 'facade/collection';
|
2014-12-29 09:51:52 -08:00
|
|
|
import {ProtoRecordRange, RecordRange, Record,
|
|
|
|
ChangeDispatcher, AST, ContextWithVariableBindings} from 'change_detection/change_detection';
|
2014-11-13 15:15:55 -08:00
|
|
|
|
2014-11-14 10:58:58 -08:00
|
|
|
import {ProtoElementInjector, ElementInjector, PreBuiltObjects} from './element_injector';
|
2014-11-11 17:33:47 -08:00
|
|
|
import {ElementBinder} from './element_binder';
|
2014-12-23 10:45:20 -08:00
|
|
|
import {DirectiveMetadata} from './directive_metadata';
|
2014-11-20 12:07:48 -08:00
|
|
|
import {SetterFn} from 'reflection/types';
|
2014-12-01 18:41:55 -08:00
|
|
|
import {FIELD, IMPLEMENTS, int, isPresent, isBlank, BaseException} from 'facade/lang';
|
2014-10-29 15:41:50 -07:00
|
|
|
import {Injector} from 'di/di';
|
2014-11-14 10:58:58 -08:00
|
|
|
import {NgElement} from 'core/dom/element';
|
2014-11-21 15:13:01 -08:00
|
|
|
import {ViewPort} from './viewport';
|
2014-12-02 17:09:46 -08:00
|
|
|
import {OnChange} from './interfaces';
|
2015-01-02 14:23:59 -08:00
|
|
|
import {Content} from './shadow_dom_emulation/content_tag';
|
|
|
|
import {LightDom, DestinationLightDom} from './shadow_dom_emulation/light_dom';
|
2014-09-28 16:29:11 -07:00
|
|
|
|
2014-11-11 17:33:47 -08:00
|
|
|
const NG_BINDING_CLASS = 'ng-binding';
|
2014-12-09 10:31:19 -08:00
|
|
|
const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
|
|
|
|
// TODO(tbosch): Cannot use `const` because of Dart.
|
|
|
|
var NO_FORMATTERS = MapWrapper.create();
|
2014-11-11 17:33:47 -08:00
|
|
|
|
2014-11-21 15:13:01 -08:00
|
|
|
/**
|
2014-10-10 20:44:55 -07:00
|
|
|
* Const of making objects: http://jsperf.com/instantiate-size-of-object
|
|
|
|
*/
|
2014-12-04 13:16:38 +01:00
|
|
|
@IMPLEMENTS(ChangeDispatcher)
|
2014-09-28 16:29:11 -07:00
|
|
|
export class View {
|
|
|
|
/// This list matches the _nodes list. It is sparse, since only Elements have ElementInjector
|
2014-11-21 21:19:23 -08:00
|
|
|
rootElementInjectors:List<ElementInjector>;
|
|
|
|
elementInjectors:List<ElementInjector>;
|
|
|
|
bindElements:List<Element>;
|
|
|
|
textNodes:List<Text>;
|
|
|
|
recordRange:RecordRange;
|
2014-09-28 16:29:11 -07:00
|
|
|
/// When the view is part of render tree, the DocumentFragment is empty, which is why we need
|
|
|
|
/// to keep track of the nodes.
|
2014-11-21 21:19:23 -08:00
|
|
|
nodes:List<Node>;
|
2014-11-21 15:13:01 -08:00
|
|
|
componentChildViews: List<View>;
|
|
|
|
viewPorts: List<ViewPort>;
|
2014-12-01 18:41:55 -08:00
|
|
|
preBuiltObjects: List<PreBuiltObjects>;
|
|
|
|
proto: ProtoView;
|
2014-12-29 12:12:11 -08:00
|
|
|
context: any;
|
2014-12-09 10:31:19 -08:00
|
|
|
contextWithLocals:ContextWithVariableBindings;
|
2015-01-02 14:23:59 -08:00
|
|
|
|
2014-12-09 10:31:19 -08:00
|
|
|
constructor(proto:ProtoView, nodes:List<Node>, protoRecordRange:ProtoRecordRange, protoContextLocals:Map) {
|
2014-12-01 18:41:55 -08:00
|
|
|
this.proto = proto;
|
2014-11-11 17:33:47 -08:00
|
|
|
this.nodes = nodes;
|
2014-12-09 10:31:19 -08:00
|
|
|
this.recordRange = protoRecordRange.instantiate(this, NO_FORMATTERS);
|
|
|
|
this.elementInjectors = null;
|
|
|
|
this.rootElementInjectors = null;
|
|
|
|
this.textNodes = null;
|
|
|
|
this.bindElements = null;
|
2014-11-21 15:13:01 -08:00
|
|
|
this.componentChildViews = null;
|
|
|
|
this.viewPorts = null;
|
2014-12-01 18:41:55 -08:00
|
|
|
this.preBuiltObjects = null;
|
|
|
|
this.context = null;
|
2015-01-08 12:16:37 -08:00
|
|
|
this.contextWithLocals = (MapWrapper.size(protoContextLocals) > 0)
|
|
|
|
? new ContextWithVariableBindings(null, MapWrapper.clone(protoContextLocals))
|
|
|
|
: null;
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
|
|
|
|
2014-12-09 10:31:19 -08:00
|
|
|
init(elementInjectors:List, rootElementInjectors:List, textNodes: List, bindElements:List, viewPorts:List, preBuiltObjects:List, componentChildViews:List) {
|
|
|
|
this.elementInjectors = elementInjectors;
|
|
|
|
this.rootElementInjectors = rootElementInjectors;
|
|
|
|
this.textNodes = textNodes;
|
|
|
|
this.bindElements = bindElements;
|
|
|
|
this.viewPorts = viewPorts;
|
|
|
|
this.preBuiltObjects = preBuiltObjects;
|
|
|
|
this.componentChildViews = componentChildViews;
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
setLocal(contextName: string, value) {
|
|
|
|
if (!this.hydrated()) throw new BaseException('Cannot set locals on dehydrated view.');
|
|
|
|
if (!MapWrapper.contains(this.proto.variableBindings, contextName)) {
|
|
|
|
throw new BaseException(
|
|
|
|
`Local binding ${contextName} not defined in the view template.`);
|
|
|
|
}
|
|
|
|
var templateName = MapWrapper.get(this.proto.variableBindings, contextName);
|
|
|
|
this.context.set(templateName, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
hydrated() {
|
|
|
|
return isPresent(this.context);
|
|
|
|
}
|
|
|
|
|
|
|
|
_hydrateContext(newContext) {
|
2014-12-09 10:31:19 -08:00
|
|
|
if (isPresent(this.contextWithLocals)) {
|
|
|
|
this.contextWithLocals.parent = newContext;
|
|
|
|
this.context = this.contextWithLocals;
|
|
|
|
} else {
|
|
|
|
this.context = newContext;
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
2014-12-09 10:31:19 -08:00
|
|
|
// 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);
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
_dehydrateContext() {
|
2014-12-09 10:31:19 -08:00
|
|
|
if (isPresent(this.contextWithLocals)) {
|
|
|
|
this.contextWithLocals.clearValues();
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
|
|
|
this.context = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A dehydrated view is a state of the view that allows it to be moved around
|
|
|
|
* the view tree, without incurring the cost of recreating the underlying
|
|
|
|
* injectors and watch records.
|
|
|
|
*
|
|
|
|
* A dehydrated view has the following properties:
|
|
|
|
*
|
|
|
|
* - all element injectors are empty.
|
|
|
|
* - all appInjectors are released.
|
|
|
|
* - all viewports are empty.
|
|
|
|
* - all context locals are set to null.
|
|
|
|
* - the view context is null.
|
|
|
|
*
|
|
|
|
* A call to hydrate/dehydrate does not attach/detach the view from the view
|
|
|
|
* tree.
|
|
|
|
*/
|
|
|
|
hydrate(appInjector: Injector, hostElementInjector: ElementInjector,
|
|
|
|
context: Object) {
|
2014-12-09 10:31:19 -08:00
|
|
|
if (this.hydrated()) throw new BaseException('The view is already hydrated.');
|
2014-12-01 18:41:55 -08:00
|
|
|
this._hydrateContext(context);
|
|
|
|
|
2014-12-09 10:31:19 -08:00
|
|
|
// viewPorts
|
|
|
|
for (var i = 0; i < this.viewPorts.length; i++) {
|
|
|
|
this.viewPorts[i].hydrate(appInjector, hostElementInjector);
|
|
|
|
}
|
|
|
|
|
|
|
|
var binders = this.proto.elementBinders;
|
|
|
|
var componentChildViewIndex = 0;
|
|
|
|
for (var i = 0; i < binders.length; ++i) {
|
|
|
|
var componentDirective = binders[i].componentDirective;
|
|
|
|
var shadowDomAppInjector = null;
|
2014-12-01 18:41:55 -08:00
|
|
|
|
2014-12-09 10:31:19 -08:00
|
|
|
// 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]);
|
|
|
|
}
|
|
|
|
|
2015-01-09 16:09:47 +01:00
|
|
|
if (isPresent(componentDirective)) {
|
2014-12-09 10:31:19 -08:00
|
|
|
this.componentChildViews[componentChildViewIndex++].hydrate(shadowDomAppInjector,
|
2015-01-02 14:23:59 -08:00
|
|
|
elementInjector, elementInjector.getComponent());
|
2015-01-08 09:11:33 -08:00
|
|
|
}
|
|
|
|
}
|
2015-01-02 14:23:59 -08:00
|
|
|
|
2015-01-08 09:11:33 -08:00
|
|
|
// this should be moved into DOM write queue
|
|
|
|
for (var i = 0; i < binders.length; ++i) {
|
|
|
|
var componentDirective = binders[i].componentDirective;
|
|
|
|
if (isPresent(componentDirective)) {
|
2015-01-02 14:23:59 -08:00
|
|
|
var lightDom = this.preBuiltObjects[i].lightDom;
|
2015-01-08 09:11:33 -08:00
|
|
|
if (isPresent(lightDom)) {
|
|
|
|
lightDom.redistribute();
|
|
|
|
}
|
2014-12-09 10:31:19 -08:00
|
|
|
}
|
|
|
|
}
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
dehydrate() {
|
2014-12-09 10:31:19 -08:00
|
|
|
// Note: preserve the opposite order of the hydration process.
|
|
|
|
|
|
|
|
// componentChildViews
|
|
|
|
for (var i = 0; i < this.componentChildViews.length; i++) {
|
|
|
|
this.componentChildViews[i].dehydrate();
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
2014-12-09 10:31:19 -08:00
|
|
|
|
|
|
|
// elementInjectors
|
2014-12-01 18:41:55 -08:00
|
|
|
for (var i = 0; i < this.elementInjectors.length; i++) {
|
2015-01-13 16:17:43 -08:00
|
|
|
if (isPresent(this.elementInjectors[i])) {
|
|
|
|
this.elementInjectors[i].clearDirectives();
|
|
|
|
}
|
2014-12-01 18:41:55 -08:00
|
|
|
}
|
2014-12-09 10:31:19 -08:00
|
|
|
|
|
|
|
// viewPorts
|
2014-12-01 18:41:55 -08:00
|
|
|
if (isPresent(this.viewPorts)) {
|
|
|
|
for (var i = 0; i < this.viewPorts.length; i++) {
|
|
|
|
this.viewPorts[i].dehydrate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-09 10:31:19 -08:00
|
|
|
this._dehydrateContext();
|
2014-09-28 16:29:11 -07:00
|
|
|
}
|
|
|
|
|
2014-12-02 17:09:46 -08:00
|
|
|
onRecordChange(groupMemento, records:List<Record>) {
|
|
|
|
this._invokeMementoForRecords(records);
|
|
|
|
if (groupMemento instanceof DirectivePropertyGroupMemento) {
|
|
|
|
this._notifyDirectiveAboutChanges(groupMemento, records);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_invokeMementoForRecords(records:List<Record>) {
|
|
|
|
for(var i = 0; i < records.length; ++i) {
|
|
|
|
this._invokeMementoFor(records[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_notifyDirectiveAboutChanges(groupMemento, records:List<Record>) {
|
|
|
|
var dir = groupMemento.directive(this.elementInjectors);
|
|
|
|
if (dir instanceof OnChange) {
|
|
|
|
dir.onChange(this._collectChanges(records));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-28 16:29:11 -07:00
|
|
|
// dispatch to element injector or text nodes based on context
|
2014-12-02 17:09:46 -08:00
|
|
|
_invokeMementoFor(record:Record) {
|
|
|
|
var memento = record.expressionMemento();
|
|
|
|
if (memento instanceof DirectivePropertyMemento) {
|
2014-10-10 20:44:55 -07:00
|
|
|
// we know that it is DirectivePropertyMemento
|
2014-12-02 17:09:46 -08:00
|
|
|
var directiveMemento:DirectivePropertyMemento = memento;
|
2014-10-10 20:44:55 -07:00
|
|
|
directiveMemento.invoke(record, this.elementInjectors);
|
2014-12-02 17:09:46 -08:00
|
|
|
|
|
|
|
} else if (memento instanceof ElementPropertyMemento) {
|
|
|
|
var elementMemento:ElementPropertyMemento = memento;
|
2014-10-10 20:44:55 -07:00
|
|
|
elementMemento.invoke(record, this.bindElements);
|
2014-12-02 17:09:46 -08:00
|
|
|
|
2014-09-28 16:29:11 -07:00
|
|
|
} else {
|
2014-10-10 20:44:55 -07:00
|
|
|
// we know it refers to _textNodes.
|
2014-12-02 17:09:46 -08:00
|
|
|
var textNodeIndex:number = memento;
|
2014-10-10 20:44:55 -07:00
|
|
|
DOM.setText(this.textNodes[textNodeIndex], record.currentValue);
|
2014-09-28 16:29:11 -07:00
|
|
|
}
|
|
|
|
}
|
2014-11-12 11:40:36 -08:00
|
|
|
|
2014-12-04 13:23:32 +01:00
|
|
|
_collectChanges(records:List<Record>) {
|
2014-12-02 17:09:46 -08:00
|
|
|
var changes = StringMapWrapper.create();
|
|
|
|
for(var i = 0; i < records.length; ++i) {
|
|
|
|
var record = records[i];
|
|
|
|
var propertyUpdate = new PropertyUpdate(record.currentValue, record.previousValue);
|
|
|
|
StringMapWrapper.set(changes, record.expressionMemento()._setterName, propertyUpdate);
|
|
|
|
}
|
|
|
|
return changes;
|
|
|
|
}
|
2014-09-28 16:29:11 -07:00
|
|
|
}
|
|
|
|
|
2014-09-28 20:02:32 -07:00
|
|
|
export class ProtoView {
|
2014-11-21 21:19:23 -08:00
|
|
|
element:Element;
|
|
|
|
elementBinders:List<ElementBinder>;
|
|
|
|
protoRecordRange:ProtoRecordRange;
|
|
|
|
variableBindings: Map;
|
2014-12-09 10:31:19 -08:00
|
|
|
protoContextLocals:Map;
|
2014-11-21 21:19:23 -08:00
|
|
|
textNodesWithBindingCount:int;
|
|
|
|
elementsWithBindingCount:int;
|
2014-12-09 10:31:19 -08:00
|
|
|
instantiateInPlace:boolean;
|
|
|
|
rootBindingOffset:int;
|
|
|
|
isTemplateElement:boolean;
|
2014-09-28 20:02:32 -07:00
|
|
|
constructor(
|
2014-11-11 17:33:47 -08:00
|
|
|
template:Element,
|
2014-11-19 15:52:01 -08:00
|
|
|
protoRecordRange:ProtoRecordRange) {
|
2014-11-11 17:33:47 -08:00
|
|
|
this.element = template;
|
|
|
|
this.elementBinders = [];
|
2014-11-18 16:38:36 -08:00
|
|
|
this.variableBindings = MapWrapper.create();
|
2014-12-09 10:31:19 -08:00
|
|
|
this.protoContextLocals = MapWrapper.create();
|
2014-11-19 15:52:01 -08:00
|
|
|
this.protoRecordRange = protoRecordRange;
|
2014-11-11 17:33:47 -08:00
|
|
|
this.textNodesWithBindingCount = 0;
|
|
|
|
this.elementsWithBindingCount = 0;
|
2014-12-09 10:31:19 -08:00
|
|
|
this.instantiateInPlace = false;
|
2015-01-08 12:16:37 -08:00
|
|
|
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS))
|
|
|
|
? 1 : 0;
|
2014-12-09 10:31:19 -08:00
|
|
|
this.isTemplateElement = this.element instanceof TemplateElement;
|
2014-09-28 20:02:32 -07:00
|
|
|
}
|
|
|
|
|
2014-12-01 18:41:55 -08:00
|
|
|
// TODO(rado): hostElementInjector should be moved to hydrate phase.
|
2014-12-09 10:31:19 -08:00
|
|
|
instantiate(hostElementInjector: ElementInjector):View {
|
|
|
|
var rootElementClone = this.instantiateInPlace ? this.element : DOM.clone(this.element);
|
2015-01-02 14:23:59 -08:00
|
|
|
var elementsWithBindingsDynamic;
|
2014-12-09 10:31:19 -08:00
|
|
|
if (this.isTemplateElement) {
|
2015-01-02 14:23:59 -08:00
|
|
|
elementsWithBindingsDynamic = DOM.querySelectorAll(rootElementClone.content, NG_BINDING_CLASS_SELECTOR);
|
2014-11-14 10:37:42 -08:00
|
|
|
} else {
|
2015-01-02 14:23:59 -08:00
|
|
|
elementsWithBindingsDynamic= DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
|
2014-11-14 10:37:42 -08:00
|
|
|
}
|
2015-01-02 14:23:59 -08:00
|
|
|
|
|
|
|
var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length);
|
|
|
|
for (var i = 0; i < elementsWithBindingsDynamic.length; ++i) {
|
|
|
|
elementsWithBindings[i] = elementsWithBindingsDynamic[i];
|
|
|
|
}
|
|
|
|
|
2014-12-09 10:31:19 -08:00
|
|
|
var viewNodes;
|
|
|
|
if (this.isTemplateElement) {
|
2014-12-11 16:07:49 -08:00
|
|
|
var childNode = DOM.firstChild(rootElementClone.content);
|
|
|
|
viewNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in ProtoView
|
|
|
|
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
|
|
|
|
while(childNode != null) {
|
|
|
|
ListWrapper.push(viewNodes, childNode);
|
|
|
|
childNode = DOM.nextSibling(childNode);
|
2014-12-09 10:31:19 -08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
viewNodes = [rootElementClone];
|
2014-11-11 17:33:47 -08:00
|
|
|
}
|
2014-12-09 10:31:19 -08:00
|
|
|
var view = new View(this, viewNodes, this.protoRecordRange, this.protoContextLocals);
|
|
|
|
|
2014-11-11 17:33:47 -08:00
|
|
|
var binders = this.elementBinders;
|
2014-12-09 10:31:19 -08:00
|
|
|
var elementInjectors = ListWrapper.createFixedSize(binders.length);
|
|
|
|
var rootElementInjectors = [];
|
|
|
|
var textNodes = [];
|
|
|
|
var elementsWithPropertyBindings = [];
|
|
|
|
var preBuiltObjects = ListWrapper.createFixedSize(binders.length);
|
|
|
|
var viewPorts = [];
|
|
|
|
var componentChildViews = [];
|
2014-10-27 11:47:13 -04:00
|
|
|
|
2014-12-09 10:31:19 -08:00
|
|
|
for (var i = 0; i < binders.length; i++) {
|
|
|
|
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)) {
|
2015-01-06 13:30:48 +01:00
|
|
|
if (isPresent(protoElementInjector.parent)) {
|
|
|
|
var parentElementInjector = elementInjectors[protoElementInjector.parent.index];
|
|
|
|
elementInjector = protoElementInjector.instantiate(parentElementInjector, null);
|
|
|
|
} else {
|
|
|
|
elementInjector = protoElementInjector.instantiate(null, hostElementInjector);
|
2014-12-09 10:31:19 -08:00
|
|
|
ListWrapper.push(rootElementInjectors, elementInjector);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
elementInjectors[i] = elementInjector;
|
2014-12-01 18:41:55 -08:00
|
|
|
|
2014-12-09 10:31:19 -08:00
|
|
|
if (binder.hasElementPropertyBindings) {
|
|
|
|
ListWrapper.push(elementsWithPropertyBindings, element);
|
|
|
|
}
|
2014-11-14 10:58:58 -08:00
|
|
|
|
2014-12-09 10:31:19 -08:00
|
|
|
// textNodes
|
|
|
|
var textNodeIndices = binder.textNodeIndices;
|
|
|
|
if (isPresent(textNodeIndices)) {
|
2014-12-11 16:07:49 -08:00
|
|
|
var childNode = DOM.firstChild(DOM.templateAwareRoot(element));
|
|
|
|
for (var j = 0, k = 0; j < textNodeIndices.length; j++) {
|
|
|
|
for(var index = textNodeIndices[j]; k < index; k++) {
|
|
|
|
childNode = DOM.nextSibling(childNode);
|
|
|
|
}
|
|
|
|
ListWrapper.push(textNodes, childNode);
|
2014-12-09 10:31:19 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// componentChildViews
|
2015-01-02 14:23:59 -08:00
|
|
|
var lightDom = null;
|
2014-12-09 10:31:19 -08:00
|
|
|
if (isPresent(binder.componentDirective)) {
|
|
|
|
var childView = binder.nestedProtoView.instantiate(elementInjector);
|
|
|
|
view.recordRange.addRange(childView.recordRange);
|
2014-12-23 10:45:20 -08:00
|
|
|
|
2015-01-02 14:23:59 -08:00
|
|
|
lightDom = binder.componentDirective.shadowDomStrategy.constructLightDom(view, childView, element);
|
2014-12-23 10:45:20 -08:00
|
|
|
binder.componentDirective.shadowDomStrategy.attachTemplate(element, childView);
|
2015-01-02 14:23:59 -08:00
|
|
|
|
2014-12-09 10:31:19 -08:00
|
|
|
ListWrapper.push(componentChildViews, childView);
|
|
|
|
}
|
2015-01-02 14:23:59 -08:00
|
|
|
|
|
|
|
// viewPorts
|
|
|
|
var viewPort = null;
|
|
|
|
if (isPresent(binder.templateDirective)) {
|
|
|
|
var destLightDom = this._parentElementLightDom(protoElementInjector, preBuiltObjects);
|
|
|
|
viewPort = new ViewPort(view, element, binder.nestedProtoView, elementInjector, destLightDom);
|
|
|
|
ListWrapper.push(viewPorts, viewPort);
|
|
|
|
}
|
|
|
|
|
|
|
|
// preBuiltObjects
|
|
|
|
if (isPresent(elementInjector)) {
|
|
|
|
preBuiltObjects[i] = new PreBuiltObjects(view, new NgElement(element), viewPort, lightDom);
|
|
|
|
}
|
2015-01-12 11:50:54 -08:00
|
|
|
|
|
|
|
// events
|
|
|
|
if (isPresent(binder.events)) {
|
|
|
|
// TODO(rado): if there is directive at this element that injected an
|
|
|
|
// event emitter for that eventType do not attach the handler.
|
|
|
|
MapWrapper.forEach(binder.events, (expr, eventName) => {
|
2015-01-16 16:30:43 -08:00
|
|
|
ProtoView._addNativeEventListener(element, eventName, expr, view);
|
2015-01-12 11:50:54 -08:00
|
|
|
});
|
|
|
|
}
|
2014-12-09 10:31:19 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
view.init(elementInjectors, rootElementInjectors, textNodes, elementsWithPropertyBindings,
|
|
|
|
viewPorts, preBuiltObjects, componentChildViews);
|
2014-11-14 10:58:58 -08:00
|
|
|
|
|
|
|
return view;
|
2014-11-11 17:33:47 -08:00
|
|
|
}
|
|
|
|
|
2015-01-16 16:30:43 -08:00
|
|
|
static _addNativeEventListener(element: Element, eventName: string, expr, view: View) {
|
2015-01-19 13:00:27 +01:00
|
|
|
var locals = MapWrapper.create();
|
2015-01-16 16:30:43 -08:00
|
|
|
DOM.on(element, eventName, (event) => {
|
|
|
|
if (event.target === element) {
|
|
|
|
// Most of the time the event will be fired only when the view is
|
|
|
|
// in the live document. However, in a rare circumstance the
|
|
|
|
// view might get dehydrated, in between the event queuing up and
|
|
|
|
// firing.
|
2015-01-19 13:00:27 +01:00
|
|
|
if (view.hydrated()) {
|
|
|
|
MapWrapper.set(locals, '\$event', event);
|
|
|
|
var context = new ContextWithVariableBindings(view.context, locals);
|
|
|
|
expr.eval(context);
|
|
|
|
}
|
2015-01-16 16:30:43 -08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-01-02 14:23:59 -08:00
|
|
|
_parentElementLightDom(protoElementInjector:ProtoElementInjector, preBuiltObjects:List):LightDom {
|
|
|
|
var p = protoElementInjector.parent;
|
|
|
|
return isPresent(p) ? preBuiltObjects[p.index].lightDom : null;
|
|
|
|
}
|
|
|
|
|
2014-11-18 16:38:36 -08:00
|
|
|
bindVariable(contextName:string, templateName:string) {
|
|
|
|
MapWrapper.set(this.variableBindings, contextName, templateName);
|
2014-12-09 10:31:19 -08:00
|
|
|
MapWrapper.set(this.protoContextLocals, templateName, null);
|
2014-11-18 16:38:36 -08:00
|
|
|
}
|
|
|
|
|
2014-11-13 15:15:55 -08:00
|
|
|
bindElement(protoElementInjector:ProtoElementInjector,
|
2014-12-23 10:45:20 -08:00
|
|
|
componentDirective:DirectiveMetadata = null, templateDirective:DirectiveMetadata = null):ElementBinder {
|
2014-11-13 15:15:55 -08:00
|
|
|
var elBinder = new ElementBinder(protoElementInjector, componentDirective, templateDirective);
|
2014-11-11 17:33:47 -08:00
|
|
|
ListWrapper.push(this.elementBinders, elBinder);
|
|
|
|
return elBinder;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a text node binding for the last created ElementBinder via bindElement
|
|
|
|
*/
|
|
|
|
bindTextNode(indexInParent:int, expression:AST) {
|
|
|
|
var elBinder = this.elementBinders[this.elementBinders.length-1];
|
2014-11-19 14:54:07 -08:00
|
|
|
if (isBlank(elBinder.textNodeIndices)) {
|
|
|
|
elBinder.textNodeIndices = ListWrapper.create();
|
|
|
|
}
|
2014-11-11 17:33:47 -08:00
|
|
|
ListWrapper.push(elBinder.textNodeIndices, indexInParent);
|
2014-12-02 17:09:46 -08:00
|
|
|
var memento = this.textNodesWithBindingCount++;
|
|
|
|
this.protoRecordRange.addRecordsFromAST(expression, memento, memento);
|
2014-11-11 17:33:47 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds an element property binding for the last created ElementBinder via bindElement
|
|
|
|
*/
|
2014-12-09 10:31:19 -08:00
|
|
|
bindElementProperty(expression:AST, setterName:string, setter:SetterFn) {
|
2014-11-11 17:33:47 -08:00
|
|
|
var elBinder = this.elementBinders[this.elementBinders.length-1];
|
|
|
|
if (!elBinder.hasElementPropertyBindings) {
|
|
|
|
elBinder.hasElementPropertyBindings = true;
|
|
|
|
this.elementsWithBindingCount++;
|
|
|
|
}
|
2014-12-09 10:31:19 -08:00
|
|
|
var memento = new ElementPropertyMemento(this.elementsWithBindingCount-1, setterName, setter);
|
2014-12-02 17:09:46 -08:00
|
|
|
this.protoRecordRange.addRecordsFromAST(expression, memento, memento);
|
2014-11-11 17:33:47 -08:00
|
|
|
}
|
|
|
|
|
2014-11-19 14:54:07 -08:00
|
|
|
/**
|
|
|
|
* Adds an event binding for the last created ElementBinder via bindElement
|
|
|
|
*/
|
|
|
|
bindEvent(eventName:string, expression:AST) {
|
|
|
|
var elBinder = this.elementBinders[this.elementBinders.length-1];
|
|
|
|
if (isBlank(elBinder.events)) {
|
|
|
|
elBinder.events = MapWrapper.create();
|
|
|
|
}
|
|
|
|
MapWrapper.set(elBinder.events, eventName, expression);
|
|
|
|
}
|
|
|
|
|
2014-11-11 17:33:47 -08:00
|
|
|
/**
|
|
|
|
* Adds a directive property binding for the last created ElementBinder via bindElement
|
|
|
|
*/
|
|
|
|
bindDirectiveProperty(
|
|
|
|
directiveIndex:number,
|
|
|
|
expression:AST,
|
|
|
|
setterName:string,
|
2014-12-05 17:44:00 -08:00
|
|
|
setter:SetterFn,
|
|
|
|
isContentWatch: boolean) {
|
2014-12-02 17:09:46 -08:00
|
|
|
|
|
|
|
var expMemento = new DirectivePropertyMemento(
|
|
|
|
this.elementBinders.length-1,
|
|
|
|
directiveIndex,
|
|
|
|
setterName,
|
|
|
|
setter
|
2014-11-11 17:33:47 -08:00
|
|
|
);
|
2014-12-02 17:09:46 -08:00
|
|
|
var groupMemento = DirectivePropertyGroupMemento.get(expMemento);
|
2014-12-05 17:44:00 -08:00
|
|
|
this.protoRecordRange.addRecordsFromAST(expression, expMemento, groupMemento, isContentWatch);
|
2014-10-27 11:47:13 -04:00
|
|
|
}
|
|
|
|
|
2014-11-07 14:30:04 -08:00
|
|
|
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>,
|
|
|
|
// and the component template is already compiled into protoView.
|
|
|
|
// Used for bootstrapping.
|
|
|
|
static createRootProtoView(protoView: ProtoView,
|
2014-12-23 10:45:20 -08:00
|
|
|
insertionElement, rootComponentAnnotatedType: DirectiveMetadata): ProtoView {
|
2014-12-09 10:31:19 -08:00
|
|
|
DOM.addClass(insertionElement, 'ng-binding');
|
2014-11-19 15:52:01 -08:00
|
|
|
var rootProtoView = new ProtoView(insertionElement, new ProtoRecordRange());
|
2014-12-09 10:31:19 -08:00
|
|
|
rootProtoView.instantiateInPlace = true;
|
2014-11-07 14:30:04 -08:00
|
|
|
var binder = rootProtoView.bindElement(
|
|
|
|
new ProtoElementInjector(null, 0, [rootComponentAnnotatedType.type], true));
|
|
|
|
binder.componentDirective = rootComponentAnnotatedType;
|
|
|
|
binder.nestedProtoView = protoView;
|
|
|
|
return rootProtoView;
|
|
|
|
}
|
2014-09-28 20:02:32 -07:00
|
|
|
}
|
|
|
|
|
2014-10-10 20:44:55 -07:00
|
|
|
export class ElementPropertyMemento {
|
2014-11-21 21:19:23 -08:00
|
|
|
_elementIndex:int;
|
2014-12-09 10:31:19 -08:00
|
|
|
_setterName:string;
|
|
|
|
_setter:SetterFn;
|
|
|
|
constructor(elementIndex:int, setterName:string, setter:SetterFn) {
|
2014-10-10 20:44:55 -07:00
|
|
|
this._elementIndex = elementIndex;
|
2014-12-09 10:31:19 -08:00
|
|
|
this._setterName = setterName;
|
|
|
|
this._setter = setter;
|
2014-10-10 20:44:55 -07:00
|
|
|
}
|
|
|
|
|
2014-10-27 23:16:31 -07:00
|
|
|
invoke(record:Record, bindElements:List<Element>) {
|
|
|
|
var element:Element = bindElements[this._elementIndex];
|
2014-12-09 10:31:19 -08:00
|
|
|
this._setter(element, record.currentValue);
|
2014-10-10 20:44:55 -07:00
|
|
|
}
|
|
|
|
}
|
2014-09-28 16:29:11 -07:00
|
|
|
|
2014-10-10 20:44:55 -07:00
|
|
|
export class DirectivePropertyMemento {
|
2014-11-21 21:19:23 -08:00
|
|
|
_elementInjectorIndex:int;
|
|
|
|
_directiveIndex:int;
|
|
|
|
_setterName:string;
|
|
|
|
_setter:SetterFn;
|
2014-09-28 16:29:11 -07:00
|
|
|
constructor(
|
|
|
|
elementInjectorIndex:number,
|
|
|
|
directiveIndex:number,
|
2014-10-27 23:16:31 -07:00
|
|
|
setterName:string,
|
|
|
|
setter:SetterFn) {
|
2014-09-28 16:29:11 -07:00
|
|
|
this._elementInjectorIndex = elementInjectorIndex;
|
|
|
|
this._directiveIndex = directiveIndex;
|
|
|
|
this._setterName = setterName;
|
|
|
|
this._setter = setter;
|
|
|
|
}
|
|
|
|
|
|
|
|
invoke(record:Record, elementInjectors:List<ElementInjector>) {
|
|
|
|
var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex];
|
2014-10-27 23:16:31 -07:00
|
|
|
var directive = elementInjector.getAtIndex(this._directiveIndex);
|
2014-09-28 16:29:11 -07:00
|
|
|
this._setter(directive, record.currentValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-02 17:09:46 -08:00
|
|
|
var _groups = MapWrapper.create();
|
|
|
|
|
|
|
|
class DirectivePropertyGroupMemento {
|
|
|
|
_elementInjectorIndex:number;
|
|
|
|
_directiveIndex:number;
|
|
|
|
|
|
|
|
constructor(elementInjectorIndex:number, directiveIndex:number) {
|
|
|
|
this._elementInjectorIndex = elementInjectorIndex;
|
|
|
|
this._directiveIndex = directiveIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
static get(memento:DirectivePropertyMemento) {
|
|
|
|
var elementInjectorIndex = memento._elementInjectorIndex;
|
|
|
|
var directiveIndex = memento._directiveIndex;
|
|
|
|
var id = elementInjectorIndex * 100 + directiveIndex;
|
|
|
|
|
2014-12-05 17:44:00 -08:00
|
|
|
if (!MapWrapper.contains(_groups, id)) {
|
2014-12-05 19:01:16 -08:00
|
|
|
MapWrapper.set(_groups, id, new DirectivePropertyGroupMemento(elementInjectorIndex, directiveIndex));
|
2014-12-02 17:09:46 -08:00
|
|
|
}
|
|
|
|
return MapWrapper.get(_groups, id);
|
|
|
|
}
|
|
|
|
|
|
|
|
directive(elementInjectors:List<ElementInjector>) {
|
|
|
|
var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex];
|
|
|
|
return elementInjector.getAtIndex(this._directiveIndex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class PropertyUpdate {
|
|
|
|
currentValue;
|
|
|
|
previousValue;
|
|
|
|
|
|
|
|
constructor(currentValue, previousValue) {
|
|
|
|
this.currentValue = currentValue;
|
|
|
|
this.previousValue = previousValue;
|
|
|
|
}
|
|
|
|
}
|