]> source.dussan.org Git - vaadin-framework.git/commitdiff
Add column freezing to Escalator (#3087)
authorLeif Åstrand <leif@vaadin.com>
Wed, 13 Nov 2013 10:37:22 +0000 (12:37 +0200)
committerLeif Åstrand <leif@vaadin.com>
Thu, 14 Nov 2013 07:26:31 +0000 (09:26 +0200)
Change-Id: I9943b20ca2568c353c90dec598534fbb5ab58203

WebContent/VAADIN/themes/base/escalator/escalator.scss
client/src/com/vaadin/client/ui/grid/ColumnConfiguration.java
client/src/com/vaadin/client/ui/grid/Escalator.java
client/src/com/vaadin/client/ui/grid/PositionFunction.java
uitest/src/com/vaadin/tests/components/grid/BasicEscalator.java
uitest/src/com/vaadin/tests/widgetset/client/grid/TestGridClientRpc.java
uitest/src/com/vaadin/tests/widgetset/client/grid/TestGridConnector.java
uitest/src/com/vaadin/tests/widgetset/server/grid/TestGrid.java

index 461a3050e9bf1caafff56e6f0a2b01867b84f0ad..436f849456c1eb8bdbb17a403d8fe00925e4d7c6 100644 (file)
@@ -8,16 +8,12 @@ $border-color: #aaa;
        background-color: $background-color;
 }
 
-.#{$primaryStyleName}-cell {
-       -moz-box-sizing: border-box;
-       box-sizing: border-box;
-}
-
 .#{$primaryStyleName}-scroller {
        position: absolute;
        overflow: auto;
        height: inherit;
-       width: inherit; /* width will be overridden if we have frozen columns */
+       left: 0; /* Left position adjusted to align with frozen columns */
+       right: 0;
 }
 
 .#{$primaryStyleName}-tablewrapper {
@@ -87,6 +83,13 @@ $border-color: #aaa;
        border: 1px solid $border-color;
        padding: 2px;
        white-space: nowrap;
+       -moz-box-sizing: border-box;
+       box-sizing: border-box;
+}
+
+.#{$primaryStyleName}-cell.frozen {
+       position: relative;
+       z-index: 0;
 }
 
 }
\ No newline at end of file
index fdd6a2538257834401b2ca2eb372376eb2b28579..cfbd2b1ea5985b3ef99b512a4dc84bfbe5990fdc 100644 (file)
@@ -27,6 +27,9 @@ public interface ColumnConfiguration {
 
     /**
      * Removes columns at a certain index.
+     * <p>
+     * If any of the removed columns were frozen, the number of frozen columns
+     * will be reduced by the number of the removed columns that were frozen.
      * 
      * @param index
      *            the index of the first column to be removed
@@ -53,6 +56,10 @@ public interface ColumnConfiguration {
      * The contents of the inserted columns will be queried from the respective
      * cell renderers in the header, body and footer.
      * <p>
+     * If there are frozen columns and the first added column is to the left of
+     * the last frozen column, the number of frozen columns will be increased by
+     * the number of inserted columns.
+     * <p>
      * <em>Note:</em> Only the contents of the inserted columns will be
      * rendered. If inserting new columns affects the contents of existing
      * columns, {@link RowContainer#refreshRows(int, int)} needs to be called as
@@ -78,4 +85,25 @@ public interface ColumnConfiguration {
      * @return the number of columns in the escalator
      */
     public int getColumnCount();
+
+    /**
+     * Sets the number of leftmost columns that are not affected by horizontal
+     * scrolling.
+     * 
+     * @param count
+     *            the number of columns to freeze
+     * 
+     * @throws IllegalArgumentException
+     *             if the column count is &lt; 0 or &gt; the number of columns
+     * 
+     */
+    public void setFrozenColumnCount(int count) throws IllegalArgumentException;
+
+    /**
+     * Get the number of leftmost columns that are not affected by horizontal
+     * scrolling.
+     * 
+     * @return the number of frozen columns
+     */
+    public int getFrozenColumnCount();
 }
\ No newline at end of file
index f786ef304546d8ba8047dfdda33307925d9943af..c90d9930224a204cd08e73a69ca5f132479353e4 100644 (file)
@@ -212,10 +212,6 @@ public class Escalator extends Widget {
      * escalator DOM). NOTE: these bits can most often also be identified by
      * searching for code that call scrollElem.getScrollTop();.
      */
-    /*
-     * [[frozencol]]: This needs to be re-inspected once frozen columns are
-     * being implemented.
-     */
     /*
      * [[widgets]]: This needs to be re-inspected once GWT/Vaadin widgets are
      * being supported.
@@ -226,8 +222,8 @@ public class Escalator extends Widget {
 
     /** An inner class that handles all logic related to scrolling. */
     private class Scroller extends JsniWorkaround {
-        private double lastScrollTop = -1;
-        private double lastScrollLeft = -1;
+        private double lastScrollTop = 0;
+        private double lastScrollLeft = 0;
 
         public Scroller() {
             super(Escalator.this);
@@ -312,9 +308,10 @@ public class Escalator extends Widget {
                     .setHeight(innerScrollerHeight, Unit.PX);
 
             // TODO [[colwidth]]: adjust for variable column widths.
+            int columnsToScroll = columnConfiguration.getColumnCount()
+                    - columnConfiguration.getFrozenColumnCount();
             innerScrollerElem.getStyle().setWidth(
-                    COLUMN_WIDTH_PX * columnConfiguration.getColumnCount(),
-                    Unit.PX);
+                    COLUMN_WIDTH_PX * columnsToScroll, Unit.PX);
 
             // we might've got new or got rid of old scrollbars.
             recalculateTableWrapperSize();
@@ -361,7 +358,11 @@ public class Escalator extends Widget {
             final int scrollTop = scrollerElem.getScrollTop();
 
             if (lastScrollLeft != scrollLeft) {
-                // TODO [[frozen]]: frozen columns should be offset here.
+                for (int i = 0; i < columnConfiguration.frozenColumns; i++) {
+                    header.updateFreezePosition(i, scrollLeft);
+                    body.updateFreezePosition(i, scrollLeft);
+                    footer.updateFreezePosition(i, scrollLeft);
+                }
 
                 position.set(headElem, -scrollLeft, 0);
 
@@ -444,16 +445,27 @@ public class Escalator extends Widget {
 
         public void scrollToColumn(final int columnIndex,
                 final ScrollDestination destination, final int padding) {
-            // TODO [[colwidth]]
-            final int targetStartPx = COLUMN_WIDTH_PX * columnIndex;
-            final int targetEndPx = targetStartPx + COLUMN_WIDTH_PX;
+            assert columnIndex >= columnConfiguration.frozenColumns : "Can't scroll to a frozen column";
 
+            // TODO [[colwidth]]
             /*
-             * TODO [[frozencol]]: offset startPx by the pixels occupied by
+             * To cope with frozen columns, we just pretend those columns are
+             * not there at all when calculating the position of the target
+             * column and the boundaries of the viewport. The resulting
+             * scrollLeft will be correct without compensation since the DOM
+             * structure effectively means that scrollLeft also ignores the
              * frozen columns.
              */
+            final int frozenPixels = columnConfiguration.frozenColumns
+                    * COLUMN_WIDTH_PX;
+
+            final int targetStartPx = COLUMN_WIDTH_PX * columnIndex
+                    - frozenPixels;
+            final int targetEndPx = targetStartPx + COLUMN_WIDTH_PX;
+
             final int viewportStartPx = getScrollLeft();
-            int viewportEndPx = viewportStartPx + getElement().getOffsetWidth();
+            int viewportEndPx = viewportStartPx + getElement().getOffsetWidth()
+                    - frozenPixels;
             if (needsVerticalScrollbars()) {
                 viewportEndPx -= Util.getNativeScrollbarSize();
             }
@@ -689,6 +701,12 @@ public class Escalator extends Widget {
                 for (int col = 0; col < columnConfiguration.getColumnCount(); col++) {
                     final Element cellElem = createCellElement();
                     tr.appendChild(cellElem);
+
+                    // Set stylename and position if new cell is frozen
+                    if (col < columnConfiguration.frozenColumns) {
+                        cellElem.addClassName("frozen");
+                        position.set(cellElem, scroller.lastScrollLeft, 0);
+                    }
                 }
 
                 refreshRow(tr, row);
@@ -844,7 +862,7 @@ public class Escalator extends Widget {
         }
 
         protected void paintInsertColumns(final int offset,
-                final int numberOfColumns) {
+                final int numberOfColumns, boolean frozen) {
             final NodeList<Node> childNodes = root.getChildNodes();
             final int topVisualRowLogicalIndex = getTopVisualRowLogicalIndex();
 
@@ -875,6 +893,12 @@ public class Escalator extends Widget {
                 recalculateRowWidth(tr);
             }
 
+            if (frozen) {
+                for (int col = offset; col < offset + numberOfColumns; col++) {
+                    setColumnFrozen(col, true);
+                }
+            }
+
             // this needs to be before the scrollbar adjustment.
             scroller.recalculateScrollbarsForVirtualViewport();
 
@@ -887,6 +911,37 @@ public class Escalator extends Widget {
                                 * COLUMN_WIDTH_PX));
             }
         }
+
+        public void setColumnFrozen(int column, boolean frozen) {
+            final NodeList<Node> childNodes = root.getChildNodes();
+
+            for (int row = 0; row < childNodes.getLength(); row++) {
+                final Element tr = childNodes.getItem(row).cast();
+
+                Element cell = (Element) tr.getChild(column);
+                if (frozen) {
+                    cell.addClassName("frozen");
+                } else {
+                    cell.removeClassName("frozen");
+                    position.reset(cell);
+                }
+            }
+
+            if (frozen) {
+                updateFreezePosition(column, scroller.lastScrollLeft);
+            }
+        }
+
+        public void updateFreezePosition(int column, double scrollLeft) {
+            final NodeList<Node> childNodes = root.getChildNodes();
+
+            for (int row = 0; row < childNodes.getLength(); row++) {
+                final Element tr = childNodes.getItem(row).cast();
+
+                Element cell = (Element) tr.getChild(column);
+                position.set(cell, scrollLeft, 0);
+            }
+        }
     }
 
     private abstract class AbstractStaticRowContainer extends
@@ -1796,6 +1851,7 @@ public class Escalator extends Widget {
 
     private class ColumnConfigurationImpl implements ColumnConfiguration {
         private int columns = 0;
+        private int frozenColumns = 0;
 
         /**
          * {@inheritDoc}
@@ -1811,6 +1867,26 @@ public class Escalator extends Widget {
             assertArgumentsAreValidAndWithinRange(index, numberOfColumns);
 
             flyweightRow.removeCells(index, numberOfColumns);
+
+            // Cope with removing frozen columns
+            if (index < frozenColumns) {
+                if (index + numberOfColumns < frozenColumns) {
+                    /*
+                     * Last removed column was frozen, meaning that all removed
+                     * columns were frozen. Just decrement the number of frozen
+                     * columns accordingly.
+                     */
+                    frozenColumns -= numberOfColumns;
+                } else {
+                    /*
+                     * If last removed column was not frozen, we have removed
+                     * columns beyond the frozen range, so all remaining frozen
+                     * columns are to the left of the removed columns.
+                     */
+                    frozenColumns = index;
+                }
+            }
+
             columns -= numberOfColumns;
 
             if (hasSomethingInDom()) {
@@ -1862,9 +1938,17 @@ public class Escalator extends Widget {
 
             flyweightRow.addCells(index, numberOfColumns);
             columns += numberOfColumns;
+
+            // Either all or none of the new columns are frozen
+            boolean frozen = index < frozenColumns;
+            if (frozen) {
+                frozenColumns += numberOfColumns;
+            }
+
             if (hasColumnAndRowData()) {
                 for (final AbstractRowContainer rowContainer : rowContainers) {
-                    rowContainer.paintInsertColumns(index, numberOfColumns);
+                    rowContainer.paintInsertColumns(index, numberOfColumns,
+                            frozen);
                 }
             }
         }
@@ -1873,6 +1957,53 @@ public class Escalator extends Widget {
         public int getColumnCount() {
             return columns;
         }
+
+        @Override
+        public void setFrozenColumnCount(int count)
+                throws IllegalArgumentException {
+            if (count < 0 || count > columns) {
+                throw new IllegalArgumentException(
+                        "count must be between 0 and the current number of columns ("
+                                + columns + ")");
+            }
+            int oldCount = frozenColumns;
+            if (count == oldCount) {
+                return;
+            }
+
+            frozenColumns = count;
+
+            if (hasSomethingInDom()) {
+                // Are we freezing or unfreezing?
+                boolean frozen = count > oldCount;
+
+                int firstAffectedCol;
+                int firstUnaffectedCol;
+
+                if (frozen) {
+                    firstAffectedCol = oldCount;
+                    firstUnaffectedCol = count;
+                } else {
+                    firstAffectedCol = count;
+                    firstUnaffectedCol = oldCount;
+                }
+
+                for (int col = firstAffectedCol; col < firstUnaffectedCol; col++) {
+                    header.setColumnFrozen(col, frozen);
+                    body.setColumnFrozen(col, frozen);
+                    footer.setColumnFrozen(col, frozen);
+                }
+            }
+
+            scrollerElem.getStyle().setLeft(frozenColumns * COLUMN_WIDTH_PX,
+                    Unit.PX);
+            scroller.recalculateScrollbarsForVirtualViewport();
+        }
+
+        @Override
+        public int getFrozenColumnCount() {
+            return frozenColumns;
+        }
     }
 
     private FlyweightRow flyweightRow = new FlyweightRow(this);
@@ -2160,13 +2291,19 @@ public class Escalator extends Widget {
      * @throws IndexOutOfBoundsException
      *             if {@code columnIndex} is not a valid index for an existing
      *             column
+     * @throws IllegalArgumentException
+     *             if the column is frozen
      */
     public void scrollToColumn(final int columnIndex,
             final ScrollDestination destination)
-            throws IndexOutOfBoundsException {
-        // TODO [[frozencol]] throw IAE if frozen
+            throws IndexOutOfBoundsException, IllegalArgumentException {
         verifyValidColumnIndex(columnIndex);
 
+        if (columnIndex < columnConfiguration.frozenColumns) {
+            throw new IllegalArgumentException("The given column index "
+                    + columnIndex + " is frozen.");
+        }
+
         scroller.scrollToColumn(columnIndex, destination, 0);
     }
 
@@ -2188,18 +2325,22 @@ public class Escalator extends Widget {
      * @throws IllegalArgumentException
      *             if {@code destination} is {@link ScrollDestination#MIDDLE},
      *             because having a padding on a centered column is undefined
-     *             behavior
+     *             behavior or if the column is frozen
      */
     public void scrollToColumn(final int columnIndex,
             final ScrollDestination destination, final int padding)
             throws IndexOutOfBoundsException, IllegalArgumentException {
-        // TODO [[frozencol]] throw IAE if frozen
         if (destination == ScrollDestination.MIDDLE) {
             throw new IllegalArgumentException(
                     "You cannot have a padding with a MIDDLE destination");
         }
         verifyValidColumnIndex(columnIndex);
 
+        if (columnIndex < columnConfiguration.frozenColumns) {
+            throw new IllegalArgumentException("The given column index "
+                    + columnIndex + " is frozen.");
+        }
+
         scroller.scrollToColumn(columnIndex, destination, padding);
     }
 
index d89d73ccd18b4a4a49c93398c066e68f293fdb07..d3c0b0ade673dd43923cb7afb463ea6181c0b97f 100644 (file)
@@ -36,6 +36,11 @@ interface PositionFunction {
             e.getStyle().setProperty("transform",
                     "translate3d(" + x + "px, " + y + "px, 0)");
         }
+
+        @Override
+        public void reset(Element e) {
+            e.getStyle().clearProperty("transform");
+        }
     }
 
     /**
@@ -48,6 +53,11 @@ interface PositionFunction {
             e.getStyle().setProperty("transform",
                     "translate(" + x + "px," + y + "px)");
         }
+
+        @Override
+        public void reset(Element e) {
+            e.getStyle().clearProperty("transform");
+        }
     }
 
     /**
@@ -60,6 +70,11 @@ interface PositionFunction {
             e.getStyle().setProperty("webkitTransform",
                     "translate3d(" + x + "px," + y + "px,0)");
         }
+
+        @Override
+        public void reset(Element e) {
+            e.getStyle().clearProperty("webkitTransform");
+        }
     }
 
     /**
@@ -72,6 +87,12 @@ interface PositionFunction {
             e.getStyle().setLeft(x, Unit.PX);
             e.getStyle().setTop(y, Unit.PX);
         }
+
+        @Override
+        public void reset(Element e) {
+            e.getStyle().clearLeft();
+            e.getStyle().clearTop();
+        }
     }
 
     /**
@@ -85,4 +106,13 @@ interface PositionFunction {
      *            the y coordinate, in pixels
      */
     void set(Element e, double x, double y);
+
+    /**
+     * Resets any previously applied positioning, clearing the used style
+     * attributes.
+     * 
+     * @param e
+     *            the element for which to reset the positioning
+     */
+    void reset(Element e);
 }
\ No newline at end of file
index c8a35b702fa2cadaa2bc15fec377fa2db94a14d5..a9cd22365e33b5b586783279de1d64ec38c24ae7 100644 (file)
@@ -57,9 +57,9 @@ public class BasicEscalator extends AbstractTestUI {
                     @Override
                     @SuppressWarnings("boxing")
                     public void buttonClick(final ClickEvent event) {
-                        final int offset = Integer.valueOf(insertRowsOffset
+                        final int offset = Integer.parseInt(insertRowsOffset
                                 .getValue());
-                        final int amount = Integer.valueOf(insertRowsAmount
+                        final int amount = Integer.parseInt(insertRowsAmount
                                 .getValue());
                         grid.insertRows(offset, amount);
                     }
@@ -80,9 +80,9 @@ public class BasicEscalator extends AbstractTestUI {
                     @Override
                     @SuppressWarnings("boxing")
                     public void buttonClick(final ClickEvent event) {
-                        final int offset = Integer.valueOf(removeRowsOffset
+                        final int offset = Integer.parseInt(removeRowsOffset
                                 .getValue());
-                        final int amount = Integer.valueOf(removeRowsAmount
+                        final int amount = Integer.parseInt(removeRowsAmount
                                 .getValue());
                         grid.removeRows(offset, amount);
                     }
@@ -99,9 +99,9 @@ public class BasicEscalator extends AbstractTestUI {
                     @Override
                     @SuppressWarnings("boxing")
                     public void buttonClick(final ClickEvent event) {
-                        final int offset = Integer.valueOf(insertColumnsOffset
+                        final int offset = Integer.parseInt(insertColumnsOffset
                                 .getValue());
-                        final int amount = Integer.valueOf(insertColumnsAmount
+                        final int amount = Integer.parseInt(insertColumnsAmount
                                 .getValue());
                         grid.insertColumns(offset, amount);
                     }
@@ -118,9 +118,9 @@ public class BasicEscalator extends AbstractTestUI {
                     @Override
                     @SuppressWarnings("boxing")
                     public void buttonClick(final ClickEvent event) {
-                        final int offset = Integer.valueOf(removeColumnsOffset
+                        final int offset = Integer.parseInt(removeColumnsOffset
                                 .getValue());
-                        final int amount = Integer.valueOf(removeColumnsAmount
+                        final int amount = Integer.parseInt(removeColumnsAmount
                                 .getValue());
                         grid.removeColumns(offset, amount);
                     }
@@ -146,14 +146,14 @@ public class BasicEscalator extends AbstractTestUI {
                     public void buttonClick(final ClickEvent event) {
                         int index;
                         try {
-                            index = Integer.valueOf(rowIndex.getValue());
+                            index = Integer.parseInt(rowIndex.getValue());
                         } catch (NumberFormatException e) {
                             index = 0;
                         }
 
                         int padding;
                         try {
-                            padding = Integer.valueOf(rowPadding.getValue());
+                            padding = Integer.parseInt(rowPadding.getValue());
                         } catch (NumberFormatException e) {
                             padding = 0;
                         }
@@ -183,14 +183,14 @@ public class BasicEscalator extends AbstractTestUI {
                     public void buttonClick(final ClickEvent event) {
                         int index;
                         try {
-                            index = Integer.valueOf(colIndex.getValue());
+                            index = Integer.parseInt(colIndex.getValue());
                         } catch (NumberFormatException e) {
                             index = 0;
                         }
 
                         int padding;
                         try {
-                            padding = Integer.valueOf(colPadding.getValue());
+                            padding = Integer.parseInt(colPadding.getValue());
                         } catch (NumberFormatException e) {
                             padding = 0;
                         }
@@ -200,6 +200,20 @@ public class BasicEscalator extends AbstractTestUI {
                     }
                 }));
         addComponent(colScroll);
+
+        final TextField freezeCount = new TextField();
+        freezeCount.setConverter(Integer.class);
+        freezeCount.setNullRepresentation("");
+        addComponent(new HorizontalLayout(freezeCount, new Button(
+                "set frozen columns", new Button.ClickListener() {
+                    @Override
+                    public void buttonClick(ClickEvent event) {
+                        grid.setFrozenColumns(((Integer) freezeCount
+                                .getConvertedValue()).intValue());
+                        freezeCount.setValue(null);
+                    }
+                })));
+
     }
 
     @Override
index 3c831d2cb496a79d4bdce5e41915a4fd932b09e5..71fcd63086aaf70d0e71b80e07a0172266e5cf23 100644 (file)
@@ -29,4 +29,6 @@ public interface TestGridClientRpc extends ClientRpc {
     void scrollToRow(int index, String destination, int padding);
 
     void scrollToColumn(int index, String destination, int padding);
+
+    void setFrozenColumns(int frozenColumns);
 }
index 6a024b9d96168b930ba74f9d7420b75e30d1ce12..7b722e03c05ae424b00315ac030862d366e1388d 100644 (file)
@@ -76,6 +76,12 @@ public class TestGridConnector extends AbstractComponentConnector {
                 }
                 return d;
             }
+
+            @Override
+            public void setFrozenColumns(int frozenColumns) {
+                getWidget().getColumnConfiguration().setFrozenColumnCount(
+                        frozenColumns);
+            }
         });
     }
 
index c4a642aafec88f3f7ee7f4c00f02768775c4147e..e37d9ccee000bb8d2bf5f69a8201084cb9d710c3 100644 (file)
@@ -61,4 +61,8 @@ public class TestGrid extends AbstractComponent {
     public void scrollToColumn(int index, String destination, int padding) {
         rpc().scrollToColumn(index, destination, padding);
     }
+
+    public void setFrozenColumns(int frozenColumns) {
+        rpc().setFrozenColumns(frozenColumns);
+    }
 }