2008-05-30

JavaScriptのprivateメソッド

Operaなどでは動きませんがJavaScriptでprivateメソッド的なものを実現してみようと思います。方法は簡単でレキシカルスコープとcallerプロパティを使います。
var a = function(){
fn();
};
var b = function(){
fn();
};
var fn = (function(accepts){
accepts = accepts || [];
return function(){
if(accepts.indexOf(arguments.callee.caller) === -1){
alert("NG");
}else{
alert("OK");
}
};
})([a]);
a();
b();

上記のコードは三つの関数を定義しています。aとbはfnを呼び出す関数で、fnは呼び出される関数です。fn関数は無名関数を呼び出すことで定義します。この時、呼び出しを許可する関数を渡すことで公開範囲を指定することができます。このコードではa関数からの呼び出しのみを許可しています。コードを実行すると「OK」と表示された後に「NG」と表示されます。OKやNGと表示している部分を書き換えて、メソッド化してあげればprivateメソッドみたいなものになると思います。

クロスブラウザが実現できないことや隠蔽したいケースは稀なことから、使い道はないと思いますが、callerプロパティのユニークな使い方ではないでしょうか。

2008-05-28

JavaScriptのプリミティブ型と参照型

ずっとマニアックなネタが続いたいたので今回は基本的なJavaScriptの型のお話。JavaScriptにはプリミティブ型と参照型があるのはご存知でしょうか。例えば次の二つの文字列は同じ"aiueo"という文字列ですが、プリミティブ型と参照型の違いがあります。
var p = "aiueo";   // プリミティブ型
var r = new String("aiueo"); // 参照型

通常、文字列を定義する場合はStringコンストラクタは使わずに直接記述すると思います。これはプリミティブ型でtypeof演算子で"string"と判別されます。またStringコンストラクタを使って定義すると参照型となり、typeof演算子で"object"と判別されます。
両者は同じようにプロパティへアクセスすることができ、普通に使う分には違いを感じないと思います。二つには違いはどこにあるのでしょうか。文字列を例に違いの一部を表にしてみました。

 プリミティブ型参照型
typeof演算子(typeof(x))"string""object"
instanceof演算子(x instanceof String)falsetrue
プロパティの定義不可
同じ値を===演算子で比較truefalse

基本的なところで大きく違うことがわかると思います。この中で余り知られていないのはプロパティの定義ではないでしょうか。次のコードを実行すると、文字列にくっつけたプロパティが参照型の値では取得できますが、プリミティブ型ではundefinedとなります。
var p = "aiueo";
var r = new String("aiueo");
p.id = 100;
r.id = 200;

alert(p); // undefined
alert(r); // 200
alert(p.hasOwnProperty("id")); // false
alert(r.hasOwnProperty("id")); // true

JavaScriptでコーディングする際はこの違いを意識して書くとミスも減ると思います。最後にプリミティブ型と参照型を区別なく判定する小技と一つ。型判定はtypeof演算子やinstanceof演算子を使うのではなく、constructorプロパティで行うとプリミティブ型と参照型を区別なく判定できます。例を下記します。
var p = "aiueo";
var r = new String("aiueo");
alert(p.constructor === String); // true
alert(r.constructor === String); // true

2008-05-27

いにしえの window.undefined ... jQuery の場合

JavaScriptにおける未定義値の判別方法
jQueryのundefined変数による判別は謎です。undefinedはECMA-262 3rdやJavaScript 1.3にGlobalオブジェクトの値プロパティとして定義されていました。もっとちゃんと調べないと。反省)。jQueryはこの判別方法を使っているので知らずに書き換えると予期しない動作をします…。
aquilegia さんの投稿の中で、jQuery の window.undefined の実装に対して疑問があった(過去形)ようなので、その経緯を調べてみました。

その経緯は jQuery General Discussion の中にそれらしき議論がありました。どうやら、window.undefined というグローバルオブジェクトは、jQuery 1.0 の開発中に導入したものらしいです。

もともとは、undefined と判定すべき箇所が、null 判定していたことに起因するようです。
foo === null は foo === undefined の誤りでは?
ですが、undefined は IE5.0 とそれ以前では使えない ので、次のように表現し直そうとしたみたいです。
typeof foo === 'undefined' 
ですが、この表現を好まないとして、次のように window.undefined を用意したようです。
window.undefined = window.undefined;
これにより、IE 5.0 とそれ以前を含む古いブラウザでも undefined が使えるようになって、かつ ECMA-262 3rd の undefined グローバルオブジェクトの要件も満たすということです。

次のようにしても、同じ効果があるとの説明もありました。スコープの影響を受けるので、汎用ライブラリには向かないかもしれませんね。
var undefined;
で、aquilegia さんが提案するとおり、undefined の判定は void 演算子を使うのが確実そうなんですが、IE 3 まで遡ると void 演算子がない ようですね。

だとすると、jQuery の window.undefined という実装は、undefined の書き換えの不都合は抱えますが、かなり広範囲でブラウザの互換性を実現する手段となっているということになりますね。

ちょっと専門外の話題に言及したので、知識が曖昧な部分があったかもしれません。誤りがあれば aquilegia さんが訂正してくれるだろうと期待してます。

2008-05-25

jQuery Detect そのページが jQuery を使っているか検出してくれる Firefox Addon

次の 2007 年の調査からも分かるように、jQuery のシェアが地道な感じでしてきています。わたしも意識してチェックするようにしていますが、国内外も含めて jQuery を使っているサイトやサービスは明らかに増えているという実感があります。

