Amazon

2011年3月31日木曜日

Django関連の有用リンク



○Django

Django v1.0 documentation

Djangoチュートリアル(前編) 

Djangoチュートリアル(後編)

CodeZineのDjangoチュートリアルで7つの詰まったところ

Python Webフレームワーク、第1回: DjangoとPythonを使ってWeb開発

初めてのDjangoプロジェクト


GAE関連の有用ページ


○基本編



○応用編

Google App Engine の Model を JSON に変換する方法

Google App Engine で JSON 出力

GAEとjavascriptでjsonをやり取り

 

 

mixiアプリ開発関連の有用ページ


○基本編

ミクシィ開発陣直伝! 今日からはじめるmixiアプリ開発

○実践編


○応用編 

Facebookの有用リンク

○基本編

新・Facebookページ(ファンページ)が登場!今すぐチェックすべき重要ポイント10

FacebookページにiFrameを組み込もう

○応用編



 

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)
を指定すると
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でリロードする。くらいかなぁ。。。

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でなくても良い

Opensocialアプリの署名付きリクエスト

mixi Developer Centerにmixiアプリから外部へリクエストするさいに署名付きリクエストを送る方法の記載があるが、Pythonを使ってサーバーサイドロジックを書いている身としては、PHPやJavaのコードで書かれていても困る。。。

ということで調べてみたらこちらのサイトにまさしくビンゴな内容が。
基本的に上記サイトを参照して頂くとして


----- 以下、引用
そこで mixiアプリからのリクエストを検証するサンプルを書いてみました。
利用するためには、次の2つのライブラリが必要です。
これらを 次のように App Engine のプロジェクト直下に置いてください。
nantoka-project/
|-- Crypto/
|-- app.yaml
|-- index.py
|-- index.yaml
|-- oauth.py
|-- signed_request.py
Cryptoは、下のようにすると簡単に入手できます。
% 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
BB896562F72CC55EEA6A483B64948B55
signed_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."と表示されたので、とりあえずはうまく動いている様子。

2011年3月21日月曜日

CSSのTipsまとめ

CSS によるブロック要素の左右中央揃え

異なるサイズの画像を縦横中央配置にしてリスト状に並べる

CSSでボックスの内容物を上下中央揃えにする方法3種

CSS float

IE6/7/8でもCSS3のようにボックスにシャドウをつけるスタイルシート

Webセーフカラー

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には以下の制限があって、保存してあるデータ量が多いと消えない可能性がある。
  • 一定時間内にリクエストを返すべし
  • 一度に取り出せる量が1000件まで
  • 使えるリソースの上限を超えた
そこでこちらのサイトを参考にしながら、制限を超えないように一回で100件消すリクエストを発行するpythonのスクリプトを作り、全部消えるまで自動でリロードするようにする。
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) {

こちらを参考にさせていただきました。


}

2011年3月19日土曜日

OpenSocialアプリのFirefoxでのCSS適用

OpenSocialアプリ(mixiアプリを含む)において、
のような入れ子ページで構成をしている場合、
親のレイアウトをcssで
#parent{
position: absolute;top: 0px;left: 0px;
}
とすると、Firefoxでコンテンツが表示されなくなる。
仕方ないので親からはこのCSS適用を削除。

2011年3月18日金曜日

JavaScriptのencodeURIでの改行

encodeURIでエンコードする際、元となる文字列に改行を入れたい場合は\r\nを入れる。

たとえば

改行を入れた
文字列は
これです。

という場合

encodeURI("改行を入れた\r\n文字列は\r\nこれです。")
とする。

encodeURIComponentでも同じ。

2011年3月16日水曜日

スタイルシートの有用リンク

使用できるフォント一覧
カラーパレット

OpenSocialのrequest方法

OpenSocialアプリを作成する際、JavaScriptのロジックで
例えば自分の情報と友達の情報を取得することがでてくるが、
その際、以下のように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

ローカルにjson2.jsを落としてくる。

JSオブジェクトJSON文字列にしたい場合は、JSON.stringify(value, replacer, space)を利用できます。
   
valueはJSオブジェクト
replacerは省略可能で、function(key, value)と言うシグネチャ関数オブジェクトを渡します。JS文字列の変換ルーチンを独自に提供できます。
spaceは、結果の文字列を人間が読みやすくするための、インデントの数を指定します。

その逆に、JSON文字列JSオブジェクトに復元したい場合は、JSON.parse(text, reviver)を使用します。

GAEのgqlでIN演算子を使う

GAEのGQLのリファレンスによれば 
"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()関数を呼び出すようにすればよいでしょう。



これで基本は高さが自動調整になるけど、特定の高さにしたければこちらを参照

2011年3月6日日曜日

OpenSocialにおける複数のfriend情報取得方法

OpenSocialアプリケーションで、すでにIDがわかっている複数ユーザーのニックネームやサムネイルURL、プロフィールURLを取得する際にハマッたのでメモしておきます。

もともとやろうとしていたコードが以下。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入門を読んで見た(第五章)

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)

が使える。

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>

Amazon3