jQueryのアニメーションとか勉強しました。Google+のサークルっぽくリストを表示できるjQueryプラグイン作ったよ。

深刻なjavascript力不足から徐々に抜け出せつつあるような気がしている今日この頃ですが、
jQueryプラグインというものを初めてリリースしてみました。


CircleList jQuery Plugin
http://kissrobber.appspot.com/circlelist_jquery_plugin/


↑のデモのような感じで、htmlのリストをGoogle+のサークルっぽい感じで表示する事ができるプラグインです。


最初はプラグインとかじゃなくて普通にGoogle+っぽくアイコンをうごかすページを作ってたんですが、
jQuery UIの機能(draggableとかselectableとか)と連携したくなった時にプラグインのルールにしたがって作っといた方が連携しやすい事に気がついて、
作り直して、
あ、これ公開できるじゃんって感じで公開してみました。


いろいろ学んだ事はあるんですが、

プラグインの作り方については、

http://www.ibm.com/developerworks/jp/web/library/wa-aj-jquery6/index.html
とか読んどけば十分


コード圧縮する事を考えてセミコロンを忘れないように付けろってやつは大変だった。
最近Rubyもやっているせいか、無意識にセミコロン抜けまくってて。
セミコロン忘れを簡単にチェックできる方法って無いのかな?

アニメーションとかは、

まぁ、テキトーにググったりすればいろいろ出てきます.
IEが言うこと聞かなかったりする事は相変わらずですのでこれはもう気合です。


CSS3のスタイルを使ったアニメーションは

  • moz-transformじゃなくてMozTransformとか注意です。

↓を参考に、
http://yurubu.org/transform/592
んだけど、CSS3のスタイルを使った場合はうまくいかないところがあったので、今回のプラグインからは結局外しましたが。


あと、今回のような変な動きをさせる場合は、
animateに渡すstep関数を自作するんですが、
どっちにしても第一引数には何かを渡す必要があって、今回はダミーで値を変化しないz-indexを指定しました。
IEの場合はautoの扱いに注意です。
他、書くと長くなるので省略しますが、いろいろめんどくさかったっす。

jQueryプラグイン登録について

ちゃんと登録できているのかもよく分かってないですが、
やたらと、読みにくい画像の文字を入力させるやつが出てきてウザかったです。
どんだけ人を疑ってんだよ。

ほか、なんか色々あったんですが、

忘れました。

とりあえず、個人的に一番学んだことはこれかな

rubyからX-FACEBOOK-PLATFORM認証を使ってFacebookチャットにログインする方法

Facebook Chat にサーバー側からパスワード認証じゃなくて、Facebook Connectを使ってXMPP Loginするサンプルです。
ここに置いたので、必要であれば勝手につかってください。
https://github.com/kissrobber/xmpp4r_facebook

1.X-FACEBOOK-PLATFORMの仕様で注意

DIGEST-MD5の実装を元にして作っただけで、DIGEST-MD5の仕様について詳しいわけじゃないですが、
なんか、X-FACEBOOK-PLATFORMはDIGEST-MD5とチャレンジのセパレータが違うっぽいです。

The mechanism starts with a server challenge, in the form of a common HTTP query string: an ampersand-separated sequence of equals-sign-delimited key/value pairs. The keys and values are UTF-8-encoded and URL-encoded. The query string contains two items: method and nonce.

https://developers.facebook.com/docs/chat/

X-FACEBOOK-PLATFORMはアンパサンド区切りですが、
DIGEST-MD5はcomma-separetedになっているので自作する時は注意です。

2.サーバーサイドでセッションキーを取得する方法

セッションキーはaccess_tokenからゲットできます。
access_tokenを「|」で三つに分割した真ん中の奴がsession keyです。

session_key = access_token.split('|')[1]

今日はSVGの勉強をしました。SVGおっぱい作ったよ

SVGに興味を持ちました。


んで、色々いじってたらなんか出来ました。
SVGおっぱい


SVGだと現在のメジャーバージョンのIEでは動かないので、
http://raphaeljs.com/
を試してみなのですが、動きはいまいちでした…
このライブラリを使うと、IEの場合だけVMLで描画してくれるんですが。


