12

通常の列テキストの下に SearchBox (単純なテキスト ボックス) を表示するカスタム列ヘッダーを使用して CellTable を実装しようとしています。
SearchBox は、ユーザーが CellTable をフィルタリングできるようにする必要があります。次のようになります。

  |Header  1|Header 2 |
  |SEARCHBOX|SEARCHBOX|
  -------------------------------------------------------
  |    ROW 1 
  ------------------------------------------------------
  |    ROW 2 

ユーザーが SearchBox に文字を入力するとすぐに、RangeChangeEventが起動され、サーバー リクエストが発生し、CellTable が新しいフィルタリングされたリストで更新されます。

基本的にすべて正常に動作します。ただし、CellTable が更新されるとすぐに、SearchBox はフォーカスを失い、ユーザーはマウスで SearchBox を再度クリックして新しい文字を入力する必要があります。

これはおそらく、CellTable の更新後にカスタム ヘッダーとそのセルの render メソッドが呼び出されることに関連しています。
検索ボックスにフォーカスを戻す方法はありますか? tabindex=0を設定しようとしましたが、役に立ちませんでした。

カスタム ヘッダー クラス

public static class SearchHeader extends Header<SearchTerm> {
    @Override
    public void render(Context context, SafeHtmlBuilder sb) {
        super.render(context, sb);
    }
    private SearchTerm searchTerm;
    public SearchHeader(SearchTerm searchTerm,ValueUpdater<SearchTerm> valueUpdater) {
        super(new SearchCell());
        setUpdater(valueUpdater);
        this.searchTerm = searchTerm;
    }
    @Override
    public SearchTerm getValue() {
        return searchTerm;
    }
 }

カスタム検索セル (カスタム ヘッダーで使用)

isChanged boolean フラグは、ユーザーが SearchBox に何かを入力するとtrueに設定され、 SearchBoxがフォーカスを失うとfalseに戻ります。どの SearchBox がフォーカスを取得するかを区別するために、このフラグを追加しました (複数の SearchBox を使用する場合)。

public static class SearchCell extends AbstractCell<SearchTerm> {

    interface Template extends SafeHtmlTemplates {
        @Template("<div style=\"\">{0}</div>")
        SafeHtml header(String columnName);

        @Template("<div style=\"\"><input type=\"text\" value=\"{0}\"/></div>")
        SafeHtml input(String value);
    }

    private static Template template;
    private boolean isChanged = false;

    public SearchCell() {
        super("keydown","keyup","change","blur");
        if (template == null) {
            template = GWT.create(Template.class);
        }
    }

    @Override
    public void render(com.google.gwt.cell.client.Cell.Context context,
        SearchTerm value, SafeHtmlBuilder sb) {
        sb.append(template.header(value.getCriteria().toString()));
        sb.append(template.input(value.getValue()));
    }

    @Override
    public void onBrowserEvent(Context context,Element parent, SearchTerm value,NativeEvent event,ValueUpdater<SearchTerm> valueUpdater) {
        if (value == null)
            return;
        super.onBrowserEvent(context, parent, value, event, valueUpdater);
        if ("keyup".equals(event.getType()))
        {
            isChanged = true;
            InputElement elem = getInputElement(parent);
            value.setValue(elem.getValue());
            if (valueUpdater != null)
                valueUpdater.update(value);
        }
        else if ("blur".equals(event.getType())) {
            isChanged =false;
        }
     }

     protected InputElement getInputElement(Element parent) {
         Element elem = parent.getElementsByTagName("input").getItem(0);
         assert(elem.getClass() == InputElement.class);
         return elem.cast();
     }
}

CellTable の初期化コード

NameColumnは、適切な型を持つ抽象Columnクラスの実装です。内部でTextCellを使用します。

ValueUpdater<SearchTerm> searchUpdater = new ValueUpdater<SearchTerm>() {
    @Override
    public void update(AccessionCellTableColumns.SearchTerm value) {
        // fires a server request to return the new filtered list
        RangeChangeEvent.fire(table, new Range(table.getPageStart(), table.getPageSize())); 
    }
};

table.addColumn(new NameColumn(searchTerm),new SearchHeader(searchTerm,searchUpdater));
4

2 に答える 2

21

スキニー

