UiCellWidgets

单元格小部件(数据展示小部件)是高性能、轻量级的小部件,由单元格组成,用于显示数据。示例包括列表表格浏览器。这些小部件旨在快速处理和显示大量数据集。单元格小部件将用户界面渲染为 HTML 字符串,使用 innerHTML 而不是传统的 DOM 操作。此设计遵循享元模式,其中数据仅在需要时访问和缓存,并传递给享元单元格对象。单元格小部件可以接受来自任何类型数据源的数据。数据模型处理异步更新以及推送更新。当您更改数据时,视图会自动更新。

单元格是用户界面的基本块,有各种可用的单元格类型。它们渲染数据的视图,解释浏览器事件,并且可以选择。单元格的类型基于单元格表示的数据;例如,DatePickerCell 是一个Cell<Date>,它表示一个日期并允许用户选择一个新的日期。单元格必须实现 render 方法,该方法将类型化值渲染为 HTML 字符串。此外,单元格可以覆盖 onBrowserEvent,充当享元来处理对单元格渲染的元素触发的事件。

例如,在单元格列表示例中,每个可选择的数据记录都由一个单元格实例渲染。请注意,单个单元格表示的数据可能是来自数据源的不同数据字段的组合。在此示例中,单元格包含类型为 ContactInfo 的数据,它表示一个联系人,包括姓名、地址和图片。

单元格表格示例中,不同的单元格用于渲染每一行的列。此示例中的五列分别显示来自布尔值和四个字符串的数据。

  1. 单元格小部件
    1. 演示和代码示例
    2. 创建单元格列表并设置数据
    3. 创建单元格表格
    4. 创建单元格树
    5. 创建单元格浏览器
  2. 单元格
    1. 可用的单元格类型
    2. 创建自定义单元格
  3. 选择、数据和分页
    1. 添加选择支持
    2. 提供动态数据
    3. 添加分页控件
    4. 从单元格中的更改更新数据库

注意:CellPanel 不是单元格小部件。CellPanel 是 GWT 面板小部件的抽象基类,这些小部件使用表格元素实现。

单元格小部件

演示和代码示例

本文档描述或指向三种代码示例,因此您可以从任何级别开始。

这些文件将在下面各节中适当地引用。

创建单元格列表并设置数据

单元格列表是最简单的单元格小部件,其中数据使用相同类型的单元格在列表中渲染。例如,您可以创建一个CellList<String>,它使用一个Cell<String>来渲染字符串列表。对于更花哨的列表视图,您可以创建一个自定义单元格,如创建自定义单元格中所述。

演示 - CwCellList 示例显示了一个CellList<ContactInfo>(左侧)。每个列表项都是一个自定义类型ContactCell<ContactInfo>。右侧的小部件是一个普通的复合小部件,它渲染所选联系人的数据。

创建单元格列表

  1. 创建一个标准自定义单元格来保存列表项。
  2. 创建一个单元格列表,将单元格传递给它的构造函数。
  3. 访问数据以填充列表。
  4. 在单元格列表上调用setRowData 以添加数据。

在最后一步插入的数据由数据提供者(ListDataProviderAsyncDataProvider)更新。如果您需要允许用户修改单元格的内容并更新数据库,请在最后一步使用 ValueUpdater 而不是 setRowData,如从单元格列表更新数据库中所述。

代码示例 - 下面的示例可在CellListExample.java处获得。

以下代码是一个非常简单的示例,它创建一个包含单个 TextCell 的单元格列表小部件并从数据源设置数据。列表显示姓名。

/**
 * Example of {@link CellList}. This example shows a list of the days of the week.
 */
public class CellListExample implements EntryPoint {

  // The list of data to display.
  private static final List<String> DAYS = Arrays.asList("Sunday", "Monday",
      "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday");

  public void onModuleLoad() {
    // Create a cell to render each value in the list.
    TextCell textCell = new TextCell();

    // Create a CellList that uses the cell.
    CellList<String> cellList = new CellList<String>(textCell);

    // Set the total row count. This isn't strictly necessary, but it affects
    // paging calculations, so its good habit to keep the row count up to date.
    cellList.setRowCount(DAYS.size(), true);

    // Push the data into the widget.
    cellList.setRowData(0, DAYS);

    // Add it to the root panel.
    RootPanel.get().add(cellList);
  }
}

