]> source.dussan.org Git - vaadin-framework.git/commitdiff
Speeds up column adding in Grid (#16474)
authorHenrik Paul <henrik@vaadin.com>
Wed, 28 Jan 2015 11:40:00 +0000 (13:40 +0200)
committerHenrik Paul <henrik@vaadin.com>
Wed, 4 Feb 2015 13:09:28 +0000 (15:09 +0200)
Grid.onStateChange is now about 40% faster when adding columns,
and setting several column widths has now way less overhead.

Change-Id: I7bd900324207bfb2543a1a90390665b90206aefd

client/src/com/vaadin/client/widget/escalator/ColumnConfiguration.java
client/src/com/vaadin/client/widgets/Escalator.java
client/src/com/vaadin/client/widgets/Grid.java
uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorProxy.java

index af49dcd64faf3fb2036fb49f9bd9f3e2f230d37c..76f6a55b8a3520916530bf6ab46774ae36088463 100644 (file)
@@ -16,7 +16,7 @@
 
 package com.vaadin.client.widget.escalator;
 
-import com.vaadin.client.widgets.Escalator;
+import java.util.Map;
 
 /**
  * A representation of the columns in an instance of {@link Escalator}.
@@ -140,6 +140,23 @@ public interface ColumnConfiguration {
      */
     public double getColumnWidth(int index) throws IllegalArgumentException;
 
+    /**
+     * Sets widths for a set of columns.
+     * 
+     * @param indexWidthMap
+     *            a map from column index to its respective width to be set. If
+     *            the given width for a column index is negative, the column is
+     *            resized-to-fit.
+     * @throws IllegalArgumentException
+     *             if {@code indexWidthMap} is {@code null}
+     * @throws IllegalArgumentException
+     *             if any column index in {@code indexWidthMap} is invalid
+     * @throws NullPointerException
+     *             If any value in the map is <code>null</code>
+     */
+    public void setColumnWidths(Map<Integer, Double> indexWidthMap)
+            throws IllegalArgumentException;
+
     /**
      * Returns the actual width of a column.
      * 
index 6a8fb311b127bbfefa59d29f54d0e53956de3e5b..94b6efc0d7ffd0508f74f66f146161d993ee899d 100644 (file)
 package com.vaadin.client.widgets;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -1161,47 +1163,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
         }
     }
 
-    private class ColumnAutoWidthAssignScheduler {
-        private boolean isScheduled = false;
-        private final ScheduledCommand widthCommand = new ScheduledCommand() {
-            @Override
-            public void execute() {
-                if (!isScheduled) {
-                    return;
-                }
-
-                isScheduled = false;
-
-                ColumnConfigurationImpl cc = columnConfiguration;
-                for (int col = 0; col < cc.getColumnCount(); col++) {
-                    ColumnConfigurationImpl.Column column = cc.columns.get(col);
-                    if (!column.isWidthFinalized()) {
-                        cc.setColumnWidth(col, -1);
-                        column.widthIsFinalized();
-                    }
-                }
-            }
-        };
-
-        /**
-         * Calculates the widths of all uncalculated cells once the javascript
-         * execution is done.
-         * <p>
-         * This method makes sure that any duplicate requests in the same cycle
-         * are ignored.
-         */
-        public void reschedule() {
-            if (!isScheduled) {
-                isScheduled = true;
-                Scheduler.get().scheduleFinally(widthCommand);
-            }
-        }
-
-        public void cancel() {
-            isScheduled = false;
-        }
-    }
-
     protected abstract class AbstractRowContainer implements RowContainer {
         private EscalatorUpdater updater = EscalatorUpdater.NULL;
 
@@ -1422,14 +1383,18 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
                 if (rows == numberOfRows) {
                     /*
                      * We are inserting the first rows in this container. We
-                     * potentially need to autocalculate the widths for the
-                     * cells for the first time.
-                     * 
-                     * To make sure that can take the entire dataset into
-                     * account, we'll do this deferredly, so that each container
-                     * section gets populated before we start calculating.
+                     * potentially need to set the widths for the cells for the
+                     * first time.
                      */
-                    columnAutoWidthAssignScheduler.reschedule();
+                    Map<Integer, Double> colWidths = new HashMap<Integer, Double>();
+                    Double width = Double
+                            .valueOf(ColumnConfigurationImpl.Column.DEFAULT_COLUMN_WIDTH_PX);
+                    for (int i = 0; i < getColumnConfiguration()
+                            .getColumnCount(); i++) {
+                        Integer col = Integer.valueOf(i);
+                        colWidths.put(col, width);
+                    }
+                    getColumnConfiguration().setColumnWidths(colWidths);
                 }
             }
         }
