l'essentiel est invisible pour les yeux

Saturday, January 26, 2008

もし、あなたがGoogleで働いていたら?

Googleに関する記事を一本書いたので、あわせてもう一本。
もし、私がGoogleplexで働いていて、同僚に20%ルールをどんなプロジェクトに使うかと聞かれれば、Google Earthのデータがリアルタイムで反映される地球儀を作りたい。Googleの巨大なサーバーの力を駆使して、世界各地で起こっている事が反映される地球儀だ。この地球儀には、マルチタッチによる拡大縮小はもちろん、様々な革新的なユーザインタフェースを取り組みたい。「指先で世界中にアクセスできる地球儀だ。」

地球儀は、データを可視化するためのメタファでしかないが、莫大に増え続けるデータは、もっとわかりやすく、もっと扱いやすくしなければいけない。

[AS3] ActionScript 3からFirebugを使うas3zerobugを作った

こんにちは。初めてのAction Scriptネタです。(最初で最後?)
昨日から、(JSで出来ない事があったので)Action Script 3を書き始めました。

AS3の開発環境を構築し、次にデバッグ環境を構築しようと調べていると、ASでは、traceやfdbを使えばよい、Mac OS XからTraceを出力する方法は…と色々と書いてあったのですが、面倒なので、Action Script 3からFirebug consoleへのシンプルなラッパーを作りました。使い方は、Firebugのconsole APIと同じです。

as3zerobug


サンプル(Firefox only)
Firebugのコンソールを開けて下さい。

zerobug::console sample



ソースコード

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
width="100%"
height="100%"
backgroundGradientColors="[0xFFFFFF, 0xAAAAAA]"
creationComplete="init();">

<mx:Script> <![CDATA[
import zerobug.Console;

public function init():void
{
var console:Console = new Console();

// start the timer
var timerId:String = 'zerobug';
console.time(timerId);

// Primitive Number
console.log(1985);
console.log("int - %d", 1985);
console.log(new Number(0.434294482));
console.log('Number - %d', new Number(0.434294482));

// String
console.log('Hello world!');
console.log('Hello world! from %s', 'Action Script 3');

console.log(new String('Hello world!'));
console.log('String - %s', new String('Hello world!'));

// Date
var now:Date = new Date();
console.log(now);
console.log('Date - %s', now);
console.dir(now);

// Array
var arr:Array = new Array([1, 1, 2, 3, 5]);
console.log(arr);
console.log('Array - %s', arr);

// stop the timer
console.timeEnd(timerId);
}

]]></mx:Script>

<mx:VBox>
<mx:Label fontSize="16" htmlText="Zerobug::Console API test."/>
<mx:Label fontSize="16" htmlText="Plase enable Firebug and see console of Firebug."/>
</mx:VBox>

</mx:Application>


インストール方法
Google Codeからチェックアウトして、bin/zerobug.swcをコンパイル時に読み込むライブラリに加えて下さい。

% svn co http://as3zerobug.googlecode.com/svn/trunk/ aswzerobug


ORSWF-config.xmlのサンプルです。適せんパスを変えてください。

<flex-config>
<compiler>
<library-path append="true">
<path-element>zerobug.swc</path-element>
</library-path>
</compiler>
</flex-config>



開発環境は、IDEが苦手なオールドタイプなので、RailsEditor, id:secondlifeさんのrascutをインストールした。(rascut素敵!Thank you!!)

Macだと、fsch, mxml共に日本語(SJIS)で出力されて文字化け & rascutが動かないのでユーザ定義定数を修正。

fcsh

#java $VMARGS "-Dapplication.home=$FLEX_HOME" -jar "$FLEX_HOME/lib/fcsh.jar"
java -Dfile.encoding=UTF8 $VMARGS "-Duser.language=en" "-Dapplication.home=$FLEX_HOME" -jar "$FLEX_HOME/lib/fcsh.jar"


P.S.
ECMAScript 4 (pdf)も、AS3のプログラミングパラダイムを受け継いでおり、ダックタイピング周りの構文が追加されている。触り始めたばかりだが、個人的には、Javaライクなプログラミングよりも、Javascript1.8のPythonスタイルの方が美しいし楽しい。下ならとことん下。CPUのパイプライン命令レベルで最適化する。上ならとことん上。抽象の世界で美しくコードを書く。私はそんなのが好きだ。

Google Maps Recent EditのユーザインタフェースをGoogler達は不気味と思うのか?

BloggerやGoogle Mapで編集された場所のプレイスマーカーをリアルタイムで表示するGoogle Maps Recent Editが公開された。(*1) Google Maps Editの利用シーンが今ひとつピンとこないが、この眺めるユーザエクスペリエンスは、ユーザのアクション無しに画面遷移が発生する。このGoogle Maps Recent Editに不快感を覚えるのは、たとえ拡大や縮小といった操作をしている途中であっても、強制的に次のプレイスマーカーに移動させられる事だろう。

「本当に、テレビにでも移して眺めるだけのインタフェースだ!それにしては、何とも退屈な映像だ。各地の美しい写真が表示され、クリック一つで旅行プランが購入できるとか、もっと楽しませてくれ。」

少し技術的な事に触れておくと、この仕組みは、headタグ内にSCRIPTタグを書き出し、実行後に削除するJSONPで実現されている。私が閲覧したある特定の時点で挿入されたタグとスクリプトは次のような物だった。


