階層メニューが出来たので、、今度は本来の目的であったソフトメニュー
(ポップアップメニューとも言うらしい)を表示出来ないかを考えてみた。
ソフトメニューは、自分のHPでも各ページに付けているが、
BLOCKBLOGでは、自分のHPにつけている物とは、違ったもので実現するしかない。
それは、階層メニューでも書いたが、なにしろHTML文が直接書けないので、
BLOCKBLOGのHTML文内にある情報を用いて、実現するしかないためである。
じゃ、どうやって実現するか?
【JavaScriptとDOMの根本的な違い】
今までのJavaScript的な作り方は、前もってHTML文に操作する情報を書き込んでおくか、
またはタグをdocumenu.writeして、それをJavaScriptを用いて制御するのが基本だったんですね。
だから、BLOCKBLOGのようにHTML文を、まったくいじれない環境で、
既に表示している内容を操作して、何かを表現することなんて考えることも出来ない。
例えば、カテゴリ一覧をコピーして、
もうひとつ同じカテゴリ一覧を別なところに配置することなんて出来ないし、
そのカテゴリ一覧をポップアップメニューのように
マウスイベントを用いて制御することは、もちろん出来っこない。
じゃ、DOMではどうなの?
これがDOMでは、上記がなんとすべて出来ちゃう。
なぜ、出来るのかというと、JavaScriptと根本的に設計思想が違うんですよね。
すなわち、JavaScriptは既に存在する文書構造の範囲内でしか操作できない。
ところが、DOMは文書構造自体を制御することが可能なので、
既にある情報をいくらでもコピー出来るし、新たに情報も作れるし、
もちろん既にある情報の内容を変更することや、削除することも出来ちゃうんですね。
で、このDOMの考え方を用いて、作成したツールは、ほとんど見たことがない。
自分もそうだったが、やはりタグをdocument.writeしたりして作成いるし、
その中でonclickとかonmouseoverとか書いて、イベントを制御している。
今回、document.writeとかは使わず、ほぼ完全にDOM仕様に合わせて作成してみた。
ただ、書式だけは面倒なので、従来のIE記述で書いた。
はじめは、なにしろDOMの関数名(メソッドとか)は、やたら長いし、
さらに、タグの要素、その子要素とかを順番に操作したりするので、
ソースがかなり長くなるだろうなとも思っていた。
ところが、実際に書いてみて、慣れてくると、これが考え方が単純なため、
書くのも簡単。ソースも従来のソフトメニューのものより断然短くなった。
一度、DOM的なJavaScriptに慣れると、今までのJavaScriptはなんてダサイんだ。
と思ってしまうところが、DOMの凄いところですね。
ということで、今回のソフトメニュー導入の基本設計を。
【考え方】
?先頭のタイトル文字および画像領域と、本文の間に隙間を空けて、
ここにソフトメニューを埋め込む。
?ソフトメニューに表示する内容は、左側のmenuブロック内の各ブロックを表示する。
すなわち、カテゴリ一覧、バックナンバー、最新記事、最新コメント、最新トラックバック等。
?まず、ソフトメニューを埋め込むには、本文領域と左側のmenuブロックをソフトメニューの
高さだけ下にずらせばよい。これは、これらの領域がbox領域上で管理されているので、
このbox領域の書式をposition:absoluteに変更し、そのtop位置を必要分下にずらせばよい。
?次に空いた部分に、
左側のメニューの内容をコピーして、ソフトメニューの位置に配置する。
ソフトメニューの親メニュー1個の横サイズは、手を抜いて120px固定とする。
?子メニューの表示非表示の制御は階層メニューと同じでdisplay属性を用いればよい。
?ソフトメニューの場合は、階層メニューのようにマウスクリックで表示するのではなく、
マウスが上に載ったら隠れていた内容を表示し、
マウスが新たに表示された領域も含めて、そこから外に出たら、
数秒後にまた隠してやればよい。
?さらに、リンク部分にマウスが来たときに、「リンク出来るよ」、
「マウスが載ったよ」と分かるように背景色を違う色に変えてやる。
もちろん、そこからマウスが出たら背景色を元に戻してやる。
【DOMの基本】
ここで、DOMを本格的にいじるのは初めてなので、
勉強のつもりでちょっとまとめておこう。
で、DOMは基本的に単純な考え方で作られているんですね。
すなわち、文書を各要素のツリー構造で定義し、このツリー構造の親子関係で制御する。
例えば、今回ソフトメニューを配置するところの親は、BODYタグである。
ここから配置された各種タグが次の子要素である。
その子要素の中に定義された要素が、また子要素を構成する。その繰り返しである。
まあ、例でいうと、
<body id="body1">
<div id="div1">
<dl id="dl1">
<dt id="dt1">カテゴリ</dt>
<dd id="dd1">自己紹介</dd>
<dd id="dd2">音楽関係</dd>
</dl>
</div>
</body>
という構成だと、
最初の親 =<body id=body1">
その下の子 =<div id="div1">
さらにその子=<dl id="dl1">
さらにその子=<dt id="dt1">と<dd id="dd1">と<dd id="dd2">
さらにその子=カテゴリ、自己紹介、音楽関係の各dt,ddタグが親の文字要素
これらを、基本的には以下のメソッド(まあ制御関数みたいなもの)で制御する。
これ以外にもメソッドはあるけど、今回の目的には、これらでまあ十分かな。
親オブジェクト.appendChile(追加するオブジェクト)
オブジェクト=複製元オブジェクト.cloneNode(true)
では、具体的に基本となるポイントだけ書いてみる。
【menu領域をコピーするには】
コピーするmenu領域のDLタグおよび、それ以下にある子要素をすべてcloneNodeで複製し、
それをbodyタグにappendChildで追加すればいい。
たったこれだけで、コピーできちゃうとは、DOMは簡単ですね。凄い!!
【box領域をソフトメニューの高さ分下にずらすには】
初めは、単純にDL要素内のDT要素のoffsetHeightで求められると思っていたが、
求めた値には、CSSファイルで設定したmarginやpaddingの値がプラスされている。
そのため、指定値により、ソフトメニューの高さより大きな隙間が空いてしまう。
問題は、CSSで指定してあるmarginやpadding値をどうやって取得するかだが、
この取得方法は、出来るのか出来ないのか良く分からなかった。
そこで方針を変えて、既にあるDT要素から求めるのではなく、
コピーしたDT要素に必要なmargin,padding値を設定してやった後に求めるようにした。
すなわち、コピー後のDT要素のmarginとpaddingをソフトメニューの書式分設定後、
そのoffsetHeightを取得してやればよい。
【マウスイベントの制御】
マウスイベントは先頭の親のDLタグにommouseoverとonmouseoutを登録してやれば、
その子要素にはマウスイベントを指定しなくても、マウスイベントが反映される。
マウスイベントは、必ずoverとoutが対で来る。
イベント発生時には、それぞれのイベントで指定した制御関数内で処理してやればいい。
子メニューからマウスを外に出した時点で、即、子メニューを消すと、
まだ見ていたいのにちょっとマウスがずれただけで、消されては非常に使いづらくなるため、
普通は数秒後に消している。
ただし、再び表示しているところにマウスが入ってきたら、そのまま表示を続けるし、
数秒経たない内に、別の親メニューにマウスが載れば、その時点で、既に表示済の
子メニューを消して、対応する新たな子メニューを表示するようにしなくてはいけない。
【まとめ】
ソフトメニュー初期化関数(onLoad時に呼ばれる)
・DDが1個以上あるカテゴリ数を調べ、ソフトメニュー数を調べる
・box領域位置を算出後、box領域をabusolute属性にし、
上左Margin=0pxにして、先に求めたbox領域の左位置へずらす。
・メニュー要素と同じものを複製し、idを変える。
・複製したメニュー要素をソフトメニュー位置に配置する
・Mozzilaの場合は、デフォルトでマージンが設定されるので、マージン=0pxにする。
・ソフトメニューの書式設定(同時に各メニュー要素の最大縦サイズを算出)
・ソフトメニューの親要素DLにマウスoverイベント、マウスoutイベントを登録する。
・メニュー要素の最大縦のサイズでbox領域を下にずらす
マウスoverイベント処理
・リンク要素なら背景色を変える。
・親要素のDLタグを求める
・メニューの表示制御の判断。
・メニューを非表示なら、表示する。
マウスoutイベント処理
・リンク要素なら背景色を元に戻す。
・親要素のDLタグを求める
・メニュー表示を1.5秒後に非表示にするため、タイマイベントを登録する。
タイマイベント処理
・タイマ起動中ならクリア。
・対象となるDLタグのメニューが表示中であれば、非表示にする。
onLoad関数の登録
ということで、出来たが、ここで思いも寄らないことが。
【大問題が】
なななんと、バックナンバーが画面からはみ出してしまう。
バックナンバーは、50個ぐらいあるので、ノートPCだと画面からはみ出してしまう。
今の仕様では、画面から下にはみ出すと、マウスだけで操作しようとしても、
はみ出した下側を表示させられない。^^);;;
まっとうにやろうとすると、年代毎に分けて、そこからカスケードして横に月毎の項目を
表示するようにすればいいが、なんか大変そう。
手を抜くなら、バックナンバーの表示個数を50個でなく、15個ぐらいに設定してやればいいが、
どうしよう。
このままブログを書いてくと、途中の書いてないところを書いてけば、
当然50個では、一番最初の記事が入らなくなるので、
もっと表示件数を増やしてやればいいんですね。
とはいっても、左側のメニューの部分に沢山表示しては見苦しいけど、
今は階層メニュー化してあるのでそんなに見苦しくはないが、
JavaScript OFFだと、完全にバックナンバー沢山表示されて、ひんしゅくもんだし、
悩むとこですね。
【BLOCKBLOGの仕様が不足かな?】
ということで、思いついたのがBLOCKBLOGさんに月別リストの改善を
お願いすることでした。まあ、今の仕様では、たぶん皆さん困るときが訪れるはず。
【ではどうするの】
上の改善がやってくれるんなら、わざわざカスケード作る必要もないかもね。
ということで、BLOCKBLOGさん次第でどうするかだけど。。。。
それ待っていても仕方ないので、お願いしたことと同じようなことを先に実現しちゃいましょう。
【考え方】
BLOCKBLOGにお願いしたことは、当然出来ないけど。
バックナンバーを沢山表示させておいて、その内の10個を表示させるようにして、
残りはmoreで制御させて表示させてやれば、同じようなことが出来ますね。
このmore表示を行うには、以下のメソッドが新しく必要です。
document.createElement(作成するタグ名)
document.createTextNode(作成する文字列)
【注意したところ】
1)Operaは、DLリスト内のある範囲のDD項目を表示する際に
その項目の範囲での最大サイズで、DD項目の表示サイズを変えている。
IE,Mozzilaは、上記のようなことはなく、サイズは変らない。
【まとめ】
more表示関数(方向等)
方向は、最初にメニュー表示時、上のmoreクリック時、下のmoreクリック時の3種類
・最初なら、
・先頭からメニューに表示する最大表示数までを表示し、
最大表示数より多いものは、非表示のままとする。
・more項目のDIVタグのID=上下判定文字+親DLタグのIDとして作成する。
・more文字列をDIVタグに追加する。
・moreDIVタグを先頭と最後のDDタグに上被せで配置する。サイズはDDと同じ。
・z-index属性を親DLタグのものより+1する。
・上側のmoreDIVタグは、非表示にする。下側のmoreDIVタグは、表示する。
・moreDIVタグにonclickイベントを登録する。
・上下moreクリック時
・DDタグ内を調べ、上端表示位置と下端表示位置を求める。
・クリックされた上下moreにより、メニュー内の表示範囲を判断して表示する。
moreクリック処理
・対象となるDLタグのメニューが表示中であれば、非表示にする。
ということで完成です。
【注意点のまとめ】
?動作環境
・DOMは、ブラウザだと、IE5以上、Mozilla、NN6以上でできるようです。
・自分の環境は、IE6.0 SP1、Mozilla1.6とOpera7.23しかないので、これで確認し、
この環境のみのことで書いてある。
それ以外のバージョンや、他のブラウザはもちろん無視(動くかどうか不明)。
・OSは、WindowsXP SP1しかないので、これ以外のOSは使ったことがないため無視。
・マシンも、PCしか持ってないので、Macとかは、もちろん無視。
・BLOCKBLOGの文書構造に合わせて作成してあるので、
この文書構造以外は当然動かないで、多分エラーになります。^^);;
【MacのSafari対応】2004.09.22 追記
Macをいじる機会があったので、
以下の環境で動作確認をやってみたら、さすがに動かない。^^);;
PowerBook G4(Mac OS X Ver10.3.4)
Safari 1.2.2(v125.7)
どう動かないかと言うと、
【問題1】「プルダウンメニューは表示するが、
そこにマウスを動かすとプルダウンメニューが直ぐ消えてしまう」
調査の結果、
Mac(Safari)とそれ以外Windowsのブラウザの場合の違いは
Windows:Aタグからイベントが発生する。
Mac:最上位の親ノードと、最下位のテキストノードからイベントが発生する。
プログラム上、最下位のテキストノードからイベントが発生した場合は、
適切なノード(DLタグ)まで処理できずに途中のノードを対象として、
おかしくなっていた。
修正:
これは、親ノードの探索のやり方を正しい探索方法に変えて、
要するに親ノードがnull(親が存在する)まで探し、
もしnullなら、そのイベントを無視するように変えた。
その結果、プルダウンメニューは消えなくなったが、
【問題2】今度は、プルダウンメニューの各リンク項目にマウスが移ると、
色を変えているが、これが変らない。^^);;
調査の結果:
色の変更は、Aタグ前提で処理していたので、
Aタグの下に位置するテキストノードを想定していなかった。
そのため、テキストノードで色を変えようとしても変えられなかった。
修正:
テキストノードの場合は、その親がAタグであれば、ノードをひとつ
上のAタグに移して処理するようにした。
【問題3】プルダウンメニューの「more」の部分で、
「more」の文字の上にマウスを移すと、
マウスが「+」にならず、色も変らない。
調査の結果:
Windowsは、問題ないが、Macだとdivと#textノードが
別々に扱われるみたい。
修正:
仕方なく、イベントが#textノードから発生時は、
親の状態を判断して、対象ノードであれば、マウス形状、背景色を
変更するように変えた。
【問題4】プルダウンメニューの「more」の部分で、
「more」の文字の上にマウスを移すと、
「more」の文字上では、クリックが効かない。
調査の結果:
DIVの中にテキスト作成したのに、
DIV領域とテキスト領域でイベント範囲が異なるのは
ちょっと違うんではないのでしょうか?
テキストノードにイベントは登録できないようなので、
これって仕様の解釈が間違ってると思うんだけど?
それとも、自分が何かを勘違いしてる?
で、Windowsとは、同じにならない。この解決方法は???
修正:
Macは、これでいいやと、諦めた。
【仕様変更:2005.01.18】
今までは、box領域をabsoluteにして、下にずらし、空いたところに
ソフトメニューを埋め込む方式としていたが、
これだと、いろいろなデザインに対応できず、またCSSの設定次第では、
主要ブラウザ間で表示が異なる結果を招くことから、
ソフトメニューのみをabsoluteにすれば、デザインには左右されずに
好きなとこに配置可能である。さらに隙間は、CSSで開ければ済むので
融通性もある。
ということで、box領域を下にずらしているところを削除した。
さらに、CSS設定部分をスタイルシートで定義した書式で
表示するように修正した。これで、CSS毎にソフトメニューの色
変えられますね。
ただし、画面リサイズ等でデザインの位置が変わる場合は、
面倒なので対応してないところが手抜きですが。^^)
【でも直ぐ使いたいという気さくな方】
ここ見てね。
当然、内容理解できて、何が必要で、どうすればいいかを
判断できる人ね。で、自分で改造してね。
もちろん、サポート、問合せはごめんね。なしで。m(_ _)m