DOMの変更は “ライブ” ページを作成するための鍵です。

ここでは、新しい要素を “その場” で作成し、既存のページコンテンツを変更する方法を見ていきます。

まず、簡単な例を見てメソッドを説明します。

例: メッセージを表示

はじめに、alert よりも見栄えの良いメッセージをページに追加する方法を見てみましょう。

次のように見えるとしましょう:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<div class="alert">
  <strong>Hi there!</strong> You've read an important message.
</div>

これは HTML の例でした。では JavaScript で同じ div を作ってみましょう(スタイルは依然として HTML の中、もしくは外部CSSにあると想定します)。

要素の作成

DOM ノードを作成するためのメソッドが2つあります。:

document.createElement(tag)

与えられたタグの新しい要素を作成します:

let div = document.createElement('div');
document.createTextNode(text)

与えられたテキストの新しい テキストノード を作成します:

let textNode = document.createTextNode('Here I am');

メッセージの作成

我々のケースでは、与えられたクラスとメッセージをその中にもつ div を作りたいです。:

let div = document.createElement('div');
div.className = "alert alert-success";
div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

これで、準備ができたDOM要素があります。 今は変数に入っていますが、まだページには挿入されていないため見えません。

挿入メソッド

div を表示するためには、document のどこかに挿入する必要があります。例えば、document.body です。

そのための特別なメソッドがあります: document.body.appendChild(div).

ここに完全なコードを載せます:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<script>
  let div = document.createElement('div');
  div.className = "alert alert-success";
  div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

  document.body.appendChild(div);
</script>

ここに、親要素(略して parentElem )へノードを挿入するメソッドの簡単なリストを示します:

parentElem.appendChild(node)

parentElem の最後の子として node を追加します。

次の例は新しい <li><ol> の末尾に追加します:

<ol id="list">
  <li>0</li>
  <li>1</li>
  <li>2</li>
</ol>

<script>
  let newLi = document.createElement('li');
  newLi.innerHTML = 'Hello, world!';

  list.appendChild(newLi);
</script>
parentElem.insertBefore(node, nextSibling)

parentElem の中の nextSibling の前に node を挿入します。

次のコードは2つ目の <li> の前に新しいリストアイテムを挿入します:

<ol id="list">
  <li>0</li>
  <li>1</li>
  <li>2</li>
</ol>
<script>
  let newLi = document.createElement('li');
  newLi.innerHTML = 'Hello, world!';

  list.insertBefore(newLi, list.children[1]);
</script>

最初の要素として挿入するには、このようにします:

list.insertBefore(newLi, list.firstChild);
parentElem.replaceChild(node, oldChild)

parentElem の子供の間の oldChildnode に置き換えます。

これらすべてのメソッドは挿入したノードを返します。言い換えると、parentElem.appendChild(node)node を返します。しかし通常は返却値は使わず、単にメソッドを実行するだけです。

これらのメソッドは “古い学校” です: これらは古代から存在し、多くの古いスクリプトで見ることができます。残念なことに、それらでは解決するのが難しい作業がいくつかあります。

例えば、文字列として持っている html を挿入する方法は?もしくは指定されたノードに対し、その に別のノードを挿入する方法は? もちろん、すべて実行可能ですが、エレガントな方法ではありません。

したがって、簡単にすべてのケースを処理する2つの挿入メソッドセットが存在します。

prepend/append/before/after

このメソッドのセットはより柔軟な挿入を提供します:

  • node.append(...nodes or strings)node の末尾にノードまたは文字列を追加します,
  • node.prepend(...nodes or strings)node の先頭にノードまたは文字列を挿入します,
  • node.before(...nodes or strings) –- node の前にノードまたは文字列を追加します,
  • node.after(...nodes or strings) –- node の後にノードまたは文字列を追加します,
  • node.replaceWith(...nodes or strings) –- node を与えられたノードまたは文字列で置き換えます。

これらのメソッドを使用して、リストに項目を追加し、項目の前後にテキストを追加する例を次に示します。:

<ol id="ol">
  <li>0</li>
  <li>1</li>
  <li>2</li>
</ol>

<script>
  ol.before('before');
  ol.after('after');

  let prepend = document.createElement('li');
  prepend.innerHTML = 'prepend';
  ol.prepend(prepend);

  let append = document.createElement('li');
  append.innerHTML = 'append';
  ol.append(append);
</script>

これはメソッドがしていることを示す図です:

なので、最終的には次のようなリストになります:

before
<ol id="ol">
  <li>prepend</li>
  <li>0</li>
  <li>1</li>
  <li>2</li>
  <li>append</li>
</ol>
after

