2009-01-24

jQuery.extend でオブジェクトの deep コピーができるの知らなかったよ

次のように jQuery.extend でオブジェクトの deep コピーができる んですね。シラナカッタよ。

var obj = $.extend(true, {}, {}, ... {});

第1引数に true を指定すると、以降のオブジェクトを再帰的に deep コピーしてくれます。

Enhancing of jQuery.ajax with JSDeferred: jQuery.ajax チェーンとその short cut のエラーハンドリング

Enhancing of jQuery.ajax with JSDeferred は、JSDeferred を使って、jQuery.ajax チェーンとその short cut のエラーハンドリングを実現するものです。

Enhancing of jQuery.ajax with JSDeferred は JSDeferred の内、Deferred オブジェクトのみを抜き出して、jQuery.ajax を上書きするという作りにしています。

jquery.ajax.deferred.js

もともと JSDeferred も jQuery サポートを提供していますが、フルスペックの JSDeferred は不要だし、jQuery.ajax のエラーハンドリングが期待どおりじゃなかったので、コンセプトを絞って小さくまとめてみました。ですので、まずはオリジナルの JSDeferred をあたってみてください。

Enhancing of jQuery.ajax with JSDeferred を使うと、次のようなコードが書けるようになります。

$.getJSON('/foo.json').next(function(foo) { // -- (1)
console.log('next', foo);
return $.getJSON('/bar.json').next(function(bar) { // -- (2)
console.log('bar', bar);
return [foo, bar];
});
}).next(function(foobar) { // -- (3)
console.log('foobar', foobar[0], foobar[1]);
}).error(function(status) { // -- (4)
console.log('error', status);
});

はじめに (1) の $.getJSON で /foo.json のデータを取得します。成功したときは (2) に続きます。失敗したときは (4) でエラーハンドリングして終了します。

次に (2) $.getJSON で /bar.json のデータを取得します。成功したときは (3) に続き、このときの引数が /foo.json と /bar.json の配列になります。失敗したときは (4) でエラーハンドリングして終了します。

(4) のエラーハンドリングは、jQuery.ajax の error イベントの status ("error" や "parseerror" など)か、throw したオブジェクトが引き渡されます。

このコードからも分かるとおり、Enhancing of jQuery.ajax with JSDeferred は、次のような利点があります。

jQuery.ajax の非同期のコールバックを next メソッドでチェーンして、その順番どおり実行させることができます。このとき、上から下に向かって、ネストしているときは、親から子に向かってという順番になります。

そして、そのチェーンの中でエラーが発生したとき、error メソッドでエラーをハンドリングできます。チェーンのはじめや途中でエラーが発生したときは、そこでチェーンが中断するので、エラーのハンドリングは 1つで済むということです。

また、jQuery.ajax の short cut はエラーのコールバックをサポートしていませんが、jQuery.getJSON や jQuery.post でも error メソッドを使ってエラーのハンドリングができます。

もちろん、次のとおり、オリジナルのコードもそのまま動きます。そのときの状況に応じて使い分けることができます。

$.ajax({
url: '/foo.json', dataType: 'json',
beforeSend: function(xhr) {},
success: function(data, status) {},
error: function(xhr, status, e) {},
complete: function(xhr, status) {}
});

はじめは、非同期のコールバックをチェーンするという仕掛けが理解しにくい印象があったのですが、使ってみると慣れてきて、だんだん手放せなくなってきています。JSDeferred を開発している cho45 さん に大感謝です。

2009-01-19

ガジェットの中から MySpace Developer Platform で開発中かどうか判別する方法

opensocial-jquery 0.3.0 JavaScript コンテナセレクタと CSS コンテナセレクタという試み
現在は "ig", "orkut", "hi5" のコンテナしか判別できませんが、代表的なコンテナは評価しつつ追加していきます。MySpace も追加しようと試みてはいますが、iframe@src の内容が iGoogle 仕様とはかなり異なるので、どう判断しようか思案中です。よいアイディアはないでしょうか。
ガジェットの中から MySpace Developer Platform で開発中かどうか判別する方法を見つけました。

MySpace Developer Platform で開発中のときだけ iframe@src に v パラメータが付与され、その値が "dev" となるようです。この違いを検出することで、開発中かどうか判別できます。

var params = gadgets.util.getUrlParameters();
var v = params['v'] || '';
if (v == 'dev') {
// MySpace Developer Platform
}

