2008-06-29

Jaxer.setEvent を使ってサーバサイドでイベントを指定する

Aptana Jaxer でイベントをサーバサイドで指定したとき、クライアントサイドでそのイベントが有効になるのか、有効なときどう振る舞うのか、いろいろなパターンを試してみました。

サーバサイドで onclick プロパティに Function を指定してみましたが、クライアントサイドでは動作しませんでした。

<script type="text/javascript" src="jquery.js" runat="both"></script>
<script type="text/javascript" runat="server">
//<![CDATA[
jQuery(function($) {
var element = $('#r')[0];
element.onclick = function() {
alert('bar1');
});
});
//]]>
</script>
<div id="r">foo</div>

サーバサイドで setAttribute を使って onclick に JavaScript コードを指定してみたところ、クライアントサイドで期待どおり動作しました。

jQuery(function($) {
var element = $('#r')[0];
element.setAttribute('onclick', 'alert(\'bar2\');');
});

サーバサイドで addEventListener を使って onclick に Function を指定してみましたが、クライアントサイドでは動作しませんでした。

jQuery(function($) {
var element = $('#r')[0];
element.addEventListener('click',
function() { alert('bar3'); },
false);
});

つまり、属性値として指定したイベントのみが対象ということです。サーバサイドの DOM をシリアライズして、クライアントサイドの HTML をレンダリングするという過程から捉えると、あたり前の結果といえます。

Jaxer.setEvent メソッドを使うと、もう少し直感的にイベントを指定できます。ただし、上記の仕組みを変えるものではなく、あくまでもヘルパメソッドですよ。

サーバサイドで Jaxer.setEvent を使って onclick に Function を指定してみたところ、クライアントサイドで期待どおり動作しました。

jQuery(function($) {
var element = $('#r')[0];
Jaxer.setEvent(element, 'onclick', function() {
alert('foo1');
});
});

サーバサイドで Jaxer.setEvent を使って onclick に JavaScript コードを指定してみたところ、クライアントサイドで期待どおり動作しました。

jQuery(function($) {
Jaxer.setEvent(element, 'onclick', 'alert(\'foo2\');');
});

サーバサイドで Jaxer.setEvent を使って、イベントから変数を参照してみましたが、undefined になりました。このときの foo は、サーバサイドではなく、クライアントサイドの foo を参照することになります。
jQuery(function($) {
var foo = 'foo4';
Jaxer.setEvent(element, 'onclick', function() {
alert(foo);
});
});

ですので、クライアントサイドに foo という Function を定義し、サーバサイドで foo を呼び出すようにイベントを指定することができます。

<script type="text/javascript" src="jquery.js" runat="both"></script>
<script type="text/javascript" runat="server">
//<![CDATA[
jQuery(function($) {
Jaxer.setEvent(element, 'onclick', 'foo();');
});
//]]>
</script>
<script type="text/javascript">
//<![CDATA[
function foo() {
alert('foo5');
}
//]]>
</script>
<div id="r">foo</div>

これまでの結果から、サーバサイドで jQuery.fn.click (jQuery.fn.bind) を指定しても、期待どおり動作しないことは自明です。jQuery はイベントの状態を jQuery オブジェクトが保持するため、その状態クライアントサイドで失われます。

jQuery(function($) {
$('#r').click(function() {
alert('foo3');
});
});

最後に参考として、Jaxer.setEvent のソースコードを抜粋します。ハンドラが文字列のときはそのまま、Function のときは文字列に変換してから setAttribute を使って指定しているだけでした。

var attribute;
if (typeof handler == "function")
{
if (handler.name == "")
{
attribute = "(" + handler.toSource() + ")()";
}
else
{
attribute = handler.name + "()";
}
}
else // handler should be a string (the handler function's body)
{
attribute = handler;
}
domElement.setAttribute(eventName, attribute);

Function.toSource メソッドを積極的に使ったコードをはじめてみた気がしますが、この使い方は妥当なんだろうか?という小さな疑問が残りました。

1 件のコメント:

aquilegia さんのコメント...

勉強になりました。


> Function.toSource メソッドを積極的に使ったコードをはじめてみた気がしますが、この使い方は妥当なんだろうか?という小さな疑問が残りました。
確かにあまり使わないですが関数を複製する場合はtoSourceかtoStringを使うことになります。個人的には互換を考慮してtoStringメソッドです。

関数の複製は上位のスコープを参照していると完全な複製ができないので、nakajimanさんが書かれている通りfooの参照先が変わってしまったりします。ですが慣れればそれはそれで便利なのでOKかなと思います^^

クライント/サーバ間で振る舞い(関数)をやりとりするのはセキュリティ的な不安を直感的に感じますが、XHRでやり取りしていればクロスドメイン制約があるので問題ないかと。