Prototype、jQuery、Mootools、YUI、Dojoが人気、Ajaxian 調査
2007 Ajax Tools Usage SurveyはAjaxian.comのユーザを対象に実施された調査で、Ajaxツールの採用状況を調べるもの。同調査結果によるとPrototype が34.10%、jQueryが29.30%、Extが22.50%、Script.aculo.usが22.30%、Mootoolsが14.30%、 YUIが13.00%、Dojoが11.90%となっている。
そうはいうけどさ。実際にそんなに使われているの。統計の方法に偏りがあって、その内容なんて信用できないよ。というひとは、次の Firefox Addon をしばらく使ってみてはどうでしょうか。

jQuery Detect



この Addon は、そのページが jQuery を使っているか自動的に検出し、jQuery の有無とそのバージョンを、アイコンで教えてくれるものです。この Addon があれば、HTML ページのソースを解析したり、Firebug を開いたりすることなく、jQuery を使っているかどうか調べることができますね。

jQuery のディスカッションでも指摘がありますが、アイコンとそのバージョンは、最後にロードしたページの状態を表します。ので、タブを選択したとしても、そのタブのページの結果を表さないので、誤解しないように気をつけてください。

2008-05-24

JavaScriptにおける未定義値の判別方法

前々からJavaScriptの書籍やライブラリ、他人のブログを見て気になっている事の一つに未定義値の判別があります。変数の中身が未定義値かどうかを確認する方法は下記したようにいくつかあり、一体どれが最良なのかを考えてみました
v === undefined;
typeof v === "undefined";
v === void 0;

結果を先に述べると最後の判別方法が最良だという結論に至りました。