残念ながら、カスタム列ヘッダーに対する GWT のサポートは、控えめに言っても少し不安定です。AbstractCell クラスの操作を楽しんだことがある人なら、私の言いたいことが分かるでしょう。さらに、複合 (ネストされたウィジェット) を列ヘッダー セルに実装する適切な方法は失敗です。適切に機能させることができず、CompositeCell が機能する実行可能な例も見つけられませんでした。

データグリッドが並べ替えの ColumnSortHandler を実装している場合 (笑)、キー イベントまたはマウス イベントを持つネストされた UI オブジェクトは、列の並べ替えをトリガーします。不合格。繰り返しになりますが、列ソート イベントをオーバーロードして、ネストされた列ヘッダー UI コンポーネント/ウィジェットとのやり取りによって発生したトリガーを除外する方法が見つかりませんでした。セルを構築する Template インターフェイスにインライン HTML を記述して、ネストされたコンポーネントを抽象的に定義する必要があることは言うまでもありません。開発者はネイティブ JavaScript コードを記述して、列ヘッダー内のネストされたコンポーネント/ウィジェットに関連付けられたハンドラーを作成および制御する必要があるため、洗練された選択とは言えません。

この「適切な」実装手法は、この質問が対処するフォーカスの問題も解決しません。また、列セル フィルタリングまたはカスタム レンダリングを使用した AsyncProvider (または ListProvider) データ セットを必要とする複雑なデータグリッドに対する優れた解決策にはほど遠いものです。これのパフォーマンスもまあまあです>_>適切な解決策にはほど遠いIMO

真剣に???

機能的な列セル フィルタリングを実装するには、GWT およびクレイジーな JQuery ライブラリよりも前の、より伝統的な動的な JavaScript/CSS アプローチからこれに取り組む必要があります。私の機能的な解決策は、「適切な」方法と巧妙な css のハイブリッドです。

疑似コードは次のとおりです。

  1. グリッドが LayoutPanel でラップされていることを確認してください
  2. グリッドの列がコレクション/リストによって管理されていることを確認してください
  3. カスタム列ヘッダーを作成して、フィルタリング用の領域を作成します
  4. テキストボックスを配置するフィルタリングコンテナを作成します
  5. グリッド コンテナーの子 (グリッド、フィルター、ページャー) をレイアウトします。
  6. css テクニックを使用してフィルターを列ヘッダーに配置する
  7. フィルターにイベント ハンドラーを追加する
  8. フィルター入力遅延を処理するタイマーを追加
  9. グリッド更新関数を起動して、データ、非同期、またはローカル リストを更新します

まだあなたを失っていないことを願っています。これを機能させるには、やるべきことがたくさんあります。


手順 1: Grid クラスをセットアップして LayoutPanel を拡張する

最初に、グリッドを作成するクラスがサポートされ、アプリケーションで適切にサイズ設定できることを確認する必要があります。これを行うには、グリッド クラスが LayoutPanel を拡張していることを確認してください。

public abstract class PagingFilterDataGrid<T> extends LayoutPanel {
     public PagingFilterDataGrid() {
          //ctor initializers
          initDataGrid();
          initColumns();
          updateColumns();
          initPager();
          setupDataGrid();
     }
}

ステップ 2: 管理列を作成する

この手順も非常に簡単です。データグリッドに新しい列を直接追加するのではなく、それらをリストに保存してから、 foreach ステートメントを使用してプログラムでグリッドに追加します

ColumnModel (数値や日付など、必要な種類の列を作成できる必要があります。簡単にするために、特別な算術演算や日付機能が明示的に必要でない限り、通常は Web アプリで文字列データを操作します)

public abstract class GridStringColumn<M> extends Column<VwGovernorRule, String> {

    private String  text_;
    private String  tooltip_;
    private boolean defaultShown_ = true;
    private boolean hidden_       = false;

    public GridStringColumn(String fieldName, String text, String tooltip, boolean defaultShown, boolean sortable, boolean hidden) {
        super(new TextCell());
        setDataStoreName(fieldName);
        this.text_ = text;
        this.tooltip_ = tooltip;
        this.defaultShown_ = defaultShown;
        setSortable(sortable);
        this.hidden_ = hidden;
    }
}

データグリッド クラスにリストを作成して、列を格納します

public abstract class PagingFilterDataGrid<T> extends LayoutPanel {
    private List<GridStringColumn<T>> columns_ = new ArrayList<GridStringColumn<T>>();
}

