文章の中に長いコードを書いた時とかに、そのコードをデフォルトでは表示しないようにしておいて、「ボタンをクリックしたらコードが表示されて、もう一回クリックしたらまた隠される」みたいな操作をできるようにしたい。

ググると、jQueryを使った方法がいくつか見つかったけど、ライブラリを使わずに作った方が融通が効く気がしたので、ライブラリを使わず作った。

実例

ここをクリックすると展開(文字列)
ふがふが
閉じる
ここをクリックすると展開(ソースコード)
閉じる

実装

コードは下記のような感じ。

<div>
    <div style="text-decoration:underline" onclick="var style = this.parentNode.getElementsByTagName('div')[1].style; style.display=(style.display ==='block' ?'none':'block')">たいとる</div>
    <div style="display:none">
        <div>
            ほげほげ
        </div>
        <div style="text-decoration:underline" onclick="this.parentNode.style.display='none'">閉じる</div>
    </div>
</div>

「たいとる」のところにボタンとして表示される文字列を書いて、「ほげほげ」のところにコンテンツを書く。

隠したり表示したりするもの(「ほげほげ」にあたるもの)がソースコードの場合、<とか>とか”とかをそのまま書くと、HTMLのタグとして解釈されてしまってうまく表示されない。

下記のような方法を使えば、<とか>とか”とかを意識せずに済む。

ただし、${...}という文字列を含めると、この箇所がうまく表示されない(これは、JavaScriptのテンプレート文字列を使っているため。後述する)。

<div>
    <div style="text-decoration:underline" onclick="var style = this.parentNode.getElementsByTagName('div')[1].style; style.display=(style.display ==='block' ?'none':'block')">ここをクリックすると展開</div>
    <div style="display:none">
        <div>
            <pre><code><script type="text/javascript">
                var src = 
`ここに、<とか>とか"が入った文字列を書くと、htmlタグと解釈されず、ちゃんとそのまま表示される。`;
                var scripts=document.getElementsByTagName('script');
                scripts[scripts.length-1].parentNode.appendChild(document.createTextNode(src));
            </script></code></pre>
        </div>
        <div style="text-decoration:underline" onclick="this.parentNode.style.display='none'">閉じる</div>
    </div>
</div>

実装の詳細: 隠したり表示したりする処理

参考にさせていただいたもの

下記のページを参考にさせていただいた。

http://k-hiura.cocolog-nifty.com/blog/2009/06/htmldiv-512d.html

このページの方法だと、id属性を使って要素を特定してる。

けど、id属性を使うと、1つのHTMLにボタンをいっぱい設置したい時に大変そう。

なので、クリックされた要素(this)からDOMツリーを辿る形で要素を特定する形に変更した。

動作の概要

DOMツリーを辿ることで要素を特定して、style属性をblockにしたりnoneしたりすることで要素を表示したり隠したりしてる。

DOMツリー

DOMツリーの構造は下記。

div
├ div # タイトル(例における、「ここをクリックすると展開」に相当)
└ div 
    ├ div # コンテンツ
    └ div # 「閉じる」

DOMツリーの辿り方

タイトルが書かれたdivタグと、「閉じる」のdivタグで、onclick属性を使って、クリックされた時の動作を指定してる。

タイトルが書かれたdivタグのonclick属性での処理

var style = this.parentNode.getElementsByTagName('div')[1].style; 
style.display=(style.display ==='block' ?'none':'block');
1行目

クリックされたdivタグ(this)から、parentNodeで親に移動。

getElementsByTagName(‘div’)を使って、子孫のdivタグの配列を取得。

配列の1番目を取ることで、コンテンツと「閉じる」を囲むdivタグに移動(0番目はタイトルを囲むdiv)。

そのdivタグのstyle要素を取得しておく。

2行目

style属性のdisplayがblockならnoneを設定し、noneならblockを設定するようにする。

備考

thisからnextSiblingに移動する方法だとうまくいかなかった。

「閉じる」のdivタグのonclick属性での処理

this.parentNode.style.display='none';

「閉じる」の親(parentNode)が、コンテンツと「閉じる」を囲むdivタグなので、そこに移動。

「閉じる」ボタンは表示を隠すしかしないので、単にnoneに設定するだけ。

備考

最初、「閉じる」のdivタグはbuttonタグとかspanタグにしようと思ったけど、WordPressかテーマかが何かしているのか、buttonタグとかspanタグの親にpタグが挿入されて、うまくいかなかった。

便利だと思う使い方

辞書ツールに、下記のような感じで登録しておく。

読み: たいとるとぐる
<div><div style="text-decoration:underline" onclick="var style = this.parentNode.getElementsByTagName('div')[1].style; style.display=(style.display ==='block' ?'none':'block')">

読み: こんてんつとぐる
</div><div style="display:none"><div>

読み: えんどとぐる
</div><div style="text-decoration:underline" onclick="this.parentNode.style.display='none'">閉じる</div></div></div>

「たいとるとぐる」の変換で出てくる部分を入れて、タイトルを入力。

その後、「こんてんつとぐる」の変換で出てくる部分を入れて、コンテンツを入力。

最後に、「えんどとぐる」で変換して確定。

これで、「ボタンのクリックで、HTMLの一部を隠したり表示したりする」をHTML内に埋め込める。

実装の詳細: <とか>とか”とかを意識しないための処理

コード再掲。

            <pre><code><script type="text/javascript">
                var src = 
`ここに、<とか>とか"が入ったコードを書いたら、そのまま表示される。`;
                var scripts=document.getElementsByTagName('script');
                scripts[scripts.length-1].parentNode.appendChild(document.createTextNode(src));
            </script></code></pre>

preタグの下に、<とか>とか”とかが入ったコードをそのまま書くと、htmlのタグとみなされてしまう。

なので、JavaScriptの文字列として記述(`の中に記述)した上で、その文字列を(preタグの下の)codeタグに埋め込むようにする。

こうすると、<とか>とか”とかをそのまま書いても問題ない。

scriptタグ内でscriptタグへの参照を取得する方法は、下記のYahoo!知恵袋の記事で完璧に説明されてた。

https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1189943841

JavaScriptで、バックティック文字(`)を使って表した文字列は、テンプレート文字列というやつ(今回これを作るために調べてて知った。。。)。

テンプレート文字列を使うと、文字列内での改行をそのまま記述できる。

けど、テンプレート文字列内では、${...}が特別な意味を持ってしまうので、理想形を目指すなら、テンプレート文字列とは別の方法で、改行とかそのまま記述する方法(ヒアドキュメントの実現方法)を模索した方がいいかもしれない。。。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/template_strings#%E8%A4%87%E6%95%B0%E8%A1%8C%E6%96%87%E5%AD%97%E5%88%97

文字列は、createTextNodeを使ってテキストノードに変換した後、appendChildを使ってcodeタグの子として挿入してる。

追記:

最初、変数をvarでなくconstで宣言してたけど、constだとダメだった。

constを使うと、1つのhtmlに複数のコードを埋め込もうとした時、「すでにsrcという変数が存在しますよ」というエラーが出てしまって、2つ目以降が表示されなくなる。