您可以向单元格列表添加 SelectionModel,如SelectionModel 示例中所示。

创建单元格表格

单元格表格在列中渲染行值。列表示数据对象中的单个字段。每个列都定义 getValue(),它从数据对象中检索该列的值。每个列都使用一个单元格来渲染特定于列的数据。请注意,列可以返回它们想要的任何对象作为 getValue(),包括行对象本身(例如,允许显示基于多个行值计算的列)。

标题表示表格中的标题或页脚。表格可以为每列都有标题和页脚。如果相邻标题相等(==),则标题可以跨越多个列。

演示 - CwCellTable 示例显示了一个CellTable<ContactInfo>。每个行项都有 5 列,分别渲染为 CheckboxCell、EditTextCell、EditTextCell、SelectionCell 和 TextCell。

创建单元格表格

  1. 为每一列数据创建一个标准自定义单元格。
  2. 创建一个单元格表格
  3. 创建并向单元格表格添加
  4. 访问数据以填充列表。
  5. 通过为每一列调用setRowData 将数据添加到单元格表格中。

在最后一步插入的数据由数据提供者(ListDataProviderAsyncDataProvider)更新。如果您需要允许用户修改单元格的内容并更新数据库,请在最后一步使用 FieldUpdater 而不是 setRowData,如从单元格表格更新数据库中所述。

更多信息 - 阅读单元格表格开发人员指南,了解更多关于特定于单元格表格的功能的信息,例如列排序。

代码示例 - 下面的示例是CellTableExample.java的简化版本。

/**
 * Example of {@link CellTable} of contacts having a name and address.
 */
public class CellTableExample implements EntryPoint {

  // A simple data type that represents a contact.
  private static class Contact {
    private final String address;
    private final String name;

    public Contact(String name, String address) {
      this.name = name;
      this.address = address;
    }
  }

  // The list of data to display.
  private static List<Contact> CONTACTS = Arrays.asList(
    new Contact("John", "123 Fourth Road"),
    new Contact("Mary", "222 Lancer Lane"));

  public void onModuleLoad() {

    // Create a CellTable.
    CellTable<Contact> table = new CellTable<Contact>();

    // Create name column.
    TextColumn<Contact> nameColumn = new TextColumn<Contact>() {
      @Override
      public String getValue(Contact contact) {
        return contact.name;
      }
    };

    // Create address column.
    TextColumn<Contact> addressColumn = new TextColumn<Contact>() {
      @Override
      public String getValue(Contact contact) {
        return contact.address;
      }
    };

    // Add the columns.
    table.addColumn(nameColumn, "Name");
    table.addColumn(addressColumn, "Address");

    // Set the total row count. This isn't strictly necessary, but it affects
    // paging calculations, so its good habit to keep the row count up to date.
    table.setRowCount(CONTACTS.size(), true);

    // Push the data into the widget.
    table.setRowData(0, CONTACTS);

    // Add it to the root panel.
    RootPanel.get().add(table);
  }
}

您可以向单元格表格添加 SelectionModel,如SelectionModel 示例中所示。

创建单元格树

单元格树渲染节点层次结构,例如CwCellTree。节点可以是叶子节点,也可以有子节点。因此,单元格树可以具有逐渐变深的节点级别。节点由 NodeInfo 表示,其中包含渲染单个节点所需的所有信息。

每个节点都有一个特定类型的单元格;通常,给定级别上的所有单元格都是同一种类型,但这并非必需。示例中有一个顶层节点,每个单元格都包含图像和字符串。同样,第二级和第三级单元格也有自己的不同类型。除了单元格之外,节点还具有一个数据提供程序,用于向 NodeInfo 的子节点提供数据,以及一个选择模型,用于指示用户如何选择它。

TreeViewModel 为每个子节点提供 NodeInfo。当打开一个节点时,CellTree 将调用 getNodeInfo() 在 TreeViewModel 上获取用于呈现子节点的 NodeInfo。

CellTree 可以拥有自己的 CSS 样式和自己的资源,例如用户单击以打开或关闭节点的图像。它也可以响应浏览器事件。此外,CellTree 可以拥有内置动画,用于在节点打开或关闭时逐步显示或隐藏子节点。

