unoh.github.com

Ext.js入門: Grid編

Tue Oct 09 09:38:23 -0700 2007

Extは、ウェブアプリケーションを構築するための、クライアントサイドのJavaScriptフレームワーク公式サイトのFAQより)です。日本ではまだ実際の利用例はあまり多くないようですが、たぶんに日本語のドキュメントがまだそれほど多くはないからではないかと思われ、今後利用事例は増えていくだろうと思われます。

Extの真骨頂は高機能でデザインの洗練された、(直ちに利用可能な)レディメイドのユーザインタフェースウィジェット群です。Extは「アダプタ」として、prototype.jsやjQuery、Yahoo! UIといったJavaScript拡張ライブラリを利用する設計ですが、現在は独自のアダプタも用意され、サードパーティのライブラリに依存しない実装も可能です。

現時点での安定版はバージョン1.1ですが、既に、大幅な機能強化が図られたバージョン2.0のアルファ版が公開されており、以下のページでそのサンプルを見ることができます。

以下ではExtの数あるウィジェットの中でももっともポピュラーなもののひとつであると思われる「グリッド」(表組み)のサンプルコードを紹介します。

Extのグリッドは、列見出しのクリックによる並べ替えやその境界をドラッグすることでの列幅の変更、そして列見出しの右クリックで表示される、こうした機能をまとめたコンテキストメニューなど、表計算ソフトを思わせる豊富な機能があらかじめ用意されています。

なお、以下のサンプルコードは、SCRIPT要素とLINK要素の「path/to/ext/」となっている箇所を実際にExtを配置したディレクトリ名に変更して利用してください。

シンプルなグリッド

以下の例は、あらかじめ定義済みの配列をグリッドで表示します(これは公式サイトのチュートリアル入門編にある例の、ほぼそのままです)。

Extではデータを、データストア(Ext.data.Store)というオブジェクトに保持します。配列をデータストアに読み込むには、以下のようにExt.data.MemoryProxyとExt.data.ArrayReaderを使用します。データがグリッドでどのように表示されるかは、カラムモデル(Ext.grid.ColumnModel)で定義します。

なお以下の画像では、列見出しを右クリックしてコンテキストメニューを表示していますが、メニュー項目が日本語で表示されているのが確認できると思います。Extの構成ファイルには各国語のリソースファイルも含まれており、日本語のリソースファイル(source/locale/ext-lang-ja.js)を読み込むようにSCRIPT要素を追加すれば、表示の多くが日本語化されるようになっています。

Ext Grid Example #1
Ext Grid Example #1 posted by (C)フォト蔵
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <meta http-equiv="Content-Style-Type" content="text/css">
    <meta http-equiv="Content-Script-Type" content="text/javascript">
    <title>TEST</title>
    <script type="text/javascript" src="path/to/ext/adapter/ext/ext-base.js"></script>
    <script type="text/javascript" src="path/to/ext/ext-all.js"></script>
    <script type="text/javascript" src="path/to/ext/source/locale/ext-lang-ja.js"></script>
    <link rel="stylesheet" type="text/css" href="path/to/ext/resources/css/ext-all.css">
    <script type="text/javascript">
    <!--
    /**
     * サンプルデータ
     */
    var myData = [
        ['Apple',       29.89,  0.24,   0.81,   '9/1 12:00am'],
        ['Ext',         83.81,  0.28,   0.34,   '9/12 12:00am'],
        ['Google',      71.72,  0.02,   0.03,   '10/1 12:00am'],
        ['Microsoft',   52.55,  0.01,   0.02,   '7/4 12:00am'],
        ['Yahoo!',      29.01,  0.42,   1.47,   '5/22 12:00am']
    ];

    /**
     * DOMの読み込みが完了した時点で実行される
     */
    Ext.onReady(function() {

        /**
         * データストア
         */
        var ds = new Ext.data.Store({
            /**
             * データストアに配列を読み込む
             */
            proxy:  new Ext.data.MemoryProxy(myData),
            reader: new Ext.data.ArrayReader(
                {id: 0},
                [
                    {name: 'company'},
                    {name: 'price',         type: 'float'},
                    {name: 'change',        type: 'float'},
                    {name: 'percentChange', type: 'float'},
                    {name: 'lastChange',    type: 'date', dateFormat: 'n/j h:ia'}
                ]
            )
        });

        /**
         * カラムモデル
         */
        var cm = new Ext.grid.ColumnModel(
            [
                {header: 'Company',      width: 320, dataIndex: 'company'},
                {header: 'Price',        width: 140, dataIndex: 'price'},
                {header: 'Change',       width: 80,  dataIndex: 'change'},
                {header: '% Change',     width: 80,  dataIndex: 'percentChange'},
                {header: 'Last Updated', width: 180, dataIndex: 'lastChange', renderer: Ext.util.Format.dateRenderer('Y/m/d')}
            ]
        );
        cm.defaultSortable = true;

        /**
         * グリッド
         */
        var grid = new Ext.grid.Grid(
            'grid-example',
            {ds: ds, cm: cm}
        );

        /**
         * グリッドの描画
         */
        grid.render();
        ds.load();
    });
    //-->
    </script>
  </head>
  <body>
    <div style="margin:10px;padding:10px;border:1px solid #000">
      <h1>Ext.grid Example #1</h1>
      <p>A very simple grid.</p>
      <div id="grid-example" style="overflow:hidden;width:800px">
        The example grid will be placed here.
      </div>
    </div>
  </body>