https://github.com/DmitryBaranovskiy/raphael/
だいぶ更新されていませんが、
バージョン2の方は精力的に活動しているっぽいので期待できるか
(バージョン2もちょっと試したけど、上手くいかないところがあったのでバージョン1にもどしました)


やっぱりIEも含めてヌルヌル動かすにはやっぱりFlashしかないかな
悩む。


あと、AndroidもなんとSVGダメっぽいですね。

って事で

まだ使うには色々微妙。

slim3を使ってて、ここはこうだったら良いのになーとか思ってた事

すごーくお世話になって、勉強させてもらったslim3に、あーだこーだ言うのはちょっと気が引けるけど、せっかくなので勇気を出して書いてみる。


と言っても、しばらくGAEから離れているのであんまり出てこないですが、とりあえず出てきた3つ。

1.ModelQueryでカーソルを扱えるIteratorを取りたい

今のモデルクエリにはカーソルを扱えるIteratorを取る手段が無いです(AbstractQueryにはありますが)
たとえば、
S3QueryResultIterator (extends ModelIterator)
みたいなクラスを返してくれる、
asQueryResultIterator()
みたいなメソッドがほしいなーとずっと思ってました。


カーソル使いたいなら今slim3にある、asQueryResultListじゃだめかって話ですが、
このメソッドはクエリの結果をすべてメソッド内で取得するので(このメソッドの挙動としては正しいです)、このメソッドの実行時間がクエリの結果件数に依存してしまいます。
何が問題かというと、
↓のAsync Queryの説明にあるように、

However, when you invoke PreparedQuery.asIterable(), PreparedQuery.asIterator() or PreparedQuery.asList(), both DatastoreService and AsyncDatastoreService immediately return and asynchronously prefetch results.

http://code.google.com/intl/en/appengine/docs/java/datastore/async.html

な挙動になってくれないんです。
つまり、asQueryResultListを使った場合、

This allows your application to perform work in parallel while query results are fetched.

って事にならないんです。
asIteratorってメソッドがありますが、これはカーソルを扱えない。


もうひとつ自分がこまった問題として、asQueryResultListだと
カーソル位置が結果セットの最後のデータ位置になっているので、
TQチェーンバッチ等で処理できるだけ処理して、次のTQに、続きのカーソルを渡すって感じの事ができない。(説明わかりにくいかな)

2.リスナーはAttributeListenerだけじゃなくてModelListenerもほしい

たとえば、
custom indexを作りたくなくて複合属性つくる場合とか、
putの前に属性まとめて圧縮したりとか、
検索の為に、あるリスト属性のsizeをもつ属性(カウンタキャッシュっていうのかな?)を更新したりとか
等々、属性単位じゃなくて、モデル単位でのprePut,postGetができるモデルリスナーがほしいなーと

3.(ついでに)ControllerでasMap, asObjectとかあったらいいなーと思ってた

あと、ついでですが、
ControllerにasMap, asObjectとかあったらいいなーと

node.jsを使って簡単にwebサイトにインスタントメッセンジャーを組み込む方法(その2 Facebook連携編)

今回は、前回作成したIMをFacebook対応してみます。


Facebook Chatに関しては一応公式ドキュメントがありますが、
http://developers.facebook.com/docs/chat/
もう、わざとなの?ってぐらい必要な事書いてなくて、間違いがあったりして、結構大変でした。


developer forumや、stackoverflowで情報集めながら、とりあえず出来たので紹介というかメモ。


デモ

認証方法

FacebookはX-FACEBOOK-PLATFORMというSASL認証を提供していて、それを使う事で、アプリからFacebookチャットに接続できるようになります。


X-FACEBOOK-PLATFORMについては公式ドキュメントには超テキトーな事と、まともに動かないサンプルコードしか載ってないので、
詳しく知りたい人は、公開されているライブラリの認証処理を読むのが一番手っ取り早いんじゃないでしょうか。
(つか、これ作った人達はいったいどうやって仕様を調べたんだろうか。)


今回はgithubで公開されていた、turedsocialを使う事にしました。
strophe.jsプラグインで、Facebook接続メソッドが追加されます。
https://github.com/rubenjgarciab/turedsocial

turedsocialを試してみたけど、

エラーorz...


