feat(components): initial implementation of emulated content tag

This commit is contained in:
vsavkin 2015-01-02 14:23:59 -08:00
parent 0f8f4801bd
commit fbcc59dc67
20 changed files with 798 additions and 57 deletions

View File

@ -13,6 +13,7 @@ import {createDefaultSteps} from './pipeline/default_steps';
import {TemplateLoader} from './template_loader'; import {TemplateLoader} from './template_loader';
import {DirectiveMetadata} from './directive_metadata'; import {DirectiveMetadata} from './directive_metadata';
import {Component} from '../annotations/annotations'; import {Component} from '../annotations/annotations';
import {Content} from './shadow_dom_emulation/content_tag';
/** /**
* Cache that stores the ProtoView of the template of a component. * Cache that stores the ProtoView of the template of a component.
@ -60,13 +61,8 @@ export class Compiler {
} }
createSteps(component:DirectiveMetadata):List<CompileStep> { createSteps(component:DirectiveMetadata):List<CompileStep> {
var annotation: Component = component.annotation; var dirs = ListWrapper.map(component.componentDirectives, (d) => this._reader.read(d));
var directives = annotation.template.directives; return createDefaultSteps(this._parser, component, dirs);
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);
} }
compile(component:Type, templateRoot:Element = null):Promise<ProtoView> { compile(component:Type, templateRoot:Element = null):Promise<ProtoView> {

View File

@ -1,5 +1,6 @@
import {Type, FIELD} from 'facade/lang'; import {Type, FIELD} from 'facade/lang';
import {Directive} from '../annotations/annotations' import {Directive} from '../annotations/annotations'
import {List} from 'facade/collection'
import {ShadowDomStrategy} from './shadow_dom'; import {ShadowDomStrategy} from './shadow_dom';
/** /**
@ -9,10 +10,13 @@ export class DirectiveMetadata {
type:Type; type:Type;
annotation:Directive; annotation:Directive;
shadowDomStrategy:ShadowDomStrategy; 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.annotation = annotation;
this.type = type; this.type = type;
this.shadowDomStrategy = shadowDomStrategy; this.shadowDomStrategy = shadowDomStrategy;
this.componentDirectives = componentDirectives;
} }
} }

View File

@ -1,4 +1,5 @@
import {Type, isPresent, BaseException, stringify} from 'facade/lang'; import {Type, isPresent, BaseException, stringify} from 'facade/lang';
import {List, ListWrapper} from 'facade/collection';
import {Directive, Component} from '../annotations/annotations'; import {Directive, Component} from '../annotations/annotations';
import {DirectiveMetadata} from './directive_metadata'; import {DirectiveMetadata} from './directive_metadata';
import {reflector} from 'reflection/reflection'; import {reflector} from 'reflection/reflection';
@ -12,11 +13,17 @@ export class DirectiveMetadataReader {
var annotation = annotations[i]; var annotation = annotations[i];
if (annotation instanceof Component) { 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) { 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{ parseShadowDomStrategy(annotation:Component):ShadowDomStrategy{
return isPresent(annotation.shadowDom) ? annotation.shadowDom : ShadowDomNative; 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;
}
} }

View File

@ -4,6 +4,7 @@ import {List, ListWrapper} from 'facade/collection';
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'di/di'; import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'di/di';
import {Parent, Ancestor} from 'core/annotations/visibility'; import {Parent, Ancestor} from 'core/annotations/visibility';
import {View} from 'core/compiler/view'; 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 {ViewPort} from 'core/compiler/viewport';
import {NgElement} from 'core/dom/element'; import {NgElement} from 'core/dom/element';
@ -19,11 +20,16 @@ class StaticKeys {
viewId:int; viewId:int;
ngElementId:int; ngElementId:int;
viewPortId:int; viewPortId:int;
destinationLightDomId:int;
sourceLightDomId:int;
constructor() { constructor() {
//TODO: vsavkin Key.annotate(Key.get(View), 'static') //TODO: vsavkin Key.annotate(Key.get(View), 'static')
this.viewId = Key.get(View).id; this.viewId = Key.get(View).id;
this.ngElementId = Key.get(NgElement).id; this.ngElementId = Key.get(NgElement).id;
this.viewPortId = Key.get(ViewPort).id; this.viewPortId = Key.get(ViewPort).id;
this.destinationLightDomId = Key.get(DestinationLightDom).id;
this.sourceLightDomId = Key.get(SourceLightDom).id;
} }
static instance() { static instance() {
@ -105,10 +111,12 @@ export class PreBuiltObjects {
view:View; view:View;
element:NgElement; element:NgElement;
viewPort:ViewPort; viewPort:ViewPort;
constructor(view, element:NgElement, viewPort: ViewPort) { lightDom:LightDom;
constructor(view, element:NgElement, viewPort:ViewPort, lightDom:LightDom) {
this.view = view; this.view = view;
this.element = element; this.element = element;
this.viewPort = viewPort; this.viewPort = viewPort;
this.lightDom = lightDom;
} }
} }
@ -306,6 +314,15 @@ export class ElementInjector extends TreeNode {
return this._getByKey(Key.get(token), 0, null); 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() { getComponent() {
if (this._proto._binding0IsComponent) { if (this._proto._binding0IsComponent) {
return this._obj0; return this._obj0;
@ -421,11 +438,15 @@ export class ElementInjector extends TreeNode {
var staticKeys = StaticKeys.instance(); var staticKeys = StaticKeys.instance();
if (keyId === staticKeys.viewId) return this._preBuiltObjects.view; if (keyId === staticKeys.viewId) return this._preBuiltObjects.view;
if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element; if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element;
if (keyId === staticKeys.viewPortId) { if (keyId === staticKeys.viewPortId) return this._preBuiltObjects.viewPort;
if (isBlank(staticKeys.viewPortId)) throw new BaseException( if (keyId === staticKeys.destinationLightDomId) {
'ViewPort is constructed only for @Template directives'); var p:ElementInjector = this._parent;
return this._preBuiltObjects.viewPort; return isPresent(p) ? p._preBuiltObjects.lightDom : null;
} }
if (keyId === staticKeys.sourceLightDomId) {
return this._host._preBuiltObjects.lightDom;
}
//TODO add other objects as needed //TODO add other objects as needed
return _undefined; return _undefined;
} }

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -1,11 +1,15 @@
import {CONST} from 'facade/lang'; import {CONST} from 'facade/lang';
import {DOM} from 'facade/dom'; import {DOM, Element} from 'facade/dom';
import {Element} from 'facade/dom'; import {List} from 'facade/collection';
import {View} from './view'; import {View} from './view';
import {Content} from './shadow_dom_emulation/content_tag';
import {LightDom} from './shadow_dom_emulation/light_dom';
export class ShadowDomStrategy { export class ShadowDomStrategy {
@CONST() constructor() {} @CONST() constructor() {}
attachTemplate(el:Element, view:View){} attachTemplate(el:Element, view:View){}
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){}
polyfillDirectives():List<Type>{ return null; };
} }
export class EmulatedShadowDomStrategy extends ShadowDomStrategy { export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
@ -14,6 +18,14 @@ export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
DOM.clearNodes(el); DOM.clearNodes(el);
moveViewNodesIntoParent(el, view); 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 { export class NativeShadowDomStrategy extends ShadowDomStrategy {
@ -21,6 +33,14 @@ export class NativeShadowDomStrategy extends ShadowDomStrategy {
attachTemplate(el:Element, view:View){ attachTemplate(el:Element, view:View){
moveViewNodesIntoParent(el.createShadowRoot(), view); moveViewNodesIntoParent(el.createShadowRoot(), view);
} }
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){
return null;
}
polyfillDirectives():List<Type> {
return [];
}
} }
function moveViewNodesIntoParent(parent, view) { function moveViewNodesIntoParent(parent, view) {

View File

@ -12,6 +12,8 @@ import {Injector} from 'di/di';
import {NgElement} from 'core/dom/element'; import {NgElement} from 'core/dom/element';
import {ViewPort} from './viewport'; import {ViewPort} from './viewport';
import {OnChange} from './interfaces'; 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 = 'ng-binding';
const NG_BINDING_CLASS_SELECTOR = '.ng-binding'; const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
@ -38,6 +40,7 @@ export class View {
proto: ProtoView; proto: ProtoView;
context: any; context: any;
contextWithLocals:ContextWithVariableBindings; contextWithLocals:ContextWithVariableBindings;
constructor(proto:ProtoView, nodes:List<Node>, protoRecordRange:ProtoRecordRange, protoContextLocals:Map) { constructor(proto:ProtoView, nodes:List<Node>, protoRecordRange:ProtoRecordRange, protoContextLocals:Map) {
this.proto = proto; this.proto = proto;
this.nodes = nodes; this.nodes = nodes;
@ -156,7 +159,12 @@ export class View {
// componentChildViews // componentChildViews
if (isPresent(shadowDomAppInjector)) { if (isPresent(shadowDomAppInjector)) {
this.componentChildViews[componentChildViewIndex++].hydrate(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>) { _invokeMementoForRecords(records:List<Record>) {
for(var i = 0; i < records.length; ++i) { for(var i = 0; i < records.length; ++i) {
this._invokeMementoFor(records[i]); this._invokeMementoFor(records[i]);
@ -267,12 +285,18 @@ export class ProtoView {
// TODO(rado): hostElementInjector should be moved to hydrate phase. // TODO(rado): hostElementInjector should be moved to hydrate phase.
instantiate(hostElementInjector: ElementInjector):View { instantiate(hostElementInjector: ElementInjector):View {
var rootElementClone = this.instantiateInPlace ? this.element : DOM.clone(this.element); var rootElementClone = this.instantiateInPlace ? this.element : DOM.clone(this.element);
var elementsWithBindings; var elementsWithBindingsDynamic;
if (this.isTemplateElement) { if (this.isTemplateElement) {
elementsWithBindings = DOM.querySelectorAll(rootElementClone.content, NG_BINDING_CLASS_SELECTOR); elementsWithBindingsDynamic = DOM.querySelectorAll(rootElementClone.content, NG_BINDING_CLASS_SELECTOR);
} else { } 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; var viewNodes;
if (this.isTemplateElement) { if (this.isTemplateElement) {
var childNode = DOM.firstChild(rootElementClone.content); var childNode = DOM.firstChild(rootElementClone.content);
@ -319,21 +343,6 @@ export class ProtoView {
} }
elementInjectors[i] = elementInjector; 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) { if (binder.hasElementPropertyBindings) {
ListWrapper.push(elementsWithPropertyBindings, element); ListWrapper.push(elementsWithPropertyBindings, element);
} }
@ -351,13 +360,29 @@ export class ProtoView {
} }
// componentChildViews // componentChildViews
var lightDom = null;
if (isPresent(binder.componentDirective)) { if (isPresent(binder.componentDirective)) {
var childView = binder.nestedProtoView.instantiate(elementInjector); var childView = binder.nestedProtoView.instantiate(elementInjector);
view.recordRange.addRange(childView.recordRange); view.recordRange.addRange(childView.recordRange);
lightDom = binder.componentDirective.shadowDomStrategy.constructLightDom(view, childView, element);
binder.componentDirective.shadowDomStrategy.attachTemplate(element, childView); binder.componentDirective.shadowDomStrategy.attachTemplate(element, childView);
ListWrapper.push(componentChildViews, 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, view.init(elementInjectors, rootElementInjectors, textNodes, elementsWithPropertyBindings,
@ -366,6 +391,11 @@ export class ProtoView {
return view; 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) { bindVariable(contextName:string, templateName:string) {
MapWrapper.set(this.variableBindings, contextName, templateName); MapWrapper.set(this.variableBindings, contextName, templateName);
MapWrapper.set(this.protoContextLocals, templateName, null); MapWrapper.set(this.protoContextLocals, templateName, null);

View File

@ -12,16 +12,18 @@ export class ViewPort {
defaultProtoView: ProtoView; defaultProtoView: ProtoView;
_views: List<View>; _views: List<View>;
_viewLastNode: List<Node>; _viewLastNode: List<Node>;
_lightDom: any;
elementInjector: ElementInjector; elementInjector: ElementInjector;
appInjector: Injector; appInjector: Injector;
hostElementInjector: ElementInjector; hostElementInjector: ElementInjector;
constructor(parentView: View, templateElement: Element, defaultProtoView: ProtoView, constructor(parentView: View, templateElement: Element, defaultProtoView: ProtoView,
elementInjector: ElementInjector) { elementInjector: ElementInjector, lightDom = null) {
this.parentView = parentView; this.parentView = parentView;
this.templateElement = templateElement; this.templateElement = templateElement;
this.defaultProtoView = defaultProtoView; this.defaultProtoView = defaultProtoView;
this.elementInjector = elementInjector; this.elementInjector = elementInjector;
this._lightDom = lightDom;
// The order in this list matches the DOM order. // The order in this list matches the DOM order.
this._views = []; this._views = [];
@ -77,7 +79,11 @@ export class ViewPort {
insert(view, atIndex=-1): View { insert(view, atIndex=-1): View {
if (atIndex == -1) atIndex = this._views.length; if (atIndex == -1) atIndex = this._views.length;
ListWrapper.insert(this._views, atIndex, view); 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.parentView.recordRange.addRange(view.recordRange);
this._linkElementInjectors(view); this._linkElementInjectors(view);
return view; return view;
@ -87,12 +93,28 @@ export class ViewPort {
if (atIndex == -1) atIndex = this._views.length - 1; if (atIndex == -1) atIndex = this._views.length - 1;
var removedView = this.get(atIndex); var removedView = this.get(atIndex);
ListWrapper.removeAt(this._views, 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(); removedView.recordRange.remove();
this._unlinkElementInjectors(removedView); this._unlinkElementInjectors(removedView);
return 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) { _linkElementInjectors(view) {
for (var i = 0; i < view.rootElementInjectors.length; ++i) { for (var i = 0; i < view.rootElementInjectors.length; ++i) {
view.rootElementInjectors[i].parent = this.elementInjector; view.rootElementInjectors[i].parent = this.elementInjector;

View File

@ -1,8 +1,13 @@
import {Element} from 'facade/dom'; import {DOM, Element} from 'facade/dom';
import {normalizeBlank} from 'facade/lang';
export class NgElement { export class NgElement {
domElement:Element; domElement:Element;
constructor(domElement:Element) { constructor(domElement:Element) {
this.domElement = domElement; this.domElement = domElement;
} }
getAttribute(name:string) {
return normalizeBlank(DOM.getAttribute(this.domElement, name));
}
} }

View File

@ -1,8 +1,20 @@
import {ddescribe, describe, it, iit, expect, beforeEach} from 'test_lib/test_lib'; import {ddescribe, describe, it, iit, expect, beforeEach} from 'test_lib/test_lib';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
import {Decorator, Component} from 'core/annotations/annotations'; import {Decorator, Component} from 'core/annotations/annotations';
import {TemplateConfig} from 'core/annotations/template_config';
import {DirectiveMetadata} from 'core/compiler/directive_metadata'; 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({ @Decorator({
selector: 'someSelector' selector: 'someSelector'
@ -17,13 +29,28 @@ class ComponentWithoutExplicitShadowDomStrategy {}
@Component({ @Component({
selector: 'someSelector', selector: 'someSelector',
shadowDom: ShadowDomEmulated shadowDom: new FakeShadowDomStrategy()
}) })
class ComponentWithExplicitShadowDomStrategy {} class ComponentWithExplicitShadowDomStrategy {}
class SomeDirectiveWithoutAnnotation { class SomeDirectiveWithoutAnnotation {
} }
@Component({
selector: 'withoutDirectives'
})
class ComponentWithoutDirectives {}
@Component({
selector: 'withDirectives',
template: new TemplateConfig({
directives: [ComponentWithoutDirectives]
})
})
class ComponentWithDirectives {}
export function main() { export function main() {
describe("DirectiveMetadataReader", () => { describe("DirectiveMetadataReader", () => {
var reader; var reader;
@ -35,7 +62,7 @@ export function main() {
it('should read out the annotation', () => { it('should read out the annotation', () => {
var directiveMetadata = reader.read(SomeDirective); var directiveMetadata = reader.read(SomeDirective);
expect(directiveMetadata).toEqual( 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', () => { it('should throw if not matching annotation is found', () => {
@ -47,7 +74,7 @@ export function main() {
describe("shadow dom strategy", () => { describe("shadow dom strategy", () => {
it('should return the provided shadow dom strategy when it is present', () => { it('should return the provided shadow dom strategy when it is present', () => {
var directiveMetadata = reader.read(ComponentWithExplicitShadowDomStrategy); var directiveMetadata = reader.read(ComponentWithExplicitShadowDomStrategy);
expect(directiveMetadata.shadowDomStrategy).toEqual(ShadowDomEmulated); expect(directiveMetadata.shadowDomStrategy).toBeAnInstanceOf(FakeShadowDomStrategy);
}); });
it('should return Native otherwise', () => { it('should return Native otherwise', () => {
@ -55,5 +82,22 @@ export function main() {
expect(directiveMetadata.shadowDomStrategy).toEqual(ShadowDomNative); 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]);
});
});
}); });
} }

View File

@ -8,11 +8,16 @@ import {View} from 'core/compiler/view';
import {ProtoRecordRange} from 'change_detection/change_detection'; import {ProtoRecordRange} from 'change_detection/change_detection';
import {ViewPort} from 'core/compiler/viewport'; import {ViewPort} from 'core/compiler/viewport';
import {NgElement} from 'core/dom/element'; import {NgElement} from 'core/dom/element';
import {LightDom, SourceLightDom, DestinationLightDom} from 'core/compiler/shadow_dom_emulation/light_dom';
@proxy @proxy
@IMPLEMENTS(View) @IMPLEMENTS(View)
class DummyView extends SpyObject {noSuchMethod(m){super.noSuchMethod(m)}} class DummyView extends SpyObject {noSuchMethod(m){super.noSuchMethod(m)}}
@proxy
@IMPLEMENTS(LightDom)
class DummyLightDom extends SpyObject {noSuchMethod(m){super.noSuchMethod(m)}}
class Directive { class Directive {
} }
@ -65,7 +70,7 @@ class NeedsView {
} }
export function main() { export function main() {
var defaultPreBuiltObjects = new PreBuiltObjects(null, null, null); var defaultPreBuiltObjects = new PreBuiltObjects(null, null, null, null);
function humanize(tree, names:List) { function humanize(tree, names:List) {
var lookupName = (item) => var lookupName = (item) =>
@ -88,12 +93,15 @@ export function main() {
return inj; return inj;
} }
function parentChildInjectors(parentBindings, childBindings) { function parentChildInjectors(parentBindings, childBindings, parentPreBuildObjects = null) {
if (isBlank(parentPreBuildObjects)) parentPreBuildObjects = defaultPreBuiltObjects;
var inj = new Injector([]); var inj = new Injector([]);
var protoParent = new ProtoElementInjector(null, 0, parentBindings); var protoParent = new ProtoElementInjector(null, 0, parentBindings);
var parent = protoParent.instantiate(null, null); var parent = protoParent.instantiate(null, null);
parent.instantiateDirectives(inj, null, defaultPreBuiltObjects);
parent.instantiateDirectives(inj, null, parentPreBuildObjects);
var protoChild = new ProtoElementInjector(protoParent, 1, childBindings); var protoChild = new ProtoElementInjector(protoParent, 1, childBindings);
var child = protoChild.instantiate(parent, null); var child = protoChild.instantiate(parent, null);
@ -102,13 +110,15 @@ export function main() {
return child; return child;
} }
function hostShadowInjectors(hostBindings, shadowBindings) { function hostShadowInjectors(hostBindings, shadowBindings, hostPreBuildObjects = null) {
if (isBlank(hostPreBuildObjects)) hostPreBuildObjects = defaultPreBuiltObjects;
var inj = new Injector([]); var inj = new Injector([]);
var shadowInj = inj.createChild([]); var shadowInj = inj.createChild([]);
var protoParent = new ProtoElementInjector(null, 0, hostBindings, true); var protoParent = new ProtoElementInjector(null, 0, hostBindings, true);
var host = protoParent.instantiate(null, null); 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 protoChild = new ProtoElementInjector(protoParent, 0, shadowBindings, false);
var shadow = protoChild.instantiate(null, host); var shadow = protoChild.instantiate(null, host);
@ -186,7 +196,7 @@ export function main() {
it("should instantiate directives that depend on pre built objects", function () { it("should instantiate directives that depend on pre built objects", function () {
var view = new DummyView(); 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); expect(inj.get(NeedsView).view).toBe(view);
}); });
@ -291,24 +301,51 @@ export function main() {
describe("pre built objects", function () { describe("pre built objects", function () {
it("should return view", function () { it("should return view", function () {
var view = new DummyView(); 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); expect(inj.get(View)).toEqual(view);
}); });
it("should return element", function () { it("should return element", function () {
var element = new NgElement(null); 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); expect(inj.get(NgElement)).toEqual(element);
}); });
it('should return viewPort', function () { it('should return viewPort', function () {
var viewPort = new ViewPort(null, null, null, null); 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); 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);
});
});
}); });
}); });
} }

View File

@ -7,6 +7,7 @@ import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler'; import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
import {ShadowDomEmulated} from 'core/compiler/shadow_dom';
import {Decorator, Component, Template} from 'core/annotations/annotations'; import {Decorator, Component, Template} from 'core/annotations/annotations';
import {TemplateConfig} from 'core/annotations/template_config'; 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({ @Decorator({
selector: '[my-dir]', selector: '[my-dir]',
bind: {'elprop':'dirProp'} bind: {'elprop':'dirProp'}
@ -124,7 +167,7 @@ class MyDir {
@Component({ @Component({
template: new TemplateConfig({ template: new TemplateConfig({
directives: [MyDir, ChildComp, SomeTemplate] directives: [MyDir, ChildComp, SomeTemplate, EmulatedShadowDomCmp, TrivialTemplateDirective]
}) })
}) })
class MyComp { class MyComp {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -7,14 +7,30 @@ import {Component, Decorator, Template} from 'core/annotations/annotations';
import {OnChange} from 'core/core'; import {OnChange} from 'core/core';
import {Lexer, Parser, ProtoRecordRange, ChangeDetector} from 'change_detection/change_detection'; import {Lexer, Parser, ProtoRecordRange, ChangeDetector} from 'change_detection/change_detection';
import {TemplateConfig} from 'core/annotations/template_config'; 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 {DOM, Element} from 'facade/dom';
import {int} from 'facade/lang'; import {int, proxy, IMPLEMENTS} from 'facade/lang';
import {Injector} from 'di/di'; import {Injector} from 'di/di';
import {View} from 'core/compiler/view'; import {View} from 'core/compiler/view';
import {ViewPort} from 'core/compiler/viewport'; import {ViewPort} from 'core/compiler/viewport';
import {reflector} from 'reflection/reflection'; import {reflector} from 'reflection/reflection';
@proxy
@IMPLEMENTS(ViewPort)
class FakeViewPort {
templateElement;
constructor(templateElement) {
this.templateElement = templateElement;
}
noSuchMethod(i) {
super.noSuchMethod(i);
}
}
export function main() { export function main() {
describe('view', function() { describe('view', function() {
var parser, someComponentDirective, someTemplateDirective; 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() { describe('with locals', function() {
var view; var view;
beforeEach(() => { beforeEach(() => {

View File

@ -96,8 +96,14 @@ class ListWrapper {
static bool isList(l) => l is List; static bool isList(l) => l is List;
static void insert(List l, int index, value) { l.insert(index, value); } static void insert(List l, int index, value) { l.insert(index, value); }
static void removeAt(List l, int index) { l.removeAt(index); } 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 void clear(List l) { l.clear(); }
static String join(List l, String s) => l.join(s); static String join(List l, String s) => l.join(s);
static bool isEmpty(list) => list.isEmpty;
} }
bool isListLikeIterable(obj) => obj is Iterable; bool isListLikeIterable(obj) => obj is Iterable;

View File

@ -143,12 +143,21 @@ export class ListWrapper {
list.splice(index, 1); list.splice(index, 1);
return res; 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) { static clear(list) {
list.splice(0, list.length); list.splice(0, list.length);
} }
static join(list, s) { static join(list, s) {
return list.join(s); return list.join(s);
} }
static isEmpty(list) {
return list.length == 0;
}
} }
export function isListLikeIterable(obj):boolean { export function isListLikeIterable(obj):boolean {

View File

@ -47,6 +47,9 @@ class DOM {
static List<Node> childNodes(el) { static List<Node> childNodes(el) {
return el.childNodes; return el.childNodes;
} }
static childNodesAsList(el) {
return childNodes(el).toList();
}
static clearNodes(el) { static clearNodes(el) {
el.nodes = []; el.nodes = [];
} }
@ -56,6 +59,12 @@ class DOM {
static removeChild(el, node) { static removeChild(el, node) {
node.remove(); node.remove();
} }
static insertBefore(el, node) {
el.parentNode.insertBefore(node, el);
}
static insertAllBefore(el, nodes) {
el.parentNode.insertAllBefore(nodes, el);
}
static insertAfter(el, node) { static insertAfter(el, node) {
el.parentNode.insertBefore(node, el.nextNode); el.parentNode.insertBefore(node, el.nextNode);
} }
@ -74,6 +83,12 @@ class DOM {
if (doc == null) doc = document; if (doc == null) doc = document;
return doc.createElement(tagName); 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) { static clone(Node node) {
return node.clone(true); return node.clone(true);
} }
@ -95,9 +110,15 @@ class DOM {
static hasClass(Element element, classname) { static hasClass(Element element, classname) {
return element.classes.contains(classname); return element.classes.contains(classname);
} }
static String tagName(Element element) {
return element.tagName;
}
static attributeMap(Element element) { static attributeMap(Element element) {
return element.attributes; return element.attributes;
} }
static getAttribute(Element element, String attribute) {
return element.getAttribute(attribute);
}
static Node templateAwareRoot(Element el) { static Node templateAwareRoot(Element el) {
return el is TemplateElement ? el.content : el; return el is TemplateElement ? el.content : el;
} }
@ -107,4 +128,7 @@ class DOM {
static HtmlDocument defaultDoc() { static HtmlDocument defaultDoc() {
return document; return document;
} }
static bool elementMatches(n, String selector) {
return n is Element && n.matches(selector);
}
} }

View File

@ -7,7 +7,7 @@ export var TemplateElement = window.HTMLTemplateElement;
export var document = window.document; export var document = window.document;
export var location = window.location; export var location = window.location;
import {List, MapWrapper} from 'facade/collection'; import {List, MapWrapper, ListWrapper} from 'facade/collection';
export class DOM { export class DOM {
static query(selector) { static query(selector) {
@ -40,6 +40,14 @@ export class DOM {
static childNodes(el):NodeList { static childNodes(el):NodeList {
return el.childNodes; 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) { static clearNodes(el) {
el.innerHTML = ""; el.innerHTML = "";
} }
@ -49,6 +57,14 @@ export class DOM {
static removeChild(el, node) { static removeChild(el, node) {
el.removeChild(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) { static insertAfter(el, node) {
el.parentNode.insertBefore(node, el.nextSibling); el.parentNode.insertBefore(node, el.nextSibling);
} }
@ -69,6 +85,11 @@ export class DOM {
static createElement(tagName, doc=document) { static createElement(tagName, doc=document) {
return doc.createElement(tagName); 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) { static clone(node:Node) {
return node.cloneNode(true); return node.cloneNode(true);
} }
@ -90,6 +111,9 @@ export class DOM {
static hasClass(element:Element, classname:string) { static hasClass(element:Element, classname:string) {
return element.classList.contains(classname); return element.classList.contains(classname);
} }
static tagName(element:Element):string {
return element.tagName;
}
static attributeMap(element:Element) { static attributeMap(element:Element) {
var res = MapWrapper.create(); var res = MapWrapper.create();
var elAttrs = element.attributes; var elAttrs = element.attributes;
@ -99,6 +123,9 @@ export class DOM {
} }
return res; return res;
} }
static getAttribute(element:Element, attribute:string) {
return element.getAttribute(attribute);
}
static templateAwareRoot(el:Element):Node { static templateAwareRoot(el:Element):Node {
return el instanceof TemplateElement ? el.content : el; return el instanceof TemplateElement ? el.content : el;
} }
@ -108,4 +135,7 @@ export class DOM {
static defaultDoc() { static defaultDoc() {
return document; return document;
} }
static elementMatches(n, selector:string):boolean {
return n instanceof Element && n.matches(selector);
}
} }