【js】DOM ノードを追加、置換、削除する
ノードの追加、置換、削除についてまとめます。
HTMLの編集には、innerHTMLプロパティを利用することもできますが、今回紹介する方法だと以下のようなメリットがあります。
- オブジェクトツリーとして操作できるので、対象となるコンテンツが複雑になった場合にも、コードの可読性が悪くなりにくい
- 要素、属性とテキストとを区別して扱えるので、ユーザーからの入力によってスクリプトが混入するような危険は回避しやすい
シンプルなコンテンツの編集 → innerHTMLプロパティ
複雑なコンテンツの編集 → 本節のアプローチ
ノードを追加
以下はフォームに入力した内容でリンクを追加するコードです。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>heading.html</title> </head> <body> <form> <div> <label for="name">サイト名:</label><br /> <input id="name" name="name" type="text" size="30" /> </div> <div> <label for="url">URL:</label><br /> <input id="url" name="url" type="text" size="50" /> </div> <div> <input id="btn" name="name" type="button" value="追加" /> </div> </form> <div id="list"></div> </body> <html>
document.addEventListener('DOMContentLoaded', () => { document.getElementById('btn').addEventListener('click', () => { // テキストボックスを取得 const name = document.getElementById('name'); const url = document.getElementById('url'); // <a>要素を生成 let link = document.createElement('a'); // <a>要素のhref属性を設定 link.href = url.value; // テキストノードを生成 const text = document.createTextNode(name.value); // テキストノードを<a>要素の直下に追加 link.appendChild(text); // <br>要素を生成 const br = document.createElement('br'); // <div id="list">を取得 let list = document.getElementById('list'); // <div>要素の直下に<a>,<br>要素の順番で追加 list.appendChild(link); list.appendChild(br); }, false); }, false);
1. 要素、テキストノードを作成する
コンテンツを追加するにはまず、createElement, createTextNodeメソッドを使用し、追加したい要素、テキストノードを生成します。
今回だと以下のような箇所で行われています。
// <a>要素を生成 let link = document.createElement('a'); // テキストノードを生成 const text = document.createTextNode(name.value); // <br>要素を生成 const br = document.createElement('br');
他にも生成するノードに応じて以下のようなメソッドがあります
// 要素ノード createElement(要素名) // 属性名ノード createAttribute(属性名) // テキストノード createTextNode(テキスト)
createHgehogeでノードを作成した時点では、お互いの階層関係を意識する必要はありません。
現状はバラバラのままなのでこの後の作業で組み立てる必要があります。
2. ノード同士を組み立てる
1の段階でバラバラになっているノードを組み立ててドキュメントに追加する必要があります。
この作業を行うのがappendChildメソッドです
appendChildメソッドは指定された要素を現在の要素の最後の子要素として追加します。
// テキストノードを<a>要素の直下に追加 link.appendChild(text); link.firstChild; // "qiita" // <div>要素の直下に<a>,<br>要素の順番で追加 list.appendChild(link); list.appendChild(br); list.children; //HTMLCollection(2) [a, br]length: 20: atarget: ""download: ""ping: ""rel: ""relList: DOMTokenList [value: ""]hreflang: ""type: ""referrerPolicy: ""text: "qiita"coords: ""charset: ""name: ""rev: ""shape: ""href: "https://qiita.com/"origin: "https://qiita.com"protocol: "https:"username: ""password: ""host: "qiita.com"hostname: "qiita.com"port: ""pathname: "/"search: ""hash: ""title: ""lang: ""translate: truedir: ""hidden: falseaccessKey: ""draggable: truespellcheck: trueautocapitalize: ""contentEditable: "inherit"isContentEditable: falseinputMode: ""offsetParent: bodyoffsetTop: 132offsetLeft: 8offsetWidth: 33offsetHeight: 16style: CSSStyleDeclaration {alignContent: "", alignItems: "", alignSelf: "", alignmentBaseline: "", all: "", …}innerText: "qiita"outerText: "qiita"oncopy: nulloncut: nullonpaste: nullonabort: nullonblur: nulloncancel: nulloncanplay: nulloncanplaythrough: nullonchange: nullonclick: nullonclose: nulloncontextmenu: nulloncuechange: nullondblclick: nullondrag: nullondragend: nullondragenter: nullondragleave: nullondragover: nullondragstart: nullondrop: nullondurationchange: nullonemptied: nullonended: nullonerror: nullonfocus: nullonformdata: nulloninput: nulloninvalid: nullonkeydown: nullonkeypress: nullonkeyup: nullonload: nullonloadeddata: nullonloadedmetadata: nullonloadstart: nullonmousedown: nullonmouseenter: nullonmouseleave: nullonmousemove: nullonmouseout: nullonmouseover: nullonmouseup: nullonmousewheel: nullonpause: nullonplay: nullonplaying: nullonprogress: nullonratechange: nullonreset: nullonresize: nullonscroll: nullonseeked: nullonseeking: nullonselect: nullonstalled: nullonsubmit: nullonsuspend: nullontimeupdate: nullontoggle: nullonvolumechange: nullonwaiting: nullonwheel: nullonauxclick: nullongotpointercapture: nullonlostpointercapture: nullonpointerdown: nullonpointermove: nullonpointerup: nullonpointercancel: nullonpointerover: nullonpointerout: nullonpointerenter: nullonpointerleave: nullonselectstart: nullonselectionchange: nullonanimationend: nullonanimationiteration: nullonanimationstart: nullontransitionend: nulldataset: DOMStringMap {}nonce: ""autofocus: falsetabIndex: 0enterKeyHint: ""onpointerrawupdate: nullnamespaceURI: "http://www.w3.org/1999/xhtml"prefix: nulllocalName: "a"tagName: "A"id: ""className: ""classList: DOMTokenList [value: ""]slot: ""attributes: NamedNodeMap {0: href, href: href, length: 1}shadowRoot: nullpart: DOMTokenList [value: ""]assignedSlot: nullinnerHTML: "qiita"outerHTML: "<a href="https://qiita.com/">qiita</a>"scrollTop: 0scrollLeft: 0scrollWidth: 0scrollHeight: 0clientTop: 0clientLeft: 0clientWidth: 0clientHeight: 0attributeStyleMap: StylePropertyMap {size: 0}onbeforecopy: nullonbeforecut: nullonbeforepaste: nullonsearch: nullelementTiming: ""previousElementSibling: nullnextElementSibling: brchildren: HTMLCollection []firstElementChild: nulllastElementChild: nullchildElementCount: 0onfullscreenchange: nullonfullscreenerror: nullonwebkitfullscreenchange: nullonwebkitfullscreenerror: nullnodeType: 1nodeName: "A"baseURI: "http://localhost:63342/memo/temp.html?_ijt=hbsc1amc7s3585dcv59sjlp0cf"isConnected: trueownerDocument: documentparentNode: div#listparentElement: div#listchildNodes: NodeList [text]firstChild: textlastChild: textpreviousSibling: nullnextSibling: brnodeValue: nulltextContent: "qiita"__proto__: HTMLAnchorElement1: br__proto__: HTMLCollection
ここでは、textを要素ノードlinkに追加した上で、この要素ノードlink、brをlist配下に追加しています。
appendChildメソッドは、insertBeforeメソッドに置き換えることもできます。
list.appendChild(link);
list.insertBefore(link, null);
insertBeforeメソッドは第1引数で指定したノードを、第2引数で指定した子ノードの直前に挿入します。
3. 属性ノードを追加する
属性ノードの設定は、属性と同名のプロパティを設定するだけです。
// <a>要素のhref属性を設定
link.href = url.value;
また、createAttributeメソッドで属性ノードを生成することもできます。
let href = document.createAttribute('href'); href.value = url.value; anchor.setAttributeNode(href);
- 属性ノードの値を設定するには、valueプロパティを使用
- 属性ノードを要素ノードに関連づけるには、appendChild, insertBeforeではなく、setAttributeNodeメソッドを使用
このやり方の方が冗長ですが、属性名を文字列で指定できるので、スクリプトから動的に属性名を変更できるメリットがあります。
既存ノードを置換、削除
先ほどの例に、
- すでにリンクがあった場合はリンクを変更
- リンクを削除できる
といった処理を追加します。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>heading.html</title> </head> <body> <form> <div> <label for="name">サイト名:</label><br /> <input id="name" name="name" type="text" size="30" /> </div> <div> <label for="url">URL:</label><br /> <input id="url" name="url" type="text" size="50" /> </div> <div> <input id="btn" name="name" type="button" value="追加" /> <input id="del" type="button" value="削除" disabled/> </div> </form> <div id="list"></div> </body> <html>
document.addEventListener('DOMContentLoaded', () => { const name = document.getElementById('name'); const url = document.getElementById('url'); const del = document.getElementById('del'); let list = document.getElementById('list'); let btn = document.getElementById('btn'); // 追加ボタンがクリックされた時の処理 btn.addEventListener('click', () => { let link = document.createElement('a'); link.href = url.value; const text = document.createTextNode(name.value); link.appendChild(text); // <div>要素配下に<a>要素が存在するか(リンクが追加されているか) if (list.getElementsByTagName('a').length > 0) { // 存在している場合はリンクを変更する list.replaceChild(link, list.lastChild); btn.value = '変更'; } else { // 存在していない場合でサイト名が入力されているかどうか if (link.text) { // 新しいリンクを追加し、削除ボタン有効、追加ボタンを変更ボタンに list.appendChild(link); del.disabled = false; btn.value = '変更'; } } }, false); // 削除ボタンがクリックされた時の処理 del.addEventListener('click', () => { // <div id="list">配下の子要素を削除し、削除ボタンを有効、変更ボタンを追加ボタンに list.removeChild(list.lastChild); del.disabled = true; btn.value = '追加'; }, false) }, false);
1. ノードを置換する
子ノードを置き換えるには、replaceChildメソッドを使用します。
list.replaceChild(link, list.lastChild);
第1引数に置き換えたいノードを、第2引数に置き換え対象のノードを指定します。
置き換え対象のノードは現在のノードに対する子ノードでならなければいけません。
2. ノードを削除する
子ノードを削除するには、removeChildメソッドを使用します。
list.removeChild(list.lastChild);
引数には、削除したい対象のノードを指定します。
こちらも同様に、対象のノードは現在のノードに対する子ノードでならなければいけません。