調べたところ、strophe.jsのバージョンが原因で動かなくなってたっぽいので、最新版に合わせて修正しました。
必要であればここから勝手にゲットしてください。
https://github.com/kissrobber/turedsocial

で、無事Facebookに接続できたけど、

このライブラリには問題があって、app secretをjavascriptに記述するような作りになっています…


これだと一般公開するアプリには使えないです。
通常、こういう(JavaScriptからapp secretを使いたい)場合はどうするのか調べてみたところ、
app secretの代わりに、ログイン時に一時的に発行されるsession secretというのを使うみたいです(たぶん)。
http://developers.facebook.com/docs/reference/rest/auth.promoteSession/


FBのjavascriptSDKを使っている場合は、

FB.getSession().secret

こんな感じでゲットできます。

これでOKかと思ったら、

だめでした…


app secretの代わりにsession secretを使うように変更してみたけど、
何か間違えているのか?
xmpp loginはsession secretサポートしないのか?
(誰か分かる人いたら教えてください)

app secret を使う処理はサーバ側で実装する様に変更

app secretを使用している処理は、SASL challengeのMD作成処理のみのようなので、
とりあえず、今回は、この処理だけサーバに投げる事にしました。
暇があったら公式SDKとかもちゃんと読んで調べた方がよいかとおもいますが。


該当する修正はこの辺り、
https://github.com/kissrobber/facebook_chat_with_javascript_on_node.js/commit/7822f541d31f54bd705b53093a6ec5513a98f532#diff-3

んで、無事完成

なんか疲れて書くの面倒くさくなってきたので、必要な人は勝手にみてください。
https://github.com/kissrobber/facebook_chat_with_javascript_on_node.js
Herokuに載せればそのまま動くようになっています。

node.jsを使って簡単にwebサイトにインスタントメッセンジャーを組み込む方法(その1)

node.jsを使ってWebサイトにインスタントメッセンジャー
Google Talkでも、Yahoo!メッセンジャーでも、MSN Messengerでも、Facebook ChatでもXMPP対応のもの)
を組み込む方法の紹介(というかメモ)です。


デモ
(IEだとAjaxでエラーになっているっぽいです。ライブラリはIEでも動くっぽいので何かがおかしいのだと思いますが、何がおかしいか分かる人いたら教えてください。)

XMPPって何よ?

あんまり理解してなくてもWebに組み込む程度なら出来ますが、詳しく知りたい人は自分で調べてください。
Extensible Messaging and Presence Protocol - Wikipedia

Webサイトで対応する場合の仕組み

XMPPはHTTPプロトコルでは無いので、直接ブラウザから使用する事はできません。
なんらかの手段でHTTPとXMPPの変換する必要がありますが、この変換にはBOSHというプロトコルが使われている様です。
XMPP Technologies: BOSH


つまり、
Webサイト ─(HTTP)→ BOSHサーバ ─(XMPP)→ IMサーバ
という流れになります。

構成

今回はBOSHサーバーの実装として、node.js上で動くnode-xmpp-boshを使う事にします。
BOSHクライアントはStrophe.jsというJavaScriptライブラリを使います。


ブラウザ(Strophe.js) ─(HTTP)→ node-xmpp-bosh ─(XMPP)→ IMサーバ
の流れです。


今回はHerokuでNode.jsを動かしてみます。
Node.js 0.4.7
Strophe.js
node-xmpp-bosh


ここでは、開発マシンは、VMPlayer上のUbuntu11.04を使用します。(CentOS5.6でも大体同じ感じでいけました。)

node.jsとnpmのインストール

node.jsのインストールは簡単なので、公式ガイド等をみながらnpmのインストールまでやってください。
バージョンは最新だとherokuやnode-xmpp-boshに対応しないので、0.4.7にしてください。
https://github.com/joyent/node/wiki/Installation


なんかわからない事があったりうまくいかない事があったら自分で調べてください。
これからnode.jsを始める人のためのインストール系エントリまとめ

herokuの設定

公式のガイドがとてもわかりやすいので、公式ガイドをみながらサンプルのHello Worldを作ってみてください(無料範囲で作れます)。
今回はデータベースは使わないのでSQL Databaseのところは飛ばしても大丈夫です。
http://devcenter.heroku.com/articles/node-js

node-xmpp-boshインストール

