JavaScript初級者から中級者になろう

戻る四章第一回四章第三回

四章第二回 クリックして表示・非表示を切り替える

前回に続いて、よく見かけるものを作ってみます。今回は、長いページを見やすくするために、ある部分を隠しておいて、リンクか何かをクリックすると、隠れた部分を表示するというものを作ってみます。

仕様を決める

まず、どこからどこまでがひとまとめで、まとめて隠すべき部分なのかを表すために、そのひとまとめにして表す、つまり何かの要素でまとめて囲む必要があります。こういうときの汎用的な要素は、div要素でした。

とりあえず、まず用意するHTMLはこんな感じにします。 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html> <head> <title>test</title> </head> <body> <div> <h1>なんたら</h1> <p>なんたらかんたらなんたらかんたらなんたらなんたらなんたらら。</p> </div> <div> <h1>かんたら</h1> <p>かんたらかんたらなんたらなんたらかんたらなんたらかんたらたら。</p> <p>なんたらかんたら。なんたらかんたらかんたらなんたらなんたらかんたら。</p> </div> <div> <h1>なんとか</h1> <p>なんとかかんとかなんとかかんとかなんとかとかとかかんとかなんとか。なんとかかんとかなんとか。</p> </div> <script type="text/javascript"> </script> </body> </html> この文書では、本文がdivによって3つに分けられているのが分かります。このそれぞれについて、隠したり表示したりできるようにしようというわけです。

さて、この状態で、隠したり表示したりするような要素に対して処理をするには、div要素に対して処理すればいいように思えます。しかし、そういうわけにもいきません。なぜなら、まったく関係ないdiv要素が他のところにあるかもしれないからです。

それで、隠せるようにしたいdiv要素に目印をつけることにします。当然何かの属性を使うわけですね。まず思いつくのは、前回使ったtitle属性だと思います。しかし、今回はそれは使いません。もっとこういうのに適切な属性があります。

それはclass属性です。CSSなどでも使いますね。これをつけておいて、それを、扱うdiv要素と関係ないdiv要素を区別する目印にしましょう。クラス名はなんでもいいです。 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html> <head> <title>test</title> </head> <body> <div class="long"> <h1>なんたら</h1> <p>なんたらかんたらなんたらかんたらなんたらなんたらなんたらら。</p> </div> <div class="long"> <h1>かんたら</h1> <p>かんたらかんたらなんたらなんたらかんたらなんたらかんたらたら。</p> <p>なんたらかんたら。なんたらかんたらかんたらなんたらなんたらかんたら。</p> </div> <div class="long"> <h1>なんとか</h1> <p>なんとかかんとかなんとかかんとかなんとかとかとかかんとかなんとか。なんとかかんとかなんとか。</p> </div> <script type="text/javascript"> </script> </body> </html> 今回は、「long」というクラス名を付けてみました。長いのを隠して読みやすくするという目的でやっているから、「このdiv要素は長い!」というのを表す感じです。本当はもっといいネーミングがあるのでしょうが、自分はあまりこだわりません。今回はこれでいきましょう。

さて、div要素を隠したり表示したりするには、そのトリガーとなる、クリックするためのものが必要です。場所は、div要素の直前あたりがいいでしょう。

だからといって、div要素のために何かそういうものを書いておくかというと、そういうわけにはいきません。なぜなら、JavaScriptが使えない(JavaScriptを使わない)環境の人が見たとき、その要素はまったく意味を成さないからです。前回も解説したとおり、JavaScriptで何かするなら、それはあくまでHTMLを補助する立場でなければいきません。できるだけ、JavaScriptのためにその大元であるHTMLを汚してはならないのです。

ではどうすればいいかというと、HTMLに直接書いておくのではなく、JavaScript側からあとで追加します。そうすれば、JavaScriptを使わない場合は無駄なものは出現しません。

では、HTMLのほうは、もうこれ以上手をつけないということにします。いよいよJavaScriptを書いていくことになります。

実際につくる