列を作成するには、データグリッド コンストラクターで呼び出される initColumn メソッドを作成します。通常、特定のグリッド初期化子を配置できるように、基本データグリッド クラスを拡張します。これにより、列ストアに列が追加されます。MyPOJODataModel は、データグリッドのレコードを格納するデータ構造であり、通常は休止状態の POJO またはバックエンドからの何かです。

@Override
public void initColumns() {
     getColumns().add(new GridStringColumn<MyPOJODataModel>("columnName", "dataStoreFieldName", "column tooltip / description information about this column", true, true, false) {

            @Override
            public String getValue(MyPOJODataModelobject) {
                return object.getFieldValue();
            }
        });
}

列をグリッドに更新するコードを作成します。initColumns メソッドを呼び出した後に、このメソッドを呼び出すようにしてください。initFilters メソッドについては後ほど説明します。ただし、今知っておく必要がある場合は、コレクション内の列に基づいてフィルターを設定する方法です。列を表示/非表示にしたり、グリッド内の列を並べ替えたりするときはいつでも、この関数を呼び出すこともできます。私はあなたがそれを愛していることを知っています!

@SuppressWarnings("unchecked")
    public void updateColumns() {
        if (dataGrid_.getColumnCount() > 0) {
            clearColumns();
        }

        for (GridStringColumn<T> column : getColumns()) {
            if (!column.isHidden()) {
                dataGrid_.addColumn((Column<T, ?>) column, new ColumnHeader(column.getText(), column.getDataStoreName()));
            }
        }

        initFilters();
    }

ステップ 3: カスタム列ヘッダーを作成する

グリッドと列をフィルタリングする準備ができたので、楽しい作業に取り掛かります。この部分は、この質問が尋ねるコード例に似ていますが、少し異なります。ここで行うことは、GWT が実行時にレンダリングするための html テンプレートを指定する新しいカスタム AbstractCell を作成することです。次に、この新しいセル テンプレートをカスタム ヘッダー クラスに挿入し、gwt のデータがデータ グリッドに新しい列を作成するために使用する addColumn() メソッドに渡します。

カスタム セル:

final public class ColumnHeaderFilterCell extends AbstractCell<String> {

    interface Templates extends SafeHtmlTemplates {
        @SafeHtmlTemplates.Template("<div class=\"headerText\">{0}</div>")
        SafeHtml text(String value);

        @SafeHtmlTemplates.Template("<div class=\"headerFilter\"><input type=\"text\" value=\"\"/></div>")
        SafeHtml filter();
    }

    private static Templates templates = GWT.create(Templates.class);

    @Override
    public void render(Context context, String value, SafeHtmlBuilder sb) {
        if (value == null) {
            return;
        }

        SafeHtml renderedText = templates.text(value);

        sb.append(renderedText);

        SafeHtml renderedFilter = templates.filter();
        sb.append(renderedFilter);
    }
}

カスタム セルの作成方法が嫌いであることを学んでいない場合は、これを実装した後すぐに確信が持てるようになります。次に、このセルを挿入するためのヘッダーが必要です

列ヘッダー:

public static class ColumnHeader extends Header<String> {

        private String name_;

        public ColumnHeader(String name, String field) {
            super(new ColumnHeaderFilterCell());
            this.name_ = name;
            setHeaderStyleNames("columnHeader " + field);
        }

        @Override
        public String getValue() {
            return name_;
        }
    }

ご覧のとおり、これは非常に簡単で単純なクラスです。正直なところ、ラッパーのようなものです.GWTがこれらを特定の列ヘッダーセルに結合することを考えた理由は、一般的なセルを挿入する必要はありません. あまり派手ではないかもしれませんが、作業がはるかに簡単になると確信しています

上記の updateColumns() メソッドを見ると、列を追加するときに、この列ヘッダー クラスの新しいインスタンスが作成されることがわかります。また、非常に大きなデータセットを作成するときにメモリをスラッシングしないように、静的および最終的なものにかなり正確であることを確認してください... 20列のIE 1000行は、20000回の呼び出しまたはテンプレートまたは保存したメンバーのインスタンスです。したがって、セルまたはヘッダーの 1 つのメンバーに 100 バイトがある場合、CTOR だけで約 2MB またはリソース以上になります。繰り返しますが、これはカスタム データ セルのレンダリングほど重要ではありませんが、ヘッダーでも重要です!!!

CSSを追加することを忘れないでください

.gridData table {
    overflow: hidden;
    white-space: nowrap;
    table-layout: fixed;
    border-spacing: 0px;
}

