Page 6/16: « 2 3 4 5 6 7 8 9 10 »

2006-10-23

SafariのDate#setMonthがバグってたのでprototype上書きで対処してみる

JavaScriptのDateオブジェクトはdate.setMonth(-1)とやると、前年の12月にしてくれて大変便利なのですが、Safariで上手く動かなかったりするようなのでprototype上書きで対処してみました。

// fix safari's Date#setMonth
(function(){
    var set_month = Date.prototype.setMonth;
    Date.prototype.setMonth = function(num){
        if(num <= -1){
            var n = Math.ceil(-num);
            var back_year = Math.ceil(n/12);
            var month = (n % 12) ? 12 - n % 12 : 0 ;
            this.setFullYear(this.getFullYear() - back_year);
            return set_month.call(this, month);
        } else {
            return set_month.apply(this, arguments);
        }
    }
})();

引数に-1以下の数字が渡ってきた場合は、自前で何年前の何月かを計算します。それ以外の場合はオリジナルのsetMonthメソッドを呼び出します。safariでもsetMonth(12)の方は上手く動いてくれるようで、きちんと来年の1月にしてくれます。

Appleのサイト上にこんな文章があるのですが、
http://www.mac.com/web/ja/Tips/425954F3-DF73-4B9B-94AC-20EE4BDE374C.html
もしアップルが推奨するブラウザを使っていて機能しないサイトを見つけたら、 苦情を申し立てましょう。 そのサイトが、 Web 上でもっとも進化したものに対応できるブラウザでもうまく機能しないのはなぜなのか、 サイトオーナーに説明を求めるとよいでしょう。

言わずもがな、Safariがバグってるからです。

Amazonのギフト券を確実に使い切る方法

「ギフト券」っていう検索フォルダを作って、使い終わったらメールを消す。

2006-10-20

livedoor Readerに何かくっつけるGreasemonkeyの書き方

何かくっつける系のGreasemonkeyが作りやすくなってます。

エントリごとに表示

投稿日時が出てる箇所に追加されます。
window.entry_widgets.add("name", generator , "description");

generatorはfunction(feed,item){ ... }のように無名関数で記述できます。feedはフィードに関する情報、itemはその記事のリンクや記事本文が取れます。データ構造はまるごとPerlに書いてあります。

descriptionは省略可能ですが、将来的に何か気の利いたUIでも作ろうかと考えていたりするので入れておくと良いかも知れません。今のところtitle属性に使うようになってます。Greasemonkeyだと日本語が通らないのでエスケープする必要があります。

エントリにくっつけるサンプル

エントリごとにlivedoorクリップの件数表示
http://la.ma.la/misc/userjs/ldr_with_livedoor_clip_count_images.user.js

エントリごとにはてなブックマークの件数表示
http://la.ma.la/misc/userjs/ldr_with_hatena_bookmark_count_images.user.js
元ネタ:http://d.hatena.ne.jp/tnx/20060716/1152998347

フィードごとに表示

新着数、購読者数の隣の部分に表示されます。
window.channel_widgets.add("name", generator, "description");

generatorはfunction(feed,items){ ... }のように無名関数で記述できます。feedはフィードに関する情報、itemsはitemの配列です。データ構造はまるごとPerlに書いてあります。

フィードにくっつけるサンプル

はてなブックマークの合計数のカウンタ表示をくっつけるやつ
http://la.ma.la/misc/userjs/ldr_with_hbcounter.user.js
元ネタ:http://yoshiori.org/blog/2006/10/ldrfeedgreasemonkey.php

feedburnerのやっているfeedflareみたいな感じで、いくつかサービス側でプリセットを持っておいて選べるようにしようかな、とか、まあそんなことを考えてます。

保証はしませんが、なるべく後方互換性を保つようにするつもりです。

変更点

それと、結構色々変えたので、いくつか動かなくなってると思います。
フック関数は直接さわれないようになりました。こんな感じで書き換えるようにしてください。
after_init.add(function(){ ... })
↓
register_hook("after_init", function(){ ... })


PlaggerLDRで使う奴はここにおいてあります。
http://ma.la/tmp/hackldrapi.user.js

ご意見ご要望などあれば、IRCの#livedoor@freenodeとか常駐してるので、お気軽にどうぞ。あとJavaScriptプログラマーも募集しているのでお気軽にどうぞ。