これで igoogle, orkut, hi5, myspace とそのサンドボックス(開発中)を判別する方法が分かりました。次はどこのコンテナを調べようかな。

2009-01-13

opensocial-jquery 0.4.0 jQuery.ajax で People を操作できるようにしました

おはようございます。なかじまんです。opensocial-jquery 0.4.0 リリースのお知らせです。

opensocial-jquery 0.4.0 では、jQuery.getData を使って、OWNER、VIEWER とその友達のプロフィールを取得できるようにしました。

次のソースコードは、OWNER のプロフィールを取得する例です。

var url = '/people/@owner/@self';
var data = {};

$.getData(url, data, function(people) {
var person = people[0];

console.info(person.id);
console.info(person.displayName);
console.info(person.thumbnailUrl);
console.info(person.name.familyName);
console.info(person.name.givenName);

});

次のソースコードは、OWNER の友達のプロフィールを取得する例です。

var url = '/people/@owner/@friends';
var data = {
startIndex: 2, // 取得位置 0..n -- 省略時 0
count: 10 // 取得件数 1..n -- 省略時 20
};

$.getData(url, data, function(people) {

console.info(people.startIndex); // 取得位置
console.info(people.itemsPerPage); // 取得件数
console.info(people.totalResults); // 総件数

$.each(people, function(i, person) {

console.info(person.id);
console.info(person.displayName);
console.info(person.thumbnailUrl);
console.info(person.name.familyName);
console.info(person.name.givenName);

});
});

jQuery.getData は、プロフィールの取得対象を URL で表現するところがミソです。@ の部分が対象を意味的に表すセレクタです。

/people/@owner -- OWNER
/people/@owner/@self -- OWNER
/people/@owner/@friends -- OWNER FRIENDS
/people/@viewer -- VIEWER
/people/@viewer/@self -- VIEWER
/people/@viewer/@friends -- VIEWER FRIENDS
/people/@me -- VIEWER
/people/@me/@self -- VIEWER
/people/@me/@friends -- VIEWER FRIENDS

jQuery.getData は、プロフィールの取得条件を URL パラメータで表現するところもミソです。

/people/@owner/@friends?count=10 -- 10件取得
/people/@owner/@friends?startIndex=10 -- 11件目から取得
/people/@owner/@friends?startIndex=10&count=10 -- 11件目から10件取得

jQuery.getData は jQuery.ajax の上に成り立っています。ですので、次のソースコードも同じ振る舞いをします。

$.get('/people/@owner', {}, function(people) {}, "data");
$.ajax({
url: '/people/@owner',
dataType: 'data',
success: function(people) {}
});

"data" は新しい dataType です。"data" は OpenSocial のソーシャルなデータという意味を表しています。Ajax のグローバルイベントや、jQuery.ajax のローカルイベントも使えますので、通常の Ajax と同じように OpenSocial のソーシャルなデータを扱えます。

簡単なサンプルを用意しました。OWNER のプロフィールを取得して表示するものです。

Self - samples - opensocial-jquery



OWNER の友達のプロフィールを取得して表示するものです。

Friends - samples - opensocial-jquery



opensocial-jquery 0.4.0 では、jQuery.container プロパティの改良と変更があります。

console.info($.container.igoogle);
console.info($.container.myspace);
console.info($.container.sandbox);
console.info($.container.domain); // "google.com", "myspace.com"

jQuery.container.ig は igoogle に改名しました。また myspace とその sandbox を判別できるようにしました。$.container.domain を追加 しました。

jQuery.getData は、どこまで実現するべきか、今後その評価を進めていきます。プロフィールの取得条件は、何らかの形ですべてサポートする予定です。バッチリクエストをサポートするべきか悩み中です。シンプルさを保つために、捨てるべきところは捨てたいのですが...。あと、MySpace たけ他のコンテナと振る舞いが違ったりするようなので、その差を吸収していきます。

2009-01-12

Dreamweaver で jQuery API のコード補完ができる jQuery API extension for Dreamweaver

こんにちは。なかじまんです。

Dreamweaver で jQuery API のコード補完ができる jQuery API extension for Dreamweaver というのがあり、さっそくインストールしてみました。