一つ目の方法はundefined定数を使った判別です。ActionScriptのundefined定数から来ているようで、ECMA-262 3rdではundefinedは予約語でも定数でもないので(※)、この方法はグローバルオブジェクトを汚染してしまっています。ローカル変数に定義して判別に使うのは問題ありません(2008/5/25 訂正:undefinedはECMA-262 3rdやJavaScript 1.3にGlobalオブジェクトの値プロパティとして定義されていました。もっとちゃんと調べないと。反省)。jQueryはこの判別方法を使っているので知らずに書き換えると予期しない動作をします…。
(※ES 4でも予約語でない模様

二つ目は昔からあり、標準仕様で定められたtypeof演算子を使った判別で一つ目と同じく可読性もあり、良い方法だと思います。しかし、パフォーマンスと記述量では三つ中最低です。

三つ目も昔からあり、同じく標準仕様で定められたvoid演算子を使った判別でvoid演算子を知らない人を対象にすると可読性は他の二つには劣りますが、パフォーマンスと記述量のバランスでは最良です。

以下に三つの方法を各ブラウザで1000万回実行したコストを纏めてみました。単位はmsです。
判別方法IEFirefoxOperaSafari
v === undefined (ローカル変数)2030734938703
v === undefined (グローバル変数)297023129681625
typeof v === "undefined"81307359694000
v === void 02030734954672

結果は一つ目と最後の判別が総合的に最も良い結果を出しました。
他の二つの結果が悪い原因としては、二つ目はスコープを跨いだ値の探索が裏で実行されている為で、三つ目は文字列比較にコストがかかっているのだと思われます。

上述したことから、判別方法はローカル変数かvoid演算子のどちらかとなりますが、ローカル変数はその都度定義するのは煩わしいので、総合的にはvoid演算子が最良だといえます。
またvoid演算子はJavaScript不遇の時代のIE4やNN4の頃からありますし、昔の書籍にも書かれています(※)。ですので互換性も問題ないと思います。
(※HTMLのA要素のhref属性に書かれた「javascript:void(0)」などは有名なのではないでしょうか)

---- ここから蛇足&愚痴です。無視してください… --------------------------
jQueryのundefined変数による判別は謎です。作者はActionScriptからのユーザなのかな?ActionScriptの構文はJavaScriptに似ていますが、全くの別ものです。悲しい事にJavaScriptのコードはActionScriptのインタプリタであっさりエラーになります。

また新しいJavaScriptの仕様であるECMAScript 4th EditionはAdobeによる圧力なのかActionScriptに強く影響を受けていて、昔のコードが動作しなくなる事もあるようです。互換性は大切にしてほしかった。また仕様も複雑になってます。3thの仕様はシンプルでとても好きです。不足している機能はライブラリを利用すれば良いですし、個人的に今で十分なのでFirefox以外の環境に実装されないことを草葉の陰から願ってます。大規模開発なんて個人はしないのだから、ECMAとブラウザ各社で調整して別の言語を搭載すればよいのに・・・。



<追記>(2008/5/25)
jQuery以外のライブラリはどうなのか気になったのでprototype.jsとExtのソースを見たところ、prototype.jsは殆どがundefined変数で判別しており、Extはtypeof演算子とundefined変数がごっちゃでした。興味深いことにvoid演算子による判別はどちらも全くしていません。

手元にあった下記のJSライブラリを'void(0)'と'void 0'でgrepしたのですが、'void(0)'でヒットしたのは殆どがHTMLファイルで、アンカーのhref属性に設定されている"javascript:void(0)"でした。JavaScriptでヒットしたのもありましたが、それもhref属性に設定するコードの末尾に戻り値を返す為に使っているぐらいです。
'void 0'は国産のConcurrent.Threadで見つかったぐらいです(こちらはちゃんと判別に使っていました)。
--------------------------
Ajax IME
Ajile
AutoSuggest
Behaviour
Concurrent.Thread
DWR
Dimensions
Dojo
env
Ext
LoJAX
Log4js
MochiKit
Narrative JavaScript
OpenAjaxHub
OpenMocha
Rico
Spry
SyntaxHighlighter
YUI
dowry
jQuery
json
moo tools
prototype
ruby.js
script.aculo.us
--------------------------

void演算子を判別に使うのはあまり一般的ではないのかもしれません。書籍などの影響でvoid演算子はアンカーのhref属性だけで使いものという変な慣習でもあるのでしょうか。私はvoid演算子派なので何か理由があるのではないかと不安になってしまいます。

FirefoxのRegExpコンストラクタ

JavaScriptで正規表現を利用する際にRegExpオブジェクトを利用すると思います。この時パターンを指定しない場合のFirefoxの振る舞いが標準仕様に従っていません。Firefox2.0で確認したのですが次のようになります。
new RegExp().toString()  // 「/?:/」
new RegExp(void 0).toString() // 「/undefined/」

IEやOpera、Safariなどは次のようになります。
new RegExp().toString()  // 「//」
new RegExp(void 0).toString() // 「//」

どちらが正しいかというとECMA-262 3rdの仕様に従っているのは後者になります。IE4やNN4は引数に未定義値を指定すると「/undefined/」となり、Firefoxと同じになります。ですので後方互換が理由と思ったのですが、引数が未指定の場合はIE4とNN4共に「//」となり後者と一致します。ですので引数が未指定の場合の理由がよくわかりません。例外的な振る舞いなので重要視していないのかな?
標準大好きのMozillaがECMAの仕様に従っていないのは違和感を感じますし、他にもありそうで不安になります。

<追記>
RhinoはFirefoxと同じ結果になりました。RhinoはNetscape社からMozilla Foundationに譲渡された経緯もありますし、SpiderMonkeyと同じ振る舞いをするのかな。でも、Rhinoはオブジェクトのプロパティに順序性がないなど、SpiderMonkeyと同じではないので違う物として扱わないといけないのがなんとも面倒。

2008-05-21

jQuery 1.2.5 が即リリースされました。1.2.4 はビルドに不都合があったようです。

jQuery 1.2.4 は ビルドに不都合があった ようで、すぐに 1.2.5 がリリースされました。

次のとおり、リリースノートにも告知がありましたので、jQuery 1.2.4 を導入しているときは、1.2.3 に戻すか、1.2.5 にアップデートするか、いずれかの対策が必要そうです。

jQuery 1.2.4 Release Notes
Note 1.2.4 was a bad build, explanation here http://www.nabble.com/1.2.4-missing-patches--td17354452s27240.html Use 1.2.5 or later instead
数日間のことですので、対象者は少ないでしょうが、お伝えしておきます。

Safari と Opera でキーボードを使ってアンカーを移動する方法

ページ上のアンカーテキストにフォーカスが移ったとき、その a 要素の onfocus イベントをハンドリングして、その中からさらにページの要素を操作するといった JavaScript コードを書いています。

この JavaScript コードを Safari 3.1 と Opera 9.2 でテストしようとしたところ、TAB キーだとアンカーを移動できないことを知りました。これだと、a 要素の onfocus イベントのテストがしにくいです。

そもそも Opera の TAB キーは、フォームの要素を移動するためのものだそうです。その変わりに Opera は、別のショートカットキーを用意しています。

a キーでアンカーを進み、q キーでアンカーを戻ります。Ctrl + ↑↓ キーも同じ動きをします。また、Shift + ↑↓←→ キーで、アンカーとフォームの要素を上下左右に移動できます。

Safari の TAB キーも、フォームの要素を移動するためのものだそうです。が、次のように、"タブキーを押したときに WEB ページ上の各項目を強調表示" オプションを ON に変更すれば、その動きを変えることができます。



オプションを ON にすると、TAB キーを使ってアンカーとフォームの要素を移動できるようになります。もちろん Shift + TAB キーで、その逆に移動できます。

2008-05-13

Dreamweaver拡張機能のJavaScriptインタプリタは独特

Dreamweaver 拡張機能のJavaScriptで気づいた点をメモしておきます。

 1. 関数内でFunctionコンストラクタによって定義した関数でグローバルオブジェクトのプロパティを参照できない
 2. NN4のdocument.layersプロパティがある
 3. Array.prototype.sortに0を返すソート関数を指定して並び替えた結果が他のJavaScript実行環境と違う

一つ目はグローバルオブジェクトに定義された関数を実行する次のコードを実行すると、何故か未定義エラーとなります。他のJavaScript実行環境ではグローバルオブジェクトのプロパティを返しますし、関数をインライン定義すると期待した結果が返るのでバグなのかな。
function objectTag() {
try{
var f = new Function("return objectTag;");
f();
}catch(e){
alert(e);
}
}

二つ目はDreamweaverにはNetscape Navigator 4にあったdocument.layersプロパティがあり、同じように使うことができます。
//「<div id="aiueo" style="position: absolute;"></div>」を取得する
var layer = document.layers.aiueo;

最後は以前記事にした0を返すソート関数で並び替えるとJavaScript実行環境毎に結果が違うという話で、Dreamweaver拡張機能は他のどの実行環境とも違う結果を返しました。document.layersプロパティがあることから、NN4と互換性を持たせているかとも思いましたがNN4の並び方も独特です。この結果で実行環境の判別ができそうで笑えます。
["",void 0,null,NaN,0]    //並び替え前

[null,void 0,NaN,"",0] //Dreamweaver
[void 0,null,NaN,0,""] //NN4
[NaN,null,"",0,void 0] //Firefox
["",null,NaN,0,void 0] //IE, Opera, Rhino
[null,NaN,0,"",void 0] //Safari, AIR

上記三点から想像するに、Dreamweaver拡張機能(またはAdobe拡張機能)は独自のJavaScriptインタプリタを持っているようです。同じAdobe製であるにも関わらずAIRとDreamweaver拡張機能で振る舞いが違うのも少し残念に感じます。純Adobe製と元Macromedia製の違いからくるのかな?

2008-05-09

AIRのセキュリティ関連エラー

最近、Adobe AIRで動作するJavaScriptを書くことがありまして、その時に得た知識を投稿しておきます。AIRでJavaScriptを書いた経験のある方は常識と思いますが、AIRはセキュリティによりJavaScriptの一部機能を制限していて使用するとエラーとなります(※)。今回私が対象になったのはその中のeval関数とFunctionコンストラクタで、AIRではこの二つを使用する事ができません。どうもコードの動的生成を制限しているようです。Functionコンストラクタは関数をインライン定義(function(){...})することで大半を置換できるので問題ないですが、eval関数は代替手段がない場合が多いので困ります。

※その他の制限については下記URLに詳細が載っています。
http://livedocs.adobe.com/air/1/devappshtml/help.html?content=ProgrammingHTMLAndJavaScript_04.html#1034123

この制限への対処方法としてSandboxBridgeを使用する方法があるようです。この制限はセキュリティモデルと呼ばれており、FlashとHTMLでそれぞれ二つあります。

Flash : アプリケーションサンドボックス / 非アプリケーションサンドボックス
HTML : アプリケーションサンドボックス / クラシックサンドボックス

HTMLを例に話すとアプリケーションサンドボックスはセキュリティの厳しく、コードの動的生成などができないがAIR APIにアクセスできる。クラシックサンドボックスはセキュリティが緩く、ブラウザのようにコードの動的生成などができるがAIR APIにアクセスできないです。SandboxBridgeはクラシックサンドボックスから一部AIR APIを呼び出すものだそうで、実際は安全なアプリケーションサンドボックスで実行して、どうしてもコードの動的生成などをしたい場合はクラシックサンドボックスで実行する形にすれば良いようです。
より分かりやすい説明がウェブ上にありますので、少しリンクを貼り付けておきます。

http://blog.r-studio.jp/?itemid=62
http://weblogs.macromedia.com/akamijo/archives/2007/10/adobe_air_2_1.html

デフォルトはどちらも安全なアプリケーションサンドボックスなので、既存のライブラリをそのまま使えないことが多いですが、jQueryなど有名ライブラリが徐々にAIRへ対応しているそうなので問題はないと楽観視しています。

NN4とFirefox拡張機能のFunction#constructorプロパティ

少しマニアックなネタですが、JavaScriptでFunctionオブジェクトを生成する方法は大きく二種類あります。一つはFunctionコンストラクタを使う方法で、もう一つはインラインで関数を定義する方法です。
//Functionコンストラクタ
Function("alert('aiueo');");

//インライン定義
function(){
alert("aiueo");
}

この二つの方法でそれぞれ生成した関数のconstructorプロパティには、殆どのJavaScript実行環境でFunctionが設定されるのですが、NN4とFirefox拡張機能では違います。

NN4は関数内でインライン定義をするとconstructorプロパティにClosureが設定されます。またFirefox拡張機能(※)のインライン定義はFunctionなのですが、Functionとは違うFunctionになります。文字列表現は同じなのですが===演算子で一致しません(※)。

NN4もFirefox拡張機能もマイナーな環境ですので通常心配する必要はありませんが、もし不幸にも関わることがありましたら、この事を頭の隅に置いておくとはまらずに済む事があるかもしれません。

※overlay要素にscript要素を追加して「alert((function(){}).constructor === Function);」を実行すると簡単に確認できます。

2008-05-07

Twitter.statuses.join(BLOGRANGER::TG)

オートタギングAPI を使ってブログのエントリを勝手に分類してみる
BLOGRANGER TG のブログパーツで、このブログのミニ地図が表示できなかったので、手始めに BLOGRANGER API の オートタギング API というのを使って、指定したブログのエントリからタグを自動推定して、そのタグごとにエントリを分類して表示するアプリケーションを作ってみました。
さらに続けて、BLOGRANGER API の オートタギング API を使った別のアプリケーションを作ってみました。次のリンクからどうぞ。

Twitter.statuses.join(BLOGRANGER::TG)

今回は Twitter API を使いました。Twitter API で public_timeline を拾い続けて、そのユーザのアイコンを並べて表示していきます。このとき同時に、オートタギング API を使って、"つぶやき" のテキストからタグを自動推定します。そして、同じタグが推定された "つぶやき" 同士をくっつけて、アイコンをグループ化します。

↓こんな感じです。黒い枠で囲まれたアイコンが、同じタグが推定された "つぶやき" を表します。つまり、同じ話題について "つぶやいた" であろうユーザのアイコンが並んで表示されることになります。アイコンをクリックすると "つぶやき" の内容を表示しますので、気になるグループがあったら確認してみるとよいでしょう。



同じタグが推定されて "つぶやき" をくっつけるとき、推定されたタグをアニメーションでちょっと大げさに表示します。↓こんな感じです。"リアル" というタグで "つぶやき" がくっついたことを表しています。



↓は "絢香" というタグで "つぶやき" がくっついたことを表しています。



"つぶやき" が少ないうちは、なかなかくっつきませんが、"つぶやき" が増えてくると、タグの推定が連鎖してきて、アニメーションが慌しくなってきます。しばらくぼ~っと眺めてみてください。

Twitter API で public_timeline を拾う頻度を高くしたいのですが、オートタギング API は、それなりの計算量がありそう?なので、その負荷を考慮しています。オートタギング API を使って "つぶやき" からタグを自動推定するたびに、1秒のウェイトを入れています。そのため、拾いきれない "つぶやき" がかなり出てくると思います。

Rhinoは||演算子による代入の仕様がブラウザと少々異なる

プログラムを書くと変数に値を代入する時に値が有効なものでない場合はデフォルト値をセットすることはよくあると思います。JavaScriptではこういうケースでよく使われるテクニックとして論理和演算子(||)を使った代入があります。
例えば次の場合、関数の引数が未指定(未定義値)の場合は"default"文字列を設定します。
function aiueo(param)
var param = param || "default";
return "aiueo_" + param;
}

