mod_auth_opensocial をリリースしました
mod_auth_opensocial 0.1.0 をリリースしました。
mod_auth_opensocial は、OpenSocial アプリからサーバに届いた署名付きリクエストの検証(Verification)を行うための Apache 2.0, 2.2 用のモジュールです。
署名付きリクエストとは、OpenSocial の makeRequest 関数 (AUTHORIZATIONパラメータにSIGNEDを指定) により生成され、コンテナにより署名されたリクエストです。
mod_auth_opensocial が署名の検証に成功すると、リクエストは通常通り(HTMLの取得や、PHP、Servlet Containerへのアクセスなど)に処理されます。 検証に失敗すると、HTTPエラーをリクエストの送信者へ返します。
バックエンドが受け取るリクエストは全て署名の検証に成功したものなので、バックエンドはリクエストを再度検証する必要が無くなります。 リクエストに署名が付加されているかどうかすらバックエンドは気にする必要がありません。
尚、本プロジェクトは Google Code で管理しています。
http://code.google.com/p/mod-auth-opensocial/
■ インストール
mod_auth_opensocial は openssl の機能を使用していますので、Apache に mod_ssl が組み込まれている必要があります。
○ Apache のインストール
※ configure コマンドへのオプションの --prefix と --enable-mods-shared の値は適宜変更してください。
またシステムによっては、 --with-ssl オプションで openssl へのパスを指定する必要があるかもしれません。 (例: --with-ssl=/usr/local/openssl )
#./configure
--prefix=/usr/local/apache \ --enable-mods-shared=most \ --enable-so \ --with-included-apr \ --enable-ssl # make # make install
○ mod_auth_opensocial のインストール
# tar xvzf mod_auth_opensocial-0.1.0tar.gz # cd mod-auth-opensocial-0.1.0
# vi Makefile APACHE_BASE=PATH_TO_APACHE の行の PATH_TO_APACHE を Apache を↑でインストールしたディレクトリに変更してください。 APACHE_BASE=/usr/local/apache # make # make install
○ httpd.conf の変更
httpd.conf 内の LoadModule ディレクティブ が記述されている箇所に↓の行を追加してください。
LoadModule auth_opensocial_module modules/mod_auth_opensocial.soApache をリスタートすると mod_auth_opensocial がロードされます。 ただし、この段階では設定を行っていないので、まだ何も起こりません。
■ mod_auth_opensocial の設定
Apache が http://www.example.com という URL で稼働しているとします。
http://www.example.com/secure/ の以下全てのディレクトリ(パス) へのアクセスを検証するには、以下のように設定します。
<Location "/secure/"> AuthType OpenSocial Require valid-user AuthOpenSocialEnabled On AuthOpenSocialAcceptSignatureMethods hmac-sha1 rsa-sha1 AuthOpenSocialConsumerKey 4345983491233 AuthOpenSocialConsumerSecret wa/nx35kdmerLwQZp8AfmoX AuthOpenSocialRSAPublicKeyFile ./conf/pub.1199819524.-1556113204990931254.cer </Location>※ consumer secret の値を記述しているので、httpd.conf のパーミッションには注意を払ってください。
AuthType と Require ディレクティブは、必ず必要なので忘れずに記述してください。
それでは mod_auth_opensocial 個別のディレクティブを見ていきましょう。
AuthOpenSocialEnabled :
引数の数 : 1
説明 : このモジュールの有効、無効を指定します。 指定の方法は、有効の場合には On を、無効にする場合には Off を指定します。
AuthOpenSocialAcceptSignatureMethods :
AuthOpenSocialRequiredKey :
今回リリースした mod_auth_opensocial の バージョン 0.1 では、ある一つの URL に対して1つの consumer key や rsa publickey しか設定できないため、複数のOpenSocial サービスからの署名リクエストを1つのLocationディレクティブでさばくことができません。 引数の数 : 1
説明 : 受け付ける署名方式です。 hmac-sha1, rsa-sha1 のどちらか、もしくは両方(スペース区切り)を指定します。
AuthOpenSocialConsumerKey : 説明 : 受け付ける署名方式です。 hmac-sha1, rsa-sha1 のどちらか、もしくは両方(スペース区切り)を指定します。
引数の数 : 1
説明 : OpenSocial サービスから提供される Consumer Key です。 AuthOpenSocialAcceptSignatureMethods ディレクティブに hmac-sha1 を指定した場合に必須となります。
AuthOpenSocialConsumerSecret : 説明 : OpenSocial サービスから提供される Consumer Key です。 AuthOpenSocialAcceptSignatureMethods ディレクティブに hmac-sha1 を指定した場合に必須となります。
引数の数 : 1
説明 : OpenSocial サービスから提供される Consumer Secret です。AuthOpenSocialAcceptSignatureMethods ディレクティブに hmac-sha1 を指定した場合に必須となります。
AuthOpenSocialRSAPublicKeyFile : 説明 : OpenSocial サービスから提供される Consumer Secret です。AuthOpenSocialAcceptSignatureMethods ディレクティブに hmac-sha1 を指定した場合に必須となります。
引数の数 : 1
説明 : OpenSocial サービスから提供される RSA PublicKey を保存したファイルへのパスを指定します。 AuthOpenSocialAcceptSignatureMethods ディレクティブに rsa-sha1 を指定した場合に必須となります。
上の設定例では用いていませんが、以下のディレクティブもオプションで指定可能です。
説明 : OpenSocial サービスから提供される RSA PublicKey を保存したファイルへのパスを指定します。 AuthOpenSocialAcceptSignatureMethods ディレクティブに rsa-sha1 を指定した場合に必須となります。
AuthOpenSocialRequiredKey :
引数の数 : 1
説明 : 必須HTTPパラメータ (query string 又は post data) の key を指定します。リクエストのパラメータにこの値が存在しなければ、HTTPエラーをリクエスト送信者へ返します。
AuthOpenSocialRequiredKeyAndValue : 説明 : 必須HTTPパラメータ (query string 又は post data) の key を指定します。リクエストのパラメータにこの値が存在しなければ、HTTPエラーをリクエスト送信者へ返します。
引数の数 :2
説明 : 必須HTTPパラメータ (query string 又は post data) の key と値のペアをスペース区切りで指定します。リクエストのパラメータに、このディレクティブで指定したkeyと値のペアが含まれていなければ、HTTPエラーをリクエスト送信者へ返します。
説明 : 必須HTTPパラメータ (query string 又は post data) の key と値のペアをスペース区切りで指定します。リクエストのパラメータに、このディレクティブで指定したkeyと値のペアが含まれていなければ、HTTPエラーをリクエスト送信者へ返します。
例えば、http://www.example/myapp/ に対して mixi アプリとOrkut の両方からの署名リクエストの検証は行えないということです。 Locationタグには、1つだけOpenSocial サービスの設定を行ってください。
この制限や、他にも課題が多数ありますが、今後のバージョンで対応していきます。
このモジュールの使いどころやOpenSocialについて、このブログに順次アップしていきます。
この機会に是非 RSSの購読をお願いいたします。
速攻で作る OpenSocialアプリ ( RSA-SHA1 )
前回のブログにて OpenSocial Client Library Ruby版に付属している Gifts アプリを動作させるまで説明しました。
OpenSocialコンテナ(Orkut) -> 外部サーバへのリクエストの署名方式には HMAC-SHA1 を用いていましたが、今回はより一般的な RSA-SHA1 を用いた方法を説明します。
HMAC-SHA1では consumer key と consumer secret の2つが必要でしたが、RSA-SHA1ではそれらは必要なく、公開鍵だけが必要となります。 ■ OpenSocialコンテナ(Orkut) -> 外部サーバ のリクエストの署名方式を HMAC-SHA1 から RSA-SHA1 へ変更
GIFT_SAMPLE/public/gifts.xml の 14行目辺りのparams["OAUTH_SERVICE_NAME"] = "HMAC"; をコメントアウトしてください。
この変更により OpenSocialコンテナ(Orkut) -> 外部サーバへのリクエストの署名方式がデフォルトの RSA-SHA1 になります。
■ Rails側で RSA-SHA1 にて署名されたリクエストを受け取れるようにする
○ 公開鍵の取得
OpenSocialコンテナ(Orkut) -> 外部サーバへのリクエストのパラメータに xoauth_signature_publickey=pub.1199819524.-1556113204990931254.cer というkeyとvalueが付与されてきますが、この pub.1199819524.-1556113204990931254.cer ファイルが公開鍵となります。
で、そのファイルはどこにあるのかというと http://sandbox.orkut.com/46/o/pub.1199819524.-1556113204990931254.cer にあります。
xoauth_signature_publickeyの値が変更されたらどうするんだ?という疑問が浮かぶかもしれませんが、Orkutの場合は、xoauth_signature_publickeyの値が変わる時にはGoogleが前もって告知してくれますので、現状はこの公開鍵のファイルの内容をソースコードにハードコーディングしておいても大丈夫だと思います。
※逆に、自動で公開鍵ファイルを取得するような実装は危険です。そのファイルが正規のものかどうか判断のしようがありませんので。
○ gifts コントローラの修正
opensocial client library ( gem opensocial-0.0.2 )ですが、何故か署名方式に HMAC-SHA1 しか使用できないようになっています・・・。
RSA-SHA1の署名方式が使用できるようにします。
gifts コントローラ内で require 'oauth/signature/rsa/sha1' を行います。
また check_signature メソッド内にてコールされている validate メソッド(OpenSocial::Auth#validate(...)) では opensocial client が内部で用いている oauth ライブラリの Consumer クラスのコンストラクタに options を渡せない(署名方式を指定できない)ので、その validate メソッドを用いず、新たに validate メソッドを作成します。
上記全てを反映した gifts コントローラは下のようになります。
が!
これだけではアプリケーションとしては動作しません。
何故かというと、index メソッド内にてユーザ情報を取得するために OpenSocialコンテナ(Orkut)へリクエストを送信するための Connection クラスのオブジェクトを作成しているのですが、そこで HMAC-SHA1 のデータが必要となるからです。
外部サーバ -> OpenSocialコンテナ(Orkut) へのリクエストには HMAC-SHA1 が必須となっていますので、結局前回のブログと同様に consumer key, consumer secret が必要になります。
それぞれを取得したら、下記のようにKEY定数を変更してます。
外部サーバ -> OpenSocialコンテナ(Orkut)へのリクエストが必要ないのであれば RSA-SHA1 を用いるのが良いと思いますが、外部サーバ -> OpenSocialコンテナ(Orkut)へのリクエストも必要だとすると 両方向のリクエストの署名方式に HMAC-SHA1 を用いるのが楽でいいかもしれません。
よういちろうさんが執筆された 「OpenSocial入門 ソーシャルアプリケーションの実践開発」が 2009年1月24日に発売されるとのことですので楽しみにしている今日この頃です
OpenSocialを用いたアプリ開発をされる方には必須となるのではないでしょうか。
makeRequestの署名方式に RSA-SHA1 を用いる
OpenSocialコンテナ(Orkut) -> 外部サーバへのリクエストの署名方式には HMAC-SHA1 を用いていましたが、今回はより一般的な RSA-SHA1 を用いた方法を説明します。
HMAC-SHA1では consumer key と consumer secret の2つが必要でしたが、RSA-SHA1ではそれらは必要なく、公開鍵だけが必要となります。 ■ OpenSocialコンテナ(Orkut) -> 外部サーバ のリクエストの署名方式を HMAC-SHA1 から RSA-SHA1 へ変更
GIFT_SAMPLE/public/gifts.xml の 14行目辺りのparams["OAUTH_SERVICE_NAME"] = "HMAC"; をコメントアウトしてください。
この変更により OpenSocialコンテナ(Orkut) -> 外部サーバへのリクエストの署名方式がデフォルトの RSA-SHA1 になります。
■ Rails側で RSA-SHA1 にて署名されたリクエストを受け取れるようにする
○ 公開鍵の取得
OpenSocialコンテナ(Orkut) -> 外部サーバへのリクエストのパラメータに xoauth_signature_publickey=pub.1199819524.-1556113204990931254.cer というkeyとvalueが付与されてきますが、この pub.1199819524.-1556113204990931254.cer ファイルが公開鍵となります。
で、そのファイルはどこにあるのかというと http://sandbox.orkut.com/46/o/pub.1199819524.-1556113204990931254.cer にあります。
xoauth_signature_publickeyの値が変更されたらどうするんだ?という疑問が浮かぶかもしれませんが、Orkutの場合は、xoauth_signature_publickeyの値が変わる時にはGoogleが前もって告知してくれますので、現状はこの公開鍵のファイルの内容をソースコードにハードコーディングしておいても大丈夫だと思います。
※逆に、自動で公開鍵ファイルを取得するような実装は危険です。そのファイルが正規のものかどうか判断のしようがありませんので。
○ gifts コントローラの修正
opensocial client library ( gem opensocial-0.0.2 )ですが、何故か署名方式に HMAC-SHA1 しか使用できないようになっています・・・。
RSA-SHA1の署名方式が使用できるようにします。
gifts コントローラ内で require 'oauth/signature/rsa/sha1' を行います。
また check_signature メソッド内にてコールされている validate メソッド(OpenSocial::Auth#validate(...)) では opensocial client が内部で用いている oauth ライブラリの Consumer クラスのコンストラクタに options を渡せない(署名方式を指定できない)ので、その validate メソッドを用いず、新たに validate メソッドを作成します。
上記全てを反映した gifts コントローラは下のようになります。
class GiftsController < ApplicationController
include OpenSocial::Auth
require 'oauth/signature/rsa/sha1'
# 公開鍵のハードコーディング
CERT = <<"EOS"
-----BEGIN CERTIFICATE-----
MIIDHDCCAoWgAwIBAgIJAMbTCksqLiWeMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIG
A1UEChMLR29vZ2xlIEluYy4xDjAMBgNVBAsTBU9ya3V0MQ4wDAYDVQQDEwVscnlh
bjAeFw0wODAxMDgxOTE1MjdaFw0wOTAxMDcxOTE1MjdaMGgxCzAJBgNVBAYTAlVT
MQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChML
R29vZ2xlIEluYy4xDjAMBgNVBAsTBU9ya3V0MQ4wDAYDVQQDEwVscnlhbjCBnzAN
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAseBXZ4NDhm24nX3sJRiZJhvy9eDZX12G
j4HWAMmhAcnm2iBgYpAigwhVHtOs+ZIUIdzQHvHeNd0ydc1Jg8e+C+Mlzo38OvaG
D3qwvzJ0LNn7L80c0XVrvEALdD9zrO+0XSZpTK9PJrl2W59lZlJFUk3pV+jFR8NY
eB/fto7AVtECAwEAAaOBzTCByjAdBgNVHQ4EFgQUv7TZGZaI+FifzjpTVjtPHSvb
XqUwgZoGA1UdIwSBkjCBj4AUv7TZGZaI+FifzjpTVjtPHSvbXqWhbKRqMGgxCzAJ
BgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEU
MBIGA1UEChMLR29vZ2xlIEluYy4xDjAMBgNVBAsTBU9ya3V0MQ4wDAYDVQQDEwVs
cnlhboIJAMbTCksqLiWeMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEA
CETnhlEnCJVDXoEtSSwUBLP/147sqiu9a4TNqchTHJObwTwDPUMaU6XIs2OTMmFu
GeIYpkHXzTa9Q6IKlc7Bt2xkSeY3siRWCxvZekMxPvv7YTcnaVlZzHrVfAzqNsTG
P3J//C0j+8JWg6G+zuo5k7pNRKDY76GxxHPYamdLfwk=
-----END CERTIFICATE-----
EOS
# Declares the keys for your app on different containers. The index is the
# incoming consumer key sent in the signed makeRequest from the gadget.
# These are sample gadget credentials and should be replaced with the HMAC
# keys granted to your own gadget.
KEYS = {
'XXXXXXXXXX' => {
:secret => 'XXXXXXXXXXXXXXXXXXXXXXXXXX',
:outgoing_key => 'orkut.com:XXXXXXXXXX',
:container => OpenSocial::Connection::ORKUT
}
}
# Declares where your application lives. Points to the page where users are
# redirected after loading the iframe.
SERVER = 'http://www.example.com/'
# Controls the auto-validation of the signed makeRequest.
before_filter :check_signature, :only => [:iframe]
# Renders pages with boilerplate HTML/CSS/JS.
layout 'default'
# Implicitly checks the signature of the incoming request and saves the
# owner id, viewer id, and consumer key in a temporary session for later use.
# The session id is returned as an iframe snippet that the gadget can use to
# render the app.
def iframe
session[:id] = params[:opensocial_owner_id]
session[:viewer] = params[:opensocial_viewer_id]
session[:consumer_key] = params[:oauth_consumer_key]
render :text => "<iframe width='98%' height='600px' frameborder='0' src='#{SERVER}?sessid=#{session.model.session_id}' />"
end
# Loads the temporary session referenced by sessid, copies the values into
# a persistent session that will be used to actually drive the app, and
# deletes the temporary session (to prevent replay). Then, initiates a
# connection to the given container and renders the appropriate content.
def index
proxied_session = session.model.class.find(:first,
:conditions => ['session_id = ?', params[:sessid]])
if params[:sessid] && proxied_session
session[:id] = proxied_session.data[:id]
session[:viewer] = proxied_session.data[:viewer]
session[:consumer_key] = proxied_session.data[:consumer_key]
proxied_session.destroy
end
c = OpenSocial::Connection.new(:container => KEYS[session[:consumer_key]][:container],
:consumer_key => KEYS[session[:consumer_key]][:outgoing_key],
:consumer_secret => KEYS[session[:consumer_key]][:secret],
:xoauth_requestor_id => session[:id])
if session[:id] == session[:viewer]
render_owner(c)
elsif session[:viewer] && session[:id] != session[:viewer]
render_viewer_with_app(c)
else
render_viewer_without_app(c)
end
end
# Sends a gift on behalf of the viewer. If the viewer is the owner, the gift
# is sent to the selected friend (but doesn't check to confirm that the owner
# is friends with the specified ID). If the viewer is not the owner, the gift
# is sent to the owner. Then the app redirects to the index.
def give
gift = Gift.new(params[:gift])
if session[:id] != session[:viewer]
gift.sent_by = session[:viewer]
gift.received_by = session[:id]
else
gift.sent_by = session[:id]
end
if gift.save
redirect_to :action => :index
else
flash[:error] = 'Error giving gift.'
redirect_to :action => :index
end
end
private
# If the viewer is also the owner, this will render the list of gifts the
# owner has sent or received. The owner can elect to send a gift to a friend.
def render_owner(c)
@id = session[:id]
@gifts = Gift.find(:all,
:conditions => ['sent_by = ? OR received_by = ?', @id, @id],
:order => 'created_at DESC', :limit => 10)
@gift_names = GiftName.find(:all)
@people = fetch_gift_givers_and_friends(@id, @gifts, c)
@owner = @people.delete(@id)
render :action => "owner"
end
# If the viewer has the app installed, this will render the list of gifts
# exchanged between the owner and viewer. The viewer can elect to send a gift
# to the owner.
def render_viewer_with_app(c)
@id = session[:viewer]
@owner_id = session[:id]
@gifts = Gift.find(:all,
:conditions => ['(sent_by = ? AND received_by = ?) OR ' +
'(sent_by = ? AND received_by = ?)',
@owner_id, @id, @id, @owner_id],
:order => 'created_at DESC', :limit => 10)
@gift_names = GiftName.find(:all)
@viewer = OpenSocial::FetchPersonRequest.new(c, @id).send
@owner = OpenSocial::FetchPersonRequest.new(c).send
render :action => "viewer_with_app"
end
# If the viewer doesn't have the app installed, this will render the list of
# gifts the owner has sent or received (without notes). No gifts may be sent.
def render_viewer_without_app(c)
@id = session[:id]
@gifts = Gift.find(:all,
:conditions => ['sent_by = ? OR received_by = ?', @id, @id],
:order => 'created_at DESC', :limit => 10)
@gift_names = GiftName.find(:all)
@people = fetch_gift_givers_and_friends(@id, @gifts, c)
render :action => "viewer_without_app"
end
# Requests social data for each of the people that have sent or received a
# gift from the owner (oid). The method first generates a unique list of
# user ids from the list of gifts, and sends a REST request for their data.
# Users without the app installed will trigger an exception which is caught.
# Finally, the owner's friends are fetched (which fills in the missing spaces
# where insufficient permissions are granted to collect the user data
# directly) and merged with the existing data.
# This function could be sped up considerably by using a single RPC request
# if the container supports it.
def fetch_gift_givers_and_friends(oid, gifts, c)
people = {}
ids = gifts.collect {|g| [g.sent_by, g.received_by]}.flatten.uniq
ids.each do |id|
begin
r = OpenSocial::FetchPersonRequest.new(c, id)
people[id] = r.send
rescue OpenSocial::AuthException
end
end
friends = OpenSocial::FetchPeopleRequest.new(c, oid).send
return people.merge(friends)
end
# Looks up the consumer secret paired with the given consumer key and
# hands them off to the client library authentication.
def check_signature
is_valid_request = false
if params[:oauth_signature_method] == 'HMAC-SHA1'
# 署名方式が HMAC-SHA1
key = params[:oauth_consumer_key]
is_valid_request = validate(key, KEYS[key][:secret], {:signature_method => 'HMAC-SHA1'})
elsif params[:oauth_signature_method] == 'RSA-SHA1'
# 署名方式が RSA-SHA1
# RSA-SHA1 では 公開鍵のみ必要なので validate メソッドの 第1引数は nil. 公開鍵は第2引数に指定する )
is_valid_request = validate(nil, CERT, {:signature_method => 'RSA-SHA1'})
end
unless is_valid_request
render :text => '401 Unauthorized', :status => :unauthorized
end
end
# opensocial client の validate メソッドを用いずに新たに作成.
def validate(key = CONSUMER_KEY, secret = CONSUMER_SECRET, options={})
consumer = OAuth::Consumer.new(key, secret, options)
begin
signature = OAuth::Signature.build(request) do
[nil, consumer.secret]
end
pass = signature.verify
rescue OAuth::Signature::UnknownSignatureMethod => e
logger.error 'An unknown signature method was supplied: ' + e.to_s
end
return pass
end
end
以上で、署名方式 RSA-SHA1 を用いたリクエストが受け取れるようになります。が!
これだけではアプリケーションとしては動作しません。
何故かというと、index メソッド内にてユーザ情報を取得するために OpenSocialコンテナ(Orkut)へリクエストを送信するための Connection クラスのオブジェクトを作成しているのですが、そこで HMAC-SHA1 のデータが必要となるからです。
外部サーバ -> OpenSocialコンテナ(Orkut) へのリクエストには HMAC-SHA1 が必須となっていますので、結局前回のブログと同様に consumer key, consumer secret が必要になります。
それぞれを取得したら、下記のようにKEY定数を変更してます。
KEYS = {
'orkut.com' => {
:secret => '<CONSUMER SECRET>',
:outgoing_key => 'orkut.com:<CONSUMER KEY>',
:container => OpenSocial::Connection::ORKUT
}
これにてアプリケーションとして動作します。外部サーバ -> OpenSocialコンテナ(Orkut)へのリクエストが必要ないのであれば RSA-SHA1 を用いるのが良いと思いますが、外部サーバ -> OpenSocialコンテナ(Orkut)へのリクエストも必要だとすると 両方向のリクエストの署名方式に HMAC-SHA1 を用いるのが楽でいいかもしれません。
よういちろうさんが執筆された 「OpenSocial入門 ソーシャルアプリケーションの実践開発」が 2009年1月24日に発売されるとのことですので楽しみにしている今日この頃です
OpenSocialを用いたアプリ開発をされる方には必須となるのではないでしょうか。
速攻で作る OpenSocialアプリ
新年明けましておめでとうございます。
日本最大のSNSである mixiが mixiアプリ にOpen Socialを採用したことにより、今年2009年は他社のサービスでもOpenSocialを採用するところが一気に増えてくるではないでしょうか。
そこで弊社Banana Systemsが2009年最初にお届けする記事は「速攻で作るOpenSocialアプリ」です。
OpenSocialの詳細につきましては、オフィシャル・ページ にて説明されていますのでここでは割愛します。
まず前提として、
・Orkut用のソーシャル・アプリ
今現在 mixiアプリはまだβ版であり一般には公開されていませんので、
GoogleのSNSであるOrkutにてソーシャル・アプリを開発します。
・自社サーバからデータの取得、保存
HTML, Javascriptのみを用いたシンプルなソーシャル・アプリではなく、
実際のサービスを考えた時には外部(自社)サーバからコンテンツ、
データの取得、また逆に保存を行いますので、↓のような形式とします。
外部サーバ <-> Orkut(OpenSocialコンテナ) <-> ソーシャル・アプリ
・OpenSocial Client Libraryの使用
2008年12月17日に OpenSocial Blog にてPHP, Java, Ruby, Python用のOpenSocial Client Library が発表されました。
このライブラリはサーバ間(外部サーバ <-> OpenSocialコンテナ)通信の複雑なやり取りを隠ぺいしてくれます。
Ruby用のものには Railsアプリのサンプルが入っていますので、今回はそのRailsアプリを動作させるまでを説明します。
それでは始めていきましょう!
Getting started with the Ruby Client Library に大まかな説明がされているので、それに沿って進めていきますが、Orkut(OpenSocialコンテナ)から外部サーバへ通信する際に必要となるSigned 通信の設定方法が記述されていなかったり、migration ファイルに誤りがありそのままでは動作しませんので、補足しながら説明していきます。
1. 必要なライブラリのインストール
# gem install oauth json mocha rails
2. opensocial ライブラリのダウンロードとインストール
ダウンロード ページ からopensocial-X.X.X.gem をダウンロードします。
ダウンロードしたgemをインストールします。
# gem install opensocial-X.X.X.gem
3. サンプルの Railsアプリのダウンロード
ダウンロード ページ から gifts_sample_X.X.zip をダウンロードします。
ダウンロードしたzipファイルを展開すると gifts_sampleディレクトリ(Railsアプリのトップ)が作成されますので、Railsアプリを設置する適切なディレクトリに移動してください。
4. DB設定
GIFT_SAMPLE/config/database.ymlの設定を適切なものに変更し、DBにデータベースを作成してください。
※GIFT_SAMPLE は 当Railsアプリのトップディレクトリとします。
今回はMySQLを使用しました。
※database, username, password はサンプルですので適宜変更してください。
GIFT_SAMPLE/config/20081201192139_create_gifts.rb に note カラムを追加します。
GIFT_SAMPLE/config/20081201203248_create_gift_names.rb に ギフトを追加します。
※ Gift アプリはギフトを友達に贈ったりするものなのですが、ギフトはDBに保存しておかないといけません。
Gift sample アプリのトップディレクトリにて
# rake migrate
7. サーバの起動
Gift sample アプリのトップディレクトリにて
# ruby script/server
上記コマンドにより、本アプリは http://www.example.com/にて動作しているとし以降の説明を進めます。
1. Orkutにアカウントのない方は、ここから アカウントを作成してください。
2. デベロッパー サンドボックスへの登録
Orkutにてサンプルのアプリをインストールするには、サンドボックスに登録する必要があります。
Orkutにログイン後、ここから 登録します。
※以前は登録が承認されるまでに数日要しましたが、現在はすぐに完了します。
3. Giftsアプリのインストール
サイドメニューのアプリの編集をクリックします。
URLに本Giftsアプリの下記URLを入力し、「アプリケーションを追加」ボタンをクリックします。
http://www.example.com/gifts.xml
アプリケーション追加をクリックします。
これでGiftsアプリのサンドボックスへのインストールは完了です。
Gifts アプリでは、Orkutと外部サーバ間にてデータの改ざんが行われないように、Orkutから外部サーバへの通信には署名付けられるように実装されています。
(GIFT_SAMPLE/public/gifts.xml の 13行目辺り params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.SIGNED; )
実サービスにおいても、署名付きリクエストは必須となるでしょう。
追記: 2009-01-10 05:30 (コメント参照) ---------
また、署名方式には HMAC-SHA1 を用いるように指定されています。 (GIFT_SAMPLE/public/gifts.xml の 14行目辺り params["OAUTH_SERVICE_NAME"] = "HMAC"; )
追記ここまで ------------
この署名方式では Consumer Key と Consumer Secret Key が必要となります。
1. Consumer Key と Consumer Secret Key の取得
Orkutでは、Consumer Key と Consumer Secret Keyを発行する際に、本アプリの所有者があなたか確認するためにトークンを発行します。
下記ページにGiftsアプリのURL( http://www.example.com/gifts.xml )を入力し、「submit」ボタンをクリックします。
https://www.google.com/gadgets/directory/verify
すると、 <!-- ALm6fM2iKDPf9qY5 ... gMjzbD1cwDBifBX5w== --> といったトークンが発行されます。
※ブラウザは閉じずにそのままにしておいてください。
GIFT_SAMPLE/public/gifts.xml の Content タグ内(CDATA開始タグとscriptタグの間)に発行されたトークンを挿入し、保存します。
Giftsアプリの所有者があなたであることが確認されると、下記のように Consumer Key と Consumer Secret Key が発行されます。
OAuth Consumer Key: 294828716828
OAuth Consumer Secret: xw/zDlw3y4p4yRFZo2mCQCID
2. Controller の修正
Gifts アプリの gifts controller にて、署名付きリクエストを受け付けられるようにします。
GIFT_SAMPLE/app/controllers/gifts_controller.rb を開き、KEYS定数に先ほどのConsumer KeyとConsumer Secretを追加します。
SERVER 定数も変更します。
Giftsアプリが署名付きリクエストを外部サーバに送信する(実際には、リクエストを送信するのはOpenSocialコンテナ(今回はOrkutのサーバ))のはユーザが本アプリにアクセスした初めの一度だけです。
これは Giftsアプリがコンテンツをiframeにて表示するからです。
よって、iframe内にて表示するコンテンツのURLを指定する必要があります。
GIFT_SAMPLE/public/gifts.xml を開いて、30行目辺りの sendSignedRequest メソッドへの引数を下記のように gifts controller の iframe アクションへのURLに変更します。
今回は OpenSocialコンテナ(Orkut) -> 外部サーバへの一方的な通信でしたが、OAuthを用いて外部サーバ -> OpenSocialコンテナへの通信も可能となります。
mixi では mixi connect がそれに当ります。
今回の記事のタイトルが「速攻で作る OpenSocialアプリ」でありながら全くもって速攻では終わりませんでしたが、実サービスにて必須となるであろう署名付きリクエストの受付方法を示すことができましたのでご参考になれば幸いです。
本年もBananan Systemsをどうぞ宜しくお願い申し上げます。
mixiアプリも採用の Open Social
日本最大のSNSである mixiが mixiアプリ にOpen Socialを採用したことにより、今年2009年は他社のサービスでもOpenSocialを採用するところが一気に増えてくるではないでしょうか。
そこで弊社Banana Systemsが2009年最初にお届けする記事は「速攻で作るOpenSocialアプリ」です。
OpenSocialの詳細につきましては、オフィシャル・ページ にて説明されていますのでここでは割愛します。
まず前提として、
・Orkut用のソーシャル・アプリ
今現在 mixiアプリはまだβ版であり一般には公開されていませんので、
GoogleのSNSであるOrkutにてソーシャル・アプリを開発します。
・自社サーバからデータの取得、保存
HTML, Javascriptのみを用いたシンプルなソーシャル・アプリではなく、
実際のサービスを考えた時には外部(自社)サーバからコンテンツ、
データの取得、また逆に保存を行いますので、↓のような形式とします。
外部サーバ <-> Orkut(OpenSocialコンテナ) <-> ソーシャル・アプリ
・OpenSocial Client Libraryの使用
2008年12月17日に OpenSocial Blog にてPHP, Java, Ruby, Python用のOpenSocial Client Library が発表されました。
このライブラリはサーバ間(外部サーバ <-> OpenSocialコンテナ)通信の複雑なやり取りを隠ぺいしてくれます。
Ruby用のものには Railsアプリのサンプルが入っていますので、今回はそのRailsアプリを動作させるまでを説明します。
OpenSocialアプリの構築
それでは始めていきましょう!
Getting started with the Ruby Client Library に大まかな説明がされているので、それに沿って進めていきますが、Orkut(OpenSocialコンテナ)から外部サーバへ通信する際に必要となるSigned 通信の設定方法が記述されていなかったり、migration ファイルに誤りがありそのままでは動作しませんので、補足しながら説明していきます。
1. 必要なライブラリのインストール
# gem install oauth json mocha rails
2. opensocial ライブラリのダウンロードとインストール
ダウンロード ページ からopensocial-X.X.X.gem をダウンロードします。
ダウンロードしたgemをインストールします。
# gem install opensocial-X.X.X.gem
3. サンプルの Railsアプリのダウンロード
ダウンロード ページ から gifts_sample_X.X.zip をダウンロードします。
ダウンロードしたzipファイルを展開すると gifts_sampleディレクトリ(Railsアプリのトップ)が作成されますので、Railsアプリを設置する適切なディレクトリに移動してください。
4. DB設定
GIFT_SAMPLE/config/database.ymlの設定を適切なものに変更し、DBにデータベースを作成してください。
※GIFT_SAMPLE は 当Railsアプリのトップディレクトリとします。
今回はMySQLを使用しました。
※database, username, password はサンプルですので適宜変更してください。
development: adapter: mysql encoding: utf8 database: gifts_development pool: 5 username: banana password: banana host: localhost test: adapter: mysql encoding: utf8 database: gifts_test pool: 5 username: banana password: banana host: localhost production: adapter: mysql encoding: utf8 database: gifts_production pool: 5 username: banana password: banana host: localhostMySQL コンソールにて ( GRANT文は適宜変更してください )
> GRANT ALL PRIVILEGES ON *.* TO banana@localhost IDENTIFIED BY 'banana' WITH GRANT OPTION; > flush privileges; > create gifts_development DEFAULT CHARSET=utf8; > create gifts_test DEFAULT CHARSET=utf8; > create gifts_production DEFAULT CHARSET=utf8;5. migrationファイルの修正
GIFT_SAMPLE/config/20081201192139_create_gifts.rb に note カラムを追加します。
class CreateGifts < ActiveRecord::Migration
def self.up
create_table :gifts do |t|
t.integer :gift_name_id
t.string :sent_by
t.string :received_by
t.boolean :viewed
# note カラムの追加
t.string :note
t.timestamps
end
end
def self.down
drop_table :gifts
end
end
※通常はmigrationファイルを直接編集せずに ruby script/generate migration AddNoteToGift note:string コマンドにて新しい migrationファイルを作成しますが、DBにまだテーブルを作成していないので今回は migration ファイルを直接編集しました。↓の migration ファイルも同様に直接編集しています。GIFT_SAMPLE/config/20081201203248_create_gift_names.rb に ギフトを追加します。
※ Gift アプリはギフトを友達に贈ったりするものなのですが、ギフトはDBに保存しておかないといけません。
class CreateGiftNames < ActiveRecord::Migration
def self.up
create_table :gift_names do |t|
t.string :name
t.timestamps
end
# ギフトの追加
GiftName.create(:id => 1, :name => 'book')
GiftName.create(:id => 2, :name => 'coffee')
GiftName.create(:id => 3, :name => 'candy')
GiftName.create(:id => 4, :name => 'icecream')
end
def self.down
drop_table :gift_names
end
end
6. DBにテーブル作成Gift sample アプリのトップディレクトリにて
# rake migrate
7. サーバの起動
Gift sample アプリのトップディレクトリにて
# ruby script/server
上記コマンドにより、本アプリは http://www.example.com/にて動作しているとし以降の説明を進めます。
OrkutにてGiftsアプリのインストール
1. Orkutにアカウントのない方は、ここから アカウントを作成してください。
2. デベロッパー サンドボックスへの登録
Orkutにてサンプルのアプリをインストールするには、サンドボックスに登録する必要があります。
Orkutにログイン後、ここから 登録します。
※以前は登録が承認されるまでに数日要しましたが、現在はすぐに完了します。
3. Giftsアプリのインストール
サイドメニューのアプリの編集をクリックします。
URLに本Giftsアプリの下記URLを入力し、「アプリケーションを追加」ボタンをクリックします。
http://www.example.com/gifts.xml
アプリケーション追加をクリックします。
これでGiftsアプリのサンドボックスへのインストールは完了です。
外部サーバにてOrkut(OpenSocialコンテナ)からの署名付きのリクエストを受け付ける
Gifts アプリでは、Orkutと外部サーバ間にてデータの改ざんが行われないように、Orkutから外部サーバへの通信には署名付けられるように実装されています。
(GIFT_SAMPLE/public/gifts.xml の 13行目辺り params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.SIGNED; )
実サービスにおいても、署名付きリクエストは必須となるでしょう。
追記: 2009-01-10 05:30 (コメント参照) ---------
また、署名方式には HMAC-SHA1 を用いるように指定されています。 (GIFT_SAMPLE/public/gifts.xml の 14行目辺り params["OAUTH_SERVICE_NAME"] = "HMAC"; )
追記ここまで ------------
この署名方式では Consumer Key と Consumer Secret Key が必要となります。
1. Consumer Key と Consumer Secret Key の取得
Orkutでは、Consumer Key と Consumer Secret Keyを発行する際に、本アプリの所有者があなたか確認するためにトークンを発行します。
下記ページにGiftsアプリのURL( http://www.example.com/gifts.xml )を入力し、「submit」ボタンをクリックします。
https://www.google.com/gadgets/directory/verify
すると、 <!-- ALm6fM2iKDPf9qY5 ... gMjzbD1cwDBifBX5w== --> といったトークンが発行されます。
※ブラウザは閉じずにそのままにしておいてください。
GIFT_SAMPLE/public/gifts.xml の Content タグ内(CDATA開始タグとscriptタグの間)に発行されたトークンを挿入し、保存します。
<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs title="Gifts">
<Require feature="opensocial-0.8" />
<Require feature="dynamic-height"/>
</ModulePrefs>
<Content type="html">
<
しかしながら、LineSeriesに関しては常に値を表示させることができません。
マウスオーバーの際に値を表示させる DataTip(ToolTip ※1) は使用可能です。
以前、自分も同じようなことを行いたく自作したLineSeriesDataLabelクラスがあったので少し変更して紹介します。

LineSeries に常に値を表示する LineSeriesDataLabel
(右クリックからソースが見れます)
今後他のシリーズにも適応できるようにLineSeriesを直接継承せずに、ChartElementを継承しています。
LineSeriesDataLabelのseriesプロパティにLineSeriesのインスタンスを指定します。
各値を表示するラベル(UITextField)がチャートの表示領域からはみ出した場合には内側にずらす簡単な処理を入れていますが、複雑なポジショニングが必要な場合は、LineSeriesDataLabelクラスのlabelPositioningFunctionに独自のFunctionを設定してください。
その Function の引数、実装等は LineSeriesDataLabel#defaultLabelPositioningFunction を参考にしてください。
※1
あまり気にする必要はありませんが、DataTip と ToolTip には下記の明確な違いがあります。
「DataTip コントロールは ToolTip コントロールに似ていますが、マウスポインタから最も近いグラフのデータポイントを表す適切な値が表示されるという点で異なります。」
http://livedocs.adobe.com/flex/3_jp/langref/mx/charts/chartClasses/ChartBase.html

しかしながら、LineSeriesに関しては常に値を表示させることができません。
マウスオーバーの際に値を表示させる DataTip(ToolTip ※1) は使用可能です。
以前、自分も同じようなことを行いたく自作したLineSeriesDataLabelクラスがあったので少し変更して紹介します。

LineSeries に常に値を表示する LineSeriesDataLabel
(右クリックからソースが見れます)
今後他のシリーズにも適応できるようにLineSeriesを直接継承せずに、ChartElementを継承しています。
LineSeriesDataLabelのseriesプロパティにLineSeriesのインスタンスを指定します。
各値を表示するラベル(UITextField)がチャートの表示領域からはみ出した場合には内側にずらす簡単な処理を入れていますが、複雑なポジショニングが必要な場合は、LineSeriesDataLabelクラスのlabelPositioningFunctionに独自のFunctionを設定してください。
その Function の引数、実装等は LineSeriesDataLabel#defaultLabelPositioningFunction を参考にしてください。
※1
あまり気にする必要はありませんが、DataTip と ToolTip には下記の明確な違いがあります。
「DataTip コントロールは ToolTip コントロールに似ていますが、マウスポインタから最も近いグラフのデータポイントを表す適切な値が表示されるという点で異なります。」
http://livedocs.adobe.com/flex/3_jp/langref/mx/charts/chartClasses/ChartBase.html
itemRenderer パート4 : ステート & トランジション
原文 : http://weblogs.macromedia.com/pent/archives/2008/03/itemrenderers_p_3.html
itemRenderers: Part 4: ステート と トランジション
itemRenderer は、視覚的に情報を伝えることにとても優れています。
伝えると一口で言っても、名前を表示するだけのシンプルなものから、たくさんの色を使い表現するもの、時にはインタラクションを含むものまであります。
itemEditor がまさにインタラクティブなコントロールですが、今回の記事の目的ではありませんので割愛します。
今回の記事では、itemRenderer の data プロパティやユーザのアクションにより外観を変化させる itemRenderer について見ていきます。
■ ステート
<mx:State> は、itemRendererの外観を変更するのにとても適した方法です。
ステートはとても簡単に使うことができ、トランジションと組み合わせることにより、ユーザにフィードバックを返して、使い勝手をよくすることができます。
今回のサンプルでは、List コンポーネント用に、MXML を用いた itemRenderer を作成していきます。( ActionScript のみでも作成可能です )
itemRenderer で表示するのは、image, title, author, price と 本を購入するための Button コントロールです。
もし本の在庫が無ければ、その本が非表示になるようにしましょう( data の <instock> タグの値 ( yes / no ) で在庫の有無を判定します )。
実装を簡単にするために、HBox の id に "priceBox" を指定しています。
この HBox の可視状態を変更することにより、内部の Label と Button 両方の可視状態も変更できるからです。
set data メソッドをオーバーライドしてもいいのですが、ここでは priceBox の可視状態を直接変更せずに、ステートを使ってみます。
このタグをルートタグ直下に配置してください。
やりたいことが簡単なわりに少々複雑になってしまっていますが、ステートの使い方がよくわかると思います。
以下の2つのステートが存在します。
・ ベースステート : これはコンポーネントの標準(デフォルト)の状態です。コンポーネントがステートを使っていないときは、この状態になっています。今回の例では、ステートがベースステートの時には priceBox の visible プロパティが true (デフォルト値) になります。 これは instock タグの値が yes の時です。
・ NoStockState : これは instock タグの値が no の時のステートです。 このステートになると <mx:State> タグ内の SetProperty が処理されます。 target はそのSetProperty 処理の対象となるインスタンスを指定します。name プロパティはアップデートするプロパティ名、value プロパティは新しくセットする値です。
set data メソッドは、instock の値に基づいてステートを切り替えます。
currentState は全ての UIComponent コントロールが備えているプロパティであり、現在のステート名を保持しています。 ステートを切り替えると、Flex フレームワークはベースステートから始め、新たなステートへの切り替えに必要な処理を行います。
----------------
itemRenderer は再利用されますので、必ず元の値(状態)に戻す処理を入れてください。itemRenderer に if を書いたら、必ず else も書く必要があります。
----------------
もし興味があればですが、下のコードのように set data メソッドをオーバーライドしない方法もあります。 データバインディングを使って、 root タグの中で currentState の値を直接変更しています。
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400"
currentState="{data.instock == 'yes' ? '' : 'NoStockState'}" >
インラインで data.instock の値を評価し、その結果を currentState プロパティに代入しています。 トリッキーなので保守性には優れていないかもしれません。
■ エレメントの追加
次の itemRenderer では、instack の値が yes の時にだけ price と 購入ボタンが表示されます。
もちろんステートを使わずに同じことは出来ますが、もし itemRenderer に追加、削除するコントロールが多数ある場合にはステートを使うのが得策です。
なぜなら、itemRenderer の currentState プロパティに値をセットするだけでそれら多数のコントロールを管理できるからです。
単に price と 購入ボタン を削除するだけでなく、在庫切れであることを表示する Label も追加します。
変更を加えたコードは次のようになります。
<mx:AddChild> タグで priceBox に Label を追加しています。
priceBox の visible プロパティに false をセットし、代わりにわかりやすい文字列を表示します。
繰り返しますが、set data メソッドをオーバーライドすることで、同じように Label を追加することもできます。また、必要なコンポーネントを前もって追加しておいて、それらの可視状態を後で変更するといった方法も可能です。
しかし、ステートにはわかりやすいメリットがあります。
ステートを使えば、在庫切れ状態の表示に必要な処理がどんなに複雑になっても、NoStockState を編集するだけで十分です。ステートを切り替えるための ActionScript コードを変更する必要はありません。
---------------
Flex Builder の Design View ではステートを編集することができます。
---------------
■ 伸び縮みする List
今回の例は、List コントロールではうまく動作しませんが、VBox と Repeater を用いて実現できます。
もし List コントロールを使用して、そのリストがスクロールされてしまうと伸長させたアイテムの itemRenderer が機能しなくなるかもしれません。
例えば、高さが同じアイテムを複数保持するリストがあるとします。
ここで2番目のアイテムの高さを伸長させると、そのアイテムの高さは他のアイテムよりも高くなります。 ここまでは大丈夫です。
( And there's the catch: the visible items. )
次にそのリストをスクロールさせます。 itemRenderer は再利用されるのを覚えていますよね。 2番目のアイテムがリストの外に出ると、そのアイテムの itemRenderer はリストの最下部のアイテムを表示するために移動します。
その時、その itemRenderer の高さを元に戻す必要があります。
そして、2番目のアイテムが再度見えるようにListをスクロールします。
2番目のアイテムの高さが伸長されたままになっていて欲しいところですが、itemRenderer は前の高さのことを知りません。
以前の記事に書いたように、それらの情報は itemRenderer の data プロパティ、もしくは何かしらの外部ソースから得なければなりません。
itemRenderer のリサイズはとても複雑になりがちなので、頑張って実装するわりには大したものにならないと思います。
もしリサイズが必要なのであれば VBox と Repeater を用いる方法が良いと思います。しかし Repeater を用いる方法の欠点として、Repeater で表示する子コンポーネントのインスタンス全てが生成されてしまいます。
もし Repeater を用いて 1000 個のレコードを表示すると、itemRenderer のインスタンスが1000 個生成されてしまいます。
今回の例では、VBox の 子コンポーネントとなる itemRenderer を作成します。
本のタイトルと著者のみを表示するとてもシンプルな itemRenderer ですが、クリックするとその場で itemRenderer が伸長するようにします。
これは下記の2つを使って実現できます。
* itemRenderer に追加情報を持つステートを持たせる
* itemRenderer をスムーズに伸縮させるため Resize トランジションを使用する
itemRenerer のベース ステートはとてもシンプルです:
ExpandedState ステートでは新たな情報を付加して itemRenderer の高さの調節をします。
itemRenderer のサイズ変更にかかる手間は、トランジションをただ追加するのと大差ありません。
この トランジションは fromState と toState プロパティの両方にワイルドカードが指定されているので、ステートが変更される毎に再生されます。
後は、itemRenderer のクリックイベントのハンドラを用意し、そのハンドラ内でステートを変更するだけです。
■ Summary
ステートは、itemRenderer の外観を変更するのにとても良い方法です。
複数の変更をステートとしてまとめることにより、itemRendererへの変更を一斉に反映させることができます。
次回の記事では、UIComponent クラスを継承して効率的な itemRenderer を作成します。
itemRenderers: Part 4: ステート と トランジション
itemRenderer は、視覚的に情報を伝えることにとても優れています。
伝えると一口で言っても、名前を表示するだけのシンプルなものから、たくさんの色を使い表現するもの、時にはインタラクションを含むものまであります。
itemEditor がまさにインタラクティブなコントロールですが、今回の記事の目的ではありませんので割愛します。
今回の記事では、itemRenderer の data プロパティやユーザのアクションにより外観を変化させる itemRenderer について見ていきます。
■ ステート
<mx:State> は、itemRendererの外観を変更するのにとても適した方法です。
ステートはとても簡単に使うことができ、トランジションと組み合わせることにより、ユーザにフィードバックを返して、使い勝手をよくすることができます。
今回のサンプルでは、List コンポーネント用に、MXML を用いた itemRenderer を作成していきます。( ActionScript のみでも作成可能です )
itemRenderer で表示するのは、image, title, author, price と 本を購入するための Button コントロールです。
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" >
<mx:Image id="bookImage" source="{data.image}" />
<mx:VBox height="115" width="100%" verticalAlign="top" verticalGap="0" paddingRight="10">
<mx:Text text="{data.title}" fontWeight="bold" width="100%"/>
<mx:Label text="{data.author}" />
<mx:HBox id="priceBox" width="100%">
<mx:Label text="{data.price}" width="100%"/>
<mx:Button label="Buy" />
</mx:HBox>
</mx:VBox>
</mx:HBox>
もし本の在庫が無ければ、その本が非表示になるようにしましょう( data の <instock> タグの値 ( yes / no ) で在庫の有無を判定します )。
実装を簡単にするために、HBox の id に "priceBox" を指定しています。
この HBox の可視状態を変更することにより、内部の Label と Button 両方の可視状態も変更できるからです。
set data メソッドをオーバーライドしてもいいのですが、ここでは priceBox の可視状態を直接変更せずに、ステートを使ってみます。
<mx:states>
<mx:State name="NoStockState">
<mx:SetProperty target="{priceBox}" name="visible" value="false"/>
</mx:State>
</mx:states>
このタグをルートタグ直下に配置してください。
やりたいことが簡単なわりに少々複雑になってしまっていますが、ステートの使い方がよくわかると思います。
以下の2つのステートが存在します。
・ ベースステート : これはコンポーネントの標準(デフォルト)の状態です。コンポーネントがステートを使っていないときは、この状態になっています。今回の例では、ステートがベースステートの時には priceBox の visible プロパティが true (デフォルト値) になります。 これは instock タグの値が yes の時です。
・ NoStockState : これは instock タグの値が no の時のステートです。 このステートになると <mx:State> タグ内の SetProperty が処理されます。 target はそのSetProperty 処理の対象となるインスタンスを指定します。name プロパティはアップデートするプロパティ名、value プロパティは新しくセットする値です。
set data メソッドは、instock の値に基づいてステートを切り替えます。
override public function set data( value:Object ) : void
{
super.data = value;
if( data )
{
if( data.instock == "yes" )
currentState = "";
else
currentState = "NoStockState";
}
}
currentState は全ての UIComponent コントロールが備えているプロパティであり、現在のステート名を保持しています。 ステートを切り替えると、Flex フレームワークはベースステートから始め、新たなステートへの切り替えに必要な処理を行います。
----------------
itemRenderer は再利用されますので、必ず元の値(状態)に戻す処理を入れてください。itemRenderer に if を書いたら、必ず else も書く必要があります。
----------------
もし興味があればですが、下のコードのように set data メソッドをオーバーライドしない方法もあります。 データバインディングを使って、 root タグの中で currentState の値を直接変更しています。
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="400"
currentState="{data.instock == 'yes' ? '' : 'NoStockState'}" >
インラインで data.instock の値を評価し、その結果を currentState プロパティに代入しています。 トリッキーなので保守性には優れていないかもしれません。
■ エレメントの追加
次の itemRenderer では、instack の値が yes の時にだけ price と 購入ボタンが表示されます。
もちろんステートを使わずに同じことは出来ますが、もし itemRenderer に追加、削除するコントロールが多数ある場合にはステートを使うのが得策です。
なぜなら、itemRenderer の currentState プロパティに値をセットするだけでそれら多数のコントロールを管理できるからです。
単に price と 購入ボタン を削除するだけでなく、在庫切れであることを表示する Label も追加します。
変更を加えたコードは次のようになります。
<mx:states>
<mx:State name="NoStockState">
<mx:SetProperty target="{priceBox}" name="visible" value="false"/>
<mx:AddChild relativeTo="{priceBox}" position="before">
<mx:Label text="-- currently not in stock --" color="#73DAF0"/>
</mx:AddChild>
</mx:State>
</mx:states>
<mx:AddChild> タグで priceBox に Label を追加しています。
priceBox の visible プロパティに false をセットし、代わりにわかりやすい文字列を表示します。
繰り返しますが、set data メソッドをオーバーライドすることで、同じように Label を追加することもできます。また、必要なコンポーネントを前もって追加しておいて、それらの可視状態を後で変更するといった方法も可能です。
しかし、ステートにはわかりやすいメリットがあります。
ステートを使えば、在庫切れ状態の表示に必要な処理がどんなに複雑になっても、NoStockState を編集するだけで十分です。ステートを切り替えるための ActionScript コードを変更する必要はありません。
---------------
Flex Builder の Design View ではステートを編集することができます。
---------------
■ 伸び縮みする List
今回の例は、List コントロールではうまく動作しませんが、VBox と Repeater を用いて実現できます。
もし List コントロールを使用して、そのリストがスクロールされてしまうと伸長させたアイテムの itemRenderer が機能しなくなるかもしれません。
例えば、高さが同じアイテムを複数保持するリストがあるとします。
ここで2番目のアイテムの高さを伸長させると、そのアイテムの高さは他のアイテムよりも高くなります。 ここまでは大丈夫です。
( And there's the catch: the visible items. )
次にそのリストをスクロールさせます。 itemRenderer は再利用されるのを覚えていますよね。 2番目のアイテムがリストの外に出ると、そのアイテムの itemRenderer はリストの最下部のアイテムを表示するために移動します。
その時、その itemRenderer の高さを元に戻す必要があります。
そして、2番目のアイテムが再度見えるようにListをスクロールします。
2番目のアイテムの高さが伸長されたままになっていて欲しいところですが、itemRenderer は前の高さのことを知りません。
以前の記事に書いたように、それらの情報は itemRenderer の data プロパティ、もしくは何かしらの外部ソースから得なければなりません。
itemRenderer のリサイズはとても複雑になりがちなので、頑張って実装するわりには大したものにならないと思います。
もしリサイズが必要なのであれば VBox と Repeater を用いる方法が良いと思います。しかし Repeater を用いる方法の欠点として、Repeater で表示する子コンポーネントのインスタンス全てが生成されてしまいます。
もし Repeater を用いて 1000 個のレコードを表示すると、itemRenderer のインスタンスが1000 個生成されてしまいます。
今回の例では、VBox の 子コンポーネントとなる itemRenderer を作成します。
本のタイトルと著者のみを表示するとてもシンプルな itemRenderer ですが、クリックするとその場で itemRenderer が伸長するようにします。
これは下記の2つを使って実現できます。
* itemRenderer に追加情報を持つステートを持たせる
* itemRenderer をスムーズに伸縮させるため Resize トランジションを使用する
itemRenerer のベース ステートはとてもシンプルです:
<mx:HBox width="100%">
<mx:Label text="{data.author}" fontWeight="bold"/>
<mx:Text text="{data.title}" width="100%" fontSize="12" selectable="false"/>
</mx:HBox>
ExpandedState ステートでは新たな情報を付加して itemRenderer の高さの調節をします。
<mx:states>
<mx:State name="ExpandedState">
<mx:AddChild position="lastChild">
<mx:HBox width="100%">
<mx:Image source="{data.image}"/>
<mx:Spacer width="100%"/>
<mx:Label text="{data.price}"/>
<mx:Button label="Buy"/>
</mx:HBox>
</mx:AddChild>
</mx:State>
</mx:states>
itemRenderer のサイズ変更にかかる手間は、トランジションをただ追加するのと大差ありません。
<mx:transitions>
<mx:Transition fromState="*" toState="*">
<mx:Resize target="{this}" />
</mx:Transition>
</mx:transitions>
この トランジションは fromState と toState プロパティの両方にワイルドカードが指定されているので、ステートが変更される毎に再生されます。
後は、itemRenderer のクリックイベントのハンドラを用意し、そのハンドラ内でステートを変更するだけです。
<mx:Script>
<![CDATA[
private function expandItem() : void
{
if( currentState == "ExpandedState" )
currentState = "";
else
currentState = "ExpandedState";
}
]]>
</mx:Script>
■ Summary
ステートは、itemRenderer の外観を変更するのにとても良い方法です。
複数の変更をステートとしてまとめることにより、itemRendererへの変更を一斉に反映させることができます。
次回の記事では、UIComponent クラスを継承して効率的な itemRenderer を作成します。