<script id="_8fbw75xce" type="text/javascript" charset="UTF-8" src="http://maps.google.com/maps?spn=3.408051,25.905762&z=6&key=ABQIAAAAs9dcsWbbZEWT6IvNCMoH7BSEkgDlNpuQ8hYNJ6DISwGIqkxfgBSsICsDOo_i02Xr_AijoU7Ytm0KBw&vp=33.945,-118.372&ev=p">
1GAddCopyright("m","38",14.5319,-118.3835,32.7187,-86.7034,4,"LeadDog Consulting",17,true);
2GAddCopyright("m","0",22.0310,-125.2410,49.0930,-62.7100,3,"Tele Atlas",null,false);
3window.GAppFeatures && window.GAppFeatures({cb:{bounds:[{s:35920000,w:-115657000,n:36479000,e:-114940000,ix:1},{s:36880860,w:-123100000,n:38100000,e:-121560000,ix:4},{s:29243270,w:-95884552,n:30105929,e:-94744720,ix:5},{s:33441755,w:-118572692,n:34214073,e:-117630615,ix:6},{s:28029561,w:-81861877,n:29372601,e:-80570983,ix:7},{s:32528289,w:-117419128,n:33267398,e:-116926117,ix:8},{s:33255915,w:-112407989,n:33706062,e:-111524963,ix:11},{s:31755028,w:-111233825,n:32623183,e:-110727081,ix:14},{s:38903858,w:-87912597,n:40603526,e:-85281372,ix:17},{s:32537551,w:-97627258,n:33144450,e:-96193542,ix:20}]}
4,traffic:{bounds:[{s:36880860,w:-123556517,n:38677492,e:-121283388,ix:0},{s:33226755,w:-119242517,n:34879656,e:-117247509,ix:2},{s:32167236,w:-97522121,n:33388946,e:-96069044,ix:8},{s:29221411,w:-95990706,n:30299390,e:-94748951,ix:10},{s:33245614,w:-84993665,n:34251728,e:-83783641,ix:11},{s:32980558,w:-112642287,n:33914806,e:-111522608,ix:12},{s:32320421,w:-117632103,n:33110939,e:-116692535,ix:14},{s:38304167,w:-90612932,n:38950954,e:-89785013,ix:16},{s:38836989,w:-84852024,n:39375978,e:-84157427,ix:22},{s:38309558,w:-121835862,n:38848547,e:-121146396,ix:24},{s:38833546,w:-94930306,n:39372535,e:-94235744,ix:25},{s:29155076,w:-98803952,n:29694065,e:-98185137,ix:26},{s:35902370,w:-115473565,n:36441360,e:-114805879,ix:27},{s:28294050,w:-81679208,n:28833040,e:-81065525,ix:31},{s:29684310,w:-90388702,n:30223299,e:-89766620,ix:33},{s:34881558,w:-90379430,n:35420547,e:-89720226,ix:35},{s:29998136,w:-98054906,n:30537126,e:-97430845,ix:36},{s:30060762,w:-81963562,n:30599752,e:-81339102,ix:38},{s:37985442,w:-86109574,n:38524431,e:-85423194,ix:39},{s:34953025,w:-81167441,n:35492015,e:-80507657,ix:42},{s:35898587,w:-87111421,n:36437576,e:-86443767,ix:43},{s:35199588,w:-97853495,n:35738578,e:-97191695,ix:44},{s:31952018,w:-111288323,n:32491007,e:-110651215,ix:45},{s:31489703,w:-106804478,n:32028692,e:-106170573,ix:48},{s:33250782,w:-87134877,n:33789771,e:-86488367,ix:49},{s:34814680,w:-106978029,n:35353669,e:-106319366,ix:51},{s:35880282,w:-96327146,n:36419271,e:-95659649,ix:57},{s:36470939,w:-120122285,n:37009929,e:-119449686,ix:58},{s:33349843,w:-118001092,n:33888833,e:-117353839,ix:62}]}
5});
6
</script>
<script id="_9fbw75xrz" type="text/javascript" charset="UTF-8" src="http://maps.google.com/maps?spn=3.329728,25.905762&z=6&key=ABQIAAAAs9dcsWbbZEWT6IvNCMoH7BSEkgDlNpuQ8hYNJ6DISwGIqkxfgBSsICsDOo_i02Xr_AijoU7Ytm0KBw&vp=35.85344,-118.366699&ev=p">
</script>



JSONPによるリアルタイム更新を、cometもしくは、ポーリングによって実現している。
リアルタイムと唄っているが、完全にサーバーサイドからのイベント通知だろうか?その場合は、更新数が多くなった時にクライアントの画面の更新が追いつかないので、おそらくは一定時間毎のポーリングだろう。実際にサイトを見ても、ほぼ、5〜6秒毎に画面が切り替わる。ソースコードを追えば分かるかもしれないが、ObfuscateされたGoogleのJavascriptを読むのは、体力を消費するので辞めておくが、ポーリングとみて間違いなさそうだ。

私が気になるのは、サーバーサイドからのイベント通知(つまりCOMET)かポーリングかという技術的な問題ではない。最も重要な事は、これまで画面の遷移や制御は、ユーザ主導型だったが、ユーザが見ている画面の制御がユーザ + サーバによる制御に移行しようとしている事だ。これは大きな論点になるだろう。このブログで、Young risk taker.: 2008年のWEBはハイブリッド型にという記事を書いたが、この記事中ではWHATWGのHTML5ドラフトとGoogleの取り組み、私の予想などを書いた。

サーバー側から画面の制御ができる事と画面の向こう側に人気を感じさせる試みは、技術的には非常に興味深い。コンピュータリソースだけでなく、WEBもクラウド型になり、これまでサーバ/クライアントアーキテクチャだったのが、サーバーを介したクライアント to クライアントへとなり、ますます複雑なネットワークへと変化していくだろう。

サーバー側に画面の制御が委ねられている事は、ユーザの興味が変化した瞬間に広告をそれに適したものにリプレイスする事ができるかもしれないし、よりユーザの利便性を追求する事につながると私は信じている。私は、楽観主義者だが、サーバー側からのクライアントの制御(自由にコードを送り込み実行させることのできる)は、大きな嫌悪感をユーザに与え、一部のコラムニストや団体が騒ぎたてるだろうと考えている。Gmailの時に、メールスキャニングで騒いだのと同じように。

結局は、この技術が人々の生活に無くてはならないイネビタブルズ(キラーアプリ)を生み出すかどうか?で全ては決まるだろう。技術の可能性だけに溺れ、中途半端でユーザ中心主義でない、アプリケーションは死滅する。ブラウザを通じて表示されている画面は、無機質なプログラムではなく、向こう側には、人がいて、人と人のコミュニケーションをより快適にするためにこれらの技術が開発されていくだろう。

私も昨年から、ユーザ + サーバによる制御される画面のユーザエクスペリエンスと、それらの技術の基盤の上に構築されるアプリケーションについて頭をひねってきた。21世紀を生きる私達は、常に大きなチャンスが横を通り過ぎている状態の中にいる。しかし、大抵の人はチャンスに気づかない。流れに身を任せる。

「世界中のあらゆる情報を探し出せる、高機能な検索エンジンを作りたい」とユーザ(創業者自身)のニーズを中心に添えた、GoogleがGoogleブランドを構築したように、21世紀に存在する、変革しなければいけない、もしくはまだ議論されていない大きな問題は何なのか?を常にユーザ中心に考えて実行に移せば、21世紀史における重要人物となれるかもしれない。

参考

  1. Making changes
  2. Google Maps Turns Recent Edits Into Voyeuristic Experience
  3. Google Gets Voyeuristic, Shows the World Some Hardcore Pornography in Real Time

Saturday, January 19, 2008

[Javascript] "うごかせるモノである”事をアフォードするiPhone firmware1.1.3のEffect.wobbleを実装した。(Effect.illuminateと組み合わせたデモ)

iPhone / iPod touchユーザの皆さんは、Firmware1.1.3にバージョンアップしましたか?
Jail Breakが面倒なので、私のiPhoneは1.1.1のままですが、1.1.3にアップグレードしたiPod touchを入手したので、作りました。

「あるモノが動かせる」事をユーザに一番早く理解してもらうにはどうすれば良いか?
おそらく、ブラウザ上で一番使われているのは、CSSでマウスカーソルをcursor:moveにする方法だろう。それなりにコンピュータに触れているユーザならば、このアイコンが出れば「ん?動かせるのかな?」と気になる。しかし、マウスポインタの概念が存在しないiPhone / iPod touchではこの方法は使えない。iPhone / touch では、より動物の持つアフォーダンスに働きかける方法を採用している。


Apple Patent shows details of iPhone 1.1.3 firmware

「(突然)動く、点滅する」これらのアクションは、人間の注意を引きつける。私達の住む世界でも、信号、ハザードのような注意を引きつける必要があるものは、点灯->点滅へと遷移する事が多い。では、「あるものが動かせる物である」ということをアフォードするにはどうするのがいいだろうか?
ネコでもイヌでも、今まで静止していたものが突然、ぐらぐらと揺れ始めたら、警戒するだろう。人間も同じで、それまで静止していた物がバランスを崩し、ぐらぐらとしだしたら、その不安定な状態 == 何か動かせるものと直感的に感じ取ることができる。

