TypeScript便利ですね。
自動で型チェックしてくれたり、クラスベース言語ぽい書き方ができたりと、BabelだけでJavaScript書いていた頃の小さなストレスが無くなり、とっても快適なのですが
jQueryやReactDOMの ↓ みたいなDOM操作を、生Typescriptでできたら、もっと最高なのにな...(・_・)
jQuery
$('#container').append('<div>Hello world!</div>');
ReactDOM
render(
<div>Hello world!</div>,
document.getElementById('container')
);
と思い、次のようなクラスを定義してみました↓
クラスの定義
/**
### DOM
Substitute of querySelector, appendChild.
*/
export type NodeType = Element | HTMLElement | DocumentFragment;
export class DOM {
private parser: DOMParser = new DOMParser();
el: NodeType = null;
constructor(node?: NodeType | string) {
if (typeof node === 'string' && !/[<>]/.test(node)) {
this.el = document.querySelector(node) as NodeType;
} else if (typeof node === 'string' && /[<>]/.test(node)) {
const collection: NodeList = this.parser.parseFromString(node, 'text/html').body.childNodes;
const doms: Element[] = [].slice.call(collection) as Element[];
if (doms.filter((e: Element) => !(e instanceof Text)).length === 1) {
this.el = doms[0];
} else {
this.el = document.createElement('div');
const fragment: DocumentFragment = document.createDocumentFragment();
for (let [i, l]: number[] = [0, doms.length]; i < l; i++) fragment.appendChild(doms[i]);
this.el.appendChild(fragment);
}
} else if (node instanceof Element || node instanceof DocumentFragment) {
this.el = node;
} else {
this.el = document.createDocumentFragment();
}
}
/**
Substitute of append - childNode:(string | Element | HTMLElement | DocumentFragment)
*/
public append(childNode: NodeType | string): Element | DocumentFragment | boolean {
if (typeof childNode === 'string' && this.el) {
const fragment: DocumentFragment = document.createDocumentFragment();
const collection: NodeList = this.parser.parseFromString(childNode, 'text/html').body.childNodes;
const doms: Element[] = [].slice.call(collection) as Element[];
for (let [i, l]: number[] = [0, doms.length]; i < l; i++) fragment.appendChild(doms[i]);
this.el.appendChild(fragment);
return false;
} else if (childNode instanceof Element || childNode instanceof DocumentFragment) {
this.el.appendChild(childNode);
return false;
} else {
return this.el;
}
}
}
使い方
例1 新しい要素の作成
let container = new DOM('<div id="container"></div>');
new DOM('body').append(container.el);
例2 要素の追加
new DOM('#container').append('<div>Hello world!</div>');
例3 DocumentFragmentによる要素の追加
let fragment = new DOM();
for(let i=0; i<10; i++){
fragment.append(`
<div>要素その${i}</div>
`);
}
new DOM('#container').append(fragment.el);
ReactのようなVirtualDOMの恩恵はありませんが、動的に要素を追加したりするのが格段に楽になりました。
おまけ
要素の上書きをしたい場合は上記クラスに以下のプロパティrewriteを追加
/**
innerHTML like method - childNode: (string | Element | HTMLElement | DocumentFragment)
*/
public rewrite(childNode: NodeType | string): Element | DocumentFragment | boolean {
if (typeof childNode === 'string') {
const fragment: DocumentFragment = document.createDocumentFragment();
const collection: NodeList = this.parser.parseFromString(childNode, 'text/html').body.childNodes;
const doms: Element[] = [].slice.call(collection) as Element[];
for (let [i, l]: number[] = [0, doms.length]; i < l; i++) fragment.appendChild(doms[i]);
while (this.el.firstChild) this.el.removeChild(this.el.firstChild);
this.el.appendChild(fragment);
return false;
} else if (childNode instanceof Element || childNode instanceof DocumentFragment) {
while (this.el.firstChild) this.el.removeChild(this.el.firstChild);
this.el.appendChild(childNode);
return false;
} else {
return this.el;
}
}
要素を手前に追加したい場合は上記クラスに以下のプロパティprependを追加
/**
Substitute of prepend - childNode:(string | Element | HTMLElement | DocumentFragment)
*/
public prepend(childNode: NodeType | string): Element | DocumentFragment | boolean {
if (typeof childNode === 'string') {
const fragment: DocumentFragment = document.createDocumentFragment();
const collection: NodeList = this.parser.parseFromString(childNode, 'text/html').body.childNodes;
const doms: Element[] = [].slice.call(collection) as Element[];
for (let [i, l]: number[] = [0, doms.length]; i < l; i++) fragment.appendChild(doms[i]);
this.el.insertBefore(fragment, this.el.firstChild);
return false;
} else if (childNode instanceof Element || childNode instanceof DocumentFragment) {
this.el.insertBefore(childNode, this.el.firstChild);
return false;
} else {
return this.el;
}
}