これも簡単で、
「npm install node-xmpp-bosh」でいけます。
参考


とりあえずHerokuで動かすだけならこれで十分ですが、
↓ここ等を参考に、いろいろ設定してみても良いと思います。
https://code.google.com/p/node-xmpp-bosh/wiki/DebianHowTo

boshパッケージと、boshに必要なパッケージの設定

herokuの設定中に作成した、package.jsonのdependenciesに必要パッケージを追加します。
node-xmpp-boshをインストールしたディレクトリにあるpackage.jsonを参考に追加すれば良いと思います。
自分の場合は↓のように追加しました。

{
  "name": "node-example",
  "version": "0.0.1",
  "dependencies": {
    "dns-srv":"0.0.6",
    "eventpipe":"0.0.3",
    "jsdom":"0.2.0",
    "htmlparser":"1.7.3",
    "request":"1.9.5",
    "ltx":"0.0.5",
    "node-expat":"1.3.2",
    "node-uuid":"1.2.0",
    "tav":"0.1.0",
    "underscore":"1.1.6",
    "node-xmpp-bosh":"0.2.3"
  }
}

Heroku上でnode-xmpp-boshを動かす

boshをインストールしたディレクトリにある、

  • bosh.conf.example.js
  • run-server.js

をプロジェクトフォルダにコピーしてください。

