具有包含SearchBox和焦点问题的自定义标题的CellTable [英] CellTable with custom Header containing SearchBox and Focus Problem

查看:119
本文介绍了具有包含SearchBox和焦点问题的自定义标题的CellTable的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  |标题1 |标题2 | 
| SEARCHBOX | SEARCHBOX |
---------------------------------------------- ---------
| ROW 1
-------------------------------------------- ----------
| ROW 2

只要用户输入一个字符到SearchBox中,一个 RangeChangeEvent 被触发,导致服务器请求,并且CellTable更新为新的过滤列表。


基本上一切正常。但是,一旦CellTable被刷新,SearchBox 失去焦点,用户必须再次用鼠标点击SearchBox才能输入新字符。


这可能与以下事实有关:在CellTable刷新后调用自定义标头的渲染方法及其单元。有没有办法将焦点设置回SearchBox?我尝试设置 tabindex = 0 ,但它没有帮助。



自定义标题类



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






自定义搜索单元格(在自定义标题中使用)



isChanged 布尔标志设置为 true ,当用户在SearchBox中键入内容并设置为 false 如果SearchBox失去焦点。我添加了这个标志来区分哪个搜索框获得焦点(在我使用多个搜索框的情况下)

  public static class SearchCell extends AbstractCell< SEARCHTERM> {

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

@Template(< div style = \\>< input type = \text \value = \{0} \/> ;< / div>)
SafeHtml输入(字符串值);
}

私有静态模板模板;
private boolean isChanged = false;
$ b $ 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的初始码


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

  ValueUpdater< SearchTerm> searchUpdater = new ValueUpdater< SearchTerm>(){
@Override
public void update(AccessionCellTableColumns.SearchTerm value){
//触发一个服务器请求返回新的过滤列表
RangeChangeEvent.fire(table,new Range(table.getPageStart(),table.getPageSize()));
}
};

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


解决方案

瘦身



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

如果您的datagrid实现了SortStringHandler(LOL thats phunny),那么可能有键或鼠标事件的嵌套UI对象将触发列排序。失败。再次,我找不到一种方法来重载列排序事件,以排除与嵌套列标题ui组件/小部件交互触发的触发器。更不用说,您需要通过将内联HTML写入构建您的单元的模板接口来抽象地定义嵌套组件。几乎不是一个优雅的选择,因为它迫使开发人员不得不编写本地JavaScript代码来创建和控制与列标题中嵌套的组件/窗口小部件相关联的处理程序。这种适当的实现技术也不能解决这个问题所关注的焦点问题,对于需要AsyncProvider(或者ListProvider)的复杂数据网格来说,它几乎不是一个很好的解决方案。具有列单元格过滤的数据集或自定义呈现。这种表现也是meh> _>远不是一个适当的解决方案IMO



认真???

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



伪代码如下:



  1. 请确保您的网格由LayoutPanel包装
  2. li> 确保您的网格列由集合/列表管理
  3. 创建自定义列标题以创建过滤区域

  4. 创建过滤容器将文本框放入

  5. 布置您的网格容器children(grid,filter, pager)

  6. 使用css技术将过滤器定位到列标题中添加事件处理程序以过滤

  7. 添加计时器来处理过滤器输入延迟
  8. 刷新数据,异步或本地列表


你呢,因为有很多事情要做第1步:设置网格类来扩展LayoutPanel

p>

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

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





$ b

第2步:创建托管列 strong>



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

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

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

private String text_;
私人字符串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(可排序);
this.hidden_​​ =隐藏;






$ p在你的datagrid类中创建列表来存储你的列


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

创建列创建一个在您的datagrid构造函数中调用的initColumn方法。通常我扩展一个基本的DataGrid类,以便我可以把我的具体网格初始化器。这会在列商店中添加一列。 MyPOJODataModel是您存储数据网格记录的数据结构,通常是您的休眠POJO或您的后端的某个POJO。

  @Override 