演示 - CwCellTree 示例 展示了一个 CellTree。它有三个级别,分别呈现为自定义类型 CategoryCell、LetterCountCell 和 ContactCell(与 CellList 演示中的类型相同)。复选框有一个更新方法,在选中时选择 ContactCell。

创建单元格树

  1. 定义一个 TreeViewModelgetNodeInfo
    1. 在 getNodeInfo 中,为子节点创建一个数据提供程序。
    2. 使用数据填充数据提供程序。
    3. 创建一个 标准自定义 单元格来呈现子节点。
  2. 创建 TreeViewModel 类的实例。
  3. 创建一个 CellTree,传入 TreeViewModel 实例。

代码示例 #1 - 下面的示例是一个简化的 CellTree 示例,可在 CellTreeExample.java 中找到。

代码示例 #2 - 有关 CellTree 的实际示例,请参阅 CellTreeExample2.java

/**
 * Example of {@link CellTree}.  Shows a Tree consisting of strings.
 */
public class CellTreeExample implements EntryPoint {

  // The model that defines the nodes in the tree.
  private static class CustomTreeModel implements TreeViewModel {

    // Get the NodeInfo that provides the children of the specified value.
    public <T> NodeInfo<?> getNodeInfo(T value) {

      // Create some data in a data provider. Use the parent value as a prefix for the next level.
      ListDataProvider<String> dataProvider = new ListDataProvider<String>();
      for (int i = 0; i < 2; i++) {
        dataProvider.getList().add(value + "." + String.valueOf(i));
      }

      // Return a node info that pairs the data with a cell.
      return new DefaultNodeInfo<String>(dataProvider, new TextCell());
    }

    // Check if the specified value represents a leaf node. Leaf nodes cannot be opened.
    public boolean isLeaf(Object value) {
      // The maximum length of a value is ten characters.
      return value.toString().length() > 10;
    }
  }

  public void onModuleLoad() {
    // Create a model for the tree.
    TreeViewModel model = new CustomTreeModel();

    // Create the tree using the model. We specify the default value of the
    // hidden root node as "Item 1".
    CellTree tree = new CellTree(model, "Item 1");

    // Add the tree to the root layout panel.
    RootLayoutPanel.get().add(tree);
  }
}

当实例化 CellTree 时,必须传入实现接口 TreeViewModel 的具体类的实例。此具体类在方法 getNodeInfo(value) 的实现中获取数据并将其组织成层次结构。当树节点被打开时,树会调用 getNodeInfo(value) 获取用于呈现子节点的数据提供程序和单元格。

您可以向 CellTree 添加选择模型,如以下 选择模型示例 所示。

创建单元格浏览器

CellBrowser 与 CellTree 类似,但它并排显示节点级别。唯一的代码差异在于您使用 CellBrowser 构造函数并使用不同的 CellBrowser.Resources 用于 CSS 样式(和图像)来创建并排级别。

演示 - CwCellBrowser 示例 展示了一个 CellBrowser。它以与上述 CellTree 示例相同的三个级别显示相同的数据,只是它并排显示级别。

创建 CellBrowser

  • 按照上面关于 CellTree 的步骤操作,但将 CellTree 构造函数更改为 CellBrowser,如下所示
// Create the browser using the model.
    CellBrowser browser = new CellBrowser(model, "Item 1");

代码示例 #1 - 有关 CellBrowser 的简单示例,请参阅 CellBrowerExample.java

代码示例 #2 - 有关 CellBrowser 的实际示例,请参阅 CellBrowserExample2.java

单元格

可用单元格类型

GWT 提供了许多具体单元格实现,您可以立即使用。请参阅 单元格示例 获取示例。

  • 文本
    • TextCell - 一个不可编辑的单元格,用于显示文本
    • ClickableTextCell - 一个文本字段;单击单元格会导致调用其 ValueUpdater
    • EditTextCell - 一个最初显示文本的单元格;单击时,文本变为可编辑
    • TextInputCell - 用于输入文本的字段
  • 按钮、复选框和菜单
    • ActionCell - 一个按钮,它接受一个委托来在鼠标抬起时执行操作
    • ButtonCell - 一个按钮,其文本是数据值
    • CheckboxCell - 一个可以选中或取消选中的复选框
    • SelectionCell - 一个下拉菜单,用于从多个选项中选择一个
  • 日期
    • DateCell - 一个符合指定日期格式的日期
    • DatePickerCell - 一个日期选择器,显示一个日历,用户可以在其中选择日期
  • 图像
  • 数字
    • NumberCell - 一个符合指定数字格式的数字
  • 组合
  • 装饰器