@@ -3808,19 +3773,12 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
 
     private class ColumnConfigurationImpl implements ColumnConfiguration {
         public class Column {
-            private static final int DEFAULT_COLUMN_WIDTH_PX = 100;
+            public static final double DEFAULT_COLUMN_WIDTH_PX = 100;
 
             private double definedWidth = -1;
             private double calculatedWidth = DEFAULT_COLUMN_WIDTH_PX;
             private boolean measuringRequested = false;
 
-            /**
-             * If a column has been created (either via insertRow or
-             * insertColumn), it will be given an arbitrary width, and only then
-             * a width will be defined.
-             */
-            private boolean widthHasBeenFinalized = false;
-
             public void setWidth(double px) {
                 definedWidth = px;
 
@@ -3884,15 +3842,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
             private void calculateWidth() {
                 calculatedWidth = getMaxCellWidth(columns.indexOf(this));
             }
-
-            public void widthIsFinalized() {
-                columnAutoWidthAssignScheduler.cancel();
-                widthHasBeenFinalized = true;
-            }
-
-            public boolean isWidthFinalized() {
-                return widthHasBeenFinalized;
-            }
         }
 
         private final List<Column> columns = new ArrayList<Column>();
@@ -4082,13 +4031,17 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
             body.paintInsertColumns(index, numberOfColumns, frozen);
             footer.paintInsertColumns(index, numberOfColumns, frozen);
 
-            // fix autowidth
+            // fix initial width
             if (header.getRowCount() > 0 || body.getRowCount() > 0
                     || footer.getRowCount() > 0) {
-                for (int col = index; col < index + numberOfColumns; col++) {
-                    getColumnConfiguration().setColumnWidth(col, -1);
-                    columnConfiguration.columns.get(col).widthIsFinalized();
+
+                Map<Integer, Double> colWidths = new HashMap<Integer, Double>();
+                Double width = Double.valueOf(Column.DEFAULT_COLUMN_WIDTH_PX);
+                for (int i = index; i < index + numberOfColumns; i++) {
+                    Integer col = Integer.valueOf(i);
+                    colWidths.put(col, width);
                 }
+                getColumnConfiguration().setColumnWidths(colWidths);
             }
 
             // Adjust scrollbar
@@ -4170,16 +4123,30 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
         @Override
         public void setColumnWidth(int index, double px)
                 throws IllegalArgumentException {
-            checkValidColumnIndex(index);
+            setColumnWidths(Collections.singletonMap(Integer.valueOf(index),
+                    Double.valueOf(px)));
+        }
 
-            columns.get(index).setWidth(px);
-            columns.get(index).widthIsFinalized();
-            widthsArray = null;
+        @Override
+        public void setColumnWidths(Map<Integer, Double> indexWidthMap)
+                throws IllegalArgumentException {
 
-            /*
-             * TODO [[optimize]]: only modify the elements that are actually
-             * modified.
-             */
+            if (indexWidthMap == null) {
+                throw new IllegalArgumentException("indexWidthMap was null");
+            }
+
+            if (indexWidthMap.isEmpty()) {
+                return;
+            }
+
+            for (Entry<Integer, Double> entry : indexWidthMap.entrySet()) {
+                int index = entry.getKey().intValue();
+                double width = entry.getValue().doubleValue();
+                checkValidColumnIndex(index);
+                columns.get(index).setWidth(width);
+            }
+
+            widthsArray = null;
             header.reapplyColumnWidths();
             body.reapplyColumnWidths();
             footer.reapplyColumnWidths();
@@ -4373,8 +4340,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
         }
     };
 
-    private final ColumnAutoWidthAssignScheduler columnAutoWidthAssignScheduler = new ColumnAutoWidthAssignScheduler();
-
     /**
      * Creates a new Escalator widget instance.
      */
@@ -5144,9 +5109,7 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
 
     @Override
     public boolean isWorkPending() {
-        return body.domSorter.waiting
-                || columnAutoWidthAssignScheduler.isScheduled
-                || verticalScrollbar.isWorkPending()
+        return body.domSorter.waiting || verticalScrollbar.isWorkPending()
                 || horizontalScrollbar.isWorkPending();
     }
 
index ea69968a75c786c2b5e1583f60cc690710b131a1..e112d868b4bc36faefdf43e8804d784fd91e8038 100644 (file)
@@ -22,6 +22,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -2217,21 +2218,67 @@ public class Grid<T> extends ResizeComposite implements
             rescheduleCount = 0;
 
             assert !dataIsBeingFetched : "Trying to calculate column widths even though data is still being fetched.";
-            /*
-             * At this point we assume that no data is being fetched anymore.
-             * Everything's rendered in the DOM. Now we just make sure
-             * everything fits as it should.
-             */
+
+            if (columnsAreGuaranteedToBeWiderThanGrid()) {
+                applyColumnWidths();
+            } else {
+                applyColumnWidthsWithExpansion();
+            }
+        }
+
+        private boolean columnsAreGuaranteedToBeWiderThanGrid() {
+            double freeSpace = escalator.getInnerWidth();
+            for (Column<?, ?> column : getColumns()) {
+                if (column.getWidth() >= 0) {
+                    freeSpace -= column.getWidth();
+                } else if (column.getMinimumWidth() >= 0) {
+                    freeSpace -= column.getMinimumWidth();
+                }
+            }
+            return freeSpace < 0;
+        }
+
+        @SuppressWarnings("boxing")
+        private void applyColumnWidths() {
+
+            /* Step 1: Apply all column widths as they are. */
+
+            Map<Integer, Double> selfWidths = new LinkedHashMap<Integer, Double>();
+            List<Column<?, T>> columns = getColumns();
+            for (int index = 0; index < columns.size(); index++) {
+                selfWidths.put(index, columns.get(index).getWidth());
+            }
+            Grid.this.escalator.getColumnConfiguration().setColumnWidths(
+                    selfWidths);
 
             /*
-             * Quick optimization: if the sum of fixed widths and minimum widths
-             * is greater than the grid can display, we already know that things
-             * will be squeezed and no expansion will happen.
+             * Step 2: Make sure that each column ends up obeying their min/max
+             * width constraints if defined as autowidth. If constraints are
+             * violated, fix it.
              */
-            if (gridWasTooNarrowAndEverythingWasFixedAlready()) {
-                return;
+
+            Map<Integer, Double> constrainedWidths = new LinkedHashMap<Integer, Double>();
+            for (int index = 0; index < columns.size(); index++) {
+                Column<?, T> column = columns.get(index);
+
+                boolean hasAutoWidth = column.getWidth() < 0;
+                if (!hasAutoWidth) {
+                    continue;
+                }
+
+                // TODO: bug: these don't honor the CSS max/min. :(
+                double actualWidth = column.getWidthActual();
+                if (actualWidth < getMinWidth(column)) {
+                    constrainedWidths.put(index, column.getMinimumWidth());
+                } else if (actualWidth > getMaxWidth(column)) {
+                    constrainedWidths.put(index, column.getMaximumWidth());
+                }
             }
+            Grid.this.escalator.getColumnConfiguration().setColumnWidths(
+                    constrainedWidths);
+        }
 
+        private void applyColumnWidthsWithExpansion() {
             boolean someColumnExpands = false;
             int totalRatios = 0;
             double reservedPixels = 0;
@@ -2416,32 +2463,6 @@ public class Grid<T> extends ResizeComposite implements
             } while (minWidthsCausedReflows);
         }
 
-        private boolean gridWasTooNarrowAndEverythingWasFixedAlready() {
-            double freeSpace = escalator.getInnerWidth();
-            for (Column<?, ?> column : getColumns()) {
-                if (column.getWidth() >= 0) {
-                    freeSpace -= column.getWidth();
-                } else if (column.getMinimumWidth() >= 0) {
-                    freeSpace -= column.getMinimumWidth();
-                }
-            }
-
-            if (freeSpace < 0) {
-                for (Column<?, ?> column : getColumns()) {
-                    column.doSetWidth(column.getWidth());
-
-                    boolean wasFixedWidth = column.getWidth() <= 0;
-                    boolean newWidthIsSmallerThanMinWidth = column
-                            .getWidthActual() < getMinWidth(column);
-                    if (wasFixedWidth && newWidthIsSmallerThanMinWidth) {
-                        column.doSetWidth(column.getMinimumWidth());
-                    }
-                }
-            }
-
-            return freeSpace < 0;
-        }
-
         private int getExpandRatio(Column<?, ?> column,
                 boolean someColumnExpands) {
             int expandRatio = column.getExpandRatio();
index 29e56b1b8c45c0b70c4e2f55eeb96a1b02113f2f..7f813b9d0f19d0da3684ec3c3b5949dda8f7cda9 100644 (file)
@@ -15,6 +15,8 @@
  */
 package com.vaadin.tests.widgetset.client.grid;
 
+import java.util.Map;
+
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.TableRowElement;
 import com.google.gwt.dom.client.TableSectionElement;
@@ -87,6 +89,12 @@ public class EscalatorProxy extends Escalator {
                 throws IndexOutOfBoundsException, IllegalArgumentException {
             columnConfiguration.refreshColumns(index, numberOfColumns);
         }
+
+        @Override
+        public void setColumnWidths(Map<Integer, Double> indexWidthMap)
+                throws IllegalArgumentException {
+            columnConfiguration.setColumnWidths(indexWidthMap);
+        }
     }
 
     private class RowContainerProxy implements RowContainer {