jQuery API extension for Dreamweaver | XTND.US - Xtnd urslf! Extensions for Adobe Dreamweaver, Drupal, jQuery, Flex, Fireworks, Prototype, APIs
Reduce tons of typing. If you are into jQuery and use Dreamweaver then you really need this extension. Let us save you tons of time by reducing keystrokes with our jQuery API extension for Dreamweaver which provides code coloring, Snippets, and code hints that list every jQuery and jQuery UI function for you.
インストールは、Download the extension からアーカイブをダウンロードし、Extension Manager で登録するだけです。インストールすると、↓こんな感じで jQuery API のコード補完ができるようになります。



これは絶対に手放せないというほとではありませんが、普段使わない API とか、API の使い方を度忘れしたときなど、曖昧な記憶を呼び出すのに役立ちます。また、jQuery を使っていると、ある一定の API のみを使ってパターン化してくる傾向があるのですが、そのシーンに即した別の API があることに気が付くという効果もありそうです。

前述の引用だと jQuery UI API のコード補完も含むとあります。jQuery UI の知識は浅いので、どのくらい役立ちそうかは判断できていません。もしかすると jQuery UI のコード補完が肝だったりするのかも。

2009-01-06

opensocial.Environment.getDomain をいろいろなコンテナで試して、どんなドメイン名が取得できるのか調べてみた

opensocial-jquery 0.3.0 JavaScript コンテナセレクタと CSS コンテナセレクタという試み
現在は "ig", "orkut", "hi5" のコンテナしか判別できませんが、代表的なコンテナは評価しつつ追加していきます。MySpace も追加しようと試みてはいますが、iframe@src の内容が iGoogle 仕様とはかなり異なるので、どう判断しようか思案中です。よいアイディアはないでしょうか。
Yoichiro さんのコメント... opensocial.getEnvironment().getDomain()にて、コンテナのドメイン名を取得することができます。
さっそく opensocial.Environment.getDomain をいろいろなコンテナで試して、どんなドメイン名が取得できるのか調べてみました。

調べるために用意したガジェットのソースコードとその結果は、次のとおりです。

<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs title="opensocial.Environment.getDomain">
<Require feature="opensocial-0.8" />
</ModulePrefs>
<Content type="html"><![CDATA[
<script type="text/javascript">
console.info(opensocial.getEnvironment().getDomain());
// iGoogle sandbox http://www.google.co.jp/ig => google.com
// Orkut sandbox http://sandbox.orkut.com/ => orkut.com
// Hi5 sandbox http://sandbox.hi5.com/ => hi5.com
// Hi5 sandbox http://betasandbox.hi5.com// => hi5.com
// MySpace Developer Platform
// http://developer.myspace.com/ => myspace.com
</script>
]]></Content>
</Module>

どのコンテナか判別するには、opensocial.Environment.getDomain を使うのが妥当(正解)のようです。Environment というオブジェクト名だけのことはありました。

ただ、サンドボックス固有のドメイン名を表現しないようなので、サンドボックスかどうか判別することはできません。ですので、サンドボックスかどうかの判断は、コンテナごとに固有のルールを発見しないといけませんね。

$USE_PARAM_SEMICOLONS か -oldstyle_urls が必要だったけ?

おはようございます。なかじまです。

Perl の CGI モジュールで、QUERY_STRING を含むページの URL を扱う必要があり、次のとおり、CGI::url を使ってみると、パラメータの区切りがセミコロンになってしまうのです。あれ?そうだったけ?

my $cgi = CGI->new();
my $page = $cgi->url( -absolute => 1, -query => 1 );
# $page => /foobar.cgi?foo1=bar1;foo2=bar2;foo3=bar3

ざっと CGI.pm のソースコードを覗いてみたところ、バージョン 2.64 からセミコロンになったようです。ということは、私が CGI::url を使ったのは、ずっと前だったのかもしれません。Perl 5.004 とかそれ以前かもしれません。

アンパサンドに変更する方法は、2とおりありました。

1つ目は $USE_PARAM_SEMICOLONS を 0 とする方法です。

use CGI;
$CGI::USE_PARAM_SEMICOLONS = 0;

2つ目は -oldstyle_urls を指定する方法です。

use CGI qw(-oldstyle_urls);

とすると、次のとおり、アンパサンドで区切られた期待どおりの結果となりました。

my $cgi = CGI->new();
my $page = $cgi->url( -absolute => 1, -query => 1 );
# $page => /foobar.cgi?foo1=bar1&foo2=bar2&foo3=bar3

初期値がセミコロンになった経緯とか知りたかったのですが、ネット(日本語サイト)で検索しても、ほとんど言及されてないんですね。みんな CGI モジュールで QUERY_STRING を吐き出したりしないんでしょうか...。