创建自定义单元格

如果您想要更多控制权,可以子类化 AbstractCell,也可以直接实现 Cell 接口来定义单元格的渲染方式以及它如何响应事件。请参阅 创建自定义单元格开发指南 中的说明以获取详细的信息。

演示 - CwCellList 示例显示了一个CellList<ContactInfo>(左侧)。每个列表项都是一个自定义类型ContactCell<ContactInfo>。右侧的小部件是一个普通的复合小部件,它渲染所选联系人的数据。

选择、数据和分页

添加选择支持

SelectionModel 是一个简单的接口,视图使用它来确定一个项目是否被选中。单元格小部件为选择节点的子节点提供了多个选择模型:DefaultSelectionModel、NoSelectionModel、SingleSelectionModel 和 MultiSelectionModel。其中一个很可能适合您的需求。

有关选择的演示,CwCellList 小部件创建了一个 SingleSelectionModel,而 CwCellTable 使用复选框实现了 MultiSelectionModel。

视图或应用程序代码可以调用 setSelected() 来选择一个项目。视图调用 isSelected() 来确定一个项目是否被选中。视图还订阅 SelectionModel,以便它们可以被告知来自视图外部的选择更改。实际上,您可以扩展 DefaultSelectionModel 并覆盖 isDefaultSelected()。

这种简单的方法提供了很多灵活性。一个复杂的实现可以使用一个布尔值来处理跨多个页面的“全选”,该布尔值指示所有内容都被选中,然后跟踪负选。

通过使用订阅模型,我们可以在多个视图之间链接选择。如果多个视图订阅同一个 SelectionModel,那么在一个视图中选择一行将在其他视图中选择该行。此行为是可选的,可以通过每个视图使用单个 SelectionModel 实例来避免。

演示 - CwCellList 示例 展示了一个添加了 SelectionModel 的单元格小部件。单击一个项目会选中它。

向单元格小部件添加选择

  1. 创建一个单元格小部件。
  2. 选择一个标准 SelectionModel(或自己编写)。
  3. 使用 setSelectionModel(SelectionModel) 将此 SelectionModel 添加到单元格小部件。
  4. 创建一个 SelectionChangeEvent.Handler 实现 onSelectionChange。
  5. 使用 addSelectionChangeHandler 将此处理程序添加到 SelectionModel。

代码示例 - 以下 SelectionModel 示例可在 CellListExample.java 中找到。

/**
 * Example of {@link CellList}. This example shows a list of the days of the week.
 */
public class CellListExample implements EntryPoint {

  // The list of data to display.
  private static final List<String> DAYS = Arrays.asList("Sunday", "Monday",
      "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday");

  public void onModuleLoad() {
    // Create a cell to render each value.
    TextCell textCell = new TextCell();

    // Create a CellList that uses the cell.
    CellList<String> cellList = new CellList<String>(textCell);
    cellList.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.ENABLED);

    // Add a selection model to handle user selection.
    final SingleSelectionModel<String> selectionModel = new SingleSelectionModel<String>();
    cellList.setSelectionModel(selectionModel);
    selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
      public void onSelectionChange(SelectionChangeEvent event) {
        String selected = selectionModel.getSelectedObject();
        if (selected != null) {
          Window.alert("You selected: " + selected);
        }
      }
    });

    // Set the total row count. This isn't strictly necessary, but it affects
    // paging calculations, so its good habit to keep the row count up to date.
    cellList.setRowCount(DAYS.size(), true);

    // Push the data into the widget.
    cellList.setRowData(0, DAYS);

    // Add it to the root panel.
    RootPanel.get().add(cellList);
  }
}