まず、ひとつひとつのdiv要素を見て回って、その前にクリックする要素を付け足していくことから始まります。 var divs = document.getElementsByTagName('div'); for(var i=0; i<divs.length; i++){ } document.getElementsByTagNameは、二章第六回で解説したもので、指定したタグ名の要素をリストにして返すというものでした。ひとつひとつの要素はitem(番号)というようにitemメソッドで取得します。今回は'div'を引数に指定しているから、div要素のリストが返ってくるというわけです。

class属性で判別するわけですが、class属性を参照するには、classNameプロパティ(二章第六回)を使うのでした。珍しく、属性名とプロパティが違うものなので、注意が必要です。

それでは、こんな感じでしょうか。 var divs = document.getElementsByTagName('div'); for(var i=0; i<divs.length; i++){ var div = divs.item(i); if(div.className == "long"){ } } これで、class属性が"long"であるdivだけを処理できます。しかし、違います。なぜなら、class属性には複数のクラス名を指定できるからです。例えば、 <div class="aaa bbb ccc"> 〜 </div> このようにすると、"aaa" "bbb" "ccc"の3つのクラスに属しているということになります。ひとつひとつのクラスは、空白文字で区切るということになっています。

そこで、今回どうするかというと、正規表現というものを使います。これは、高度な文字列の検索ができるもので、今回このようにします。 var divs = document.getElementsByTagName('div'); for(var i=0; i<divs.length; i++){ var div = divs.item(i); if(/(?:^|\s)long(?:$|\s)/.test(div.className)){ } } /(?:^|\s)long(?:$|\s)/」という記号の羅列が、testというメソッドを持っていて、その引数にdiv.classNameを渡しています。この記号の羅列は正規表現オブジェクトというオブジェクトを作るもので、これが検索の条件を表しています。そのtestメソッドを呼び出すと、引数の文字列がその条件と合うか調べて、真偽値で結果を返します。

この正規表現オブジェクトは、「前が無いか空白で、後ろが無いか空白の、"long"という文字列」が含まれているという条件を表します。これで、とにかくlongというクラスを持った要素であるかが分かります。興味があったら、もっと詳しく調べてみましょう。

さて、条件に合うdiv要素に対してする処理は、「クリックする要素を追加する」というものでした。適当に、p要素を、そのdiv要素の前に追加しておきましょう。

また、そのp要素には、関係ないp要素と区別するために、またクラスを設定しておきましょう。クラス名は、とりあえず「隠すためのもの」という意味で"hider"とでもしてみます。 var divs = document.getElementsByTagName('div'); for(var i=0; i<divs.length; i++){ var div = divs.item(i); if(/(?:^|\s)long(?:$|\s)/.test(div.className)){ var newp = document.createElement('p'); newp.appendChild(document.createTextNode("[表示/隠す]")); newp.className = "hider"; div.parentNode.insertBefore(newp, div); } } createElement,appendChild,createTextNode,insertBeforeなどは、二章第六回でまとめて出てきました。

新しいp要素を作る部分はいいとして、最後の div.parentNode.insertBefore(newp, div);について解説しておきます。

DIVの前に追加するということは、 | ├――DIV |   | |   └―・・・ ├――DIV |   | |   └―・・・ └――DIV     |     └―・・・ 例えばこのようになっている木構造のDIVについて、 | | ├―― P | ├――DIV |   | |   └―・・・ ├―― P | ├――DIV |   | |   └―・・・ ├―― P | └――DIV     |     └―・・・ このように追加するということです。このとき、DIVとPは兄弟ノードであり、すなわち同じ親を持ちます。だから、P要素は「DIV要素の親ノード」の子ノードとして追加されることになります。insertBeforeの第二引数は、挿入する場所の直後のノードなので、DIV要素自身となります。

さて、無事にP要素を追加したし、あとはクリックされたときのイベントの設定です。イベントでする動作は、クリック時、「クリックされた要素が追加したP要素なら、その次の要素の表示/非表示を切り替える」という動作です。完璧とはいえない仕様ですが、まあこんなものでいいでしょう。

