logo

サイト内検索
ココログ最強検索 by 暴想

最近のトラックバック

無料ブログはココログ

ext.js

Gwt Ext 特定のHTMLエレメントの属性を調整する

  • ソースを表示して文中のリンクをクリックすると該当行が選択されます。
  • GwtExtの実装例ですが、ext.jsでも同じ考えで行けると思います。

CSS selectorの話題が出たところでGwtで特定のHTMLエレメントの属性を調整する技について少し。
GWTのapiやext.jsのコンフィグオプションなどだけでHTMLの細かい属性をすべて設定するのは無理なようで、たとえば<div>等に指定するalign属性は、api等ではあちこち探したが設定する方法が無い。たとえば、上記「ソース表示」ボタンを押して出るWindowですが、これがほって置くとblogで指定されたalignがcenterなので文字が真ん中によってでます。↓
Untitled
上記画像をクリックしてみてもらえばわかるようにソースを見るのには非常に悲しい状態になります。
これのalignをleftにするには上位のタグのどこかでalign=leftとしてやればよいのだが、ext.jsのWindowにalignを調整する機能は見当たらない。少々悩んだが、こうなりゃDOMのapiでやっちまえ、ということで以下のように(113)しました。

        DOM.setElementProperty(w.getElement(), "align", "left");

単純にWindowのエレメントを取ってきてプロパティをセットするだけです。

GwtExt Gridの特定行までスクロールする

の記事にも書いた CSS selectorを使用すれば生成されるHTMLの特定のエレメントを捕まえることができるので、あとはDOM.setElementPropertyとやってしまえば、なんでもありということです。(多分)

GwtExt Gridの特定行までスクロールする

  • ソースを表示して文中のリンクをクリックすると該当行が選択されます。
  • GwtExtの実装例ですが、ext.jsでも同じ考えで行けると思います。
     

スクロール可能なGridの場合、表示されるたびにスクロール位置が最初に戻って不便だったりしませんか?
上記でも使用している「ソースの表示」機能で、ある特定のまでプログラムからスクロールさせる方法実装しましたので紹介します。

上記「ソースの表示」ボタンを押すと現れるソースはこのソースの表示機能自体を実現しているソースです。この中で特定行までスクロールする機能の部分は以下のところ(170-185)

// スクロール位置調整
Element[] es = Ext.get(gp.getId()).query(
		"*[class*=x-grid3-row-table]");
Element con = Ext.get(gp.getId()).child(
		"*[class*=x-grid3-scroller]");
int p = is[0];
if (p > 2) {
	p = p - 2;
}
int ep = is[is.length - 1];
if (ep < es.length - 3) {
	ep = ep + 2;
}
w.setPosition(w.getPosition()[0], Ext.getDoc().getScroll()[1]);
new ExtElement(es[p]).scrollIntoView(con, true);
new ExtElement(es[ep]).scrollIntoView(con, true);

Gridの各行はx-grid3-row-tableというCSSのクラスが設定されているので、170-172のようにExtElementのqueryメソッドを発行するとGrid gpの配下の該当のCSSが設定されているElementの配列が取れます。つまりこれが、全行の配列になります。一方、int配列isに選択すべき行番号が入っています。多少上下に余裕を持ったところに位置づけたいので前後2行ずつ足し引きしてスクロール範囲に入れたい範囲の上限行と下限行をp,epに得ます。
実際に、スクロールするのは184-185。conはExtElementのchildメソッドでスクロールをしているコンテナのエレメントを捕まえたものです。scrollIntoViewメソッドでconの中のes[p]とes[ep]を表示エリアに出せ!とやって出来上がりです。
なお183は現在のブラウザー自体の縦スクロール位置にソースのWindowを持ってくる処理です。Ext.getDoc().getScroll()で現在のブラウザー自体のスクロール位置が取れるので、縦方向のみそこに位置づけしております。

ちなみに、queryやchildメソッドで引数にしている

"*[class*=x-grid3-scroller]"

という文字列はCSS selectorというもので、HTML上の特定のエレメントを捕まえるための文法でたとえばこの辺↓
http://pigs.sourceforge.jp/wiki/index.php?CSS%2Fselector

に説明があります。

GwtExt FormPanelとGridの連携

  • ソースを表示して文中のリンクをクリックすると該当行が選択されます。
  • GwtExtの実装例ですが、ext.jsでも同じ考えで行けると思います。

Untitled

FormPanelからgetForm().loadRecord(record)を実行することで、Grid上の特定のレコードの内容をFormPanelに簡単に表示することができます。
http://www.gwt-ext.com/demo/#formGridにサンプルコードが掲載されているが、このケースはGrid→FormPanelは実装されているが、フォーム上で編集した内容をGrid側のデータに書き戻す部分が無かったのでそこを追加実装した例を示します。

前準備