||演算子は値がtrueと評価できる場合は値を返し、そうでない場合は右側の式の結果を返します。条件を満たせばif文や条件演算子(?:)を使うよりも簡素に記述できるので重宝します。

ここから本題なのですが、Rhinoでは||演算子による代入の仕様がブラウザと少々異なります。例えば次のコードをブラウザで実行するとvalue変数にはnullが入るのですが、Rhinoでは未定義値(void 0)が入ります。
//全てfalseと評価される値を||演算子で繋げて、どれが代入されるか確認する
var value = NaN || false || "" || void 0 || 0 || null;

ブラウザは全てfalseと評価される値の場合は最後の値を返すのですが、Rhinoは値の型により優先度の高い値を返します。その為、ブラウザとRhino両方で動作するプログラムを書く際は留意する必要があります。参考までに優先度を下記しておきます。

void 0 - 高い
""
NaN
null
0
false - 低い

Array.prototype.lastIndexOfのドキュメント不備?

mozilla developer centorのArray.prototype.lastIndexOfページで互換性の為に示しているJavaScriptコード(「互換コード」と呼ぶ)はFirefox(SpiderMonkeyは未確認)と完全互換と書いてありますがそうではないようです。indexOfメソッドを第二引数のfromIndexにNaNなど数値ではない値を指定して呼び出すと、Firefoxは0に置き換えて検索した結果を返しますが、互換コードは配列末尾から検索した結果を返します。
var a = [1, "0", NaN, 1];
a.lastIndexOf(1, null)); //Firefox:0、互換:0
a.lastIndexOf(1, void 0)); //Firefox:0、互換:3
a.lastIndexOf(1, NaN)); //Firefox:0、互換:3