每个 DTO(数据传输对象)都必须与它关联一个键,以便能够将它识别为同一个对象,即使它的一些属性可能已更改。例如,给定一个当前股票价格表,股票价格可能会在其中一列中发生变化,但该行代表相同的底层 DTO。

键允许我们将 ViewData(如选择状态和验证信息)与 DTO 关联。如果在表格或列表中选择了一些项目,那么当列表使用新数据刷新时,您可以保持相同的选择。

代码示例 - 以下 KeyProvider 示例可在 KeyProviderExample.java 中找到。

/**
  * Example of using a {@link ProvidesKey}.
  */
public class KeyProviderExample implements EntryPoint {

  // A simple data type that represents a contact.
  private static class Contact {
    private static int nextId = 0;

    private final int id;
    private String name;

    public Contact(String name) {
      nextId++;
      this.id = nextId;
      this.name = name;
    }
  }

  // A custom Cell used to render a Contact.
  private static class ContactCell extends AbstractCell<Contact> {
    @Override
    public void render(Contact value, Object key, SafeHtmlBuilder sb) {
      if (value != null) {
        sb.appendEscaped(value.name);
      }
    }
  }

  // The list of data to display.
  private static final List<Contact> CONTACTS = Arrays.asList(new Contact(
      "John"), new Contact("Joe"), new Contact("Michael"),
      new Contact("Sarah"), new Contact("George"));

  public void onModuleLoad() {
    // Define a key provider for a Contact. We use the unique ID as the key,
    // which allows to maintain selection even if the name changes.
    ProvidesKey<Contact> keyProvider = new ProvidesKey<Contact>() {
      public Object getKey(Contact item) {
        // Always do a null check.
        return (item == null) ? null : item.id;
      }
    };

    // Create a CellList using the keyProvider.
    CellList<Contact> cellList = new CellList<Contact>(new ContactCell(),
        keyProvider);

    // Push data into the CellList.
    cellList.setRowCount(CONTACTS.size(), true);
    cellList.setRowData(0, CONTACTS);

    // Add a selection model using the same keyProvider.
    SelectionModel<Contact> selectionModel = new SingleSelectionModel<Contact>(
        keyProvider);
    cellList.setSelectionModel(selectionModel);

    // Select a contact. The selectionModel will select based on the ID because
    // we used a keyProvider.
    Contact sarah = CONTACTS.get(3);
    selectionModel.setSelected(sarah, true);

    // Modify the name of the contact.
    sarah.name = "Sara";

    // Redraw the CellList. Sarah/Sara will still be selected because we
    // identify her by ID. If we did not use a keyProvider, Sara would not be
    // selected.
    cellList.redraw();

    // Add the widgets to the root panel.
    RootPanel.get().add(cellList);
  }
}

提供动态数据

我们在上一节 创建 CellList 并设置数据 中看到了如何将数据推送到 CellList。但是,在大多数应用程序中,您希望显示动态数据或数据范围,而不仅仅是静态列表。本节介绍如何将数据源附加到单元格小部件。

单元格小部件不限制数据源。相反,数据源监听单元格小部件的可见范围变化,然后将新数据推送到单元格小部件。数据源通过添加 RangeChangeEvent.Handler(通过 addRangeChangeHandler())来检测可见范围的变化。然后,数据源可以异步访问数据,最终调用 HasData#setRowData() 使用新数据。

幸运的是,我们提供了一些方便的类,让这一切变得更加容易。ListDataProvider 是一个具体的数据源,它由一个 java.util.List 支持,如果您的数据完全驻留在客户端,这将非常有用。如果您的数据驻留在服务器上,您可以扩展抽象类 AsyncDataProvider,您可以覆盖它来连接到异步数据源,例如运行在服务器上的数据库。

或者,您可以通过直接处理 RangeChangeEvents 来创建 自定义数据源。如果您正在编写自己的演示者逻辑来控制单元格小部件,您可能会发现编写自己的数据源比使用数据提供程序更容易。

ListDataProvider

ListDataProvider 将您的单元格小部件绑定到一个 java.util.List。对内部列表的任何更改(可以通过 getList() 访问)都将反映在视图中。视图将在当前事件块结束时更新,因此您可以进行多次同步更改,而不会导致视图多次刷新。