Gridで使用しているStoreにFieldDefの配列をセットする(206-210)。ここで指定した文字列がFieldのカラム名になる。
    private static RecordDef recordDef = new RecordDef(new FieldDef[] {
            new StringFieldDef("company"), new FloatFieldDef("price"),
            new FloatFieldDef("change"), new FloatFieldDef("pctChange"),
            new DateFieldDef("lastChanged", "n/j h:ia"),
            new StringFieldDef("symbol"), new StringFieldDef("industry") });

FormPanelにのせるTextFieldのオブジェクト生成時に上記カラム名と一致したnameを付ける。(89-95)この例ではコンストラクターの2つ目の引数がnameになる。

        // the field names msut match the data field values from the Store
        fieldSet.add(new TextField("Name", "company", 120));
        TextField priceField = new TextField("Price", "price", 120);
        fieldSet.add(priceField);
        TextField pctChangeField = new TextField("% Change", "pctChange", 120);
        fieldSet.add(pctChangeField);
        fieldSet.add(new DateField2("Last Updated", "lastChanged", 120));

GridからFormPanelへ連携

まず、Grid→FormPanelの反映する部分をおさらいすると、以下の部分(49-62)

        final RowSelectionModel sm = new RowSelectionModel(true);
        sm.addListener(new RowSelectionListenerAdapter() {
            public void onRowSelect(RowSelectionModel sm, int rowIndex,
                    Record record) {
                formPanel.getForm().loadRecord(record);
            }
        });

行をGridから選択した際、RowSelectionModelのonRowSelectアクションで選択されたrecordが入手できるので、これをformPanel.getForm().loadRecord(record)とやるだけでOK。

FormPanelからGridへ連携(121-137)

逆の連携は少々手間がかかる。まず、FormPanel上のすべてのFieldのonChangeイベントを拾うListenerを仕込んでおいて、Fieldの変更時に該当のRecord側に値を設定する。

    private void setFieldListeners(FormPanel fp, final RowSelectionModel sm) {
        // FormPanel上のすべてのFieldにChangeイベントを拾うListenerを追加
        Field[] fds = fp.getFields();
        for (int i = 0; i < fds.length; i++) {
            Field fd = fds[i];
            fd.addListener(new FieldListenerAdapter() {
                public void onChange(Field field, Object newVal, Object oldVal) {
                    // Fieldの値変更時にRecordへ反映
                    Record record = sm.getSelected();
                    if (!(record.getAsString(field.getName()).equals(newVal))) {
                        setAsString(SampleGrid.recordDef, record, field);
                    }
                }
            });
        }
    }

ここで注意!

上記Listenerの設定は、RootPanel.get().add(panel);でFormPanelがrender(表示)された後でないと有効にならないようだ。だから、以下のように(115-117)最後に行う。

        RootPanel.get().add(panel);
        setFieldListeners(formPanel, gridPanel.getSelectionModel());

値の設定(139-159)はRecord定義時のRecordDefから型を判断してセットする。FloatFieldDefの場合はdoubleへの変換。DateFieldDefの場合はDateFieldのgetDateでDateオブジェクトを取る。

    private void setAsString(RecordDef def, Record record, Field field) {
        if (!field.validate()) {
            return false;;
        }
        FieldDef[] fdefs = def.getFields();
        for (int i = 0; i < fdefs.length; i++) {
            FieldDef fdef = fdefs[i];
            String id = field.getName();
            if (fdef.getName().equals(id)) {
                if (fdef instanceof FloatFieldDef) {
                    record.set(id, new Double(field.getValueAsString())
                            .doubleValue());
                } else if (fdef instanceof DateFieldDef) {
                    DateField df = (DateField) field;
                    record.set(id, df.getValue());
                } else {
                    record.set(id, field.getValueAsString());
                }
            }
        }
    }

さて、これでOKと思ったのだが、このまま動かすとFieldを編集した直後にGridをクリックして直接他の行に選択を移したケースで変更が反映されない事が判明。デバッグした結果、なぜかonRowSelectイベントがFieldのonChangeよりも前に呼ばれる事が原因だった。onRowSelectで新たに選択した行のrecordでFieldを上書きしてしまうので、編集したデータが失われる(onChangeをonBlurにしても同じ。納得いかないが..)
仕方が無いのでonDeSelectイベントを拾って、ここでも変更されたfieldの値をrecordに反映する処理(51-62)を行う事にした。

            public void onRowDeselect(RowSelectionModel sm, int rowIndex,
                    Record record) {
                // 行の選択が解除された時の処理
                // 変更されたfieldの値をrecordに反映
                Field[] fs = formPanel.getFields();
                for (int i = 0; i < fs.length; i++) {
                    Field field = fs[i];
                    if (field.isDirty()) {
                        setAsString(SampleGrid.recordDef, record, field);
                    }
                }
            }

入力チェックの実装

