包含SearchBox和焦点问题的自定义标题的CellTable

Ümi*_*mit 11 java gwt gwt-2.2-celltable

我正在尝试使用自定义列标题实现CellTable,它在正常的列文本下方显示一个SearchBox(简单文本框).
SearchBox应该允许用户过滤CellTable.它应该看起来像这样:

  |Header  1|Header 2 |
  |SEARCHBOX|SEARCHBOX|
  -------------------------------------------------------
  |    ROW 1 
  ------------------------------------------------------
  |    ROW 2 
Run Code Online (Sandbox Code Playgroud)

一旦用户在SearchBox中键入一个字符,就会触发一个RangeChangeEvent,这会导致服务器请求,并使用新的筛选列表更新CellTable.

基本上一切都很好.但是,只要刷新CellTable,SearchBox 就会失去焦点,用户必须再次单击鼠标进入SearchBox才能输入新字符.

这可能与在CellTable刷新后调用自定义标头及其单元格的render方法有关.
有没有办法如何将焦点设置回SearchBox?我试图设置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;
    }
 }
Run Code Online (Sandbox Code Playgroud)

自定义搜索单元格(用于自定义标题)

isChanged布尔标志设置为,当用户键入的东西到搜索盒,并重新设置为,如果在搜寻失去了焦点.我添加了这个标志,以区分哪个SearchBox获得焦点(如果我使用多个SearchBoxes)

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();
     }
}
Run Code Online (Sandbox Code Playgroud)

CellTable的Init代码

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));
Run Code Online (Sandbox Code Playgroud)

1-1*_*x0r 22

瘦子

不幸的是,GWT对自定义列标题的支持至少可以说有点不可思议.如果有人有使用AbstractCell类的乐趣,你就会明白我的意思.此外,将复合(嵌套窗口小部件)实现到列标题单元格中的正确方法是破坏,因为我无法使其正常工作,也没有找到任何可行的CompositeCell工作示例.

如果您的datagrid实现了排序的ColumnSortHandler(LOL即phunny),则可能具有键或鼠标事件的嵌套UI对象将触发列排序.失败.再一次,我找不到重载columnort事件的方法来排除通过与嵌套列标题ui components/widgets交互而触发的触发器.更不用说您需要通过将内联HTML写入构建单元格的模板界面来抽象地定义嵌套组件.这不是一个优雅的选择,因为它迫使开发人员必须编写本机JavaScript代码来创建和控制与列标题中的嵌套组件/小部件相关联的处理程序.

这种"正确的"实现技术也无法解决此问题所解决的焦点问题,并且对于需要具有列单元格过滤或自定义呈现的AsyncProvider(或ListProvider)数据集的复杂数据网格而言,它几乎不是一个很好的解决方案.这个的表现也是meh> _>远非IMO的正确解决方案

认真???

为了实现功能列单元格过滤,您必须从GWT和疯狂的JQuery库之前的更传统的动态javascript/css appoarch中解决这个问题.我的功能解决方案是"适当"方式与一些狡猾的CSS的混合.

伪代码如下:

  1. 确保您的网格由LayoutPanel包装
  2. 确保您的网格列由集合/列表管理
  3. 创建自定义列标题以创建过滤区域
  4. 创建过滤容器以放置文本框
  5. 布局你的网格容器子(网格,过滤器,寻呼机)
  6. 使用css技术将过滤器放入列标题中
  7. 将事件处理程序添加到过滤器
  8. 添加计时器以处理过滤器输入延迟
  9. 消防网格更新功能,用于刷新数据,异步或本地列表

哇,希望我还没有失去你,因为为了使这项工作有很多工作要做


步骤1:设置网格类以扩展LayoutPanel

首先,您需要确保创建网格的类可以在应用程序中支持并正确调整大小.为此,请确保您的网格类扩展LayoutPanel.

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

第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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在datagrid类中创建列表以存储列

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

创建列创建一个在datagrid构造函数中调用的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();
            }
        });
}
Run Code Online (Sandbox Code Playgroud)

现在创建一些代码以将列更新到网格中,确保在调用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();
    }
Run Code Online (Sandbox Code Playgroud)

第3步:创建自定义列标题

现在我们已经开始了解有趣的东西,因为我们已准备好网格和列进行过滤.这部分类似于这个问题的示例代码,但它有点不同.我们在这里做的是创建一个新的自定义AbstractCell,我们特定一个html模板供GWT在运行时呈现.然后我们将这个新的单元格模板注入我们的自定义头类并将其传递给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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您还没有学会讨厌如何制作自定义单元格,那么在完成此操作后,您很快就会确定.接下来我们需要一个头来注入这个单元格

列标题:

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_;
        }
    }
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,这是一个非常简单和简单的课程.老实说它更像是一个包装器,为什么GWT想到将这些组合成一个特定的列标题单元而不是必须注入一个通用单元格超出我.也许不是超级幻想,但我相信它会更容易使用