問題は互換コードは値が数値ではない場合に配列の末尾から検索しているところで、ここを0を設定して検索するようにするのと、fromIndex引数の指定有無を確認する処理を入れてあげれば結果が同じになります。
保障しませんが参考までに勝手に直したコードを下記しておきます。
if (!Array.prototype.lastIndexOf)
{
Array.prototype.lastIndexOf = function(elt /*, from*/)
{
var len = this.length;

var from = arguments.length < 2
?len - 1
:Number(arguments[1]);
if (isNaN(from))
{
from = 0;
}
else
{
from = (from < 0)
? Math.ceil(from)
: Math.floor(from);
if (from < 0)
from += len;
else if (from >= len)
from = len - 1;
}

for (; from > -1; from--)
{
if (from in this &&
this[from] === elt)
return from;
}
return -1;
};
}

実装とドキュメントに差異がある時は個人的に実装を正と考えるので、ドキュメントが誤っているのだと思っています。そのうち整理されればいいなぁ。

追記
蛇足ですがSafariのlastIndexOfメソッドは互換コードと同様の振る舞いをします。クロスブラウザ対応する場合は考慮する必要があります。

2008-05-05

オートタギングAPI を使ってブログのエントリを勝手に分類してみる

BLOGRANGER TG のミニ地図を貼り付けられるブログパーツ
百式さんのブログで紹介しているのを見て、さっそく試してみました。が、残念です。このブログは収集してもらえてないようです。BLOGRANGER TG API というのが提供されているので、このブログパーツと同じようなことはできるかもしれません。
少し過去の話題にさかのぼります。上記のとおり、BLOGRANGER TG のブログパーツで、このブログのミニ地図が表示できなかったので、手始めに BLOGRANGER API の オートタギング API というのを使って、指定したブログのエントリからタグを自動推定して、そのタグごとにエントリを分類して表示するアプリケーションを作ってみました。

↓こんな感じです。タグごとにボックスを表示し、そのボックスの中に関連するエントリをリスト表示します。iGoogle っぽくしてみました。


このアプリケーションの URL は、次のとおりです。q パラメータに RSS や Atom といったブログのフィードの URL を指定してください。
http://developmentor.lrlab.to/blogranger/categorize?q=(feed URL)
次のとおり、いくつか実例を用意しましたので、参考にしてください。このアプリケーションを使うには Google Gears が必要です。前述の URL にアクセスすると、Google Gears のインストールを促すページを表示しますので、そのページの手順にしたがって、Google Gears をインストールしてください。