public void initColumns(){
getColumns()。add(new GridStringColumn< MyPOJODataModel>(columnName,dataStoreFieldName,关于此列的工具提示/描述信息列 ,true,false){

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

立即创建一些代码,将您的列更新到您的网格中,确保您致电这个方法在你调用initColumns方法之后。我们将很快得到initFilters方法。但是,如果您现在需要知道,则可以根据您收藏中的哪些列设置过滤器。只要您想显示/隐藏列或重新排列网格中的列,您也可以调用此函数。

  @SuppressWarnings(unchecked)
public void updateColumns(){$ b $ (dataGrid_.getColumnCount()> 0){
clearColumns(); (列< T>列:getColumns()){
if(!column.isHidden()){
dataGrid_.addColumn((< ; T,?>)列,新的ColumnHeader(column.getText(),column.getDataStoreName()));
}
}

initFilters();

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

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

Your定制单元格:

  final public class ColumnHeaderFilterCell extends AbstractCell< String> {

界面模板扩展了SafeHtmlTemplates {
@ SafeHtmlTemplates.Template(< div class = \headerText \> {0}< / div>)
SafeHtml文本(字符串值);

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

private static Templates templates = GWT.create(Templates.class);
$ b @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);




$ b如果你还没有学会讨厌你如何制作自定义单元格,你很快就会确定完成这个实现之后。接下来,我们需要一个标题将这个单元格注入到

列标题中:

  public static class ColumnHeader extends Header< String> {

私人字符串名称_;
$ b $ public ColumnHeader(String name,String field){
super(new ColumnHeaderFilterCell());
this.name_ = name;
setHeaderStyleNames(columnHeader+ field);
}

@Override
public String getValue(){
return name_;






$ b

你可以看到这是一个非常简单和简单的类。说实话,它更像一个包装器,为什么GWT想到将它们组合成一个特定的列标题单元格,而不必将一个通用单元格注入到我之中。也许不是一个超级幻想,但我确信如果你在上面查看你的updateColumns()方法,你可以看到它创建一个新的当它添加列时,该columnheader类的实例。此外,请确保您完全符合静态和最终的要求,因此当您创建非常大的数据集时,您并不会抖动您的内存...... 20列的IE 1000行是20000个调用或您存储的模板或成员的实例。因此,如果您的单元格或标题中有一个成员拥有100个字节,而这个字节可以变成大约2MB或者更多资源,那么只需要CTOR的成员。再次,这不像定制数据单元格渲染那样容易,但它仍然对你的头文件很重要!



现在不要忘记添加你的css

  .grid数据表{
overflow:hidden;
white-space:nowrap;
table-layout:fixed;
border-spacing:0px;
}

.grid数据表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%;
}

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

.grid数据表.columnHeader div img {
position:relative;
top:-18px;
}

.grid数据表.columnHeader .headerText {
font-size:90%;
line-height:92%;
}

.grid数据表.columnHeader .headerFilter {
visibility:hidden;
height:32px;
}

现在这就是您将要添加的所有内容的CSS。im懒得分开它,再加上我想你可以弄清楚。 gridContainer是包装数据网格的布局面板,gridData是实际的数据网格。



现在编译时应该会在列标题文本下面看到一个间隙。这是您将使用css定位过滤器的地方。



第4步:创建您的过滤器容器

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

  private Horizo​​ntalPanel filterContainer_ = new Horizo​​ntalPanel(); 

和您的过滤器初始化

  public void initFilters(){
filterContainer_.setStylePrimaryName(filterContainer); (GridStringColumn< T>列:getColumns()){
if(!column.isHidden()){
过滤filterInput = new Filter(column);


filters_.add(filterInput);
filterContainer_.add(filterInput);
filterContainer_.setCellWidth(filterInput,auto);
}
}
}

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

  public class Filter extends TextBox {

final private GridStringColumn< ; T> boundColumn_;
$ b $ public Filter(GridStringColumn< T>列){
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(){
返回boundColumn_;





$ b

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



现在,当您初始化网格以将您的寻呼机和网格添加到布局容器时,我们不会考虑过滤器通常需要的垂直高度向上。由于它被设置为z-index的相对位置,所以网格和列具有什么,它实际上会出现在标题中。 MAGIC !!!

  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 Magic



将过滤器放置在上面的列上是这个

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

将会将容器移动到列上并将其放置在标题上方的图层



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

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

接下来确保我们的输入根据他们居住的容器单元的大小进行缩放in

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

最后,我们要移动排序图像指示器,以便滤镜输入文本框不要隐藏它们

.grid数据表.columnHeader div img {
position:relative;
top:-18px;
}



现在,当您编译时,您应该会看到列标题上的过滤器。你可能需要调整CSS以使它们完全排列。这也假设你没有设置任何特殊的列宽。如果这样做,则需要创建一些附加功能来手动设置单元格大小,并设置宽度的样式以与列同步。






* 现在是休息时间了,你差不多在那里!^ _ __ _ __ _ _ ^ *
$ b




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



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

  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事件中调用(用于排序和数据加载)时,我们想要遍历我们的用于我们应用的过滤器的用户输入的过滤器集合。我个人将所有请求参数存储到一个用于方便访问和更新的hashmap,然后将整个map传递到我的请求引擎中,它执行你的RPC或者RequestFactory的东西

  public void updateDataList(){
initParameters();

//所需参数由datagrid控制
parameters_.put(limit,limit_ +);
parameters_.put(offset,offset_ +);

//排序参数
if(sortField_.equals()|| sortOrder_.equals()){
parameters_.remove(sortField);
parameters_.remove(sortDir);
} e lse {
parameters_.put(sortField,sortField_);
parameters_.put(sortDir,sortOrder_);
}

//过滤器参数
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以上)保留字。


$ b

* 完成< em> _ __ _ _> *






<我希望能够帮助你们所有人使用一些先进的gwt datagrid的东西。我知道创造自己是一种痛苦,所以希望这将为您节省将来的一大堆时间。好运!


I am trying to implement a CellTable with a custom Column Header which displays a SearchBox (simple Textbox) below the normal Column text.
The SearchBox should allow the user to filter the CellTable. It should look something like this:

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

As soon as the user types in a character into the SearchBox a RangeChangeEvent is fired which leads to a server requests and the CellTable is updated with the new filtered list.

Basically everything works fine. However as soon as the CellTable is refreshed the SearchBox loses its focus and the user has to click with the mouse into the SearchBox again to type in a new character.

This is probably related to the fact that the render method of the custom header and its cell is called after the CellTable refresh.
Is there any way how to set the focus back to the SearchBox? I tried to set tabindex=0 but it didn't help.

Custom Header Class

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;
    }
 }

Custom Search Cell (used in the custom Header)

The isChanged boolean flag is set to true when the user types something into the SearchBox and is set back to false if the SearchBox loses its focus. I added this flag in order to distinguish which SearchBox gets the focus (in case I use multiple 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();
     }
}

Init Code for the CellTable

NameColumn is the implementation of the abstract Column class with the appropriate types. It uses a TextCell internally.

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));

解决方案

The Skinny

Unfortunately GWT's support for custom column headers is a bit wonky to say the least. If anyone has had the joy of working with the AbstractCell classes you would know what i mean. Additionally the proper way to implement composite (nested widgets) into your column header cell is a bust, as i have not been able to get it to work proper, nor have found any workable examples of a CompositeCell working.

If your datagrid implements a ColumnSortHandler of sorts (LOL thats phunny) your nested UI objects that might have key or mouse events will trigger a column sort. FAIL. Again i could not find a way to overload the columnsort events to exclude triggers fired by interacting with the nested column header ui components/widgets. Not to mention that you need to abstractly define the nested components by writing inline HTML into the Template interface that builds your cell. Not nearly an elegant choice, as it forces developers to have to write native JavaScript code to create and control the handlers associated with the nested components/widgets in the column header.

This "proper" implementation technique also does not solve the focus problem that this question addresses, and is not nearly a great solution to complex datagrids that need AsyncProvider (or ListProvider) data sets with column cell filtering, or custom rendering. The performance of this is also meh >_> Far from a proper solution IMO

Seriously???

In order to implement a functional column cell filtering, you must tackle this from a more traditional dynamical javascript/css appoarch from the days before GWT and crazy JQuery libraries. My functional solution is a hybrid of the "proper" way with some crafty css.

the psuedo code is as follows:

  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

whew, hope i haven't lost you yet, as there is alot to do in order to make this work


Step 1: Setup Grid Class to Extend LayoutPanel

First you need to make sure your class that creates your grid can support and be sized properly in your application. To do this make sure your grid class extends a LayoutPanel.

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

Step 2: Create Managed Columns

This step is also pretty straight forward. Rather then directly added new columns into your datagrid, store them into a list, then programmatically add them into your grid with a foreach statement

ColumnModel (you should be able to create a number or date, or whatever else type of column you want. for simplicity i generally work with string data in web apps, unless i explicitly need special arithmetic or date functionality)

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;
    }
}

create list in your datagrid class to store your columns into

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

to create your columns create a initColumn method that is called in your datagrid constructor. Usually i extend the a base datagrid class, so that i can put my specific grid initializers into. This adds a column to your column store. MyPOJODataModel is your data structure that you store your records for the datagrid in, usually its a POJO of your hibernate or something from your backend.

@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();
            }
        });
}

