1週間でFlexファイラを作ろうプロジェクト 1日目
何となくWeb系の技術も勉強してみたい今日この頃、今Adobe AIRの影で密かに人気上昇中かもしれないWeb技術「Flex」を使って何か作ってみよう! ということで、ブラウザで動くファイラを作っています。
Flexとはなんぞや?という事については、↓のWebサイトが参考になります。
- FlexStoreのデモ - Adobe
- Flex Developer Center - Adobe
- Flex 2.0でリッチなWebアプリを作ろう---目次 - IT pro
- Flexのリファレンス
Ajaxに比べてずっとリッチなUIが書けて、しかもパフォーマンスがなかなか良好です。デフォルトのままでも見た目がキレイなのが嬉しい。アラートを表示すると背景がぼけるなど、なかなかにくい演出してくれます。
では、以下からメモ開始。
0日目
まずは開発環境の準備から。
必要なモノ:
- Flex SDK
- Flexのコマンドラインツール
- Java
- Flex SDKを実行するために必要
- fcsh
- Flex Compiler Shell。コンパイル時間を短縮
- rlwrap
- readline wrapper。fcshを使いやすくする
- いつものエディタといつものシェル
- vimとbash
統合開発環境のFlex Builderは有償ですが、コマンドラインツールのFlex SDKは無償で使用可。最近ユーザー登録が必要になったようです。
fcshについては、ActionScript3 (mxmlc) でのコンパイルを100倍速にする方法 - 川o・-・)<2nd lifeが参考になります。
ダウンロードしてきたら、バッサリと展開。そしてMy $PATHに追加。
$ mkdir ~/self/usr/flex $ cd ~/self/usr/flex $ unzip flex_sdk_2.zip $ unzip flex_sdk_2_ja.zip $ unzip flex_compiler_shell_012307.zip $ export PATH=$PATH:/self/usr/flex/bin # $PATHに~/self/usr/flex/binを追加 $ ls ~/self/usr/flex/bin asdoc* asdoc.exe* compc* compc.exe* fcsh* fcsh.exe* fdb* fdb.exe* jvm.config* mxmlc* mxmlc.exe*
~/self/usr/flex/bin以下のプログラムはシェルスクリプトになっていて、最後にjavaで本体を実行しています。ここで、MacのJavaは何故かデフォルトの文字コードがShift_JISなので、表示が文字化けしてしまいます。しょうがないので最終行を修正。
--- bin.orig/mxmlc 2006-12-07 11:06:44.000000000 +0900 +++ bin/mxmlc 2007-08-21 17:27:22.000000000 +0900 @@ -31,4 +31,4 @@ VMARGS="-Xmx384m -Dsun.io.useCanonCaches=false" -java $VMARGS -jar "$FLEX_HOME/lib/mxmlc.jar" +flexlib="$FLEX_HOME/frameworks" $* +java -Dfile.encoding=UTF8 $VMARGS -jar "$FLEX_HOME/lib/mxmlc.jar" +flexlib="$FLEX_HOME/frameworks" $*
同様に他のスクリプトも修正。
続いてrlwrapをインストール。普通にconfigureでmakeでmake install。
これで快適なFlex開発環境が完成しました。↓こんな感じでコンパイルできます。
$ rlwrap -c fcsh …略… (fcsh) mxmlc hoge.mxml # ←Tabでファイル名の補完が聞く fcsh: Assigned 1 as the compile target id # ←Assigned 1が重要 …略… (fcsh) compile 1 # ←以下compile idで高速にコンパイルできる
1日目
1日目の成果はこのあたり (ここにソースコード)に転がしておきます。
まずはファイル一覧の表示と、ディレクトリの移動まで。
設計としては、サーバー側にRubyで書いたCGI、クライアントでFlexを動かし、CGIとFlex間で通信することで動かそうという案。
通信プロトコル
まずはCGIとFlex間の通信手段を決めるところから。と言ってもHTTPを使うことは確定なので、データフォーマットを決める。
最初はSOAPでにしようと思っていたものの(FlexにSOAPで通信するライブラリがあるので、楽かなーと思った)、WSDLを書くところからしてサッパリ分からなかったので、挫折。
ではXML-RPCは?というと、Flexで使えるXML-RPCのライブラリ(as3-rpclib)を発見。
XML-RPCで注意すべきは、Ruby(1.8)に標準で付いてくるXML-RPCのサーバー(xmlrpc/server)は、XML-RPC要求のContent-typeが「text/xml」でないと受け付けてくれないのですが、as3-rpclibでは「application/xml」を使ってきます。XML-RPC 仕様書を見るに、どうやら「text/xml」が正しいようなので、as3-rpclibを修正して「text/xml」で要求するようにしてみたのですが、そうすると何故か要求がPOSTではなくGETで飛んできます(method="POST"と指定してもGETになる…)。このActionScriptの仕様はサッパリ…
まぁRubyのライブラリ側を書き換えれば良いのですが、それ以前にRESTで良いのでは?ということに気付いたので、XML-RPCはここで終了。
というわけで、要求は http://server:port/flexfile.cgi?command=list&path=/Users/frsyuki という感じで。GET。
応答のXMLは↓こんな手抜きで。
<DirectoryEntryList> <path>/Users/frsyuki</path> <DirectoryEntries> <entry> <name>.</name> <type>directory</type> <size>--</size> </entry> <entry> <name>..</name> <type>directory</type> <size>--</size> </entry> </DirectoryEntries> </DirectoryEntryList>
CGI
こちらはRubyなので、お気楽に。
やはりセキュリティ的にどこでも見られてしまうとマズイので、特定のディレクトリ以下しか見られないように。
キモの部分は以下。
require 'rexml/document' class FlexFile def xmlDirectoryEntries(path, parent) xml = REXML::Element.new("DirectoryEntries", parent) Dir.foreach(path) {|name| xe = REXML::Element.new("entry") stat = File.lstat("#{path}/#{name}") xname = REXML::Element.new("name", xe) xname.text = name xtype = REXML::Element.new("type", xe) xtype.text = stat.ftype xsize = REXML::Element.new("size", xe) if stat.ftype == "file" xsize.text = stat.size else xsize.text = "--" #xsize.text = "-1" end xml << xe } end end
まぁRubyなので。お気楽お気楽。
Flex
本題はこちら。
まずflexfile.mxml。
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init()"> <mx:Script source="flexfile.as"/> <mx:Panel id="window" title="flexfile" width="80%" height="80%"> <mx:DataGrid id="grid" width="100%" height="100%" doubleClickEnabled="true" doubleClick="onDoubleClickData(event)"> <mx:columns> <mx:DataGridColumn dataField="name" headerText="name" width="200"/> <mx:DataGridColumn dataField="size" headerText="size" width="100" textAlign="right" sortCompareFunction="sizeSortCompareFunction"/> <mx:DataGridColumn dataField="type" headerText="type" width="100" textAlign="right"/> </mx:columns> </mx:DataGrid> <mx:ControlBar> <mx:Label id="pathInput" width="100%"/> <!--<mx:TextInput id="pathInput" width="100%"/>--> <mx:Button label="Upload File"/> </mx:ControlBar> </mx:Panel> <mx:HTTPService id="fileService" url="http://localhost:48002/flexfile.cgi" showBusyCursor="true" method="GET" result="onResult(event)" fault="onFault(event)" /> <mx:TextArea width="500" height="200" id="area"/> </mx:Application>
そしてflexfile.as。
// vim:syntax=javascript import mx.controls.Alert; import mx.rpc.events.*; import mx.rpc.http.HTTPService; import mx.utils.ObjectUtil; private var _currentPath:String; private function init():void { sendRequest("/"); } private function sendRequest(path:String):void { fileService.request.command = "list"; fileService.request.path = path; fileService.send(); } private function onResult(e:ResultEvent):void { grid.dataProvider = fileService.lastResult.DirectoryEntryList.DirectoryEntries.entry; _currentPath = fileService.lastResult.DirectoryEntryList.path; pathInput.text = _currentPath; } private function onFault(e:FaultEvent):void { Alert.show(e.fault.faultString); } private function sizeSortCompareFunction(obj1:Object, obj2:Object):int { var num1:Number = obj1.size; var num2:Number = obj2.size; if( isNaN(num1) && isNaN(num2) ) { return 0; } else if( isNaN(num1) && !isNaN(num2) ) { return 1; } else if( !isNaN(num1) && isNaN(num2) ) { return -1; } else if( num1 < num2 ) { return 1; } else if( num1 > num2 ) { return -1; } return 0; } private function onDoubleClickData(event:MouseEvent):void { area.text = _currentPath + "/" + event.currentTarget.selectedItem.name; sendRequest( _currentPath + "/" + event.currentTarget.selectedItem.name ); }
以上で全部です。
ポイントは、
- mxmlの要素を、ActionScript側から、mxmlのid=名で参照できる
- 変数に値を入れると、すぐさま表示も変わる
- mxmlファイル1つ=クラス1つ?
- HTTPの通信はイベントドリブン
- 表にXMLを渡すとそのままデータが入ってくれる
というところ。
表(DataGrid)にXMLを渡すとデータが入ってくれるというのは、なかなか爽快。↓ココの部分。
private function onResult(e:ResultEvent):void { grid.dataProvider = fileService.lastResult.DirectoryEntryList.DirectoryEntries.entry; // …略… }