2009年5月23日土曜日

Greasemonkey(JavaScript)入門

ここ2週間ほどGreasemonkeyのスクリプトを毎日書き続けてる。
まじめにJavaScriptを書いたのが今回が初めてなので色々作法がわからずつまずいたところとかがあったのでまとめておく。解決した項目も未解決の項目も・・・長文になりそうだ

最初にJavaScriptの第一印象なんだけど、なんというか自由な言語。好き勝手できるし、しちゃうと収拾が付かなくなる。最初に作られた仕様の拡張拡張でここまできちゃってもうぐちゃぐちゃなのかな~という感じ
こんな言語で高度で複雑なものなんか作れるわけ無いと最初は思ってたけど色々使ってるうちに意外といける気もしてたり・・・コーディングに自分なりの制約をかけてやればちゃんと使えるのかな?

まず、Greasemonkeyを使うにあたっての開発環境だけど、ブラウザはFireFox3 アドオンにもちろんGreasemonkey、あとFireBug、これが無いと色々不便。最低限必要なものはこんな感じか
で、肝心のエディタなんだけど・・・これ普通何つかうものなの?
最初はnotepad.exeでちくちく書いてたんだけど8タブなのがどうしても我慢できずサクラエディタに切り替え。最近のプログラマにありがちなオートコンプリートが無いとコードがまともに書けない状態に陥りVisual Web Developer 2008 Express Edition をインストールして単純にエディタとして使ってるのが現状。普段使ってるエディタがVisualStudioなのでやっぱり使い慣れたエディタが一番だよね

デバッグについて、GM_logって関数でいわゆるprintfデバッグのようなことができる・・・が、ブレークポイントとか張れないのか!?FireBugでサーバー側のスクリプトに対してはブレークポイントが張れたんだけどgreasemonkeyのスクリプトが一覧に出てこないのでブレークが張れずいまだにGM_logでがんばってデバッグしてる・・・ちょっと調べたけどいまいちやり方がわからんかった。

配列をforeachしたい

for(var i in list)
{
    list[i];
}
とりあえずコレでいいみたい。ただプロトタイプ拡張(?)をすると追加したメンバー関数もリストアップされちゃうので注意らしい。まだその域に達していないので良くわからない。単純な配列であれば問題ないみたい。

ページ遷移のたびに変数が消えてしまう・・・
GM_setValue GM_getValueってのを使ってページ遷移を通じで変数の受け渡しをするみたい。だけど連想配列とか丸ごとsetValue getValueしたいけど対応している型がbool int string の3種類だけみたい。
ちょっと調べてみたらFireFox3.1からはJSON形式なるものになってオブジェクトがまるっと保存できるそうな?まぁJSONってのが何なのかわかってないんだけどねw 現状では無理みたい。toSource関数とevalってのを組み合わせることでJSON形式の文字列としてうんたらってのもあったけどよくわからなかったのでとりあえず保留。1次連想配列を id:name=value:name=value,id:name=value:name=value っていう独自の形式で出力、入力する関数を作って対応してしまった。もうちょいすればきっとこの辺を簡単にやってくれるようになるはず!

ページを開いて5秒してから実行したい
setTimeoutなる関数を使う。
setTimeout(function(){
    //code...
}, 5 * 1000);
比較的簡単でこりゃ便利と思ってたら思わぬ罠が・・・どうもこのfunction(){}の中が実行されるのが指定した時間の後になるため、function内部でfunction外部の変数を使ってると変数が変わってしまうと動作時に参照される変数も変わってしまう。ややこしいなつまり
var list = ["hoge", "boe", "moe"];
for(var i in list){
    setTimeout(function(){
        alert(list[i]);
    }, 5 * 1000);
}
こんなコードを実行するとhoge boe moe と表示してほしいのに
moe moe moe と表示されてしまう。ようはfor文が回りきった後のi=2 の状態に対してfunctionが呼ばれてしまうからみたい。回避策としては匿名の関数を作ってその中でローカル変数に使いたい値を代入してやるのが一番らくちんかな?
var list = ["hoge", "boe", "moe"];
for(var i in list){
    (function(){
        var tmp = list[i];
        setTimeout(function(){
            alert(tmp);
        }, 5 * 1000);
    })();
}
(function(){})(); でこの外側のfunctionがその場で実行されて、その中の変数tmpはそのつど作られるのでそこに値を代入(参照?)してやればうまくいくとそういう感じ

ページ遷移、リンククリック
locationというオブジェクトに対して操作することで可能
location.reload(); //ページをリロード
location.href = "http://....";  //指定のURLに飛ぶ
var a = document.getElementsByTagName("a")[0];
location.href = a.href; //aタグのリンクに飛ぶ
location.href = a.getAttribute("href"); //これはダメ どうも絶対パスじゃないといけないみたい

さて、ここから先は解決していない問題。
GM_xmlhttpRequestの結果のパース
 GM_xmlhttpRequest({
  method:"GET",
  url:target_url,
  onload:function(x)
  {
                    x.responseText;
                    //code...
                }
 });
とりあえずリクエストを発行するところまではいいんだが、結果のこのx.responseTextの処理の仕方がわからなかった。この結果をdocumentと同じように扱いたいがどうやってその形式にパースしてやるのかわからない。結局responseTextを単なるテキストとして文字列マッチで処理してしまってる。
DOMParserとかを使ってパースするとか書いてあったんだけど、リクエスト先が普通にhtmlでbrタグが閉じられてなかったりとかでまともにパースできない。でもGreasemonkeyで実際にHTMLをパースして結果をdocumentというオブジェクトであらわせてるんだからできると思うんだけどなぁ 分からん。

Arrayクラスのメンバー関数がいろいろ便利
.someとか最初知らなくて普通にforでまわしてフラグ立てたりしてた。
そんな中困ったのが.filterや.sort 関数。配列で飛び飛びの配列ID番号(list[332] = 10; list[567] = 20; みたいな感じ)が入ってるのがあって、そのID番号に意味があるんだけどそいつらを加工するのにfilterやsortを使うと。出来上がる配列がもとにあった飛び飛びのIDがきえちゃってて0,1,2 と詰まった感じのものになってしまう。これIDを引き継いでやる方法ないのかな?

別のJavaScriptで付加されたイベントの実行ができない
input type=button のオブジェクトにたいしてonclickが別JavaScriptで設定されている場合はうまく動いている気がするんだけど、img タグにonclickイベントを負荷した場合にimgタグに対してimg.click(); って呼び出しをしてもonclickイベントが通知されないみたいでコードが実行されずうんともすんとも言わない。これは何で!?結局良くわからなかったので別ソースのJavaScriptのonclick部分をコピペして自分で独自にイベントを追加しちゃいました。何でなんだろう・・・

でもまぁ、なんだかんだ言いながらも作りたいものは作れる状態になってきた。Haskellをやったときのような不自由さは感じないかなぁ。

1 件のコメント:

Otchy さんのコメント...

回答書いたよ。(URL 参照)