</html>

Ajaxなグリッド

以下の例では、あらかじめ用意してある配列に代えて、Ajaxで読み込んだXMLのデータを表示します。これにはデータストアへのデータの読み込みに、以下のようにExt.data.HttpProxyとExt.data.XmlReaderを使用します。ちなみに、データがJSON形式の場合はExt.data.JsonReaderを使用します。また、Ext.data.ScriptTagProxyを使用してクロスドメインのアクセスも行えます。

以下の例は、Amazon Webサービスで取得したXMLデータが「amazon.xml」というファイル名でこのHTMLと同じディレクトリにあるという構成が前提になっています。

また、以下の例ではカラムモデルにコールバック関数を設定し、アイテムの題名をそのまま表示するのではなく、そのアイテムのAmazon.co.jpのページへのハイパーリンク付きで表示しています。さらに、グリッドに「rowdblclick」イベントを設定し、列をダブルクリックすることでもそのアイテムのページを表示するようにしています。

Ext Grid Example #2
Ext Grid Example #2 posted by (C)フォト蔵
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <meta http-equiv="Content-Style-Type" content="text/css">
    <meta http-equiv="Content-Script-Type" content="text/javascript">
    <title>TEST</title>
    <script type="text/javascript" src="path/to/ext/adapter/ext/ext-base.js"></script>
    <script type="text/javascript" src="path/to/ext/ext-all.js"></script>
    <script type="text/javascript" src="path/to/ext/source/locale/ext-lang-ja.js"></script>
    <link rel="stylesheet" type="text/css" href="path/to/ext/resources/css/ext-all.css">
    <script type="text/javascript">
    <!--
    /**
     * DOMの読み込みが完了した時点で実行される
     */
    Ext.onReady(function() {

        /**
         * データストア
         */
        var ds = new Ext.data.Store({
            /**
             * データストアにAjaxでXMLを読み込む
             */
            proxy:  new Ext.data.HttpProxy(
                {url: 'amazon.xml'}
            ),
            reader: new Ext.data.XmlReader(
                {id: 'ASIN', record: 'Item', totalRecords: '@total'},
                [
                    'DetailPageURL',
                    {name: 'Title',        mapping: 'ItemAttributes > Title'},
                    {name: 'Author',       mapping: 'ItemAttributes > Author'},
                    {name: 'Manufacturer', mapping: 'ItemAttributes > Manufacturer'}
                ]
            )
        });

        /**
         * カラムモデル
         */
        var cm = new Ext.grid.ColumnModel(
            [
                {header: '題名',   width: 480, dataIndex: 'Title', renderer: formatTitle},
                {header: '著者',   width: 140, dataIndex: 'Author'},
                {header: '出版社', width: 180, dataIndex: 'Manufacturer'}
            ]
        );
        cm.defaultSortable = true;

        /**
         * グリッド
         */
        var grid = new Ext.grid.Grid(
            'grid-example',
            {ds: ds, cm: cm}
        );
        grid.on('rowdblclick', openWindow, this);

        /**
         * グリッドの描画
         */
        grid.render();
        ds.load();
    });

    /**
     * カラムモデル用コールバック関数
     */
    function formatTitle(val, metadata, record) {
        return '<a href="' + record.data['DetailPageURL'] + '">' + val + '<\/a>';
    }
    function openWindow(grid, rowIndex) {
        window.open(grid.getDataSource().getAt(rowIndex).data.DetailPageURL, 'foo');
    }
    //-->
    </script>
  </head>
  <body>
    <div style="margin:10px;padding:10px;border:1px solid #000">
      <h1>Ext.grid Example #2</h1>
      <p>Loading XML into a grid w/ Ajax.</p>
      <div id="grid-example" style="overflow:hidden;width:800px">
        The example grid will be placed here.
      </div>
    </div>
  </body>