Google Gears をインストールして、前述の URL にアクセスすると、Google Gears を使ってよいかの許可を求めるダイアログを表示します。そのとき許可してはじめて、このアプリケーションが使えるようになります。

パラメータで指定したブログのフィードの URL から Google AJAX Feed API を使って、新しいエントリの情報を 10件ほど取得します。続けて、そのエントリのタイトルとサマリからオートタギング API を使って、タグを自動推定します。そして、エントリの情報と自動推定したタグを Google Gears のローカルデータベースに格納します。

エントリの情報と自動推定したタグは、ローカルデータベースにどんどん蓄積していきます。新しいエントリがあるかどうかは、このアプリケーションにアクセスしたとき 1度だけチェックします。また、ページ右上の更新リンクをクリックすることでもチェックできます。

ローカルデータベースの内容が多くなってくると、ページの表示が遅く重くなってきます。そのときは、ページ右上の削除リンクをクリックして、ローカルデータベースを空にしてやり直しができます。

タグのボックスとそのエントリのリストは、ローカルデータベースの内容から表示しています。タグのリンクをクリックすると、そのタグを中心とした BLOGRANGER TG の地図を表示します。このタグは、エントリから自動推定したタグと、エントリにもともと付与されていたタグの両方を使っています。エントリのリンクをクリックすると、そのエントリを表示します。

ちょっとバグっぽい印象を持つかもしれませんが、パラメータに指定するブログのフィードの URL を変更しても、ローカルデータベースでは、情報源となったブログを区別しませんので、異なる複数のブログをまとめて分類できちゃったりします。例えば、404 Blog Not Found に続けて [N]ネタフル を表示すると、その両方のエントリをまとめて分類できるということです。

RegExp.prototype.testのインデックスバグ?

JSON変換の独自実装を作成したのでテストをしようと思い、他人のライブラリの結果とマッチングしてテストを行ったのですが、特定の文字がアンマッチしてしまい原因を調べたところ標題の事象を発見しました。

内容としては全ての文字で「true」と表示される次のコードを実行したところ、replaceメソッドは問題ないのですがtestメソッドがFirefoxとOpera、Rhinoでtrueとfalseを交互に出力してしまいました。
var r = "";
for(var i=0; i<256; i++){
var s = String.fromCharCode(i);
r += i.toString(16) + ":";
r += /[\x00-\xff]/g.test(s) + ":" ;
r += s.replace(/[\x00-\xff]/g,"aiueo").length + "\n";
}
alert(r);

また、直書きしているRegExpオブジェクトを変数に入れて実行したところ、FirefoxとOperaは全てtrueとなりましたが、今度はSafariがtrueとfalseを繰り返し、IEでは一つ目がtrue、二つ目以降はfalseと表示されてしまいました。

試行錯誤したところ、testメソッドを呼ぶRegExpオブジェクトをnew演算子でインスタンス化すると問題なく動作するようになりました。
var r = "";
for(var i=0; i<256; i++){
var s = String.fromCharCode(i);
r += i.toString(16) + ":";
r += new RegExp("[\\x00-\\xff]","g").test(s) + ":" ;
r += s.replace(/[\x00-\xff]/g,"aiueo").length + "\n";
}
alert(r);


原因ははっきりとわからないのですが、IEに限っては直書きすると内部で変なキャッシュが効いてしまいインデックスを共有化してしまうのではないかと思っています(IE4で同じようなバグがありますし)。他のブラウザについても似たようなものなのかなと思っています。
原因がわからないのは気持ち悪いですがtestメソッドをクロスブラウザで使う場合はその都度、new演算子でインスタンス化して呼び出す必要があるようです。ちなみにnew演算子で作成したものを変数にいれて使いまわしてもtrueとfalseが交互に表示されます。

Array.prototype.sortの並び方が違う

未定義値やnull、空文字などを含んだ配列をArray.prototype.sortに0を返すソート関数を指定して並び替えた場合、ブラウザ毎に結果が違います。
["",void 0,null,NaN,0].sort(function(){
return 0;
});

上記コードで配列を並び替えた場合、各ブラウザの結果は次のようになります。
Firefox   [NaN,null,"",0,void 0]
IE, Opera, Rhino ["",null,NaN,0,void 0]
Safari [null,NaN,0,"",void 0]

これを見る限り0を返した後に独自の判断で並び替えるようです。個人的にどれも正しい結果と思えません。結果をみてもいまいち仕様がわかりませんし、ソート関数なしの場合とも結果が違うので困ります。
対応としてはソート関数を指定する場合は、値の型も見て慎重に並び替える必要がありそうです。また、型を見るだけではまだダメで、SafariとRhinoは0とNaNの比較で0を返すと[0,NaN]としますが、他のブラウザは[NaN,0]としてしまいます。ですのでNumber型はisNaN関数でNaNかどうかを判別して順序付けをする必要があるようです。

蛇足ですがソート関数なしで並び替える場合、結果は全て同じになるのでここは問題ないです。
["",0,NaN,null,void 0]


追記<17:05>
ECMAScriptの仕様にソート関数の結果が0の場合、順序が必ずしも安定していない事が明記されていました。順序がどうなるかはJavaScript実装によるようです。従って、結果を一定にするには自分達でしっかりしたソート関数を作る必要があります。