Effect.wobble and Effect.illuminate demo


あるオブジェクトがぐらぐらと揺れるエフェクト(Effect.wobble)をJavascriptで実装した。以前作成した、Effect.illuminateと組み合わせたデモを作った。(excanvasを使えば、IEでも動作するかもしれませんが、IEが無いのでテストできていません。Firefox 2.x or Safariで動作確認しています。)


「早速、CPUが高速回転し始めましたか?」
仕組みは、それほど複雑ではないが少しトリッキーな事をしている。
  1. このエフェクトは、画像にしか適用できない。
  2. Effect.wobberが最初に呼び出された時点で、canvasを作成する。(一度作成されたcanvasはキャッシュ)
  3. canvasのサイズは、画像の斜辺の長さの正方形に設定し、canvasの中心を画像の中心と合わせる。(キャンバスが画像と同じサイズでは、回転させた時に表示されない部分が出るため。)
  4. canvasに画像をレンダリングし、元の画像はdisplay:noneにするのではなく、別の画像(spacer.gif)を読み込ませて強制的に、元画像と同じサイズに設定する。この方法でないと、画像を隠した段階でブラウザにより強制的に再レイアウトされるため、レイアウトが崩れる。
  5. canvas上の画像は、指定した角度(デフォルトでは1.5度)に、時計回り、反時計回りを交互に繰り返す。


How to wobbling images
使い方は簡単で、三つのオプションを渡せる。degreeは回転させる角度を指定する(デフォルトは、1.5°)。durationは何秒間ぐらぐらさせるか。デフォルトでは、永続的にエフェクトが実行される。秒数を指定する事で経過秒後にエフェクトが停止する。freequencyは、ぐらぐらさせる関数を呼び出すインターバル値を指定するパラーメタでミリ秒(デフォルト 90 msec)で指定する。(秒に統一した方がよい)


$$('img.wobble').each(function(img) {
img.observe('mouseover', function(event) { event.target.wobble({duration: 2, degree: 1.5}) });
});


Effect.illuminate


このエフェクトは、iPhone / iPod touchのロック解除前に表示されるエフェクト。文字列の上をスポットライトが左から右に照らすように動く。仕組みは次のようになっている。
  1. 全ての文字をspanタグで置換
  2. 順番にスポットライトの色(オプションで指定可能)でstyleを変更していく。
  3. 最後まで照らし終わったらまた最初の文字からスタートする。


Source code
興味があればどぞ。

// The MIT License
// Copyright (c) 2008 Rakuto Furutani, All Rights Reserved.
// mail: rakuto at gmail.com
// blog: http://rakuto.blogspot.com/

// Add arbitrary methods as HTML#{tag}Element instance methods
// See: http://rakuto.blogspot.com/2008/01/javascripts-element.html
Element.addMethodsByTag = function(aTag, aMethods) {
if(aMethods.constructor == Object) {
var methods = new Object();
methods[aTag.toUpperCase()] = aMethods;
Object.extend(Element.Methods.ByTag, methods);
Element.addMethods();
}
};

// Effect object
var Effect = Effect || {};
Object.extend(Effect, (function() {
// NOTE: You need to replace it.
var SPACER_PATH = '/images/spacer.gif';

// The canvas will be used for wobbling effect
function createCanvas(element)
{
var attrs = {width: element.width, height: element.height};
var style = {zIndex: 0, display: 'none', position: 'absolute'};
var ctx, canvas = Element.extend(document.createElement('canvas'));
canvas.writeAttribute(attrs).setStyle(style);
(element.parentNode || document.body).appendChild(canvas);
if(ctx = canvas.getContext('2d')) {
ctx.drawImage(element, 0, 0);
return canvas;
} else {
throw new Exception('Effect.wobble requires fecture of HTMLCanvasElement.');
}
}
function saveOriginal(element)
{
var width = element.width, height = element.height;
element._originalSrc = element.src;
element.src = SPACER_PATH;
element.width = width;
element.height = height;
element.setStyle({width: width, height: height});
return element;
}
function restoreOriginal(element)
{
element.src = element._originalSrc;
return element;
}
// Called when stop the wobbling effect
function teardown(element)
{
element._wobbling = false;
restoreOriginal(element).show()._canvas.hide();
}

return {
wobble: function(element, options) {
if(element._wobbling) return;
var image, radian, ctx, clockwise = -1;
var width = element.width, height = element.height, offset = element.cumulativeOffset();
var sidelen = Math.ceil(Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)));
var dx = Math.ceil((sidelen - width) / 2), dy = Math.ceil((sidelen - height) / 2);
// Default options
options = Object.extend({
degree: 1.5,
frequency: 90,
duration: 0
}, options);
element._canvas = element._canvas || createCanvas(element);
element._canvas.writeAttribute({width: sidelen, height: sidelen}).setStyle({
left: offset[0] - dx + 'px', top: offset[1] - dy + 'px'}).show();
ctx = element._canvas.show().getContext('2d');
ctx.translate(dx, dy);
saveOriginal(element)._canvas.show();
radian = Math.PI / 180 * options.degree;

// Start the effect animation
image = new Image();
image.src = element._originalSrc;
element._wobbling = setInterval(function() {
ctx.clearRect(-dx, -dy, sidelen, sidelen);
ctx.save();
ctx.translate(Math.ceil(width / 2), Math.ceil(height / 2));
ctx.rotate((clockwise *= -1) * radian);
ctx.drawImage(image, -(width / 2), -(height / 2));
ctx.restore();
}, options.frequency);

// If option.duration isn't zero, then the effect will be stopped
// after a lapse of option.duration secounds.
if(options.duration != 0) teardown.delay(options.duration, element);

return element;
},
// Stop the wobbling effect
stopWobble: function(element) {
if(element._wobbling) teardown(element);
return element;
},
illuminate: function(element, options) {
var effect = arguments.callee;
if(element.tagName) {
// Save the original style
with(effect) {
color = element.getStyle('color');
innerHTML = element.innerHTML;
}
$A(element.childNodes).each(function(node) {effect(node, options)});
return;
}

// It's only executed when node is text node.
options = Object.extend({
color: '#ffffff',
size: 4,
repeat: true,
interval: 70
}, options);

// The all characters is replaced because accessing the innerHTML property is pretty slow.
var parent = element.parentNode;
parent.innerHTML = $A(element.nodeValue).map(function(text) {
return ['<span style="color: ', this.color, '">', text, '</span>'].join('');
}).join('');

var self = this, started = false;
var turnOn = function(node) {node.style.color = options.color};
var turnOff = function(node) {node.style.color = self.color};
var restore = function() {parent.innerHTML = self.innerHTML};
var startTurnOffEffect = function(callback) {
started = true;
$A(parent.childNodes).inject(0, function(delay, node, idx) {
with({idx: idx}) {
setTimeout(function() {
turnOff(node);
if(callback['onEnd'] && idx == parent.childNodes.length - 1) callback['onEnd']();
}, delay);
}
return delay + options.interval;
});
};
// Start the effect
$A(parent.childNodes).inject(0, function(delay, node, idx) {
setTimeout(function() {
turnOn(node);
if(!started && idx > options.size) {
startTurnOffEffect({onEnd: function() {
restore();
if(options.repeat) effect(parent, options);
}});
}
}, delay);
return delay + options.interval;
});
}
};
})());