これらのメソッドは1回の呼び出しで複数のノードやテキストのリストを挿入できます。

例えば、これは文字列と要素が挿入されます:

<div id="div"></div>
<script>
  div.before('<p>Hello</p>', document.createElement('hr'));
</script>

すべてのテキストは テキストとして 挿入されます。

なので、最終的なHTMLは次のようになります:

&lt;p&gt;Hello&lt;/p&gt;
<hr>
<div id="div"></div>

言い換えると、elem.textContent がするように、文字列は安全な方法で挿入されています。

従って、これらのメソッドは DOM ノードまたはテキストを挿入するためにのみ使うことができます。

しかし、仮に elem.innerHTML のようにすべてのタグやものが動作するよう、 “HTML として” HTMLを挿入したい場合にはどうすればよいでしょう?

insertAdjacentHTML/Text/Element

もう1つ、非常に多彩なメソッドがあります: elem.insertAdjacentHTML(where、html)

最初のパラメータは文字列で、挿入場所を指定し、次のどれかでなければなりません:

  • "beforebegin"elem の前に html を挿入します,
  • "afterbegin"elem の中の先頭に html を挿入します,
  • "beforeend"elem の中の末尾に html を挿入します,
  • "afterend"elem の後に html を挿入します.

2つ目のパラメータは “そのまま” 挿入されるHTML文字列です。

例:

<div id="div"></div>
<script>
  div.insertAdjacentHTML('beforebegin', '<p>Hello</p>');
  div.insertAdjacentHTML('afterend', '<p>Bye</p>');
</script>

…は次のようになります:

<p>Hello</p>
<div id="div"></div>
<p>Bye</p>

これは任意のHTMLをページに追加できる方法です。

これは挿入バリエーションの図です:

これと前の図との類似点は簡単に気づけます。 挿入ポイントは実際には同じですが、このメソッドはHTMLを挿入します。

メソッドは2つの兄弟がいます:

  • elem.insertAdjacentText(where, text) – 同じ構文ですが、HTML の代わりに “テキストとして” 挿入される text の文字列です。
  • elem.insertAdjacentElement(where, elem) – 同じ構文ですが、要素を挿入します。

これらは主に構文を “統一的” にするために存在します。実際にはほぼ insertAdjacentHTML だけが使われます。なぜなら、要素やテキストに対しては、append/prepend/before/after というメソッドがあるためです – これらはより短く書くことができ、ノード/テキストの部分を挿入することが可能です。

したがって、これはメッセージを表示する別の方法です:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<script>
  document.body.insertAdjacentHTML("afterbegin", `<div class="alert alert-success">
    <strong>Hi there!</strong> You've read an important message.
  </div>`);
</script>

ノードのクローン:cloneNode

似たようなメッセージを複数挿入するにはどうすればよいでしょう?

我々は関数を実行してコードをそこに置くことができました。 しかし、別の方法は、既存の divクローン し、その中のテキストを変更することです(必要な場合)。

大きな要素を持っているケースで、この方法がより速くシンプルになる場合があります。

  • 呼び出し elem.cloneNode(true) は、すべての属性とサブ要素を含む要素の “ディープ” クローンを作成します。もし elem.cloneNode(false) を呼び出すと、子要素なしのクローンが作られます。

メッセージをコピーする例です:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<div class="alert" id="div">
  <strong>Hi there!</strong> You've read an important message.
</div>

<script>
  let div2 = div.cloneNode(true); // メッセージをクローンします
  div2.querySelector('strong').innerHTML = 'Bye there!'; // クローンを変更します

  div.after(div2); // 既存の div の後に div2 を表示
</script>

削除メソッド

ノードを削除するために、次のメソッドがあります:

parentElem.removeChild(node)
parentElem から node を削除します(parentElem の子と想定)
node.remove()
その場所から node を削除します。

2つ目のメソッドの方がはるかに短いのが分かると思います。1つ目のメソッドは歴史的な理由で存在しています。

注意:

もしある要素を別の場所に 移動 したいとき – 古い場所からそれを除去する必要はありません。

すべての挿入メソッドは自動的に古い場所からノードを削除します

例えば、要素を入れ替えてみましょう:

<div id="first">First</div>
<div id="second">Second</div>
<script>
  // 削除呼び出しする必要はありません
  second.after(first); // #second の後に #first を挿入します
</script>

1秒後に消えるメッセージを作りましょう:

<style>
.alert {
  padding: 15px;
  border: 1px solid #d6e9c6;
  border-radius: 4px;
  color: #3c763d;
  background-color: #dff0d8;
}
</style>

