DWRのScriptBufferクラス

八角研究所 チャット作成で学ぶWebリモーティング(4) - DWRのReverse Ajaxで簡単Comet!*1

ここで取り扱ってるScriptBufferクラスの取り扱い方について意味が分からなかったんだけど、ようやく理解できたので書いときます。

分からなかったのはこの部分

new ScriptBuffer("_showMessages(")
.appendData(messages)
.appendScript(");");

一応色分けしてみたのですが、意味分かりますか…?僕は全然分からなかったですね(恥)
ちなみに変数messageはあちらのサイト*2
を見ていただければわかりますが、ChatMessageDTOというクラスの配列です。

勘違いしてたのは、この部分全体がScriptBufferのコンストラクタの引数だと思っていたことです。

"_showMessages(").appendData(messages).appendScript(");"

でも実際、これの意味はこういうことだったそうです…。

ScriptBuffer bf =new ScriptBuffer();

bf.appendScript("_showMessages(");
bf.appendData(message);
bf.appendScript(");");

ScriptBufferのAPI*3をちゃんと読んでみました。

最初の例ってこんな形になってますよね

buffer.append(x).append(y).append(z);

どうやらScriptBufferではこんな書き方を出来るようにするために、わざわざappendData()とか,appendScript()といったメソッドの返り値にScriptBufferを設定しているみたいです。なんかすごいややこしいですね…。

appendCallメソッドで代用?

実はScriptBufferにはappendCall()っていうメソッドが用意されています。名前からしてあきらかにJavaScriptの関数を呼び出してくれるメソッドですよね。こいつを使えば、さっきみたいにややこしい書き方しなくていいんじゃないの!?
実はここに落とし穴があります。

間違った使い方

ScriptBuffer bf =new ScriptBuffer();
bf.appendCall("_showMessages",message);

これに対して今用意されてるJavaScript側の_showMessage関数はこんな形をしています

function _showMessage(array){}

こんな風に実行してしまうとarrayにはChatMessageDTO*4クラスの配列のmessageでなく、message[0]が代入されちゃいます。
これはまずいです。

appendCallを正しく理解する

ScriptBufferにはtoStringメソッドが実装されているので、これを使ってどんなスクリプトが入ってるのか見てみます。

まず最初にappendCallを使わない場合。最初にあげためんどくさい書き方の方です。

[_showMessages(, [Lorg.sample.dwr.ChatMessageDTO;@31b712, );]

こっちがappendCallを使った場合

[_showMessages, (, org.sample.dwr.ChatMessageDTO@e7531d, ,, org.sample.dwr.ChatMessageDTO@101c7a5, ,, org.sample.dwr.ChatMessageDTO@1da42fa, );]


上の例の[Lorg.sample.dwr.ChatMessageDTO;@31b712, );]がChatMessageDTOの配列を意味しています。つまりmessageのことです。これに対して下側は配列が勝手に展開されてますね。要するにappendCallの二つ目の引数は、もともと呼び出す関数に渡す引数の集合を入れる場所です。説明足らずで申し訳ないですが、最後にこれの正しい使い方を書いておきます。

appendCallの正しい使い方

正しい使い方は二通り考えられます

一つ目 messageをObject配列に入れる

ScriptBuffer bf =new ScriptBuffer();
//要素1の配列にmessage配列を入れてます
Object[] arguments = {message}

bf.appendCall("_showMessages",arguments);

ブラウザ側はそのまま

function _showMessage(array){}


配列が展開されて呼び出す関数の引数になっちゃうわけですから、message配列をもう一つ配列にいれちゃいましょうってことです。

二つ目 JavaScriptのargumentsを利用する

上の方法でも正しいですが、多分こっちが本来の使われ方です。

サーバ側はそのまま

ScriptBuffer bf =new ScriptBuffer();
bf.appendCall("_showMessages",message);

ブラウザ側に変更を加える

function _showMessage(){
for(var i = 0 ; i < arguments.length ; i++){

arguments[i]に何かの処理をする...

}
}


「appendCallを正しく理解する」の項目で説明したようにこれを実行すると

_showMessage(message配列の1番目,message配列の2番目,message配列の3番目,message配列の4番目,…)

なんて風に実行されます。JavaScriptでは引数の数は可変です。引数はいくつでもいいらしいです。*5

ScriptBufferを使ってスクリプトを送り込んでみる

今度はScriptBufferでJavaScriptの新しい関数を定義してみます。

今回ScriptBufferで作る関数

function test(){
alert("test");
}

このスクリプトを送りこむScriptBuffer

bf.appendScript(" function test(){alert('test')}" );

これを呼び出す方法

サーバから呼び出す

bf.appendScript(" function test(){alert('test')}" );
bf.appendCall("test");

appendCallは引数を指定しない場合、こんな風につかえます。


やってみて分かったんですが、一度ScriptBufferでtest関数を送ったからといって、test関数はブラウザ側に書き込まれて他の関数と同じように扱えるわけじゃないみたいです。上の場合だとappendScriptとappendCallを同じScriptBufferに乗せて送ったので正しく実行できましたが、あらかじめ送っておいたtest()関数を後から呼びだすことはできませんでした。ちょっと残念です。もしかしたら別の方法があるかもしれないので、また見つけたら書きます。

*1:http://www.hakkaku.net/articles/20071017-54/trackback

*2:http://www.hakkaku.net/articles/20071105-7099

*3:http://directwebremoting.org/dwr-javadoc/org/directwebremoting/ScriptBuffer.html

*4:このデータ型についてはあちらのサイトを参照してください

*5:昨日知ったばっかりですが…