gpt4 book ai didi

java - 带有包含搜索框和焦点问题的自定义标题的 CellTable

转载 作者:行者123 更新时间:2023-12-03 18:12:43 26 4
gpt4 key购买 nike

我正在尝试实现一个带有自定义列标题的 CellTable,它在普通列文本下方显示一个 SearchBox(简单文本框)。 SearchBox 应该允许用户过滤 CellTable。它应该是这样的:

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

只要用户在 SearchBox 中输入一个字符 RangeChangeEvent 被触发,这会导致服务器请求,并且 CellTable 会使用新的过滤列表进行更新。
基本上一切正常。然而,一旦 CellTable 被刷新,SearchBox 失去焦点 并且用户必须再次用鼠标单击搜索框以输入新字符。
这可能与CellTable刷新后调用自定义header及其cell的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 中键入内容并设置回 时假 如果 SearchBox 失去焦点。我添加了这个标志是为了区分哪个 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 的初始化代码

名称栏目是抽象 的实现栏目 具有适当类型的类。它使用 文本单元 内部。
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));

最佳答案

瘦子

不幸的是,GWT 对自定义列标题的支持至少可以说有点不稳定。如果有人喜欢使用 AbstractCell 类,你就会明白我的意思。此外,在列标题单元格中实现复合(嵌套小部件)的正确方法是失败的,因为我无法让它正常工作,也没有找到任何可行的 CompositeCell 工作示例。

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

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

认真的???

为了实现功能性列单元格过滤,您必须从 GWT 和疯狂的 JQuery 库出现之前的更传统的动态 javascript/css appoarch 来解决这个问题。我的功能解决方案是“正确”方式与一些巧妙的 css 的混合。

伪代码如下:

  1. make sure your grid is wrapped by a LayoutPanel
  2. make sure your grid's columns are managed by a collection/list
  3. create custom column header to create an area for your filtering
  4. create filtering container to place you textboxes into
  5. layout your grid containers children (grid, filter, pager)
  6. use css techniques to position filters into column headers
  7. add event handlers to filters
  8. add timer to handle filter input delays
  9. fire grid update function to refresh data, async or local list


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

第 1 步:设置 Grid 类以扩展 LayoutPanel

首先,您需要确保创建网格的类可以支持并在您的应用程序中正确调整大小。为此,请确保您的网格类扩展了 LayoutPanel。
public abstract class PagingFilterDataGrid<T> extends LayoutPanel {
public PagingFilterDataGrid() {
//ctor initializers
initDataGrid();
initColumns();
updateColumns();
initPager();
setupDataGrid();
}
}

步骤 2:创建托管列

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

ColumnModel(您应该能够创建数字或日期,或您想要的任何其他类型的列。为简单起见,我通常在网络应用程序中使用字符串数据,除非我明确需要特殊的算术或日期功能)
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>>();
}

要创建列,请创建在 datagrid 构造函数中调用的 initColumn 方法。通常我会扩展一个基本的 datagrid 类,这样我就可以将我的特定网格初始值设定项放入其中。这会向您的列存储添加一列。 MyPOJODataModel 是您存储数据网格记录的数据结构,通常是您的 hibernate 状态的 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 步:创建自定义列标题

现在我们开始处理有趣的事情,因为我们已经准备好过滤网格和列。这部分与此问题询问的示例代码类似,但略有不同。我们在这里做的是创建一个新的自定义 AbstractCell,我们为 GWT 指定一个 html 模板,以便在运行时呈现。然后我们将这个新的单元格模板注入(inject)到我们的自定义标题类中,并将它传递给 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);
}
}

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

列标题:
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 考虑将它们组合到特定的列标题单元格中,而不是必须将通用单元格注入(inject)其中,这超出了我的理解。也许不是一个 super 花哨的但我相信它会更容易使用

如果您查看上面的 updateColumns() 方法,您可以看到它在添加列时创建了此列标题类的新实例。还要确保您对静态和最终的内容非常准确,这样当您创建非常大的数据集时就不会影响您的内存...... IE 1000 行 20 列是 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 是您实际的数据网格。

现在,当您编译时,您应该会在列标题文本下方看到一个间隙。这是您将过滤器定位到使用 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");
}
}
}

您可以看到它需要您的列集合才能正确创建进入容器的过滤器输入此外,过滤器类也会在列中传递,以便将列绑定(bind)到特定过滤器。这允许您访问字段等
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 样式,从技术上讲,这是确保一切紧密贴合的 slug 空间。

第 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 {
position:relative;
顶部:-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 或其他愚蠢的处理程序,但毫无意义。用户一次只能在一个字段中输入数据,因此它是一个静音点。只需使用 onKeyUp 处理程序..

第 9 步:更新您的网格

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

你可以看到我们如何以及为什么需要将过滤器绑定(bind)到一列,所以当我们遍历过滤器时,我们可以获得存储的字段名称。通常我只是将字段名和过滤器值作为查询参数传递,而不是将所有过滤器作为单个过滤器查询参数传递。这是更具可扩展性的方式,并且很少有边缘情况,您的 db 列应该 == 为您的查询参数(如上面的 sortDir 或 sortField)保留字。

* 完成 <_____>*

好吧,我希望通过一些高级的 gwt datagrid 东西来帮助你们所有人。我知道创建自己很痛苦,所以希望这会在 future 为你们节省很多时间。好运!

关于java - 带有包含搜索框和焦点问题的自定义标题的 CellTable,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6422896/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com