.gridData table td {
    border: none;
    border-right: 1px solid #DBDBDB;
    border-bottom: 1px solid #DBDBDB;
    padding: 2px 9px
}

.gridContainer .filterContainer {
    position: relative;
    z-index: 1000;
    top: 28px;
}

.gridContainer .filterContainer td {
    padding: 0 13px 0 5px;
    width: auto;
    text-align: center;
}

.gridContainer .filterContainer .filterInput {
    width: 100%;
}

.gridData table .columnHeader {
    white-space: normal;
    vertical-align: bottom;
    text-align: center;
    background-color: #EEEEEE;
    border-right: 1px solid #D4D4D4;
}

.gridData table .columnHeader  div img {
    position: relative;
    top: -18px;
}

.gridData table .columnHeader .headerText {
    font-size: 90%;
    line-height: 92%;
}

.gridData table .columnHeader .headerFilter {
    visibility: hidden;
    height: 32px;
}

これが、追加するすべてのもののcssです。私はそれを分離するのが面倒ですが、それを理解できると思います。gridContainer はデータ グリッドをラップするレイアウト パネルであり、gridData は実際のデータ グリッドです。

コンパイルすると、列見出しテキストの下にギャップが表示されるはずです。これは、css を使用してフィルターを配置する場所です。

ステップ 4: フィルター コンテナーを作成する

ここで、フィルター入力を入れる何かが必要です。このコンテナーには css も適用されており、ヘッダーで作成したばかりのスペースに移動します。はい、そうです、ヘッダーにあるフィルターは実際には技術的にヘッダーにありません。これは、並べ替えイベントの問題を回避し、フォーカスの問題を失う唯一の方法です

private HorizontalPanel filterContainer_ = new HorizontalPanel();

そしてあなたのフィルターの初期化

public void initFilters() {
        filterContainer_.setStylePrimaryName("filterContainer");

        for (GridStringColumn<T> column : getColumns()) {
            if (!column.isHidden()) {
                Filter filterInput = new Filter(column);
                filters_.add(filterInput);
                filterContainer_.add(filterInput);
                filterContainer_.setCellWidth(filterInput, "auto");
            }
        }
    }

コンテナーに入るフィルター入力を適切に作成するには、列のコレクションが必要であることがわかります。さらに、列を特定のフィルターにバインドするために、フィルター クラスも列に渡されます。これにより、フィールドなどにアクセスできます

public class Filter extends TextBox {

        final private GridStringColumn<T> boundColumn_;

        public Filter(GridStringColumn<T> column) {
            super();
            boundColumn_ = column;
            addStyleName("filterInput " + boundColumn_.getDataStoreName());
            addKeyUpHandler(new KeyUpHandler() {

                @Override
                public void onKeyUp(KeyUpEvent event) {
                    filterTimer.cancel();
                    filterTimer.schedule(FILTER_DELAY);
                }
            });
        }

        public GridStringColumn<T> getBoundColumn() {
            return boundColumn_;
        }
    }

ステップ 5: コンポーネントを LayoutPanel に追加する

グリッドを初期化してページャーとグリッドをレイアウト コンテナーに追加するときに、フィルターが通常使用する垂直方向の高さを考慮しません。グリッドと列よりも大きい z-index を使用して相対位置に設定されているため、実際にはヘッダーにあるように見えます。マジック!!!

public void setupDataGrid() {
        add(pagerContainer_);
        setWidgetTopHeight(pagerContainer_, 0, Unit.PX, PAGER_HEIGHT, Unit.PX);
        add(filterContainer_);
        setWidgetTopHeight(filterContainer_, PAGER_HEIGHT + FILTER_HEIGHT, Unit.PX, FILTER_HEIGHT, Unit.PX);
        add(dataGrid_);
        setWidgetTopHeight(dataGrid_, PAGER_HEIGHT, Unit.PX, ScreenManager.getScreenHeight() - PAGER_HEIGHT - BORDER_HEIGHT, Unit.PX);


        pager_.setVisible(true);
        dataGrid_.setVisible(true);
    }

そして今、いくつかの定数について

final private static int PAGER_HEIGHT = 32;
final private static int FILTER_HEIGHT = 32;
final private static int BORDER_HEIGHT = 2;

境界線の高さは、アプリが持つ可能性のある特定の css スタイリング用です。技術的には、これはすべてがぴったりと収まるようにするためのスラッグ スペースです。

ステップ 6: CSS マジックを使用する

上からフィルターを列に配置する特定のcssはこれです