[を] CGI.pmでのパラメタの調整 がヒントになりました。だんだん。

2009-01-05

opensocial-jquery 0.3.0 JavaScript コンテナセレクタと CSS コンテナセレクタという試み

こんばんは。なかじまんです。opensocial-jquery 0.3.0 Preference と Window の強化と見直しをしました。 の続きです。

opensocial-jquery 0.3.0 では、JavaScript と CSS によるコンテナの判別という新しい仕組みを導入しました。ガジェットの JavaScript や CSS の中で、いま実行しているコンテナが iGoogle か Orkut かなどを簡単に判断できます。

そもそも OpenSocial ガジェットは、異なるコンテナにおいても、同じソースコードで実行でき、コンテナの実装に差異があったとしても、API レベルで判別できるんじゃないの?と指摘されそうですが、あえてコンテナを判別できる仕組みを導入してみました。

次のとおり、JavaScript の中で jQuery.container プロパティを参照してコンテナを判別できます。

if (jQuery.container.ig) { // iGoogle のとき }
if (jQuery.container.orkut) { // Orkut のとき }
if (jQuery.container.hi5) { // Hi5 のとき }

次のとおり、サンドボックスかどうかも判断できます。

if (jQuery.container.orkut && jQuery.container.sandbox) {
// Orkut Sandbox のとき
}

さらに HTML 要素に対して、コンテナを表すクラスを自動的に追加します。そのため、次のように、CSS の中でコンテナを判別できます。

.ig div.photo img { /* iGoogle のとき */ }
.orkut div.photo img { /* Orkut のとき */ }
.orkut.sandbox div.photo img { /* Orkut Sandbox のとき */ }
.hi5 div.photo img { /* Hi5 のとき */ }

JavaScript でコンテナ別にコードを分岐でき、CSS でコンテナ別にデザインを適用できることになります。

現在は "ig", "orkut", "hi5" のコンテナしか判別できませんが、代表的なコンテナは評価しつつ追加していきます。MySpace も追加しようと試みてはいますが、iframe@src の内容が iGoogle 仕様とはかなり異なるので、どう判断しようか思案中です。よいアイディアはないでしょうか。

CSS によるコンテナの判別という仕掛けは CSS Browser Selector のアイディアを転用したものです。デザイン畑に疎い私としては、CSS hack を駆使して悩むくらいなら、このアプローチを使っちゃえという感じですね。ほんと、このアイディアは素晴らしい発見ですよ。

opensocial-jquery 0.3.0 Preference と Window の強化と見直しをしました。

おはようございます。なかじまんです。opensocial-jquery 0.3.0 を公開しました。

Preference と Window の強化と見直しをしました。

Preference

次のとおり、ユーザ設定を保存できるようにしました。feature/setprefs を必要とします。

$.pref('foo', 'foo1');
$.pref('bar', 123);
$.pref('buz', false);

次のとおり、キーと値の組を指定できます。直感的でいいでしょ?

$.pref({
foo: 'foo1',
bar: 123,
buz: false
});

次のとおり、値として配列も保存できます。直感的でいいでしょ? gadgets.Prefs.setArray を代替するものです。なぜか gadgets.Prefs.setArray は、ユーザ設定を永続化しません。バグでしょうかね。$.pref は、配列であっても永続化するようにしてあります。

$.pref('foo', ['foo1', 123, false]);

そして、次のとおり、配列として値を取得できます。

var foo = $.prefArray('foo');
console.log(foo); // => ['foo1', '123', 'false']

次のとおり、ユーザ設定を取得するとき、HTML エスケープしないように振る舞いを変更しました。$.prefArray も同様です。gadgets.Prefs.getString と getArray は HTML エスケープしていて使い勝手が悪かったのです。

$.pref('foo', '<b>foo</b>');
var foo = $.pref('foo');
console.log(foo); // => '<b>foo</b>'

次のとおり、URL パラメータも取得できるようにしました。ユーザ設定と URL パラメータを区別する線引きが難しい... つまり、URL パラメータは、ユーザ設定の一環としてみなしてもよいと整理し、よい意味で妥協しています。このほうが覚えやすいでしょ?

var lang = $.pref('lang'); // 言語
var country = $.pref('country'); // 地域
var moduleId = $.pref('mid'); // モジュールID
var lang = $.pref('up_lang');

URL パラメータとユーザ設定のキーが同名のときは、URL パラメータを優先します。同名にならないような命名を推奨しますが、どうしても区別したいときは、ユーザ設定のキーの先頭に "up_" を付与してください。

Window

次のとおり、ガジェットの横幅を取得できます。iframe@width に相当します。直感的でいいでしょ?

var width = $(window).width();
console.log(width); // => 257

次のとおり、ガジェットの縦幅を取得できます。iframe@height に相当します。直感的でいいでしょ?

var height = $(window).height();
console.log(height); // => 200

$(window).width と height は、gadgets.window.getViewportDimensions を代替するものです。

次のとおり、ガジェットの高さを変更できます。$(window).height(height) は、gadgets.window.adjustHeight(opt_height) を代替するものです。直感的でいいでしょ?

$(window).height(400);
// $(window).adjustHeight(400); と同じ

次のとおり、ガジェットのタイトルを設定するとき、その中で HTML エスケープするように振る舞いを変更しました。つまり、HTML エスケープする前のタイトルを指定できるようにしました。

$(window).title('<b>foo</b>');
// gadgets.window.adjustHeight('&lt;b&gt;foo&lt;/b&gt;'); と同じ

opensocial-jquery 0.3.0 では、jQuery.container プロパティと CSS container selector という新しい考え方を導入しました。これは、別途エントリを投稿して説明します。

2009-01-02

Postal Search Ajax API のデータを Amazon CloudFront 経由で配信して効率化しました

こんにちは。なかじまんです。

Postal Search Ajax API の新しい組み込み方法を追加しました
また、今回から同時に、住所のデータを Amazon Simple Storage Service (Amazon S3) を使って、Amazon のデータセンタで運用するようにして、信頼性を高めました。
本日 (1/2) から Amazon CloudFront を経由して Postal Search Ajax API の住所データを配信するように変更しました。Amazon CloudFront は Japan Edge Locations をサポートするため、日本国内のキャッシュで完結するようになり、米国の Amazon S3 から直接データを配信するよりも、圧倒的に高速になりました。

Amazon CloudFront とはどんなもので、どのような利点や欠点があるかは、次のエントリで分かりやすくまとまっていますので、興味がありましたら読んでみてください。

AmazonS3上のファイルを国内でも高速配信可能なAmazon CloudFrontリリース

Amazon CloudFrontの使用上の注意とTipsまとめ

なお、Postal Search Ajax API の使い方やインタフェースに変更はありませんので、修正などの必要はありません。今までと同じように使ってください。また、Postal Search Ajax API の構築キットにも影響はありません。

あなたの WEB サーバに API の script ファイルを格納し、Postal Search APIs & Solutions のサーバにデータを預けるという構成で利用しているときは、postalua.js をダウンロードして差し替え てください。差し替えるまでは、従来どおり Amazon S3 から住所データを配信することになります。

United States や European と比べて Japan Edge Locations は 30% くらい割高なんですよね。とはいっても、私のような個人でも手軽に使えちゃうんだから Amazon CloudFront 恐るべしですよ。

2009-01-01

Opera 9.5 から script 要素を appendChild してもブロッキングされなくなったよ!?

Happy New Year! なかじまんです。

Opera 9.5 から script 要素を appendChild しても、スクリプトがロードされるまで、ブロッキングされなくなったのですね。まったく知らなかったよ。

var node = jQuery(document.createElement('script'))
.attr('type', 'text/javascript')
.attr('charset', 'UTF-8')
.attr('src', 'http://...');
// Opera 9.5 からはブロッキングされない!?
jQuery('head', document)[0]
.appendChild(node);

ざっとネットで探してみましたが、あまり言及がないようです。その中で見つけたエントリが↓これです。どうやら公式なアナウンスもされてないようですね。

Opera9.2から9.5でのJavaScript周りの変更点 - 0x集積蔵
JSONPが非同期リクエストに修正JSONP が Opera だと非同期処理できない - 川o・-・)<2nd lifeの件で、TAKESAKO @ Yet another Cybozu Labs: Operaでも非同期リクエストが並列処理できる img-JSONPのようなBKも生まれていた困った動作がようやく改善されました。最速インターフェース研究会 :: OperaでJSONPを非同期リクエストするのサンプルで、タイマーが止まらないことを確認できます。(なんでChangelogにないんだ…)
この件であまり困ることはありませんが、Opera 9.2 さえ捨ててしまえば、今後は Opera 固有の振る舞いを意識した実装は不要ですね。