feat(compiler): attach components and project light dom during compilation.
Closes #2529 BREAKING CHANGES: - shadow dom emulation no longer supports the `<content>` tag. Use the new `<ng-content>` instead (works with all shadow dom strategies). - removed `DomRenderer.setViewRootNodes` and `AppViewManager.getComponentView` -> use `DomRenderer.getNativeElementSync(elementRef)` and change shadow dom directly - the `Renderer` interface has changed: * `createView` now also has to support sub views * the notion of a container has been removed. Instead, the renderer has to implement methods to attach views next to elements or other views. * a RenderView now contains multiple RenderFragments. Fragments are used to move DOM nodes around. Internal changes / design changes: - Introduce notion of view fragments on render side - DomProtoViews and DomViews on render side are merged, AppProtoViews are not merged, AppViews are partially merged (they share arrays with the other merged AppViews but we keep individual AppView instances for now). - DomProtoViews always have a `<template>` element as root * needed for storing subviews * we have less chunks of DOM to clone now - remove fake ElementBinder / Bound element for root text bindings and model them explicitly. This removes a lot of special cases we had! - AppView shares data with nested component views - some methods in AppViewManager (create, hydrate, dehydrate) are iterative now * now possible as we have all child AppViews / ElementRefs already in an array!
This commit is contained in:
@ -13,7 +13,9 @@ import {ProtoViewDto, ViewType} from '../../api';
|
||||
*/
|
||||
export class CompilePipeline {
|
||||
_control: CompileControl;
|
||||
constructor(steps: List<CompileStep>) { this._control = new CompileControl(steps); }
|
||||
constructor(steps: List<CompileStep>, private _useNativeShadowDom: boolean = false) {
|
||||
this._control = new CompileControl(steps);
|
||||
}
|
||||
|
||||
process(rootElement, protoViewType: ViewType = null,
|
||||
compilationCtxtDescription: string = ''): List<CompileElement> {
|
||||
@ -22,7 +24,8 @@ export class CompilePipeline {
|
||||
}
|
||||
var results = [];
|
||||
var rootCompileElement = new CompileElement(rootElement, compilationCtxtDescription);
|
||||
rootCompileElement.inheritedProtoView = new ProtoViewBuilder(rootElement, protoViewType);
|
||||
rootCompileElement.inheritedProtoView =
|
||||
new ProtoViewBuilder(rootElement, protoViewType, this._useNativeShadowDom);
|
||||
rootCompileElement.isViewRoot = true;
|
||||
this._process(results, null, rootCompileElement, compilationCtxtDescription);
|
||||
return results;
|
||||
|
@ -10,13 +10,15 @@ import {
|
||||
ViewType,
|
||||
DirectiveMetadata,
|
||||
RenderCompiler,
|
||||
RenderProtoViewRef
|
||||
RenderProtoViewRef,
|
||||
RenderProtoViewMergeMapping
|
||||
} from '../../api';
|
||||
import {CompilePipeline} from './compile_pipeline';
|
||||
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
|
||||
import {CompileStepFactory, DefaultStepFactory} from './compile_step_factory';
|
||||
import {Parser} from 'angular2/change_detection';
|
||||
import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
|
||||
import * as pvm from '../view/proto_view_merger';
|
||||
|
||||
/**
|
||||
* The compiler loads and translates the html templates of components into
|
||||
@ -24,7 +26,10 @@ import {ShadowDomStrategy} from '../shadow_dom/shadow_dom_strategy';
|
||||
* the CompilePipeline and the CompileSteps.
|
||||
*/
|
||||
export class DomCompiler extends RenderCompiler {
|
||||
constructor(public _stepFactory: CompileStepFactory, public _viewLoader: ViewLoader) { super(); }
|
||||
constructor(public _stepFactory: CompileStepFactory, public _viewLoader: ViewLoader,
|
||||
public _useNativeShadowDom: boolean) {
|
||||
super();
|
||||
}
|
||||
|
||||
compile(view: ViewDefinition): Promise<ProtoViewDto> {
|
||||
var tplPromise = this._viewLoader.load(view);
|
||||
@ -42,13 +47,21 @@ export class DomCompiler extends RenderCompiler {
|
||||
styleAbsUrls: null,
|
||||
directives: [directiveMetadata]
|
||||
});
|
||||
var element = DOM.createElement(directiveMetadata.selector);
|
||||
return this._compileTemplate(hostViewDef, element, ViewType.HOST);
|
||||
var template = DOM.createTemplate('');
|
||||
DOM.appendChild(DOM.content(template), DOM.createElement(directiveMetadata.selector));
|
||||
return this._compileTemplate(hostViewDef, template, ViewType.HOST);
|
||||
}
|
||||
|
||||
mergeProtoViewsRecursively(
|
||||
protoViewRefs:
|
||||
List<RenderProtoViewRef | List<any>>): Promise<List<RenderProtoViewMergeMapping>> {
|
||||
return PromiseWrapper.resolve(pvm.mergeProtoViewsRecursively(protoViewRefs));
|
||||
}
|
||||
|
||||
_compileTemplate(viewDef: ViewDefinition, tplElement,
|
||||
protoViewType: ViewType): Promise<ProtoViewDto> {
|
||||
var pipeline = new CompilePipeline(this._stepFactory.createSteps(viewDef));
|
||||
var pipeline =
|
||||
new CompilePipeline(this._stepFactory.createSteps(viewDef), this._useNativeShadowDom);
|
||||
var compileElements = pipeline.process(tplElement, protoViewType, viewDef.componentId);
|
||||
|
||||
return PromiseWrapper.resolve(compileElements[0].inheritedProtoView.build());
|
||||
@ -58,6 +71,7 @@ export class DomCompiler extends RenderCompiler {
|
||||
@Injectable()
|
||||
export class DefaultDomCompiler extends DomCompiler {
|
||||
constructor(parser: Parser, shadowDomStrategy: ShadowDomStrategy, viewLoader: ViewLoader) {
|
||||
super(new DefaultStepFactory(parser, shadowDomStrategy), viewLoader);
|
||||
super(new DefaultStepFactory(parser, shadowDomStrategy), viewLoader,
|
||||
shadowDomStrategy.hasNativeContentElement());
|
||||
}
|
||||
}
|
||||
|
@ -27,11 +27,11 @@ var _SELECTOR_REGEXP = RegExpWrapper.create(
|
||||
*/
|
||||
export class CssSelector {
|
||||
element: string = null;
|
||||
classNames: List<string> = [];
|
||||
attrs: List<string> = [];
|
||||
notSelectors: List<CssSelector> = [];
|
||||
classNames: string[] = [];
|
||||
attrs: string[] = [];
|
||||
notSelectors: CssSelector[] = [];
|
||||
|
||||
static parse(selector: string): List<CssSelector> {
|
||||
static parse(selector: string): CssSelector[] {
|
||||
var results: CssSelector[] = [];
|
||||
var _addResult = (res: CssSelector[], cssSel) => {
|
||||
if (cssSel.notSelectors.length > 0 && isBlank(cssSel.element) &&
|
||||
@ -135,21 +135,21 @@ export class CssSelector {
|
||||
* are contained in a given CssSelector.
|
||||
*/
|
||||
export class SelectorMatcher {
|
||||
static createNotMatcher(notSelectors: List<CssSelector>): SelectorMatcher {
|
||||
static createNotMatcher(notSelectors: CssSelector[]): SelectorMatcher {
|
||||
var notMatcher = new SelectorMatcher();
|
||||
notMatcher.addSelectables(notSelectors, null);
|
||||
return notMatcher;
|
||||
}
|
||||
|
||||
private _elementMap: Map<string, List<SelectorContext>> = new Map();
|
||||
private _elementMap: Map<string, SelectorContext[]> = new Map();
|
||||
private _elementPartialMap: Map<string, SelectorMatcher> = new Map();
|
||||
private _classMap: Map<string, List<SelectorContext>> = new Map();
|
||||
private _classMap: Map<string, SelectorContext[]> = new Map();
|
||||
private _classPartialMap: Map<string, SelectorMatcher> = new Map();
|
||||
private _attrValueMap: Map<string, Map<string, List<SelectorContext>>> = new Map();
|
||||
private _attrValueMap: Map<string, Map<string, SelectorContext[]>> = new Map();
|
||||
private _attrValuePartialMap: Map<string, Map<string, SelectorMatcher>> = new Map();
|
||||
private _listContexts: List<SelectorListContext> = [];
|
||||
private _listContexts: SelectorListContext[] = [];
|
||||
|
||||
addSelectables(cssSelectors: List<CssSelector>, callbackCtxt?: any) {
|
||||
addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) {
|
||||
var listContext = null;
|
||||
if (cssSelectors.length > 1) {
|
||||
listContext = new SelectorListContext(cssSelectors);
|
||||
@ -220,7 +220,7 @@ export class SelectorMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
private _addTerminal(map: Map<string, List<SelectorContext>>, name: string,
|
||||
private _addTerminal(map: Map<string, SelectorContext[]>, name: string,
|
||||
selectable: SelectorContext) {
|
||||
var terminalList = map.get(name);
|
||||
if (isBlank(terminalList)) {
|
||||
@ -298,7 +298,7 @@ export class SelectorMatcher {
|
||||
return result;
|
||||
}
|
||||
|
||||
_matchTerminal(map: Map<string, List<SelectorContext>>, name, cssSelector: CssSelector,
|
||||
_matchTerminal(map: Map<string, SelectorContext[]>, name, cssSelector: CssSelector,
|
||||
matchedCallback: (CssSelector, any) => void): boolean {
|
||||
if (isBlank(map) || isBlank(name)) {
|
||||
return false;
|
||||
@ -341,12 +341,12 @@ export class SelectorMatcher {
|
||||
class SelectorListContext {
|
||||
alreadyMatched: boolean = false;
|
||||
|
||||
constructor(public selectors: List<CssSelector>) {}
|
||||
constructor(public selectors: CssSelector[]) {}
|
||||
}
|
||||
|
||||
// Store context to pass back selector and context when a selector is matched
|
||||
class SelectorContext {
|
||||
notSelectors: List<CssSelector>;
|
||||
notSelectors: CssSelector[];
|
||||
|
||||
constructor(public selector: CssSelector, public cbContext: any,
|
||||
public listContext: SelectorListContext) {
|
||||
|
@ -26,7 +26,11 @@ export class TextInterpolationParser implements CompileStep {
|
||||
var expr = this._parser.parseInterpolation(text, current.elementDescription);
|
||||
if (isPresent(expr)) {
|
||||
DOM.setText(node, ' ');
|
||||
current.bindElement().bindText(node, expr);
|
||||
if (current.element === current.inheritedProtoView.rootElement) {
|
||||
current.inheritedProtoView.bindRootText(node, expr);
|
||||
} else {
|
||||
current.bindElement().bindText(node, expr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,23 +65,31 @@ export class ViewSplitter implements CompileStep {
|
||||
}
|
||||
}
|
||||
if (hasTemplateBinding) {
|
||||
var newParent = new CompileElement(DOM.createTemplate(''));
|
||||
newParent.inheritedProtoView = current.inheritedProtoView;
|
||||
newParent.inheritedElementBinder = current.inheritedElementBinder;
|
||||
newParent.distanceToInheritedBinder = current.distanceToInheritedBinder;
|
||||
var anchor = new CompileElement(DOM.createTemplate(''));
|
||||
anchor.inheritedProtoView = current.inheritedProtoView;
|
||||
anchor.inheritedElementBinder = current.inheritedElementBinder;
|
||||
anchor.distanceToInheritedBinder = current.distanceToInheritedBinder;
|
||||
// newParent doesn't appear in the original template, so we associate
|
||||
// the current element description to get a more meaningful message in case of error
|
||||
newParent.elementDescription = current.elementDescription;
|
||||
anchor.elementDescription = current.elementDescription;
|
||||
|
||||
current.inheritedProtoView = newParent.bindElement().bindNestedProtoView(current.element);
|
||||
var viewRoot = new CompileElement(DOM.createTemplate(''));
|
||||
viewRoot.inheritedProtoView = anchor.bindElement().bindNestedProtoView(viewRoot.element);
|
||||
// viewRoot doesn't appear in the original template, so we associate
|
||||
// the current element description to get a more meaningful message in case of error
|
||||
viewRoot.elementDescription = current.elementDescription;
|
||||
viewRoot.isViewRoot = true;
|
||||
|
||||
current.inheritedProtoView = viewRoot.inheritedProtoView;
|
||||
current.inheritedElementBinder = null;
|
||||
current.distanceToInheritedBinder = 0;
|
||||
current.isViewRoot = true;
|
||||
this._parseTemplateBindings(templateBindings, newParent);
|
||||
|
||||
this._addParentElement(current.element, newParent.element);
|
||||
control.addParent(newParent);
|
||||
DOM.remove(current.element);
|
||||
this._parseTemplateBindings(templateBindings, anchor);
|
||||
DOM.insertBefore(current.element, anchor.element);
|
||||
control.addParent(anchor);
|
||||
|
||||
DOM.appendChild(DOM.content(viewRoot.element), current.element);
|
||||
control.addParent(viewRoot);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,11 +102,6 @@ export class ViewSplitter implements CompileStep {
|
||||
}
|
||||
}
|
||||
|
||||
_addParentElement(currentElement, newParentElement) {
|
||||
DOM.insertBefore(currentElement, newParentElement);
|
||||
DOM.appendChild(newParentElement, currentElement);
|
||||
}
|
||||
|
||||
_parseTemplateBindings(templateBindings: string, compileElement: CompileElement) {
|
||||
var bindings =
|
||||
this._parser.parseTemplateBindings(templateBindings, compileElement.elementDescription);
|
||||
|
@ -10,17 +10,26 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src
|
||||
|
||||
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 {DomElement} from './view/element';
|
||||
import {DomViewContainer} from './view/view_container';
|
||||
import {NG_BINDING_CLASS_SELECTOR, NG_BINDING_CLASS, camelCaseToDashCase} from './util';
|
||||
import {DomFragmentRef, resolveInternalDomFragment} from './view/fragment';
|
||||
import {
|
||||
NG_BINDING_CLASS_SELECTOR,
|
||||
NG_BINDING_CLASS,
|
||||
cloneAndQueryProtoView,
|
||||
camelCaseToDashCase
|
||||
} from './util';
|
||||
|
||||
import {Renderer, RenderProtoViewRef, RenderViewRef, RenderElementRef} from '../api';
|
||||
import {
|
||||
Renderer,
|
||||
RenderProtoViewRef,
|
||||
RenderViewRef,
|
||||
RenderElementRef,
|
||||
RenderFragmentRef,
|
||||
RenderViewWithFragments
|
||||
} from '../api';
|
||||
|
||||
export const DOCUMENT_TOKEN = CONST_EXPR(new OpaqueToken('DocumentToken'));
|
||||
export const DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES =
|
||||
@ -32,8 +41,7 @@ export class DomRenderer extends Renderer {
|
||||
_document;
|
||||
_reflectPropertiesAsAttributes: boolean;
|
||||
|
||||
constructor(public _eventManager: EventManager, public _shadowDomStrategy: ShadowDomStrategy,
|
||||
@Inject(DOCUMENT_TOKEN) document,
|
||||
constructor(public _eventManager: EventManager, @Inject(DOCUMENT_TOKEN) document,
|
||||
@Inject(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES) reflectPropertiesAsAttributes:
|
||||
boolean) {
|
||||
super();
|
||||
@ -41,109 +49,57 @@ export class DomRenderer extends Renderer {
|
||||
this._document = document;
|
||||
}
|
||||
|
||||
createRootHostView(hostProtoViewRef: RenderProtoViewRef,
|
||||
hostElementSelector: string): RenderViewRef {
|
||||
createRootHostView(hostProtoViewRef: RenderProtoViewRef, fragmentCount: number,
|
||||
hostElementSelector: string): RenderViewWithFragments {
|
||||
var hostProtoView = resolveInternalDomProtoView(hostProtoViewRef);
|
||||
var element = DOM.querySelector(this._document, hostElementSelector);
|
||||
if (isBlank(element)) {
|
||||
throw new BaseException(`The selector "${hostElementSelector}" did not match any elements`);
|
||||
}
|
||||
return new DomViewRef(this._createView(hostProtoView, element));
|
||||
return this._createView(hostProtoView, element);
|
||||
}
|
||||
|
||||
createView(protoViewRef: RenderProtoViewRef): RenderViewRef {
|
||||
createView(protoViewRef: RenderProtoViewRef, fragmentCount: number): RenderViewWithFragments {
|
||||
var protoView = resolveInternalDomProtoView(protoViewRef);
|
||||
return new DomViewRef(this._createView(protoView, null));
|
||||
return this._createView(protoView, null);
|
||||
}
|
||||
|
||||
destroyView(view: RenderViewRef) {
|
||||
destroyView(viewRef: RenderViewRef) {
|
||||
// noop for now
|
||||
}
|
||||
|
||||
getNativeElementSync(location: RenderElementRef): any {
|
||||
if (isBlank(location.renderBoundElementIndex)) {
|
||||
return null;
|
||||
}
|
||||
return resolveInternalDomView(location.renderView)
|
||||
.boundElements[location.boundElementIndex]
|
||||
.element;
|
||||
.boundElements[location.renderBoundElementIndex];
|
||||
}
|
||||
|
||||
attachComponentView(location: RenderElementRef, componentViewRef: RenderViewRef) {
|
||||
var hostView = resolveInternalDomView(location.renderView);
|
||||
var componentView = resolveInternalDomView(componentViewRef);
|
||||
var element = hostView.boundElements[location.boundElementIndex].element;
|
||||
var lightDom = hostView.boundElements[location.boundElementIndex].lightDom;
|
||||
if (isPresent(lightDom)) {
|
||||
lightDom.attachShadowDomView(componentView);
|
||||
getRootNodes(fragment: RenderFragmentRef): List<Node> {
|
||||
return resolveInternalDomFragment(fragment);
|
||||
}
|
||||
|
||||
attachFragmentAfterFragment(previousFragmentRef: RenderFragmentRef,
|
||||
fragmentRef: RenderFragmentRef) {
|
||||
var previousFragmentNodes = resolveInternalDomFragment(previousFragmentRef);
|
||||
var sibling = previousFragmentNodes[previousFragmentNodes.length - 1];
|
||||
moveNodesAfterSibling(sibling, resolveInternalDomFragment(fragmentRef));
|
||||
}
|
||||
|
||||
attachFragmentAfterElement(elementRef: RenderElementRef, fragmentRef: RenderFragmentRef) {
|
||||
if (isBlank(elementRef.renderBoundElementIndex)) {
|
||||
return;
|
||||
}
|
||||
var shadowRoot = this._shadowDomStrategy.prepareShadowRoot(element);
|
||||
this._moveViewNodesIntoParent(shadowRoot, componentView);
|
||||
componentView.hostLightDom = lightDom;
|
||||
componentView.shadowRoot = shadowRoot;
|
||||
var parentView = resolveInternalDomView(elementRef.renderView);
|
||||
var element = parentView.boundElements[elementRef.renderBoundElementIndex];
|
||||
moveNodesAfterSibling(element, resolveInternalDomFragment(fragmentRef));
|
||||
}
|
||||
|
||||
setComponentViewRootNodes(componentViewRef: RenderViewRef, rootNodes: List</*node*/ any>) {
|
||||
var componentView = resolveInternalDomView(componentViewRef);
|
||||
this._removeViewNodes(componentView);
|
||||
componentView.rootNodes = rootNodes;
|
||||
this._moveViewNodesIntoParent(componentView.shadowRoot, componentView);
|
||||
}
|
||||
|
||||
getRootNodes(viewRef: RenderViewRef): List</*node*/ any> {
|
||||
return resolveInternalDomView(viewRef).rootNodes;
|
||||
}
|
||||
|
||||
detachComponentView(location: RenderElementRef, componentViewRef: RenderViewRef) {
|
||||
var hostView = resolveInternalDomView(location.renderView);
|
||||
var componentView = resolveInternalDomView(componentViewRef);
|
||||
this._removeViewNodes(componentView);
|
||||
var lightDom = hostView.boundElements[location.boundElementIndex].lightDom;
|
||||
if (isPresent(lightDom)) {
|
||||
lightDom.detachShadowDomView();
|
||||
}
|
||||
componentView.hostLightDom = null;
|
||||
componentView.shadowRoot = null;
|
||||
}
|
||||
|
||||
attachViewInContainer(location: RenderElementRef, atIndex: number, viewRef: RenderViewRef) {
|
||||
var parentView = resolveInternalDomView(location.renderView);
|
||||
var view = resolveInternalDomView(viewRef);
|
||||
var viewContainer = this._getOrCreateViewContainer(parentView, location.boundElementIndex);
|
||||
ListWrapper.insert(viewContainer.views, atIndex, view);
|
||||
view.hostLightDom = parentView.hostLightDom;
|
||||
|
||||
var directParentLightDom = this._directParentLightDom(parentView, location.boundElementIndex);
|
||||
if (isBlank(directParentLightDom)) {
|
||||
var siblingToInsertAfter;
|
||||
if (atIndex == 0) {
|
||||
siblingToInsertAfter = parentView.boundElements[location.boundElementIndex].element;
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
|
||||
detachViewInContainer(location: RenderElementRef, atIndex: number, viewRef: RenderViewRef) {
|
||||
var parentView = resolveInternalDomView(location.renderView);
|
||||
var view = resolveInternalDomView(viewRef);
|
||||
var viewContainer = parentView.boundElements[location.boundElementIndex].viewContainer;
|
||||
var detachedView = viewContainer.views[atIndex];
|
||||
ListWrapper.removeAt(viewContainer.views, atIndex);
|
||||
var directParentLightDom = this._directParentLightDom(parentView, location.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();
|
||||
detachFragment(fragmentRef: RenderFragmentRef) {
|
||||
var fragmentNodes = resolveInternalDomFragment(fragmentRef);
|
||||
for (var i = 0; i < fragmentNodes.length; i++) {
|
||||
DOM.remove(fragmentNodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,13 +108,6 @@ export class DomRenderer extends Renderer {
|
||||
if (view.hydrated) throw new BaseException('The view is already hydrated.');
|
||||
view.hydrated = true;
|
||||
|
||||
for (var i = 0; i < view.boundElements.length; ++i) {
|
||||
var lightDom = view.boundElements[i].lightDom;
|
||||
if (isPresent(lightDom)) {
|
||||
lightDom.redistribute();
|
||||
}
|
||||
}
|
||||
|
||||
// add global events
|
||||
view.eventHandlerRemovers = [];
|
||||
var binders = view.proto.elementBinders;
|
||||
@ -173,9 +122,6 @@ export class DomRenderer extends Renderer {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isPresent(view.hostLightDom)) {
|
||||
view.hostLightDom.redistribute();
|
||||
}
|
||||
}
|
||||
|
||||
dehydrateView(viewRef: RenderViewRef) {
|
||||
@ -191,8 +137,11 @@ export class DomRenderer extends Renderer {
|
||||
}
|
||||
|
||||
setElementProperty(location: RenderElementRef, propertyName: string, propertyValue: any): void {
|
||||
if (isBlank(location.renderBoundElementIndex)) {
|
||||
return;
|
||||
}
|
||||
var view = resolveInternalDomView(location.renderView);
|
||||
view.setElementProperty(location.boundElementIndex, propertyName, propertyValue);
|
||||
view.setElementProperty(location.renderBoundElementIndex, propertyName, propertyValue);
|
||||
// Reflect the property value as an attribute value with ng-reflect- prefix.
|
||||
if (this._reflectPropertiesAsAttributes) {
|
||||
this.setElementAttribute(location, `${REFLECT_PREFIX}${camelCaseToDashCase(propertyName)}`,
|
||||
@ -202,26 +151,41 @@ export class DomRenderer extends Renderer {
|
||||
|
||||
setElementAttribute(location: RenderElementRef, attributeName: string, attributeValue: string):
|
||||
void {
|
||||
if (isBlank(location.renderBoundElementIndex)) {
|
||||
return;
|
||||
}
|
||||
var view = resolveInternalDomView(location.renderView);
|
||||
view.setElementAttribute(location.boundElementIndex, attributeName, attributeValue);
|
||||
view.setElementAttribute(location.renderBoundElementIndex, attributeName, attributeValue);
|
||||
}
|
||||
|
||||
setElementClass(location: RenderElementRef, className: string, isAdd: boolean): void {
|
||||
if (isBlank(location.renderBoundElementIndex)) {
|
||||
return;
|
||||
}
|
||||
var view = resolveInternalDomView(location.renderView);
|
||||
view.setElementClass(location.boundElementIndex, className, isAdd);
|
||||
view.setElementClass(location.renderBoundElementIndex, className, isAdd);
|
||||
}
|
||||
|
||||
setElementStyle(location: RenderElementRef, styleName: string, styleValue: string): void {
|
||||
if (isBlank(location.renderBoundElementIndex)) {
|
||||
return;
|
||||
}
|
||||
var view = resolveInternalDomView(location.renderView);
|
||||
view.setElementStyle(location.boundElementIndex, styleName, styleValue);
|
||||
view.setElementStyle(location.renderBoundElementIndex, styleName, styleValue);
|
||||
}
|
||||
|
||||
invokeElementMethod(location: RenderElementRef, methodName: string, args: List<any>): void {
|
||||
if (isBlank(location.renderBoundElementIndex)) {
|
||||
return;
|
||||
}
|
||||
var view = resolveInternalDomView(location.renderView);
|
||||
view.invokeElementMethod(location.boundElementIndex, methodName, args);
|
||||
view.invokeElementMethod(location.renderBoundElementIndex, methodName, args);
|
||||
}
|
||||
|
||||
setText(viewRef: RenderViewRef, textNodeIndex: number, text: string): void {
|
||||
if (isBlank(textNodeIndex)) {
|
||||
return;
|
||||
}
|
||||
var view = resolveInternalDomView(viewRef);
|
||||
DOM.setText(view.boundTextNodes[textNodeIndex], text);
|
||||
}
|
||||
@ -231,99 +195,50 @@ export class DomRenderer extends Renderer {
|
||||
view.eventDispatcher = dispatcher;
|
||||
}
|
||||
|
||||
_createView(protoView: DomProtoView, inplaceElement): DomView {
|
||||
var rootElementClone;
|
||||
var elementsWithBindingsDynamic;
|
||||
var viewRootNodes;
|
||||
_createView(protoView: DomProtoView, inplaceElement: HTMLElement): RenderViewWithFragments {
|
||||
var clonedProtoView = cloneAndQueryProtoView(protoView, true);
|
||||
|
||||
var boundElements = clonedProtoView.boundElements;
|
||||
|
||||
// adopt inplaceElement
|
||||
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);
|
||||
viewRootNodes = ListWrapper.createFixedSize(protoView.rootNodeCount);
|
||||
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
|
||||
var childNode = DOM.firstChild(rootElementClone);
|
||||
for (var i = 0; i < protoView.rootNodeCount; i++, childNode = DOM.nextSibling(childNode)) {
|
||||
viewRootNodes[i] = childNode;
|
||||
if (protoView.fragmentsRootNodeCount[0] !== 1) {
|
||||
throw new BaseException('Root proto views can only contain one element!');
|
||||
}
|
||||
} else {
|
||||
rootElementClone = DOM.importIntoDoc(protoView.element);
|
||||
elementsWithBindingsDynamic = DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
|
||||
viewRootNodes = [rootElementClone];
|
||||
DOM.clearNodes(inplaceElement);
|
||||
var tempRoot = clonedProtoView.fragments[0][0];
|
||||
moveChildNodes(tempRoot, inplaceElement);
|
||||
if (boundElements.length > 0 && boundElements[0] === tempRoot) {
|
||||
boundElements[0] = inplaceElement;
|
||||
}
|
||||
clonedProtoView.fragments[0][0] = inplaceElement;
|
||||
}
|
||||
|
||||
var view = new DomView(protoView, clonedProtoView.boundTextNodes, boundElements);
|
||||
|
||||
var binders = protoView.elementBinders;
|
||||
var boundTextNodes = ListWrapper.createFixedSize(protoView.boundTextNodeCount);
|
||||
var boundElements = ListWrapper.createFixedSize(binders.length);
|
||||
var boundTextNodeIdx = 0;
|
||||
|
||||
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||
var binder = binders[binderIdx];
|
||||
var element;
|
||||
var childNodes;
|
||||
if (binderIdx === 0 && protoView.rootBindingOffset === 1) {
|
||||
// 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);
|
||||
} else {
|
||||
element = elementsWithBindingsDynamic[binderIdx - protoView.rootBindingOffset];
|
||||
childNodes = DOM.childNodes(element);
|
||||
}
|
||||
|
||||
// boundTextNodes
|
||||
var textNodeIndices = binder.textNodeIndices;
|
||||
for (var i = 0; i < textNodeIndices.length; i++) {
|
||||
boundTextNodes[boundTextNodeIdx++] = childNodes[textNodeIndices[i]];
|
||||
}
|
||||
|
||||
// contentTags
|
||||
var contentTag = null;
|
||||
if (isPresent(binder.contentTagSelector)) {
|
||||
contentTag = new Content(element, binder.contentTagSelector);
|
||||
}
|
||||
boundElements[binderIdx] = new DomElement(binder, element, contentTag);
|
||||
}
|
||||
|
||||
var view = new DomView(protoView, viewRootNodes, boundTextNodes, boundElements);
|
||||
|
||||
for (var binderIdx = 0; binderIdx < binders.length; binderIdx++) {
|
||||
var binder = binders[binderIdx];
|
||||
var element = boundElements[binderIdx];
|
||||
var domEl = element.element;
|
||||
|
||||
// lightDoms
|
||||
var lightDom = null;
|
||||
// Note: for the root element we can't use the binder.elementIsEmpty
|
||||
// information as we don't use the element from the ProtoView
|
||||
// but an element from the document.
|
||||
if (isPresent(binder.componentId) && (!binder.elementIsEmpty || isPresent(inplaceElement))) {
|
||||
lightDom = this._shadowDomStrategy.constructLightDom(view, domEl);
|
||||
}
|
||||
element.lightDom = lightDom;
|
||||
|
||||
// init contentTags
|
||||
var contentTag = element.contentTag;
|
||||
if (isPresent(contentTag)) {
|
||||
var directParentLightDom = this._directParentLightDom(view, binderIdx);
|
||||
contentTag.init(directParentLightDom);
|
||||
// native shadow DOM
|
||||
if (binder.hasNativeShadowRoot) {
|
||||
var shadowRootWrapper = DOM.firstChild(element);
|
||||
moveChildNodes(shadowRootWrapper, DOM.createShadowRoot(element));
|
||||
DOM.remove(shadowRootWrapper);
|
||||
}
|
||||
|
||||
// events
|
||||
if (isPresent(binder.eventLocals) && isPresent(binder.localEvents)) {
|
||||
for (var i = 0; i < binder.localEvents.length; i++) {
|
||||
this._createEventListener(view, domEl, binderIdx, binder.localEvents[i].name,
|
||||
this._createEventListener(view, element, binderIdx, binder.localEvents[i].name,
|
||||
binder.eventLocals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
return new RenderViewWithFragments(
|
||||
new DomViewRef(view), clonedProtoView.fragments.map(nodes => new DomFragmentRef(nodes)));
|
||||
}
|
||||
|
||||
_createEventListener(view, element, elementIndex, eventName, eventLocals) {
|
||||
@ -331,45 +246,26 @@ export class DomRenderer extends Renderer {
|
||||
element, eventName, (event) => { view.dispatchEvent(elementIndex, eventName, event); });
|
||||
}
|
||||
|
||||
|
||||
_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]);
|
||||
}
|
||||
}
|
||||
|
||||
_getOrCreateViewContainer(parentView: DomView, boundElementIndex) {
|
||||
var el = parentView.boundElements[boundElementIndex];
|
||||
var vc = el.viewContainer;
|
||||
if (isBlank(vc)) {
|
||||
vc = new DomViewContainer();
|
||||
el.viewContainer = vc;
|
||||
}
|
||||
return vc;
|
||||
}
|
||||
|
||||
_directParentLightDom(view: DomView, boundElementIndex: number) {
|
||||
var directParentEl = view.getDirectParentElement(boundElementIndex);
|
||||
return isPresent(directParentEl) ? directParentEl.lightDom : null;
|
||||
}
|
||||
|
||||
_createGlobalEventListener(view, elementIndex, eventName, eventTarget, fullName): Function {
|
||||
return this._eventManager.addGlobalEventListener(
|
||||
eventTarget, eventName, (event) => { view.dispatchEvent(elementIndex, fullName, event); });
|
||||
}
|
||||
}
|
||||
|
||||
function moveNodesAfterSibling(sibling, nodes) {
|
||||
if (isPresent(DOM.parentElement(sibling))) {
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
DOM.insertBefore(sibling, nodes[i]);
|
||||
}
|
||||
DOM.insertBefore(nodes[nodes.length - 1], sibling);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
import * as ldModule from './light_dom';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
class ContentStrategy {
|
||||
nodes: List</*node*/ any>;
|
||||
insert(nodes: List</*node*/ any>) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of the content tag that is used by transcluding components.
|
||||
* It is used when the content tag is not a direct child of another component,
|
||||
* and thus does not affect redistribution.
|
||||
*/
|
||||
class RenderedContent extends ContentStrategy {
|
||||
beginScript: any;
|
||||
endScript;
|
||||
|
||||
constructor(contentEl) {
|
||||
super();
|
||||
this.beginScript = contentEl;
|
||||
this.endScript = DOM.nextSibling(this.beginScript);
|
||||
this.nodes = [];
|
||||
}
|
||||
|
||||
// Inserts the nodes in between the start and end scripts.
|
||||
// Previous content is removed.
|
||||
insert(nodes: List</*node*/ any>) {
|
||||
this.nodes = nodes;
|
||||
DOM.insertAllBefore(this.endScript, nodes);
|
||||
this._removeNodesUntil(ListWrapper.isEmpty(nodes) ? this.endScript : nodes[0]);
|
||||
}
|
||||
|
||||
_removeNodesUntil(node) {
|
||||
var p = DOM.parentElement(this.beginScript);
|
||||
for (var next = DOM.nextSibling(this.beginScript); next !== node;
|
||||
next = DOM.nextSibling(this.beginScript)) {
|
||||
DOM.removeChild(p, next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of the content tag that is used by transcluding components.
|
||||
* It is used when the content tag is a direct child of another component,
|
||||
* and thus does not get rendered but only affect the distribution of its parent component.
|
||||
*/
|
||||
class IntermediateContent extends ContentStrategy {
|
||||
constructor(public destinationLightDom: ldModule.LightDom) {
|
||||
super();
|
||||
this.nodes = [];
|
||||
}
|
||||
|
||||
insert(nodes: List</*node*/ any>) {
|
||||
this.nodes = nodes;
|
||||
this.destinationLightDom.redistribute();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Content {
|
||||
private _strategy: ContentStrategy = null;
|
||||
|
||||
constructor(public contentStartElement, public select: string) {}
|
||||
|
||||
init(destinationLightDom: ldModule.LightDom) {
|
||||
this._strategy = isPresent(destinationLightDom) ? new IntermediateContent(destinationLightDom) :
|
||||
new RenderedContent(this.contentStartElement);
|
||||
}
|
||||
|
||||
nodes(): List</*node*/ any> { return this._strategy.nodes; }
|
||||
|
||||
insert(nodes: List</*node*/ any>) { this._strategy.insert(nodes); }
|
||||
}
|
@ -1,8 +1,5 @@
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import * as viewModule from '../view/view';
|
||||
|
||||
import {LightDom} from './light_dom';
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
import {insertSharedStyleText} from './util';
|
||||
|
||||
@ -20,12 +17,6 @@ export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
|
||||
|
||||
hasNativeContentElement(): boolean { return false; }
|
||||
|
||||
prepareShadowRoot(el): Node { return el; }
|
||||
|
||||
constructLightDom(lightDomView: viewModule.DomView, el): LightDom {
|
||||
return new LightDom(lightDomView, el);
|
||||
}
|
||||
|
||||
processStyleElement(hostComponentId: string, templateUrl: string, styleEl): void {
|
||||
var cssText = DOM.getText(styleEl);
|
||||
insertSharedStyleText(cssText, this.styleHost, styleEl);
|
||||
|
@ -1,141 +0,0 @@
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
|
||||
import * as viewModule from '../view/view';
|
||||
import * as elModule from '../view/element';
|
||||
import {Content} from './content_tag';
|
||||
|
||||
export class DestinationLightDom {}
|
||||
|
||||
class _Root {
|
||||
constructor(public node, public boundElement: elModule.DomElement) {}
|
||||
}
|
||||
|
||||
// TODO: LightDom should implement DestinationLightDom
|
||||
// once interfaces are supported
|
||||
export class LightDom {
|
||||
// The light DOM of the element is enclosed inside the lightDomView
|
||||
lightDomView: viewModule.DomView;
|
||||
// The shadow DOM
|
||||
shadowDomView: viewModule.DomView = null;
|
||||
// The nodes of the light DOM
|
||||
nodes: List</*node*/ any>;
|
||||
private _roots: List<_Root> = null;
|
||||
|
||||
constructor(lightDomView: viewModule.DomView, element) {
|
||||
this.lightDomView = lightDomView;
|
||||
this.nodes = DOM.childNodesAsList(element);
|
||||
}
|
||||
|
||||
attachShadowDomView(shadowDomView: viewModule.DomView) { this.shadowDomView = shadowDomView; }
|
||||
|
||||
detachShadowDomView() { this.shadowDomView = null; }
|
||||
|
||||
redistribute() { redistributeNodes(this.contentTags(), this.expandedDomNodes()); }
|
||||
|
||||
contentTags(): List<Content> {
|
||||
if (isPresent(this.shadowDomView)) {
|
||||
return this._collectAllContentTags(this.shadowDomView, []);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Collects the Content directives from the view and all its child views
|
||||
private _collectAllContentTags(view: viewModule.DomView, acc: List<Content>): List<Content> {
|
||||
// Note: exiting early here is important as we call this function for every view
|
||||
// that is added, so we have O(n^2) runtime.
|
||||
// TODO(tbosch): fix the root problem, see
|
||||
// https://github.com/angular/angular/issues/2298
|
||||
if (view.proto.transitiveContentTagCount === 0) {
|
||||
return acc;
|
||||
}
|
||||
var els = view.boundElements;
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
var el = els[i];
|
||||
if (isPresent(el.contentTag)) {
|
||||
acc.push(el.contentTag);
|
||||
}
|
||||
if (isPresent(el.viewContainer)) {
|
||||
ListWrapper.forEach(el.viewContainer.contentTagContainers(),
|
||||
(view) => { this._collectAllContentTags(view, acc); });
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
// Collects the nodes of the light DOM by merging:
|
||||
// - nodes from enclosed ViewContainers,
|
||||
// - nodes from enclosed content tags,
|
||||
// - plain DOM nodes
|
||||
expandedDomNodes(): List</*node*/ any> {
|
||||
var res = [];
|
||||
|
||||
var roots = this._findRoots();
|
||||
for (var i = 0; i < roots.length; ++i) {
|
||||
var root = roots[i];
|
||||
if (isPresent(root.boundElement)) {
|
||||
var vc = root.boundElement.viewContainer;
|
||||
var content = root.boundElement.contentTag;
|
||||
if (isPresent(vc)) {
|
||||
res = ListWrapper.concat(res, vc.nodes());
|
||||
} else if (isPresent(content)) {
|
||||
res = ListWrapper.concat(res, content.nodes());
|
||||
} else {
|
||||
res.push(root.node);
|
||||
}
|
||||
} else {
|
||||
res.push(root.node);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Returns a list of Roots for all the nodes of the light DOM.
|
||||
// The Root object contains the DOM node and its corresponding boundElement
|
||||
private _findRoots() {
|
||||
if (isPresent(this._roots)) return this._roots;
|
||||
|
||||
var boundElements = this.lightDomView.boundElements;
|
||||
|
||||
this._roots = ListWrapper.map(this.nodes, (n) => {
|
||||
var boundElement = null;
|
||||
for (var i = 0; i < boundElements.length; i++) {
|
||||
var boundEl = boundElements[i];
|
||||
if (isPresent(boundEl) && boundEl.element === n) {
|
||||
boundElement = boundEl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new _Root(n, boundElement);
|
||||
});
|
||||
|
||||
return this._roots;
|
||||
}
|
||||
}
|
||||
|
||||
// Projects the light DOM into the shadow DOM
|
||||
function redistributeNodes(contents: List<Content>, nodes: List</*node*/ any>) {
|
||||
for (var i = 0; i < contents.length; ++i) {
|
||||
var content = contents[i];
|
||||
var select = content.select;
|
||||
|
||||
// Empty selector is identical to <content/>
|
||||
if (select.length === 0) {
|
||||
content.insert(ListWrapper.clone(nodes));
|
||||
ListWrapper.clear(nodes);
|
||||
} else {
|
||||
var matchSelector = (n) => DOM.elementMatches(n, select);
|
||||
var matchingNodes = ListWrapper.filter(nodes, matchSelector);
|
||||
content.insert(matchingNodes);
|
||||
ListWrapper.removeAll(nodes, matchingNodes);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
if (isPresent(node.parentNode)) {
|
||||
DOM.remove(nodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
|
||||
/**
|
||||
@ -10,5 +9,5 @@ import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
*/
|
||||
@Injectable()
|
||||
export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
||||
prepareShadowRoot(el): Node { return DOM.createShadowRoot(el); }
|
||||
hasNativeContentElement(): boolean { return true; }
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
import {isBlank, isPresent, assertionsEnabled, isPromise} from 'angular2/src/facade/lang';
|
||||
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {CompileStep} from '../compiler/compile_step';
|
||||
@ -15,8 +13,6 @@ export class ShadowDomCompileStep implements CompileStep {
|
||||
var tagName = DOM.tagName(current.element).toUpperCase();
|
||||
if (tagName == 'STYLE') {
|
||||
this._processStyleElement(current, control);
|
||||
} else if (tagName == 'CONTENT') {
|
||||
this._processContentElement(current);
|
||||
} else {
|
||||
var componentId = current.isBound() ? current.inheritedElementBinder.componentId : null;
|
||||
this._shadowDomStrategy.processElement(this._view.componentId, componentId, current.element);
|
||||
@ -31,25 +27,4 @@ export class ShadowDomCompileStep implements CompileStep {
|
||||
// bindings. Skipping further compiler steps allow speeding up the compilation process.
|
||||
control.ignoreCurrentElement();
|
||||
}
|
||||
|
||||
_processContentElement(current: CompileElement) {
|
||||
if (this._shadowDomStrategy.hasNativeContentElement()) {
|
||||
return;
|
||||
}
|
||||
var attrs = current.attrs();
|
||||
var selector = attrs.get('select');
|
||||
selector = isPresent(selector) ? selector : '';
|
||||
|
||||
var contentStart = DOM.createScriptTag('type', 'ng/contentStart');
|
||||
if (assertionsEnabled()) {
|
||||
DOM.setAttribute(contentStart, 'select', selector);
|
||||
}
|
||||
var contentEnd = DOM.createScriptTag('type', 'ng/contentEnd');
|
||||
DOM.insertBefore(current.element, contentStart);
|
||||
DOM.insertBefore(current.element, contentEnd);
|
||||
DOM.remove(current.element);
|
||||
|
||||
current.element = contentStart;
|
||||
current.bindElement().setContentTagSelector(selector);
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,9 @@
|
||||
import {isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||
|
||||
import * as viewModule from '../view/view';
|
||||
import {LightDom} from './light_dom';
|
||||
|
||||
export class ShadowDomStrategy {
|
||||
// Whether the strategy understands the native <content> tag
|
||||
hasNativeContentElement(): boolean { return true; }
|
||||
|
||||
// Prepares and returns the (emulated) shadow root for the given element.
|
||||
prepareShadowRoot(el): any { return null; }
|
||||
|
||||
constructLightDom(lightDomView: viewModule.DomView, el): LightDom { return null; }
|
||||
|
||||
// An optional step that can modify the template style elements.
|
||||
processStyleElement(hostComponentId: string, templateUrl: string, styleElement): void {}
|
||||
|
||||
|
@ -1,13 +1,21 @@
|
||||
import {StringWrapper, isPresent} from 'angular2/src/facade/lang';
|
||||
import {StringWrapper, isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {DomProtoView} from './view/proto_view';
|
||||
import {DomElementBinder} from './view/element_binder';
|
||||
|
||||
export const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
|
||||
export const NG_BINDING_CLASS = 'ng-binding';
|
||||
|
||||
export const EVENT_TARGET_SEPARATOR = ':';
|
||||
|
||||
export const NG_CONTENT_ELEMENT_NAME = 'ng-content';
|
||||
export const NG_SHADOW_ROOT_ELEMENT_NAME = 'shadow-root';
|
||||
|
||||
var CAMEL_CASE_REGEXP = /([A-Z])/g;
|
||||
var DASH_CASE_REGEXP = /-([a-z])/g;
|
||||
|
||||
|
||||
export function camelCaseToDashCase(input: string): string {
|
||||
return StringWrapper.replaceAllMapped(input, CAMEL_CASE_REGEXP,
|
||||
(m) => { return '-' + m[1].toLowerCase(); });
|
||||
@ -17,3 +25,101 @@ export function dashCaseToCamelCase(input: string): string {
|
||||
return StringWrapper.replaceAllMapped(input, DASH_CASE_REGEXP,
|
||||
(m) => { return m[1].toUpperCase(); });
|
||||
}
|
||||
|
||||
// Attention: This is on the hot path, so don't use closures or default values!
|
||||
export function queryBoundElements(templateContent: Node, isSingleElementChild: boolean):
|
||||
Element[] {
|
||||
var result;
|
||||
var dynamicElementList;
|
||||
var elementIdx = 0;
|
||||
if (isSingleElementChild) {
|
||||
var rootElement = DOM.firstChild(templateContent);
|
||||
var rootHasBinding = DOM.hasClass(rootElement, NG_BINDING_CLASS);
|
||||
dynamicElementList = DOM.getElementsByClassName(rootElement, NG_BINDING_CLASS);
|
||||
result = ListWrapper.createFixedSize(dynamicElementList.length + (rootHasBinding ? 1 : 0));
|
||||
if (rootHasBinding) {
|
||||
result[elementIdx++] = rootElement;
|
||||
}
|
||||
} else {
|
||||
dynamicElementList = DOM.querySelectorAll(templateContent, NG_BINDING_CLASS_SELECTOR);
|
||||
result = ListWrapper.createFixedSize(dynamicElementList.length);
|
||||
}
|
||||
for (var i = 0; i < dynamicElementList.length; i++) {
|
||||
result[elementIdx++] = dynamicElementList[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export class ClonedProtoView {
|
||||
constructor(public original: DomProtoView, public fragments: Node[][],
|
||||
public boundElements: Element[], public boundTextNodes: Node[]) {}
|
||||
}
|
||||
|
||||
export function cloneAndQueryProtoView(pv: DomProtoView, importIntoDocument: boolean):
|
||||
ClonedProtoView {
|
||||
var templateContent = importIntoDocument ? DOM.importIntoDoc(DOM.content(pv.rootElement)) :
|
||||
DOM.clone(DOM.content(pv.rootElement));
|
||||
|
||||
var boundElements = queryBoundElements(templateContent, pv.isSingleElementFragment);
|
||||
var boundTextNodes = queryBoundTextNodes(templateContent, pv.rootTextNodeIndices, boundElements,
|
||||
pv.elementBinders, pv.boundTextNodeCount);
|
||||
|
||||
var fragments = queryFragments(templateContent, pv.fragmentsRootNodeCount);
|
||||
return new ClonedProtoView(pv, fragments, boundElements, boundTextNodes);
|
||||
}
|
||||
|
||||
function queryFragments(templateContent: Node, fragmentsRootNodeCount: number[]): Node[][] {
|
||||
var fragments = ListWrapper.createGrowableSize(fragmentsRootNodeCount.length);
|
||||
|
||||
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
|
||||
var childNode = DOM.firstChild(templateContent);
|
||||
for (var fragmentIndex = 0; fragmentIndex < fragments.length; fragmentIndex++) {
|
||||
var fragment = ListWrapper.createFixedSize(fragmentsRootNodeCount[fragmentIndex]);
|
||||
fragments[fragmentIndex] = fragment;
|
||||
for (var i = 0; i < fragment.length; i++) {
|
||||
fragment[i] = childNode;
|
||||
childNode = DOM.nextSibling(childNode);
|
||||
}
|
||||
}
|
||||
return fragments;
|
||||
}
|
||||
|
||||
function queryBoundTextNodes(templateContent: Node, rootTextNodeIndices: number[],
|
||||
boundElements: Element[], elementBinders: DomElementBinder[],
|
||||
boundTextNodeCount: number): Node[] {
|
||||
var boundTextNodes = ListWrapper.createFixedSize(boundTextNodeCount);
|
||||
var textNodeIndex = 0;
|
||||
if (rootTextNodeIndices.length > 0) {
|
||||
var rootChildNodes = DOM.childNodes(templateContent);
|
||||
for (var i = 0; i < rootTextNodeIndices.length; i++) {
|
||||
boundTextNodes[textNodeIndex++] = rootChildNodes[rootTextNodeIndices[i]];
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < elementBinders.length; i++) {
|
||||
var binder = elementBinders[i];
|
||||
var element: Node = boundElements[i];
|
||||
if (binder.textNodeIndices.length > 0) {
|
||||
var childNodes = DOM.childNodes(element);
|
||||
for (var j = 0; j < binder.textNodeIndices.length; j++) {
|
||||
boundTextNodes[textNodeIndex++] = childNodes[binder.textNodeIndices[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return boundTextNodes;
|
||||
}
|
||||
|
||||
|
||||
export function isElementWithTag(node: Node, elementName: string): boolean {
|
||||
return DOM.isElementNode(node) && DOM.tagName(node).toLowerCase() == elementName.toLowerCase();
|
||||
}
|
||||
|
||||
export function queryBoundTextNodeIndices(parentNode: Node, boundTextNodes: Map<Node, any>,
|
||||
resultCallback: Function) {
|
||||
var childNodes = DOM.childNodes(parentNode);
|
||||
for (var j = 0; j < childNodes.length; j++) {
|
||||
var node = childNodes[j];
|
||||
if (boundTextNodes.has(node)) {
|
||||
resultCallback(node, j, boundTextNodes.get(node));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import {ElementBinder} from './element_binder';
|
||||
import {DomViewContainer} from './view_container';
|
||||
import {LightDom} from '../shadow_dom/light_dom';
|
||||
import {Content} from '../shadow_dom/content_tag';
|
||||
|
||||
export class DomElement {
|
||||
viewContainer: DomViewContainer;
|
||||
lightDom: LightDom;
|
||||
constructor(public proto: ElementBinder, public element: any /* element */,
|
||||
public contentTag: Content) {}
|
||||
}
|
@ -1,42 +1,29 @@
|
||||
import {AST} from 'angular2/change_detection';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import * as protoViewModule from './proto_view';
|
||||
|
||||
export class ElementBinder {
|
||||
contentTagSelector: string;
|
||||
export class DomElementBinder {
|
||||
textNodeIndices: List<number>;
|
||||
nestedProtoView: protoViewModule.DomProtoView;
|
||||
hasNestedProtoView: boolean;
|
||||
eventLocals: AST;
|
||||
localEvents: List<Event>;
|
||||
globalEvents: List<Event>;
|
||||
componentId: string;
|
||||
parentIndex: number;
|
||||
distanceToParent: number;
|
||||
elementIsEmpty: boolean;
|
||||
hasNativeShadowRoot: boolean;
|
||||
|
||||
constructor({textNodeIndices, contentTagSelector, nestedProtoView, componentId, eventLocals,
|
||||
localEvents, globalEvents, parentIndex, distanceToParent, elementIsEmpty}: {
|
||||
contentTagSelector?: string,
|
||||
constructor({textNodeIndices, hasNestedProtoView, eventLocals, localEvents, globalEvents,
|
||||
hasNativeShadowRoot}: {
|
||||
textNodeIndices?: List<number>,
|
||||
nestedProtoView?: protoViewModule.DomProtoView,
|
||||
hasNestedProtoView?: boolean,
|
||||
eventLocals?: AST,
|
||||
localEvents?: List<Event>,
|
||||
globalEvents?: List<Event>,
|
||||
componentId?: string,
|
||||
parentIndex?: number,
|
||||
distanceToParent?: number,
|
||||
elementIsEmpty?: boolean
|
||||
hasNativeShadowRoot?: boolean
|
||||
} = {}) {
|
||||
this.textNodeIndices = textNodeIndices;
|
||||
this.contentTagSelector = contentTagSelector;
|
||||
this.nestedProtoView = nestedProtoView;
|
||||
this.componentId = componentId;
|
||||
this.hasNestedProtoView = hasNestedProtoView;
|
||||
this.eventLocals = eventLocals;
|
||||
this.localEvents = localEvents;
|
||||
this.globalEvents = globalEvents;
|
||||
this.parentIndex = parentIndex;
|
||||
this.distanceToParent = distanceToParent;
|
||||
this.elementIsEmpty = elementIsEmpty;
|
||||
this.hasNativeShadowRoot = hasNativeShadowRoot;
|
||||
}
|
||||
}
|
||||
|
||||
|
9
modules/angular2/src/render/dom/view/fragment.ts
Normal file
9
modules/angular2/src/render/dom/view/fragment.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import {RenderFragmentRef} from '../../api';
|
||||
|
||||
export function resolveInternalDomFragment(fragmentRef: RenderFragmentRef): Node[] {
|
||||
return (<DomFragmentRef>fragmentRef)._nodes;
|
||||
}
|
||||
|
||||
export class DomFragmentRef extends RenderFragmentRef {
|
||||
constructor(public _nodes: Node[]) { super(); }
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
import {isBlank} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
|
||||
import {ElementBinder} from './element_binder';
|
||||
import {NG_BINDING_CLASS} from '../util';
|
||||
import {DomElementBinder} from './element_binder';
|
||||
import {RenderProtoViewRef, ViewType} from '../../api';
|
||||
|
||||
import {RenderProtoViewRef} from '../../api';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
|
||||
export function resolveInternalDomProtoView(protoViewRef: RenderProtoViewRef): DomProtoView {
|
||||
return (<DomProtoViewRef>protoViewRef)._protoView;
|
||||
@ -17,24 +15,40 @@ export class DomProtoViewRef extends RenderProtoViewRef {
|
||||
}
|
||||
|
||||
export class DomProtoView {
|
||||
element;
|
||||
elementBinders: List<ElementBinder>;
|
||||
isTemplateElement: boolean;
|
||||
rootBindingOffset: number;
|
||||
// the number of content tags seen in this or any child proto view.
|
||||
transitiveContentTagCount: number;
|
||||
boundTextNodeCount: number;
|
||||
rootNodeCount: number;
|
||||
|
||||
constructor({elementBinders, element, transitiveContentTagCount, boundTextNodeCount}) {
|
||||
this.element = element;
|
||||
this.elementBinders = elementBinders;
|
||||
this.transitiveContentTagCount = transitiveContentTagCount;
|
||||
this.isTemplateElement = DOM.isTemplateElement(this.element);
|
||||
this.rootBindingOffset =
|
||||
(isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) ? 1 : 0;
|
||||
this.boundTextNodeCount = boundTextNodeCount;
|
||||
this.rootNodeCount =
|
||||
this.isTemplateElement ? DOM.childNodes(DOM.content(this.element)).length : 1;
|
||||
static create(type: ViewType, rootElement: Element, fragmentsRootNodeCount: number[],
|
||||
rootTextNodeIndices: number[], elementBinders: List<DomElementBinder>,
|
||||
mappedElementIndices: number[], mappedTextIndices: number[],
|
||||
hostElementIndicesByViewIndex: number[]): DomProtoView {
|
||||
var boundTextNodeCount = rootTextNodeIndices.length;
|
||||
for (var i = 0; i < elementBinders.length; i++) {
|
||||
boundTextNodeCount += elementBinders[i].textNodeIndices.length;
|
||||
}
|
||||
if (isBlank(mappedElementIndices)) {
|
||||
mappedElementIndices = ListWrapper.createFixedSize(elementBinders.length);
|
||||
for (var i = 0; i < mappedElementIndices.length; i++) {
|
||||
mappedElementIndices[i] = i;
|
||||
}
|
||||
}
|
||||
if (isBlank(mappedTextIndices)) {
|
||||
mappedTextIndices = ListWrapper.createFixedSize(boundTextNodeCount);
|
||||
for (var i = 0; i < mappedTextIndices.length; i++) {
|
||||
mappedTextIndices[i] = i;
|
||||
}
|
||||
}
|
||||
if (isBlank(hostElementIndicesByViewIndex)) {
|
||||
hostElementIndicesByViewIndex = [null];
|
||||
}
|
||||
var isSingleElementFragment = fragmentsRootNodeCount.length === 1 &&
|
||||
fragmentsRootNodeCount[0] === 1 &&
|
||||
DOM.isElementNode(DOM.firstChild(DOM.content(rootElement)));
|
||||
return new DomProtoView(type, rootElement, elementBinders, rootTextNodeIndices,
|
||||
boundTextNodeCount, fragmentsRootNodeCount, isSingleElementFragment,
|
||||
mappedElementIndices, mappedTextIndices, hostElementIndicesByViewIndex);
|
||||
}
|
||||
|
||||
constructor(public type: ViewType, public rootElement: Element,
|
||||
public elementBinders: List<DomElementBinder>, public rootTextNodeIndices: number[],
|
||||
public boundTextNodeCount: number, public fragmentsRootNodeCount: number[],
|
||||
public isSingleElementFragment: boolean, public mappedElementIndices: number[],
|
||||
public mappedTextIndices: number[], public hostElementIndicesByViewIndex: number[]) {}
|
||||
}
|
||||
|
@ -19,17 +19,19 @@ import {
|
||||
} from 'angular2/change_detection';
|
||||
|
||||
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view';
|
||||
import {ElementBinder, Event, HostAction} from './element_binder';
|
||||
import {DomElementBinder, Event, HostAction} from './element_binder';
|
||||
|
||||
import * as api from '../../api';
|
||||
|
||||
import {NG_BINDING_CLASS, EVENT_TARGET_SEPARATOR} from '../util';
|
||||
import {NG_BINDING_CLASS, EVENT_TARGET_SEPARATOR, queryBoundTextNodeIndices} from '../util';
|
||||
|
||||
export class ProtoViewBuilder {
|
||||
variableBindings: Map<string, string> = new Map();
|
||||
elements: List<ElementBinderBuilder> = [];
|
||||
rootTextBindings: Map<Node, ASTWithSource> = new Map();
|
||||
|
||||
constructor(public rootElement, public type: api.ViewType) {}
|
||||
constructor(public rootElement, public type: api.ViewType,
|
||||
public useNativeShadowDom: boolean = false) {}
|
||||
|
||||
bindElement(element, description = null): ElementBinderBuilder {
|
||||
var builder = new ElementBinderBuilder(this.elements.length, element, description);
|
||||
@ -49,12 +51,22 @@ export class ProtoViewBuilder {
|
||||
this.variableBindings.set(value, name);
|
||||
}
|
||||
|
||||
// Note: We don't store the node index until the compilation is complete,
|
||||
// as the compiler might change the order of elements.
|
||||
bindRootText(textNode, expression) { this.rootTextBindings.set(textNode, expression); }
|
||||
|
||||
build(): api.ProtoViewDto {
|
||||
var renderElementBinders = [];
|
||||
var domElementBinders = [];
|
||||
|
||||
var apiElementBinders = [];
|
||||
var transitiveContentTagCount = 0;
|
||||
var boundTextNodeCount = 0;
|
||||
var textNodeExpressions = [];
|
||||
var rootTextNodeIndices = [];
|
||||
queryBoundTextNodeIndices(DOM.content(this.rootElement), this.rootTextBindings,
|
||||
(node, nodeIndex, expression) => {
|
||||
textNodeExpressions.push(expression);
|
||||
rootTextNodeIndices.push(nodeIndex);
|
||||
});
|
||||
|
||||
ListWrapper.forEach(this.elements, (ebb: ElementBinderBuilder) => {
|
||||
var directiveTemplatePropertyNames = new Set();
|
||||
var apiDirectiveBinders = ListWrapper.map(ebb.directives, (dbb: DirectiveBuilder) => {
|
||||
@ -71,15 +83,12 @@ export class ProtoViewBuilder {
|
||||
});
|
||||
});
|
||||
var nestedProtoView = isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build() : null;
|
||||
var nestedRenderProtoView =
|
||||
isPresent(nestedProtoView) ? resolveInternalDomProtoView(nestedProtoView.render) : null;
|
||||
if (isPresent(nestedRenderProtoView)) {
|
||||
transitiveContentTagCount += nestedRenderProtoView.transitiveContentTagCount;
|
||||
}
|
||||
if (isPresent(ebb.contentTagSelector)) {
|
||||
transitiveContentTagCount++;
|
||||
}
|
||||
var parentIndex = isPresent(ebb.parent) ? ebb.parent.index : -1;
|
||||
var textNodeIndices = [];
|
||||
queryBoundTextNodeIndices(ebb.element, ebb.textBindings, (node, nodeIndex, expression) => {
|
||||
textNodeExpressions.push(expression);
|
||||
textNodeIndices.push(nodeIndex);
|
||||
});
|
||||
apiElementBinders.push(new api.ElementBinder({
|
||||
index: ebb.index,
|
||||
parentIndex: parentIndex,
|
||||
@ -91,64 +100,28 @@ export class ProtoViewBuilder {
|
||||
ebb.propertyBindings, directiveTemplatePropertyNames),
|
||||
variableBindings: ebb.variableBindings,
|
||||
eventBindings: ebb.eventBindings,
|
||||
textBindings: ebb.textBindings,
|
||||
readAttributes: ebb.readAttributes
|
||||
}));
|
||||
var childNodeInfo = this._analyzeChildNodes(ebb.element, ebb.textBindingNodes);
|
||||
boundTextNodeCount += ebb.textBindingNodes.length;
|
||||
renderElementBinders.push(new ElementBinder({
|
||||
textNodeIndices: childNodeInfo.boundTextNodeIndices,
|
||||
contentTagSelector: ebb.contentTagSelector,
|
||||
parentIndex: parentIndex,
|
||||
distanceToParent: ebb.distanceToParent,
|
||||
nestedProtoView:
|
||||
isPresent(nestedProtoView) ? resolveInternalDomProtoView(nestedProtoView.render) : null,
|
||||
componentId: ebb.componentId,
|
||||
domElementBinders.push(new DomElementBinder({
|
||||
textNodeIndices: textNodeIndices,
|
||||
hasNestedProtoView: isPresent(nestedProtoView) || isPresent(ebb.componentId),
|
||||
hasNativeShadowRoot: isPresent(ebb.componentId) && this.useNativeShadowDom,
|
||||
eventLocals: new LiteralArray(ebb.eventBuilder.buildEventLocals()),
|
||||
localEvents: ebb.eventBuilder.buildLocalEvents(),
|
||||
globalEvents: ebb.eventBuilder.buildGlobalEvents(),
|
||||
elementIsEmpty: childNodeInfo.elementIsEmpty
|
||||
globalEvents: ebb.eventBuilder.buildGlobalEvents()
|
||||
}));
|
||||
});
|
||||
var rootNodeCount = DOM.childNodes(DOM.content(this.rootElement)).length;
|
||||
return new api.ProtoViewDto({
|
||||
render: new DomProtoViewRef(new DomProtoView({
|
||||
element: this.rootElement,
|
||||
elementBinders: renderElementBinders,
|
||||
transitiveContentTagCount: transitiveContentTagCount,
|
||||
boundTextNodeCount: boundTextNodeCount
|
||||
})),
|
||||
render: new DomProtoViewRef(DomProtoView.create(this.type, this.rootElement, [rootNodeCount],
|
||||
rootTextNodeIndices, domElementBinders, null,
|
||||
null, null)),
|
||||
type: this.type,
|
||||
elementBinders: apiElementBinders,
|
||||
variableBindings: this.variableBindings
|
||||
variableBindings: this.variableBindings,
|
||||
textBindings: textNodeExpressions
|
||||
});
|
||||
}
|
||||
|
||||
// Note: We need to calculate the next node indices not until the compilation is complete,
|
||||
// as the compiler might change the order of elements.
|
||||
private _analyzeChildNodes(parentElement: /*element*/ any,
|
||||
boundTextNodes: List</*node*/ any>): _ChildNodesInfo {
|
||||
var childNodes = DOM.childNodes(DOM.templateAwareRoot(parentElement));
|
||||
var boundTextNodeIndices = [];
|
||||
var indexInBoundTextNodes = 0;
|
||||
var elementIsEmpty = true;
|
||||
for (var i = 0; i < childNodes.length; i++) {
|
||||
var node = childNodes[i];
|
||||
if (indexInBoundTextNodes < boundTextNodes.length &&
|
||||
node === boundTextNodes[indexInBoundTextNodes]) {
|
||||
boundTextNodeIndices.push(i);
|
||||
indexInBoundTextNodes++;
|
||||
elementIsEmpty = false;
|
||||
} else if ((DOM.isTextNode(node) && DOM.getText(node).trim().length > 0) ||
|
||||
(DOM.isElementNode(node))) {
|
||||
elementIsEmpty = false;
|
||||
}
|
||||
}
|
||||
return new _ChildNodesInfo(boundTextNodeIndices, elementIsEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
class _ChildNodesInfo {
|
||||
constructor(public boundTextNodeIndices: List<number>, public elementIsEmpty: boolean) {}
|
||||
}
|
||||
|
||||
export class ElementBinderBuilder {
|
||||
@ -161,9 +134,7 @@ export class ElementBinderBuilder {
|
||||
propertyBindingsToDirectives: Set<string> = new Set();
|
||||
eventBindings: List<api.EventBinding> = [];
|
||||
eventBuilder: EventBuilder = new EventBuilder();
|
||||
textBindingNodes: List</*node*/ any> = [];
|
||||
textBindings: List<ASTWithSource> = [];
|
||||
contentTagSelector: string = null;
|
||||
textBindings: Map<Node, ASTWithSource> = new Map();
|
||||
readAttributes: Map<string, string> = new Map();
|
||||
componentId: string = null;
|
||||
|
||||
@ -229,12 +200,9 @@ export class ElementBinderBuilder {
|
||||
this.eventBindings.push(this.eventBuilder.add(name, expression, target));
|
||||
}
|
||||
|
||||
bindText(textNode, expression) {
|
||||
this.textBindingNodes.push(textNode);
|
||||
this.textBindings.push(expression);
|
||||
}
|
||||
|
||||
setContentTagSelector(value: string) { this.contentTagSelector = value; }
|
||||
// Note: We don't store the node index until the compilation is complete,
|
||||
// as the compiler might change the order of elements.
|
||||
bindText(textNode, expression) { this.textBindings.set(textNode, expression); }
|
||||
|
||||
setComponentId(componentId: string) { this.componentId = componentId; }
|
||||
}
|
||||
|
451
modules/angular2/src/render/dom/view/proto_view_merger.ts
Normal file
451
modules/angular2/src/render/dom/view/proto_view_merger.ts
Normal file
@ -0,0 +1,451 @@
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {isPresent, isBlank, BaseException, isArray} from 'angular2/src/facade/lang';
|
||||
|
||||
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view';
|
||||
import {DomElementBinder} from './element_binder';
|
||||
import {RenderProtoViewMergeMapping, RenderProtoViewRef, ViewType} from '../../api';
|
||||
import {
|
||||
NG_BINDING_CLASS,
|
||||
NG_CONTENT_ELEMENT_NAME,
|
||||
ClonedProtoView,
|
||||
cloneAndQueryProtoView,
|
||||
queryBoundElements,
|
||||
queryBoundTextNodeIndices,
|
||||
NG_SHADOW_ROOT_ELEMENT_NAME,
|
||||
isElementWithTag
|
||||
} from '../util';
|
||||
import {CssSelector} from '../compiler/selector';
|
||||
|
||||
const NOT_MATCHABLE_SELECTOR = '_not-matchable_';
|
||||
|
||||
export function mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRef | List<any>>):
|
||||
RenderProtoViewMergeMapping[] {
|
||||
var target = [];
|
||||
_mergeProtoViewsRecursively(protoViewRefs, target);
|
||||
return target;
|
||||
}
|
||||
|
||||
function _mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRef | List<any>>,
|
||||
target: RenderProtoViewMergeMapping[]): RenderProtoViewRef {
|
||||
var targetIndex = target.length;
|
||||
target.push(null);
|
||||
|
||||
var resolvedProtoViewRefs = protoViewRefs.map((entry) => {
|
||||
if (isArray(entry)) {
|
||||
return _mergeProtoViewsRecursively(<List<any>>entry, target);
|
||||
} else {
|
||||
return entry;
|
||||
}
|
||||
});
|
||||
var mapping = mergeProtoViews(resolvedProtoViewRefs);
|
||||
target[targetIndex] = mapping;
|
||||
return mapping.mergedProtoViewRef;
|
||||
}
|
||||
|
||||
export function mergeProtoViews(protoViewRefs: RenderProtoViewRef[]): RenderProtoViewMergeMapping {
|
||||
var hostProtoView = resolveInternalDomProtoView(protoViewRefs[0]);
|
||||
|
||||
var mergeableProtoViews: DomProtoView[] = [];
|
||||
var hostElementIndices: number[] = [];
|
||||
|
||||
mergeableProtoViews.push(hostProtoView);
|
||||
var protoViewIdx = 1;
|
||||
for (var i = 0; i < hostProtoView.elementBinders.length; i++) {
|
||||
var binder = hostProtoView.elementBinders[i];
|
||||
if (binder.hasNestedProtoView) {
|
||||
var nestedProtoViewRef = protoViewRefs[protoViewIdx++];
|
||||
if (isPresent(nestedProtoViewRef)) {
|
||||
mergeableProtoViews.push(resolveInternalDomProtoView(nestedProtoViewRef));
|
||||
hostElementIndices.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _mergeProtoViews(mergeableProtoViews, hostElementIndices);
|
||||
}
|
||||
|
||||
|
||||
function _mergeProtoViews(mergeableProtoViews: DomProtoView[], hostElementIndices: number[]):
|
||||
RenderProtoViewMergeMapping {
|
||||
var clonedProtoViews: ClonedProtoView[] =
|
||||
mergeableProtoViews.map(domProtoView => cloneAndQueryProtoView(domProtoView, false));
|
||||
var hostProtoView: ClonedProtoView = clonedProtoViews[0];
|
||||
|
||||
// modify the DOM
|
||||
mergeDom(clonedProtoViews, hostElementIndices);
|
||||
|
||||
// create a new root element with the changed fragments and elements
|
||||
var rootElement = createRootElementFromFragments(hostProtoView.fragments);
|
||||
var fragmentsRootNodeCount = hostProtoView.fragments.map(fragment => fragment.length);
|
||||
var rootNode = DOM.content(rootElement);
|
||||
|
||||
// read out the new element / text node / ElementBinder order
|
||||
var mergedBoundElements = queryBoundElements(rootNode, false);
|
||||
var mergedBoundTextIndices: Map<Node, number> = new Map();
|
||||
var boundTextNodeMap: Map<Node, any> = indexBoundTextNodes(clonedProtoViews);
|
||||
var rootTextNodeIndices =
|
||||
calcRootTextNodeIndices(rootNode, boundTextNodeMap, mergedBoundTextIndices);
|
||||
var mergedElementBinders = calcElementBinders(clonedProtoViews, mergedBoundElements,
|
||||
boundTextNodeMap, mergedBoundTextIndices);
|
||||
|
||||
// create element / text index mappings
|
||||
var mappedElementIndices = calcMappedElementIndices(clonedProtoViews, mergedBoundElements);
|
||||
var mappedTextIndices = calcMappedTextIndices(clonedProtoViews, mergedBoundTextIndices);
|
||||
var hostElementIndicesByViewIndex =
|
||||
calcHostElementIndicesByViewIndex(clonedProtoViews, hostElementIndices);
|
||||
|
||||
// create result
|
||||
var mergedProtoView = DomProtoView.create(
|
||||
hostProtoView.original.type, rootElement, fragmentsRootNodeCount, rootTextNodeIndices,
|
||||
mergedElementBinders, mappedElementIndices, mappedTextIndices, hostElementIndicesByViewIndex);
|
||||
return new RenderProtoViewMergeMapping(new DomProtoViewRef(mergedProtoView),
|
||||
fragmentsRootNodeCount.length, mappedElementIndices,
|
||||
mappedTextIndices, hostElementIndicesByViewIndex);
|
||||
}
|
||||
|
||||
function indexBoundTextNodes(mergableProtoViews: ClonedProtoView[]): Map<Node, any> {
|
||||
var boundTextNodeMap = new Map();
|
||||
for (var pvIndex = 0; pvIndex < mergableProtoViews.length; pvIndex++) {
|
||||
var mergableProtoView = mergableProtoViews[pvIndex];
|
||||
mergableProtoView.boundTextNodes.forEach(
|
||||
(textNode) => { boundTextNodeMap.set(textNode, null); });
|
||||
}
|
||||
return boundTextNodeMap;
|
||||
}
|
||||
|
||||
function mergeDom(clonedProtoViews: ClonedProtoView[], hostElementIndices: number[]) {
|
||||
var nestedProtoViewByHostElement: Map<Element, ClonedProtoView> =
|
||||
indexProtoViewsByHostElement(clonedProtoViews, hostElementIndices);
|
||||
|
||||
var hostProtoView = clonedProtoViews[0];
|
||||
var mergableProtoViewIdx = 1;
|
||||
hostElementIndices.forEach((boundElementIndex) => {
|
||||
var binder = hostProtoView.original.elementBinders[boundElementIndex];
|
||||
if (binder.hasNestedProtoView) {
|
||||
var mergableNestedProtoView: ClonedProtoView = clonedProtoViews[mergableProtoViewIdx++];
|
||||
if (mergableNestedProtoView.original.type === ViewType.COMPONENT) {
|
||||
mergeComponentDom(hostProtoView, boundElementIndex, mergableNestedProtoView,
|
||||
nestedProtoViewByHostElement);
|
||||
} else {
|
||||
mergeEmbeddedDom(hostProtoView, mergableNestedProtoView);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function indexProtoViewsByHostElement(mergableProtoViews: ClonedProtoView[],
|
||||
hostElementIndices: number[]): Map<Element, ClonedProtoView> {
|
||||
var hostProtoView = mergableProtoViews[0];
|
||||
var mergableProtoViewIdx = 1;
|
||||
var nestedProtoViewByHostElement: Map<Element, ClonedProtoView> = new Map();
|
||||
hostElementIndices.forEach((hostElementIndex) => {
|
||||
nestedProtoViewByHostElement.set(hostProtoView.boundElements[hostElementIndex],
|
||||
mergableProtoViews[mergableProtoViewIdx++]);
|
||||
});
|
||||
return nestedProtoViewByHostElement;
|
||||
}
|
||||
|
||||
function mergeComponentDom(hostProtoView: ClonedProtoView, boundElementIndex: number,
|
||||
nestedProtoView: ClonedProtoView,
|
||||
nestedProtoViewByHostElement: Map<Element, ClonedProtoView>) {
|
||||
var hostElement = hostProtoView.boundElements[boundElementIndex];
|
||||
|
||||
// We wrap the fragments into elements so that we can expand <ng-content>
|
||||
// even for root nodes in the fragment without special casing them.
|
||||
var fragmentElements = mapFragmentsIntoElements(nestedProtoView.fragments);
|
||||
var contentElements = findContentElements(fragmentElements);
|
||||
|
||||
var projectableNodes = DOM.childNodesAsList(hostElement);
|
||||
for (var i = 0; i < contentElements.length; i++) {
|
||||
var contentElement = contentElements[i];
|
||||
var select = DOM.getAttribute(contentElement, 'select');
|
||||
projectableNodes = projectMatchingNodes(select, contentElement, projectableNodes);
|
||||
}
|
||||
|
||||
// unwrap the fragment elements into arrays of nodes after projecting
|
||||
var fragments = extractFragmentNodesFromElements(fragmentElements);
|
||||
appendComponentNodesToHost(hostProtoView, boundElementIndex, fragments[0]);
|
||||
|
||||
for (var i = 1; i < fragments.length; i++) {
|
||||
hostProtoView.fragments.push(fragments[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function mapFragmentsIntoElements(fragments: Node[][]): Element[] {
|
||||
return fragments.map((fragment) => {
|
||||
var fragmentElement = DOM.createTemplate('');
|
||||
fragment.forEach(node => DOM.appendChild(DOM.content(fragmentElement), node));
|
||||
return fragmentElement;
|
||||
});
|
||||
}
|
||||
|
||||
function extractFragmentNodesFromElements(fragmentElements: Element[]): Node[][] {
|
||||
return fragmentElements.map(
|
||||
(fragmentElement) => { return DOM.childNodesAsList(DOM.content(fragmentElement)); });
|
||||
}
|
||||
|
||||
function findContentElements(fragmentElements: Element[]): Element[] {
|
||||
var contentElements = [];
|
||||
fragmentElements.forEach((fragmentElement: Element) => {
|
||||
var fragmentContentElements =
|
||||
DOM.querySelectorAll(DOM.content(fragmentElement), NG_CONTENT_ELEMENT_NAME);
|
||||
for (var i = 0; i < fragmentContentElements.length; i++) {
|
||||
contentElements.push(fragmentContentElements[i]);
|
||||
}
|
||||
});
|
||||
return sortContentElements(contentElements);
|
||||
}
|
||||
|
||||
function appendComponentNodesToHost(hostProtoView: ClonedProtoView, boundElementIndex: number,
|
||||
componentRootNodes: Node[]) {
|
||||
var hostElement = hostProtoView.boundElements[boundElementIndex];
|
||||
var binder = hostProtoView.original.elementBinders[boundElementIndex];
|
||||
if (binder.hasNativeShadowRoot) {
|
||||
var shadowRootWrapper = DOM.createElement(NG_SHADOW_ROOT_ELEMENT_NAME);
|
||||
for (var i = 0; i < componentRootNodes.length; i++) {
|
||||
DOM.appendChild(shadowRootWrapper, componentRootNodes[i]);
|
||||
}
|
||||
var firstChild = DOM.firstChild(hostElement);
|
||||
if (isPresent(firstChild)) {
|
||||
DOM.insertBefore(firstChild, shadowRootWrapper);
|
||||
} else {
|
||||
DOM.appendChild(hostElement, shadowRootWrapper);
|
||||
}
|
||||
} else {
|
||||
DOM.clearNodes(hostElement);
|
||||
for (var i = 0; i < componentRootNodes.length; i++) {
|
||||
DOM.appendChild(hostElement, componentRootNodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mergeEmbeddedDom(parentProtoView: ClonedProtoView, nestedProtoView: ClonedProtoView) {
|
||||
nestedProtoView.fragments.forEach((fragment) => parentProtoView.fragments.push(fragment));
|
||||
}
|
||||
|
||||
function projectMatchingNodes(selector: string, contentElement: Element, nodes: Node[]): Node[] {
|
||||
var remaining = [];
|
||||
var removeContentElement = true;
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
if (isWildcard(selector)) {
|
||||
DOM.insertBefore(contentElement, node);
|
||||
} else if (DOM.isElementNode(node)) {
|
||||
if (isElementWithTag(node, NG_CONTENT_ELEMENT_NAME)) {
|
||||
// keep the projected content as other <ng-content> elements
|
||||
// might want to use it as well.
|
||||
remaining.push(node);
|
||||
DOM.setAttribute(contentElement, 'select',
|
||||
mergeSelectors(selector, DOM.getAttribute(node, 'select')));
|
||||
removeContentElement = false;
|
||||
} else {
|
||||
if (DOM.elementMatches(node, selector)) {
|
||||
DOM.insertBefore(contentElement, node);
|
||||
} else {
|
||||
remaining.push(node);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// non projected text nodes
|
||||
remaining.push(node);
|
||||
}
|
||||
}
|
||||
if (removeContentElement) {
|
||||
DOM.remove(contentElement);
|
||||
}
|
||||
return remaining;
|
||||
}
|
||||
|
||||
function isWildcard(selector): boolean {
|
||||
return isBlank(selector) || selector.length === 0 || selector == '*';
|
||||
}
|
||||
|
||||
export function mergeSelectors(selector1: string, selector2: string): string {
|
||||
if (isWildcard(selector1)) {
|
||||
return isBlank(selector2) ? '' : selector2;
|
||||
} else if (isWildcard(selector2)) {
|
||||
return isBlank(selector1) ? '' : selector1;
|
||||
} else {
|
||||
var sels1 = CssSelector.parse(selector1);
|
||||
var sels2 = CssSelector.parse(selector2);
|
||||
if (sels1.length > 1 || sels2.length > 1) {
|
||||
throw new BaseException('multiple selectors are not supported in ng-content');
|
||||
}
|
||||
var sel1 = sels1[0];
|
||||
var sel2 = sels2[0];
|
||||
if (sel1.notSelectors.length > 0 || sel2.notSelectors.length > 0) {
|
||||
throw new BaseException(':not selector is not supported in ng-content');
|
||||
}
|
||||
var merged = new CssSelector();
|
||||
if (isBlank(sel1.element)) {
|
||||
merged.setElement(sel2.element);
|
||||
} else if (isBlank(sel2.element)) {
|
||||
merged.setElement(sel1.element);
|
||||
} else {
|
||||
return NOT_MATCHABLE_SELECTOR;
|
||||
}
|
||||
merged.attrs = sel1.attrs.concat(sel2.attrs);
|
||||
merged.classNames = sel1.classNames.concat(sel2.classNames);
|
||||
return merged.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// we need to sort content elements as they can originate from
|
||||
// different sub views
|
||||
function sortContentElements(contentElements: Element[]): Element[] {
|
||||
// for now, only move the wildcard selector to the end.
|
||||
// TODO(tbosch): think about sorting by selector specifity...
|
||||
var firstWildcard = null;
|
||||
var sorted = [];
|
||||
contentElements.forEach((contentElement) => {
|
||||
var select = DOM.getAttribute(contentElement, 'select');
|
||||
if (isWildcard(select)) {
|
||||
if (isBlank(firstWildcard)) {
|
||||
firstWildcard = contentElement;
|
||||
}
|
||||
} else {
|
||||
sorted.push(contentElement);
|
||||
}
|
||||
});
|
||||
if (isPresent(firstWildcard)) {
|
||||
sorted.push(firstWildcard);
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
|
||||
|
||||
function createRootElementFromFragments(fragments: Node[][]): Element {
|
||||
var rootElement = DOM.createTemplate('');
|
||||
var rootNode = DOM.content(rootElement);
|
||||
fragments.forEach(
|
||||
(fragment) => { fragment.forEach((node) => { DOM.appendChild(rootNode, node); }); });
|
||||
return rootElement;
|
||||
}
|
||||
|
||||
function calcRootTextNodeIndices(rootNode: Node, boundTextNodes: Map<Node, any>,
|
||||
targetBoundTextIndices: Map<Node, number>): number[] {
|
||||
var rootTextNodeIndices = [];
|
||||
queryBoundTextNodeIndices(rootNode, boundTextNodes, (textNode, nodeIndex, _) => {
|
||||
rootTextNodeIndices.push(nodeIndex);
|
||||
targetBoundTextIndices.set(textNode, targetBoundTextIndices.size);
|
||||
});
|
||||
return rootTextNodeIndices;
|
||||
}
|
||||
|
||||
function calcElementBinders(clonedProtoViews: ClonedProtoView[], mergedBoundElements: Element[],
|
||||
boundTextNodes: Map<Node, any>,
|
||||
targetBoundTextIndices: Map<Node, number>): DomElementBinder[] {
|
||||
var elementBinderByElement: Map<Element, DomElementBinder> =
|
||||
indexElementBindersByElement(clonedProtoViews);
|
||||
var mergedElementBinders = [];
|
||||
for (var i = 0; i < mergedBoundElements.length; i++) {
|
||||
var element = mergedBoundElements[i];
|
||||
var textNodeIndices = [];
|
||||
queryBoundTextNodeIndices(element, boundTextNodes, (textNode, nodeIndex, _) => {
|
||||
textNodeIndices.push(nodeIndex);
|
||||
targetBoundTextIndices.set(textNode, targetBoundTextIndices.size);
|
||||
});
|
||||
mergedElementBinders.push(
|
||||
updateElementBinderTextNodeIndices(elementBinderByElement.get(element), textNodeIndices));
|
||||
}
|
||||
return mergedElementBinders;
|
||||
}
|
||||
|
||||
function indexElementBindersByElement(mergableProtoViews: ClonedProtoView[]):
|
||||
Map<Element, DomElementBinder> {
|
||||
var elementBinderByElement = new Map();
|
||||
mergableProtoViews.forEach((mergableProtoView) => {
|
||||
for (var i = 0; i < mergableProtoView.boundElements.length; i++) {
|
||||
var el = mergableProtoView.boundElements[i];
|
||||
if (isPresent(el)) {
|
||||
elementBinderByElement.set(el, mergableProtoView.original.elementBinders[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
return elementBinderByElement;
|
||||
}
|
||||
|
||||
function updateElementBinderTextNodeIndices(elementBinder: DomElementBinder,
|
||||
textNodeIndices: number[]): DomElementBinder {
|
||||
var result;
|
||||
if (isBlank(elementBinder)) {
|
||||
result = new DomElementBinder({
|
||||
textNodeIndices: textNodeIndices,
|
||||
hasNestedProtoView: false,
|
||||
eventLocals: null,
|
||||
localEvents: [],
|
||||
globalEvents: [],
|
||||
hasNativeShadowRoot: null
|
||||
});
|
||||
} else {
|
||||
result = new DomElementBinder({
|
||||
textNodeIndices: textNodeIndices,
|
||||
hasNestedProtoView: false,
|
||||
eventLocals: elementBinder.eventLocals,
|
||||
localEvents: elementBinder.localEvents,
|
||||
globalEvents: elementBinder.globalEvents,
|
||||
hasNativeShadowRoot: elementBinder.hasNativeShadowRoot
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function calcMappedElementIndices(clonedProtoViews: ClonedProtoView[],
|
||||
mergedBoundElements: Element[]): number[] {
|
||||
var mergedBoundElementIndices: Map<Element, number> = indexArray(mergedBoundElements);
|
||||
var mappedElementIndices = [];
|
||||
clonedProtoViews.forEach((clonedProtoView) => {
|
||||
clonedProtoView.original.mappedElementIndices.forEach((boundElementIndex) => {
|
||||
var mappedElementIndex = null;
|
||||
if (isPresent(boundElementIndex)) {
|
||||
var boundElement = clonedProtoView.boundElements[boundElementIndex];
|
||||
mappedElementIndex = mergedBoundElementIndices.get(boundElement);
|
||||
}
|
||||
mappedElementIndices.push(mappedElementIndex);
|
||||
});
|
||||
});
|
||||
return mappedElementIndices;
|
||||
}
|
||||
|
||||
function calcMappedTextIndices(clonedProtoViews: ClonedProtoView[],
|
||||
mergedBoundTextIndices: Map<Node, number>): number[] {
|
||||
var mappedTextIndices = [];
|
||||
clonedProtoViews.forEach((clonedProtoView) => {
|
||||
clonedProtoView.original.mappedTextIndices.forEach((textNodeIndex) => {
|
||||
var mappedTextIndex = null;
|
||||
if (isPresent(textNodeIndex)) {
|
||||
var textNode = clonedProtoView.boundTextNodes[textNodeIndex];
|
||||
mappedTextIndex = mergedBoundTextIndices.get(textNode);
|
||||
}
|
||||
mappedTextIndices.push(mappedTextIndex);
|
||||
});
|
||||
});
|
||||
return mappedTextIndices;
|
||||
}
|
||||
|
||||
function calcHostElementIndicesByViewIndex(clonedProtoViews: ClonedProtoView[],
|
||||
hostElementIndices: number[]): number[] {
|
||||
var mergedElementCount = 0;
|
||||
var hostElementIndicesByViewIndex = [];
|
||||
for (var i = 0; i < clonedProtoViews.length; i++) {
|
||||
var clonedProtoView = clonedProtoViews[i];
|
||||
clonedProtoView.original.hostElementIndicesByViewIndex.forEach((hostElementIndex) => {
|
||||
var mappedHostElementIndex;
|
||||
if (isBlank(hostElementIndex)) {
|
||||
mappedHostElementIndex = i > 0 ? hostElementIndices[i - 1] : null;
|
||||
} else {
|
||||
mappedHostElementIndex = hostElementIndex + mergedElementCount;
|
||||
}
|
||||
hostElementIndicesByViewIndex.push(mappedHostElementIndex);
|
||||
});
|
||||
mergedElementCount += clonedProtoView.original.mappedElementIndices.length;
|
||||
}
|
||||
return hostElementIndicesByViewIndex;
|
||||
}
|
||||
|
||||
function indexArray(arr: any[]): Map<any, number> {
|
||||
var map = new Map();
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
map.set(arr[i], i);
|
||||
}
|
||||
return map;
|
||||
}
|
@ -3,10 +3,8 @@ import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src
|
||||
import {isPresent, isBlank, BaseException, stringify} from 'angular2/src/facade/lang';
|
||||
|
||||
import {DomProtoView} from './proto_view';
|
||||
import {LightDom} from '../shadow_dom/light_dom';
|
||||
import {DomElement} from './element';
|
||||
|
||||
import {RenderViewRef, EventDispatcher} from '../../api';
|
||||
import {RenderViewRef, RenderEventDispatcher} from '../../api';
|
||||
import {camelCaseToDashCase} from '../util';
|
||||
|
||||
export function resolveInternalDomView(viewRef: RenderViewRef): DomView {
|
||||
@ -21,30 +19,19 @@ export class DomViewRef extends RenderViewRef {
|
||||
* Const of making objects: http://jsperf.com/instantiate-size-of-object
|
||||
*/
|
||||
export class DomView {
|
||||
hostLightDom: LightDom = null;
|
||||
shadowRoot = null;
|
||||
hydrated: boolean = false;
|
||||
eventDispatcher: EventDispatcher = null;
|
||||
eventDispatcher: RenderEventDispatcher = null;
|
||||
eventHandlerRemovers: List<Function> = [];
|
||||
|
||||
constructor(public proto: DomProtoView, public rootNodes: List</*node*/ any>,
|
||||
public boundTextNodes: List</*node*/ any>, public boundElements: List<DomElement>) {}
|
||||
|
||||
getDirectParentElement(boundElementIndex: number): DomElement {
|
||||
var binder = this.proto.elementBinders[boundElementIndex];
|
||||
var parent = null;
|
||||
if (binder.parentIndex !== -1 && binder.distanceToParent === 1) {
|
||||
parent = this.boundElements[binder.parentIndex];
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
constructor(public proto: DomProtoView, public boundTextNodes: List<Node>,
|
||||
public boundElements: Element[]) {}
|
||||
|
||||
setElementProperty(elementIndex: number, propertyName: string, value: any) {
|
||||
DOM.setProperty(this.boundElements[elementIndex].element, propertyName, value);
|
||||
DOM.setProperty(this.boundElements[elementIndex], propertyName, value);
|
||||
}
|
||||
|
||||
setElementAttribute(elementIndex: number, attributeName: string, value: string) {
|
||||
var element = this.boundElements[elementIndex].element;
|
||||
var element = this.boundElements[elementIndex];
|
||||
var dashCasedAttributeName = camelCaseToDashCase(attributeName);
|
||||
if (isPresent(value)) {
|
||||
DOM.setAttribute(element, dashCasedAttributeName, stringify(value));
|
||||
@ -54,7 +41,7 @@ export class DomView {
|
||||
}
|
||||
|
||||
setElementClass(elementIndex: number, className: string, isAdd: boolean) {
|
||||
var element = this.boundElements[elementIndex].element;
|
||||
var element = this.boundElements[elementIndex];
|
||||
var dashCasedClassName = camelCaseToDashCase(className);
|
||||
if (isAdd) {
|
||||
DOM.addClass(element, dashCasedClassName);
|
||||
@ -64,7 +51,7 @@ export class DomView {
|
||||
}
|
||||
|
||||
setElementStyle(elementIndex: number, styleName: string, value: string) {
|
||||
var element = this.boundElements[elementIndex].element;
|
||||
var element = this.boundElements[elementIndex];
|
||||
var dashCasedStyleName = camelCaseToDashCase(styleName);
|
||||
if (isPresent(value)) {
|
||||
DOM.setStyle(element, dashCasedStyleName, stringify(value));
|
||||
@ -74,7 +61,7 @@ export class DomView {
|
||||
}
|
||||
|
||||
invokeElementMethod(elementIndex: number, methodName: string, args: List<any>) {
|
||||
var element = this.boundElements[elementIndex].element;
|
||||
var element = this.boundElements[elementIndex];
|
||||
DOM.invoke(element, methodName, args);
|
||||
}
|
||||
|
||||
@ -91,7 +78,7 @@ export class DomView {
|
||||
// Locals(null, evalLocals));
|
||||
// this.eventDispatcher.dispatchEvent(elementIndex, eventName, localValues);
|
||||
allowDefaultBehavior =
|
||||
this.eventDispatcher.dispatchEvent(elementIndex, eventName, evalLocals);
|
||||
this.eventDispatcher.dispatchRenderEvent(elementIndex, eventName, evalLocals);
|
||||
if (!allowDefaultBehavior) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
@ -5,14 +5,4 @@ import * as viewModule from './view';
|
||||
export class DomViewContainer {
|
||||
// The order in this list matches the DOM order.
|
||||
views: List<viewModule.DomView> = [];
|
||||
|
||||
contentTagContainers(): List<viewModule.DomView> { return this.views; }
|
||||
|
||||
nodes(): List</*node*/ any> {
|
||||
var r = [];
|
||||
for (var i = 0; i < this.views.length; ++i) {
|
||||
r = ListWrapper.concat(r, this.views[i].rootNodes);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user