var divs = document.getElementsByTagName('div'); for(var i=0; i<divs.length; i++){ var div = divs.item(i); if(/(?:^|\s)long(?:$|\s)/.test(div.className)){ > var newp = document.createElement('p'); newp.appendChild(document.createTextNode("[表示/隠す]")); newp.className = "hider"; div.parentNode.insertBefore(newp, div); } } var listener = function(ev){ if(/(?:^|\s)hider(?:$|\s)/.test(ev.target.className)){ } }; document.addEventListener('click', listener, false); こんな感じでしょう。listenerの中のif文は、上のdivのときと同じで、「ev.target.classNameにhiderというクラス名が含まれていたら、処理する」ということです。

その中で処理するのは、次のノードの表示/非表示を切り替えるというものでした。次のノードはnextSibling二章第七回)で取得できるからいいとして、表示・非表示を切り替えるというのはどうやってするのでしょうか。

実は、これはCSSの力を借ります。CSSで「display」というのがあり、これを"none"にするとその要素が消えます。CSSの扱い方は二章第十一回で解説したので、細かい解説は省略します。

すると、こんな感じになります。 var listener = function(ev){ if(/(?:^|\s)hider(?:$|\s)/.test(ev.target.className)){ var next = ev.target.nextSibling; if(next.style.display != "none"){ next.style.display = "none"; } } }; 次の要素は、nextという変数に入れました。さて、次のif文で、next.style.display != "none"とは、「displayがnoneでない場合」、つまり表示されている場合です。その場合、非表示にしたいので、noneを設定します。

では、既に非表示の場合は、今度は表示するようにしなければいけません。ここで、表示するにはdisplayに何が入っていればいいのでしょうか。実は複数あり、場合によって違います。これでは面倒なので、今回は別の方法を使います。

それは、displayに何も入っていない状態にするということです。普段、div要素などを使うときにいちいちCSSでdisplayを指定しませんが、正しく表示されます。これは、ブラウザが適切に判断しているからです。そこで、displayを指定していない状態に戻して、ブラウザに判断させればいいのです。

そのためには、次のようにします。 var listener = function(ev){ if(/(?:^|\s)hider(?:$|\s)/.test(ev.target.className)){ var next = ev.target.nextSibling; if(next.style.display != "none"){ next.style.display = "none"; }else{ next.style.removeProperty("display"); } } }; ここで、新しいメソッドremovePropertyが登場しました。見ての通り、styleが持つメソッドです。引数は文字列が1つですね。

このメソッドの動作は簡単で、CSSのプロパティを何も入っていない状態にするのです。どのプロパティかは引数で指定します。今回は"display"だから、displayを何も入っていない状態にするということです。

これで、表示されているときは非表示に、非表示のときは表示されるようになります。

これで、完成しました。最終的なソースは、 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> <html> <head> <title>test</title> </head> <body> <div class="long"> <h1>なんたら</h1> <p>なんたらかんたらなんたらかんたらなんたらなんたらなんたらら。</p> </div> <div class="long"> <h1>かんたら</h1> <p>かんたらかんたらなんたらなんたらかんたらなんたらかんたらたら。</p> <p>なんたらかんたら。なんたらかんたらかんたらなんたらなんたらかんたら。</p> </div> <div class="long"> <h1>なんとか</h1> <p>なんとかかんとかなんとかかんとかなんとかとかとかかんとかなんとか。なんとかかんとかなんとか。</p> </div> <script type="text/javascript"> var divs = document.getElementsByTagName('div'); for(var i=0; i<divs.length; i++){ var div = divs.item(i); if(/(?:^|\s)long(?:$|\s)/.test(div.className)){ var newp = document.createElement('p'); newp.appendChild(document.createTextNode("[表示/隠す]")); newp.className = "hider"; div.parentNode.insertBefore(newp, div); } } var listener = function(ev){ if(/(?:^|\s)hider(?:$|\s)/.test(ev.target.className)){ var next = ev.target.nextSibling; if(next.style.display != "none"){ next.style.display = "none"; }else{ next.style.removeProperty("display"); } } }; document.addEventListener('click', listener, false); </script> </body> </html> となります。これそのままだと何か見栄えが悪い気もしますが、そこはCSSでいくらでも調整できます。今回、表示/非表示になるdivにはlong、クリックするための要素にはhiderというクラスをそれぞれつけたので、CSSで調整するのは簡単だと思います。