refactor(core): remove direct accesses to DOM

Closes #713
This commit is contained in:
Marc Laval
2015-02-19 11:18:23 +01:00
committed by Misko Hevery
parent 3496c8ac54
commit 89b3995756
11 changed files with 84 additions and 29 deletions

View File

@ -1,6 +1,6 @@
import {isPresent} from 'angular2/src/facade/lang'; import {isPresent} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper} from 'angular2/src/facade/collection';
import {Element, Node, DOM} from 'angular2/src/facade/dom'; import {Element, DOM} from 'angular2/src/facade/dom';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control'; import {CompileControl} from './compile_control';
import {CompileStep} from './compile_step'; import {CompileStep} from './compile_step';
@ -25,12 +25,12 @@ export class CompilePipeline {
var additionalChildren = this._control.internalProcess(results, 0, parent, current); var additionalChildren = this._control.internalProcess(results, 0, parent, current);
if (current.compileChildren) { if (current.compileChildren) {
var node = DOM.templateAwareRoot(current.element).firstChild; var node = DOM.firstChild(DOM.templateAwareRoot(current.element));
while (isPresent(node)) { while (isPresent(node)) {
// compiliation can potentially move the node, so we need to store the // compiliation can potentially move the node, so we need to store the
// next sibling before recursing. // next sibling before recursing.
var nextNode = DOM.nextSibling(node); var nextNode = DOM.nextSibling(node);
if (node.nodeType === Node.ELEMENT_NODE) { if (DOM.isElementNode(node)) {
this._process(results, current, new CompileElement(node)); this._process(results, current, new CompileElement(node));
} }
node = nextNode; node = nextNode;

View File

@ -1,6 +1,6 @@
import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang';
import {List, MapWrapper} from 'angular2/src/facade/collection'; import {List, MapWrapper} from 'angular2/src/facade/collection';
import {TemplateElement} from 'angular2/src/facade/dom'; import {DOM} from 'angular2/src/facade/dom';
import {SelectorMatcher} from '../selector'; import {SelectorMatcher} from '../selector';
import {CssSelector} from '../selector'; import {CssSelector} from '../selector';
@ -44,7 +44,7 @@ export class DirectiveParser extends CompileStep {
var classList = current.classList(); var classList = current.classList();
var cssSelector = new CssSelector(); var cssSelector = new CssSelector();
cssSelector.setElement(current.element.nodeName); cssSelector.setElement(DOM.nodeName(current.element));
for (var i=0; i < classList.length; i++) { for (var i=0; i < classList.length; i++) {
cssSelector.addClassName(classList[i]); cssSelector.addClassName(classList[i]);
} }
@ -66,7 +66,7 @@ export class DirectiveParser extends CompileStep {
} }
// Note: We assume that the ViewSplitter already did its work, i.e. template directive should // Note: We assume that the ViewSplitter already did its work, i.e. template directive should
// only be present on <template> elements any more! // only be present on <template> elements any more!
var isTemplateElement = current.element instanceof TemplateElement; var isTemplateElement = DOM.isTemplateElement(current.element);
this._selectorMatcher.match(cssSelector, (directive) => { this._selectorMatcher.match(cssSelector, (directive) => {
if (directive.annotation instanceof Viewport) { if (directive.annotation instanceof Viewport) {
if (!isTemplateElement) { if (!isTemplateElement) {

View File

@ -1,5 +1,5 @@
import {RegExpWrapper, StringWrapper, isPresent} from 'angular2/src/facade/lang'; import {RegExpWrapper, StringWrapper, isPresent} from 'angular2/src/facade/lang';
import {Node, DOM} from 'angular2/src/facade/dom'; import {DOM} from 'angular2/src/facade/dom';
import {Parser} from 'angular2/change_detection'; import {Parser} from 'angular2/change_detection';
@ -27,17 +27,17 @@ export class TextInterpolationParser extends CompileStep {
return; return;
} }
var element = current.element; var element = current.element;
var childNodes = DOM.templateAwareRoot(element).childNodes; var childNodes = DOM.childNodes(DOM.templateAwareRoot(element));
for (var i=0; i<childNodes.length; i++) { for (var i=0; i<childNodes.length; i++) {
var node = childNodes[i]; var node = childNodes[i];
if (node.nodeType === Node.TEXT_NODE) { if (DOM.isTextNode(node)) {
this._parseTextNode(current, node, i); this._parseTextNode(current, node, i);
} }
} }
} }
_parseTextNode(pipelineElement, node, nodeIndex) { _parseTextNode(pipelineElement, node, nodeIndex) {
var ast = this._parser.parseInterpolation(node.nodeValue, this._compilationUnit); var ast = this._parser.parseInterpolation(DOM.nodeValue(node), this._compilationUnit);
if (isPresent(ast)) { if (isPresent(ast)) {
DOM.setText(node, ' '); DOM.setText(node, ' ');
pipelineElement.addTextNodeBinding(nodeIndex, ast); pipelineElement.addTextNodeBinding(nodeIndex, ast);

View File

@ -45,12 +45,12 @@ export class ViewSplitter extends CompileStep {
if (isBlank(parent)) { if (isBlank(parent)) {
current.isViewRoot = true; current.isViewRoot = true;
} else { } else {
if (current.element instanceof TemplateElement) { if (DOM.isTemplateElement(current.element)) {
if (!current.isViewRoot) { if (!current.isViewRoot) {
var viewRoot = new CompileElement(DOM.createTemplate('')); var viewRoot = new CompileElement(DOM.createTemplate(''));
var currentElement:TemplateElement = current.element; var currentElement:TemplateElement = current.element;
var viewRootElement:TemplateElement = viewRoot.element; var viewRootElement:TemplateElement = viewRoot.element;
this._moveChildNodes(currentElement.content, viewRootElement.content); this._moveChildNodes(DOM.content(currentElement), DOM.content(viewRootElement));
viewRoot.isViewRoot = true; viewRoot.isViewRoot = true;
control.addChild(viewRoot); control.addChild(viewRoot);
} }
@ -81,15 +81,17 @@ export class ViewSplitter extends CompileStep {
this._addParentElement(current.element, newParent.element); this._addParentElement(current.element, newParent.element);
control.addParent(newParent); control.addParent(newParent);
current.element.remove(); DOM.remove(current.element);
} }
} }
} }
} }
_moveChildNodes(source, target) { _moveChildNodes(source, target) {
while (isPresent(source.firstChild)) { var next = DOM.firstChild(source);
DOM.appendChild(target, source.firstChild); while (isPresent(next)) {
DOM.appendChild(target, next);
next = DOM.firstChild(source);
} }
} }
@ -107,7 +109,7 @@ export class ViewSplitter extends CompileStep {
} else if (isPresent(binding.expression)) { } else if (isPresent(binding.expression)) {
compileElement.addPropertyBinding(binding.key, binding.expression); compileElement.addPropertyBinding(binding.key, binding.expression);
} else { } else {
compileElement.element.setAttribute(binding.key, ''); DOM.setAttribute(compileElement.element, binding.key, '');
} }
} }
} }

View File

@ -1,4 +1,4 @@
import {DOM, Element, Node, Text, DocumentFragment, TemplateElement} from 'angular2/src/facade/dom'; import {DOM, Element, Node, Text, DocumentFragment} from 'angular2/src/facade/dom';
import {ListWrapper, MapWrapper, StringMapWrapper, List} from 'angular2/src/facade/collection'; import {ListWrapper, MapWrapper, StringMapWrapper, List} from 'angular2/src/facade/collection';
import {AST, ContextWithVariableBindings, ChangeDispatcher, ProtoChangeDetector, ChangeDetector, ChangeRecord} import {AST, ContextWithVariableBindings, ChangeDispatcher, ProtoChangeDetector, ChangeDetector, ChangeRecord}
from 'angular2/change_detection'; from 'angular2/change_detection';
@ -289,7 +289,7 @@ export class ProtoView {
this.instantiateInPlace = false; this.instantiateInPlace = false;
this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS)) this.rootBindingOffset = (isPresent(this.element) && DOM.hasClass(this.element, NG_BINDING_CLASS))
? 1 : 0; ? 1 : 0;
this.isTemplateElement = this.element instanceof TemplateElement; this.isTemplateElement = DOM.isTemplateElement(this.element);
this.shadowDomStrategy = shadowDomStrategy; this.shadowDomStrategy = shadowDomStrategy;
this._viewPool = new ViewPool(VIEW_POOL_CAPACITY); this._viewPool = new ViewPool(VIEW_POOL_CAPACITY);
} }
@ -311,7 +311,7 @@ export class ProtoView {
var rootElementClone = this.instantiateInPlace ? this.element : DOM.clone(this.element); var rootElementClone = this.instantiateInPlace ? this.element : DOM.clone(this.element);
var elementsWithBindingsDynamic; var elementsWithBindingsDynamic;
if (this.isTemplateElement) { if (this.isTemplateElement) {
elementsWithBindingsDynamic = DOM.querySelectorAll(rootElementClone.content, NG_BINDING_CLASS_SELECTOR); elementsWithBindingsDynamic = DOM.querySelectorAll(DOM.content(rootElementClone), NG_BINDING_CLASS_SELECTOR);
} else { } else {
elementsWithBindingsDynamic= DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS); elementsWithBindingsDynamic= DOM.getElementsByClassName(rootElementClone, NG_BINDING_CLASS);
} }
@ -323,7 +323,7 @@ export class ProtoView {
var viewNodes; var viewNodes;
if (this.isTemplateElement) { if (this.isTemplateElement) {
var childNode = DOM.firstChild(rootElementClone.content); var childNode = DOM.firstChild(DOM.content(rootElementClone));
viewNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in ProtoView viewNodes = []; // TODO(perf): Should be fixed size, since we could pre-compute in in ProtoView
// Note: An explicit loop is the fastest way to convert a DOM array into a JS array! // Note: An explicit loop is the fastest way to convert a DOM array into a JS array!
while(childNode != null) { while(childNode != null) {

View File

@ -11,6 +11,7 @@ export 'dart:html' show
Node, Node,
StyleElement, StyleElement,
TemplateElement, TemplateElement,
InputElement,
Text, Text,
window; window;
@ -57,6 +58,10 @@ class DOM {
static void setInnerHTML(Element el, String value) { static void setInnerHTML(Element el, String value) {
el.innerHtml = value; el.innerHtml = value;
} }
static String nodeName(Node el) => el.nodeName;
static String nodeValue(Node el) => el.nodeValue;
static String type(InputElement el) => el.type;
static Node content(TemplateElement el) => el.content;
static Node firstChild(el) => el.firstChild; static Node firstChild(el) => el.firstChild;
static Node nextSibling(Node el) => el.nextNode; static Node nextSibling(Node el) => el.nextNode;
static Element parentElement(Node el) => el.parent; static Element parentElement(Node el) => el.parent;
@ -87,6 +92,14 @@ class DOM {
static void setText(Node el, String value) { static void setText(Node el, String value) {
el.text = value; el.text = value;
} }
static String getValue(InputElement el) => el.value;
static void setValue(InputElement el, String value) {
el.value = value;
}
static bool getChecked(InputElement el) => el.checked;
static void setChecked(InputElement el, bool isChecked) {
el.checked = isChecked;
}
static TemplateElement createTemplate(String html) { static TemplateElement createTemplate(String html) {
var t = new TemplateElement(); var t = new TemplateElement();
t.setInnerHtml(html, treeSanitizer: identitySanitizer); t.setInnerHtml(html, treeSanitizer: identitySanitizer);
@ -163,4 +176,10 @@ class DOM {
static HtmlDocument defaultDoc() => document; static HtmlDocument defaultDoc() => document;
static bool elementMatches(n, String selector) => static bool elementMatches(n, String selector) =>
n is Element && n.matches(selector); n is Element && n.matches(selector);
static bool isTemplateElement(Element el) =>
el is TemplateElement;
static bool isTextNode(Node node) =>
node.nodeType == Node.TEXT_NODE;
static bool isElementNode(Node node) =>
node.nodeType == Node.ELEMENT_NODE;
} }

View File

@ -42,6 +42,18 @@ export class DOM {
static getOuterHTML(el) { static getOuterHTML(el) {
return el.outerHTML; return el.outerHTML;
} }
static nodeName(node:Node):string {
return node.nodeName;
}
static nodeValue(node:Node):string {
return node.nodeValue;
}
static type(node:Element):string {
return node.type;
}
static content(node:TemplateElement):Node {
return node.content;
}
static firstChild(el):Node { static firstChild(el):Node {
return el.firstChild; return el.firstChild;
} }
@ -97,6 +109,18 @@ export class DOM {
static setText(el, value:string) { static setText(el, value:string) {
el.textContent = value; el.textContent = value;
} }
static getValue(el: Element) {
return el.value;
}
static setValue(el: Element, value:string) {
el.value = value;
}
static getChecked(el: Element) {
return el.checked;
}
static setChecked(el: Element, value:boolean) {
el.checked = value;
}
static createTemplate(html) { static createTemplate(html) {
var t = document.createElement('template'); var t = document.createElement('template');
t.innerHTML = html; t.innerHTML = html;
@ -181,4 +205,13 @@ export class DOM {
static elementMatches(n, selector:string):boolean { static elementMatches(n, selector:string):boolean {
return n instanceof Element && n.matches(selector); return n instanceof Element && n.matches(selector);
} }
static isTemplateElement(el:any):boolean {
return el instanceof TemplateElement;
}
static isTextNode(node:Node):boolean {
return node.nodeType === Node.TEXT_NODE;
}
static isElementNode(node:Node):boolean {
return node.nodeType === Node.ELEMENT_NODE;
}
} }

View File

@ -22,11 +22,11 @@ class DefaultControlValueAccessor extends ControlValueAccessor {
} }
readValue(el) { readValue(el) {
return el.value; return DOM.getValue(el);
} }
writeValue(el, value):void { writeValue(el, value):void {
el.value = value; DOM.setValue(el,value);
} }
} }
@ -37,11 +37,11 @@ class CheckboxControlValueAccessor extends ControlValueAccessor {
} }
readValue(el):boolean { readValue(el):boolean {
return el.checked; return DOM.getChecked(el);
} }
writeValue(el, value:boolean):void { writeValue(el, value:boolean):void {
el.checked = value; DOM.setChecked(el, value);
} }
} }

View File

@ -12,8 +12,9 @@ export function getStringParameter(name:string) {
for (var i=0; i<els.length; i++) { for (var i=0; i<els.length; i++) {
el = els[i]; el = els[i];
if ((el.type !== 'radio' && el.type !== 'checkbox') || el.checked) { var type = DOM.type(el);
value = el.value; if ((type !== 'radio' && type !== 'checkbox') || DOM.getChecked(el)) {
value = DOM.getValue(el);
break; break;
} }
} }

View File

@ -151,12 +151,12 @@ export class SpyObject {
function elementText(n) { function elementText(n) {
var hasShadowRoot = (n) => n instanceof Element && n.shadowRoot; var hasShadowRoot = (n) => n instanceof Element && n.shadowRoot;
var hasNodes = (n) => n.childNodes && n.childNodes.length > 0; var hasNodes = (n) => {var children = DOM.childNodes(n); return children && children.length > 0;}
if (n instanceof Comment) return ''; if (n instanceof Comment) return '';
if (n instanceof Array) return n.map((nn) => elementText(nn)).join(""); if (n instanceof Array) return n.map((nn) => elementText(nn)).join("");
if (n instanceof Element && n.tagName == 'CONTENT') if (n instanceof Element && DOM.tagName(n) == 'CONTENT')
return elementText(Array.prototype.slice.apply(n.getDistributedNodes())); return elementText(Array.prototype.slice.apply(n.getDistributedNodes()));
if (hasShadowRoot(n)) return elementText(DOM.childNodesAsList(n.shadowRoot)); if (hasShadowRoot(n)) return elementText(DOM.childNodesAsList(n.shadowRoot));
if (hasNodes(n)) return elementText(DOM.childNodesAsList(n)); if (hasNodes(n)) return elementText(DOM.childNodesAsList(n));

View File

@ -39,5 +39,5 @@ export function dispatchEvent(element, eventType) {
} }
export function el(html) { export function el(html) {
return DOM.firstChild(DOM.createTemplate(html).content); return DOM.firstChild(DOM.content(DOM.createTemplate(html)));
} }