// Add some method as Element's instance methods
Element.addMethodsByTag('img', {
wobble: Effect.wobble,
stopWobble: Effect.stopWobble
});
Element.addMethods({
illuminate: Effect.illuminate
});

Event.observe(window, 'load', function(event) {
// Start the wobbling effect
$('img.wobble').each(function(img) {
img.wobble();
});

$('img.wobbleOnMouseOver').each(function(img) {
img.observe('mouseover', function(event) { event.target.wobble({duration: 2}) });
});

// Start the Illuminate Effect
$('nuts').illuminate();
});



P.S.
ドバイの夜景が綺麗だ。ドバイに行きたい。最後の夜景は大阪の梅田。
久しく夜景見てないなぁ。モテモテモテるコードが書けるようになりたい。

[Javascript] iPhone's two pretty cool effects - Effect.wobble and Effect.illuminate

This entry for Japanese is here.

I've impemented two interesting effects in iPhone, Effect.wobble and Effect.illuminate. You can see the wobbering effect in iPhone / iPod touch's firmware 1.1.3. The wobbering effect is able to inform users about draggable object. A following movie is demo of the wobbering effect.



You can see the Effect.illuminate on the top page in iPhone and iPod touch. You've seen "slide to unlock"'s effect, haven't you? The Effect.illuminate is just it.

Demo of the Effect.wobble and Effect.illuminate


These effects are resource-hungry. This is demo, and Firefox 2.x and Safari only. Maybe Effect.wobble will be working correctly on IE with ExplolerCanvas, but I haven't tested yet.


How to use it

// Effect.wobble
$('img.wobble').each(function(img) {
img.observe('mouseover', function(event) { event.target.wobble({duration: 2, degree: 1.5}) });
});

// Effect.illuminate
$('illuminated_msg').illuminate({color: '#fff'});


How to work - Effect.wobble
  1. The mecanism is simple, but there are some tricky technics.
  2. The Effect.wobble can only call for IMG element.
  3. Create a canvas element for wobbering effect when the effect will be called at first.
  4. The width and height of canvas are same that an image's length of oblique line.
  5. Fit in position of canvas element in order to align image's center position.
  6. Draw an image on canvas, and the original image never set 'style="display: none"'. If display property will be none, then the layout will be broken. So we set src property with dummy image. (spacer.gif)
  7. Run the effect to rotate image drawing on canvas. The effect will change the clockwise rotation and anticlock rotation alternately.


How to work - Effect.illuminate
  • At first, all characters is replaced with span element.
  • Start the turn on effect and turn off effect ATST.


Other demo of Effect.illuminate




Source code

// The MIT License
// Copyright (c) 2008 Rakuto Furutani, All Rights Reserved.
// mail: rakuto at gmail.com
// blog: http://rakuto.blogspot.com/

// Add arbitrary methods as HTML#{tag}Element instance methods
// See: http://rakuto.blogspot.com/2008/01/javascripts-element.html
Element.addMethodsByTag = function(aTag, aMethods) {
if(aMethods.constructor == Object) {
var methods = new Object();
methods[aTag.toUpperCase()] = aMethods;
Object.extend(Element.Methods.ByTag, methods);
Element.addMethods();
}
};

// Effect object
var Effect = Effect || {};
Object.extend(Effect, (function() {
// NOTE: You need to replace it.
var SPACER_PATH = '/images/spacer.gif';

// The canvas will be used for wobbling effect
function createCanvas(element)
{
var attrs = {width: element.width, height: element.height};
var style = {zIndex: 0, display: 'none', position: 'absolute'};
var ctx, canvas = Element.extend(document.createElement('canvas'));
canvas.writeAttribute(attrs).setStyle(style);
(element.parentNode || document.body).appendChild(canvas);
if(ctx = canvas.getContext('2d')) {
ctx.drawImage(element, 0, 0);
return canvas;
} else {
throw new Exception('Effect.wobble requires fecture of HTMLCanvasElement.');
}
}
function saveOriginal(element)
{
var width = element.width, height = element.height;
element._originalSrc = element.src;
element.src = SPACER_PATH;
element.width = width;
element.height = height;
element.setStyle({width: width, height: height});
return element;
}
function restoreOriginal(element)
{
element.src = element._originalSrc;
return element;
}
// Called when stop the wobbling effect
function teardown(element)
{
element._wobbling = false;
restoreOriginal(element).show()._canvas.hide();
}

return {
wobble: function(element, options) {
if(element._wobbling) return;
var image, radian, ctx, clockwise = -1;
var width = element.width, height = element.height, offset = element.cumulativeOffset();
var sidelen = Math.ceil(Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)));
var dx = Math.ceil((sidelen - width) / 2), dy = Math.ceil((sidelen - height) / 2);
// Default options
options = Object.extend({
degree: 1.5,
frequency: 90,
duration: 0
}, options);
element._canvas = element._canvas || createCanvas(element);
element._canvas.writeAttribute({width: sidelen, height: sidelen}).setStyle({
left: offset[0] - dx + 'px', top: offset[1] - dy + 'px'}).show();
ctx = element._canvas.show().getContext('2d');
ctx.translate(dx, dy);
saveOriginal(element)._canvas.show();
radian = Math.PI / 180 * options.degree;

// Start the effect animation
image = new Image();
image.src = element._originalSrc;
element._wobbling = setInterval(function() {
ctx.clearRect(-dx, -dy, sidelen, sidelen);
ctx.save();
ctx.translate(Math.ceil(width / 2), Math.ceil(height / 2));
ctx.rotate((clockwise *= -1) * radian);
ctx.drawImage(image, -(width / 2), -(height / 2));
ctx.restore();
}, options.frequency);

// If option.duration isn't zero, then the effect will be stopped
// after a lapse of option.duration secounds.
if(options.duration != 0) teardown.delay(options.duration, element);

return element;
},
// Stop the wobbling effect
stopWobble: function(element) {
if(element._wobbling) teardown(element);
return element;
},
illuminate: function(element, options) {
var effect = arguments.callee;
if(element.tagName) {
// Save the original style
with(effect) {
color = element.getStyle('color');
innerHTML = element.innerHTML;
}
$A(element.childNodes).each(function(node) {effect(node, options)});
return;
}

// It's only executed when node is text node.
options = Object.extend({
color: '#ffffff',
size: 4,
repeat: true,
interval: 70
}, options);

// The all characters is replaced because accessing the innerHTML property is pretty slow.
var parent = element.parentNode;
parent.innerHTML = $A(element.nodeValue).map(function(text) {
return ['<span style="color: ', this.color, '">', text, '</span>'].join('');
}).join('');

var self = this, started = false;
var turnOn = function(node) {node.style.color = options.color};
var turnOff = function(node) {node.style.color = self.color};
var restore = function() {parent.innerHTML = self.innerHTML};
var startTurnOffEffect = function(callback) {
started = true;
$A(parent.childNodes).inject(0, function(delay, node, idx) {
with({idx: idx}) {
setTimeout(function() {
turnOff(node);
if(callback['onEnd'] && idx == parent.childNodes.length - 1) callback['onEnd']();
}, delay);
}
return delay + options.interval;
});
};
// Start the effect
$A(parent.childNodes).inject(0, function(delay, node, idx) {
setTimeout(function() {
turnOn(node);
if(!started && idx > options.size) {
startTurnOffEffect({onEnd: function() {
restore();
if(options.repeat) effect(parent, options);
}});
}
}, delay);
return delay + options.interval;
});
}
};
})());