----
追記:2006-10-20

widgetsの中でエラーが起きるとフィードが全部表示できなくなってしまうので例外処理を入れるようにしました。開発しやすくなったはず。
あと、デフォルトで付いてるやつも同じ仕組みを使って表示していて、外すこともできるようになってます。

entry_widgets.remove('created_on'); // 投稿日時を消す

2006-10-13

FirefoxでJSONPのロードされるタイミングを調べてみた

Firefoxでのスクリプト読み込みの同時実行について
http://d.hatena.ne.jp/shinichitomita/20061013/1160707042

を読んで。
Opera以外は読み込み順に実行されるよね、と思ってたんだけど、どうも違うみたいなので、わかりやすいようにサンプルを作ってみた。
http://la.ma.la/misc/sleep/

ウェイト入れてレスポンスを返すCGIを書いてロード。0,1,2,3って順番で表示されるはずだ、と思って実験してみたらSafariでしかうまくいかなかった。

色々試してみたけど、

- Safari: 並列リクエストされてロードされた順に実行。
- Firefox: 並列リクエストされるけど実行順は固定。
- IE: 並列リクエストされて実行順はほぼ固定?応答が遅いと後回しにされてるような気がする。
- Opera: 並列リクエストされない。

こんな具合になってる様子。

2006-10-10

Googleに入力補完機能

http://www.google.com/webhp?hl=en&complete=1

時期Googleに備わるであろう単語補完&件数表示機能。
まるで魔法のようだが、バックグラウンドでXMLHTTP通信をしている。
XMLHTTPはJavaScriptから特定のアドレスに任意のデータをPOSTしたり、同じドメイン内のXMLデータを取得したり出来る。フォームを送る警告とか通信中の表示がまったくでないわけで、XMLHTTPを使ったキーロガーなんてのを作ったことがあったが、さすがGoogle、これぞまっとうな使い道だ。

XMLHTTPはOperaで使えないのが問題だったんだが、最新版のOperaはXMLHTTPが使える模様。まあ、Googleがこんな機能つけたらOperaの方も対応せざるを得ないだろう。

似たようなのものに。
http://asp03.infosign.co.jp/~saida/mt/index.php?p=104
Blogのインクリメンタル検索、動的ロードしている。
http://blog.bitflux.ch/

文字が追加入力された場合にリクエストしているようだ。

----
Googleについてもう少し詳しく調べてみた。入力に変化があるたびに問い合わせている模様。一度補完された検索ワードについてはメモリ内の変数を使って、存在しなければ問い合わせ、負荷対策もちゃんと考えてある。補完候補と検索件数だけでいいので通信量はさほどでもない。

GoogleAPIを使って検索する前に検索件数がわかるような検索フォームを作ったら面白いんじゃないかと考えていたが、こういった補完候補並べるようなのはGoogle自らじゃないとできないな。太刀打ちできない。

----
日本語対応の可能性は?

現時点では完全未対応。IME変換前のデータなんかを逐一送られても困るだろうな。

まあ類似キーワード自動判別して動的ロードしてキーボード操作で選択、ぐらいなら結構楽に作れそうだ。作ってみようかな。

2006-10-04

Sledge::Plugin::DebugTemplateで開発効率が135倍になるデモ

フォームポストでテンプレートを送りつけると、それを使ってページを表示してくれるSledge::Plugin::DebugTemplateというのを作った。これによりJavaScriptやCSSのみならず、HTMLテンプレートすらローカルファイルと差し替えて開発ができるようになった。

ソース(nopasteが24時間で消えるの知らんかった)
http://rafb.net/paste/results/Ohpek040.html
http://la.ma.la/misc/src/DebugTemplate.pm

Winkで作ったデモ
http://la.ma.la/misc/demo/debug_template.htm

ブラウザを制御してページの内容をダミーのフォームに置き換えて現在のバッファの内容をペーストしてフォームポストしています。どちらかというとこっちの方が肝で、このデモは萌ディタとSleipnirで作っていますが、AutoHotkeyやらMozRepl使ったりすれば他のエディタとブラウザの組み合わせでもできる。