create some code now to update your columns into your grid, make sure you call this method after you call initColumns method. the initFilters method we will get to shortly. But if you need to know now, it is the method that sets up your filters based on what columns you have in your collection. You can also call this function whenever you want to show/hide columns or reorder the columns in your grid. i know you love it!

@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();
    }

Step 3: Create Custom Column Header

Now we are starting to get to the fun stuff now that we have the grid and columns ready for filtering. This part is similiar to the example code this question askes, but it is a little different. What we do here is create a new custom AbstractCell that we specific an html template for GWT to render at runtime. Then we inject this new cell template into our custom header class and pass it into the addColumn() method that gwt's data uses to create a new column in your data grid

Your custom cell:

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);
    }
}

If you haven't learned to hate how you make custom cells, you soon will im sure after you get done implementing this. Next we need a header to inject this cell into

column header:

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_;
        }
    }

as you can see this is a pretty straightforward and simple class. Honestly its more like a wrapper, why GWT has thought of combining these into a specific column header cell rather then having to inject a generic cell into is beyond me. Maybe not a super fancy but im sure it would be much easier to work with

if you look up above to your updateColumns() method you can see that it creates a new instance of this columnheader class when it adds the column. Also make sure you are pretty exact with what you make static and final so you arent thrashing your memory when you create very large data sets... IE 1000 rows at 20 columns is 20000 calls or instances of template or members you have stored. So if one member in your cell or header has 100 Bytes that turns into about 2MB or resources or more just for the CTOR's. Again this isn't as impportant as custom data cell rendering, but it is still important on your headers too!!!