.gridContainer .filterContainer {
    position: relative;
    z-index: 1000;
    top: 28px;
}

コンテナーを列の上に移動し、ヘッダーの上にレイヤーを配置します

次に、filterContainer 内のセルがデータグリッド内のセルと整列していることを確認する必要があります

.gridContainer .filterContainer td {
    padding: 0 13px 0 5px;
    width: auto;
    text-align: center;
}

次に、入力が格納されているコンテナ セルのサイズに応じてスケーリングされることを確認します。

.gridContainer .filterContainer .filterInput {
    width: 100%;
}

最後に、フィルター入力テキストボックスがそれらを隠さないように、並べ替え画像インジケーターを上に移動します。

.gridData テーブル .columnHeader div img { 位置: 相対; 上: -18px; }

コンパイルすると、列ヘッダーにフィルターが表示されるはずです。それらを正確に並べるために、css を微調整する必要がある場合があります。これは、特別な列幅が設定されていないことも前提としています。その場合は、セル サイズを手動で設定し、列と同期するように幅をスタイル設定する追加機能を作成する必要があります。私は正気のためにこれを省略しました。


*休憩の時間です。あと少しです!^ _ _ _ _ _ ^*


ステップ 7 & 8: イベント ハンドラーを追加する

これは簡単な部分です。上記のフィルター クラスを見る場合は、このメソッド本体に注意してください。

addKeyUpHandler(new KeyUpHandler() {

                @Override
                public void onKeyUp(KeyUpEvent event) {
                    filterTimer.cancel();
                    filterTimer.schedule(FILTER_DELAY);
                }
            });

フィルター タイマーを作成する

private FilterTimer filterTimer = new FilterTimer();

インラインではなく、クラス本体のル​​ートでフィールドを指定してください。

private class FilterTimer extends Timer {

        @Override
        public void run() {
            updateDataList();
        }
    }

ユーザーが値を入力するたびにイベントが発生しないように、タイマーが必要です。多くの人が onblur やその他のばかげたハンドラーを追加しましたが、それは無意味です。ユーザーは一度に 1 つのフィールドにしかデータを入力できないため、ミュート ポイントになります。onKeyUp ハンドラを使用するだけです。

ステップ 9: グリッドを更新する

updateDataList を呼び出すとき (onRangeChanged イベント (並べ替えとデータの読み込み用) からも呼び出す必要があります) を呼び出すときに、ユーザーが入力した適用済みフィルターのフィルターのコレクションを反復処理します。個人的には、すべての要求パラメーターを簡単なアクセスと更新のためのハッシュマップ次に、マップ全体を RPC または RequestFactory を処理するリクエスト エンジンに渡すだけです

public void updateDataList() {
        initParameters();

        // required parameters controlled by datagrid
        parameters_.put("limit", limit_ + "");
        parameters_.put("offset", offset_ + "");

        // sort parameters
        if (sortField_.equals("") || sortOrder_.equals("")) {
            parameters_.remove("sortField");
            parameters_.remove("sortDir");
        } else {
            parameters_.put("sortField", sortField_);
            parameters_.put("sortDir", sortOrder_);
        }

        // filter parameters
        for (Filter filter : filters_) {
            if (!filter.getValue().equals("")) {
                CGlobal.LOG.info("filter: " + filter.getBoundColumn().getDataStoreName() + "=" + filter.getValue());
                parameters_.put(filter.getBoundColumn().getDataStoreName(), filter.getValue());
            }
        }

        RequestServiceAsync requestService = (RequestServiceAsync) GWT.create(RequestService.class);
        requestService.getRequest("RPC", getParameters(), new ProviderAsyncCallback());
    }

フィルターを列にバインドする必要がある方法と理由がわかるので、フィルターを反復処理すると、格納されているフィールド名を取得できます。通常、すべてのフィルターを単一のフィルター クエリ パラメーターとして渡すのではなく、フィールド名とフィルター値をクエリ パラメーターとして渡すだけです。これははるかに拡張性が高く、上記の sortDir や sortField などのクエリ パラメータの予約語を db 列で == にする必要があるエッジケースはめったにありません。

*完了 < _ __ _ _>*


高度な gwt データグリッド関連の作業で皆さんのお役に立てば幸いです。これは自分自身を作成するのが面倒だったことを知っているので、うまくいけば、これにより多くの時間を節約できます. 幸運を!

于 2012-08-15T18:09:12.180 に答える