現在策定中のHTML 5に於いて導入が予定されている<details>要素のエミュレーションを行う簡単なJAVAスクリプトを紹介します。
今回紹介するJAVAスクリプトは、HTML 5で導入される予定の<details>要素をエミュレートするためのJAVAスクリプトです。
<details>要素とは、クリックで表示/非表示を切り替えられるブロックです。
内容モデルとしては、
と言う形になっております。
<summary>要素はクリック出来る見出し部分で、常に表示される事とされております(内容は従来のHTMLで言うところのインライン要素、HTML 5で言うところのフレージング内容要素となります)。
<details>要素にはグローバル属性に加えてopen属性が定義されており、open="open" 属性を与えると(在来HTMLなら属性値となる open だけでも良い)、初めから表示された状態になり、一回目のクリックで非表示になるようにします。
以下に<details>要素のサンプルを挙げておきます。
<details><summary>アイドル女優</summary><ul><li><a href="HORIKITA_Maki.html">堀北真希</a></li><li><a href="UETO_Aya.html">上戸彩</a></li>…</ul></details>
この例で言えば、一番目の子要素となっている<summary>要素の内容であるアイドル女優が常に表示された状態になっており、これをクリックする毎に二番目の<ul>要素が表示されたり非表示にされたりします。
現在、HTML 5は策定途上にあるため、<details>要素の扱いに変更が生じたり、<details>要素そのものが草案から削除される事もあり得ます。
実際、<details>要素については、平成22年 2月25日までに二度ほど内容モデルが変更されております。
今回紹介するスクリプトも、内容モデルの変更で使えなくなる場合もあります。
また、そのような状況ですので、まだ<details>要素を実装しているウェブブラウザは存在しません。
今回紹介するスクリプトは、平成22年 2月25日現在の仕様草案に従った<details>要素のエミュレーションを行います。
すなわち、<details>要素の一番目の子要素・<summary>要素を除いて全て予め非表示にしておき、クリックで表示/非表示を切り替えられるようにするものです。
但し、<details>要素に open="open" 属性が与えられている場合は逆に全部表示された状態のままにし、<summary>要素のクリックで非表示/表示を切り替えるものとします。
今回紹介するスクリプトについて、以下の制約がありますのでご留意ください。
文書内の全ての<details>要素がopen属性を持っている場合、ネイティヴで<details>要素をサポートしているものと誤認してしまい、何の処理も行わなくなります。
これは、ネイティヴで<details>要素をサポートしている場合、当該要素ノードには必ず open プロパティが定義されている事で判断するのですが、インターネットエクスプローラがopen属性を与えた<details>要素に対して当該プロパティを定義済みとしてしまうため、open属性が存在しない場合に限りチェックすると事にしているからです。
また、現時点でサポートしているウェブブラウザが皆無なのにこのような事をしているのは、将来サポートするウェブブラウザが登場したときにスクリプトを変えずに済ませられるようにしたかったからです。
在来HTMLとしてのHTML 5であれば、open属性は属性値だけの最小化記述が可能ですが、それだと認識が出来ないため、最小化記述はしないようにしてください。
つまり、open="open" と記述するようにしてください。
<summary>属性がない<details>要素に対しては何もしません。
仕様草案では、<summary>属性を必須としている一方、万一<summary>属性が見出せなかった場合にはユーザエージェントが適宜補うようにすべきとされておりますが、そもそも必須の要素を書かない方が悪いのでこの処理は省略しました。
これは、仕様草案にある見本画像(勿論未だ実装されているブラウザはないので架空のものです)でそのように描かれているためそうしました。
それぞれ附与します。
CSSでお好きなデザインに出来るようにするため、スタイルは殆ど決めておりません。
display: block; プロパティを与えておいてください。通常、ウェブブラウザは未知の要素はインライン要素のスタイルを取る事としているからです。この文書はXHTML 1.1でマークアップされているので(ブラウザに依ってはHTML 4.01 ストリクトまたはトランジッショナルに変換されます)、実際にHTML 5で書いたサンプルページをご案内します。
スクリプトを別ファイルにして、HTML文書内の<script>要素で埋め込みます。
尚、スクリプトはなるべく文書の後ろの方で埋め込む事をお奨めします。
実際のスクリプトのコードは以下のようになります。
// スクリプトの前処理。document.createElement('details');document.createElement('summary');document.createElement('detailscontent');dtls_onload=window.onload;// スクリプトの本処理。window.onload=function() {if (dtls_onload) dtls_onload();var e=document.getElementsByTagName('details');var i=e.length;if (i<1) return;// ネイティヴで<details>要素をサポートしているなら何も要らない。while (--i>=0) {if (!(e[i].getAttribute('open')) && e[i].open==undefined) break;}if (i<0) return;// 各<details>要素の処理。 // ※仕様書では、必須である<summary>要素がない場合には // 補うべきとされているが必須でないので手抜きする。i=e.length;while (--i>=0) {var f=e[i].childNodes;var dc=document.createElement('detailscontent');// 私的な要素。dc.style.display=(e[i].getAttribute('open')) ? 'block' : 'none';dc.style.margin='0';dc.style.padding='0';dc.style.border='none 0';var j=-1; var sm=-1;// 内容ノードの処理。while (++j<f.length) {// <summary>要素を除く。if (f[j].nodeType==1 && f[j].nodeName.toLowerCase()=='summary') {sm=j; continue;}// その他の子ノードは<detailscontent>要素の内容とする。dc.appendChild(f[j].cloneNode(true));}// <summary>要素を認識出来なかった場合は何もしない。if (sm<0) continue;// <summary>要素がクリックされた際の処理。f=(e[i].getElementsByTagName('summary'))[0];f.onclick=function () {var dc2=(this.parentNode.getElementsByTagName('detailscontent'))[0];// <summary>要素以外の内容が表示されている場合。if (dc2.style.display=='block') {dc2.style.display='none';this.innerHTML='▶'+this.innerHTML.substring(1,this.innerHTML.length);return;}// <summary>要素以外の内容が表示されていない場合。dc2.style.display='block';this.innerHTML='▾'+this.innerHTML.substring(1,this.innerHTML.length);return;}// <summary>要素にマーカを付ける。f.innerHTML=((dc.style.display=='block') ? '▾': '▶')+f.innerHTML;// <summary>要素以外を除去する。f=e[i].childNodes; j=f.length;while (--j>=0) {if (f[j].nodeType!=1 || f[j].nodeName.toLowerCase()!='summary') e[i].removeChild(f[j]);}// 内容を<detailscontent>要素にしたものを追加する。e[i].appendChild(dc);}}
スクリプトの処理の流れは以下のようになります。
前処理は以下のようになります。
先ず、最初の三行はインターネットエクスプローラが未知の要素を取扱えるようにするためのトリックです。このようにdocument.createElement()メソッドで必要な要素を発生させると、インターネットエクスプローラでも取扱が可能になります。
本処理では、以下のような処理が行われます。
続いて、ブラウザがネイティヴで<details>要素をサポートしているかどうかを調べます。
これは、文書中にある<details>要素に置いて、open属性がない要素のノードに、openプロパティがない事を確認する事で調べられます。
この方法を採ったのは、インターネットエクスプローラでは、open属性を与えた場合に自動的にopenプロパティも与えられるようになっているため、判別が不可能になってしまう事に拠ります。
確認が出来なかった場合には、ネイティヴで<details>要素をサポートしているものと見なし、従って何の処理もせずに戻ります。
この後、各<details>要素に対して処理を行います。
先ず、<summary>要素以外の内容を収める私的な要素・<detailscontent>要素を発生させ、それのデフォルトスタイルを決めます。デフォルトスタイルは、open属性があればブロックレヴェル表示、そうでなければ非表示となります。
この方法を採ったのは、内容のうち特定の要素だけ残してあとは非表示と言うのが意外に難しいからです。
<details>要素を display: none;プロパティを与えて非表示にしてしまうと、その直下にある<sumary>要素も非表示になってしまい表示させる事が出来なくなってしまいます。
ところが、CSSには、「特定の要素以外全て」と言うセレクタは存在しないため、<summary>要素以外非表示と言う記述は出来ないのです。
一方、<details>要素の内容モデルは単一の要素と決められている訳ではなく、このままだとsummary要素以外の全ての要素をいちいち切り替えなければならなくなってしまい面倒です。
しかも<details>要素の内容モデルはブロックレヴェル要素でもインライン要素でも構わない事となっており、style.displayプロパティに明確に値を与えていないと表示に切り替える際にどちらであるかを判断出来なくなってしまいます。
結局、<summary>要素以外の直下要素を単一の<detailscontent>要素に収め、それの表示/非表示を切り替えるのが最も簡単で汎用性が高くなると判断したのです。
続いて、<details>要素の子ノードのうち、<summary>要素以外の全ノードを先ほど生成した<detailscontent>要素ノードに付け足します。
このとき注意しないといけないのは、単純に子ノードを付け足そうとすると元の<details>要素ノードから移動して消えてしまい、ノード配列が崩れてしまうため、cloneNode(true)メソッドでクローンノードを生成してそれを付け足すようにします。
尚、<summary>要素ノードが見出される事を併せて確認します。
最後に、<summary>要素以外の全要素を除去して、代わりに<detailscontent>要素ノードを付け加えていち<details>要素の処理を終えます。