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

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

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

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

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

这可能与在CellTable刷新后调用自定义标头及其单元格的render方法有关。
有没有办法如何将焦点设置回SearchBox? 我试图设置tabindex = 0但它没有帮助。

自定义标题类

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

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

当用户在SearchBox中键入内容时, isChanged布尔标志设置为true ,如果SearchBox失去焦点,则设置回false 。 我添加了这个标志,以区分哪个SearchBox获得焦点(如果我使用多个SearchBoxes)

 public static class SearchCell extends AbstractCell { interface Template extends SafeHtmlTemplates { @Template("
{0}
") SafeHtml header(String columnName); @Template("
") 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 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的Init代码

NameColumn是具有适当类型的抽象Column类的实现。 它在内部使用TextCell

 ValueUpdater searchUpdater = new ValueUpdater() { @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)); 

瘦子

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

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

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

认真???

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

伪代码如下:

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

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


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

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

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

第2步:创建托管列

这一步也很简单。 而是直接将新列添加到数据网格中,将它们存储到列表中,然后使用foreach语句以编程方式将它们添加到网格中

ColumnModel(您应该能够创建一个数字或日期,或者您想要的任何其他类型的列。为简单起见,我通常使用Web应用程序中的字符串数据,除非我明确需要特殊的算术或日期function)

 public abstract class GridStringColumn extends Column { 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; } } 

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

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

创建列创建一个在datagrid构造函数中调用的initColumn方法。 通常我扩展一个基础数据网格类,以便我可以将我的特定网格初始化器。 这会向列商店添加一列。 MyPOJODataModel是您存储数据网格记录的数据结构,通常是您的hibernate的POJO或后端的某些内容。

 @Override public void initColumns() { getColumns().add(new GridStringColumn("columnName", "dataStoreFieldName", "column tooltip / description information about this column", true, true, false) { @Override public String getValue(MyPOJODataModelobject) { return object.getFieldValue(); } }); } 

现在创建一些代码以将列更新到网格中,确保在调用initColumns方法后调用此方法。 我们将很快得到initFilters方法。 但是,如果您现在需要了解,则可以根据您的集合中的列来设置filter。 每当要显示/隐藏列或重新排序网格中的列时,也可以调用此函数。 我知道你喜欢它!

 @SuppressWarnings("unchecked") public void updateColumns() { if (dataGrid_.getColumnCount() > 0) { clearColumns(); } for (GridStringColumn column : getColumns()) { if (!column.isHidden()) { dataGrid_.addColumn((Column) column, new ColumnHeader(column.getText(), column.getDataStoreName())); } } initFilters(); } 

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

现在我们已经开始了解有趣的东西,因为我们已准备好网格和列进行过滤。 这部分类似于这个问题的示例代码,但它有点不同。 我们在这里做的是创建一个新的自定义AbstractCell,我们特定一个html模板供GWT在运行时呈现。 然后我们将这个新的单元格模板注入我们的自定义头类并将其传递给gwt数据用于在数据网格中创建新列的addColumn()方法

您的自定义单元格

 final public class ColumnHeaderFilterCell extends AbstractCell { interface Templates extends SafeHtmlTemplates { @SafeHtmlTemplates.Template("
{0}
") SafeHtml text(String value); @SafeHtmlTemplates.Template("
") 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 { 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()方法上面查看,你可以看到它在添加列时创建了这个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; } 

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

现在编译时,应该在列标题文本下面看到一个间隙。 这是您将filter定位到使用css的位置

第4步:创建filter容器

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

 private HorizontalPanel filterContainer_ = new HorizontalPanel(); 

和你的filter初始化

 public void initFilters() { filterContainer_.setStylePrimaryName("filterContainer"); for (GridStringColumn column : getColumns()) { if (!column.isHidden()) { Filter filterInput = new Filter(column); filters_.add(filterInput); filterContainer_.add(filterInput); filterContainer_.setCellWidth(filterInput, "auto"); } } } 

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

 public class Filter extends TextBox { final private GridStringColumn boundColumn_; public Filter(GridStringColumn 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 getBoundColumn() { return boundColumn_; } } 

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

现在,当您初始化网格以将寻呼机和网格添加到布局容器中时,我们不考虑filter通常应占用的垂直高度。 由于它被设置为相对位置,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); } 

现在对于一些常数

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

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

第6步:使用CSS Magic

将filter从上方定位到列上的特定css就是这个

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

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

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

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

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

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

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

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

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


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


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

这是简单的部分。 如果你从上面看filter类,请注意这个方法体

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

创建filter计时器

 private FilterTimer filterTimer = new FilterTimer(); 

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

 private class FilterTimer extends Timer { @Override public void run() { updateDataList(); } } 

需要一个计时器,以便每次用户输入值时不会触发事件。 很多人都添加了onblur或其他愚蠢的处理程序,但它毫无意义。 用户一次只能将数据输入到一个字段中,因此它是一个静音点。 只需使用onKeyUp处理程序..

第9步:更新您的网格

现在,当我们调用updateDataList(也应该从你的onRangeChanged事件中调用(用于排序和数据加载)时,我们想要通过我们的用户输入filter的filter集合进行迭代。我个人将所有请求参数存储到一个简单的访问和更新的哈希映射。然后只需将整个映射传递到我的请求引擎,它执行您的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()); } 

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

* 完成< _ __ _ _> *


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