如果你在updateColumns()方法上面查看,你可以看到它在添加列时创建了这个columnheader类的新实例.另外,请确保您对静态和最终的内容非常精确,这样当您创建非常大的数据集时,您就不会破坏内存...... 20列的IE 1000行是20000个调用或模板实例或您存储的成员.因此,如果您的单元格或标题中的一个成员有100个字节,大约为2MB或资源或更多,仅用于CTOR.同样,这并不像自定义数据单元格渲染那样重要,但它在您的标题上仍然很重要!

现在别忘了添加你的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;
}
Run Code Online (Sandbox Code Playgroud)

现在这就是你要添加它的所有东西的css.我太懒了把它分开,加上我想你可以搞清楚.gridContainer是包装数据网格的布局面板,gridData是您的实际数据网格.

现在编译时,您应该看到列标题文本下方的间隙.这是您将过滤器定位到使用css的位置

第4步:创建过滤器容器

现在我们需要一些东西来把我们的过滤器输入.这个容器还有一个css应用于它,它将向下移动到我们刚刚在标题中创建的空间.是的,正确的是标题中的过滤器实际上并且技术上不在标题中.这是避免排序事件问题并失去焦点问题的唯一方法

private HorizontalPanel filterContainer_ = new HorizontalPanel();
Run Code Online (Sandbox Code Playgroud)

和你的过滤器初始化

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");
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

您可以看到它需要您的列集合才能正确创建进入容器的过滤器输入此外,过滤器类也会在列中传递,以便将列绑定到特定过滤器.这允许您访问字段等

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_;
        }
    }
Run Code Online (Sandbox Code Playgroud)

第5步:将您的组件添加到LayoutPanel

现在,当您初始化网格以将寻呼机和网格添加到布局容器中时,我们不会考虑过滤器通常应占用的垂直高度.由于它被设置为相对位置,z-index为greated,然后是网格和列所具有的,它实际上将显示在标题中.魔法!!!

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);
    }
Run Code Online (Sandbox Code Playgroud)

现在是一些常数

final private static int PAGER_HEIGHT = 32;
final private static int FILTER_HEIGHT = 32;
final private static int BORDER_HEIGHT = 2;
Run Code Online (Sandbox Code Playgroud)

边框高度是针对您的应用程序可能具有的特定CSS样式,技术上这是slug空间,以确保一切紧密.

第6步:使用CSS Magic

将过滤器从上方定位到列上的特定css就是这个

.gridContainer .filterContainer {
    position: relative;
    z-index: 1000;
    top: 28px;
}
Run Code Online (Sandbox Code Playgroud)

这会将容器移动到列上并将位置放在标题上方

接下来我们需要确保filterContainer中的单元格与datagrid中的单元格对齐

.gridContainer .filterContainer td {
    padding: 0 13px 0 5px;
    width: auto;
    text-align: center;
}
Run Code Online (Sandbox Code Playgroud)

接下来确保我们的输入根据他们所居住的容器单元的大小进行扩展

.gridContainer .filterContainer .filterInput {
    width: 100%;
}
Run Code Online (Sandbox Code Playgroud)

最后,我们想要将我们的排序图像指示器向上移动,以便过滤器输入文本框不会隐藏它们

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

现在编译时应该看到列标题上的过滤器.您可能需要调整css以使它们完全对齐.这也假设您没有设置任何特殊的列宽.如果这样做,您将需要创建一些附加功能来手动设置单元格大小并设置宽度样式以与列同步.我已经因为santity而忽略了这一点.


*现在是休息的时间,你几乎就在那里!^ _ __ _ __ _ _ ^*


步骤7和8:添加事件处理程序

这是简单的部分.如果你从上面看过滤器类,请注意这个方法体

addKeyUpHandler(new KeyUpHandler() {

                @Override
                public void onKeyUp(KeyUpEvent event) {
                    filterTimer.cancel();
                    filterTimer.schedule(FILTER_DELAY);
                }
            });
Run Code Online (Sandbox Code Playgroud)

创建过滤器计时器

private FilterTimer filterTimer = new FilterTimer();
Run Code Online (Sandbox Code Playgroud)

确保在类主体的根目录中指定字段而不是内联.

private class FilterTimer extends Timer {

        @Override
        public void run() {
            updateDataList();
        }
    }
Run Code Online (Sandbox Code Playgroud)

需要一个计时器,以便每次用户输入值时不会触发事件.很多人都添加了onblur或其他愚蠢的处理程序,但它毫无意义.用户一次只能将数据输入到一个字段中,因此它是一个静音点.只需使用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());
    }
Run Code Online (Sandbox Code Playgroud)

您可以看到我们需要将过滤器绑定到列的方式和原因,因此当我们迭代过滤器时,我们可以获取存储的字段名称.通常我只是将fieldname和filter值作为查询参数传递,而不是将所有过滤器作为单个过滤器查询参数传递.这是更具可扩展性的方式,并且很少有你的数据库列应该为您的查询参数保留字的边框,例如上面的sortDir或sortField.

*完成< _ __ _ _>*


好吧,我希望能帮助你们所有人提供一些先进的gwt数据网格.我知道创造自己是一种痛苦,所以希望这将为你节省大量的时间.祝好运!