代码示例 - 以下示例通过 ListDataProvider 更新视图。

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class CellListExample implements EntryPoint {
  // The list of data to display.
  private static final List<String> DAYS = Arrays.asList("Sunday", "Monday",
      "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday");

  public void onModuleLoad() {
    // Create a cell to render each value in the list.
    TextCell textCell = new TextCell();

    // Create a CellList that uses the cell.
    CellList<String> cellList = new CellList<String>(textCell);

    // Set the range to display. In this case, our visible range is smaller than
    // the data set.
    cellList.setVisibleRange(1, 3);

    // Create a data provider.
    ListDataProvider<String> dataProvider = new ListDataProvider<String>();
    
    // Connect the list to the data provider.
    dataProvider.addDataDisplay(cellList);
    
    // Add the data to the data provider, which automatically pushes it to the
    // widget. Our data provider will have seven values, but it will only push
    // the four that are in range to the list.
    List<String> list = dataProvider.getList();
    for (String day : DAYS) {
      list.add(day);
    }

    // Add it to the root panel.
    RootPanel.get().add(cellList);
  }
}

AsyncDataProvider

AsyncListDataProvider 将您的单元格小部件绑定到异步数据源。当单元格小部件请求新数据时,AsyncDataProvider 会获取新数据并将其推送到小部件。只需实现 onRangeChanged() 方法并在指定单元格小部件的新范围内请求数据。当数据返回时,调用 updateRowCount() 和/或 updateRowData() 将数据推送到小部件。

基本配方

  1. 创建 AsyncDataProvider 的子类。
  2. 实现 onRangeChanged(HasData)
    1. 从显示器获取当前范围
    2. 从服务器或数据源请求数据
  3. 当数据返回时,调用 updateRowData() 将数据推送到小部件。

代码示例 - 下面的示例通过 AsyncDataProvider 更新视图。

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class CellListExample implements EntryPoint {
  // The list of data to display.
  private static final List<String> DAYS = Arrays.asList("Sunday", "Monday",
      "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday");

  public void onModuleLoad() {
    // Create a cell to render each value in the list.
    TextCell textCell = new TextCell();

    // Create a CellList that uses the cell.
    final CellList<String> cellList = new CellList<String>(textCell);

    // Set the total row count. You might send an RPC request to determine the
    // total row count.
    cellList.setRowCount(DAYS.size(), true);

    // Set the range to display. In this case, our visible range is smaller than
    // the data set.
    cellList.setVisibleRange(1, 3);

    // Create a data provider.
    AsyncDataProvider<String> dataProvider = new AsyncDataProvider<String>() {
      @Override
      protected void onRangeChanged(HasData<String> display) {
        final Range range = display.getVisibleRange();

        // This timer is here to illustrate the asynchronous nature of this data
        // provider. In practice, you would use an asynchronous RPC call to
        // request data in the specified range.
        new Timer() {
          @Override
          public void run() {
            int start = range.getStart();
            int end = start + range.getLength();
            List<String> dataInRange = DAYS.subList(start, end);

            // Push the data back into the list.
            cellList.setRowData(start, dataInRange);
          }
        }.schedule(2000);
      }
    };

    // Connect the list to the data provider.
    dataProvider.addDataDisplay(cellList);

    // Add it to the root panel.
    RootPanel.get().add(cellList);
  }
}

自定义数据源

当用户在列表中翻页时,单元格小部件会触发 RangeChangeEvent。您可以处理视图中的 RangeChangeEvents 并相应地将新数据推送到视图中。如果您正在为单元格小部件编写演示者类,这将很有用。

代码示例 - 下面的示例处理来自视图的 RangeChangeEvents,并根据新范围推送新数据。

/**
 * Example of using a {@link RangeChangeEvent.Handler} to push data into a
 * {@link CellList} when the range changes.
 */
public class RangeChangeHandlerExample implements EntryPoint {

