○Django
Amazon
2011年3月31日木曜日
2011年3月30日水曜日
GQLのLIMIT, OFFSETではバウンドパラメータは無効
2011/3/30現在:
GQLのLIMITおよびOFFSETではバウンドパラメータによる指定は無効となる。
LIMIT_MAX=3
SomeModel.gql("WHERE ID=:1 LIMIT :2", id, LIMIT_MAX)
を指定すると
Google App Engineのディスカッションでは確かにこの議論がされているが
GQLのLIMITおよびOFFSETではバウンドパラメータによる指定は無効となる。
LIMIT_MAX=3
SomeModel.gql("WHERE ID=:1 LIMIT :2", id, LIMIT_MAX)
を指定すると
Parse Error: Non-number limit in LIMIT clause at symbol :2
となる。
Google App Engineのディスカッションでは確かにこの議論がされているが
comment3:LIMIT and OFFSET don't currently support bound parameters.
ドキュメント読むあたり書いてないんだよね。。。
2011年3月29日火曜日
IEでJavaScriptの構文エラーが出たら連想配列の定義を疑ってみる
JavaScriptで連想配列を定義する際に、複数行にわたってコードを記述する時がある。
たとえば
var dictonary={
key: key1,
value: value1
}
このとき注意しなくてはならないのが、最後の要素を記述する際に","をつけてはいけないということだ。
少なくともIEでは構文エラーとなって警告が出てしまう。
これはFirefoxやChromeではエラーにならずIEで実行した時だけ警告が出るのでかえってデバッグが難しい。
それとブラウザはコンテンツをある程度キャッシュしてしまうため、直したコードのアプリケーションやサービスそのものをロードしても、読み込むJavaScriptまでロードしてくれるとは限らない。これまた厄介。URLをもう一度叩いて、さらにF5でリロードする。くらいかなぁ。。。
たとえば
var dictonary={
key: key1,
value: value1
}
このとき注意しなくてはならないのが、最後の要素を記述する際に","をつけてはいけないということだ。
少なくともIEでは構文エラーとなって警告が出てしまう。
これはFirefoxやChromeではエラーにならずIEで実行した時だけ警告が出るのでかえってデバッグが難しい。
それとブラウザはコンテンツをある程度キャッシュしてしまうため、直したコードのアプリケーションやサービスそのものをロードしても、読み込むJavaScriptまでロードしてくれるとは限らない。これまた厄介。URLをもう一度叩いて、さらにF5でリロードする。くらいかなぁ。。。
ラベル:
デバッグ,
Chrome,
Firefox,
IE,
JavaScript
2011年3月26日土曜日
GAEでDirectoryIndexを指定する
例えば、http://www.hogehoge.com/にアクセスすると実はその下にあるhttp://www.hogehoge.com/index.htmlやindex.phpに自動でアクセスするようにすることをDirectoryIndexというらしい。(Apache的に?)
これをApacheのWebサーバーで実現する際には.htaccessファイルにDirectoryIndexという設定項目を記述する。
-----以下こちらより引用
DirectoryIndex index.html index.cgi index.php index.shtml
と書くと、「/(スラッシュ)」止めのURLでアクセスされた場合に表示させるファイル名の優先順位を指定できます。つまり、上のような設定をしている場 合は、index.htmlがそのディレクトリー内にあれば、「/(スラッシュ)」止めのURLでのアクセス時にindex.htmlのコンテンツが表示 されますが(http://www.example.comでアクセスしてもhttp://www.example.com/index.htmlでアクセスしても同一のコンテンツが表示されるということ)、index.htmlが無ければindex.cgiを探しに行き、さらに、それが無ければ、index.phpをと見ていきます。列挙されているファイル名全て見て、どれも存在しなかった場合で、かつ、「Options Indexes」がonに設定されている場合は、そのディレクトリー内のファイル一覧が表示されますので、注意が必要です。「Options Indexes」をonにする場合の危険性については、こちらでも説明していますので、参照してください。
-----
で、同じことをGAEで実現しようとすると「.htaccessはどこにあるんだ?」となる。
少なくともユーザーは.htaccessファイルは触れないので代わりの方法としてapp.yamlに擬似的に記述する。以下がその記述。
app.yamlの記述詳細はGAEドキュメントに譲るとして
------- app.yamlの一部
handlers:
- url: /
static_files: htdocs/index.html
upload: /
- url: /
static_dir: htdocs
-------
この記述がhttp://hogehoge.appspot.com/というアプリケーションのものだとすると、
ユーザーがhttp://hogehoge.appspot.com/にアクセスすると実際はhttp://hogehoge.appspot.com/static/index.htmlに自動転送される。このファイルは別にindex.htmlでなくても良い
これをApacheのWebサーバーで実現する際には.htaccessファイルにDirectoryIndexという設定項目を記述する。
-----以下こちらより引用
DirectoryIndex index.html index.cgi index.php index.shtml
と書くと、「/(スラッシュ)」止めのURLでアクセスされた場合に表示させるファイル名の優先順位を指定できます。つまり、上のような設定をしている場 合は、index.htmlがそのディレクトリー内にあれば、「/(スラッシュ)」止めのURLでのアクセス時にindex.htmlのコンテンツが表示 されますが(http://www.example.comでアクセスしてもhttp://www.example.com/index.htmlでアクセスしても同一のコンテンツが表示されるということ)、index.htmlが無ければindex.cgiを探しに行き、さらに、それが無ければ、index.phpをと見ていきます。列挙されているファイル名全て見て、どれも存在しなかった場合で、かつ、「Options Indexes」がonに設定されている場合は、そのディレクトリー内のファイル一覧が表示されますので、注意が必要です。「Options Indexes」をonにする場合の危険性については、こちらでも説明していますので、参照してください。
-----
で、同じことをGAEで実現しようとすると「.htaccessはどこにあるんだ?」となる。
少なくともユーザーは.htaccessファイルは触れないので代わりの方法としてapp.yamlに擬似的に記述する。以下がその記述。
app.yamlの記述詳細はGAEドキュメントに譲るとして
------- app.yamlの一部
handlers:
- url: /
static_files: htdocs/index.html
upload: /
- url: /
static_dir: htdocs
-------
この記述がhttp://hogehoge.appspot.com/というアプリケーションのものだとすると、
ユーザーがhttp://hogehoge.appspot.com/にアクセスすると実際はhttp://hogehoge.appspot.com/static/index.htmlに自動転送される。このファイルは別にindex.htmlでなくても良い
ラベル:
app.yaml,
DirectoryIndex,
GAE
Opensocialアプリの署名付きリクエスト
mixi Developer Centerにmixiアプリから外部へリクエストするさいに署名付きリクエストを送る方法の記載があるが、Pythonを使ってサーバーサイドロジックを書いている身としては、PHPやJavaのコードで書かれていても困る。。。
ということで調べてみたらこちらのサイトにまさしくビンゴな内容が。
基本的に上記サイトを参照して頂くとして
----- 以下、引用
そこで mixiアプリからのリクエストを検証するサンプルを書いてみました。
利用するためには、次の2つのライブラリが必要です。
これらを 次のように App Engine のプロジェクト直下に置いてください。
だけだと素っ気ないので、signed_request.py の中身を簡単に説明します。通常なら OAuth のクライアントライブラリを使って簡単にできそうなものですが、Google App Engine では openssl のライブラリが使えないので、多くのコンテナで採用されている RSA-SHA1 形式の署名を検証するには少し工夫する必要があります。外部サーバの呼び出し » mDCのページに記載されている公開鍵を mixi.cer という名前のテキストファイルに保存します。そして、この公開鍵を16進数表記に変換します。
* 上記の16進数表記は、変更された後(2011/3/26現在)のmixiの公開鍵を適用しています。
なお、自分の開発環境(Aptana + Python 2.7)ではsigned_request.pyの中の
local_hash = hashlib.sha1(message).digest()
の一行で .sha1がundefined variableエラーを出したままでしたが、とりあえずそのままにしました。
-----
上記がサーバーサイドロジックの変更点。
続いてクライアント側の変更点。
これまで外部へのリクエストを作成する際にgadgets.io.RequestParameters.AUTHORIZATIONパラメータをgadgets.io.AuthorizationType.NONEにしていたら、それをgadgets.io.AuthorizationType.SIGNEDに変更する。付加していなかったのなら追加する。
params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.GET;
params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.TEXT;
params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.SIGNED;
これでリクエストは通るはず。
デコレータを付与した関数に対して、ブラウザから直接アクセスすると"Invalid OAuth Signature."と表示されたので、とりあえずはうまく動いている様子。
ということで調べてみたらこちらのサイトにまさしくビンゴな内容が。
基本的に上記サイトを参照して頂くとして
----- 以下、引用
そこで mixiアプリからのリクエストを検証するサンプルを書いてみました。
利用するためには、次の2つのライブラリが必要です。
これらを 次のように App Engine のプロジェクト直下に置いてください。
nantoka-project/ |-- Crypto/ |-- app.yaml |-- index.py |-- index.yaml |-- oauth.py |-- signed_request.pyCryptoは、下のようにすると簡単に入手できます。
% svn export http://gdata-python-client.googlecode.com/svn/trunk/src/gdata/Crypto準備ができたら、さっそく使ってみます。使い方は、モジュールをインポートして、署名を検証したいリクエスト・メソッドのデコレータとして指定します。動いているアプリケーションに下線部分を追加するだけです。
from google.appengine.ext import webappimport wsgiref.handlers from signed_request import signed_request class Index(webapp.RequestHandler): @signed_request def get(self): # do something self.response.headers['Content-Type'] = 'text/plain' self.response.out.write('OK') def main(): application = webapp.WSGIApplication( [('/', Index),], debug=True) wsgiref.handlers.CGIHandler().run(application) if __name__ == "__main__": main()以上。
だけだと素っ気ないので、signed_request.py の中身を簡単に説明します。通常なら OAuth のクライアントライブラリを使って簡単にできそうなものですが、Google App Engine では openssl のライブラリが使えないので、多くのコンテナで採用されている RSA-SHA1 形式の署名を検証するには少し工夫する必要があります。外部サーバの呼び出し » mDCのページに記載されている公開鍵を mixi.cer という名前のテキストファイルに保存します。そして、この公開鍵を16進数表記に変換します。
% openssl x509 -modulus -noout < mixi.cer | sed s/Modulus=/0x/ B5BAB9467B3920492D5E5759FB7EC58E56AF9EE73826EC2B0F817EE0 D43057056D479CB0560D7411A9D759218801B505C7A865A6960E6F4C 7FDCF04C1BCD3AE372E65D2266BEFE1589150197662CF487B62FF0FD 2954170D68098F1046AA8E4C3BAAC0FC8A7BF246E235B210254209B5 BB896562F72CC55EEA6A483B64948B55signed_request.py 中の MIXI_CERT がこれに当たります。他のコンテナに対応させる場合も同様に変換すれば OK です。
* 上記の16進数表記は、変更された後(2011/3/26現在)のmixiの公開鍵を適用しています。
なお、自分の開発環境(Aptana + Python 2.7)ではsigned_request.pyの中の
local_hash = hashlib.sha1(message).digest()
の一行で .sha1がundefined variableエラーを出したままでしたが、とりあえずそのままにしました。
-----
上記がサーバーサイドロジックの変更点。
続いてクライアント側の変更点。
これまで外部へのリクエストを作成する際にgadgets.io.RequestParameters.AUTHORIZATIONパラメータをgadgets.io.AuthorizationType.NONEにしていたら、それをgadgets.io.AuthorizationType.SIGNEDに変更する。付加していなかったのなら追加する。
params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.GET;
params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.TEXT;
params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.SIGNED;
これでリクエストは通るはず。
デコレータを付与した関数に対して、ブラウザから直接アクセスすると"Invalid OAuth Signature."と表示されたので、とりあえずはうまく動いている様子。
ラベル:
mixi,
OpenSocial,
signedrequest
2011年3月21日月曜日
GQLの注意点(まとめ)
- StringPropertyは500バイト未満まで、それ以上はTextProperty
- リクエスト1 つのクエリに対して最大 1,000 件の結果
IN
と!=
の各演算子は、背後で複数のクエリを使用します。たとえばIN
演算子は、リスト内のどのアイテムについても、基礎となるデータストア クエリを個別に実行します。返されたエンティティは、すべての基礎となるデータストア クエリのクロス積の結果であり、重複が除去されます。1 つの GQL クエリに最大 30 のデータストア クエリが許可されます。(超えた場合はrc=504 errorを返す)- 静的 ListProperty には、空のリストを値として割り当てることができます。このプロパティはデータストアには存在しませんが、モデル インスタンスの動作は値が空のリストである場合と同じです。静的 ListProperty は、
None
の値を持つことはできません。 list
値を持つ動的プロパティには、空のリスト値を割り当てることはできません。ただし、この動的プロパティはNone
の値を持つことができるほか、del
を使用して削除することもできます。
最大エンティティ サイズ | 1 MB |
エンティティのインデックス値の最大数 (1) | 1、000 個 |
バッチ put または delete の最大エンティティ数 | 500 エンティティ |
バッチ get の最大エンティティ数 | 1,000 エンティティ |
クエリの最大結果オフセット | 1,000 |
GAE上のデータストアからデータを消す方法
GAEからデータを削除する方法は公式ドキュメントによれば以下の方法で削除できる。
q = db.GqlQuery("SELECT * FROM Message WHERE create_date < :1", earliest_date)
results = q.fetch(10)
for result in results:
result.delete()
# or...
q = db.GqlQuery("SELECT * FROM Message WHERE create_date < :1", earliest_date)
results = q.fetch(10)
db.delete(results)
但し、GQLには以下の制限があって、保存してあるデータ量が多いと消えない可能性がある。
getMyData, getMyURLを自分のGAEデータモデルに変更すること。
q = db.GqlQuery("SELECT * FROM Message WHERE create_date < :1", earliest_date)
results = q.fetch(10)
for result in results:
result.delete()
# or...
q = db.GqlQuery("SELECT * FROM Message WHERE create_date < :1", earliest_date)
results = q.fetch(10)
db.delete(results)
但し、GQLには以下の制限があって、保存してあるデータ量が多いと消えない可能性がある。
- 一定時間内にリクエストを返すべし
- 一度に取り出せる量が1000件まで
- 使えるリソースの上限を超えた
getMyData, getMyURLを自分のGAEデータモデルに変更すること。
from google.appengine.ext import webapp from google.appengine.ext import db from google.appengine.ext.webapp.util import run_wsgi_app class MyData1(db.Model): pass class MyData2(db.Model): pass class AbstractRemoveAll(webapp.RequestHandler): def get(self) : #limit = 50 limit = 100 list = self.getMyList(limit) if list==None : self.response.headers['Content-Type'] = 'text/plain' self.response.out.write( 'Error.' ) return if len(list)<1: self.response.headers['Content-Type'] = 'text/plain' self.response.out.write( 'OK remove all data.' ) return for c in list: c.delete() self.response.headers['Content-Type'] = 'text/html' self.response.out.write( '<html>' ) self.response.out.write( '<head>' ) self.response.out.write( '<META HTTP-EQUIV="REFRESH" CONTENT="10;URL='+self.getMyUrl()+'">') self.response.out.write( '</head>' ) self.response.out.write( '<body>' ) self.response.out.write( '<p>' ) self.response.out.write( len(list) ) self.response.out.write( '</p>' ) self.response.out.write( 'Again after 10seconds.' ) self.response.out.write( '</body>' ) self.response.out.write( '</html>' ) def getMyUrl(self) : return '/RemoveAll' def getMyList(self,limit): return [] class RemoveAll1(AbstractRemoveAll): def getMyUrl(self) : return '/RemoveAll1' def getMyList(self,limit): q=MyData1.gql('LIMIT '+str(limit)) return q[0:min(q.count(),limit)] class RemoveAll2(AbstractRemoveAll): def getMyUrl(self) : return '/RemoveAll2' def getMyList(self,limit): q=MyData2.gql('LIMIT '+str(limit)) return q[0:min(q.count(),limit)] application = webapp.WSGIApplication( [ ('/RemoveAll2', RemoveAllPage2), ('/RemoveAll1', RemoveAllPage1) ], debug=True) def main(): run_wsgi_app(application) if __name__ == "__main__": main()
2011年3月20日日曜日
JavaScriptのtarget.nameはIEでは無効
JavaScriptでクリックされたターゲットオブジェクトを取得するのにev.target.nameをFirefoxでは使うが、これ、IEではダメらしい。代わりにev.srcElement.nameで取得できる。
そこでブラウザがIEかどうかを判定してスイッチするのが手っ取り早い方法
ev.target.name
// ブラウザがIEならtrueを返す
function IsIE() { return (navigator.userAgent.indexOf("MSIE") != -1); }
function click_button(ev)
var tagname = (IsIE()) ? ev.srcElement.name: ev.target.name;
if (typeof(tagname) == 'string') {
switch (tagname) {
こちらを参考にさせていただきました。
}
そこでブラウザがIEかどうかを判定してスイッチするのが手っ取り早い方法
ev.target.name
// ブラウザがIEならtrueを返す
function IsIE() { return (navigator.userAgent.indexOf("MSIE") != -1); }
function click_button(ev)
var tagname = (IsIE()) ? ev.srcElement.name: ev.target.name;
if (typeof(tagname) == 'string') {
switch (tagname) {
こちらを参考にさせていただきました。
}
2011年3月19日土曜日
OpenSocialアプリのFirefoxでのCSS適用
OpenSocialアプリ(mixiアプリを含む)において、
のような入れ子ページで構成をしている場合、
親のレイアウトをcssで
#parent{
position: absolute;top: 0px;left: 0px;
}
とすると、Firefoxでコンテンツが表示されなくなる。
仕方ないので親からはこのCSS適用を削除。
親のレイアウトをcssで
#parent{
position: absolute;top: 0px;left: 0px;
}
とすると、Firefoxでコンテンツが表示されなくなる。
仕方ないので親からはこのCSS適用を削除。
ラベル:
CSS,
mixi,
OpenSocial
2011年3月18日金曜日
JavaScriptのencodeURIでの改行
encodeURIでエンコードする際、元となる文字列に改行を入れたい場合は\r\nを入れる。
たとえば
改行を入れた
文字列は
これです。
という場合
encodeURI("改行を入れた\r\n文字列は\r\nこれです。")
とする。
encodeURIComponentでも同じ。
たとえば
改行を入れた
文字列は
これです。
という場合
encodeURI("改行を入れた\r\n文字列は\r\nこれです。")
とする。
encodeURIComponentでも同じ。
2011年3月16日水曜日
OpenSocialのrequest方法
OpenSocialアプリを作成する際、JavaScriptのロジックで
例えば自分の情報と友達の情報を取得することがでてくるが、
その際、以下のように2つのメソッドを書いていた。
まだまだあまり理解できていないなぁ。。。
例えば自分の情報と友達の情報を取得することがでてくるが、
その際、以下のように2つのメソッドを書いていた。
function getMyInfo(){ var params = {}; var req = opensocial.newDataRequest(); req.add(req.newFetchPersonRequest(opensocial.IdSpec.PersonId.VIEWER,params), "viewer"); req.send(function(data){ if (data.hadError()) { var msg = data.getErrorMessage(); // エラー発生時の処理 } else { var item = data.get("viewer"); if (!item.hadError()) { var viewer = item.getData(); myID = viewer.getId(); myName = viewer.getDisplayName(); myURL = viewer.getField(opensocial.Person.Field.THUMBNAIL_URL); } }); } function friendNumCheck(){ var fparams = {}; fparams[opensocial.IdSpec.Field.USER_ID] = opensocial.IdSpec.PersonId.VIEWER; fparams[opensocial.IdSpec.Field.GROUP_ID] = "FRIENDS"; var fetchOpt = {}; fetchOpt[opensocial.DataRequest.PeopleRequestFields.MAX] = 1; var idSpec = opensocial.newIdSpec(fparams); var req = opensocial.newDataRequest(); req.add(req.newFetchPeopleRequest(idSpec, fetchOpt), "friends"); req.send(function(data){ if (data.hadError()) { var msg = data.getErrorMessage(); } else { var friends = data.get('friends') if (!friends.hadError()) { var friend = friend.getData(); friendID = friend.getId(); friendName = friend.getDisplayName(); friendURL = friend.getField(opensocial.Person.Field.THUMBNAIL_URL); } } }); }よくよく考えたら、'viewer','friends'のリクエストをreq.addして一回のreq.send(function(data))とする方がリクエスト回数も少ないし、待つ時間も少なくて済む。
まだまだあまり理解できていないなぁ。。。
function getInfo(){ var params = {}; var req = opensocial.newDataRequest(); req.add(req.newFetchPersonRequest(opensocial.IdSpec.PersonId.VIEWER,params), "viewer"); var fparams = {}; fparams[opensocial.IdSpec.Field.USER_ID] = opensocial.IdSpec.PersonId.VIEWER; fparams[opensocial.IdSpec.Field.GROUP_ID] = "FRIENDS"; var fetchOpt = {}; fetchOpt[opensocial.DataRequest.PeopleRequestFields.MAX] = 1; var idSpec = opensocial.newIdSpec(fparams); var req = opensocial.newDataRequest(); req.add(req.newFetchPeopleRequest(idSpec, fetchOpt), "friends"); req.send(function(data){ if (data.hadError()) { var msg = data.getErrorMessage(); // エラー発生時の処理 } else { var item = data.get("viewer"); if (!item.hadError()) { var viewer = item.getData(); myID = viewer.getId(); myName = viewer.getDisplayName(); myURL = viewer.getField(opensocial.Person.Field.THUMBNAIL_URL); var friends = data.get('friends') if (!friends.hadError()) { var friend = friend.getData(); friendID = friend.getId(); friendName = friend.getDisplayName(); friendURL = friend.getField(opensocial.Person.Field.THUMBNAIL_URL); } }); }
2011年3月10日木曜日
JavaScriptでJSON形式を受け取るには?3
GAEのgqlでIN演算子を使う
GAEのGQLのリファレンスによれば
friendInfo = ["f0001","f0002"]
someFriend = FriendModel.gql('WHERE friend IN :1",friendInfo)
という感じでFriendModel内のfriendプロパティ値がfriendInfoにあるアイテムの値("f0001","f0002")であるときに抽出してくる。
とても使いやすいのだけど、この比較するアイテムリストを空にした場合はどうなるか?
直感的には何もヒットしないのが正解と思うのだけど、
実際のところは全部がヒットする。つまり、この場合はWHERE句がないのと同じようです。
someFriend = FriendModel.gql('WHERE friend IN :1 AND gender=:2",friendInfo,gender)
だと、friend IN friendInfoは無視されて、gender=genderの条件だけ適用されるみたい。
なんだか変な感じ。
自分の環境では、このIN演算子は無視されるよう。
"IN
演算子は、プロパティの値とリスト内の各アイテムを比較します。IN
演算子は、各値に 1 つのクエリが OR 演算された多数の =
クエリに相当します。このクエリでは、指定されたプロパティの値がリスト内の値と同じであるエンティティが返されます。"friendInfo = ["f0001","f0002"]
someFriend = FriendModel.gql('WHERE friend IN :1",friendInfo)
という感じでFriendModel内のfriendプロパティ値がfriendInfoにあるアイテムの値("f0001","f0002")であるときに抽出してくる。
とても使いやすいのだけど、この比較するアイテムリストを空にした場合はどうなるか?
直感的には何もヒットしないのが正解と思うのだけど、
実際のところは全部がヒットする。つまり、この場合はWHERE句がないのと同じようです。
someFriend = FriendModel.gql('WHERE friend IN :1 AND gender=:2",friendInfo,gender)
だと、friend IN friendInfoは無視されて、gender=genderの条件だけ適用されるみたい。
なんだか変な感じ。
自分の環境では、このIN演算子は無視されるよう。
2011年3月9日水曜日
OpenSocialにおけるアプリの縦幅の自動調整
mixiデベロッパーサイトより
縦幅の自動調整
mixiアプリが表示するコンテンツは様々です。使える横幅は固定ですが、縦幅については可変となります。この際、mixiアプリが表示したコンテンツに従って最適な縦幅に自動的に調整がかかると非常に便利です。
OpenSocialでは、縦幅の自動調整を行うための関数が提供されています。gadgets.window.adjustHeight()関数を使うことで、縦幅の自動調整を行うことができます。この関数を利用するためには、dynamic-height機能を有効にする必要があります。
dynamic-height機能の利用宣言をタグを使って記述します。これにより、以下のようにしてadjustHeight()関数が使えるようになります。
// do something
gadgets.window.adjustHeight();
adjustHeight()関数の呼び出し直後に、領域の大きさが変更される点に注意します。例えば、mixiアプリの起動時に領域調整を adjustHeight()関数で行いたい場合は、画像や文字列など、通信やDOM操作を一通り終えた後にadjustHeight()関数を呼び出すようにすればよいでしょう。
これで基本は高さが自動調整になるけど、特定の高さにしたければこちらを参照
縦幅の自動調整
mixiアプリが表示するコンテンツは様々です。使える横幅は固定ですが、縦幅については可変となります。この際、mixiアプリが表示したコンテンツに従って最適な縦幅に自動的に調整がかかると非常に便利です。
OpenSocialでは、縦幅の自動調整を行うための関数が提供されています。gadgets.window.adjustHeight()関数を使うことで、縦幅の自動調整を行うことができます。この関数を利用するためには、dynamic-height機能を有効にする必要があります。
dynamic-height機能の利用宣言をタグを使って記述します。これにより、以下のようにしてadjustHeight()関数が使えるようになります。
// do something
gadgets.window.adjustHeight();
adjustHeight()関数の呼び出し直後に、領域の大きさが変更される点に注意します。例えば、mixiアプリの起動時に領域調整を adjustHeight()関数で行いたい場合は、画像や文字列など、通信やDOM操作を一通り終えた後にadjustHeight()関数を呼び出すようにすればよいでしょう。
2011年3月6日日曜日
OpenSocialにおける複数のfriend情報取得方法
OpenSocialアプリケーションで、すでにIDがわかっている複数ユーザーのニックネームやサムネイルURL、プロフィールURLを取得する際にハマッたのでメモしておきます。
もともとやろうとしていたコードが以下。friendIDListは詳細情報を取得したいIDの配列。
たとえば、friendIDList=["friend1","friend2"]
だった場合
friend1
friend2
ではなく
friend2
friend2
となるのだ。
む。。。困ったぞ。
いろいろOpenSocial系のドキュメントを探していたら、リクエストの種類を複数にして1回の問い合わせにする方法が上手くいきそうな感触をつかむ。
そこで直したコードが以下。
という感じに
req.addで複数人の友達情報のリクエストを格納して一度にリクエストを投げる。とうまくいく。
確かにコード実行速度とAPIリクエストのタイミングは随分差があるのと、そんなに無駄にリクエストするのを防ぐために一回にまとめる方法はスマートですね。
【参考】
Opensocial入門を読んで見た(第五章)
もともとやろうとしていたコードが以下。friendIDListは詳細情報を取得したいIDの配列。
function multiFriendRequest(friendIDList){ for (var i = 0; i < friendIDList.length; i++) { var friendID = friendIDList[i]; var params = {}; params[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS] = [opensocial.Person.Field.PROFILE_URL]; var req = opensocial.newDataRequest(); req.add(req.newFetchPersonRequest(friendID), "request"); req.send(function(data){ var viewer = data.get("request").getData(); var name = viewer.getDisplayName(); var url = viewer.getField(opensocial.Person.Field.THUMBNAIL_URL); var purl = viewer.getField(opensocial.Person.Field.PROFILE_URL); var html = "<a href=\"" + purl + "\" >" + name + "<br><img src=\"" + url + "\" \/><\/a>"; document.getElementById("friend" + i).innerHTML = html; } }); } }For文の中にrequestを入れて、複数回リクエストをしようとしたのだが、全てのリクエスト結果がiが最後の値で返ってきた。
たとえば、friendIDList=["friend1","friend2"]
だった場合
friend1
friend2
ではなく
friend2
friend2
となるのだ。
む。。。困ったぞ。
いろいろOpenSocial系のドキュメントを探していたら、リクエストの種類を複数にして1回の問い合わせにする方法が上手くいきそうな感触をつかむ。
そこで直したコードが以下。
funcation multiFriendRequest(friendIDList){ var params = {}; params[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS] = [opensocial.Person.Field.PROFILE_URL]; var req = opensocial.newDataRequest(); for (var i = 0; i < friendIDList.length; i++) { req.add(req.newFetchPersonRequest(friendIDList[i]), "request" + i); } req.send(function(data){ for (var j = 0; j < friendIDList.length; j++) { var viewer = data.get("request" + j).getData(); var name = viewer.getDisplayName(); var url = viewer.getField(opensocial.Person.Field.THUMBNAIL_URL); var purl = viewer.getField(opensocial.Person.Field.PROFILE_URL); var html = "<a href=\"" + purl + "\" >" + name + "<br><img src=\"" + url + "\" \/><\/a>"; document.getElementById("friend" + j).innerHTML = html; } }); }
という感じに
req.addで複数人の友達情報のリクエストを格納して一度にリクエストを投げる。とうまくいく。
確かにコード実行速度とAPIリクエストのタイミングは随分差があるのと、そんなに無駄にリクエストするのを防ぐために一回にまとめる方法はスマートですね。
【参考】
Opensocial入門を読んで見た(第五章)
ラベル:
JavaScript,
OpenSocial
2011年3月5日土曜日
JavaScriptでJSON形式を受け取るには?2
前回に引き続きJavaScriptでJSON形式を送受信するには。
prototype.jsを読み込んでいれば、
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js" charset="UTF-8"></script>
JSON形式に出力する場合はstr.toJSON()かObject.toJSON(var)
JSON形式のデータを解釈するにはjsonStr.evalJSON()かevalJSON(jsonStr)
が使える。
prototype.jsを読み込んでいれば、
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js" charset="UTF-8"></script>
JSON形式に出力する場合はstr.toJSON()かObject.toJSON(var)
JSON形式のデータを解釈するにはjsonStr.evalJSON()かevalJSON(jsonStr)
が使える。
Postのテスト用JavaScript
以下をHTMLのheadの下に追加。
<script type="text/javascript"> <!-- function submitForm(){ var form = document.createElement('form'); document.body.appendChild(form); var input = document.createElement('input'); input.setAttribute( 'type' , 'hidden'); input.setAttribute( 'name' , 'name' ); input.setAttribute( 'value' , 'Nanashi' ); form.appendChild( input );
var input = document.createElement('input'); var array = ["f0001","f0002","f0003"]; input.setAttribute( 'type' , 'hidden'); input.setAttribute( 'name' , 'array' ); input.setAttribute( 'value' , array );
var input = document.createElement('input'); var listArray = [{key11:"value1",key12:"value2"},{key21:"value21",key22:"value22"}]; input.setAttribute( 'type' , 'hidden'); input.setAttribute( 'name' , 'listArray' ); input.setAttribute( 'value' , listArray ); form.appendChild( input ); var input = document.createElement('input');
//配列の配列
var arrayArray = [["p1","p2","p3"],["p3","p5","p1"]]; input.setAttribute( 'type' , 'hidden'); input.setAttribute( 'name' , 'arrayArray' ); input.setAttribute( 'value' , arrayArray ); form.appendChild( input );
// 以下のPOST先は適宜変更
form.setAttribute( 'action' , 'http://localhost:8080/request/post'); form.setAttribute( 'method' , 'post' ); form.submit(); } </script> <body> <input type="button" value="doPost" onClick="JavaScript:submitForm()" /> <a href=“JavaScript:submitForm()”>クリックしたら値POST送信</a> </body> </html>
登録:
投稿 (Atom)