<script>
  let div = document.createElement('div');
  div.className = "alert alert-success";
  div.innerHTML = "<strong>Hi there!</strong> You've read an important message.";

  document.body.append(div);
  setTimeout(() => div.remove(), 1000);
  // もしくは setTimeout(() => document.body.removeChild(div), 1000);
</script>

“document.write” について

もう1つ、webページに何かを追加する非常に古代の方法があります: document.write です。

構文:

<p>Somewhere in the page...</p>
<script>
  document.write('<b>Hello from JS</b>');
</script>
<p>The end</p>

document.write(html) の呼び出しは html を “すぐにその場で” ページに書き込みます。html 文字列は動的に生成することができるので、柔軟性があります。JavaScriptを使用して本格的なWebページを作成し、それを書き出すことができます。

このメソッドは DOMがなく、標準もないときに出来ました… 本当に昔です。それは、それを使っているスクリプトがあるので、まだ生きています。

現代のスクリプトでは、重要な制限があるためほとんど見かけません。

document.writeの呼び出しはページがロードされている間だけ動作します

もしその後に呼び出した場合、既存のドキュメントのコンテンツが削除されます。

例:

<p>After one second the contents of this page will be replaced...</p>
<script>
  // 1秒後に document.write をします
  // それはページがロードされた後です、なのでそれは既存のコンテンツを削除します
  setTimeout(() => document.write('<b>...By this.</b>'), 1000);
</script>

したがって、上で取り上げたような他の DOMメソッドとは異なり、“ロード後” の段階では利用できません。

それは欠点でした。

技術的には、ブラウザがまだ HTML を読んでいる間に document.write が呼び出されると、それは何かを追加し、ブラウザは最初と同じようにそれを消費します。

それは我々に良い面をもたらします – それは DOMの修正がないため 驚くほど速く動作します。DOMがまだビルドされていない間、それは直接ページテキストに書き込み、ブラウザは生成時にそれをDOMを挿入します。

なので、HTMLの動的に多くのテキストを追加する必要があり、またページをロードするフェーズであること、速度を考慮する必要がある場合にはそれは役立ちます。しかし実際にはこれらの要件が一緒に来ることは殆どありません。通常、このメソッドを見るときは、単に古いスクリプトだからと言う理由です。

サマリ

新しいノードを生成するメソッド:

  • document.createElement(tag) – 与えられたタグを要素を作成します
  • document.createTextNode(value) – テキストノードを作成します (めったに使われません)),
  • elem.cloneNode(deep) – 要素をクローンします。 deep==true の場合、すべての子孫も含みます。

ノードの挿入と削除:

  • 親から:

    • parent.appendChild(node)
    • parent.insertBefore(node, nextSibling)
    • parent.removeChild(node)
    • parent.replaceChild(newElem, node)

    これらのメソッドはすべて node を返します。

  • 与えられたノードと文字列のリスト:

    • node.append(...nodes or strings)node の末尾に追加します,
    • node.prepend(...nodes or strings)node の先頭に挿入します,
    • node.before(...nodes or strings) –- node の前に追加します,
    • node.after(...nodes or strings) –- node の後に追加します,
    • node.replaceWith(...nodes or strings) –- node を置き換えます。
    • node.remove() –- node を削除します.

    テキスト文字列は “テキストとして” 挿入されます。

  • 与えられた HTML の一部: elem.insertAdjacentHTML(where, html), where に応じて挿入します:

    • "beforebegin"elem の前に html を挿入します,
    • "afterbegin"elem の中の先頭に html を挿入します,
    • "beforeend"elem の中の末尾に html を挿入します,
    • "afterend"elem の後に html を挿入します.

    また、類似メソッド elem.insertAdjacentTextelem.insertAdjacentElement があり、それらはテキスト文字列と要素を挿入しますが、めったに使われません。

  • ロードが完了する前に、ページにHTMLをつかするには:

    • document.write(html)

    ページがロードされた後、この呼び出しはドキュメントを削除します。ほぼ古いスクリプトで見られます。

タスク

重要性: 5

空のDOM要素 elem と文字列 text があります。

これら3つのどのコマンドが正確に同じことをするでしょうか?

  1. elem.append(document.createTextNode(text))
  2. elem.innerHTML = text
  3. elem.textContent = text

答え: 1 and 3.

両方のコマンドは elem へ “テキストとして” text を追加します。

例:

<div id="elem1"></div>
<div id="elem2"></div>
<div id="elem3"></div>
<script>
  let text = '<b>text</b>';

  elem1.append(document.createTextNode(text));
  elem2.textContent = text;
  elem3.innerHTML = text;
</script>
重要性: 5