2008-05-04

jQuery を 1.1.* から 1.2.* にしたら tablesorter pager が動かなくなった

Postal Search Ajax API (構築キットを含む) をアップデートしました
Postal Search Ajax API に同封している jQuery のバージョンを 1.1.2 から 1.2.3 に差し替えました。この jQuery 1.2.3 は Minified 圧縮版 を使っています。
バージョンの差し替えで不都合はないと考えていましたが、現在の位置をページ単位でなく行単位で表示する方法 という jQuery tablesorter プラグインのデモでエラーが発生して動作しなくなっていました。なお、この不都合は改善してあります。

参考のため、不都合の原因やその対応を紹介しておきます。

tablesorter pager プラグインの次のコードでエラーが発生していました。このコードは、テーブルの高さ(レコード数など)が変わっても、ページャの位置が固定になるよう調整するためのものです。
function fixPosition(table) {
var c = table.config;
if(!c.pagerPositionSet && c.positionFixed) {
var c = table.config, o = $(table);
if(o.offset) {
c.container.css({
top: o.offset().top + o.height() + 'px',
position: 'absolute'
});
}
c.pagerPositionSet = true;
}
}
前述のデモでは、ページャの位置が固定されないことを期待していました。というのは、jQuery 1.1.* では、jQuery.fn.offset メソッドが存在しないため、このコードのスタイルを変更する部分が動作することはありませんでした。なお、jQuery.fn.offset メソッドを使おうとするときは、dimensions プラグインを必要としていました。

が、jQuery 1.2.* から jQuery.fn.offset メソッドが標準で組み込まれ ました。ですので、今まで動作しなかったコードが動作するようになり、c.container.css でエラーが発生するようになりました。

この不都合を改善する前は、次のように jQuery.fn.tablesorterPager メソッドを使っていました。ここで、container は jQuery オブジェクトを要求していますが、セレクタを指定していたため、css メソッドが存在しないというエラーになっていました。
$('#r1')
.tablesorter({
widgets: ['zebra']
})
.tablesorterPager({
container: '#p1',
size: 3
});
ので、次のように container には jQuery オブジェクトを指定するように修正しました。また、このままだと、ページャの位置が固定になってしまうので、positionFixed を無効にしました。
$('#r1')
.tablesorter({
widgets: ['zebra']
})
.tablesorterPager({
container: $('#p1'),
size: 3,
positionFixed: false
});
今回の不都合は、jQuery のバージョンアップによる標準メソッドの変化と、tablesorter pager プラグインの誤用という複合的なものでした。

前者は、しかたがないですかね。jQuery の新しいバージョンがリリースされるごとに、リリースノート をチェックするしかない気がします。

後者は、jQuery オブジェクトを要求せず、セレクタをも受け入れるインタフェースにすべきと考えます。というのが jQuery の特徴ですし、その他のプラグインもそうしているケースが多いですね。

Amazon S3 bucket の参照は、ディレクトリ、サブホスト、CNAME から選べます

Amazon Simple Storage Service (Amazon S3) を使ってみようと試行中です。

Amazon S3 bucket は、いろいろな方法を使って参照できます。

1つ目は、ディレクトリに bucket 名を指定する方法です。↓こんな感じです。
http://s3.amazonaws.com/(bucket)
2つ目は、s3.amazonaws.com のサブホストに bucket 名を指定する方法です。↓こんな感じです。
http://(bucket).s3.amazonaws.com/
3つ目は、DNS の CNAME レコードを使って、s3.amazonaws.com 以外のホストと bucket を関連付け、ユーザ固有のドメインのホストから bucket を参照する方法です。↓こんな感じです。ユーザ固有のドメインが example.com であると仮定しています。
http://images.example.com/
このとき、DNS の CNAME レコードは、次のように登録します。
images.example.com CNAME images.example.com.s3.amazonaws.com
関連付ける(別名を付ける)ホスト名を bucket 名とする bucket を作成しておくことが条件です。Amazon S3 は、リクエストの HOST ヘッダを bucket 名に置き換えて bucket を参照し、レスポンスするという流れになると考えられます。いわゆるネームベースのバーチャルホストです。

実際に所有するドメインで試してみましたが、上記の CNAME レコードは期待どおり機能しました。s3.amazonaws.com というホスト名を見せたくないとき(どんなときだろう?)に使えますね。ただ、セキュアな経路(SSL)が使えないなど、1つ目や2つ目の方法とは性格の異なる制限が出てくることが予想できますね。

Amazon S3 の x-amz- ヘッダの正体は何なのか?

Amazon Simple Storage Service (Amazon S3) を使ってみようと試行中です。

Amazon S3 Firefox Organizer (S3 Fox) を使って bucket を操作していますが、そのレスポンスに x-amz- から始まる Amazon S3 固有のヘッダを含むことに気が付きました。これは何だろう?

そこで Developer Guide から探してみました。Developer Guide に x-amz- の説明があるというのは、某氏より教わりました。ありがとうございました。

x-amz- ヘッダは、システム向けとユーザ向けに分類 されるとのことです。