priceとpctChangeはdoubleに変換する都合上変な値を入力されると困るので、入力チェックを実装しましょう。正規表現を使用して以下のようにsetRegexに正規表現をしていすればOK。なお、この機能は内部的にRegExp.test()メソッドでチェックをしているので、regexの最初と最後にちゃんと^と$を入れないとザル状態になりますので注意。(そうしないと正規表現に一致する部分がどこかにあればOKになってしまう。これで少々ハマッタ)

        // 正規表現とalowblankによる入力チェック
        String regex = "^[-+]?[0-9]+(\\.[0-9]*)?$";
        String regexText = "数値が無効です";
        String blankText = "空白には出来ません";
        priceField.setRegex(regex);
        priceField.setRegexText(regexText);
        priceField.setAllowBlank(false);
        priceField.setBlankText(blankText);
        pctChangeField.setRegex(regex);
        pctChangeField.setRegexText(regexText);
        pctChangeField.setAllowBlank(false);
        pctChangeField.setBlankText(blankText);

なおsetAllowBlank(false)は空白値を許さない設定です。

ext.jsについて

Ext JSについて

GwtExt/ExtGWTを使うにはExt JSの知識が多少なりとも必要になります。そこでExt JSの基本的な話を少々書きます。Ext JSはAjaxアプリケーションを構築するためのJavaScriptライブラリ(フレームワーク)です。画面デザインの観点からいうと画面を構成する部品単位にすでにCSSも含めて作成済みのものが使用できることから、画面設計をより手っ取り早く行うことが出来ます。

Ext Jsの部品のサンプル
http://extjs.com/products/extjs/

コンフィグオプションによるコンポーネントの生成

Ext Jsの特徴としてコンフィグオプションがあげられます。コンポーネントの属性を設定するのに、GWTの場合は主にsetterを使用して設定しますが、Ext Jsではコンポーネント生成時にコンストラクターにコンフィグオプションという文字列(JSONだと思う)を与えることで基本的に(多分)すべての属性を決めることが出来ます。
コンフィグオプションは以下のように{}でオブジェクトひとつを表し、属性は 属性名:値 という書式になります。また配列は[]を使用します。(つまりJSON書式)
{labelWidth: 75,
   width: 350,
   title: 'FormPanel',
   layout: 'form',
   xtype: 'form',
   defaultType: 'textfield',
   buttons: [
     {text: 'Save'
     },
     {text: 'Cancel'
     }
   ],
   items: [
     {fieldLabel: 'First Name',
       name: 'first',
       allowBlank: false
     },
     {fieldLabel: 'Last Name',
       name: 'last'
     },
     {fieldLabel: 'Company',
       name: 'company'
     },
     {fieldLabel: 'Email',
       name: 'email',
       vtype: 'email'
     }
   ]
}
ここでitemsという属性はこのコンポーネントに含まれる他のコンポーネントの配列を意味します。したがってコンポーネントの入れ子になった構造もすべて一つの文字列だけですべて表現できる事を意味しています。上記コンフィグオプションは以下のような四つのtextfieldと二つのbuttonを含むFormPanelのインスタンスを生成します。

Ext01_2

xtype

xtype属性はExt Jsのコンポーネントのクラスを決定する重要な属性です。xtypeによりインスタンスのクラス自体も決定できるという事は言ってみればコンフィグオプションだけですべてを表現できる事を意味しています。xtypeとクラスの関係はExt JsのApiドキュメントのComponentの項にあります。
http://docs.ext-japan.org/docs/

layout

Panelクラスのlayout属性を設定することで様々な機能を持ったパネルのコンポーネントを作ることができます。

レイアウトの例


form layout

layout: 'form'
xtype: 'panel'またはxtype'form'
子のコンポーネントが縦に並ぶlayoutです。GWTのVerticalPanelに相当します。また、Ext.formパッケージのコンポーネントを貼り付けるにはform layoutが必要です。
xtype'form'の場合は、クラスがFormPanelとなり、submit機能等が付加されます。

column layout

layout: 'column'
xtype: 'panel'
子のコンポーネントを横に並べるlayoutです。基本的に貼り付ける各コンポーネントの位置はそれぞれのコンポーネントのcolumnWidth属性で決定します。

border layout

layout: 'border'
xtype: 'panel'
 Ext02_3

東西南北+中心のエリアを定義できるパネルです。GWTのDockPanelに相当します。しかも、オプションでボーダーをドラッグして大きさを配分を変化させる機能を付加することが出来ます。

TabPanel

layout: 'card'
xtype: 'tabpanel'
Ext03
タブによる選択を可能にするパネルです。

AcordionLayout

layout: 'acordion'
xtype: 'panel'
複数のパネルを重ねてそのうちひとつのパネルを選択可能にするレイアウトです。

GridPanel

layout: 'grid'
xtype: 'grid'
Ext04
データ表形式のパネル。元データとの連携機能も実装された強力なパネル。

TableLayout

layout: 'table'
xtype: 'panel'
Ext05
HTMLのテーブル構造を元にレイアウトできるパネルです。