脱 jQuery の為の Dom 事情

ってことで、超今さら感たっぷりかもだけど Dom やるよ。
\* getElementsByName みたいなあまり使わないものは省くよ

# 手始めにノードの選択あたりから

## サンプル HTML

```html
<!DOCTYPE html>
<html lang="ja">
	<head>
		<meta charset="UTF-8">
		<title>Dom Sample</title>
	</head>
	<body>

		<div id="header">
			<h1><a href="#"><img src="http://placekitten.com/g/200/300" alt="sample"></a></h1>
			<ul>
				<li><a href="#">Site Map</a></li>
				<li><a href="#">Login</a></li>
			</ul>
		</div>

		<div id="nav">
			<ul>
				<li><a href="#">Home</a></li>
				<li><a href="#">About</a></li>
				<li><a href="#">Documentation</a></li>
				<li><a href="#">Download</a></li>
				<li><a href="#">Plugins</a></li>
				<li><a href="#">Support</a></li>
			</ul>
		</div>

		<div id="content">
			<h2>What is XXX?</h2>
		<p class="text">
			Sit delectus laboriosam in alias maxime ad accusamus. Delectus provident molestias voluptates quibusdam voluptas dolor alias. Nam eos qui incidunt molestiae modi voluptas earum sint autem dolores accusamus molestiae aliquid.
		</p>
		<p class="text">
			Amet quae perspiciatis dolor quos incidunt amet quaerat sunt itaque eos cupiditate eius voluptate eum. Animi enim rerum voluptates magnam obcaecati eveniet libero. Ea labore aut hic nisi temporibus animi?
		</p>
		<p class="text">
			Consectetur commodi quibusdam sed veniam in accusamus aperiam, voluptatum quasi quaerat eligendi! In sint adipisci ut exercitationem sit expedita, modi porro harum molestiae magni impedit. Accusantium ipsum ea eaque nisi.
		</p>
		
		<h2>Corporate Members</h2>
		<ul>
			<li><a href="#">Google</a></li>
			<li><a href="#">Yahoo</a></li>
			<li><a href="#">Apple</a></li>
			<li><a href="#">Microsoft</a></li>
		</ul>

		<h2>API</h2>
		<div class="LImgRTextWrap">
			<dl>
				<dt><a href="#"><img src="http://placekitten.com/g/200/300" alt="sample"></a></dt>
				<dd>Elit distinctio eveniet porro iste cumque. Atque voluptatum ducimus impedit sed eveniet quo beatae quos. Quam laudantium ad ipsam veritatis quam. Eius suscipit at autem nesciunt aliquid sequi laudantium cum.</dd>
			</dl>
		</div>

		<div class="RImgLTextWrap">
			<dl>
				<dt>Ipsum voluptatibus cumque perspiciatis possimus possimus molestias! A tempora enim fugit quam voluptatem deserunt perspiciatis exercitationem voluptatibus! Et et adipisci accusantium officia necessitatibus magni necessitatibus ut! Eligendi fugit veritatis reiciendis!</dt>
				<dd><a href="#"><img src="http://placekitten.com/g/200/300" alt="sample"></a></dd>
			</dl>
		</div>

		<div class="wrap">
			<div class="left"></div>
			<div class="right">
				<div id="uniqBox"></div>
			</div>
		</div>

		<div id="side">
			<h3>Banner</h3>
			<ul>
				<li><a href="#"><img src="http://placekitten.com/g/200/300" alt="sample"></a></li>
				<li><a href="#"><img src="http://placekitten.com/300/300?image=14" alt="sample"></a></li>
				<li><a href="#"><img src="http://placekitten.com/400/300?image=15" alt="sample"></a></li>
			</ul>
			<p class="text">
				Elit iure rem voluptatem cupiditate ducimus repudiandae voluptatibus enim delectus nisi eos ipsam molestias delectus? Assumenda tempora cumque pariatur blanditiis ratione ex qui nesciunt error. Ipsum neque nemo illo officia.
			</p>
		</div>
	</div>

	<div id="footer">
		<h2>Books</h2>
		<ul>
			<li><a href="#">Learning XXX</a></li>
			<li><a href="#">XXX First Step</a></li>
			<li><a href="#">Programming XXX</a></li>
		</ul>
		<p>Copyright 2015 XXX Foundation. XXX License</p>
		</div>

	</body>
</html>
```

## IDによる指定

```js

// jQuery
var nav = jQuery("#nav");
```

```js
// Dom
var nav = document.getElementById("nav");
```

## タグ名（要素名）による指定

```js
// jQuery
var divs = jQuery("div");
console.log( divs.length );

7
```

```js
//dom
var divs = document.getElementsByTagName("div");
console.log( divs.length );

7
```


## クラス名による指定

```js
// jQuery
var txts = jQuery(".text");
console.log(txts)

[p.text, p.text, p.text, p.text, prevObject: n.fn.init[1], context: document, selector: ".text", jquery: "2.1.4", constructor: function…]
```

```js
// dom
var txts = document.getElementsByClassName("text");
console.log(txts);

[p.text, p.text, p.text, p.text, item: function, namedItem: function]
```