bosh.conf.example.jsのファイル名をbosh.conf.jsに変更して、少々修正します。
exports.config = {
	port: process.env.PORT || 5280, //←Herokuに割り当てられたportを動的に設定する設定
	host: '0.0.0.0',                //これはこのままでOK
	path: /^\/http-bind(\/+)?$/,    //これ以降は必要に応じて変更してください。
	logging: 'INFO', 

	// The maximum number of bytes that the BOSH server will 
	// "hold" from the client
	max_data_held: 100000, 
次に、run-server.jsを修正します。
var fs   = require('fs');
var path = require('path');

var BOSH_DEFAULT_CONFIG_PATH = 'bosh.js.conf'; //コンフィグファイルのパスをさっき作成したものをみるように変更します。

...省略

	var nxb    = require("node-xmpp-bosh"); //node-xmpp-boshはパッケージから読み込むように修正します。
	//require("./src/main.js");
Procfileを修正します。

run-server.jsを実行するように変更します。

web: node run-server.js


ここまで終わったらHerokuにデプロイしてください。
「heroku logs」コマンドなどで確認すると、動いている事が確認できると思います。

Webのクライアント作成

strophe.jsを公式サイトからゲットします。
http://strophe.im/strophejs/


次のHTMLファイルをstrophe.jsと同じ場所に作成します。
(var BOSH_SERVICEの値は自分のサーバーに変更してください。)

<html>
<head>
<title>XMPP with JavaScript</title>
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js'></script>
<script src='strophe.js'></script>

<script>
	var BOSH_SERVICE = 'http://node-xmpp-boshサーバーのアドレス/http-bind/';
	var connection = null;

	function log(msg) {
		$('#log').append('<div></div>').append(document.createTextNode(msg));
	}

	function onConnect(status) {
		if (status == Strophe.Status.CONNECTING) {
			log('Strophe is connecting.');
		} else if (status == Strophe.Status.CONNFAIL) {
			log('Strophe failed to connect.');
			$('#connect').get(0).value = 'connect';
		} else if (status == Strophe.Status.DISCONNECTING) {
			log('Strophe is disconnecting.');
		} else if (status == Strophe.Status.DISCONNECTED) {
			log('Strophe is disconnected.');
			$('#connect').get(0).value = 'connect';
		} else if (status == Strophe.Status.CONNECTED) {
			log('Strophe is connected.');
			log('Send a message to ' + connection.jid +
			' to talk to me.');

			connection.addHandler(onMessage, null, 'message', null, null,  null);
			connection.send($pres().tree());
		}
	}

	function onMessage(msg) {
		var to = msg.getAttribute('to');
		var from = msg.getAttribute('from');
		var type = msg.getAttribute('type');
		var elems = msg.getElementsByTagName('body');

		if (type == "chat" && elems.length > 0) {
			var body = elems[0];

			log('I got a message from ' + from + ': ' +
			Strophe.getText(body));
		}

		// we must return true to keep the handler alive.
		// returning false would remove it after it finishes.
		return true;
	}

	function sendMessage() {
		var message = $('#message').get(0).value;
		var to = $('#to').get(0).value;
		
		if(message && to){
			var reply = $msg({
				to: to,
				type: 'chat'
			})
			.cnode(Strophe.xmlElement('body', message));
			connection.send(reply.tree());

			log('I sent ' + to + ': ' + message);
		}
	}

	$(document).ready( function () {
		connection = new Strophe.Connection(BOSH_SERVICE);

		// Uncomment the following lines to spy on the wire traffic.
		//connection.rawInput = function (data) { log('RECV: ' + data); };
		//connection.rawOutput = function (data) { log('SEND: ' + data); };

		// Uncomment the following line to see all the debug output.
		//Strophe.log = function (level, msg) { log('LOG: ' + msg); };

		$('#connect').bind('click', function () {
			var button = $('#connect').get(0);
			if (button.value == 'connect') {
				button.value = 'disconnect';

		var jid = $('#jid').get(0).value;
				connection.connect(jid,
				$('#pass').get(0).value,
				onConnect);
			} else {
				button.value = 'connect';
				connection.disconnect();
			}
		});
		
		$('#send').bind('click', function () {
			sendMessage();
		});
	});
</script>
</head>
<body>
	<div id='login' style='text-align: center'>
		<form name='cred'>
			<label for='jid'>
				JID:
			</label>
			<input type='text' id='jid'>
			<label for='pass'>
				Password:
			</label>
			<input type='password' id='pass'>
			<input type='button' id='connect' value='connect'>
		</form>
	</div>
	<div id='login' style='text-align: center'>
		<label for='to'>
			to:
		</label>
		<input type='text' id='to'>
		<label for='message'>
			message:
		</label>
		<input type='text' id='message'>
		<input type='button' id='send' value='send'>
	</div>
	<hr>
	<div id='log'>
	</div>
</body>
</html>

という事で、

完成です。
作成したHTMLをブラウザで開くと使えます。
GoogleアカウントやFacebookアカウントを使ってログインできます(HTTPS対応してないのでログインは自己責任でお願いします)。
Facebookアカウントを使用する場合は、
ユーザーID@chat.facebook.comがJIDになります。


次回はFacebookOAuth認証を使ったログインを試してみます。

EBSをRAID0にした時のパフォーマンス結果

21:00追記


↓ここから追記前

EBSでRAID0にしたらほんとに速かったので、(当たり前だけど)

  • EBSって確保した容量で課金なのでたとえば、2Gを1つと、1G2つでRAID0とでは料金わらないよね?ならRAIDしない理由は無い?
  • CPUとかメモリの値だけ見てると、smallインスタンス1台よりも、microインスタンス3台とかたてた方が良いんじゃないかと思ってたけど(料金的にも変わらないし)、色々測定してみてから判断した方が良さそうな感じ。

以下、テキトーに測った結果

RAID無し(microインスタンス)

Write
19.5 MB/s
22.8 MB/s
21.5 MB/s


Read(↓これキャッシュ効いてたかも)
103 MB/s
103 MB/s
104 MB/s

EBS2つでRAID0(microインスタンス)

Write
45.9 MB/s
38.1 MB/s
38.2 MB/s


Read
85.8 MB/s
80.4 MB/s
94.1 MB/s

EBS3つでRAID0(microインスタンス)

Write
46.9 MB/s
38.7 MB/s
38.4 MB/s


Read
94.9 MB/s
76.3 MB/s
85.3 MB/s

EBS2つでRAID0(largeインスタンス)

Write
60.8 MB/s
62.3 MB/s


Read
225 MB/s

EBS3つでRAID0(largeインスタンス)

Write
62.8 MB/s
67.5 MB/s


Read
241 MB/s

EBS4つでRAID0(largeインスタンス)

Write
72.7 MB/s
73.1 MB/s


Read
249 MB/s

EBS5つでRAID0(largeインスタンス)

Write
67.5 MB/s
61.3 MB/s


Read
264 MB/s

EBS6つでRAID0(largeインスタンス)

Write
62.2 MB/s
54.1 MB/s


Read
256 MB/s