  @Override
  public void onModuleLoad() {
    // Create a CellList.
    CellList<String> cellList = new CellList<String>(new TextCell());

    // Add a range change handler.
    cellList.addRangeChangeHandler(new RangeChangeEvent.Handler() {
      @Override
      public void onRangeChange(RangeChangeEvent event) {
        Range range = event.getNewRange();
        int start = range.getStart();
        int length = range.getLength();

        // Create the data to push into the view. At this point, you could send
        // an asynchronous RPC request to a server.
        List<String> data = new ArrayList<String>();
        for (int i = start; i < start + length; i++) {
          data.add("Item " + i);
        }

        // Push the data into the list.
        updateRowData(start, data);
      }
    });

    // Force the cellList to fire an initial range change event.
    cellList.setVisibleRangeAndClearData(new Range(0, 25), true);

    // Create paging controls.
    SimplePager pager = new SimplePager();
    pager.setDisplay(cellList);

    // Add the widgets to the root panel.
    VerticalPanel vPanel = new VerticalPanel();
    vPanel.add(pager);
    vPanel.add(cellList);
    RootPanel.get().add(vPanel);
  }
}

添加分页控件

分页是指加载并显示当前未加载的数据范围的操作。分页通过仅加载当前视图所需的数据来改善大型数据集的初始加载时间。

有两个步骤 - 一个用于将标准 SimplePager 添加到单元格小部件,另一个用于将自定义分页控件添加到单元格小部件。

演示 - CwCellTable 示例 在表格下方显示 SimplePager 控件。

代码示例 - 下面的示例位于 SimplePagerExample.java 中。

将 SimplePager 添加到单元格小部件

  1. 使用其构造函数创建 SimplePager 小部件的实例。
  2. 使用 setDisplay(HasRows) 将 SimplePager 分配给您要控制的单元格小部件。
  3. 将 SimplePager 实例添加到面板。
/**
 * Example of {@link SimplePager}.
 */
public class SimplePagerExample implements EntryPoint {

  public void onModuleLoad() {
    // Create a CellList.
    CellList<String> cellList = new CellList<String>(new TextCell());

    // Add a cellList to a data provider.
    ListDataProvider<String> dataProvider = new ListDataProvider<String>();
    List<String> data = dataProvider.getList();
    for (int i = 0; i < 200; i++) {
      data.add("Item " + i);
    }
    dataProvider.addDataDisplay(cellList);

    // Create a SimplePager.
    SimplePager pager = new SimplePager();

    // Set the cellList as the display.
    pager.setDisplay(cellList);

    // Add the pager and list to the page.
    VerticalPanel vPanel = new VerticalPanel();
    vPanel.add(pager);
    vPanel.add(cellList);
    RootPanel.get().add(vPanel);
  }
}

将自定义分页控件添加到单元格小部件

  1. 创建一个自定义分页器 - 扩展 AbstractPager 适用于大多数用例。AbstractPager 提供了许多便利方法,您的分页器将使用这些方法来更改可见范围,包括一种将单元格小部件连接起来的方法。
    1. AbstractPager 是一个 Composite,因此您需要定义分页器的 Widget 部分,并通过调用 initWidget(Widget) 初始化 AbstractPager。
    2. 您还需要覆盖 onRangeOrRowCountChanged() 以在可见范围因任何原因更改时更新小部件。
  2. 使用 setDisplay(HasRows) 将分页器分配给您要控制的单元格小部件。
  3. 将自定义分页器添加到面板。

从单元格中的更改更新数据库

在大多数应用程序中,用户会在用户界面中执行操作,这些操作应该更新应用程序的当前状态或将数据发送回数据库(或数据源)。这些用户操作可能是单击复选框、按下按钮或在字段中输入文本并按下“保存”。

此过程对于 CellList、CellTree和 CellTable 略有不同,如下所述。

注意:在 ButtonCell 的情况下,值(按钮文本)实际上并没有改变。相反,ValueUpdater 的作用是向外部代码通知更改或重要操作,例如单击。

从 CellList 更新数据库

在 Column 中使用 ValueUpdater 允许用户修改 Cell 的内容(如 TextInputCell 所示)。下面的示例展示了如何更新数据和处理无效数据。FieldUpdater 的 update 方法接受三个参数:数据对象的行索引、表示该字段的数据对象以及 Cell 的新值。

当用户更改数据时,Cell 会在其 onBrowserEvent 方法中收到一个事件。对于支持用户交互的单元格,onBrowserEvent 会调用 ValueUpdater 的 update 方法,并将新值传递进去。

演示 - (无)

