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 {EventManager} from './events/event_manager';
|
|
|
|
|
|
|
|
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './view/proto_view';
|
|
|
|
import {DomView, DomViewRef, resolveInternalDomView} from './view/view';
|
2015-06-24 13:46:39 -07:00
|
|
|
import {DomFragmentRef, resolveInternalDomFragment} from './view/fragment';
|
|
|
|
import {
|
|
|
|
NG_BINDING_CLASS_SELECTOR,
|
|
|
|
NG_BINDING_CLASS,
|
|
|
|
cloneAndQueryProtoView,
|
|
|
|
camelCaseToDashCase
|
|
|
|
} from './util';
|
2015-05-06 10:49:42 -07:00
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
import {
|
|
|
|
Renderer,
|
|
|
|
RenderProtoViewRef,
|
|
|
|
RenderViewRef,
|
|
|
|
RenderElementRef,
|
|
|
|
RenderFragmentRef,
|
|
|
|
RenderViewWithFragments
|
|
|
|
} from '../api';
|
2015-05-06 10:49:42 -07:00
|
|
|
|
2015-07-23 09:03:39 +01:00
|
|
|
export const DOCUMENT_TOKEN: OpaqueToken = CONST_EXPR(new OpaqueToken('DocumentToken'));
|
|
|
|
export const DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES: OpaqueToken =
|
2015-07-15 10:55:44 -07:00
|
|
|
CONST_EXPR(new OpaqueToken('DomReflectPropertiesAsAttributes'));
|
2015-07-23 09:03:39 +01:00
|
|
|
const REFLECT_PREFIX: string = 'ng-reflect-';
|
2015-05-06 10:49:42 -07:00
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class DomRenderer extends Renderer {
|
|
|
|
_document;
|
2015-07-15 10:55:44 -07:00
|
|
|
_reflectPropertiesAsAttributes: boolean;
|
2015-05-06 10:49:42 -07:00
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
constructor(public _eventManager: EventManager, @Inject(DOCUMENT_TOKEN) document,
|
2015-07-15 10:55:44 -07:00
|
|
|
@Inject(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES) reflectPropertiesAsAttributes:
|
|
|
|
boolean) {
|
2015-05-06 10:49:42 -07:00
|
|
|
super();
|
2015-07-15 10:55:44 -07:00
|
|
|
this._reflectPropertiesAsAttributes = reflectPropertiesAsAttributes;
|
2015-05-06 10:49:42 -07:00
|
|
|
this._document = document;
|
|
|
|
}
|
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
createRootHostView(hostProtoViewRef: RenderProtoViewRef, fragmentCount: number,
|
|
|
|
hostElementSelector: string): RenderViewWithFragments {
|
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`);
|
|
|
|
}
|
2015-06-24 13:46:39 -07:00
|
|
|
return this._createView(hostProtoView, element);
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
createView(protoViewRef: RenderProtoViewRef, fragmentCount: number): RenderViewWithFragments {
|
2015-05-06 10:49:42 -07:00
|
|
|
var protoView = resolveInternalDomProtoView(protoViewRef);
|
2015-06-24 13:46:39 -07:00
|
|
|
return this._createView(protoView, null);
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
destroyView(viewRef: RenderViewRef) {
|
2015-05-06 10:49:42 -07:00
|
|
|
// noop for now
|
|
|
|
}
|
|
|
|
|
2015-06-26 11:10:52 -07:00
|
|
|
getNativeElementSync(location: RenderElementRef): any {
|
2015-06-24 13:46:39 -07:00
|
|
|
if (isBlank(location.renderBoundElementIndex)) {
|
|
|
|
return null;
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
2015-06-24 13:46:39 -07:00
|
|
|
return resolveInternalDomView(location.renderView)
|
|
|
|
.boundElements[location.renderBoundElementIndex];
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
getRootNodes(fragment: RenderFragmentRef): List<Node> {
|
|
|
|
return resolveInternalDomFragment(fragment);
|
2015-05-15 09:55:43 -07:00
|
|
|
}
|
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
attachFragmentAfterFragment(previousFragmentRef: RenderFragmentRef,
|
|
|
|
fragmentRef: RenderFragmentRef) {
|
|
|
|
var previousFragmentNodes = resolveInternalDomFragment(previousFragmentRef);
|
2015-07-16 15:18:02 -07:00
|
|
|
if (previousFragmentNodes.length > 0) {
|
|
|
|
var sibling = previousFragmentNodes[previousFragmentNodes.length - 1];
|
|
|
|
moveNodesAfterSibling(sibling, resolveInternalDomFragment(fragmentRef));
|
|
|
|
}
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
attachFragmentAfterElement(elementRef: RenderElementRef, fragmentRef: RenderFragmentRef) {
|
|
|
|
if (isBlank(elementRef.renderBoundElementIndex)) {
|
|
|
|
return;
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
2015-06-24 13:46:39 -07:00
|
|
|
var parentView = resolveInternalDomView(elementRef.renderView);
|
|
|
|
var element = parentView.boundElements[elementRef.renderBoundElementIndex];
|
|
|
|
moveNodesAfterSibling(element, resolveInternalDomFragment(fragmentRef));
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
detachFragment(fragmentRef: RenderFragmentRef) {
|
|
|
|
var fragmentNodes = resolveInternalDomFragment(fragmentRef);
|
|
|
|
for (var i = 0; i < fragmentNodes.length; i++) {
|
|
|
|
DOM.remove(fragmentNodes[i]);
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
// add global events
|
2015-06-17 11:17:21 -07:00
|
|
|
view.eventHandlerRemovers = [];
|
2015-05-06 10:49:42 -07:00
|
|
|
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-06-17 11:17:21 -07:00
|
|
|
view.eventHandlerRemovers.push(remover);
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-06-23 11:21:56 -07:00
|
|
|
setElementProperty(location: RenderElementRef, propertyName: string, propertyValue: any): void {
|
2015-06-24 13:46:39 -07:00
|
|
|
if (isBlank(location.renderBoundElementIndex)) {
|
|
|
|
return;
|
|
|
|
}
|
2015-06-23 11:21:56 -07:00
|
|
|
var view = resolveInternalDomView(location.renderView);
|
2015-06-24 13:46:39 -07:00
|
|
|
view.setElementProperty(location.renderBoundElementIndex, propertyName, propertyValue);
|
2015-07-15 10:55:44 -07:00
|
|
|
// Reflect the property value as an attribute value with ng-reflect- prefix.
|
|
|
|
if (this._reflectPropertiesAsAttributes) {
|
|
|
|
this.setElementAttribute(location, `${REFLECT_PREFIX}${camelCaseToDashCase(propertyName)}`,
|
2015-07-24 12:10:46 -07:00
|
|
|
`${propertyValue}`);
|
2015-07-15 10:55:44 -07:00
|
|
|
}
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
|
2015-07-10 11:29:41 +02:00
|
|
|
setElementAttribute(location: RenderElementRef, attributeName: string, attributeValue: string):
|
|
|
|
void {
|
2015-06-24 13:46:39 -07:00
|
|
|
if (isBlank(location.renderBoundElementIndex)) {
|
|
|
|
return;
|
|
|
|
}
|
2015-06-23 11:21:56 -07:00
|
|
|
var view = resolveInternalDomView(location.renderView);
|
2015-06-24 13:46:39 -07:00
|
|
|
view.setElementAttribute(location.renderBoundElementIndex, attributeName, attributeValue);
|
2015-06-18 15:44:44 -07:00
|
|
|
}
|
|
|
|
|
2015-06-23 11:21:56 -07:00
|
|
|
setElementClass(location: RenderElementRef, className: string, isAdd: boolean): void {
|
2015-06-24 13:46:39 -07:00
|
|
|
if (isBlank(location.renderBoundElementIndex)) {
|
|
|
|
return;
|
|
|
|
}
|
2015-06-23 11:21:56 -07:00
|
|
|
var view = resolveInternalDomView(location.renderView);
|
2015-06-24 13:46:39 -07:00
|
|
|
view.setElementClass(location.renderBoundElementIndex, className, isAdd);
|
2015-06-18 15:44:44 -07:00
|
|
|
}
|
|
|
|
|
2015-06-23 11:21:56 -07:00
|
|
|
setElementStyle(location: RenderElementRef, styleName: string, styleValue: string): void {
|
2015-06-24 13:46:39 -07:00
|
|
|
if (isBlank(location.renderBoundElementIndex)) {
|
|
|
|
return;
|
|
|
|
}
|
2015-06-23 11:21:56 -07:00
|
|
|
var view = resolveInternalDomView(location.renderView);
|
2015-06-24 13:46:39 -07:00
|
|
|
view.setElementStyle(location.renderBoundElementIndex, styleName, styleValue);
|
2015-06-18 15:44:44 -07:00
|
|
|
}
|
|
|
|
|
2015-06-23 11:21:56 -07:00
|
|
|
invokeElementMethod(location: RenderElementRef, methodName: string, args: List<any>): void {
|
2015-06-24 13:46:39 -07:00
|
|
|
if (isBlank(location.renderBoundElementIndex)) {
|
|
|
|
return;
|
|
|
|
}
|
2015-06-23 11:21:56 -07:00
|
|
|
var view = resolveInternalDomView(location.renderView);
|
2015-06-24 13:46:39 -07:00
|
|
|
view.invokeElementMethod(location.renderBoundElementIndex, methodName, args);
|
2015-05-11 12:31:16 -07:00
|
|
|
}
|
|
|
|
|
2015-05-18 11:57:20 -07:00
|
|
|
setText(viewRef: RenderViewRef, textNodeIndex: number, text: string): void {
|
2015-06-24 13:46:39 -07:00
|
|
|
if (isBlank(textNodeIndex)) {
|
|
|
|
return;
|
|
|
|
}
|
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-06-24 13:46:39 -07:00
|
|
|
_createView(protoView: DomProtoView, inplaceElement: HTMLElement): RenderViewWithFragments {
|
|
|
|
var clonedProtoView = cloneAndQueryProtoView(protoView, true);
|
2015-05-28 15:02:05 -07:00
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
var boundElements = clonedProtoView.boundElements;
|
2015-05-06 10:49:42 -07:00
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
// adopt inplaceElement
|
|
|
|
if (isPresent(inplaceElement)) {
|
|
|
|
if (protoView.fragmentsRootNodeCount[0] !== 1) {
|
|
|
|
throw new BaseException('Root proto views can only contain one element!');
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
2015-06-24 13:46:39 -07:00
|
|
|
DOM.clearNodes(inplaceElement);
|
|
|
|
var tempRoot = clonedProtoView.fragments[0][0];
|
|
|
|
moveChildNodes(tempRoot, inplaceElement);
|
|
|
|
if (boundElements.length > 0 && boundElements[0] === tempRoot) {
|
|
|
|
boundElements[0] = inplaceElement;
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
2015-06-24 13:46:39 -07:00
|
|
|
clonedProtoView.fragments[0][0] = inplaceElement;
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
var view = new DomView(protoView, clonedProtoView.boundTextNodes, boundElements);
|
2015-05-06 10:49:42 -07:00
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
var binders = protoView.elementBinders;
|
2015-05-06 10:49:42 -07:00
|
|
|
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
|
|
|
var binder = binders[binderIdx];
|
|
|
|
var element = boundElements[binderIdx];
|
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
// native shadow DOM
|
|
|
|
if (binder.hasNativeShadowRoot) {
|
|
|
|
var shadowRootWrapper = DOM.firstChild(element);
|
|
|
|
moveChildNodes(shadowRootWrapper, DOM.createShadowRoot(element));
|
|
|
|
DOM.remove(shadowRootWrapper);
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// events
|
|
|
|
if (isPresent(binder.eventLocals) && isPresent(binder.localEvents)) {
|
|
|
|
for (var i = 0; i < binder.localEvents.length; i++) {
|
2015-06-24 13:46:39 -07:00
|
|
|
this._createEventListener(view, element, binderIdx, binder.localEvents[i].name,
|
2015-05-18 11:57:20 -07:00
|
|
|
binder.eventLocals);
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
return new RenderViewWithFragments(
|
|
|
|
new DomViewRef(view), clonedProtoView.fragments.map(nodes => new DomFragmentRef(nodes)));
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
_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
|
|
|
}
|
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
_createGlobalEventListener(view, elementIndex, eventName, eventTarget, fullName): Function {
|
|
|
|
return this._eventManager.addGlobalEventListener(
|
|
|
|
eventTarget, eventName, (event) => { view.dispatchEvent(elementIndex, fullName, event); });
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
2015-06-24 13:46:39 -07:00
|
|
|
}
|
2015-05-06 10:49:42 -07:00
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
function moveNodesAfterSibling(sibling, nodes) {
|
2015-07-16 15:18:02 -07:00
|
|
|
if (nodes.length > 0 && isPresent(DOM.parentElement(sibling))) {
|
2015-06-24 13:46:39 -07:00
|
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
|
|
DOM.insertBefore(sibling, nodes[i]);
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
2015-06-24 13:46:39 -07:00
|
|
|
DOM.insertBefore(nodes[nodes.length - 1], sibling);
|
2015-06-02 10:15:16 -07:00
|
|
|
}
|
2015-06-24 13:46:39 -07:00
|
|
|
}
|
2015-06-02 10:15:16 -07:00
|
|
|
|
2015-06-24 13:46:39 -07:00
|
|
|
function moveChildNodes(source: Node, target: Node) {
|
|
|
|
var currChild = DOM.firstChild(source);
|
|
|
|
while (isPresent(currChild)) {
|
|
|
|
var nextChild = DOM.nextSibling(currChild);
|
|
|
|
DOM.appendChild(target, currChild);
|
|
|
|
currChild = nextChild;
|
2015-05-06 10:49:42 -07:00
|
|
|
}
|
2015-07-09 17:32:42 -07:00
|
|
|
}
|