Flex の DataGrid

Flex 3 SDK の DataGrid コンポーネントを使って、以前 Ext JS で作成したようなデータグリッドのサンプルを作ってみた。

機能としては以下のようなものを実装した。

  • カスタムソートの設定
  • フィルタリング処理の設定
  • スタイルの変更(選択行の背景色変更など)
  • 指定の URL から XML データを取得
  • JavaScript で選択データを表示

SWF ファイルの作成

ExternalInterface.addCallback("JavaScriptから呼び出す際の関数名", ActionScript側の処理) を使って JavaScript から ActionScript を実行できるようにする。

とりあえず以下のような処理を JavaScript から実行できるようにしてみた。

  • データの読み込み(データ取得先の URL 指定)
  • フィルターの設定処理
  • フィルターのリセット処理

なお、データを XML 形式(e4x)で取得するようにした場合、grid.selectedItems(選択中の行データ)の値は XML なので XML として処理する必要がある。(フィルターやカスタムソートの処理に関しても同様)

今回、grid.selectedItems の値を JavaScript の関数の引数として渡そうとしたのだが思うようにいかなかったので、とりあえずハッシュオブジェクト化して JavaScript の関数に渡すようにした。(たぶん、もっと良い方法があるだろう)

また、XML データではデフォルトで文字列順のソートとなるため、no 項目では数値順のソートとなるように自作のソート処理を実行するようにしている。

sample_datagrid.as ファイル
import mx.collections.ICollectionView;
import mx.controls.Alert;
import mx.events.ListEvent;
import mx.rpc.events.ResultEvent;
import mx.utils.ObjectUtil;
import mx.utils.StringUtil;

//初期化完了時のイベント処理
private function initApp(): void {

    if (ExternalInterface.available) {
        try {
            //データロード処理(JavaScript から実行)
            ExternalInterface.addCallback("load", function(url: String): void {
                service.url = url;
                service.send();
            });

            //フィルターの設定処理(JavaScript から実行)
            ExternalInterface.addCallback("filter", function(filterString: String): void {
                filterString = StringUtil.trim(filterString);

                var func: Function = function(item: Object): Boolean {
                    var data: String = item.name.text();
                    return (data.indexOf(filterString) >= 0);
                };

                setFilter(func);
            });

            //フィルターのリセット処理(JavaScript から実行)
            ExternalInterface.addCallback("resetFilter", function(): void {
                setFilter(null);
            });
        }
        catch(e : Error) {
            Alert.show("error : " + e);
        }
    }
}

//番号順のソート処理
//XML データの no 属性を参照するため、@no で値を取得している
private function sortNumber(obj1: Object, obj2: Object): int {
    return ObjectUtil.numericCompare(toInt(obj1.@no), toInt(obj2.@no));
}

private function toInt(obj: String): Number {
    var result: Number = parseInt(obj);
    return (isNaN(result))? int.MAX_VALUE: result;
}

//フィルターを設定する
private function setFilter(filter: Function): void {
    var dp: ICollectionView = grid.dataProvider as ICollectionView;

    dp.filterFunction = filter;
    dp.refresh();
}

//DataGrid の選択中の行データ(XML)をオブジェクト化する
private function toObjectArray(items: Array): Array {
    var result: Array = [];

    for (var i: int = 0; i < items.length; i++) {
        var item: XML = items[i];

        var obj: Object = {};

        obj.no = item.@no;

        //以下のような実装では XML の要素が省略されていた場合に値が欠落し、
        //JavaScript 側で一手間増えるので、
        //実際は grid.columns の構成を元に
        //ハッシュオブジェクト化した方が良さそう
        for each (var node: XML in item.elements()) {
            obj[node.name()] = escapeString(node.text());
        }

        result.push(obj);
    }

    return result;
}

private function escapeString(data: String): String {

    //JavaScript の関数呼び出し時にエラーが発生するため
    //データ内に @ の文字があればエスケープする
    //他にも " や \ なんかもエスケープする必要ありそう
    return data.replace(/@/g, '\\@');
}

//HTTPService がデータを取得した際のイベント処理
private function onXMLLoad(e: ResultEvent): void {
    try {
        grid.dataProvider = e.result.customer;
    }
    catch (e:Error) {
        Alert.show("error : " + e);
    }
}

//行の選択を変更した際のイベント処理
private function onSelectionChange(e: ListEvent): void {
    //JavaScript の onSelect 関数を実行
    ExternalInterface.call("onSelect", toObjectArray(grid.selectedItems));
}

MXML ファイルでは、HTTPService 要素で取得するデータのフォーマットやデータ取得時に実行されるイベント処理を設定している。接続先 URL は ActionScript 側で設定するので未指定。

sample_datagrid.mxml ファイル
<?xml version="1.0" encoding="UTF-8"?>