Amazon S3 REST API のレスポンスに、次のような x-amz- ヘッダが、ユーザの意向に関わらず、必ず含まれます。ユーザからみて、x-amz-id-2 と x-amz-request-id ヘッダに特別な用途や意味はなく、このヘッダとその値は、Amazon がトラブルシューティングなどの対応 に使うとのことです。
x-amz-id-2: XxYljM5HbdXpXXMspuaJMwHMIgN4jRne2CRNIkQxnyABHmbGQ8f45wYbmAOgEk2U
x-amz-request-id: 04E2FFCFB46A42BE
Amazon S3 REST API のレスポンスに x-amz-meta- ヘッダが含まれることがあります。この x-amz-meta- ヘッダは、オブジェクト(フォルダやファイル)を格納するときに付与した ユーザ固有のメタデータ を表しています。

S3 Fox を使うと、次のような x-amz-meta- ヘッダが自動的に付与されます。x-amz-meta-s3fox-filesize はファイルサイズ、x-amz-meta-s3fox-modifiedtime は更新日時を表し、S3 Fox は、このメターデータを使って、オブジェクトをリスト表示していると考えられます。
x-amz-meta-s3fox-filesize: 28637
x-amz-meta-s3fox-modifiedtime: 1191805825031
x-amz-meta- ヘッダやその値は、Amazon S3 の振る舞いに影響を与えない とのこと。つまり、ユーザが自由に使えるということですね。ただし、ヘッダのキーと値の総長が 2KB まで という制限があります。

また HTTP ヘッダですので、その値は ASCII として解釈される ことになるので、UTF-8 (3バイト)は指定しない方がいいのでしょうかね。ごめんなさい。Developer Guide から読み取れませんでした。

x-amz-missing-meta ヘッダという特別なヘッダもあります。x-amz-meta- ヘッダの値に、印刷不可な文字を含むとき、そのヘッダは無効として扱われます。その無効になったヘッダは、x-amz-missing-meta ヘッダを使って、そのヘッダ名を知らせるとのことです。ヘッダ名は大文字小文字の区別がなく、複数のヘッダが無効だったときは、ヘッダ名がカンマ区切りになります。

2008-05-02

Amazon S3 GET Bucket のパラメータとレスポンスの関係

Amazon Simple Storage Service (Amazon S3) を使ってみようと試行中です。

まず単純なところで、その振る舞いを把握しようと WEB ブラウザと REST API の GET Bucket を使って、フォルダやファイルをリストアップしてみた。この GET Bucket ですが、Developer Guide を読んでも、どう振る舞うのかイメージできないことが多々ありましたので、補足を意図して残しておきます。

prefix パラメータは、指定した文字列の前方一致で、ファイル(フォルダ)をフィルタするものです。↓こんな感じです。このとき、先頭に foo を含むパスがリストアップされます。
http://s3.amazonaws.com/(bucket)?prefix=foo
特定のフォルダで絞り込むときなどに使えそうです。

marker パラメータは、多数のファイル(フォルダ)があるとき、そのリストをページングするためのものです。↓こんな感じです。このとき、foo.txt 以降のファイル(フォルダ)がリストアップされます。
http://s3.amazonaws.com/(bucket)?marker=foo.txt
marker パラメータの値には、リストの ListBucketResult/Contents/Key 要素の値を指定します。つまり、リストの最後の同要素を指定すればよいということです。

リストの並び順は、パスの辞書順のようです。リストが継続しているかどうかは、レスポンスの ListBucketResult/IsTruncated 要素で判断できます。

max-keys パラメータは、リストに含めるファイル(フォルダ)の上限件数です。↓こんな感じです。このとき、10件のファイル(フォルダ)がリストアップされます。
http://s3.amazonaws.com/(bucket)?max-keys=10
省略値は 100 件です。ただし、後述の delimiter パラメータを使って、フォルダをまとめたときの ListBucketResult/CommonPrefixes 要素もこの件数に含まれるため、delimiter パラメータと併用したときは、max-keys パラメータの件数と、実際のファイル(フォルダ)の件数が一致しないことがあります。

delimiter パラメータは、指定した文字列を区切りとして、パスをまとめ上げるものです(っていっても伝わりませんよね)。↓こんな感じです。このとき、ルートのファイルとフォルダ(フォルダの中身は含まない)がリストアップされます。
http://s3.amazonaws.com/(bucket)?delimiter=/
カレントフォルダのフォルダとファイルをリストアップするときに使えそうです。カレントフォルダは prefix パラメータを使って表現し、その prefix パラメータの値には、レスポンスの ListBucketResult/CommonPrefixes/Prefix 要素の値を指定します。よく考えたら、カレントフォルダは URI での表現もできますね。そして、これらを繰り返せば、フォルダツリーが得られることになります。

delimiter パラメータを使ったとき、レスポンス中のフォルダは ListBucketResult/Contents/Key = "(folder)_$folder$" として表現されます。_$folder$ の接尾辞でフォルダと判断するのでしょうかね。

2008-05-01

Interface elements for jQuery サイトが復活してひと安心

Interface elements for jQuery サイトが消えてしまった!?
先週くらいなのですが、↑が参照できないことに気が付いたのですが、未だに復活していないようです。追記です。先ほど、ダメモトで、再開する予定があるかどうか、作者にメールしておきました。返事がもらえるといいなぁ。
Interface elements for jQuery のサイトが復活しました。

Interface elements for jQuery
http://interface.eyecon.ro/

作者の Stefan Petre 氏から返事があり、ホスティング会社がある措置のため、interface ホスト (サブドメイン) のみ停止していたとのことでした。そして、その後、すぐに再開してくれたようです。素早すぎる対応に感謝です。ありがとうございました。

また jQuer UI フォーラムの該当スレッドにもお知らせしておきました。