2015-05-29 14:28:17 -07:00
|
|
|
import {Inject, Injectable, OpaqueToken} from 'angular2/di';
|
|
|
|
import {
|
|
|
|
isPresent,
|
|
|
|
isBlank,
|
|
|
|
BaseException,
|
|
|
|
RegExpWrapper,
|
|
|
|
CONST_EXPR
|
|
|
|
} from 'angular2/src/facade/lang';
|
2015-05-06 10:49:42 -07:00
|
|
|
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';
|
|
|
|
import {EventManager} from './events/event_manager';
|
|
|
|
|
|
|
|
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './view/proto_view';
|
|
|
|
import {DomView, DomViewRef, resolveInternalDomView} from './view/view';
|
|
|
|
import {DomViewContainer} from './view/view_container';
|
|
|
|
import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS} from './util';
|
|
|
|
|
|
|
|
import {Renderer, RenderProtoViewRef, RenderViewRef} from '../api';
|
|
|
|
|
2015-05-29 14:28:17 -07:00
|
|
|
export const DOCUMENT_TOKEN = CONST_EXPR(new OpaqueToken('DocumentToken'));
|
2015-05-06 10:49:42 -07:00
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class DomRenderer extends Renderer {
|
2015-05-18 11:57:20 -07:00
|
|
|
_eventManager: EventManager;
|
|
|
|
_shadowDomStrategy: ShadowDomStrategy;
|
2015-05-06 10:49:42 -07:00
|
|
|
_document;
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
constructor(eventManager: EventManager, shadowDomStrategy: ShadowDomStrategy,
|
|
|
|
@Inject(DOCUMENT_TOKEN) document) {
|
2015-05-06 10:49:42 -07:00
|
|
|
super();
|
|
|
|
this._eventManager = eventManager;
|
|
|
|
this._shadowDomStrategy = shadowDomStrategy;
|
|
|
|
this._document = document;
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
createRootHostView(hostProtoViewRef: RenderProtoViewRef,
|
|
|
|
hostElementSelector: string): RenderViewRef {
|
2015-05-15 09:55:43 -07:00
|
|
|
var hostProtoView = resolveInternalDomProtoView(hostProtoViewRef);
|
|
|
|
var element = DOM.querySelector(this._document, hostElementSelector);
|
2015-05-06 10:49:42 -07:00
|
|
|
if (isBlank(element)) {
|
|
|
|
throw new BaseException(`The selector "${hostElementSelector}" did not match any elements`);
|
|
|
|
}
|
|
|
|
return new DomViewRef(this._createView(hostProtoView, element));
|
|
|
|
}
|
|
|
|
|
2015-06-03 11:02:51 -07:00
|
|
|
detachFreeView(viewRef: RenderViewRef) {
|
|
|
|
var view = resolveInternalDomView(viewRef);
|
|
|
|
this._removeViewNodes(view);
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
createView(protoViewRef: RenderProtoViewRef): RenderViewRef {
|
2015-05-06 10:49:42 -07:00
|
|
|
var protoView = resolveInternalDomProtoView(protoViewRef);
|
|
|
|
return new DomViewRef(this._createView(protoView, null));
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
destroyView(view: RenderViewRef) {
|
2015-05-06 10:49:42 -07:00
|
|
|
// noop for now
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
attachComponentView(hostViewRef: RenderViewRef, elementIndex: number,
|
|
|
|
componentViewRef: RenderViewRef) {
|
2015-05-06 10:49:42 -07:00
|
|
|
var hostView = resolveInternalDomView(hostViewRef);
|
|
|
|
var componentView = resolveInternalDomView(componentViewRef);
|
|
|
|
var element = hostView.boundElements[elementIndex];
|
|
|
|
var lightDom = hostView.lightDoms[elementIndex];
|
|
|
|
if (isPresent(lightDom)) {
|
|
|
|
lightDom.attachShadowDomView(componentView);
|
|
|
|
}
|
|
|
|
var shadowRoot = this._shadowDomStrategy.prepareShadowRoot(element);
|
|
|
|
this._moveViewNodesIntoParent(shadowRoot, componentView);
|
|
|
|
componentView.hostLightDom = lightDom;
|
|
|
|
componentView.shadowRoot = shadowRoot;
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
setComponentViewRootNodes(componentViewRef: RenderViewRef, rootNodes: List</*node*/ any>) {
|
2015-05-06 10:49:42 -07:00
|
|
|
var componentView = resolveInternalDomView(componentViewRef);
|
|
|
|
this._removeViewNodes(componentView);
|
|
|
|
componentView.rootNodes = rootNodes;
|
|
|
|
this._moveViewNodesIntoParent(componentView.shadowRoot, componentView);
|
|
|
|
}
|
|
|
|
|
2015-06-03 11:02:51 -07:00
|
|
|
getRootNodes(viewRef: RenderViewRef): List</*node*/ any> {
|
|
|
|
return resolveInternalDomView(viewRef).rootNodes;
|
2015-05-15 09:55:43 -07:00
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
detachComponentView(hostViewRef: RenderViewRef, boundElementIndex: number,
|
|
|
|
componentViewRef: RenderViewRef) {
|
2015-05-06 10:49:42 -07:00
|
|
|
var hostView = resolveInternalDomView(hostViewRef);
|
|
|
|
var componentView = resolveInternalDomView(componentViewRef);
|
|
|
|
this._removeViewNodes(componentView);
|
|
|
|
var lightDom = hostView.lightDoms[boundElementIndex];
|
|
|
|
if (isPresent(lightDom)) {
|
|
|
|
lightDom.detachShadowDomView();
|
|
|
|
}
|
|
|
|
componentView.hostLightDom = null;
|
|
|
|
componentView.shadowRoot = null;
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
attachViewInContainer(parentViewRef: RenderViewRef, boundElementIndex: number, atIndex: number,
|
|
|
|
viewRef: RenderViewRef) {
|
2015-05-06 10:49:42 -07:00
|
|
|
var parentView = resolveInternalDomView(parentViewRef);
|
|
|
|
var view = resolveInternalDomView(viewRef);
|
|
|
|
var viewContainer = this._getOrCreateViewContainer(parentView, boundElementIndex);
|
|
|
|
ListWrapper.insert(viewContainer.views, atIndex, view);
|
|
|
|
view.hostLightDom = parentView.hostLightDom;
|
|
|
|
|
|
|
|
var directParentLightDom = parentView.getDirectParentLightDom(boundElementIndex);
|
|
|
|
if (isBlank(directParentLightDom)) {
|
|
|
|
var siblingToInsertAfter;
|
|
|
|
if (atIndex == 0) {
|
|
|
|
siblingToInsertAfter = parentView.boundElements[boundElementIndex];
|
|
|
|
} else {
|
|
|
|
siblingToInsertAfter = ListWrapper.last(viewContainer.views[atIndex - 1].rootNodes);
|
|
|
|
}
|
|
|
|
this._moveViewNodesAfterSibling(siblingToInsertAfter, view);
|
|
|
|
} else {
|
|
|
|
directParentLightDom.redistribute();
|
|
|
|
}
|
|
|
|
// new content tags might have appeared, we need to redistribute.
|
|
|
|
if (isPresent(parentView.hostLightDom)) {
|
|
|
|
parentView.hostLightDom.redistribute();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
detachViewInContainer(parentViewRef: RenderViewRef, boundElementIndex: number, atIndex: number,
|
|
|
|
viewRef: RenderViewRef) {
|
2015-05-06 10:49:42 -07:00
|
|
|
var parentView = resolveInternalDomView(parentViewRef);
|
|
|
|
var view = resolveInternalDomView(viewRef);
|
|
|
|
var viewContainer = parentView.viewContainers[boundElementIndex];
|
|
|
|
var detachedView = viewContainer.views[atIndex];
|
|
|
|
ListWrapper.removeAt(viewContainer.views, atIndex);
|
|
|
|
var directParentLightDom = parentView.getDirectParentLightDom(boundElementIndex);
|
|
|
|
if (isBlank(directParentLightDom)) {
|
|
|
|
this._removeViewNodes(detachedView);
|
|
|
|
} else {
|
|
|
|
directParentLightDom.redistribute();
|
|
|
|
}
|
|
|
|
view.hostLightDom = null;
|
|
|
|
// content tags might have disappeared we need to do redistribution.
|
|
|
|
if (isPresent(parentView.hostLightDom)) {
|
|
|
|
parentView.hostLightDom.redistribute();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
hydrateView(viewRef: RenderViewRef) {
|
2015-05-06 10:49:42 -07:00
|
|
|
var view = resolveInternalDomView(viewRef);
|
|
|
|
if (view.hydrated) throw new BaseException('The view is already hydrated.');
|
|
|
|
view.hydrated = true;
|
|
|
|
|
|
|
|
for (var i = 0; i < view.lightDoms.length; ++i) {
|
|
|
|
var lightDom = view.lightDoms[i];
|
|
|
|
if (isPresent(lightDom)) {
|
|
|
|
lightDom.redistribute();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
// add global events
|
2015-05-06 10:49:42 -07:00
|
|
|
view.eventHandlerRemovers = ListWrapper.create();
|
|
|
|
var binders = view.proto.elementBinders;
|
|
|
|
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
|
|
|
var binder = binders[binderIdx];
|
|
|
|
if (isPresent(binder.globalEvents)) {
|
|
|
|
for (var i = 0; i < binder.globalEvents.length; i++) {
|
|
|
|
var globalEvent = binder.globalEvents[i];
|
2015-05-18 11:57:20 -07:00
|
|
|
var remover = this._createGlobalEventListener(view, binderIdx, globalEvent.name,
|
|
|
|
globalEvent.target, globalEvent.fullName);
|
2015-05-06 10:49:42 -07:00
|
|
|
ListWrapper.push(view.eventHandlerRemovers, remover);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (isPresent(view.hostLightDom)) {
|
|
|
|
view.hostLightDom.redistribute();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
dehydrateView(viewRef: RenderViewRef) {
|
2015-05-06 10:49:42 -07:00
|
|
|
var view = resolveInternalDomView(viewRef);
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
// remove global events
|
2015-05-06 10:49:42 -07:00
|
|
|
for (var i = 0; i < view.eventHandlerRemovers.length; i++) {
|
|
|
|
view.eventHandlerRemovers[i]();
|
|
|
|
}
|
|
|
|
|
|
|
|
view.eventHandlerRemovers = null;
|
|
|
|
view.hydrated = false;
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
setElementProperty(viewRef: RenderViewRef, elementIndex: number, propertyName: string,
|
|
|
|
propertyValue: any): void {
|
2015-05-06 10:49:42 -07:00
|
|
|
var view = resolveInternalDomView(viewRef);
|
|
|
|
view.setElementProperty(elementIndex, propertyName, propertyValue);
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
callAction(viewRef: RenderViewRef, elementIndex: number, actionExpression: string,
|
|
|
|
actionArgs: any): void {
|
2015-05-11 12:31:16 -07:00
|
|
|
var view = resolveInternalDomView(viewRef);
|
|
|
|
view.callAction(elementIndex, actionExpression, actionArgs);
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
setText(viewRef: RenderViewRef, textNodeIndex: number, text: string): void {
|
2015-05-06 10:49:42 -07:00
|
|
|
var view = resolveInternalDomView(viewRef);
|
|
|
|
DOM.setText(view.boundTextNodes[textNodeIndex], text);
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
setEventDispatcher(viewRef: RenderViewRef, dispatcher: any /*api.EventDispatcher*/): void {
|
2015-05-06 10:49:42 -07:00
|
|
|
var view = resolveInternalDomView(viewRef);
|
|
|
|
view.eventDispatcher = dispatcher;
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
_createView(protoView: DomProtoView, inplaceElement): DomView {
|
2015-05-28 15:02:05 -07:00
|
|
|
var rootElementClone;
|
2015-05-06 10:49:42 -07:00
|
|
|
var elementsWithBindingsDynamic;
|
|
|
|
var viewRootNodes;
|
2015-05-28 15:02:05 -07:00
|
|
|
if (isPresent(inplaceElement)) {
|
|
|
|
rootElementClone = inplaceElement;
|
|
|
|
elementsWithBindingsDynamic = [];
|
|
|
|
viewRootNodes = [inplaceElement];
|
|
|
|
} else if (protoView.isTemplateElement) {
|
|
|
|
rootElementClone = DOM.importIntoDoc(DOM.content(protoView.element));
|
|
|
|
elementsWithBindingsDynamic =
|
|
|
|
DOM.querySelectorAll(rootElementClone, NG_BINDING_CLASS_SELECTOR);
|
|
|
|
var childNode = DOM.firstChild(rootElementClone);
|
|
|
|
// TODO(perf): Should be fixed size, since we could pre-compute in in DomProtoView
|
|
|
|
viewRootNodes = [];
|
2015-05-06 10:49:42 -07:00
|
|
|
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
|
2015-05-18 11:57:20 -07:00
|
|
|
while (childNode != null) {
|
2015-05-06 10:49:42 -07:00
|
|
|
ListWrapper.push(viewRootNodes, childNode);
|
|
|
|
childNode = DOM.nextSibling(childNode);
|
|
|
|
}
|
|
|
|
} else {
|
2015-05-28 15:02:05 -07:00
|
|
|
rootElementClone = DOM.importIntoDoc(protoView.element);
|
|
|
|
elementsWithBindingsDynamic = DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
|
2015-05-06 10:49:42 -07:00
|
|
|
viewRootNodes = [rootElementClone];
|
|
|
|
}
|
2015-05-28 15:02:05 -07:00
|
|
|
var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length);
|
|
|
|
for (var binderIdx = 0; binderIdx < elementsWithBindingsDynamic.length; ++binderIdx) {
|
|
|
|
elementsWithBindings[binderIdx] = elementsWithBindingsDynamic[binderIdx];
|
|
|
|
}
|
|
|
|
|
2015-05-06 10:49:42 -07:00
|
|
|
var binders = protoView.elementBinders;
|
|
|
|
var boundTextNodes = [];
|
|
|
|
var boundElements = ListWrapper.createFixedSize(binders.length);
|
|
|
|
var contentTags = ListWrapper.createFixedSize(binders.length);
|
|
|
|
|
|
|
|
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
|
|
|
var binder = binders[binderIdx];
|
|
|
|
var element;
|
2015-05-28 15:02:05 -07:00
|
|
|
var childNodes;
|
2015-05-06 10:49:42 -07:00
|
|
|
if (binderIdx === 0 && protoView.rootBindingOffset === 1) {
|
2015-05-28 15:02:05 -07:00
|
|
|
// Note: if the root element was a template,
|
|
|
|
// the rootElementClone is a document fragment,
|
|
|
|
// which will be empty as soon as the view gets appended
|
|
|
|
// to a parent. So we store null in the boundElements array.
|
|
|
|
element = protoView.isTemplateElement ? null : rootElementClone;
|
|
|
|
childNodes = DOM.childNodes(rootElementClone);
|
2015-05-06 10:49:42 -07:00
|
|
|
} else {
|
|
|
|
element = elementsWithBindings[binderIdx - protoView.rootBindingOffset];
|
2015-05-28 15:02:05 -07:00
|
|
|
childNodes = DOM.childNodes(element);
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
boundElements[binderIdx] = element;
|
|
|
|
|
|
|
|
// boundTextNodes
|
|
|
|
var textNodeIndices = binder.textNodeIndices;
|
2015-05-18 11:57:20 -07:00
|
|
|
for (var i = 0; i < textNodeIndices.length; i++) {
|
2015-05-06 10:49:42 -07:00
|
|
|
ListWrapper.push(boundTextNodes, childNodes[textNodeIndices[i]]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// contentTags
|
|
|
|
var contentTag = null;
|
|
|
|
if (isPresent(binder.contentTagSelector)) {
|
|
|
|
contentTag = new Content(element, binder.contentTagSelector);
|
|
|
|
}
|
|
|
|
contentTags[binderIdx] = contentTag;
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
var view = new DomView(protoView, viewRootNodes, boundTextNodes, boundElements, contentTags);
|
2015-05-06 10:49:42 -07:00
|
|
|
|
|
|
|
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
|
|
|
var binder = binders[binderIdx];
|
|
|
|
var element = boundElements[binderIdx];
|
|
|
|
|
|
|
|
// lightDoms
|
|
|
|
var lightDom = null;
|
|
|
|
if (isPresent(binder.componentId)) {
|
|
|
|
lightDom = this._shadowDomStrategy.constructLightDom(view, boundElements[binderIdx]);
|
|
|
|
}
|
|
|
|
view.lightDoms[binderIdx] = lightDom;
|
|
|
|
|
|
|
|
// init contentTags
|
|
|
|
var contentTag = contentTags[binderIdx];
|
|
|
|
if (isPresent(contentTag)) {
|
|
|
|
var destLightDom = view.getDirectParentLightDom(binderIdx);
|
|
|
|
contentTag.init(destLightDom);
|
|
|
|
}
|
|
|
|
|
|
|
|
// events
|
|
|
|
if (isPresent(binder.eventLocals) && isPresent(binder.localEvents)) {
|
|
|
|
for (var i = 0; i < binder.localEvents.length; i++) {
|
2015-05-18 11:57:20 -07:00
|
|
|
this._createEventListener(view, element, binderIdx, binder.localEvents[i].name,
|
|
|
|
binder.eventLocals);
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return view;
|
|
|
|
}
|
|
|
|
|
|
|
|
_createEventListener(view, element, elementIndex, eventName, eventLocals) {
|
2015-05-18 11:57:20 -07:00
|
|
|
this._eventManager.addEventListener(
|
|
|
|
element, eventName, (event) => { view.dispatchEvent(elementIndex, eventName, event); });
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_moveViewNodesAfterSibling(sibling, view) {
|
|
|
|
for (var i = view.rootNodes.length - 1; i >= 0; --i) {
|
|
|
|
DOM.insertAfter(sibling, view.rootNodes[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_moveViewNodesIntoParent(parent, view) {
|
|
|
|
for (var i = 0; i < view.rootNodes.length; ++i) {
|
|
|
|
DOM.appendChild(parent, view.rootNodes[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_removeViewNodes(view) {
|
|
|
|
var len = view.rootNodes.length;
|
|
|
|
if (len == 0) return;
|
|
|
|
var parent = view.rootNodes[0].parentNode;
|
|
|
|
for (var i = len - 1; i >= 0; --i) {
|
|
|
|
DOM.removeChild(parent, view.rootNodes[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
_getOrCreateViewContainer(parentView: DomView, boundElementIndex) {
|
2015-05-06 10:49:42 -07:00
|
|
|
var vc = parentView.viewContainers[boundElementIndex];
|
|
|
|
if (isBlank(vc)) {
|
|
|
|
vc = new DomViewContainer();
|
|
|
|
parentView.viewContainers[boundElementIndex] = vc;
|
|
|
|
}
|
|
|
|
return vc;
|
|
|
|
}
|
|
|
|
|
|
|
|
_createGlobalEventListener(view, elementIndex, eventName, eventTarget, fullName): Function {
|
2015-05-18 11:57:20 -07:00
|
|
|
return this._eventManager.addGlobalEventListener(
|
|
|
|
eventTarget, eventName, (event) => { view.dispatchEvent(elementIndex, fullName, event); });
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
}
|