要素からすべてを削除する関数 clear(elem) を作成してください。

<ol id="elem">
  <li>Hello</li>
  <li>World</li>
</ol>

<script>
  function clear(elem) { /* your code */ }

  clear(elem); // リストをクリアします
</script>

まず、それを しない 方法を見てみましょう:

function clear(elem) {
  for (let i=0; i < elem.childNodes.length; i++) {
      elem.childNodes[i].remove();
  }
}

これは動作しません。なぜなら、remove() の呼び出しは集合 elem.childNodes をシフトするからです。なので、要素は毎回インデックス 0 から開始します。しかし、 i は増加するので、いくつかの要素はスキップされるでしょう。

for..of ループも同じです。

正しいバリアントは次のようになります:

function clear(elem) {
  while (elem.firstChild) {
    elem.firstChild.remove();
  }
}

また、よりシンプルな方法として次のように書くこともできます:

function clear(elem) {
  elem.innerHTML = '';
}
重要性: 1

例を実行してください。なぜ table.remove() はテキスト "aaa" を削除しないのでしょうか?

<table id="table">
  aaa
  <tr>
    <td>Test</td>
  </tr>
</table>

<script>
  alert(table); // テーブルがあります

  table.remove();
  // なぜドキュメントの中にまだ aaa があるのでしょう?
</script>

このタスクの HTML は正しくありません。これが奇妙な事象の理由です。

ブラウザはそれを自動的に修正する必要があります。しかし、<table> の中にテキストはないかもしれません。: 仕様によると、テーブル固有のタグだけが許可されます。したがって、ブラウザは <table>前に "aaa" を追加します。

これで、テーブルを削除したときに文字列が残っていた理由は明白になりました。

この問題はブラウザツールを使ってDOMを調べると簡単に答えることができます。<table> の前に "aaa" があります。

HHTML標準では、悪いHTMLを処理する方法を詳細に指定しています。このようなブラウザの動作は正しいです。

重要性: 4

ユーザの入力からリストを作成するインタフェースを書いてください。

すべてのリスト項目に対して:

  1. prompt を使用して、ユーザにそのコンテンツについて訪ねます。
  2. それを持つ <li> を作成し、<ul> に追加します。
  3. ユーザが入力をキャンセルするまで続けます(Esc を押すかプロンプトの CANCEL をするか)。

すべての要素は動的に作られる必要があります。

もしユーザが HTMLタグを入力したとき、それはテキストのように扱う必要があります。

新しいウィンドウでデモ

<li> の中身を割り当てるための textContent の使い方に注意してください。

サンドボックスで解答を開く

重要性: 5

入れ子オブジェクトから、入れ子の ul/li のリストを作成する関数 createTree を書いてください。

例:

let data = {
  "Fish": {
    "trout": {},
    "salmon": {}
  },

  "Tree": {
    "Huge": {
      "sequoia": {},
      "oak": {}
    },
    "Flowering": {
      "redbud": {},
      "magnolia": {}
    }
  }
};

構文:

let container = document.getElementById('container');
createTree(container, data); // container の中にリストを作ります

結果(ツリー)は次のようになる必要があります。:

このタスクを解決する2つの方法のうち1つを選んでください:

  1. ツリーのためのHTMLを生成し、container.innerHTML で割り当てます。
  2. ツリーノードを生成し、DOMメソッドで追加します。

両方をすることができれば素晴らしいです。

P.S. ツリーは空の <ul></ul> のような余分な要素を葉に持つべきではありません。

タスクのためのサンドボックスを開く

オブジェクトを歩いて回る最も簡単な方法は再帰を使う方法です。

  1. innerHTML を使った解法.
  2. DOM を使った解法.
重要性: 5

入れ子の ul/li で構成されているツリーがあります。

<li> にその子孫の数を追加するコードを書いてください。葉(子を持たないノード)はスキップしてください。

結果:

タスクのためのサンドボックスを開く

テキストノード data を修正することで、各 <li> にテキストを追加することができます。

サンドボックスで解答を開く

重要性: 4

関数 createCalendar(elem, year, month) を書いてください。

この呼び出しは、与えられた年/月でカレンダーを作成し、elem の中に置きます。

カレンダーはテーブルである必要があり、その週は <tr> で、日は <td> です。テーブルのトップは曜日の <tr> です: 最初の日は月曜で、日曜まで続きます。

例えば、createCalendar(cal, 2012, 9) は要素 cal に次のカレンダーを生成します:

P.S. このタスクでは、カレンダーの生成で十分です。まだクリック可能にする必要はありません。

タスクのためのサンドボックスを開く