代码示例 - 下面的示例位于 CellListValueUpdaterExample.java 中。

从 CellList 更新数据库

  1. 创建一个实现了 ValueUpdater 的类,以接受新的数据值并将其发送到您的数据库。
  2. 使用 cellList.setValueUpdater 将 ValueUpdater 设置为 CellList。

代码示例 - ValueUpdater


/** * Example of using a {@link ValueUpdater} with a {@link CellList}. */ public class CellListValueUpdaterExample implements EntryPoint { /** * The list of data to display. */ private static final List<String> DAYS = Arrays.asList("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"); public void onModuleLoad() { // Create a cell that will interact with a value updater. TextInputCell inputCell = new TextInputCell(); // Create a CellList that uses the cell. CellList<String> cellList = new CellList<String>(inputCell); // Create a value updater that will be called when the value in a cell changes. ValueUpdater<String> valueUpdater = new ValueUpdater<String>() { public void update(String newValue) { Window.alert("You typed: " + newValue); } }; // Add the value updater to the cellList. cellList.setValueUpdater(valueUpdater); // Set the total row count. This isn't strictly necessary, but it affects // paging calculations, so its good habit to keep the row count up to date. cellList.setRowCount(DAYS.size(), true); // Push the data into the widget. cellList.setRowData(0, DAYS); // Add it to the root panel. RootPanel.get().add(cellList); } }

从 CellTable 更新数据库

在 Column 中使用 FieldUpdater 允许用户修改 Cell 的内容(如 TextInputCell 所示)。下面的示例展示了如何更新数据和处理无效数据。FieldUpdater 的 update 方法接受三个参数:数据对象的行索引、表示该字段的数据对象以及 Cell 的新值。

演示 - CwCellTable 示例 允许您修改姓氏和名字(这些列使用 EditTextCell)。

从 CellTable 更新数据库

  1. 创建一个实现了 FieldUpdater 的类,以接受新的数据值并将其发送到您的数据库。
  2. 通过调用 column.setFieldUpdater(fieldUpdater) 在 Column 中设置 FieldUpdater。

代码示例 - 示例位于 CellTableFieldUpdaterExample.java 中。

/**
 * Example of using a {@link FieldUpdater} with a {@link CellTable}.
 */
public class CellTableFieldUpdaterExample implements EntryPoint {

  /**
   * A simple data type that represents a contact with a unique ID.
   */
  private static class Contact {
    private static int nextId = 0;

    private final int id;
    private String name;

    public Contact(String name) {
      nextId++;
      this.id = nextId;
      this.name = name;
    }
  }

  /**
   * The list of data to display.
   */
  private static final List<Contact> CONTACTS = Arrays.asList(new Contact("John"), new Contact(
      "Joe"), new Contact("George"));

  /**
   * The key provider that allows us to identify Contacts even if a field
   * changes. We identify contacts by their unique ID.
   */
  private static final ProvidesKey<Contact> KEY_PROVIDER =
      new ProvidesKey<CellTableFieldUpdaterExample.Contact>() {
        @Override
        public Object getKey(Contact item) {
          return item.id;
        }
      };

  @Override
  public void onModuleLoad() {
    // Create a CellTable with a key provider.
    final CellTable<Contact> table = new CellTable<Contact>(KEY_PROVIDER);

    // Add a text input column to edit the name.
    final TextInputCell nameCell = new TextInputCell();
    Column<Contact, String> nameColumn = new Column<Contact, String>(nameCell) {
      @Override
      public String getValue(Contact object) {
        // Return the name as the value of this column.
        return object.name;
      }
    };
    table.addColumn(nameColumn, "Name");

    // Add a field updater to be notified when the user enters a new name.
    nameColumn.setFieldUpdater(new FieldUpdater<Contact, String>() {
      @Override
      public void update(int index, Contact object, String value) {
        // Inform the user of the change.
        Window.alert("You changed the name of " + object.name + " to " + value);

        // Push the changes into the Contact. At this point, you could send an
        // asynchronous request to the server to update the database.
        object.name = value;

        // Redraw the table with the new data.
        table.redraw();
      }
    });

    // Push the data into the widget.
    table.setRowData(CONTACTS);

    // Add it to the root panel.
    RootPanel.get().add(table);
  }
}