開発サーバー上で直接編集したり、あるいはSambaでマウントして編集したり、といったことも可能なわけですが、この方法だと編集するのは全くダミーのファイルでよいし、デバッグ中に他の人の作業に全く影響を与えないのがうれしい。開発サーバーが共用でも、個人で好き勝手に試すことができる。

当然のことながら本番環境で使えるようになっていると丸ごとセキュリティホールになるので注意が必要です。(イントラの場合でもURLがバレていると危険なので、何らかの認証処理を入れたいと思っている)

----
ついでに萌ディタ用のTemplateToolkitクラス。
http://la.ma.la/misc/src/tt.javascript.txt

- htmlの設定を継承して、ファイル保存時のフックのみを変更
- Sleipnir制御用のクラスを別ファイルに作ってあるんだけど、これ用に切り出し。

2006-10-03

非検索会議に行ってきました

詳細は以下の通り

----

飲み会でBBWatchのカイさんとバソキヤ2006の話で盛り上がった。

「売ってたら全部買うのに」
「俺が全部買うから仕入れてくれればいいのに」
「今度勇気を出してコンビニの店員に仕入れてくれるように頼んでみる」
「箱で買いたい」

とのことです。

万が一このブログを六本木ヒルズ森ビル4階のam/pm関係者の方がご覧になっていましたら
仕入れていただけると大変うれしく思います。

----
追記:2006-10-03

バソキヤ2006はすでに在庫が無いそうです。
結局12個ぐらいしか食えなかった。バソキヤ2007に期待。

2006-09-28

Firefoxの拡張MozLabの中に含まれるMozReplがヤバすぎる件について

MozLabという拡張を昨日知ったのですが
http://dev.hyperstruct.net/trac/mozlab

この中に含まれているMozReplというのがヤバい。Firefoxにtelnet接続できるようになる。

とりあえずRubyで書いた簡単なサンプル、今見ているページをリロードするだけ。
require 'net/telnet'
telnet = Net::Telnet.new({
    "Host" => "localhost",
    "Port" => 4242
}){|c| print c}
telnet.puts("content.location.reload(true)")
telnet.close

ひたすら自分が見ているURLとページタイトルを記録する系とか簡単に作れそう。
今見ているページのURLとタイトルを取得するサンプル。
require 'net/telnet'
prompt = /^repl> /
telnet = Net::Telnet.new({
    "Host" => "localhost",
    "Port" => 4242,
    "Prompt" => prompt,
})

telnet.waitfor(prompt)
telnet.cmd("content.location.href") do |c|
  print c.gsub(prompt,"")
end
telnet.cmd("content.document.title") do |c|
  print c.gsub(prompt,"")
end
telnet.close

ただし一秒おきとかに実行してたらそれなりにFirefoxの側に負荷がかかるようなので注意。
まだ詳しく調べてはいないんだけど、原理的にはFirefoxの全操作をtelnetから叩くことができそう。

about:configからextensions.mozlab.mozrepl.autoStartをtrueにすればFirefoxの起動時に自動でtelnet接続できる状態になる。extensions.mozlab.mozrepl.portでポートの変更も可能。localhost以外からの接続は受け付けないようなので、起動しただけでクラックされるようなことはないだろう。もちろん自己責任でFirefoxをぶっ壊すようなコードを実行することもできる、と思う。

ポートフォワード仕込んでMacBookのFirefoxを遠隔操作してみたり。必須の拡張がまた増えた。

2006-09-22

SafariのAjaxの文字化けをクライアント側だけで対応するバッドノウハウ

SafariでXMLHttpRequestのresponseTextが文字化けするという話。
http://blog.33rpm.jp/garbled-on-safari.html

最近のバージョンだとcontent-typeがちゃんとしてれば化けなかったような記憶があるけど、まあともかくとして、Safariで文字化けするのはJavaScript側だけで対処することができたりする。
http://kawa.at.webry.info/200511/article_9.html

これ読んでほんとかよ、って感じだったんだけど案外役に立った。livedoor Readerで使われてたり。
実際に使われてるコードはこんなの。
if(browser.isKHTML){
    ajax.filter.add(function(t){
        var esc = escape(t);
         return(esc.indexOf("%u") < 0 && esc.indexOf("%") > -1) ? decodeURIComponent(esc) : t
    })
}

