feat(components): initial implementation of emulated content tag
This commit is contained in:
parent
0f8f4801bd
commit
fbcc59dc67
|
@ -13,6 +13,7 @@ import {createDefaultSteps} from './pipeline/default_steps';
|
|||
import {TemplateLoader} from './template_loader';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {Component} from '../annotations/annotations';
|
||||
import {Content} from './shadow_dom_emulation/content_tag';
|
||||
|
||||
/**
|
||||
* Cache that stores the ProtoView of the template of a component.
|
||||
|
@ -60,13 +61,8 @@ export class Compiler {
|
|||
}
|
||||
|
||||
createSteps(component:DirectiveMetadata):List<CompileStep> {
|
||||
var annotation: Component = component.annotation;
|
||||
var directives = annotation.template.directives;
|
||||
var annotatedDirectives = ListWrapper.create();
|
||||
for (var i=0; i<directives.length; i++) {
|
||||
ListWrapper.push(annotatedDirectives, this._reader.read(directives[i]));
|
||||
}
|
||||
return createDefaultSteps(this._parser, component, annotatedDirectives);
|
||||
var dirs = ListWrapper.map(component.componentDirectives, (d) => this._reader.read(d));
|
||||
return createDefaultSteps(this._parser, component, dirs);
|
||||
}
|
||||
|
||||
compile(component:Type, templateRoot:Element = null):Promise<ProtoView> {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {Type, FIELD} from 'facade/lang';
|
||||
import {Directive} from '../annotations/annotations'
|
||||
import {List} from 'facade/collection'
|
||||
import {ShadowDomStrategy} from './shadow_dom';
|
||||
|
||||
/**
|
||||
|
@ -9,10 +10,13 @@ export class DirectiveMetadata {
|
|||
type:Type;
|
||||
annotation:Directive;
|
||||
shadowDomStrategy:ShadowDomStrategy;
|
||||
componentDirectives:List<Type>;
|
||||
|
||||
constructor(type:Type, annotation:Directive, shadowDomStrategy:ShadowDomStrategy) {
|
||||
constructor(type:Type, annotation:Directive, shadowDomStrategy:ShadowDomStrategy,
|
||||
componentDirectives:List<Type>) {
|
||||
this.annotation = annotation;
|
||||
this.type = type;
|
||||
this.shadowDomStrategy = shadowDomStrategy;
|
||||
this.componentDirectives = componentDirectives;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {Type, isPresent, BaseException, stringify} from 'facade/lang';
|
||||
import {List, ListWrapper} from 'facade/collection';
|
||||
import {Directive, Component} from '../annotations/annotations';
|
||||
import {DirectiveMetadata} from './directive_metadata';
|
||||
import {reflector} from 'reflection/reflection';
|
||||
|
@ -12,11 +13,17 @@ export class DirectiveMetadataReader {
|
|||
var annotation = annotations[i];
|
||||
|
||||
if (annotation instanceof Component) {
|
||||
return new DirectiveMetadata(type, annotation, this.parseShadowDomStrategy(annotation));
|
||||
var shadowDomStrategy = this.parseShadowDomStrategy(annotation);
|
||||
return new DirectiveMetadata(
|
||||
type,
|
||||
annotation,
|
||||
shadowDomStrategy,
|
||||
this.componentDirectivesMetadata(annotation, shadowDomStrategy)
|
||||
);
|
||||
}
|
||||
|
||||
if (annotation instanceof Directive) {
|
||||
return new DirectiveMetadata(type, annotation, null);
|
||||
return new DirectiveMetadata(type, annotation, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,4 +33,16 @@ export class DirectiveMetadataReader {
|
|||
parseShadowDomStrategy(annotation:Component):ShadowDomStrategy{
|
||||
return isPresent(annotation.shadowDom) ? annotation.shadowDom : ShadowDomNative;
|
||||
}
|
||||
|
||||
componentDirectivesMetadata(annotation:Component, shadowDomStrategy:ShadowDomStrategy):List<Type> {
|
||||
var polyDirs = shadowDomStrategy.polyfillDirectives();
|
||||
var template = annotation.template;
|
||||
var templateDirs = isPresent(template) && isPresent(template.directives) ? template.directives : [];
|
||||
|
||||
var res = [];
|
||||
res = ListWrapper.concat(res, templateDirs)
|
||||
res = ListWrapper.concat(res, polyDirs)
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import {List, ListWrapper} from 'facade/collection';
|
|||
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'di/di';
|
||||
import {Parent, Ancestor} from 'core/annotations/visibility';
|
||||
import {View} from 'core/compiler/view';
|
||||
import {LightDom, SourceLightDom, DestinationLightDom} from 'core/compiler/shadow_dom_emulation/light_dom';
|
||||
import {ViewPort} from 'core/compiler/viewport';
|
||||
import {NgElement} from 'core/dom/element';
|
||||
|
||||
|
@ -19,11 +20,16 @@ class StaticKeys {
|
|||
viewId:int;
|
||||
ngElementId:int;
|
||||
viewPortId:int;
|
||||
destinationLightDomId:int;
|
||||
sourceLightDomId:int;
|
||||
|
||||
constructor() {
|
||||
//TODO: vsavkin Key.annotate(Key.get(View), 'static')
|
||||
this.viewId = Key.get(View).id;
|
||||
this.ngElementId = Key.get(NgElement).id;
|
||||
this.viewPortId = Key.get(ViewPort).id;
|
||||
this.destinationLightDomId = Key.get(DestinationLightDom).id;
|
||||
this.sourceLightDomId = Key.get(SourceLightDom).id;
|
||||
}
|
||||
|
||||
static instance() {
|
||||
|
@ -105,10 +111,12 @@ export class PreBuiltObjects {
|
|||
view:View;
|
||||
element:NgElement;
|
||||
viewPort:ViewPort;
|
||||
constructor(view, element:NgElement, viewPort: ViewPort) {
|
||||
lightDom:LightDom;
|
||||
constructor(view, element:NgElement, viewPort:ViewPort, lightDom:LightDom) {
|
||||
this.view = view;
|
||||
this.element = element;
|
||||
this.viewPort = viewPort;
|
||||
this.lightDom = lightDom;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -306,6 +314,15 @@ export class ElementInjector extends TreeNode {
|
|||
return this._getByKey(Key.get(token), 0, null);
|
||||
}
|
||||
|
||||
hasDirective(type:Type):boolean {
|
||||
return this._getDirectiveByKeyId(Key.get(type).id) !== _undefined;
|
||||
}
|
||||
|
||||
hasPreBuiltObject(type:Type):boolean {
|
||||
var pb = this._getPreBuiltObjectByKeyId(Key.get(type).id);
|
||||
return pb !== _undefined && isPresent(pb);
|
||||
}
|
||||
|
||||
getComponent() {
|
||||
if (this._proto._binding0IsComponent) {
|
||||
return this._obj0;
|
||||
|
@ -421,11 +438,15 @@ export class ElementInjector extends TreeNode {
|
|||
var staticKeys = StaticKeys.instance();
|
||||
if (keyId === staticKeys.viewId) return this._preBuiltObjects.view;
|
||||
if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element;
|
||||
if (keyId === staticKeys.viewPortId) {
|
||||
if (isBlank(staticKeys.viewPortId)) throw new BaseException(
|
||||
'ViewPort is constructed only for @Template directives');
|
||||
return this._preBuiltObjects.viewPort;
|
||||
if (keyId === staticKeys.viewPortId) return this._preBuiltObjects.viewPort;
|
||||
if (keyId === staticKeys.destinationLightDomId) {
|
||||
var p:ElementInjector = this._parent;
|
||||
return isPresent(p) ? p._preBuiltObjects.lightDom : null;
|
||||
}
|
||||
if (keyId === staticKeys.sourceLightDomId) {
|
||||
return this._host._preBuiltObjects.lightDom;
|
||||
}
|
||||
|
||||
//TODO add other objects as needed
|
||||
return _undefined;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import {Decorator} from '../../annotations/annotations';
|
||||
import {SourceLightDom, DestinationLightDom, LightDom} from './light_dom';
|
||||
import {Inject} from 'di/di';
|
||||
import {Element, Node, DOM} from 'facade/dom';
|
||||
import {List, ListWrapper} from 'facade/collection';
|
||||
import {NgElement} from 'core/dom/element';
|
||||
|
||||
var _scriptTemplate = DOM.createScriptTag('type', 'ng/content')
|
||||
|
||||
@Decorator({
|
||||
selector: 'content'
|
||||
})
|
||||
export class Content {
|
||||
_destinationLightDom:LightDom;
|
||||
|
||||
_beginScript:Element;
|
||||
_endScript:Element;
|
||||
|
||||
select:string;
|
||||
|
||||
constructor(@Inject(DestinationLightDom) destinationLightDom, contentEl:NgElement) {
|
||||
this._destinationLightDom = destinationLightDom;
|
||||
|
||||
this.select = contentEl.getAttribute('select');
|
||||
|
||||
this._replaceContentElementWithScriptTags(contentEl.domElement);
|
||||
}
|
||||
|
||||
insert(nodes:List<Node>) {
|
||||
DOM.insertAllBefore(this._endScript, nodes);
|
||||
this._removeNodesUntil(ListWrapper.isEmpty(nodes) ? this._endScript : nodes[0]);
|
||||
}
|
||||
|
||||
_replaceContentElementWithScriptTags(contentEl:Element) {
|
||||
this._beginScript = DOM.clone(_scriptTemplate);
|
||||
this._endScript = DOM.clone(_scriptTemplate);
|
||||
|
||||
DOM.insertBefore(contentEl, this._beginScript);
|
||||
DOM.insertBefore(contentEl, this._endScript);
|
||||
DOM.removeChild(DOM.parentElement(contentEl), contentEl);
|
||||
}
|
||||
|
||||
_removeNodesUntil(node: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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
import {Element, Node, DOM} from 'facade/dom';
|
||||
import {List, ListWrapper} from 'facade/collection';
|
||||
import {isBlank, isPresent} from 'facade/lang';
|
||||
|
||||
import {View} from '../view';
|
||||
import {ElementInjector} from '../element_injector';
|
||||
import {ViewPort} from '../viewport';
|
||||
import {Content} from './content_tag';
|
||||
|
||||
export class SourceLightDom {}
|
||||
export class DestinationLightDom {}
|
||||
|
||||
// TODO: LightDom should implement SourceLightDom and DestinationLightDom
|
||||
// once interfaces are supported
|
||||
export class LightDom {
|
||||
lightDomView:View;
|
||||
shadowDomView:View;
|
||||
roots:List<Node>;
|
||||
|
||||
constructor(lightDomView:View, shadowDomView:View, element:Element) {
|
||||
this.lightDomView = lightDomView;
|
||||
this.shadowDomView = shadowDomView;
|
||||
this.roots = DOM.childNodesAsList(element);
|
||||
DOM.clearNodes(element);
|
||||
}
|
||||
|
||||
redistribute() {
|
||||
redistributeNodes(this.contentTags(), this.expandedDomNodes());
|
||||
}
|
||||
|
||||
contentTags(): List<Content> {
|
||||
return this._collectAllContentTags(this.shadowDomView, []);
|
||||
}
|
||||
|
||||
_collectAllContentTags(item, acc:List<Content>):List<Content> {
|
||||
ListWrapper.forEach(item.elementInjectors, (ei) => {
|
||||
if (ei.hasDirective(Content)) {
|
||||
ListWrapper.push(acc, ei.get(Content));
|
||||
|
||||
} else if (ei.hasPreBuiltObject(ViewPort)) {
|
||||
var vp = ei.get(ViewPort);
|
||||
ListWrapper.forEach(vp.contentTagContainers(), (c) => {
|
||||
this._collectAllContentTags(c, acc);
|
||||
});
|
||||
}
|
||||
});
|
||||
return acc;
|
||||
}
|
||||
|
||||
expandedDomNodes():List {
|
||||
var res = [];
|
||||
ListWrapper.forEach(this.roots, (root) => {
|
||||
// TODO: vsavkin calculcate this info statically when creating light dom
|
||||
var viewPort = this.lightDomView.getViewPortByTemplateElement(root);
|
||||
if (isPresent(viewPort)) {
|
||||
res = ListWrapper.concat(res, viewPort.nodes());
|
||||
} else {
|
||||
ListWrapper.push(res, root);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
function redistributeNodes(contents:List<Content>, nodes:List<Node>) {
|
||||
for (var i = 0; i < contents.length; ++i) {
|
||||
var content = contents[i];
|
||||
var select = content.select;
|
||||
var matchSelector = (n) => DOM.elementMatches(n, select);
|
||||
|
||||
if (isBlank(select)) {
|
||||
content.insert(nodes);
|
||||
ListWrapper.clear(nodes);
|
||||
|
||||
} else {
|
||||
var matchingNodes = ListWrapper.filter(nodes, matchSelector);
|
||||
content.insert(matchingNodes);
|
||||
ListWrapper.removeAll(nodes, matchingNodes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,15 @@
|
|||
import {CONST} from 'facade/lang';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {Element} from 'facade/dom';
|
||||
import {DOM, Element} from 'facade/dom';
|
||||
import {List} from 'facade/collection';
|
||||
import {View} from './view';
|
||||
import {Content} from './shadow_dom_emulation/content_tag';
|
||||
import {LightDom} from './shadow_dom_emulation/light_dom';
|
||||
|
||||
export class ShadowDomStrategy {
|
||||
@CONST() constructor() {}
|
||||
attachTemplate(el:Element, view:View){}
|
||||
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){}
|
||||
polyfillDirectives():List<Type>{ return null; };
|
||||
}
|
||||
|
||||
export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
||||
|
@ -14,6 +18,14 @@ export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
|||
DOM.clearNodes(el);
|
||||
moveViewNodesIntoParent(el, view);
|
||||
}
|
||||
|
||||
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){
|
||||
return new LightDom(lightDomView, shadowDomView, el);
|
||||
}
|
||||
|
||||
polyfillDirectives():List<Type> {
|
||||
return [Content];
|
||||
}
|
||||
}
|
||||
|
||||
export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
||||
|
@ -21,6 +33,14 @@ export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
|||
attachTemplate(el:Element, view:View){
|
||||
moveViewNodesIntoParent(el.createShadowRoot(), view);
|
||||
}
|
||||
|
||||
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){
|
||||
return null;
|
||||
}
|
||||
|
||||
polyfillDirectives():List<Type> {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function moveViewNodesIntoParent(parent, view) {
|
||||
|
|
|
@ -12,6 +12,8 @@ import {Injector} from 'di/di';
|
|||
import {NgElement} from 'core/dom/element';
|
||||
import {ViewPort} from './viewport';
|
||||
import {OnChange} from './interfaces';
|
||||
import {Content} from './shadow_dom_emulation/content_tag';
|
||||
import {LightDom, DestinationLightDom} from './shadow_dom_emulation/light_dom';
|
||||
|
||||
const NG_BINDING_CLASS = 'ng-binding';
|
||||
const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
|
||||
|
@ -38,6 +40,7 @@ export class View {
|
|||
proto: ProtoView;
|
||||
context: any;
|
||||
contextWithLocals:ContextWithVariableBindings;
|
||||
|
||||
constructor(proto:ProtoView, nodes:List<Node>, protoRecordRange:ProtoRecordRange, protoContextLocals:Map) {
|
||||
this.proto = proto;
|
||||
this.nodes = nodes;
|
||||
|
@ -156,7 +159,12 @@ export class View {
|
|||
// componentChildViews
|
||||
if (isPresent(shadowDomAppInjector)) {
|
||||
this.componentChildViews[componentChildViewIndex++].hydrate(shadowDomAppInjector,
|
||||
elementInjector, elementInjector.getComponent());
|
||||
elementInjector, elementInjector.getComponent());
|
||||
}
|
||||
|
||||
if (isPresent(componentDirective)) {
|
||||
var lightDom = this.preBuiltObjects[i].lightDom;
|
||||
if (isPresent(lightDom)) lightDom.redistribute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,6 +199,16 @@ export class View {
|
|||
}
|
||||
}
|
||||
|
||||
getViewPortByTemplateElement(node):ViewPort {
|
||||
if (!(node instanceof Element)) return null;
|
||||
|
||||
for (var i = 0; i < this.viewPorts.length; ++i) {
|
||||
if (this.viewPorts[i].templateElement === node) return this.viewPorts[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_invokeMementoForRecords(records:List<Record>) {
|
||||
for(var i = 0; i < records.length; ++i) {
|
||||
this._invokeMementoFor(records[i]);
|
||||
|
@ -267,12 +285,18 @@ export class ProtoView {
|
|||
// TODO(rado): hostElementInjector should be moved to hydrate phase.
|
||||
instantiate(hostElementInjector: ElementInjector):View {
|
||||
var rootElementClone = this.instantiateInPlace ? this.element : DOM.clone(this.element);
|
||||
var elementsWithBindings;
|
||||
var elementsWithBindingsDynamic;
|
||||
if (this.isTemplateElement) {
|
||||
elementsWithBindings = DOM.querySelectorAll(rootElementClone.content, NG_BINDING_CLASS_SELECTOR);
|
||||
elementsWithBindingsDynamic = DOM.querySelectorAll(rootElementClone.content, NG_BINDING_CLASS_SELECTOR);
|
||||
} else {
|
||||
elementsWithBindings = DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
|
||||
elementsWithBindingsDynamic= DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
|
||||
}
|
||||
|
||||
var elementsWithBindings = ListWrapper.createFixedSize(elementsWithBindingsDynamic.length);
|
||||
for (var i = 0; i < elementsWithBindingsDynamic.length; ++i) {
|
||||
elementsWithBindings[i] = elementsWithBindingsDynamic[i];
|
||||
}
|
||||
|
||||
var viewNodes;
|
||||
if (this.isTemplateElement) {
|
||||
var childNode = DOM.firstChild(rootElementClone.content);
|
||||
|
@ -319,21 +343,6 @@ export class ProtoView {
|
|||
}
|
||||
elementInjectors[i] = elementInjector;
|
||||
|
||||
// viewPorts
|
||||
var viewPort = null;
|
||||
if (isPresent(binder.templateDirective)) {
|
||||
viewPort = new ViewPort(view, element, binder.nestedProtoView, elementInjector);
|
||||
ListWrapper.push(viewPorts, viewPort);
|
||||
}
|
||||
|
||||
// preBuiltObjects
|
||||
var preBuiltObject = null;
|
||||
if (isPresent(elementInjector)) {
|
||||
preBuiltObject = new PreBuiltObjects(view, new NgElement(element), viewPort);
|
||||
}
|
||||
preBuiltObjects[i] = preBuiltObject;
|
||||
|
||||
// elementsWithPropertyBindings
|
||||
if (binder.hasElementPropertyBindings) {
|
||||
ListWrapper.push(elementsWithPropertyBindings, element);
|
||||
}
|
||||
|
@ -351,13 +360,29 @@ export class ProtoView {
|
|||
}
|
||||
|
||||
// componentChildViews
|
||||
var lightDom = null;
|
||||
if (isPresent(binder.componentDirective)) {
|
||||
var childView = binder.nestedProtoView.instantiate(elementInjector);
|
||||
view.recordRange.addRange(childView.recordRange);
|
||||
|
||||
lightDom = binder.componentDirective.shadowDomStrategy.constructLightDom(view, childView, element);
|
||||
binder.componentDirective.shadowDomStrategy.attachTemplate(element, childView);
|
||||
|
||||
ListWrapper.push(componentChildViews, childView);
|
||||
}
|
||||
|
||||
// viewPorts
|
||||
var viewPort = null;
|
||||
if (isPresent(binder.templateDirective)) {
|
||||
var destLightDom = this._parentElementLightDom(protoElementInjector, preBuiltObjects);
|
||||
viewPort = new ViewPort(view, element, binder.nestedProtoView, elementInjector, destLightDom);
|
||||
ListWrapper.push(viewPorts, viewPort);
|
||||
}
|
||||
|
||||
// preBuiltObjects
|
||||
if (isPresent(elementInjector)) {
|
||||
preBuiltObjects[i] = new PreBuiltObjects(view, new NgElement(element), viewPort, lightDom);
|
||||
}
|
||||
}
|
||||
|
||||
view.init(elementInjectors, rootElementInjectors, textNodes, elementsWithPropertyBindings,
|
||||
|
@ -366,6 +391,11 @@ export class ProtoView {
|
|||
return view;
|
||||
}
|
||||
|
||||
_parentElementLightDom(protoElementInjector:ProtoElementInjector, preBuiltObjects:List):LightDom {
|
||||
var p = protoElementInjector.parent;
|
||||
return isPresent(p) ? preBuiltObjects[p.index].lightDom : null;
|
||||
}
|
||||
|
||||
bindVariable(contextName:string, templateName:string) {
|
||||
MapWrapper.set(this.variableBindings, contextName, templateName);
|
||||
MapWrapper.set(this.protoContextLocals, templateName, null);
|
||||
|
|
|
@ -12,16 +12,18 @@ export class ViewPort {
|
|||
defaultProtoView: ProtoView;
|
||||
_views: List<View>;
|
||||
_viewLastNode: List<Node>;
|
||||
_lightDom: any;
|
||||
elementInjector: ElementInjector;
|
||||
appInjector: Injector;
|
||||
hostElementInjector: ElementInjector;
|
||||
|
||||
constructor(parentView: View, templateElement: Element, defaultProtoView: ProtoView,
|
||||
elementInjector: ElementInjector) {
|
||||
elementInjector: ElementInjector, lightDom = null) {
|
||||
this.parentView = parentView;
|
||||
this.templateElement = templateElement;
|
||||
this.defaultProtoView = defaultProtoView;
|
||||
this.elementInjector = elementInjector;
|
||||
this._lightDom = lightDom;
|
||||
|
||||
// The order in this list matches the DOM order.
|
||||
this._views = [];
|
||||
|
@ -77,7 +79,11 @@ export class ViewPort {
|
|||
insert(view, atIndex=-1): View {
|
||||
if (atIndex == -1) atIndex = this._views.length;
|
||||
ListWrapper.insert(this._views, atIndex, view);
|
||||
ViewPort.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view);
|
||||
if (isBlank(this._lightDom)) {
|
||||
ViewPort.moveViewNodesAfterSibling(this._siblingToInsertAfter(atIndex), view);
|
||||
} else {
|
||||
this._lightDom.redistribute();
|
||||
}
|
||||
this.parentView.recordRange.addRange(view.recordRange);
|
||||
this._linkElementInjectors(view);
|
||||
return view;
|
||||
|
@ -87,12 +93,28 @@ export class ViewPort {
|
|||
if (atIndex == -1) atIndex = this._views.length - 1;
|
||||
var removedView = this.get(atIndex);
|
||||
ListWrapper.removeAt(this._views, atIndex);
|
||||
ViewPort.removeViewNodesFromParent(this.templateElement.parentNode, removedView);
|
||||
if (isBlank(this._lightDom)) {
|
||||
ViewPort.removeViewNodesFromParent(this.templateElement.parentNode, removedView);
|
||||
} else {
|
||||
this._lightDom.redistribute();
|
||||
}
|
||||
removedView.recordRange.remove();
|
||||
this._unlinkElementInjectors(removedView);
|
||||
return removedView;
|
||||
}
|
||||
|
||||
contentTagContainers() {
|
||||
return this._views;
|
||||
}
|
||||
|
||||
nodes():List<Node> {
|
||||
var r = [];
|
||||
for (var i = 0; i < this._views.length; ++i) {
|
||||
r = ListWrapper.concat(r, this._views[i].nodes);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
_linkElementInjectors(view) {
|
||||
for (var i = 0; i < view.rootElementInjectors.length; ++i) {
|
||||
view.rootElementInjectors[i].parent = this.elementInjector;
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import {Element} from 'facade/dom';
|
||||
import {DOM, Element} from 'facade/dom';
|
||||
import {normalizeBlank} from 'facade/lang';
|
||||
|
||||
export class NgElement {
|
||||
domElement:Element;
|
||||
constructor(domElement:Element) {
|
||||
this.domElement = domElement;
|
||||
}
|
||||
|
||||
getAttribute(name:string) {
|
||||
return normalizeBlank(DOM.getAttribute(this.domElement, name));
|
||||
}
|
||||
}
|
|
@ -1,8 +1,20 @@
|
|||
import {ddescribe, describe, it, iit, expect, beforeEach} from 'test_lib/test_lib';
|
||||
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
|
||||
import {Decorator, Component} from 'core/annotations/annotations';
|
||||
import {TemplateConfig} from 'core/annotations/template_config';
|
||||
import {DirectiveMetadata} from 'core/compiler/directive_metadata';
|
||||
import {ShadowDomEmulated, ShadowDomNative} from 'core/compiler/shadow_dom';
|
||||
import {ShadowDomStrategy, ShadowDomNative} from 'core/compiler/shadow_dom';
|
||||
import {CONST} from 'facade/lang';
|
||||
|
||||
|
||||
class FakeShadowDomStrategy extends ShadowDomStrategy {
|
||||
@CONST()
|
||||
constructor() {}
|
||||
|
||||
polyfillDirectives() {
|
||||
return [SomeDirective];
|
||||
}
|
||||
}
|
||||
|
||||
@Decorator({
|
||||
selector: 'someSelector'
|
||||
|
@ -17,13 +29,28 @@ class ComponentWithoutExplicitShadowDomStrategy {}
|
|||
|
||||
@Component({
|
||||
selector: 'someSelector',
|
||||
shadowDom: ShadowDomEmulated
|
||||
shadowDom: new FakeShadowDomStrategy()
|
||||
})
|
||||
class ComponentWithExplicitShadowDomStrategy {}
|
||||
|
||||
class SomeDirectiveWithoutAnnotation {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'withoutDirectives'
|
||||
})
|
||||
class ComponentWithoutDirectives {}
|
||||
|
||||
@Component({
|
||||
selector: 'withDirectives',
|
||||
template: new TemplateConfig({
|
||||
directives: [ComponentWithoutDirectives]
|
||||
})
|
||||
})
|
||||
class ComponentWithDirectives {}
|
||||
|
||||
|
||||
|
||||
export function main() {
|
||||
describe("DirectiveMetadataReader", () => {
|
||||
var reader;
|
||||
|
@ -35,7 +62,7 @@ export function main() {
|
|||
it('should read out the annotation', () => {
|
||||
var directiveMetadata = reader.read(SomeDirective);
|
||||
expect(directiveMetadata).toEqual(
|
||||
new DirectiveMetadata(SomeDirective, new Decorator({selector: 'someSelector'}), null));
|
||||
new DirectiveMetadata(SomeDirective, new Decorator({selector: 'someSelector'}), null, null));
|
||||
});
|
||||
|
||||
it('should throw if not matching annotation is found', () => {
|
||||
|
@ -47,7 +74,7 @@ export function main() {
|
|||
describe("shadow dom strategy", () => {
|
||||
it('should return the provided shadow dom strategy when it is present', () => {
|
||||
var directiveMetadata = reader.read(ComponentWithExplicitShadowDomStrategy);
|
||||
expect(directiveMetadata.shadowDomStrategy).toEqual(ShadowDomEmulated);
|
||||
expect(directiveMetadata.shadowDomStrategy).toBeAnInstanceOf(FakeShadowDomStrategy);
|
||||
});
|
||||
|
||||
it('should return Native otherwise', () => {
|
||||
|
@ -55,5 +82,22 @@ export function main() {
|
|||
expect(directiveMetadata.shadowDomStrategy).toEqual(ShadowDomNative);
|
||||
});
|
||||
});
|
||||
|
||||
describe("componentDirectives", () => {
|
||||
it("should return an empty list when no directives specified", () => {
|
||||
var cmp = reader.read(ComponentWithoutDirectives);
|
||||
expect(cmp.componentDirectives).toEqual([]);
|
||||
});
|
||||
|
||||
it("should return a list of directives specified in the template config", () => {
|
||||
var cmp = reader.read(ComponentWithDirectives);
|
||||
expect(cmp.componentDirectives).toEqual([ComponentWithoutDirectives]);
|
||||
});
|
||||
|
||||
it("should include directives required by the shadow DOM strategy", () => {
|
||||
var cmp = reader.read(ComponentWithExplicitShadowDomStrategy);
|
||||
expect(cmp.componentDirectives).toEqual([SomeDirective]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -8,11 +8,16 @@ import {View} from 'core/compiler/view';
|
|||
import {ProtoRecordRange} from 'change_detection/change_detection';
|
||||
import {ViewPort} from 'core/compiler/viewport';
|
||||
import {NgElement} from 'core/dom/element';
|
||||
import {LightDom, SourceLightDom, DestinationLightDom} from 'core/compiler/shadow_dom_emulation/light_dom';
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(View)
|
||||
class DummyView extends SpyObject {noSuchMethod(m){super.noSuchMethod(m)}}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(LightDom)
|
||||
class DummyLightDom extends SpyObject {noSuchMethod(m){super.noSuchMethod(m)}}
|
||||
|
||||
|
||||
class Directive {
|
||||
}
|
||||
|
@ -65,7 +70,7 @@ class NeedsView {
|
|||
}
|
||||
|
||||
export function main() {
|
||||
var defaultPreBuiltObjects = new PreBuiltObjects(null, null, null);
|
||||
var defaultPreBuiltObjects = new PreBuiltObjects(null, null, null, null);
|
||||
|
||||
function humanize(tree, names:List) {
|
||||
var lookupName = (item) =>
|
||||
|
@ -88,12 +93,15 @@ export function main() {
|
|||
return inj;
|
||||
}
|
||||
|
||||
function parentChildInjectors(parentBindings, childBindings) {
|
||||
function parentChildInjectors(parentBindings, childBindings, parentPreBuildObjects = null) {
|
||||
if (isBlank(parentPreBuildObjects)) parentPreBuildObjects = defaultPreBuiltObjects;
|
||||
|
||||
var inj = new Injector([]);
|
||||
|
||||
var protoParent = new ProtoElementInjector(null, 0, parentBindings);
|
||||
var parent = protoParent.instantiate(null, null);
|
||||
parent.instantiateDirectives(inj, null, defaultPreBuiltObjects);
|
||||
|
||||
parent.instantiateDirectives(inj, null, parentPreBuildObjects);
|
||||
|
||||
var protoChild = new ProtoElementInjector(protoParent, 1, childBindings);
|
||||
var child = protoChild.instantiate(parent, null);
|
||||
|
@ -102,13 +110,15 @@ export function main() {
|
|||
return child;
|
||||
}
|
||||
|
||||
function hostShadowInjectors(hostBindings, shadowBindings) {
|
||||
function hostShadowInjectors(hostBindings, shadowBindings, hostPreBuildObjects = null) {
|
||||
if (isBlank(hostPreBuildObjects)) hostPreBuildObjects = defaultPreBuiltObjects;
|
||||
|
||||
var inj = new Injector([]);
|
||||
var shadowInj = inj.createChild([]);
|
||||
|
||||
var protoParent = new ProtoElementInjector(null, 0, hostBindings, true);
|
||||
var host = protoParent.instantiate(null, null);
|
||||
host.instantiateDirectives(inj, shadowInj, null);
|
||||
host.instantiateDirectives(inj, shadowInj, hostPreBuildObjects);
|
||||
|
||||
var protoChild = new ProtoElementInjector(protoParent, 0, shadowBindings, false);
|
||||
var shadow = protoChild.instantiate(null, host);
|
||||
|
@ -186,7 +196,7 @@ export function main() {
|
|||
|
||||
it("should instantiate directives that depend on pre built objects", function () {
|
||||
var view = new DummyView();
|
||||
var inj = injector([NeedsView], null, null, new PreBuiltObjects(view, null, null));
|
||||
var inj = injector([NeedsView], null, null, new PreBuiltObjects(view, null, null, null));
|
||||
|
||||
expect(inj.get(NeedsView).view).toBe(view);
|
||||
});
|
||||
|
@ -291,24 +301,51 @@ export function main() {
|
|||
describe("pre built objects", function () {
|
||||
it("should return view", function () {
|
||||
var view = new DummyView();
|
||||
var inj = injector([], null, null, new PreBuiltObjects(view, null, null));
|
||||
var inj = injector([], null, null, new PreBuiltObjects(view, null, null, null));
|
||||
|
||||
expect(inj.get(View)).toEqual(view);
|
||||
});
|
||||
|
||||
it("should return element", function () {
|
||||
var element = new NgElement(null);
|
||||
var inj = injector([], null, null, new PreBuiltObjects(null, element, null));
|
||||
var inj = injector([], null, null, new PreBuiltObjects(null, element, null, null));
|
||||
|
||||
expect(inj.get(NgElement)).toEqual(element);
|
||||
});
|
||||
|
||||
it('should return viewPort', function () {
|
||||
var viewPort = new ViewPort(null, null, null, null);
|
||||
var inj = injector([], null, null, new PreBuiltObjects(null, null, viewPort));
|
||||
var inj = injector([], null, null, new PreBuiltObjects(null, null, viewPort, null));
|
||||
|
||||
expect(inj.get(ViewPort)).toEqual(viewPort);
|
||||
});
|
||||
|
||||
describe("light DOM", () => {
|
||||
var lightDom, parentPreBuiltObjects;
|
||||
|
||||
beforeEach(() => {
|
||||
lightDom = new DummyLightDom();
|
||||
parentPreBuiltObjects = new PreBuiltObjects(null, null, null, lightDom);
|
||||
});
|
||||
|
||||
it("should return destination light DOM from the parent's injector", function () {
|
||||
var child = parentChildInjectors([], [], parentPreBuiltObjects);
|
||||
|
||||
expect(child.get(DestinationLightDom)).toEqual(lightDom);
|
||||
});
|
||||
|
||||
it("should return null when parent's injector is a component boundary", function () {
|
||||
var child = hostShadowInjectors([], [], parentPreBuiltObjects);
|
||||
|
||||
expect(child.get(DestinationLightDom)).toBeNull();
|
||||
});
|
||||
|
||||
it("should return source light DOM from the closest component boundary", function () {
|
||||
var child = hostShadowInjectors([], [], parentPreBuiltObjects);
|
||||
|
||||
expect(child.get(SourceLightDom)).toEqual(lightDom);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection';
|
|||
|
||||
import {Compiler, CompilerCache} from 'core/compiler/compiler';
|
||||
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
|
||||
import {ShadowDomEmulated} from 'core/compiler/shadow_dom';
|
||||
|
||||
import {Decorator, Component, Template} from 'core/annotations/annotations';
|
||||
import {TemplateConfig} from 'core/annotations/template_config';
|
||||
|
@ -108,9 +109,51 @@ export function main() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should emulate content tag', (done) => {
|
||||
var el = `<emulated-shadow-dom-component>` +
|
||||
`<div>Light</div>` +
|
||||
`<div template="trivial-template">DOM</div>` +
|
||||
`</emulated-shadow-dom-component>`;
|
||||
|
||||
function createView(pv) {
|
||||
var view = pv.instantiate(null);
|
||||
view.hydrate(new Injector([]), null, {});
|
||||
return view;
|
||||
}
|
||||
|
||||
compiler.compile(MyComp, createElement(el)).
|
||||
then(createView).
|
||||
then((view) => {
|
||||
expect(DOM.getText(view.nodes[0])).toEqual('Before LightDOM After');
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Template({
|
||||
selector: '[trivial-template]'
|
||||
})
|
||||
class TrivialTemplateDirective {
|
||||
constructor(viewPort:ViewPort) {
|
||||
viewPort.create();
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'emulated-shadow-dom-component',
|
||||
template: new TemplateConfig({
|
||||
inline: 'Before <content></content> After',
|
||||
directives: []
|
||||
}),
|
||||
shadowDom: ShadowDomEmulated
|
||||
})
|
||||
class EmulatedShadowDomCmp {
|
||||
|
||||
}
|
||||
|
||||
@Decorator({
|
||||
selector: '[my-dir]',
|
||||
bind: {'elprop':'dirProp'}
|
||||
|
@ -124,7 +167,7 @@ class MyDir {
|
|||
|
||||
@Component({
|
||||
template: new TemplateConfig({
|
||||
directives: [MyDir, ChildComp, SomeTemplate]
|
||||
directives: [MyDir, ChildComp, SomeTemplate, EmulatedShadowDomCmp, TrivialTemplateDirective]
|
||||
})
|
||||
})
|
||||
class MyComp {
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import {describe, beforeEach, it, expect, ddescribe, iit, SpyObject} from 'test_lib/test_lib';
|
||||
import {proxy, IMPLEMENTS} from 'facade/lang';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {Content} from 'core/compiler/shadow_dom_emulation/content_tag';
|
||||
import {NgElement} from 'core/dom/element';
|
||||
import {LightDom} from 'core/compiler/shadow_dom_emulation/light_dom';
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(LightDom)
|
||||
class DummyLightDom extends SpyObject {noSuchMethod(m){super.noSuchMethod(m)}}
|
||||
|
||||
var _script = `<script type="ng/content"></script>`;
|
||||
|
||||
export function main() {
|
||||
describe('Content', function() {
|
||||
it("should insert the nodes", () => {
|
||||
var lightDom = new DummyLightDom();
|
||||
var parent = createElement("<div><content></content></div>");
|
||||
var content = DOM.firstChild(parent);
|
||||
|
||||
var c = new Content(lightDom, new NgElement(content));
|
||||
c.insert([createElement("<a></a>"), createElement("<b></b>")])
|
||||
|
||||
expect(DOM.getInnerHTML(parent)).toEqual(`${_script}<a></a><b></b>${_script}`);
|
||||
});
|
||||
|
||||
it("should remove the nodes from the previous insertion", () => {
|
||||
var lightDom = new DummyLightDom();
|
||||
var parent = createElement("<div><content></content></div>");
|
||||
var content = DOM.firstChild(parent);
|
||||
|
||||
var c = new Content(lightDom, new NgElement(content));
|
||||
c.insert([createElement("<a></a>")]);
|
||||
c.insert([createElement("<b></b>")]);
|
||||
|
||||
expect(DOM.getInnerHTML(parent)).toEqual(`${_script}<b></b>${_script}`);
|
||||
});
|
||||
|
||||
it("should insert empty list", () => {
|
||||
var lightDom = new DummyLightDom();
|
||||
var parent = createElement("<div><content></content></div>");
|
||||
var content = DOM.firstChild(parent);
|
||||
|
||||
var c = new Content(lightDom, new NgElement(content));
|
||||
c.insert([createElement("<a></a>")]);
|
||||
c.insert([]);
|
||||
|
||||
expect(DOM.getInnerHTML(parent)).toEqual(`${_script}${_script}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
import {describe, beforeEach, it, expect, ddescribe, iit, SpyObject} from 'test_lib/test_lib';
|
||||
import {proxy, IMPLEMENTS, isBlank} from 'facade/lang';
|
||||
import {ListWrapper, MapWrapper} from 'facade/collection';
|
||||
import {DOM} from 'facade/dom';
|
||||
import {Content} from 'core/compiler/shadow_dom_emulation/content_tag';
|
||||
import {NgElement} from 'core/dom/element';
|
||||
import {LightDom} from 'core/compiler/shadow_dom_emulation/light_dom';
|
||||
import {View} from 'core/compiler/view';
|
||||
import {ViewPort} from 'core/compiler/viewport';
|
||||
import {ElementInjector} from 'core/compiler/element_injector';
|
||||
import {ProtoRecordRange} from 'change_detection/change_detection';
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(ElementInjector)
|
||||
class FakeElementInjector {
|
||||
content;
|
||||
viewPort;
|
||||
|
||||
constructor(content, viewPort) {
|
||||
this.content = content;
|
||||
this.viewPort = viewPort;
|
||||
}
|
||||
|
||||
hasDirective(type) {
|
||||
return this.content != null;
|
||||
}
|
||||
|
||||
hasPreBuiltObject(type) {
|
||||
return this.viewPort != null;
|
||||
}
|
||||
|
||||
get(t) {
|
||||
if (t === Content) return this.content;
|
||||
if (t === ViewPort) return this.viewPort;
|
||||
return null;
|
||||
}
|
||||
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(View)
|
||||
class FakeView {
|
||||
elementInjectors;
|
||||
ports;
|
||||
|
||||
constructor(elementInjectors = null, ports = null) {
|
||||
this.elementInjectors = elementInjectors;
|
||||
this.ports = ports;
|
||||
}
|
||||
|
||||
getViewPortByTemplateElement(el) {
|
||||
if (isBlank(this.ports)) return null;
|
||||
return MapWrapper.get(this.ports, el);
|
||||
}
|
||||
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(ViewPort)
|
||||
class FakeViewPort {
|
||||
_nodes;
|
||||
_contentTagContainers;
|
||||
|
||||
constructor(nodes, views) {
|
||||
this._nodes = nodes;
|
||||
this._contentTagContainers = views;
|
||||
}
|
||||
|
||||
nodes(){
|
||||
return this._nodes;
|
||||
}
|
||||
|
||||
contentTagContainers(){
|
||||
return this._contentTagContainers;
|
||||
}
|
||||
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(Content)
|
||||
class FakeContentTag {
|
||||
select;
|
||||
nodes;
|
||||
|
||||
constructor(select = null) {
|
||||
this.select = select;
|
||||
}
|
||||
|
||||
insert(nodes){
|
||||
this.nodes = ListWrapper.clone(nodes);
|
||||
}
|
||||
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function main() {
|
||||
describe('LightDom', function() {
|
||||
var lightDomView;
|
||||
|
||||
beforeEach(() => {
|
||||
lightDomView = new FakeView([], MapWrapper.create());
|
||||
});
|
||||
|
||||
describe("contentTags", () => {
|
||||
it("should collect content tags from element injectors", () => {
|
||||
var tag = new FakeContentTag();
|
||||
var shadowDomView = new FakeView([new FakeElementInjector(tag, null)]);
|
||||
|
||||
var lightDom = new LightDom(lightDomView, shadowDomView, createElement("<div></div>"));
|
||||
|
||||
expect(lightDom.contentTags()).toEqual([tag]);
|
||||
});
|
||||
|
||||
it("should collect content tags from view ports", () => {
|
||||
var tag = new FakeContentTag();
|
||||
var vp = new FakeViewPort(null, [
|
||||
new FakeView([new FakeElementInjector(tag, null)])
|
||||
]);
|
||||
|
||||
var shadowDomView = new FakeView([new FakeElementInjector(null, vp)]);
|
||||
|
||||
var lightDom = new LightDom(lightDomView, shadowDomView, createElement("<div></div>"));
|
||||
|
||||
expect(lightDom.contentTags()).toEqual([tag]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("expanded roots", () => {
|
||||
it("should contain root nodes", () => {
|
||||
var lightDomEl = createElement("<div><a></a></div>")
|
||||
var lightDom = new LightDom(lightDomView, new FakeView(), lightDomEl);
|
||||
expect(toHtml(lightDom.expandedDomNodes())).toEqual(["<a></a>"]);
|
||||
});
|
||||
|
||||
it("should include view port nodes", () => {
|
||||
var lightDomEl = createElement("<div><template></template></div>")
|
||||
var template = lightDomEl.childNodes[0];
|
||||
|
||||
var lightDomView = new FakeView([],
|
||||
MapWrapper.createFromPairs([
|
||||
[template, new FakeViewPort([createElement("<a></a>")], null)]
|
||||
])
|
||||
);
|
||||
|
||||
var lightDom = new LightDom(lightDomView, new FakeView(), lightDomEl);
|
||||
|
||||
expect(toHtml(lightDom.expandedDomNodes())).toEqual(["<a></a>"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("redistribute", () => {
|
||||
it("should redistribute nodes between content tags with select property set", () => {
|
||||
var contentA = new FakeContentTag("a");
|
||||
var contentB = new FakeContentTag("b");
|
||||
|
||||
var lightDomEl = createElement("<div><a>1</a><b>2</b><a>3</a></div>")
|
||||
|
||||
var lightDom = new LightDom(lightDomView, new FakeView([
|
||||
new FakeElementInjector(contentA, null),
|
||||
new FakeElementInjector(contentB, null)
|
||||
]), lightDomEl);
|
||||
|
||||
lightDom.redistribute();
|
||||
|
||||
expect(toHtml(contentA.nodes)).toEqual(["<a>1</a>", "<a>3</a>"]);
|
||||
expect(toHtml(contentB.nodes)).toEqual(["<b>2</b>"]);
|
||||
});
|
||||
|
||||
it("should support wildcard content tags", () => {
|
||||
var wildcard = new FakeContentTag(null);
|
||||
var contentB = new FakeContentTag("b");
|
||||
|
||||
var lightDomEl = createElement("<div><a>1</a><b>2</b><a>3</a></div>")
|
||||
|
||||
var lightDom = new LightDom(lightDomView, new FakeView([
|
||||
new FakeElementInjector(wildcard, null),
|
||||
new FakeElementInjector(contentB, null)
|
||||
]), lightDomEl);
|
||||
|
||||
lightDom.redistribute();
|
||||
|
||||
expect(toHtml(wildcard.nodes)).toEqual(["<a>1</a>", "<b>2</b>", "<a>3</a>"]);
|
||||
expect(toHtml(contentB.nodes)).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toHtml(nodes) {
|
||||
if (isBlank(nodes)) return [];
|
||||
return ListWrapper.map(nodes, DOM.getOuterHTML);
|
||||
}
|
||||
|
||||
function createElement(html) {
|
||||
return DOM.createTemplate(html).content.firstChild;
|
||||
}
|
|
@ -7,14 +7,30 @@ import {Component, Decorator, Template} from 'core/annotations/annotations';
|
|||
import {OnChange} from 'core/core';
|
||||
import {Lexer, Parser, ProtoRecordRange, ChangeDetector} from 'change_detection/change_detection';
|
||||
import {TemplateConfig} from 'core/annotations/template_config';
|
||||
import {List} from 'facade/collection';
|
||||
import {List, MapWrapper} from 'facade/collection';
|
||||
import {DOM, Element} from 'facade/dom';
|
||||
import {int} from 'facade/lang';
|
||||
import {int, proxy, IMPLEMENTS} from 'facade/lang';
|
||||
import {Injector} from 'di/di';
|
||||
import {View} from 'core/compiler/view';
|
||||
import {ViewPort} from 'core/compiler/viewport';
|
||||
import {reflector} from 'reflection/reflection';
|
||||
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(ViewPort)
|
||||
class FakeViewPort {
|
||||
templateElement;
|
||||
|
||||
constructor(templateElement) {
|
||||
this.templateElement = templateElement;
|
||||
}
|
||||
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function main() {
|
||||
describe('view', function() {
|
||||
var parser, someComponentDirective, someTemplateDirective;
|
||||
|
@ -53,6 +69,25 @@ export function main() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("getViewPortByTemplateElement", () => {
|
||||
var view, viewPort, templateElement;
|
||||
|
||||
beforeEach(() => {
|
||||
templateElement = createElement("<template></template>");
|
||||
view = new View(null, null, new ProtoRecordRange(), MapWrapper.create());
|
||||
viewPort = new FakeViewPort(templateElement);
|
||||
view.viewPorts = [viewPort];
|
||||
});
|
||||
|
||||
it("should return null when the given element is not an element", () => {
|
||||
expect(view.getViewPortByTemplateElement("not an element")).toBeNull();
|
||||
});
|
||||
|
||||
it("should return a view port with the matching template element", () => {
|
||||
expect(view.getViewPortByTemplateElement(templateElement)).toBe(viewPort);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with locals', function() {
|
||||
var view;
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -96,8 +96,14 @@ class ListWrapper {
|
|||
static bool isList(l) => l is List;
|
||||
static void insert(List l, int index, value) { l.insert(index, value); }
|
||||
static void removeAt(List l, int index) { l.removeAt(index); }
|
||||
static void removeAll(List list, List items) {
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
list.remove(items[i]);
|
||||
}
|
||||
}
|
||||
static void clear(List l) { l.clear(); }
|
||||
static String join(List l, String s) => l.join(s);
|
||||
static bool isEmpty(list) => list.isEmpty;
|
||||
}
|
||||
|
||||
bool isListLikeIterable(obj) => obj is Iterable;
|
||||
|
|
|
@ -143,12 +143,21 @@ export class ListWrapper {
|
|||
list.splice(index, 1);
|
||||
return res;
|
||||
}
|
||||
static removeAll(list, items) {
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
var index = list.indexOf(items[i]);
|
||||
list.splice(index, 1);
|
||||
}
|
||||
}
|
||||
static clear(list) {
|
||||
list.splice(0, list.length);
|
||||
}
|
||||
static join(list, s) {
|
||||
return list.join(s);
|
||||
}
|
||||
static isEmpty(list) {
|
||||
return list.length == 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function isListLikeIterable(obj):boolean {
|
||||
|
|
|
@ -47,6 +47,9 @@ class DOM {
|
|||
static List<Node> childNodes(el) {
|
||||
return el.childNodes;
|
||||
}
|
||||
static childNodesAsList(el) {
|
||||
return childNodes(el).toList();
|
||||
}
|
||||
static clearNodes(el) {
|
||||
el.nodes = [];
|
||||
}
|
||||
|
@ -56,6 +59,12 @@ class DOM {
|
|||
static removeChild(el, node) {
|
||||
node.remove();
|
||||
}
|
||||
static insertBefore(el, node) {
|
||||
el.parentNode.insertBefore(node, el);
|
||||
}
|
||||
static insertAllBefore(el, nodes) {
|
||||
el.parentNode.insertAllBefore(nodes, el);
|
||||
}
|
||||
static insertAfter(el, node) {
|
||||
el.parentNode.insertBefore(node, el.nextNode);
|
||||
}
|
||||
|
@ -74,6 +83,12 @@ class DOM {
|
|||
if (doc == null) doc = document;
|
||||
return doc.createElement(tagName);
|
||||
}
|
||||
static createScriptTag(String attrName, String attrValue, [doc=null]) {
|
||||
if (doc == null) doc = document;
|
||||
var el = doc.createElement("SCRIPT");
|
||||
el.setAttribute(attrName, attrValue);
|
||||
return el;
|
||||
}
|
||||
static clone(Node node) {
|
||||
return node.clone(true);
|
||||
}
|
||||
|
@ -95,9 +110,15 @@ class DOM {
|
|||
static hasClass(Element element, classname) {
|
||||
return element.classes.contains(classname);
|
||||
}
|
||||
static String tagName(Element element) {
|
||||
return element.tagName;
|
||||
}
|
||||
static attributeMap(Element element) {
|
||||
return element.attributes;
|
||||
}
|
||||
static getAttribute(Element element, String attribute) {
|
||||
return element.getAttribute(attribute);
|
||||
}
|
||||
static Node templateAwareRoot(Element el) {
|
||||
return el is TemplateElement ? el.content : el;
|
||||
}
|
||||
|
@ -107,4 +128,7 @@ class DOM {
|
|||
static HtmlDocument defaultDoc() {
|
||||
return document;
|
||||
}
|
||||
static bool elementMatches(n, String selector) {
|
||||
return n is Element && n.matches(selector);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ export var TemplateElement = window.HTMLTemplateElement;
|
|||
export var document = window.document;
|
||||
export var location = window.location;
|
||||
|
||||
import {List, MapWrapper} from 'facade/collection';
|
||||
import {List, MapWrapper, ListWrapper} from 'facade/collection';
|
||||
|
||||
export class DOM {
|
||||
static query(selector) {
|
||||
|
@ -40,6 +40,14 @@ export class DOM {
|
|||
static childNodes(el):NodeList {
|
||||
return el.childNodes;
|
||||
}
|
||||
static childNodesAsList(el):List {
|
||||
var childNodes = el.childNodes;
|
||||
var res = ListWrapper.createFixedSize(childNodes.length);
|
||||
for (var i=0; i<childNodes.length; i++) {
|
||||
res[i] = childNodes[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
static clearNodes(el) {
|
||||
el.innerHTML = "";
|
||||
}
|
||||
|
@ -49,6 +57,14 @@ export class DOM {
|
|||
static removeChild(el, node) {
|
||||
el.removeChild(node);
|
||||
}
|
||||
static insertBefore(el, node) {
|
||||
el.parentNode.insertBefore(node, el);
|
||||
}
|
||||
static insertAllBefore(el, nodes) {
|
||||
ListWrapper.forEach(nodes, (n) => {
|
||||
el.parentNode.insertBefore(n, el);
|
||||
});
|
||||
}
|
||||
static insertAfter(el, node) {
|
||||
el.parentNode.insertBefore(node, el.nextSibling);
|
||||
}
|
||||
|
@ -69,6 +85,11 @@ export class DOM {
|
|||
static createElement(tagName, doc=document) {
|
||||
return doc.createElement(tagName);
|
||||
}
|
||||
static createScriptTag(attrName:string, attrValue:string, doc=document) {
|
||||
var el = doc.createElement("SCRIPT");
|
||||
el.setAttribute(attrName, attrValue);
|
||||
return el;
|
||||
}
|
||||
static clone(node:Node) {
|
||||
return node.cloneNode(true);
|
||||
}
|
||||
|
@ -90,6 +111,9 @@ export class DOM {
|
|||
static hasClass(element:Element, classname:string) {
|
||||
return element.classList.contains(classname);
|
||||
}
|
||||
static tagName(element:Element):string {
|
||||
return element.tagName;
|
||||
}
|
||||
static attributeMap(element:Element) {
|
||||
var res = MapWrapper.create();
|
||||
var elAttrs = element.attributes;
|
||||
|
@ -99,6 +123,9 @@ export class DOM {
|
|||
}
|
||||
return res;
|
||||
}
|
||||
static getAttribute(element:Element, attribute:string) {
|
||||
return element.getAttribute(attribute);
|
||||
}
|
||||
static templateAwareRoot(el:Element):Node {
|
||||
return el instanceof TemplateElement ? el.content : el;
|
||||
}
|
||||
|
@ -108,4 +135,7 @@ export class DOM {
|
|||
static defaultDoc() {
|
||||
return document;
|
||||
}
|
||||
static elementMatches(n, selector:string):boolean {
|
||||
return n instanceof Element && n.matches(selector);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue