Revision: 9042
Author: jlaba...@google.com
Date: Wed Oct 13 05:03:29 2010
Log: Fixing a bug where headers are not redrawn when the data changes. I was trying to be crafty and keep track of when headers are stale, but since they can depend on the data, we need to refesh them every time. This patch also allows headers to span multiple columns if adjacent headers == each other.

Review at http://gwt-code-reviews.appspot.com/971801

Review by: r...@google.com
http://code.google.com/p/google-web-toolkit/source/detail?r=9042

Modified:
 /trunk/user/src/com/google/gwt/user/cellview/client/CellTable.java
 /trunk/user/test/com/google/gwt/user/cellview/client/CellTableTest.java

=======================================
--- /trunk/user/src/com/google/gwt/user/cellview/client/CellTable.java Tue Oct 12 07:55:56 2010 +++ /trunk/user/src/com/google/gwt/user/cellview/client/CellTable.java Wed Oct 13 05:03:29 2010
@@ -52,7 +52,25 @@

 /**
  * A tabular view that supports paging and columns.
- *
+ *
+ * <p>
+ * <h3>Columns</h3>
+ * The {...@link Column} class defines the {...@link Cell} used to render a column. + * Implement {...@link Column#getValue(Object)} to retrieve the field value from
+ * the row object that will be rendered in the {...@link Cell}.
+ * </p>
+ *
+ * <p>
+ * <h3>Headers and Footers</h3>
+ * A {...@link Header} can be placed at the top (header) or bottom (footer) of the
+ * {...@link CellTable}. You can specify a header as text using
+ * {...@link #addColumn(Column, String)}, or you can create a custom {...@link Header}
+ * that can change with the value of the cells, such as a column total. The
+ * {...@link Header} will be rendered every time the row data changes or the table + * is redrawn. If you pass the same header instance (==) into adjacent columns,
+ * the header will span the columns.
+ * </p>
+ *
  * <p>
  * <h3>Examples</h3>
  * <dl>
@@ -269,8 +287,8 @@
     @Template("<table><tfoot>{0}</tfoot></table>")
     SafeHtml tfoot(SafeHtml rowHtml);

-    @Template("<th class=\"{0}\">{1}</th>")
-    SafeHtml th(String classes, SafeHtml contents);
+    @Template("<th colspan=\"{0}\" class=\"{1}\">{2}</th>")
+    SafeHtml th(int colspan, String classes, SafeHtml contents);

     @Template("<table><thead>{0}</thead></table>")
     SafeHtml thead(SafeHtml rowHtml);
@@ -432,11 +450,6 @@

   private final List<Header<?>> headers = new ArrayList<Header<?>>();

-  /**
-   * Set to true when the footer is stale.
-   */
-  private boolean headersStale;
-
   private TableRowElement hoveringRow;

   /**
@@ -640,7 +653,6 @@
     }
     CellBasedWidgetImpl.get().sinkEvents(this, consumedEvents);

-    headersStale = true;
     scheduleRedraw();
   }

@@ -778,7 +790,6 @@
     headers.remove(index);
     footers.remove(index);
     updateDependsOnSelection();
-    headersStale = true;

// Find an interactive column. Stick with 0 if no column is interactive.
     if (index <= keyboardSelectedColumn) {
@@ -1149,7 +1160,7 @@

   /**
    * Render the header or footer.
-   *
+   *
* @param isFooter true if this is the footer table, false if the header table
    */
   private void createHeaders(boolean isFooter) {
@@ -1162,30 +1173,52 @@
     SafeHtmlBuilder sb = new SafeHtmlBuilder();
     sb.appendHtmlConstant("<tr>");
     int columnCount = columns.size();
-    int curColumn = 0;
-    for (Header<?> header : theHeaders) {
+    if (columnCount > 0) {
+      // Setup the first column.
+      Header<?> prevHeader = theHeaders.get(0);
+      int prevColspan = 1;
       StringBuilder classesBuilder = new StringBuilder(className);
-      if (curColumn == 0) {
-        classesBuilder.append(" ");
-        classesBuilder.append(isFooter ? style.cellTableFirstColumnFooter()
-            : style.cellTableFirstColumnHeader());
-      }
-      // The first and last columns could be the same column.
-      if (curColumn == columnCount - 1) {
-        classesBuilder.append(" ");
-        classesBuilder.append(isFooter ? style.cellTableLastColumnFooter()
-            : style.cellTableLastColumnHeader());
+      classesBuilder.append(" ");
+      classesBuilder.append(isFooter ? style.cellTableFirstColumnFooter()
+          : style.cellTableFirstColumnHeader());
+
+      // Loop through all column headers.
+      for (int curColumn = 1; curColumn < columnCount; curColumn++) {
+        Header<?> header = theHeaders.get(curColumn);
+
+        if (header != prevHeader) {
+          // The header has changed, so append the previous one.
+          SafeHtmlBuilder headerBuilder = new SafeHtmlBuilder();
+          if (prevHeader != null) {
+            hasHeader = true;
+            prevHeader.render(headerBuilder);
+          }
+          sb.append(template.th(prevColspan, classesBuilder.toString(),
+              headerBuilder.toSafeHtml()));
+
+          // Reset the previous header.
+          prevHeader = header;
+          prevColspan = 1;
+          classesBuilder = new StringBuilder(className);
+        } else {
+          // Increment the colspan if the headers == each other.
+          prevColspan++;
+        }
       }

+      // Append the last header.
       SafeHtmlBuilder headerBuilder = new SafeHtmlBuilder();
-      if (header != null) {
+      if (prevHeader != null) {
         hasHeader = true;
-        header.render(headerBuilder);
+        prevHeader.render(headerBuilder);
       }

-      sb.append(template.th(classesBuilder.toString(),
+      // The first and last columns could be the same column.
+      classesBuilder.append(" ");
+      classesBuilder.append(isFooter ? style.cellTableLastColumnFooter()
+          : style.cellTableLastColumnHeader());
+      sb.append(template.th(prevColspan, classesBuilder.toString(),
           headerBuilder.toSafeHtml()));
-      curColumn++;
     }
     sb.appendHtmlConstant("</tr>");

@@ -1197,11 +1230,8 @@
   }

   private void createHeadersAndFooters() {
-    if (headersStale) {
-      headersStale = false;
-      createHeaders(false);
-      createHeaders(true);
-    }
+    createHeaders(false);
+    createHeaders(true);
   }

   private void deselectKeyboardRow(int row) {
=======================================
--- /trunk/user/test/com/google/gwt/user/cellview/client/CellTableTest.java Wed Sep 22 12:58:01 2010 +++ /trunk/user/test/com/google/gwt/user/cellview/client/CellTableTest.java Wed Oct 13 05:03:29 2010
@@ -1,12 +1,12 @@
 /*
  * Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- *
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
  * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -16,11 +16,129 @@
 package com.google.gwt.user.cellview.client;

 import com.google.gwt.cell.client.TextCell;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.TableCellElement;
+import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.dom.client.TableRowElement;
+import com.google.gwt.dom.client.TableSectionElement;
+import com.google.gwt.user.cellview.client.CellTable.Resources;
+import com.google.gwt.user.cellview.client.CellTable.Style;

 /**
  * Tests for {...@link CellTable}.
  */
 public class CellTableTest extends AbstractHasDataTestBase {
+
+  /**
+   * Test headers that span multiple columns.
+   */
+  public void testMultiColumnHeader() {
+    Resources res = GWT.create(Resources.class);
+    CellTable<String> table = new CellTable<String>(10, res);
+    TextHeader header = new TextHeader("Hello");
+
+    // Get the style information.
+    Style style = res.cellTableStyle();
+    String styleHeader = style.cellTableHeader();
+    String styleFirstColumn = style.cellTableFirstColumnHeader();
+    String styleLastColumn = style.cellTableLastColumnHeader();
+
+    // No header.
+    table.redraw();
+    assertEquals(0, getHeaderCount(table));
+
+    // Single column.
+    table.addColumn(new TextColumn<String>() {
+      @Override
+      public String getValue(String object) {
+        return null;
+      }
+    }, header);
+    table.redraw();
+    assertEquals(1, getHeaderCount(table));
+    assertEquals(1, getHeaderElement(table, 0).getColSpan());
+ assertTrue(getHeaderElement(table, 0).getClassName().contains(styleHeader));
+    assertTrue(getHeaderElement(table, 0).getClassName().contains(
+        styleFirstColumn));
+    assertTrue(getHeaderElement(table, 0).getClassName().contains(
+        styleLastColumn));
+
+    // Header spans both columns.
+    table.addColumn(new TextColumn<String>() {
+      @Override
+      public String getValue(String object) {
+        return null;
+      }
+    }, header);
+    table.redraw();
+    assertEquals(1, getHeaderCount(table));
+    assertEquals(2, getHeaderElement(table, 0).getColSpan());
+ assertTrue(getHeaderElement(table, 0).getClassName().contains(styleHeader));
+    assertTrue(getHeaderElement(table, 0).getClassName().contains(
+        styleFirstColumn));
+    assertTrue(getHeaderElement(table, 0).getClassName().contains(
+        styleLastColumn));
+
+    // Header spans all three columns.
+    table.addColumn(new TextColumn<String>() {
+      @Override
+      public String getValue(String object) {
+        return null;
+      }
+    }, header);
+    table.redraw();
+    assertEquals(1, getHeaderCount(table));
+    assertEquals(3, getHeaderElement(table, 0).getColSpan());
+ assertTrue(getHeaderElement(table, 0).getClassName().contains(styleHeader));
+    assertTrue(getHeaderElement(table, 0).getClassName().contains(
+        styleFirstColumn));
+    assertTrue(getHeaderElement(table, 0).getClassName().contains(
+        styleLastColumn));
+
+    // New header at fourth column.
+    table.addColumn(new TextColumn<String>() {
+      @Override
+      public String getValue(String object) {
+        return null;
+      }
+    }, "New Header");
+    table.redraw();
+    assertEquals(2, getHeaderCount(table));
+    assertEquals(3, getHeaderElement(table, 0).getColSpan());
+ assertTrue(getHeaderElement(table, 0).getClassName().contains(styleHeader));
+    assertTrue(getHeaderElement(table, 0).getClassName().contains(
+        styleFirstColumn));
+    assertEquals(1, getHeaderElement(table, 1).getColSpan());
+ assertTrue(getHeaderElement(table, 1).getClassName().contains(styleHeader));
+    assertTrue(getHeaderElement(table, 1).getClassName().contains(
+        styleLastColumn));
+
+    // Two separate spans of same header: HHHXHH.
+    table.addColumn(new TextColumn<String>() {
+      @Override
+      public String getValue(String object) {
+        return null;
+      }
+    }, header);
+    table.addColumn(new TextColumn<String>() {
+      @Override
+      public String getValue(String object) {
+        return null;
+      }
+    }, header);
+    table.redraw();
+    assertEquals(3, getHeaderCount(table));
+    assertEquals(3, getHeaderElement(table, 0).getColSpan());
+ assertTrue(getHeaderElement(table, 0).getClassName().contains(styleHeader));
+    assertTrue(getHeaderElement(table, 0).getClassName().contains(
+        styleFirstColumn));
+    assertEquals(1, getHeaderElement(table, 1).getColSpan());
+ assertTrue(getHeaderElement(table, 1).getClassName().contains(styleHeader));
+    assertEquals(2, getHeaderElement(table, 2).getColSpan());
+ assertTrue(getHeaderElement(table, 2).getClassName().contains(styleHeader));
+    assertTrue(getHeaderElement(table, 2).getClassName().contains(
+        styleLastColumn));
+  }

   @Override
   protected CellTable<String> createAbstractHasData() {
@@ -39,4 +157,31 @@
     });
     return table;
   }
-}
+
+  /**
+   * Get the number of column headers in the table.
+   *
+   * @param table the {...@link CellTable}
+   * @return the number of column headers
+   */
+  private int getHeaderCount(CellTable<?> table) {
+    TableElement tableElem = table.getElement().cast();
+    TableSectionElement thead = tableElem.getTHead();
+    TableRowElement tr = thead.getRows().getItem(0);
+    return tr.getCells().getLength();
+  }
+
+  /**
+   * Get a column header from the table.
+   *
+   * @param table the {...@link CellTable}
+   * @param column the column index
+   * @return the column header
+   */
+ private TableCellElement getHeaderElement(CellTable<?> table, int column) {
+    TableElement tableElem = table.getElement().cast();
+    TableSectionElement thead = tableElem.getTHead();
+    TableRowElement tr = thead.getRows().getItem(0);
+    return tr.getCells().getItem(column);
+  }
+}

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to