// Add some method as Element's instance methods
Element.addMethodsByTag('img', {
wobble: Effect.wobble,
stopWobble: Effect.stopWobble
});
Element.addMethods({
illuminate: Effect.illuminate
});

Event.observe(window, 'load', function(event) {
// Start the wobbling effect
$('img.wobble').each(function(img) {
img.wobble();
});

$('img.wobbleOnMouseOver').each(function(img) {
img.observe('mouseover', function(event) { event.target.wobble({duration: 2}) });
});

// Start the Illuminate Effect
$('nuts').illuminate();
});

Wednesday, January 16, 2008

[Ruby] Ruby-GetText-1.10.0をRails 2.x系erubyテンプレートに対応させるパッチ

むとうさん作の、Ruby-GetText-1.10.0をRails 2.0.2で使っていたのだが、GetText.update_pofiles(内部では、GetText.rgettextを呼び出し)によるmsgidの抽出がうまく動作しなかったので、ソースを見た所、パース対象のファイルの拡張子で適用すべきパーサーを識別しているが、lib/gettext/parser/erb.rbでは、.rhtmlのみ適用となっていたため、Rails 2系の*.html.erb拡張子はパース対象に入っていなかった。.erb拡張子もerubyパーサーに追加するパッチです。

/gettext-10.0.0/lib/gettext/parser/erb.rbに適用して下さい。


% cd RUBY_GETTEXT_INSTALL_PATH
% sudo patch lib/gettext/parser/erb.rb < add_erb_extension.patch


add_erb_extension.patch

*** erb.rb.org 2008-01-16 12:53:36.000000000 +0000
--- erb.rb 2008-01-16 12:53:58.000000000 +0000
***************
*** 15,21 ****
module GetText
module ErbParser
@config = {
! :extnames => ['.rhtml']
}

module_function
--- 15,21 ----
module GetText
module ErbParser
@config = {
! :extnames => ['.rhtml', '.erb']
}

module_function


追記
確認したら、最新のCVSでは対応済みとの事です。パッチを当てる以外にもRake taskでGetText::ErbParser.initを呼び出せば対応可能なので、こちらの方がよさそうです。

# For new Erb template of Rails 2.x
GetText::ErbParser.init(:extnames => ['.rhtml', '.erb'])

desc "Update pot/po files."
task :updatepo do
require 'gettext/utils'
GetText.update_pofiles('myapp',
Dir.glob("{app,config,components,lib}/**/*.{rb,erb,rhtml}"),
'myapp 1.0.0')
end

desc "Create mo-fileds"
task :makemo do
require 'gettext/utils'
GetText.create_mofiles(true, "po", "locale")
end

Tuesday, January 15, 2008

iPhone は3月にNTT DoCoMoから登場?(via MacWorld 2008 Keynote)

16日早朝の2:00〜のKeynoteの内容が明らかに?
Macworld2008: Steve Jobs keynote speech leaked?

iPhone is coming to Japan in March with NTT DoCoMo


家族全員、ソフトバンクに変更せずにすみそうだ。

iPhone / iPod touch SDKについても書かれています。ソースコードをAppleに送り、Appleが承認した物だけが、iTunes Storeから利用できるようです。アプリケーションの価格は、無料〜$6.99, Widgetは無料〜$2.99. 開発者は、70%のレベニューシェアを取れるとの事。30%をAppleが持っていく所は、さすがといった所か。