Now dont forget to add your 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;
}

now thats the css for all of the stuff your gonna add it in. im too lazy to separate it out, plus i think you can figure that out. gridContainer is the layoutpanel that wraps your datagrid, and gridData is your actual data grid.

Now when you compile you should see a gap below the column heading text. This is where you will position your filters into using css

Step 4: Create Your Filter Container

now we need something to put our filter inputs into. This container also has css applied to it that will move it down into the space we just created in the headers. Yes thats right the filters that are in the header are actually and technically not in the header. This is the only way to avoid the sorting event issue and lose focus issue

private HorizontalPanel filterContainer_ = new HorizontalPanel();

and your filter initialization

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");
            }
        }
    }

you can see that it requires your collection of columns in order to properly create the filter inputs that go into your container Additionally the filter class also gets passed in the column in order to bind the column to the specific filter. This allows you to access the fields and such

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_;
        }
    }

Step 5: Add Your Components To Your LayoutPanel

now when you init your grid to add your pager and grid into the layout container we do not account for the vertical height that the filter should normally take up. Since it is set to relative position with a z-index greated then what the grid and columns have, it will appear to actually be in the header. MAGIC!!!

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);
    }

and now for some constants

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

Border height is for specific css styling your app might have, technially this is slug space to make sure everything fits tightly.

Step 6: Use CSS Magic

the specific css that positions the filters onto your columns from above is this

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

that will move the container over the columns and position there layer above your headers

next we need to make sure that the cells in the filterContainer line up with the ones in our datagrid

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

next make sure our inputs scale according to the size of the containers cell they live in

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

lastly we want to move our sorting image indicator up so that the filter input textboxes do not hide them

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

now when you compile you should see the filters over the column headers. you may need to tweak the css to get them to line up exactly. This also assumes you do not have any special column widths set. if you do, you will need to create some additional functionality to manually set the cell sizes and style the widths to sync with the columns. I have ommited this for santity.


*now its time for a break, your almost there!^________^*


Step 7 & 8: Add event handlers

this is the easy part. if you look at the filter class from above take note of this method body

addKeyUpHandler(new KeyUpHandler() {

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

create your filter timer

private FilterTimer filterTimer = new FilterTimer();

make sure you specify the field in the root of the class body and not inline.

private class FilterTimer extends Timer {

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

a timer is required so that the event isn't fired everytime a user enters a value. Alot of people have added onblur or other silly handlers, but its pointless. A user can only be entering data into one field at a time, so its a mute point. just use a onKeyUp handler..

Step 9: Update your grid

now when we call updateDataList (which should also be called from your onRangeChanged event (for sorting and data loading) we want to iterate though our collection of Filters for our applied filters the user has entered in. Personally i store all of the request parameters into a hashmap for easy access and updating. Then just pass the entire map into my request engine that does your RPC or RequestFactory stuff

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());
    }

you can see how and why we need to bind the filter to a column, so when we iterate though the filters we can get the stored field name. usually i just pass the fieldname and filter value as a query parameter rather then pass all of the filters as a single filter query parameter. This is way more extensible, and rarely are there edgecases that your db columns should == reserved words for your query parameters like sortDir or sortField above.

*Done <_____>*


well i hope that helps all of you with some advanced gwt datagrid stuff. I know this was a pain to create myself, so hopefully this will save you all a bunch of time in the future. Goodluck!

这篇关于具有包含SearchBox和焦点问题的自定义标题的CellTable的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