<mx:Application styleName="plain" xmlns:mx="http://www.adobe.com/2006/mxml" 
    creationComplete="initApp()">

    <mx:Script source="sample_datagrid.as" />

    <!-- 選択時の背景色など、DataGrid のスタイルを定義 -->
    <mx:Style>
        DataGrid {
            selectionColor: #AAFFAA;
            textSelectedColor: #FF0000;
            rollOverColor: #FFEEEE;
            alternatingItemColors: #FAFFFF, #EEEEFF;
        }
        .dataGridStyles {
            /* DataGrid のヘッダー部分の文字を bold から normal に変更 */
            fontWeight: normal;
        }
    </mx:Style>
    <mx:HTTPService id="service" resultFormat="e4x" result="onXMLLoad(event)" showBusyCursor="true" />

    <mx:DataGrid id="grid" horizontalScrollPolicy="on" 
        allowMultipleSelection="true" percentWidth="100" percentHeight="100" 
        change="onSelectionChange(event)">
        <mx:columns>
            <!-- sortCompareFunction でカスタムソート処理を設定 -->
            <mx:DataGridColumn dataField="@no" headerText="No" width="50" sortCompareFunction="sortNumber" />
            <mx:DataGridColumn dataField="name" headerText="Name" width="200" />
            <mx:DataGridColumn dataField="address" headerText="Address" width="300" />
            <mx:DataGridColumn dataField="tel" headerText="Tel" />

            <mx:DataGridColumn dataField="mail" headerText="E-mail" />

            <mx:DataGridColumn dataField="url" headerText="URL" />

            <mx:DataGridColumn dataField="note" headerText="Note" width="400" />
        </mx:columns>
    </mx:DataGrid>

</mx:Application>

なお、処理対象 XML のサンプルは以下の通り。no だけ属性を使って他は要素を使用。

<?xml version="1.0" encoding="UTF-8" ?>
<list>
    <customer no="1">
        <name>ABC株式会社</name>
        <address>東京都</address>
        <tel>000-111-222</tel>
        <mail>aaa@bbb.ccc</mail>
        <url>http://aaa/bbb</url>
        <note>テスト用のサンプルデータ</note>
    </customer>
    ・・・
</list>
ビルド

Flex 3 SDKmxmlc コマンドを使って .mxml ファイルをビルドし .swf ファイルを作成する。

>mxmlc sample_datagrid.mxml

HTML の作成

作成した .swf ファイルを表示する HTML ファイルを作成。JavaScript の処理を簡素化するために JavaScript 用のライブラリ jQuery を使用している。

index.html
<html>
<head>
<script type="text/javascript" src="jquery-1.2.3.js"></script>
<script type="text/javascript">
<!--
    //onload 時の処理
    $(function() {
        $("#loadButton").bind("click", null, function() {
            document["gridView"].load($("#url").val());
        });

        var filterItem = $("#filter");
        filterItem.bind("blur", null, function() {
            document["gridView"].filter(filterItem.val());
        });

        $("#resetButton").bind("click", null, function() {
            filterItem.val("");
            document["gridView"].resetFilter();
        });
    });

    //行選択時に Flash から呼び出される関数
    function onSelect(items) {
        if (items && items[0]) {

            //最後に選択されたデータを取得
            var item = items[0];

            setValue("data_no", item.no);
            setValue("data_name", item.name);
            setValue("data_address", item.address);
            setValue("data_tel", item.tel);
            setValue("data_mail", item.mail);
            setValue("data_url", item.url);
            setValue("data_note", item.note);
        }
    }

    function setValue(key, value) {
        if (value == undefined) {
            value = "";
        }
        $("#" + key).text(value);
    }
//-->
</script>
</head>
<body>
<div>
    url: <input id="url" type="text" value="data.xml" />
    <input id="loadButton" type="button" value="Load" />
</div>

<!-- Flash の定義 -->
<object id="gridView" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab" width="98%" height="50%">
    <param name="src" value="sample_datagrid.swf" />

    <embed name="gridView" src="sample_datagrid.swf" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" menu="false" width="98%" height="50%"></embed>
</object>

<div>
    filter: <input id="filter" type="text" />
    <input id="resetButton" type="button" value="Reset" />
</div>
<br />

<!-- 選択データの表示 -->
<div>
    No: <span id="data_no"></span>
</div>
<div>
    Name: <span id="data_name"></span>
</div>
<div>
    Address: <span id="data_address"></span>
</div>
<div>
    Tel: <span id="data_tel"></span>
</div>
<div>
    E-mail: <span id="data_mail"></span>
</div>
<div>
    URL: <span id="data_url"></span>
</div>
<div>
    Note: <span id="data_note"></span>
</div>

</body>
</html>

実行

作成した index.html ファイルを Web サーバー経由で実行する。

Web サーバー経由で参照した理由は、ローカルファイルとして開くと(URL が file:///・・・)、ExternalInterface.addCallback の呼び出しで SecurityError #2060 が発生したため。