前回はGreasemonkeyの基本的な使い方について解説しました。今回からは本題の通りGreasemonkeyによるアプリケーション開発の話を進めていきたいと思います。
どのページを開いていても、ちらっとカレンダを見ることができたら便利ですよね?(「ちょっと首傾けるだけで本物の紙のカレンダを見ることができてすでに便利だ、これ以上カレンダなんていらないよ」という方、今すぐそのカレンダもしくはあたなの席を移すか、そのカレンダを捨てさってください)(←これは冗談ですよ。念のため) 少なくとも私は便利に思いました。そこでページ内にカレンダを表示する機能をGreasemonkeyによるアプリケーションとして実現することとします。具体的には図1のようなカレンダをページ内に表示できるようにします。
カレンダを命令一発で表示できるとよいのですが、そんな魔法はありません(魔法のようなカレンダライブラリは世の中にすでにあると思いますが、ここではそのようなライブラリに頼るのはやめます)。少なくとも私は知りません。javascriptに標準で用意されているDateオブジェクトとDOM操作を駆使して作成することにします。
まずはカレンダを表現するHTMLをそのままページに挿入してみる
まずは単純にその月のカレンダを表示することから取りかかります。HTMLのTABLE(とTR、TH、TD)タグを利用してカレンダを表示することとします。できあがりのHTMLは図2のようになることを想定しています。
この形式のHTMLを実行時点の月に合わせて作成すればよいわけですが、まずは試しに固定的に2007年8月の月間カレンダを表すHTMLを用意して、それをページ内に出力するユーザスクリプトを作成してみます。
今回最初のユーザスクリプトは図3です。
(1)でカレンダを表すHTMLをE4Xを使って変数tableにセットしています。E4XはECMAScriptにXMLを表現する文法とセマンティクスを追加する拡張仕様です。仕様について詳しくはECMAのページに載っているドキュメントに示されていますが、ここではXMLをインラインで記述することでxml型の値を表すことができ、xml型の値はtoSource関数を呼び出すことでそのxmlの文字列表現を取得することができる、ということだけ分かればこのユーザスクリプトの処理は理解できると思います。
(2)でdiv要素を作成し、(3)でその内側のhtmlを(1)で作成したカレンダを示すhtmlに設定しています。最後に(4)でページのbody要素の先頭に作成したdiv要素を追加しています。
このユーザスクリプトを実行すると、図4のように質素なカレンダが表示されます。
最初の単純なユーザスクリプトとしてはできは上々だと思いますが、まだまだもの足りません。以下のような不満があると思います。
- (1) 常に2007年8月に固定のカレンダである。実行時の月のカレンダにしたい。
- (2) ページの一番上に表示されてしまい、スクロールすると見えなくなってしまう・見栄えがよくない。
- (3) 常にカレンダが表示されているのは邪魔である。必要なときだけ表示するようにしたい。
上から順に不満を解決していきたいと思います。
実行時の月のカレンダにする
実行時の月のカレンダにするには、実行時の月を取得する必要があります。これには組み込みオブジェクトのDateを使います。new Date()を実行することでその時点の日時情報を持つDateオブジェクトが返ります。Dateオブジェクトは内部で保持している日時情報を取得、設定するメソッドを持っています。そこで、このメソッド群を活用して、実行時の月を取得し、日にちを一日ずらしながらカレンダを表すHTMLを生成する、というアプローチをとることとします。
前ページでで示したスクリプト(図3)ではHTML文字列をインラインで用意してそれをそのままdiv要素内のHTMLとして設定することでページ内にカレンダを挿入していました。このようにHTMLを生成するのにできあがりのHTMLの文字列を生成していく方法をとることもできますが、DOMオブジェクトを生成してツリーを構成していく方法もあります。ここでは後者のDOMツリーを構成していく方法をとることとします。
一般に、DOMツリーを構成する場合、document.createElement()やElement#appendChild()(Greasemonkeyでは正確にはElementそものではなくXPCNativeWrapperでラップされたオブジェクト)を利用することになりますが、そのまま使うとプログラムが長くなって読みにくくなったり、階層構造が分かりにくくなったりしがちです。そこで私はこの手のプログラムを書くときは短くプログラムが書けるようにするためのユーティリティ関数を自作して対処しています。図5が私が自作したユーティリティ関数の定義です。
これらのユーティリティ関数を使ったところで実行速度が上がる訳ではありません。むしろ多少下がるでしょう。しかし込み入ったツリーを作るプログラムでは見た目上分かりやすくなる方が多少の速度低下よりもスクリプトのメンテナンス性が高くなると考え、このような関数を使うことにしています。
使い方の具体例を図6に示します。
いかがでしょう。階層構造も分かりやすいし、各タグ要素の属性値も読みやすいと思います。階層構造が分かりやすいのはインデントのおかげですが、インデントをサポートする機能のあるエディタであれば、おおむね上記のようなインデントになるでしょう。前者のコードも文法を無視してインデントすれば階層構造の理解に役立てることはできるでしょうが、そのようなインデントを自らつけるのは大変だと思います。
さて前置きが長くなってしまいましたが、肝心の実行時の月のカレンダHTMLを作成するスクリプトを図7に示しましょう。
図7のmakeCalendarTable関数で実行時の月のカレンダを表すtableタグをルートとしてDOMツリーを生成しいます。内部の処理は三つの関数内関数定義に分離して読みやすいようにしました。
- (1)setMonthHeader
- "年/月"の形式で月を表すヘッダを生成する関数
- (2)setDayHeader
- 曜日を表すヘッダを生成する関数
- (3)setDates
- カレンダの本体である日にちを表すセル要素を生成する関数
(1)(2)の関数は行数も少ないですし、解説の必要はないでしょう。(3)の関数は(1)(2)と比べると多少行数も多いので簡単に処理手順を解説します。
まず、現在の日時を取得して、1日に設定にします((3)-1)。そして、曜日に合わせて前の月の日曜日にずらしてから((3)-2)、一日ずつずらしてセルを生成する処理を週単位で繰り返し((3)-3,4)、次の月に入る週まで生成したら終了します((3)-5,6)。
最後の行でdiv要素にmakeCalendarTable()で生成したカレンダのtableを追加し、それをbody要素の先頭に追加することで、カレンダを表示しています。これで最初の不満は解消できました。
見栄えを調整する
第2の不満は
「ページの一番上に表示されてしまい、スクロールすると見えなくなってしまう・見栄えがよくない。」
でした。これはCSSを設定することで解決できます。GreasemonkeyではCSSを追加設定するためのGM_addStyle関数が用意されていますので、これを利用することにします。
図7のユーザスクリプトのmakeCalendarTable関数の処理に、タグ要素にid属性やclass属性の値を設定する処理を追加して、CSSを設定しやすいようにします。そして、GM_addStyle関数でCSSを設定します。その結果が図8のユーザスクリプトです。
基本的な処理方法は図7と変わっていません。変更点は大きく見ると2点で、(1)CSS用にid属性とclass属性を各要素に追加する処理を加えたことと、(2)GM_addStyle関数を使ってCSSを設定するsetStyle関数を追加したこと、です。
図8内に(1)(2)の変更点に関わる箇所についてコメントでも示しておきました。(2)のsetStyle関数ではE4Xで<></>の子要素としてCDATAセクションを使うことでヒアドキュメントライクにCSSを記述しています。また、そこで定義しているCSSでスクロールしても常に画面の左上隅に表示されるようにプロパティを設定しています。
以上で第2の不満も解消できました。
キーの押し下げでカレンダの表示/非表示を切り替える
第3の不満は
「常にカレンダが表示されているのは邪魔である。必要なときだけ表示するようにしたい。」
でした。「必要なとき」に特定のキーを押すことでカレンダを表示するようにすることで解決することにします。
表示/非表示の切り替えはCSSのdisplayプロパティの値を"block"/"none"に切り替えることで実現します。キーの押し下げイベントのリスナー関数を作成し、特定のキーが押されているときにこの切り替え処理を行えばOKです。
図8のユーザスクリプトの最後の部分をこの処理方式をとるよう、図9のように書き換えます。
はじめに、キーの押し下げイベントの処理設定をするための関数keybindを定義しています。カレンダの表示/非表示の切り替え処理一つのためだけにこのようなユーティリティ関数を定義するのは無駄に感じられると思いますが、今後もっと多くの操作をキー操作で行えるようにする可能性も見据えてこの関数を作成しました。
keybind関数の利用方法は簡単で、図8の最後の行のように、キーを示す文字列と、そのキーが押されたときに実行する関数を指定するだけです。キー文字列の指定方法はkeybind関数内で定義している関数codeが返す文字列の形式で、アルファベットや数字だけでなくカーソルキーなども"up","down",left","right"といった文字列で指定でき、修飾キーと組み合わせた場合にも対応できるようにしています。例えばシフトキーを押しながらスペースキー、の場合であれば "S-space" で指定できます。
カレンダの表示/非表示の処理自体は単純で、カレンダのフレームを表すdiv要素のスタイルのdisplayプロパティの値を切り替えるようにしているだけです。
以上で第3の不満も解決できました。
今回のまとめ
最初は質素でしかも2007年8月に固定のカレンダを表示するだけのユーザスクリプトでしたが、キーバインドで表示/非表示を切り替えられる今月カレンダを表示できるユーザスクリプトにまで成長させることができ、ちらっとカレンダを確認したい、というときに使えるようなものにできたのではないかと思います。
今回のポイントは以下の2点です。
- (1) スクリプトが読みやすく/書きやすくなるよう、ユーティリティ関数を定義するなどの工夫をする
- (2) CSSで見栄えを設定することはできるが、表示中に含まれるページ内のCSS定義とパッティングしないように工夫する
(1)については、私が作成したユーティリティ関数を使うようにしましょう、ということではなく、読みやすくなるよう・書きやすくなるように工夫しながらスクリプトを書くようにしましょう、ということです。速度性能が要求されるケースでは読みやすさだけを重視したような関数定義はよろしくないと思いますが、それほどの速度性能がGreasemonkeyのユーザスクリプトに要求されることはほとんどないでしょう。
(2)については、ユーザスクリプトで設定するCSSと、表示中のページ内に含まれるCSS定義とバッティングしてしまうと、ユーザスクリプトで生成/挿入したHTML要素の見栄えが崩れしまったり、表示中のページのレイアウトが崩れてしまったりします。CSSで指定するセレクタに使うクラスやIDが表示中のページで使われているものと重なりにくいものになるようにする、などの工夫が必要になります。
次回の予告
次回はこのカレンダを「Greasemonkeyによるアプリケーション」らしくなるよう進化させ、今月だけでなく別の月も表示できるようにしたり、予定情報の管理機能をつけたりしようと思います。