ブラウザがSafariの時だけresponseTextにフィルターを適用するようになっている。
直し方わかってんなら直せよ、ってことで、このブログのナビゲーションのやつも直してみた。
var ajax_filter = function(t){return t};
if(navigator.appVersion.indexOf( "KHTML" ) > -1){
    ajax_filter = function(t){
        var esc = escape(t);
        return(esc.indexOf("%u") < 0 && esc.indexOf("%") > -1) ? decodeURIComponent(esc) : t
    }
}

こんなん書いてresponseText処理する前にはさんだ。

ブラウザ依存の処理を書くときは関数内で細かく処理を振り分けたりしないで、そもそも関数自体全く別のものに切り替えてしまう、ってのが好き。そうすると他のブラウザで余計な分岐処理を通らずに済む。この場合だと振り分け部分のコストは全然たいしたこと無いけど、DOM参照したりすると大したことあったりする。

ってのがここら辺に書いてあったりする。
http://dean.edwards.name/weblog/2005/12/js-tip1/

2006-09-09

2006-09-06

JavaScriptでPythonのsetみたいなの

http://la.ma.la/misc/js/set/

Pythonのset型をjsに移植してみた。setってのは要素が重複しないリスト。
http://www.python.jp/doc/release/lib/types-set.html

配列として扱いたいケースが多いように思うのでArrayを拡張してSetのメソッドを加える感じで作った。制約付きの配列、みたいな感覚で扱えるように。addやupdateを使わずにpushを使って要素を追加すると重複してしまう(pushも上書きすればいいけど、直接値を代入されたらどうせ防げない)。なので厳格ではない。格納できるのはstringとnumberとboolean型のみ。でもエラーが出るわけでもない。そこら辺いい加減なので作り直すかも。

用途としてはAさんとBさんとCさんがブックマークしてるURLとか、にも関わらずあなたがブックマークしてないURLとか、そういうのを計算するのを楽にしたいなーと思って丁度Pythonの本読んでたら具合が良さそうなので書いてみた感じ。

OperaでJSONPを非同期リクエストする

JSONP が Opera だと非同期処理できない
http://d.hatena.ne.jp/secondlife/20060906/1157515075

に書かれているとおりOperaだとscript要素を足した瞬間にJavaScriptの実行が止まって、ロード完了まで後続のスクリプトが実行されなくなります。

サンプル
http://la.ma.la/misc/js/opera_jsonp_test.html

そこで、リクエストの度にダミーのIFRAMEを作って、そのIFRAME内のcontentWindowで実行するという方法を試してみました。
IFRAME内でJSONをロードするサンプル
http://la.ma.la/misc/js/iframe_jsonp_request.html

なんかIEで動かないっぽいけど気にしない。相変わらずタイマーは停止しますが、appendChildした後のscriptも実行されます。ただIFRAMEを生成する分遅くなっていまいちな感じです。で、一見並列でリクエストされているように見えるのですが、コネクションを見張ってみたら、最初のscriptがロード完了するまで次のscriptを読まない、リクエストすらしていないようでした。

そして色々考えた結果、script要素を追加するタイミングをsetTimeoutでずらしてやればいいんじゃないかと言うことでやってみたのがこれ。
http://la.ma.la/misc/js/jsonp_request_with_delay.html

ディレイなしだとスクリプト追加直後に書いたコードが、外部スクリプトのロード後に実行されてうまく動かないけれど、ディレイを入れるとうまく動く。他のブラウザではどちらでも問題は無し。

非同期と言えるかどうかは微妙だけど、読み込み完了まで何も実行されない、という状況はある程度回避できそう。

まとめると
- Operaではタイマーが止まるのでタイムアウトを処理するのはなんか無理っぽい
- IFRAME使ってもscriptは順次読み込まれて、並列にリクエストされることはない
- script挿入のタイミングをずらしてやればコネクション数の管理なんかはできる

これぐらいか。

IFRAMEの中で処理する方法は、IFRAME内で実行されて名前空間が分かれるのでコールバック関数名を共通にできる(ユニークなIDをつけなくてもいいので、ブラウザのキャッシュが効きやすい)っていうメリットがあるんだけど、その他もろもろのデメリットを考えると大したメリットでもないかなーという具合。

2006-09-01

history.backが成功したかどうかを判別する

