1週間でFlexファイラを作ろうプロジェクト 1日目

何となくWeb系の技術も勉強してみたい今日この頃、今Adobe AIRの影で密かに人気上昇中かもしれないWeb技術「Flex」を使って何か作ってみよう! ということで、ブラウザで動くファイラを作っています。
Flexとはなんぞや?という事については、↓のWebサイトが参考になります。

Ajaxに比べてずっとリッチなUIが書けて、しかもパフォーマンスがなかなか良好です。デフォルトのままでも見た目がキレイなのが嬉しい。アラートを表示すると背景がぼけるなど、なかなかにくい演出してくれます。


では、以下からメモ開始。

0日目

まずは開発環境の準備から。

必要なモノ:

Flex SDK
Flexのコマンドラインツール
Java
Flex SDKを実行するために必要
fcsh
Flex Compiler Shell。コンパイル時間を短縮
rlwrap
readline wrapper。fcshを使いやすくする
いつものエディタといつものシェル
vimbash

統合開発環境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;
        // …略…
}