## 親を指定

```js
// jQuery
var targetEle = jQuery("#uniqBox");
console.log(targetEle.parent());

[div.right, prevObject: n.fn.init[1], context: document, jquery: "2.1.4", constructor: function, selector: ""…]
```

```js 
// dom
var targetEle = document.getElementById("uniqBox");
console.log(targetEle.parentNode);

/* parentNode で親要素が返ってくる
	<div class="right">​…</div>
*/
```

### 嗚呼、closest、嗚呼、closest
jQuery の closest() がすごく便利だったんだけど、jQuery でない方の closest() は IEで使えないっぽい。IEを考えなくてい良い状況ならば使えるかもしれない（参考：[Mozilla Developer Network : Element.closest()](https://developer.mozilla.org/ja/docs/Web/API/Element/closest)）。スマホでどうなのか、Safariでどうなのかっつー検証する価値はあるな。

```js
// jQuery
var targetEle = jQuery("#uniqBox");
console.log(targetEle.closest('.wrap'));

[div.wrap, prevObject: n.fn.init[1], context: document, jquery: "2.1.4", constructor: function, selector: ""…]
```

```js

var targetEle = document.getElementById("uniqBox");
console.log(targetEle.closest('.wrap'));

/*
	<div class=​"wrap">​…​</div>​
*/
```

## 子を指定 - childNodes

```js
// jQuery
var targetEle = jQuery('.wrap');
console.log(targetEle[0].children);

[div.left, div.right, item: function, namedItem: function]
```


```js

// dom
var targetEle = document.getElementsByClassName('wrap');
console.log(targetEle[0].childNodes);

[text, div.left, text, div.right, text, item: function]
```

## 子を指定 - firstElementChild

```js
// jQuery
var targetEle = jQuery('.wrap :first');
console.log(targetEle);

[div.left, prevObject: n.fn.init[1], context: document, selector: ".wrap :first", jquery: "2.1.4", constructor: function…]
```

```js
// dom + Element Traversal API
var targetEle = document.getElementsByClassName('wrap');
console.log(targetEle[0].firstElementChild);

/*
	<div class=​"left">​</div>
*/​
```

### firstChild や nextSibling などの直接的な指定をする場合の注意

parentNode や childNodes はあくまでも Node を取得するため用意されたプロパティです。要素でなく Node ね。

Node には 要素とは違い「空白文字」や「改行」も「テキストノード」として扱われているよ。

そして、firstChild や lastChild、nextSibling や previousSibling はテキストノードも取ってきちゃうプロパティなんだ。

たとえば、さっきのこの例の最初の要素は text になっているよ

```js

// dom
var targetEle = document.getElementsByClassName('wrap');
console.log(targetEle[0].childNodes);

[text, div.left, text, div.right, text, item: function]
```

この最初の要素を出力してみるとこうなるよ

```js

var targetEle = document.getElementsByClassName('wrap');
console.log(targetEle[0].childNodes[0]);

baseURI: (...)
childNodes: (...)
data: "↵			"
firstChild: (...)
lastChild: (...)
length: 4
localName: null
namespaceURI: null
nextElementSibling: div.left
nextSibling: (...)
nodeName: "#text"
nodeType: 3
nodeValue: "↵			"
ownerDocument: (...)
parentElement: div.wrap
parentNode: (...)
previousElementSibling: null
previousSibling: (...)
textContent: "↵			"
wholeText: "↵			"
```

こので注目なのが length 。4となっているということは 4文字 存在しているということ。chrome の developer tool で dataプロパティや、textContentプロパティを見ると「改行文字1つ」と「タブ文字3つ」が含まれているのがわかる。

要するに、次の要素までの文字列全てが テキストノードとして扱われている。もし、意図した動き（たとえば要素を取りたいなど）でない場合はこの点に注意が必要。


### 要素だけを取るためには

children プロパティや Element Traversal API を利用するよ。さっきの例がまさにそれ。

```js
// dom + Element Traversal API
var targetEle = document.getElementsByClassName('wrap');
console.log(targetEle[0].firstElementChild);

/*
	<div class=​"left">​</div>
*/​
```

ついでに children を使ってみる

```js
var targetEle = document.getElementsByClassName('wrap');
console.log(targetEle[0].children);

[div.left, div.right, item: function, namedItem: function]
```

便利だね。

ってことで Element Traversal API の一部を挙げるよ

|プロパティ|取得できる要素|
|:-----------|:------------|
|firstElementChild|最初の子要素|
|lastElementChild|最後の子要素|
|nextElementSibling|次の要素|
|previousElementSibling|前の要素|


## Selectors API

querySelector() や querySelectorAll() を利用することによってより直感的な dom の取得ができるようになるよ。

```js
// Selectors API
var targetEle = document.querySelector("#nav");
console.log(targetEle);

/*
	<div id=​"nav">​…​</div>​
*/
```

```js
// Selectors API
var targetEle = document.querySelectorAll("#nav li");
console.log(targetEle);

/*
	[li, li, li, li, li, li, item: function]
*/
```

CSS のセレクター的な指定の仕方ができるのでより直感的だよね


# ノードの作成

ノードを作成し反映するには 「作成」→「DOMツリーに追加」の操作が必要です。
\* 見ていて胸焼けするコードがちょっと連発します

## ノードの作成

```js
var addLiEle = document.createElement('li');
var addAEle = document.createElement('a');
var addImg = document.createElement('img');
```

## 属性追加

属性の追加には、「定義」→「値の設定」の操作が必要

```js
var hrefAttr = document.createAttribute('href');
hrefAttr.value = "#";
var srcAttr = document.createAttribute('src');
srcAttr.value = "http://placekitten.com/g/500/500?image=12";
```

属性を要素に設定

```js
addAEle.setAttributeNode(hrefAttr);
addImg. setAttributeNode(srcAttr);
```

## ノードの追加

```js
addAEle.appendChild(addImg);
addLiEle.appendChild(addAEle);
targetEle.appendChild(addLiEle);
```

まとめるとこんな感じ

```js
var targetEle = document.querySelectorAll("#side ul")[0];
var addLiEle = document.createElement('li');
var addAEle = document.createElement('a');
var addImg = document.createElement('img');
var hrefAttr = document.createAttribute('href');
hrefAttr.value = "#";
var srcAttr = document.createAttribute('src');
srcAttr.value = "http://placekitten.com/g/500/500?image=12";

addAEle.setAttributeNode(hrefAttr);
addImg. setAttributeNode(srcAttr);

addAEle.appendChild(addImg);
addLiEle.appendChild(addAEle);
targetEle.appendChild(addLiEle);
```


# ノードの削除

ノードの削除には removeChild を使うよ。

```js
var targetEle = document.querySelectorAll("#side ul li")[1];
console.log(targetEle);
var parentEle = targetEle.parentNode;
parentEle.removeChild(targetEle);
```

querySelector を使って親ノードを指定してもよいよ

```js
var targetEle = document.querySelectorAll("#side ul li")[1];
console.log(targetEle);
var parentEle = document.querySelector("#side ul");
parentEle.removeChild(targetEle);
```


# innerHTML

createElement()やappendChild() の連発はさすがに胸焼けするよね。この胸焼けを解消する1つに innerHTMLプロパティ があるよ。

```js
var newDom = '<li><a href="#"><img src="http://placekitten.com/g/200/300?image=1"></a></li>'+
'<li><a href="#"><img src="http://placekitten.com/400/500?image=3"></a></li>'+
'<li><a href="#"><img src="http://placekitten.com/200/100?image=5"></a></li>';

var targetEle = document.querySelector("#side ul");
targetEle.innerHTML = newDom;
```



# NodeList と StaticNodeList

getElementsByTagName() の様に、ノード取得系メソッドの中には NodeList インターフェースを実装しているものもあるよ。

このインターフェースは item() メソッド と length プロパティ が定義してあり、item() メソッドは引数にインデックスを渡せば DOMツリーて定義されている順の1つにアクセスすることができるよ。

また、length プロパティは その要素数を返すよ。次のコードを見てみよう

```js
//dom
var pTags = document.getElementsByTagName("p");
console.log(pTags);

[p.text, p.text, p.text, p.text, p, p, p, item: function, namedItem: function]
```

item() という関数を確認できる。length に関しても確認してみよう。

```js
var targetTags = document.getElementsByTagName("p");
console.log(targetTags.length);

5
```

このポストで使用している サンプル HTML では p 要素が 5つあるので。思い通りの結果が返ってきている。では次のコードはどうでしょう。


```js
var targetWrap = document.getElementById("content");
var targetTags = document.getElementsByTagName("p");

console.log(targetTags.length);
5

var addEle = document.createElement('p');
targetWrap.appendChild(addEle);

console.log(targetTags.length);
6
```

getElementsByTagName() で取得しなおしていないのに、appendChild で足した分も2回目の targetTags.length で反映されている。

これは NodeList は Live Correction でもあり、常に DOMツリー への参照をを持っているからで、取得時には最新の情報を取得できるわけです。

これに対し、querySelectorAll()で取得されるオブジェクトは StaticNodeList です。StaticNodeList は Live Correction ではないので、オブジェクトに対して加えた変更は HTML ドキュメントに反映されません。

したがって上記の length のテストのようなことを行っても、思い通りの結果は返ってきません。

```js
var targetWrap = document.getElementById("content");
var targetTags = document.querySelectorAll("p");

console.log(targetTags.length);
5 // 初期状態は 5

var addEle = document.createElement('p');
targetWrap.appendChild(addEle);

console.log(targetTags.length);
5 // StaticNodeList なので 要素が追加されても 5

targetTags = document.querySelectorAll("p");
console.log(targetTags.length);
6 // 新たに取得し直すと最新の状態
```


さしずめよく使いそうなメソッドで NodeList を返すのは、getElementsByTagName()やchildNodesくらいで、逆に StaticNodeList を返すのは querySelectorAll() あたりで。