</html>

ドラッグ&ドロップなグリッド

以下の例は、ふたつのグリッドを並べて表示し、ドラッグ&ドロップ操作で、グリッド間で各行のデータを移動できるようにしたものです。同じ構造のグリッドをふたつ表示するので、データストアとグリッドをふたつずつ定義し、各グリッドのパラメータには「enableDragDrop」と「ddGroup」を設定しています。そしてExt.dd.DropTargetで、各グリッドのドラッグ&ドロップ時の動作を定義しています。

なお、上のふたつの例ではグリッドを単体でそのまま(DIV要素内に)表示していましたが、以下の例では表示にレイアウトマネージャ(Ext.BorderLayout)を使用しています(レイアウトマネージャの詳細は今回は端折ります)。

Ext Grid Example #3
Ext Grid Example #3 posted by (C)フォト蔵
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <meta http-equiv="Content-Style-Type" content="text/css">
    <meta http-equiv="Content-Script-Type" content="text/javascript">
    <title>TEST</title>
    <script type="text/javascript" src="path/to/ext/adapter/ext/ext-base.js"></script>
    <script type="text/javascript" src="path/to/ext/ext-all.js"></script>
    <script type="text/javascript" src="path/to/ext/source/locale/ext-lang-ja.js"></script>
    <link rel="stylesheet" type="text/css" href="path/to/ext/resources/css/ext-all.css">
    <script type="text/javascript">
    <!--
    /**
     * DOMの読み込みが完了した時点で実行される
     */
    Ext.onReady(function() {

        /**
         * データストア
         */
        var ds1 = new Ext.data.Store({
            /**
             * データストアにAjaxでXMLを読み込む
             */
            proxy:  new Ext.data.HttpProxy(
                {url: 'amazon-small.xml'}
            ),
            reader: new Ext.data.XmlReader(
                {id: 'ASIN', record: 'Item', totalRecords: '@total'},
                [
                    'DetailPageURL',
                    {name: 'Title',        mapping: 'ItemAttributes > Title'},
                    {name: 'Author',       mapping: 'ItemAttributes > Author'},
                    {name: 'Manufacturer', mapping: 'ItemAttributes > Manufacturer'}
                ]
            )
        });
        ds2 = new Ext.data.Store({
            reader: new Ext.data.ArrayReader({}, [
                'DetailPageURL',
                'Title',
                'Author',
                'Manufacturer'
            ])
        });

        /**
         * カラムモデル
         */
        var cm = new Ext.grid.ColumnModel(
            [
                {header: '題名',   dataIndex: 'Title', id: 'Title', renderer: formatTitle},
                {header: '著者',   dataIndex: 'Author'},
                {header: '出版社', dataIndex: 'Manufacturer'}
            ]
        );
        cm.defaultWidth = 150;
        cm.defaultSortable = true;

        /**
         * グリッド
         */
        var grid1 = new Ext.grid.Grid(
            'grid-example',
            {ds: ds1, cm: cm, enableDragDrop: true, ddGroup: 'ddGroup1', autoExpandColumn: 'Title', loadMask: {msg: 'しばらくお待ちください'}}
        );
        grid1.on('rowdblclick', openWindow, this);
        var grid2 = new Ext.grid.Grid(
            'grid-example2',
            {ds: ds2, cm: cm, enableDragDrop: true, ddGroup: 'ddGroup2', autoExpandColumn: 'Title'}
        );
        grid2.on('rowdblclick', openWindow, this);

        /**
         * ドラッグ&ドロップ
         */
        var grid1dd = new Ext.dd.DropTarget(grid1.container, {
            ddGroup: 'ddGroup2',
            notifyDrop: function(dd, e, data) {
                for (var i = 0; i < data.selections.length; i++) {
                    if (!ds1.getById(data.selections[i].id)) {
                        ds1.add(data.selections[i]);
                        ds2.remove(data.selections[i]);
                    }
                }
                grid2.getSelectionModel().clearSelections();
                return true;
            }
        });
        var grid2dd = new Ext.dd.DropTarget(grid2.container, {
            ddGroup: 'ddGroup1',
            notifyDrop: function(dd, e, data) {
                for (var i = 0; i < data.selections.length; i++) {
                    if (!ds2.getById(data.selections[i].id)) {
                        ds2.add(data.selections[i]);
                        ds1.remove(data.selections[i]);
                    }
                }
                grid1.getSelectionModel().clearSelections();
                return true;
            }
        });

        /**
         * レイアウト
         */
        layout = new Ext.BorderLayout(document.body, {
            north: {
                split:false,
                initialSize: 60,
                titlebar: false
            },
            center: {
                titlebar:    true,
                resizeTabs:  true
            },
            east: {
                split:true,
                initialSize: 400,
                titlebar:    true,
                collapsible: true,
                collapsed:   true,
                animate:     true
            },
            south: {
                split:false,
                initialSize: 30,
                titlebar: false
            }
        });
        layout.add('north',  new Ext.ContentPanel('header'));
        layout.add('center', new Ext.GridPanel(grid1, {title: '検索結果'}));
        layout.add('east',   new Ext.GridPanel(grid2, {title: 'ドラッグ &amp; ドロップ'}));
        layout.add('south',  new Ext.ContentPanel('footer'));

        /**
         * グリッドの描画
         */
        grid1.render();
        grid2.render();
        ds1.load();
    });

    /**
     * カラムモデル用コールバック関数
     */
    function formatTitle(val, metadata, record) {
        return '<a href="' + record.data['DetailPageURL'] + '">' + val + '<\/a>';
    }
    function openWindow(grid, rowIndex) {
        window.open(grid.getDataSource().getAt(rowIndex).data.DetailPageURL, 'foo');
    }
    //-->
    </script>
  </head>
  <body>
    <div id="header" style="padding:5px">
      <h1>Ext.grid Example #3</h1>
      <p>Drag &amp; drop between two grids in the layout panel</p>
    </div>
    <div id="grid-example" style="overflow:hidden;width:400px">
      The example grid #1 will be placed here.
    </div>
    <div id="grid-example2" style="overflow:hidden;width:400px">
      The example grid #2 will be placed here.
    </div>
    <div id="footer" style="padding:5px">
      <address style="font-size:12px;font-style:oblique;text-align:right">
        2007 Unoh, Inc. All rights reserved.
      </address>
    </div>
  </body>
</html>

なおFirebugがインストールされたFirefoxでは、Extのデバッグコンソールが利用できます。HTMLのSCRIPT要素で「ext-all.js」を読み込んでいるところを「ext-all-debug.js」を読み込むように変更しておくと、Control+Shift+Homeキーをタイプするとデバッグコンソールが表示されます。ただしExtを使用したページは、FirebugがインストールされたFirefoxではかなり表示が遅くなるので、その点は注意が必要です。