今も昔も大変よく使われている古典的JavaScriptの一つjavascript:history.back()なんですが、実際に戻る操作が成功したのかどうかを判別することができません。タブブラウザなんかを使っててミドルクリックで新規タブで開いてたりすると、history.backをクリックしても無反応で何も起こらない、なんてことよくありませんか?

そんなわけなのでhistory.backを実行後にページ移動が発生しているかどうかを監視して、戻るに失敗したときに特定の処理を発生させることができるような関数を作ってみました。

function try_back(errback){
    var bs = false;
    Event.observe(window,"unload",function(){bs=true});
    Event.observe(window,"beforeunload",function(){bs=true});
    history.back();
    switch(typeof errback){
        case "function": setTimeout(function(){if(!bs) errback()},100);break;
        case "string"  : setTimeout(function(){if(!bs) location.href=errback},100);break;
    }
    return bs;
}
// 使い方
// 戻るに失敗したときの処理
try_back(function(){window.status="戻る失敗"});
// もしくは戻るに失敗したときにジャンプするURL
try_back("http://example.com");


Event.observeは使ってるライブラリに合わせて適当に書き換えてください。
IEやFirefoxだとhistory.backを実行した後、即座にwindow.onbeforeunloadが実行されるみたいです。Operaはonbeforeunloadが実装されてないのでunloadでも使えるようにしてあります。

window.onbeforeunloadはページから立ち去ってよろしいですか?とメッセージを出すのに使ったりするイベントハンドラ。Google Spreadsheetsなんかで使われてますね。

2006-08-30

実践JavaScriptで配列をシャッフルする方法リファクタリング

JavaScriptで配列をシャッフルする話を見て、そういえばArray#shuffleは以前書いた記憶があるなーと思って調べてみたらコピペだった。
http://www.fumiononaka.com/TechNotes/Flash/FN0212002.html

Fisher-Yatesというアルゴリズムだそうです。
Array.prototype.shuffle = function() {
    var i = this.length;
    while(i){
        var j = Math.floor(Math.random()*i);
        var t = this[--i];
        this[i] = this[j];
        this[j] = t;
    }
    return this;
}
a = [1,2,3,4,5];
a.shuffle() // 3,1,5,2,4
a // 3,1,5,2,4

ごく普通に実装するとランダムに一件ずつ取り出すのがわかりやすいんじゃないかと思う。Rubyの本とか見ると大体そんな感じで書いてある気がする。JavaScriptには破壊的sliceがないので代わりにspliceを使えば良い。
Array.prototype.shuffle = function(){
    var len = this.length;
    var ary = this.concat();
    var res = [];
    while(len) res.push(ary.splice(Math.floor(Math.random()*len--),1));
    return res
}
a = [1,2,3,4,5];
a.shuffle() // 3,1,5,2,4
a // 1,2,3,4,5

元の配列には変更を加えず、ランダムに並べ替えられた新しい配列を返す。要素数の増減があるから遅くなるかも。ベンチ取ってないけど。

配列に独自のプロパティを持たせてあったり、toStringをインスタンス固有の関数に置き換えてある配列オブジェクトがあったとして、元のオブジェクトのまま中身をシャッフルしたい、という場合には、中身を直接入れ替えるのが良いでしょう。(最初に書いたやり方のように)

lazyな処理をしたい場合はランダムピック方式の方がやりやすい。巨大な配列をシャッフルしたい場合は、ランダムな順番で要素を返すイテレータを作るのがよいだろう。
Array.prototype.random_iter = function(){
    var len = this.length;
    var ary = this.concat();
    return {
        has_next: function(){
            return len ? true : false
        },
        next: function(){
            if(!len) return null;
            var i = Math.floor(Math.random() * len--);
            return ary.splice(i,1)
        }
    }
}

これなら一万件ぐらいの配列からでも高速に一件ずつ取り出すことができる。

あと配列をランダムに並べ替えるのにsortメソッドを使ってコンペア関数にランダムな値を返すようにする、という方式は片寄る、って話をしてて、なんで片寄るのかを少しわかりやすくしてみた。
http://la.ma.la/misc/js/randomize.html

端っこの要素があまり比較されないで片寄る様子がわかると思います。


なんか大した話でもないのに無理矢理発展させた感が否めない。