私たちは文字列としてテーブルを作成します: "<table>...</table>" 、そしてそれを innerHTML に代入します。

アルゴリズムは次の通りです:

  1. <th> と曜日名でテーブルヘッダを作成します。
  2. 日付オブジェクト d = new Date(year, month-1) を生成します。これは month の最初の日です(JavaScriptでの月は 1 からではなく 0 から開始することを考慮してください)。
  3. 月の最初の日 d.getDay() までの最初の数個のセルは空であるかもしれません。 それらを <td></td> で埋めましょう。
  4. d で日を増やしましょう: d.setDate(d.getDate()+1) 。もし d.getMonth() がまだ次の月ではない場合、カレンダーに新しいセル <td> を追加します。もしそれが日曜であれば、改行 “</tr><tr>” を追加します。
  5. もし月は終わりだが、行はまだ埋まっていない場合は、空の <td> を追加し、四角形を作ります。

サンドボックスで解答を開く

重要性: 4

次のような色付き時計を作成してください:

タスクのためのサンドボックスを開く

まず、HTML/CSS を作りましょう。

時間の各要素は独自の <span> ではっきり見えます:

<div id="clock">
  <span class="hour">hh</span>:<span class="min">mm</span>:<span class="sec">ss</span>
</div>

またそれらを色付けするために CSS が必要です。

udpate 関数は時計をリフレッシュし、毎秒 setInterval によって呼び出されます:

function update() {
  let clock = document.getElementById('clock');
  let date = new Date(); // (*)
  let hours = date.getHours();
  if (hours < 10) hours = '0' + hours;
  clock.children[0].innerHTML = hours;

  let minutes = date.getMinutes();
  if (minutes < 10) minutes = '0' + minutes;
  clock.children[1].innerHTML = minutes;

  let seconds = date.getSeconds();
  if (seconds < 10) seconds = '0' + seconds;
  clock.children[2].innerHTML = seconds;
}

(*) で、毎回現在の日付をチェックします。setInterval の呼び出しは信頼できません: 遅延が発生する可能性があります。

時計管理の関数です:

let timerId;

function clockStart() { // run the clock
  timerId = setInterval(update, 1000);
  update(); // (*)
}

function clockStop() {
  clearInterval(timerId);
  timerId = null;
}

update() の呼び出しは clockStart() でスケジュールされるだけでなく、行 (*) でも即時実行さることに注意してください。それ以外の場合は、訪問者はsetIntervalの最初の実行まで待つ必要があります。また、時計はそれまで空です。

サンドボックスで解答を開く

重要性: 5

2つの <li> の間に <li>2</li><li>3</li> を挿入するコードを書いてください。:

<ul id="ul">
  <li id="one">1</li>
  <li id="two">4</li>
</ul>

どこかに HTML を部分を挿入する必要がある場合、insertAdjacentHTML がベストです。

解決策:

one.insertAdjacentHTML('afterend', '<li>2</li><li>3</li>');
重要性: 5

テーブルがあります:

Name Surname Age
John Smith 10
Pete Brown 15
Ann Lee 5
... ... ...

そこにはもっと多くの行があるかもしれません。

"name" 列でソートするコードを書いてください。

タスクのためのサンドボックスを開く

解答は短いですが、少しトリッキーに見えるかもしれないので、ここでは詳細なコメントをします。

let sortedRows = Array.from(table.rows)
  .slice(1)
  .sort((rowA, rowB) => rowA.cells[0].innerHTML > rowB.cells[0].innerHTML ? 1 : -1);

table.tBodies[0].append(...sortedRows);
  1. table.querySelectorAll('tr') のように、すべての <tr> を取得し、それらから配列を作ります。なぜなら配列メソッドが必要なためです。

  2. 最初の TR (table.rows[0]) は実際にはテーブルのヘッダです、なので `.slice(1) で残りを取ります。

  3. 最初の <td> (name フィールド) のコンテンツで比較してソートします。

  4. .append(...sortedRows) で正しい順序でノードを挿入します。

    テーブルは常に暗黙の要素を持っているので、それを取り、その中に挿入する必要があります。単純な table.append(...) は失敗します。

    私たちはそれらを削除する必要がないことに留意してください。単に “再挿入” です。それらは自動的に古い場所を去ります。

サンドボックスで解答を開く

チュートリアルマップ

コメント

コメントをする前に読んでください…
  • 自由に記事への追加や質問を投稿をしたり、それらに回答してください。
  • 数語のコードを挿入するには、<code> タグを使ってください。複数行の場合は <pre> を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。
  • 記事の中で理解できないことがあれば、詳しく説明してください。