2015-04-07 20:54:20 -07:00
|
|
|
import {OpaqueToken, Inject, Injectable} from 'angular2/di';
|
2015-03-23 14:10:55 -07:00
|
|
|
import {int, isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
|
|
|
|
|
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
|
|
|
|
|
|
|
|
|
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
|
|
|
|
|
|
|
|
|
import {Content} from '../shadow_dom/content_tag';
|
|
|
|
|
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
|
2015-04-02 09:52:00 -07:00
|
|
|
import {EventManager} from 'angular2/src/render/dom/events/event_manager';
|
2015-03-23 14:10:55 -07:00
|
|
|
|
2015-04-07 20:54:20 -07:00
|
|
|
import * as vcModule from './view_container';
|
|
|
|
|
import * as pvModule from './proto_view';
|
|
|
|
|
import * as viewModule from './view';
|
2015-03-23 14:10:55 -07:00
|
|
|
import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from '../util';
|
|
|
|
|
|
2015-04-07 20:54:20 -07:00
|
|
|
// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this!
|
|
|
|
|
export const VIEW_POOL_CAPACITY = 'render.ViewFactory.viewPoolCapacity';
|
2015-03-23 14:10:55 -07:00
|
|
|
|
2015-04-07 20:54:20 -07:00
|
|
|
@Injectable()
|
2015-03-23 14:10:55 -07:00
|
|
|
export class ViewFactory {
|
2015-04-10 09:34:46 -07:00
|
|
|
_poolCapacityPerProtoView:number;
|
|
|
|
|
_pooledViewsPerProtoView:Map<pvModule.RenderProtoView, List<viewModule.RenderView>>;
|
2015-03-23 14:10:55 -07:00
|
|
|
_eventManager:EventManager;
|
|
|
|
|
_shadowDomStrategy:ShadowDomStrategy;
|
|
|
|
|
|
2015-04-10 09:34:46 -07:00
|
|
|
constructor(@Inject(VIEW_POOL_CAPACITY) poolCapacityPerProtoView,
|
|
|
|
|
eventManager:EventManager, shadowDomStrategy:ShadowDomStrategy) {
|
|
|
|
|
this._poolCapacityPerProtoView = poolCapacityPerProtoView;
|
|
|
|
|
this._pooledViewsPerProtoView = MapWrapper.create();
|
2015-03-23 14:10:55 -07:00
|
|
|
this._eventManager = eventManager;
|
|
|
|
|
this._shadowDomStrategy = shadowDomStrategy;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-09 21:20:11 +02:00
|
|
|
getView(protoView:pvModule.RenderProtoView):viewModule.RenderView {
|
2015-04-10 09:34:46 -07:00
|
|
|
var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView);
|
|
|
|
|
if (isPresent(pooledViews)) {
|
|
|
|
|
var result = ListWrapper.removeLast(pooledViews);
|
|
|
|
|
if (pooledViews.length === 0) {
|
|
|
|
|
MapWrapper.delete(this._pooledViewsPerProtoView, protoView);
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
2015-04-10 09:34:46 -07:00
|
|
|
return result;
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
2015-04-10 09:34:46 -07:00
|
|
|
return this._createView(protoView);
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
|
|
|
|
|
2015-04-09 21:20:11 +02:00
|
|
|
returnView(view:viewModule.RenderView) {
|
2015-03-23 14:10:55 -07:00
|
|
|
if (view.hydrated()) {
|
|
|
|
|
view.dehydrate();
|
|
|
|
|
}
|
2015-04-10 09:34:46 -07:00
|
|
|
var protoView = view.proto;
|
|
|
|
|
var pooledViews = MapWrapper.get(this._pooledViewsPerProtoView, protoView);
|
|
|
|
|
if (isBlank(pooledViews)) {
|
|
|
|
|
pooledViews = [];
|
|
|
|
|
MapWrapper.set(this._pooledViewsPerProtoView, protoView, pooledViews);
|
|
|
|
|
}
|
|
|
|
|
if (pooledViews.length < this._poolCapacityPerProtoView) {
|
|
|
|
|
ListWrapper.push(pooledViews, view);
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-09 21:20:11 +02:00
|
|
|
_createView(protoView:pvModule.RenderProtoView): viewModule.RenderView {
|
2015-03-23 14:10:55 -07:00
|
|
|
var rootElementClone = protoView.isRootView ? protoView.element : DOM.importIntoDoc(protoView.element);
|
|
|
|
|
var elementsWithBindingsDynamic;
|
|
|
|
|
if (protoView.isTemplateElement) {
|
|
|
|
|
elementsWithBindingsDynamic = DOM.querySelectorAll(DOM.content(rootElementClone), NG_BINDING_CLASS_SELECTOR);
|
|
|
|
|
} else {
|
|
|
|
|
elementsWithBindingsDynamic = DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length);
|
|
|
|
|
for (var binderIdx = 0; binderIdx < elementsWithBindingsDynamic.length; ++binderIdx) {
|
|
|
|
|
elementsWithBindings[binderIdx] = elementsWithBindingsDynamic[binderIdx];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var viewRootNodes;
|
|
|
|
|
if (protoView.isTemplateElement) {
|
|
|
|
|
var childNode = DOM.firstChild(DOM.content(rootElementClone));
|
2015-04-09 21:20:11 +02:00
|
|
|
viewRootNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in pvModule.RenderProtoView
|
2015-03-23 14:10:55 -07:00
|
|
|
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
|
|
|
|
|
while(childNode != null) {
|
|
|
|
|
ListWrapper.push(viewRootNodes, childNode);
|
|
|
|
|
childNode = DOM.nextSibling(childNode);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
viewRootNodes = [rootElementClone];
|
|
|
|
|
}
|
|
|
|
|
var binders = protoView.elementBinders;
|
|
|
|
|
var boundTextNodes = [];
|
|
|
|
|
var boundElements = ListWrapper.createFixedSize(binders.length);
|
|
|
|
|
var viewContainers = ListWrapper.createFixedSize(binders.length);
|
|
|
|
|
var contentTags = ListWrapper.createFixedSize(binders.length);
|
|
|
|
|
|
|
|
|
|
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
|
|
|
|
var binder = binders[binderIdx];
|
|
|
|
|
var element;
|
|
|
|
|
if (binderIdx === 0 && protoView.rootBindingOffset === 1) {
|
|
|
|
|
element = rootElementClone;
|
|
|
|
|
} else {
|
|
|
|
|
element = elementsWithBindings[binderIdx - protoView.rootBindingOffset];
|
|
|
|
|
}
|
|
|
|
|
boundElements[binderIdx] = element;
|
|
|
|
|
|
|
|
|
|
// boundTextNodes
|
|
|
|
|
var childNodes = DOM.childNodes(DOM.templateAwareRoot(element));
|
|
|
|
|
var textNodeIndices = binder.textNodeIndices;
|
|
|
|
|
for (var i = 0; i<textNodeIndices.length; i++) {
|
|
|
|
|
ListWrapper.push(boundTextNodes, childNodes[textNodeIndices[i]]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// viewContainers
|
|
|
|
|
var viewContainer = null;
|
|
|
|
|
if (isBlank(binder.componentId) && isPresent(binder.nestedProtoView)) {
|
2015-04-07 20:54:20 -07:00
|
|
|
viewContainer = new vcModule.ViewContainer(this, element);
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
|
|
|
|
viewContainers[binderIdx] = viewContainer;
|
|
|
|
|
|
|
|
|
|
// contentTags
|
|
|
|
|
var contentTag = null;
|
|
|
|
|
if (isPresent(binder.contentTagSelector)) {
|
|
|
|
|
contentTag = new Content(element, binder.contentTagSelector);
|
|
|
|
|
}
|
|
|
|
|
contentTags[binderIdx] = contentTag;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-09 21:20:11 +02:00
|
|
|
var view = new viewModule.RenderView(
|
2015-03-23 14:10:55 -07:00
|
|
|
protoView, viewRootNodes,
|
2015-04-02 15:56:58 +02:00
|
|
|
boundTextNodes, boundElements, viewContainers, contentTags, this._eventManager
|
2015-03-23 14:10:55 -07:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
|
|
|
|
var binder = binders[binderIdx];
|
|
|
|
|
var element = boundElements[binderIdx];
|
|
|
|
|
|
|
|
|
|
// static child components
|
2015-04-14 18:01:44 -07:00
|
|
|
if (binder.hasStaticComponent()) {
|
2015-03-23 14:10:55 -07:00
|
|
|
var childView = this._createView(binder.nestedProtoView);
|
|
|
|
|
view.setComponentView(this._shadowDomStrategy, binderIdx, childView);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// events
|
2015-04-02 15:56:58 +02:00
|
|
|
if (isPresent(binder.eventLocals) && isPresent(binder.localEvents)) {
|
|
|
|
|
for (var i = 0; i < binder.localEvents.length; i++) {
|
|
|
|
|
this._createEventListener(view, element, binderIdx, binder.localEvents[i].name, binder.eventLocals);
|
|
|
|
|
}
|
2015-03-23 14:10:55 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (protoView.isRootView) {
|
|
|
|
|
view.hydrate(null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_createEventListener(view, element, elementIndex, eventName, eventLocals) {
|
|
|
|
|
this._eventManager.addEventListener(element, eventName, (event) => {
|
|
|
|
|
view.dispatchEvent(elementIndex, eventName, event);
|
|
|
|
|
});
|
|
|
|
|
}
|
2015-04-09 21:20:11 +02:00
|
|
|
}
|