- Demonstration of exporting from XCode 3 to iTunes Store
- Submits source code to Apple for validation (make sure that people aren't abusing the system, prevent malware and viruses)
- If using microphone or GSM, iPhone only; otherwise, available for both iPhone and iPod Touch
- Apps can be free or up to $6.99; Widgets free or up to $2.99
- Developers recieve 70% of revenue for their products
- Licensed under Apple Mobile Software License
- Can download wirelessly from iTunes Wi-Fi Store or docked to computer from iTunes Store

Monday, January 14, 2008

Macworld 2008いよいよ開幕。時代がNewtonに追いついた。

Macworld 2008でのSteve Jobs氏のKeynoteが現地時間、15日9:00〜(日本時間 16日 2:00〜)始まる。今年もAppleは、多くのマカーを楽しませてくれるだろうか?

両親がグラフィックデザイナーの我が家は、家族全員、昔からずっとマカーだった。
Macを購入して触り始めたのが、16年ぐらい前で、以来ずっと、キッドピックス、HyperCard、Illustorator、Photoshopといったソフトでコンピュータに触れてきた。家では、Windowsの悪口や「いよいよ、Apple潰れるか?」「(Mac miniがヒットしたから)何とか乗り切れるか?」といった会話が昔から繰り返されてきた。Newtonが発売された時には、「Newtonは時代が早すぎる。」 そしてNewtonが失敗し、「Newtonは時代が早すぎた。」という会話がしばしば出ていた。


Source: Macbook air on Flickr
※ これはイメージ画像です。フォントが違うので、念のため。

Macbook air, nano or thin?, タブレットPC, iPhone SDK, iTunesでムービーレンタル(DisneyとFox)などが発表されるのではと噂されている。Jobs氏のスピーチに合わせたiPhone用ビンゴWEBアプリまで用意されている。(MWSF 2008 Keynote Bingo for the iPhone) 果たして、どのラインでビンゴ!できるのだろうか?

Macbook air, nano, thinのスペックが色々とリークしているので、抜粋。
果たして、Macbook airは正式名称なのか?airは、WiiMax搭載を暗示しているのだろうか?ストレージは?バッテリーは?色々と気になる。

via Macworld.Ars: Rumors ad infinitum

  • no CPU hints, but don't expect a pricey ULV CPU running around 1 GHz
  • 12" or 13" widescreen, LED-backlit display
  • 32, 64, or maybe even 128GB solid-state drive
  • an external optical drive
  • less than 1" thick
  • under 3 pounds
  • no hints on battery life, but comparable PC laptops run at least six hours
  • around $1599


via Apple sub-notebook to retail for $1500, 3G iPhone by June - report
  • New 3G iPhone by late May, early June 2008.
  • iPod Touch is selling better than expected this holiday season, with one of Apple's manufacturers increasing production to about 5.1 million units this quarter.
  • New sub-notebook MacBook Pro.
  • New sub-notebook 50 percent thinner.
  • New sub-notebook employs flash memory instead of hard drive.
  • New sub-notebook to retail for $1500.




PowerBook 2400が発売されて10年後の今年2008年にMacbook air(仮)が出るとは、予想できただろうか?iPhoneにも、Newtonで採用された様々な革新的なユーザインタフェースが受け継がれているが、ようやく時代は、Newtonに追いついたのかもしれない。今度は、スカリー氏の陣頭指揮ではなく、Jobs氏によるNewton. 明日は、Appleからどのようなプレゼントがあるのか楽しみだ。

But, one more things...

iMacにUltra Thin MacbookBookがドッキングされる事を連想させる特許出願に関する記事から画像を一枚。決して、Docking Stationも夢の話ではない。(See: United States Patent Application 20080002350 )


Source: Apple patents idiosyncratic docking station

References:
MWSF 2008 Keynote Bingo for the iPhone
Macworld 2008: How can Steve Jobs top the iPhone?
New report claims 24-hour, variable price iTunes rentals
Macworld.Ars: Rumors ad infinitum
United States Patent Application 20080002350 (ドッキングステーションに関する特許)

[Javascript] Prototype.js 1.6.0 - Event.fireでNative DOM Eventを呼び出し可能にする

Javascriptの話題が続いてます。

Changeset 7835 - prototype: Namespace all custom event names to avoid conflicts with native DOM events.

上記Changesetで、Native DOM Eventとの名前衝突を避けるために、コロンで区切るネームスペースが導入された & Native DOM Eventの呼び出しが削除された?ため、Native DOM Eventが呼び出せない。[Javascript] クロージャを利用したイベントリスナの登録のように、クロージャー + 無名関数でイベントリスナを登録すると、スコープの外部から、現在選択中のボックスを取得するとなると、各ノードを走査して調べる事になりますが、これは美しくない。


Prior to this change, Prototype treated only the event names present in the Event.DOMEvents array as native DOM events. Now, Prototype looks for the presence of the namespace delimiter—a single colon—to determine whether you’re observing a custom event or a native event.(*1)

RC1の時点では、Event.DOMEventsの配列中の値を指定する事でNative Eventを呼び出せたみたいだけど、上記のChangesetを見る限り、1.6.0ではこれ無くなってるね。

で、Native DOM Eventを呼び出し可能な、Event.fireを再定義する事にした。
ボックスをクリックした時に呼び出されるイベントハンドラを、ボタンをクリックする事でも呼び出すようにしたサンプル。
Fire the native DOM Event


テスト

// A event listener
$('selection').observe('click', (function() {
var SELECTED_CLASS_NAME = 'selected';
var selected;
return function(event) {
if(selected) selected.removeClassName(SELECTED_CLASS_NAME);
event.target.addClassName(SELECTED_CLASS_NAME);
selected = event.target;
};
})());

// Fire the DOM Event dynamically
$('container').observe('click', function(event) {
if(event.target.tagName.toUpperCase() == 'INPUT') {
var choiceIdx = parseInt(event.target.id.replace('btn_', ''));
$('box' + choiceIdx).fire('click', {});
}
});



DOM 3 Eventならイベントオブジェクトを作成して、イベントを呼び出します。
Event.fireの第三引数には、Eventオブジェクト作成時のパラメータをハッシュ(Object)で指定できます。もし、カスタムイベントの場合は、通常のEvent.fireが呼び出され、Event.fireの第三引数として渡されます。最後に、Event#fireを新しく定義した、Event.fire_with_native_eventsで置換しておわり。

※今、手元にIEが無いので、IEでのテストが出来ていません。(あとで)

サンプル

$('text_field').fire('keydown', {charCode: Event.KEY_RETURN});


実装

Object.extend(Event, (function() {
// DOM Level 3 events
var W3C_MOUSE_EVENTS = $w('click mousedown mousemove mouseout mouseup');
var W3C_KEYBOARD_EVENTS = $w('keydown keyup keypress');
var W3C_BASIC_EVENTS = $w('abort change error load reset resize scroll submit unload');

function createDOMEvent(aEventName, aEventParams)
{
var event;
if(W3C_MOUSE_EVENTS.include(aEventName)) {
var p = Object.extend({
bubble: true,
cancelable: true,
view: window,
detail: 0,
screenX: 0,
screenY: 0,
clientX: 0,
clientY: 0,
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
button: 0,
relatedTarget: null
}, aEventParams);
if(document.createEvent) {
event = document.createEvent('MouseEvent');
event.initMouseEvent(aEventName, p.bubble, p.cancelable, p.view, p.detail, p.screenX,
p.screenY, p.clientX, p.clientY, p.ctrlKey, p.altKey, p.shiftKey, p.metaKey,
p.button, p.relatedTarget);
} else {
// TODO: IE
Object.extend(event, p);
event.eventType = 'on' + aEventName;
}
} else if(W3C_KEYBOARD_EVENTS.include(aEventName)){
var p = Object.extend({
bubble: true,
cancelable: true,
view: null,
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
keyCode: 0,
charCode: 0
}, aEventParams);
if(document.createEvent) {
event = document.createEvent('KeyboardEvent');
event.initKeyEvent(aEventName, p.canBubble, p.cancelable, p.view, p.ctrlkey, p.altKey, p.shiftKey, p.metaKey,
p.keyCode, p.charCode);
} else {
// TODO: IE
event = document.createEventObject();
Object.extend(event, p);
event.eventType = 'on' + aEventName;
}
} else if(W3C_BASIC_EVENTS.include(aEventName)) {
var p = Object.extend({
bubbles: true,
cancelable: true
}, aEventName);
if(document.createEvent) {
event = document.createEvent('HTMLEvents');
event.initEvent(aEventName, p.bubbles, p.cancelable);
} else {
// TODO: IE
event = doument.createEventObject();
Object.extend(event, p);
event.eventType = 'on' + aEventName;
}
}
return event;
}

return {
fire_with_native_events: function(element, eventName, eventParamsOrMemo) {
var event = createDOMEvent(eventName, eventParamsOrMemo);
if(event) {
document.createEvent? element.dispatchEvent(event) : element.fireEvent(event.eventType, event);
} else {
Event.fire(element, eventName, eventParamsOrMemo);
}
}
};
})());

// Replace an Element#fire
Element.addMethods({
fire: Event.fire_with_native_events
});


脚注
1. Prototype 1.6.0 RC1: Changes to the Class and Event APIs, Hash rewrite, and bug fixes

Sunday, January 13, 2008

[Javascript] クロージャを利用したイベントリスナの登録

時々、特定のイベント間で値を共有したい事があります。
以下の例は、選択されているボックスの状態をどこかに保存しておいて、別のボックスが選択されたら選択を取り消すサンプルです。選択されているエレメントの値を保存する場所としては、グローバル変数か、エレメントのプロパティ(IEで使用するためには、Element.extendされている必要がある。event.targetは、Element.extendされている。)がありますが、イベントリスナにクロージャーを利用すると名前空間も汚染されません。



Source Code
selected変数に、現在選択されているボックスをバインドしている。

document.observe('dom:loaded', function() {
$('selection').observe('click', (function() {
var SELECTED_CLASS_NAME = 'selected';
var selected;
return function(event) {
if(selected) selected.removeClassName(SELECTED_CLASS_NAME);
event.target.addClassName(SELECTED_CLASS_NAME);
selected = event.target;
};
})());
});

Thursday, January 10, 2008

Bill Gates' Last Day

Bill Gatesの最後のKeynote


UIEJを退社したので、CESに行く機会がなかったのだが、豪華なこのビデオは、過去のCESでのGatesの講演の中でも一番拍手が起こっているような気がする。Satoshi-sanや、Shun-sanからGatesの話やMicrosoftの企業文化など色々聞かせてもらったが、Gatesも年をとったなぁとしみじみと感じる。友達がCESに行っているので、着色料がどうだとか、農薬がどうだとか、ウダウダ言ってる温室育ちのジャパニーズには、食えたものではないと聞くREDVINESをお土産に頼んだ。Microsoftの人間が好んで食べるとか?(本当か?)

REDVINESの黒色は、タイアの味がするそうだ。

Tuesday, January 08, 2008

[Javascripts] Element.addMethodsByTag

This code requires prototype.js 1.6.x

特定のElementにメソッドを追加します。
以下の例は、canvasタグにキャンバス全体を初期化するメソッドを追加します。(HTMLCanvasElement#clear)


// Add arbitrary methods as HTML#{tag}Element instance methods
Element.addMethodsByTag = function(aTag, aMethods) {
if(aMethods.constructor == Object) {
var methods = new Object();
methods[aTag.toUpperCase()] = aMethods;
Object.extend(Element.Methods.ByTag, methods);
Element.addMethods();
}
};

Element.addMethodsByTag('canvas', {
clear: function(element) {
if(element.getContext) {
var ctx = element.getContext('2d');
ctx.clearRect(0, 0, element.getAttribute('width'), element.getAttribute('height'));
}
return element;
}
});



特定のHTML#{tag}Elementごとに、共有されるクラス変数を定義する事も出来ます。

Element.addMethodsByTag('canvas', (function() {
var someValue;
return {
// ...
};
}})());

[Javascript] Prototype.jsのEventオブジェクトを拡張する

prototype.js 1.6では、Element#observeで登録したイベントハンドラに渡されるEventオブジェクトが、Event.extendを呼び出したPrototype.js独自のEventオブジェクトで拡張されていますが、Eventオブジェクトに任意のメソッドを追加する方法です。

例えば、親ノードの座標からの相対座標を取得する、Event#relativeX, Event#relativeYをEventオブジェクトに追加する際は次のようになる。コードは単純だが、Event.addMethodsの引数に渡されるオブジェクトで定義されている要素が、eventを参照可能にするため、Event.extend内で再びメソッドを定義し直して、eventをスコープに加える必要がある。クロージャーを用いるメソッドの再定義は、関数のスコープを自由に変更できるので便利である。

※ Firefox 2.x, Safari 3で動作確認。IE未確認。


// Add arbitrary methods as instance methods of Event object
Event.addMethods = function(aMethods) {
var _extend = Event.extend;

Event.extend = function(event) {
var methods = Object.keys(aMethods).inject({}, function(m, name) {
Event.Methods[name] = aMethods[name];
m[name] = Event.Methods[name].methodize();
return m;
});
_extend(event);
Object.extend(Prototype.Browser.IE ? event : Event.prototype, methods);
return event;
};
};

// example
Event.addMethods({
relativeX: function(event) {
var origin = event.element().cumulativeOffset();
return event.pointerX() - origin[0];
},
relativeY: function(event) {
var origin = event.element().cumulativeOffset();
return event.pointerY() - origin[1];
}
});




イベントリスナ

Element.extend(node).observe('mousemove', function(event) {
console.log('pointerX: %d, pointerY: %d, relativeX: %d, relativeY: %d',
event.pointerX(), event.pointerY(), event.relativeX(), event.relativeY());
});

Sunday, January 06, 2008

WEBは"小さい"が、Google帝国が生まれた。Cloud Computingはどのようなビジネスを生み出すのか?

昨年中頃から、BusinessWeek, New York Times, Youtube (Google official channel)で、cloud computing, grab-spanning networkというフレーズが目立つ。Cloud Computingと言う言葉は新しい言葉ではないが、Google CEOのEric Schmidtがマーケティング効果を狙ってか、多用しているバズワードだ。(*1)

このブログでも、PCが無くなる話や、ハードウェアの結びつきが強くなる話を時々書いている。

We now have the return of the mainframe, and the mainframe is a set of computers. You never visit them, you never see them. But they're out there. They're in a cloud somewhere. (※2)


1970年代のアメリカで、それまで主流だったメインフレームに代わり、個人用途で使えるパーソナルコンピューターが登場し、現在、先進国では、一人一台以上のデバイスを持つのが当たり前となっている。しかし、2008年、我々はまたメインフレームの世界に戻る事になるだろう。あなたは、実際にそれを見た事も手にした事もなければ、コンピュータのある場所を訪れた事もない。そのメインフレームとは、大型汎用機ではなく、クラウド状のコンピューターネットワークのことだ。


Eric Schmidt talks about the importance of search, the impact of cloud computing, and the future potential of the web, followed by a Q&A with journalists, at Google Press Day in Paris on June 19, 2007.


クラウドコンピューティングを先立ってビジネス化した、米AmazonにGoogle, IBMの二大巨頭も続いている。昨年半ばから米GoogleのCEO Eric Schmidtも各国でCloud Computingの重要性と可能性を説き、また、IBMとパートナーシップを組み(*3)、Googleの基盤となるMapReduceといったGoogleライクな分散コンピューティングを支える技術の教育に力を入れている。この資料は、Google Code for Educators - Distribute Systemsから閲覧可能である。これは、WEBの次の一手がクラウドコンピューティングから始まり、GoogleがWEBを超えて社会のインフラに進展してきた時に向けての人材育成に他ならない。Yahoo, IBMは、Open Sourceの分散コンピューティングシステムであり、Map Reduce実装を持つHadoopの開発にも力をいれている。

AWS(Amazon Web Service)で利用可能なコンピュータークラウドは、Amazonの安定したインフラに魅力があるtq、Googleのコンピュータークラウドの魅力は、それだけではない。Googleが巨額を投資したインフラに加えて、MapReduce, GFS, Crawler, web searchといった技術やソフトウェアが利用可能になる。また、Googleのサーバは、3年毎に新しい物にリプレイスされるので、その恩恵に預かる事が出来る。


"There are only five computers on earth. Google, Yahoo, Microsoft, IBM, and Amazon." by Yahoo Research Chief Prabhakar Raghavan(※1)


「世界には5つのコンピューターしかない」事は、私達にとって不幸な事だろうか?
5社に対して、同様の事業で資金力の無いスタートアップが参加した所で全く歯が立たないだろう。しかし、この全く新しいインフラを使う事は、起業する者に多くの可能性を与え、全く新しいビジネスをたくさん生み出す事になるだろう。

"The Web is tiny. We'll be laughing at how small the Web is." And yet, if this "tiny" Web was big enough to spawn Google and its empire, there's no telling what opportunities could open up in the giant clouds.


WEBは小さいが、Google帝国を生んだ。Cloud Computingは、どのようなビジネスを生み出すだろうか?
ハードディスクレコーダーで録画した映像が、どこにあるかもわからないGoogleのデータセンター上に保存され、あらゆる場所/デバイスから閲覧できる。でも、あなたの手元に物理的に存在している訳ではない。携帯で購入した音楽が自宅のオーディーオコンポから流れ出す。カメラで取った画像が、アップロードされ、Google Earthと結びつき、時系列で保存され検索も自由にできる。人々がデータのある場所、どんなサービスを使っているかを意識せずに、あらゆる物事を直感的に操作できるようにするために、全てのインフラ、多様なサービス、多数のデバイスがつながり、Cloud Computingを形成する。

WEBのアーキテクチャは、昔は単純なC/Sアーキテクチャであったが、サービスはAPIを公開し、サーバとサーバは、複雑に結びつきマッシュアップされるようになった。OpenID, OAuthは、サーバ間の結合度が上がった副産物として生まれた物である。次は、クライアントとクライアントが複雑に結びついてくるだろう。複雑に絡み合ったネットワークをCloud Computingと呼んでもいいのではないだろうか?

アメリカ渡らないとね。

関連記事
World Wide Living とHardware Mashupの世界
2008年のWEBはハイブリッド型に

脚注 & 参考
  1. Google CEO’s new paradigm: ‘cloud computing and advertising go hand-in-hand’
  2. Google and the Wisdom of Clouds
  3. Google and IBM Announce University Initiative to Address Internet-Scale Computing Challenges
  4. Google's CEO on the Power of Clouds
  5. Google Gets Ready to Rumble With Microsoft
  6. Google Code for Educators - Distribute Systems

Thursday, January 03, 2008

2008年のWEBはハイブリッド型に

あけましておめでとうございます。

今年は「P2Pのハイブリッド型アーキテクチャ」に似たアーキテクチャを取るアプリケーションが登場し、「クライアント間の結びつき」がますます強くなるだろう。「ブラウザ上での並列プログラミングやブラウザをノードに見立てたP2Pプログラミング」を可能にする技術に、今年は大きな動きがありそうだ。私は、今年はCometが鍵になるのではないかと考えている。

現在、ブラウザだけではできない事は、

  1. クロスドメイン間のメッセージ通信
  2. マルチスレッドのアプリケーション構築
  3. ストレージへのデータ保存
  4. Serverからのイベントの通知


1はClient to Clientのメッセージパッシング、4はServer to Clientのイベント通知である。
これらの制約を超えれば、ブラウザ上のプラットフォームはますます強力になる。

Cross-document Messaging


クロスドメイン間のメッセージ通信は、WHATWGがHTML5 で提案しているpostMessageと同様の仕組みをGoogleが実装している。

HTML5のCross-document messagingは、DOM2 イベントモデルと似た方法でメッセージ受信時のリスナを登録する。リスナに渡されるMessageEventオブジェクトには、data, domain, uri等のプロパティを含むので、CSSを防げる。


var o = document.getElementsByTagName('object')[0];
o.contentWindow.postMessage('Hello world');

document.addEventListener('message', receiver, false);
function receiver(e) {
if (e.domain == 'example.com') {
if (e.data == 'Hello world') {
e.source.postMessage('Hello');
} else {
alert(e.data);
}
}
}



GoogleもWHATWGのHTML5のpostMessageと似たメッセージングAPIをGoogle Gearsで提案している。Messaging APIは、portオブジェクトを作成して、listenする。sendMessageでテキストメッセージを送信する。

Cometサーバを利用すれば簡単にクロスドメインの非同期メッセージングを実装できる。ShootingStarを使用してMessaging APIを作った際は、create/subscriberモデルの非同期Messaging APIを次のように設計した。

var subscriber = createSubscriber('mychannel');
var publisher = createPublisher('mychannel');

subscriber.addEventListener(function(params) {
switch(params.event) {
case 'enter':
log(params.uid + ' entered');
break;
case 'leave':
log(params.uid + ' entered');
break;
}
});

// listener
subscriber.addMessageListener(function(params) {
log(params.uid + ' sent a message. timestamp = ' + params.timestamp);
});

// publish message
publisher.publishMessage({timestamp: (new Date()).getTime()}, tag);

dojo foundationのCometdは、Bayeux Protocolに基づき実装されている。dojoのクライアントサイドAPIもsubscriber / publisherアーキテクチャである。

dojo.require("dojox.cometd");
jQuery(function($) {
dojox.cometd.init("http://example.com/cometd");
dojox.cometd.subscribe("/slideshow/change", function(comet) {
$('#currentSlide').attr('src', comet.data.src);
});
});



subscriber / publisher型Messaging APIを使用して多数のクライアントが連携するアプリケーションを構築すると、コードが煩雑になり、デバッグも面倒になる。メッセージパッシングAPIではなく、DOM2 合成イベントと同様にリモートクライアント(別のブラウザ)のイベントやDOMをRemoteEventとしてイベントドリブンでコードを統一できれば美しい。

Server Sent Event(SSE)


Server-sent DOM events

現在、SSEを実装しているブラウザは、Opera 9だけである。その他のブラウザに実装される動きは、今の所ない。WHATWGのSSEは、HTMLEventSourceElementに対して任意のイベントハンドラを指定する。HTMLEventSourceElementは、srcプロパティ一つだけ持ち、Remote Event受信時のイベントハンドラをonmessageで指定できる。srcプロパティには、イベントを通知するURIを指定する。serverはイベントをContent-Type: application/x-dom-event-streamで返す。


Event: server-time
data: [time on the server]

Event: the-answer
data: 42


Event Filedには任意の値を指定できる。Interpreting an event streamで、指定可能なフィールドが列挙されている。指定した値でEventオブジェクトが作成されイベントリスナに渡される。

イベントハンドラはDOM2 イベントモデルと同じである。

<event-source id="event_source" src="/event/timestamp"/>




var es = document.getElementById('event_source');
es.addEventListener('server-time', function(event) {
log('SSE: A server-time event recieved.');
}, false);



Event Streaming to Web Browsers
でSSEを使用したWeb Chatを紹介している。
しかし、HTMLEventSourceElementをOpera9でテストした所、srcに指定したURIをポーリングする挙動を見せた。(ほぼ1秒間隔) Cometとは異なるようだ。(要調査)

Multi Treads


Javascriptは、完全にシングルスレッドで動作するが興味深い取り組みが二つ存在する。一つ目はあたかもマルチスレッドアプリケーションのように動作させる方法で、もう一つは並列プログラミングのパラダイムをJavascriptに持ち込む方法である。

Javascript 1.7では、yieldが実装されている。yieldを使用すれば関数の実行を任意の場所で実行/停止させることができるので、マルチスレッドアプリケーションのように振る舞う事が出来る。ただし、Javascript 1.7を実装しているのは、Firefox 2.x以上限定である。



余談だが、yieldを使えば、ジェネレータを作成し、setTimeoutで指定秒後に実行を再開する事ができるので、sleep関数を簡単に実装できる。Thread.jsを使った例は次の通り。


function spawn(generator)
{
return new Thread(generator).start();
}
function sleep(msec)
{
setTimeout((yield CONTINUATION), msec);
yield SUSPEND;
}
spawn(function() {
console.log('start');
yield sleep(3000);
console.log('passed 3 sec.');
});



複数のBackground Processを多数のWorkerと考え、並列プログラミングのパラダイムを持ち込む動きもある。Google GearsのWorkerPool Moduleは、並列プログラミングのパラダイムをブラウザ上に導入している。しかし、Google GearsのWorker Moduleは、計算途中の状態やDOMにアクセスできない。

※ Mac OS10.4.11乗のFirefox 2.0.0.11で、workerPoolにonmessageプロパティにイベントハンドラを登録するとブラウザが必ずクラッシュする。まだ、不安定。

Client Side Storage


DB APIを提供するものと値のset / getのみ提供するシンプルなStorageの二つの取り組みがある。


結論
WHATWGの興味深い新機能がブラウザに搭載されるまでには、まだしばらく時間がかかるし、ベンダー毎に取り組みも異なる。クロスドメイン非同期メッセージング, マルチスレッド(or Worker-Poolモデル), リモートイベントの処理は、Cometを利用すれば解決可能な問題である。今年は、Cometの年。Erlang OTPのプログラミングモデルや、並列プログラミングモデルをJavascriptに取り組む試みが進み、Remote Eventの操作も合成イベント(IEとの互換性を考え、dataavailable)で扱えるようなライブラリが登場するかもしれない。