]> source.dussan.org Git - vaadin-framework.git/commitdiff
Provide declarative support for Grid. (#7961)
authorDenis <denis@vaadin.com>
Tue, 13 Dec 2016 13:46:29 +0000 (15:46 +0200)
committerPekka Hyvönen <pekka@vaadin.com>
Tue, 13 Dec 2016 13:46:29 +0000 (15:46 +0200)
Fixes vaadin/framework8-issues#390

14 files changed:
all/src/main/templates/release-notes.html
server/src/main/java/com/vaadin/ui/AbstractListing.java
server/src/main/java/com/vaadin/ui/DeclarativeValueProvider.java [new file with mode: 0644]
server/src/main/java/com/vaadin/ui/Grid.java
server/src/main/java/com/vaadin/ui/components/grid/Footer.java
server/src/main/java/com/vaadin/ui/components/grid/Header.java
server/src/main/java/com/vaadin/ui/components/grid/MultiSelectionModelImpl.java
server/src/main/java/com/vaadin/ui/components/grid/SingleSelectionModelImpl.java
server/src/main/java/com/vaadin/ui/components/grid/StaticSection.java
server/src/test/java/com/vaadin/tests/design/DeclarativeTestBaseBase.java
server/src/test/java/com/vaadin/tests/server/component/abstractcomponent/AbstractComponentDeclarativeTestBase.java
server/src/test/java/com/vaadin/tests/server/component/grid/GridDeclarativeTest.java [new file with mode: 0644]
uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
uitest/src/test/java/com/vaadin/tests/components/grid/GridSelectionTest.java

index 1daed885087a90ccb143e795ef280bfbdaa82e16..70f8539e6d009dc8c4763c8a969e69a16f9ca383 100644 (file)
             <li><tt>required-indicator-visible</tt> attribute replaces the <tt>required</tt> attribute</li>
             <li><tt>placeholder</tt> attribute replaces the <tt>input-prompt</tt> attribute for input components</li>
             <li><tt>multi-select</tt> attribute is no longer used for select components</li>
-            <li><tt>v-option-group</tt> with attribute <tt>multi-select=true</tt> is replaced by <tt>v-check-box-group</tt></li>
-            <li><tt>v-option-group</tt> with attribute <tt>multi-select=false</tt> is replaced by <tt>v-radio-button-group</tt></li>
+            <li><tt>vaadin-option-group</tt> with attribute <tt>multi-select=true</tt> is replaced by <tt>v-check-box-group</tt></li>
+            <li><tt>vaadin-option-group</tt> with attribute <tt>multi-select=false</tt> is replaced by <tt>v-radio-button-group</tt></li>
             <li><tt>immediate</tt> attribute is not used for any component</li>
             <li><tt>read-only</tt> attribute is now only used for field components instead of all components</li>
-            <li><tt>v-upload</tt> has a new attribute <tt>immediate-mode</tt> that replaces the removed <tt>immediate</tt> attribue</li>
+            <li><tt>vaadin-upload</tt> has a new attribute <tt>immediate-mode</tt> that replaces the removed <tt>immediate</tt> attribue</li>
+            <li><tt>vaadin-grid</tt> column elements don't have <tt>property-id</tt> attribute anymore. Columns aren't addressed by a property anymore but they have an id. So there is <tt>column-id</tt> attribute instead</li>
+            <li><tt>vaadin-grid</tt> doesn't have <tt>readonly</tt> attribute anymore. It  is replaced by <tt>selection-allowed</tt> attribute which has special meaning for a <tt>Grid</tt></li> 
         </ul>
         <ul id="legacycomponents"><h4>Legacy components in the v7 compatiblity package <tt>com.vaadin.v7.ui</tt> available as a separate dependency</h4>
             <li><tt>Calendar</tt> - no replacement in 8</li>
index de742e21454b74b45ae0534c0c8dd14e6b8fd77c..0fa18bddbcd12da1341290cbef0fcacb0f7a39c3 100644 (file)
@@ -269,7 +269,26 @@ public abstract class AbstractListing<T> extends AbstractComponent {
     @Override
     public void writeDesign(Element design, DesignContext designContext) {
         super.writeDesign(design, designContext);
+        doWriteDesign(design, designContext);
+    }
 
+    /**
+     * Writes listing specific state into the given design.
+     * <p>
+     * This method is separated from {@link writeDesign(Element, DesignContext)}
+     * to be overridable in subclasses that need to replace this, but still must
+     * be able to call {@code super.writeDesign(...)}.
+     * 
+     * @see #doReadDesign(Element, DesignContext)
+     *
+     * @param design
+     *            The element to write the component state to. Any previous
+     *            attributes or child nodes are <i>not</i> cleared.
+     * @param designContext
+     *            The DesignContext instance used for writing the design
+     *
+     */
+    protected void doWriteDesign(Element design, DesignContext designContext) {
         // Write options if warranted
         if (designContext.shouldWriteData(this)) {
             writeItems(design, designContext);
@@ -330,6 +349,24 @@ public abstract class AbstractListing<T> extends AbstractComponent {
     @Override
     public void readDesign(Element design, DesignContext context) {
         super.readDesign(design, context);
+        doReadDesign(design, context);
+    }
+
+    /**
+     * Reads the listing specific state from the given design.
+     * <p>
+     * This method is separated from {@link readDesign(Element, DesignContext)}
+     * to be overridable in subclasses that need to replace this, but still must
+     * be able to call {@code super.readDesign(...)}.
+     * 
+     * @see #doWriteDesign(Element, DesignContext)
+     *
+     * @param design
+     *            The element to obtain the state from
+     * @param context
+     *            The DesignContext instance used for parsing the design
+     */
+    protected void doReadDesign(Element design, DesignContext context) {
         Attributes attr = design.attributes();
         if (attr.hasKey("readonly")) {
             setReadOnly(DesignAttributeHandler.readAttribute("readonly", attr,
@@ -441,7 +478,7 @@ public abstract class AbstractListing<T> extends AbstractComponent {
      * <p>
      * Default implementation delegates a call to {@code item.toString()}.
      *
-     * @see #serializeDeclarativeRepresentation(Object)
+     * @see #deserializeDeclarativeRepresentation(String)
      *
      * @param item
      *            a data item
diff --git a/server/src/main/java/com/vaadin/ui/DeclarativeValueProvider.java b/server/src/main/java/com/vaadin/ui/DeclarativeValueProvider.java
new file mode 100644 (file)
index 0000000..9e2975b
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui;
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+import com.vaadin.server.SerializableFunction;
+
+/**
+ * Value provider class for declarative support.
+ * <p>
+ * Provides a straightforward mapping between an item and its value.
+ *
+ * @param <T>
+ *            item type
+ */
+class DeclarativeValueProvider<T> implements SerializableFunction<T, String> {
+
+    private final Map<T, String> values = new IdentityHashMap<>();
+
+    @Override
+    public String apply(T t) {
+        return values.get(t);
+    }
+
+    /**
+     * Sets a {@code value} for the item {@code t}.
+     *
+     * @param t
+     *            a data item
+     * @param value
+     *            a value for the item {@code t}
+     */
+    void addValue(T t, String value) {
+        values.put(t, value);
+    }
+
+}
index 4e0b8b7457dd8d04eb4d9ddf49d5d7e1b8ba64da..60bd9abcaf8d7ef4581f5e59efdc8839364d7b08 100644 (file)
@@ -30,7 +30,9 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
+import java.util.UUID;
 import java.util.function.BiFunction;
 import java.util.function.BinaryOperator;
 import java.util.function.Consumer;
@@ -38,7 +40,9 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import org.jsoup.nodes.Attributes;
 import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
 
 import com.vaadin.data.Binder;
 import com.vaadin.data.BinderValidationStatus;
@@ -57,6 +61,7 @@ import com.vaadin.server.SerializableComparator;
 import com.vaadin.server.SerializableFunction;
 import com.vaadin.server.data.DataCommunicator;
 import com.vaadin.server.data.DataProvider;
+import com.vaadin.server.data.Query;
 import com.vaadin.server.data.SortOrder;
 import com.vaadin.shared.MouseEventDetails;
 import com.vaadin.shared.Registration;
@@ -74,6 +79,7 @@ import com.vaadin.shared.ui.grid.HeightMode;
 import com.vaadin.shared.ui.grid.SectionState;
 import com.vaadin.shared.util.SharedUtil;
 import com.vaadin.ui.Grid.FooterRow;
+import com.vaadin.ui.Grid.SelectionMode;
 import com.vaadin.ui.components.grid.AbstractSelectionModel;
 import com.vaadin.ui.components.grid.EditorImpl;
 import com.vaadin.ui.components.grid.Footer;
@@ -82,8 +88,12 @@ import com.vaadin.ui.components.grid.Header.Row;
 import com.vaadin.ui.components.grid.MultiSelectionModelImpl;
 import com.vaadin.ui.components.grid.NoSelectionModel;
 import com.vaadin.ui.components.grid.SingleSelectionModelImpl;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
 import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.ui.declarative.DesignException;
+import com.vaadin.ui.declarative.DesignFormatter;
 import com.vaadin.ui.renderers.AbstractRenderer;
+import com.vaadin.ui.renderers.HtmlRenderer;
 import com.vaadin.ui.renderers.Renderer;
 import com.vaadin.ui.renderers.TextRenderer;
 import com.vaadin.util.ReflectTools;
@@ -1780,6 +1790,136 @@ public class Grid<T> extends AbstractListing<T>
                         "Column is no longer attached to a grid.");
             }
         }
+
+        /**
+         * Writes the design attributes for this column into given element.
+         *
+         * @since 7.5.0
+         *
+         * @param element
+         *            Element to write attributes into
+         *
+         * @param designContext
+         *            the design context
+         */
+        protected void writeDesign(Element element,
+                DesignContext designContext) {
+            Attributes attributes = element.attributes();
+
+            ColumnState defaultState = new ColumnState();
+
+            DesignAttributeHandler.writeAttribute("column-id", attributes,
+                    getId(), null, String.class, designContext);
+
+            // Sortable is a special attribute that depends on the data
+            // provider.
+            DesignAttributeHandler.writeAttribute("sortable", attributes,
+                    isSortable(), null, boolean.class, designContext);
+            DesignAttributeHandler.writeAttribute("editable", attributes,
+                    isEditable(), defaultState.editable, boolean.class,
+                    designContext);
+            DesignAttributeHandler.writeAttribute("resizable", attributes,
+                    isResizable(), defaultState.resizable, boolean.class,
+                    designContext);
+
+            DesignAttributeHandler.writeAttribute("hidable", attributes,
+                    isHidable(), defaultState.hidable, boolean.class,
+                    designContext);
+            DesignAttributeHandler.writeAttribute("hidden", attributes,
+                    isHidden(), defaultState.hidden, boolean.class,
+                    designContext);
+            DesignAttributeHandler.writeAttribute("hiding-toggle-caption",
+                    attributes, getHidingToggleCaption(),
+                    defaultState.hidingToggleCaption, String.class,
+                    designContext);
+
+            DesignAttributeHandler.writeAttribute("width", attributes,
+                    getWidth(), defaultState.width, Double.class,
+                    designContext);
+            DesignAttributeHandler.writeAttribute("min-width", attributes,
+                    getMinimumWidth(), defaultState.minWidth, Double.class,
+                    designContext);
+            DesignAttributeHandler.writeAttribute("max-width", attributes,
+                    getMaximumWidth(), defaultState.maxWidth, Double.class,
+                    designContext);
+            DesignAttributeHandler.writeAttribute("expand", attributes,
+                    getExpandRatio(), defaultState.expandRatio, Integer.class,
+                    designContext);
+        }
+
+        /**
+         * Reads the design attributes for this column from given element.
+         *
+         * @since 7.5.0
+         * @param design
+         *            Element to read attributes from
+         * @param designContext
+         *            the design context
+         */
+        protected void readDesign(Element design, DesignContext designContext) {
+            Attributes attributes = design.attributes();
+
+            if (design.hasAttr("sortable")) {
+                setSortable(DesignAttributeHandler.readAttribute("sortable",
+                        attributes, boolean.class));
+            } else {
+                setSortable(false);
+            }
+            if (design.hasAttr("editable")) {
+                /*
+                 * This is a fake editor just to have something (otherwise
+                 * "setEditable" throws an exception.
+                 * 
+                 * Let's use TextField here because we support only Strings as
+                 * inline data type. It will work incorrectly for other types
+                 * but we don't support them anyway.
+                 */
+                setEditorComponentGenerator(item -> new TextField(
+                        Optional.ofNullable(valueProvider.apply(item))
+                                .map(Object::toString).orElse("")));
+                setEditable(DesignAttributeHandler.readAttribute("editable",
+                        attributes, boolean.class));
+            }
+            if (design.hasAttr("resizable")) {
+                setResizable(DesignAttributeHandler.readAttribute("resizable",
+                        attributes, boolean.class));
+            }
+
+            if (design.hasAttr("hidable")) {
+                setHidable(DesignAttributeHandler.readAttribute("hidable",
+                        attributes, boolean.class));
+            }
+            if (design.hasAttr("hidden")) {
+                setHidden(DesignAttributeHandler.readAttribute("hidden",
+                        attributes, boolean.class));
+            }
+            if (design.hasAttr("hiding-toggle-caption")) {
+                setHidingToggleCaption(DesignAttributeHandler.readAttribute(
+                        "hiding-toggle-caption", attributes, String.class));
+            }
+
+            // Read size info where necessary.
+            if (design.hasAttr("width")) {
+                setWidth(DesignAttributeHandler.readAttribute("width",
+                        attributes, Double.class));
+            }
+            if (design.hasAttr("min-width")) {
+                setMinimumWidth(DesignAttributeHandler
+                        .readAttribute("min-width", attributes, Double.class));
+            }
+            if (design.hasAttr("max-width")) {
+                setMaximumWidth(DesignAttributeHandler
+                        .readAttribute("max-width", attributes, Double.class));
+            }
+            if (design.hasAttr("expand")) {
+                if (design.attr("expand").isEmpty()) {
+                    setExpandRatio(1);
+                } else {
+                    setExpandRatio(DesignAttributeHandler.readAttribute(
+                            "expand", attributes, Integer.class));
+                }
+            }
+        }
     }
 
     /**
@@ -3306,15 +3446,8 @@ public class Grid<T> extends AbstractListing<T>
         fireEvent(new ColumnResizeEvent(this, column, userOriginated));
     }
 
-    @Override
-    protected Element writeItem(Element design, T item, DesignContext context) {
-        // TODO see vaadin/framework8-issues#390
-        return null;
-    }
-
     @Override
     protected List<T> readItems(Element design, DesignContext context) {
-        // TODO see vaadin/framework8-issues#390
         return Collections.emptyList();
     }
 
@@ -3328,4 +3461,224 @@ public class Grid<T> extends AbstractListing<T>
         internalSetDataProvider(dataProvider);
     }
 
+    @Override
+    protected void doReadDesign(Element design, DesignContext context) {
+        Attributes attrs = design.attributes();
+        if (attrs.hasKey("selection-mode")) {
+            setSelectionMode(DesignAttributeHandler.readAttribute(
+                    "selection-mode", attrs, SelectionMode.class));
+        }
+        Attributes attr = design.attributes();
+        if (attr.hasKey("selection-allowed")) {
+            setReadOnly(DesignAttributeHandler
+                    .readAttribute("selection-allowed", attr, Boolean.class));
+        }
+
+        if (attrs.hasKey("rows")) {
+            setHeightByRows(DesignAttributeHandler.readAttribute("rows", attrs,
+                    double.class));
+        }
+
+        readStructure(design, context);
+
+        // Read frozen columns after columns are read.
+        if (attrs.hasKey("frozen-columns")) {
+            setFrozenColumnCount(DesignAttributeHandler
+                    .readAttribute("frozen-columns", attrs, int.class));
+        }
+    }
+
+    @Override
+    protected void doWriteDesign(Element design, DesignContext designContext) {
+        Attributes attr = design.attributes();
+        DesignAttributeHandler.writeAttribute("selection-allowed", attr,
+                isReadOnly(), false, Boolean.class, designContext);
+
+        Attributes attrs = design.attributes();
+        Grid<?> defaultInstance = designContext.getDefaultInstance(this);
+
+        DesignAttributeHandler.writeAttribute("frozen-columns", attrs,
+                getFrozenColumnCount(), defaultInstance.getFrozenColumnCount(),
+                int.class, designContext);
+
+        if (HeightMode.ROW.equals(getHeightMode())) {
+            DesignAttributeHandler.writeAttribute("rows", attrs,
+                    getHeightByRows(), defaultInstance.getHeightByRows(),
+                    double.class, designContext);
+        }
+
+        SelectionMode mode = getSelectionMode();
+
+        if (mode != null) {
+            DesignAttributeHandler.writeAttribute("selection-mode", attrs, mode,
+                    SelectionMode.SINGLE, SelectionMode.class, designContext);
+        }
+
+        writeStructure(design, designContext);
+    }
+
+    @Override
+    protected T deserializeDeclarativeRepresentation(String item) {
+        if (item == null) {
+            return super.deserializeDeclarativeRepresentation(
+                    new String(UUID.randomUUID().toString()));
+        }
+        return super.deserializeDeclarativeRepresentation(new String(item));
+    }
+
+    @Override
+    protected boolean isReadOnly() {
+        SelectionMode selectionMode = getSelectionMode();
+        if (SelectionMode.SINGLE.equals(selectionMode)) {
+            return asSingleSelect().isReadOnly();
+        } else if (SelectionMode.MULTI.equals(selectionMode)) {
+            return asMultiSelect().isReadOnly();
+        }
+        return false;
+    }
+
+    @Override
+    protected void setReadOnly(boolean readOnly) {
+        SelectionMode selectionMode = getSelectionMode();
+        if (SelectionMode.SINGLE.equals(selectionMode)) {
+            asSingleSelect().setReadOnly(readOnly);
+        } else if (SelectionMode.MULTI.equals(selectionMode)) {
+            asMultiSelect().setReadOnly(readOnly);
+        }
+    }
+
+    private void readStructure(Element design, DesignContext context) {
+        if (design.children().isEmpty()) {
+            return;
+        }
+        if (design.children().size() > 1
+                || !design.child(0).tagName().equals("table")) {
+            throw new DesignException(
+                    "Grid needs to have a table element as its only child");
+        }
+        Element table = design.child(0);
+
+        Elements colgroups = table.getElementsByTag("colgroup");
+        if (colgroups.size() != 1) {
+            throw new DesignException(
+                    "Table element in declarative Grid needs to have a"
+                            + " colgroup defining the columns used in Grid");
+        }
+
+        List<DeclarativeValueProvider<T>> providers = new ArrayList<>();
+        for (Element col : colgroups.get(0).getElementsByTag("col")) {
+            String id = DesignAttributeHandler.readAttribute("column-id",
+                    col.attributes(), null, String.class);
+            Column<T, String> column;
+            DeclarativeValueProvider<T> provider = new DeclarativeValueProvider<>();
+            if (id != null) {
+                column = addColumn(id, provider, new HtmlRenderer());
+            } else {
+                column = addColumn(provider, new HtmlRenderer());
+            }
+            providers.add(provider);
+            column.readDesign(col, context);
+        }
+
+        for (Element child : table.children()) {
+            if (child.tagName().equals("thead")) {
+                getHeader().readDesign(child, context);
+            } else if (child.tagName().equals("tbody")) {
+                readData(child, providers);
+            } else if (child.tagName().equals("tfoot")) {
+                getFooter().readDesign(child, context);
+            }
+        }
+    }
+
+    private void readData(Element body,
+            List<DeclarativeValueProvider<T>> providers) {
+        getSelectionModel().deselectAll();
+        List<T> items = new ArrayList<>();
+        for (Element row : body.children()) {
+            T item = deserializeDeclarativeRepresentation(row.attr("item"));
+            if (row.hasAttr("selected")) {
+                getSelectionModel().select(item);
+            }
+            Elements cells = row.children();
+            int i = 0;
+            for (Element cell : cells) {
+                providers.get(i).addValue(item, cell.html());
+                i++;
+            }
+        }
+
+        setItems(items);
+    }
+
+    private void writeStructure(Element design, DesignContext designContext) {
+        if (getColumns().isEmpty()) {
+            return;
+        }
+        Element tableElement = design.appendElement("table");
+        Element colGroup = tableElement.appendElement("colgroup");
+
+        getColumns().forEach(column -> column
+                .writeDesign(colGroup.appendElement("col"), designContext));
+
+        // Always write thead. Reads correctly when there no header rows
+        getHeader().writeDesign(tableElement.appendElement("thead"),
+                designContext);
+
+        if (designContext.shouldWriteData(this)) {
+            Element bodyElement = tableElement.appendElement("tbody");
+            getDataProvider().fetch(new Query<>()).forEach(
+                    item -> writeRow(bodyElement, item, designContext));
+        }
+
+        if (getFooter().getRowCount() > 0) {
+            getFooter().writeDesign(tableElement.appendElement("tfoot"),
+                    designContext);
+        }
+    }
+
+    private void writeRow(Element container, T item, DesignContext context) {
+        Element tableRow = container.appendElement("tr");
+        tableRow.attr("item", serializeDeclarativeRepresentation(item));
+        if (getSelectionModel().isSelected(item)) {
+            tableRow.attr("selected", "");
+        }
+        for (Column<T, ?> column : getColumns()) {
+            Object value = column.valueProvider.apply(item);
+            tableRow.appendElement("td")
+                    .append((Optional.ofNullable(value).map(Object::toString)
+                            .map(DesignFormatter::encodeForTextNode)
+                            .orElse("")));
+        }
+    }
+
+    private SelectionMode getSelectionMode() {
+        GridSelectionModel<T> selectionModel = getSelectionModel();
+        SelectionMode mode = null;
+        if (selectionModel.getClass().equals(SingleSelectionModelImpl.class)) {
+            mode = SelectionMode.SINGLE;
+        } else if (selectionModel.getClass()
+                .equals(MultiSelectionModelImpl.class)) {
+            mode = SelectionMode.MULTI;
+        } else if (selectionModel.getClass().equals(NoSelectionModel.class)) {
+            mode = SelectionMode.NONE;
+        }
+        return mode;
+    }
+
+    @Override
+    protected Collection<String> getCustomAttributes() {
+        Collection<String> result = super.getCustomAttributes();
+        // "rename" for frozen column count
+        result.add("frozen-column-count");
+        result.add("frozen-columns");
+        // "rename" for height-mode
+        result.add("height-by-rows");
+        result.add("rows");
+        // add a selection-mode attribute
+        result.add("selection-mode");
+
+        return result;
+    }
+
 }
index 0da179a2c08d452bb34552f78a53154b287ba7c3..7d4f45cd1e00a0f5566cab71a5288dc51cefd865 100644 (file)
  */
 package com.vaadin.ui.components.grid;
 
-import com.vaadin.ui.Grid;
-
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
+import com.vaadin.ui.Grid;
+
 /**
  * Represents the footer section of a Grid.
  *
@@ -67,12 +67,14 @@ public abstract class Footer extends StaticSection<Footer.Row> {
         }
 
         /**
-         * Merges column cells in the row. Original cells are hidden, and new merged cell is shown instead.
-         * The cell has a width of all merged cells together, inherits styles of the first merged cell
-         * but has empty caption.
+         * Merges column cells in the row. Original cells are hidden, and new
+         * merged cell is shown instead. The cell has a width of all merged
+         * cells together, inherits styles of the first merged cell but has
+         * empty caption.
          *
          * @param cellsToMerge
-         *            the cells which should be merged. The cells should not be merged to any other cell set.
+         *            the cells which should be merged. The cells should not be
+         *            merged to any other cell set.
          * @return the remaining visible cell after the merge
          *
          * @see #join(Grid.FooterCell...)
@@ -97,12 +99,14 @@ public abstract class Footer extends StaticSection<Footer.Row> {
         }
 
         /**
-         * Merges column cells in the row. Original cells are hidden, and new merged cell is shown instead.
-         * The cell has a width of all merged cells together, inherits styles of the first merged cell
-         * but has empty caption.
+         * Merges column cells in the row. Original cells are hidden, and new
+         * merged cell is shown instead. The cell has a width of all merged
+         * cells together, inherits styles of the first merged cell but has
+         * empty caption.
          *
          * @param cellsToMerge
-         *            the cells which should be merged. The cells should not be merged to any other cell set.
+         *            the cells which should be merged. The cells should not be
+         *            merged to any other cell set.
          * @return the remaining visible cell after the merge
          *
          * @see #join(Set)
@@ -110,15 +114,16 @@ public abstract class Footer extends StaticSection<Footer.Row> {
          */
         @Override
         public Grid.FooterCell join(Grid.FooterCell... cellsToMerge) {
-            Set<Grid.FooterCell> footerCells = new HashSet<>(Arrays.asList(cellsToMerge));
+            Set<Grid.FooterCell> footerCells = new HashSet<>(
+                    Arrays.asList(cellsToMerge));
             return join(footerCells);
         }
 
-
     }
 
     @Override
     public Row createRow() {
         return new Row();
     }
+
 }
index 5236a200fa462f0814eeb096a083af8d56094850..d1d369a9c8039cdf0cf4c54d3f59d8f113207aec 100644 (file)
  */
 package com.vaadin.ui.components.grid;
 
-import com.vaadin.ui.Grid;
-
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
+import org.jsoup.nodes.Element;
+
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
+import com.vaadin.ui.declarative.DesignContext;
+
 /**
  * Represents the header section of a Grid.
  *
@@ -39,8 +43,8 @@ public abstract class Header extends StaticSection<Header.Row> {
         /**
          * A cell in a Grid header row.
          */
-        public class Cell extends StaticSection.StaticCell implements
-                Grid.HeaderCell {
+        public class Cell extends StaticSection.StaticCell
+                implements Grid.HeaderCell {
             /**
              * Creates a new header cell.
              */
@@ -87,12 +91,14 @@ public abstract class Header extends StaticSection<Header.Row> {
         }
 
         /**
-         * Merges column cells in the row. Original cells are hidden, and new merged cell is shown instead.
-         * The cell has a width of all merged cells together, inherits styles of the first merged cell
-         * but has empty caption.
+         * Merges column cells in the row. Original cells are hidden, and new
+         * merged cell is shown instead. The cell has a width of all merged
+         * cells together, inherits styles of the first merged cell but has
+         * empty caption.
          *
          * @param cellsToMerge
-         *            the cells which should be merged. The cells should not be merged to any other cell set.
+         *            the cells which should be merged. The cells should not be
+         *            merged to any other cell set.
          * @return the remaining visible cell after the merge
          *
          * @see #join(Grid.HeaderCell...)
@@ -117,12 +123,14 @@ public abstract class Header extends StaticSection<Header.Row> {
         }
 
         /**
-         * Merges column cells in the row. Original cells are hidden, and new merged cell is shown instead.
-         * The cell has a width of all merged cells together, inherits styles of the first merged cell
-         * but has empty caption.
+         * Merges column cells in the row. Original cells are hidden, and new
+         * merged cell is shown instead. The cell has a width of all merged
+         * cells together, inherits styles of the first merged cell but has
+         * empty caption.
          *
          * @param cellsToMerge
-         *            the cells which should be merged. The cells should not be merged to any other cell set.
+         *            the cells which should be merged. The cells should not be
+         *            merged to any other cell set.
          * @return the remaining visible cell after the merge
          *
          * @see #join(Set)
@@ -130,10 +138,34 @@ public abstract class Header extends StaticSection<Header.Row> {
          */
         @Override
         public Grid.HeaderCell join(Grid.HeaderCell... cellsToMerge) {
-            Set<Grid.HeaderCell> headerCells = new HashSet<>(Arrays.asList(cellsToMerge));
+            Set<Grid.HeaderCell> headerCells = new HashSet<>(
+                    Arrays.asList(cellsToMerge));
             return join(headerCells);
         }
 
+        @Override
+        protected void readDesign(Element trElement,
+                DesignContext designContext) {
+            super.readDesign(trElement, designContext);
+
+            boolean defaultRow = DesignAttributeHandler.readAttribute("default",
+                    trElement.attributes(), false, boolean.class);
+            if (defaultRow) {
+                setDefault(true);
+            }
+        }
+
+        @Override
+        protected void writeDesign(Element trElement,
+                DesignContext designContext) {
+            super.writeDesign(trElement, designContext);
+
+            if (isDefault()) {
+                DesignAttributeHandler.writeAttribute("default",
+                        trElement.attributes(), true, null, boolean.class,
+                        designContext);
+            }
+        }
     }
 
     @Override
@@ -156,9 +188,7 @@ public abstract class Header extends StaticSection<Header.Row> {
      * @return the default row, or {@code null} if there is no default row
      */
     public Row getDefaultRow() {
-        return getRows().stream()
-                .filter(Row::isDefault)
-                .findAny().orElse(null);
+        return getRows().stream().filter(Row::isDefault).findAny().orElse(null);
     }
 
     /**
@@ -185,4 +215,5 @@ public abstract class Header extends StaticSection<Header.Row> {
 
         markAsDirty();
     }
+
 }
index ba17315b07f9fece06e9d3d06461430d441bbf0f..fa25ff677c11eaa46747beaacc45ed85a8f5cbcf 100644 (file)
@@ -316,12 +316,12 @@ public class MultiSelectionModelImpl<T> extends AbstractSelectionModel<T>
 
             @Override
             public void setReadOnly(boolean readOnly) {
-                getState().selectionAllowed = readOnly;
+                getState().selectionAllowed = !readOnly;
             }
 
             @Override
             public boolean isReadOnly() {
-                return isUserSelectionAllowed();
+                return !isUserSelectionAllowed();
             }
 
             @Override
index 108ca6669e4753dbb452913e682a7b667fd28374..8487ea87ef72a1300a29773f4291948d12e6329a 100644 (file)
@@ -269,12 +269,12 @@ public class SingleSelectionModelImpl<T> extends AbstractSelectionModel<T>
 
             @Override
             public void setReadOnly(boolean readOnly) {
-                getState().selectionAllowed = readOnly;
+                getState().selectionAllowed = !readOnly;
             }
 
             @Override
             public boolean isReadOnly() {
-                return isUserSelectionAllowed();
+                return !isUserSelectionAllowed();
             }
         };
     }
index 5c7d682b17e11aaccb94b6c2d8e6c7c3d6720556..3edb4fecc98ff10689cf1bf6b1d2ff23fd0b62fb 100644 (file)
@@ -19,12 +19,21 @@ import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
 
 import com.vaadin.shared.ui.grid.GridStaticCellType;
 import com.vaadin.shared.ui.grid.SectionState;
@@ -33,6 +42,10 @@ import com.vaadin.shared.ui.grid.SectionState.RowState;
 import com.vaadin.ui.Component;
 import com.vaadin.ui.Grid;
 import com.vaadin.ui.Grid.Column;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
+import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.ui.declarative.DesignException;
+import com.vaadin.ui.declarative.DesignFormatter;
 
 /**
  * Represents the header or footer section of a Grid.
@@ -108,10 +121,11 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
             CELL cell = cells.remove(columnId);
             if (cell != null) {
                 rowState.cells.remove(columnId);
-                for (Iterator<Set<String>> iterator = rowState.cellGroups.values().iterator(); iterator.hasNext(); ) {
+                for (Iterator<Set<String>> iterator = rowState.cellGroups
+                        .values().iterator(); iterator.hasNext();) {
                     Set<String> group = iterator.next();
                     group.remove(columnId);
-                    if(group.size() < 2) {
+                    if (group.size() < 2) {
                         iterator.remove();
                     }
                 }
@@ -147,6 +161,143 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
             return cell;
         }
 
+        /**
+         * Reads the declarative design from the given table row element.
+         *
+         * @since 7.5.0
+         * @param trElement
+         *            Element to read design from
+         * @param designContext
+         *            the design context
+         * @throws DesignException
+         *             if the given table row contains unexpected children
+         */
+        protected void readDesign(Element trElement,
+                DesignContext designContext) throws DesignException {
+            Elements cellElements = trElement.children();
+            for (int i = 0; i < cellElements.size(); i++) {
+                Element element = cellElements.get(i);
+                if (!element.tagName().equals(getCellTagName())) {
+                    throw new DesignException(
+                            "Unexpected element in tr while expecting "
+                                    + getCellTagName() + ": "
+                                    + element.tagName());
+                }
+
+                int colspan = DesignAttributeHandler.readAttribute("colspan",
+                        element.attributes(), 1, int.class);
+
+                String columnIdsString = DesignAttributeHandler.readAttribute(
+                        "column-ids", element.attributes(), "", String.class);
+                if (columnIdsString.trim().isEmpty()) {
+                    throw new DesignException(
+                            "Unexpected 'column-ids' attribute value '"
+                                    + columnIdsString
+                                    + "'. It cannot be empty and must "
+                                    + "be comma separated column identifiers");
+                }
+                String[] columnIds = columnIdsString.split(",");
+                if (columnIds.length != colspan) {
+                    throw new DesignException(
+                            "Unexpected 'colspan' attribute value '" + colspan
+                                    + "' whereas there is " + columnIds.length
+                                    + " column identifiers specified : '"
+                                    + columnIdsString + "'");
+                }
+
+                Stream.of(columnIds).forEach(this::addCell);
+
+                Stream<String> idsStream = Stream.of(columnIds);
+                if (colspan > 1) {
+                    CELL newCell = createCell();
+                    addMergedCell(createCell(),
+                            idsStream.collect(Collectors.toSet()));
+                    newCell.readDesign(element, designContext);
+                } else {
+                    idsStream.map(this::getCell).forEach(
+                            cell -> cell.readDesign(element, designContext));
+                }
+            }
+        }
+
+        /**
+         * Writes the declarative design to the given table row element.
+         *
+         * @since 7.5.0
+         * @param trElement
+         *            Element to write design to
+         * @param designContext
+         *            the design context
+         */
+        protected void writeDesign(Element trElement,
+                DesignContext designContext) {
+            Set<String> visited = new HashSet<>();
+            for (Entry<String, CELL> entry : cells.entrySet()) {
+                if (visited.contains(entry.getKey())) {
+                    continue;
+                }
+                visited.add(entry.getKey());
+
+                Element cellElement = trElement.appendElement(getCellTagName());
+
+                Optional<Entry<CellState, Set<String>>> groupCell = getRowState().cellGroups
+                        .entrySet().stream().filter(groupEntry -> groupEntry
+                                .getValue().contains(entry.getKey()))
+                        .findFirst();
+                Stream<String> columnIds = Stream.of(entry.getKey());
+                if (groupCell.isPresent()) {
+                    Set<String> orderedSet = new LinkedHashSet<>(
+                            cells.keySet());
+                    orderedSet.retainAll(groupCell.get().getValue());
+                    columnIds = orderedSet.stream();
+                    visited.addAll(orderedSet);
+                    cellElement.attr("colspan", "" + orderedSet.size());
+                    writeCellState(cellElement, designContext,
+                            groupCell.get().getKey());
+                } else {
+                    writeCellState(cellElement, designContext,
+                            entry.getValue().getCellState());
+                }
+                cellElement.attr("column-ids",
+                        columnIds.collect(Collectors.joining(",")));
+            }
+        }
+
+        /**
+         * 
+         * Writes declarative design for the cell using its {@code state} to the
+         * given table cell element.
+         * <p>
+         * The method is used instead of StaticCell::writeDesign because
+         * sometimes there is no a reference to the cell which should be written
+         * (merged cell) but only its state is available (the cell is virtual
+         * and is not stored).
+         * 
+         * @param cellElement
+         *            Element to write design to
+         * @param context
+         *            the design context
+         * @param state
+         *            a cell state
+         */
+        protected void writeCellState(Element cellElement,
+                DesignContext context, CellState state) {
+            switch (state.type) {
+            case TEXT:
+                cellElement.attr("plain-text", true);
+                cellElement
+                        .appendText(Optional.ofNullable(state.text).orElse(""));
+                break;
+            case HTML:
+                cellElement.append(Optional.ofNullable(state.html).orElse(""));
+                break;
+            case WIDGET:
+                cellElement.appendChild(
+                        context.createElement((Component) state.connector));
+                break;
+            }
+        }
+
         void detach() {
             for (CELL cell : cells.values()) {
                 cell.detach();
@@ -299,6 +450,31 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
             return cellState.type;
         }
 
+        /**
+         * Reads the declarative design from the given table cell element.
+         *
+         * @since 7.5.0
+         * @param cellElement
+         *            Element to read design from
+         * @param designContext
+         *            the design context
+         */
+        protected void readDesign(Element cellElement,
+                DesignContext designContext) {
+            if (!cellElement.hasAttr("plain-text")) {
+                if (cellElement.children().size() > 0
+                        && cellElement.child(0).tagName().contains("-")) {
+                    setComponent(
+                            designContext.readDesign(cellElement.child(0)));
+                } else {
+                    setHtml(cellElement.html());
+                }
+            } else {
+                // text – need to unescape HTML entities
+                setText(DesignFormatter.decodeFromTextNode(cellElement.html()));
+            }
+        }
+
         private void removeComponentIfPresent() {
             Component component = (Component) cellState.connector;
             if (component != null) {
@@ -440,6 +616,48 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
         markAsDirty();
     }
 
+    /**
+     * Writes the declarative design to the given table section element.
+     *
+     * @param tableSectionElement
+     *            Element to write design to
+     * @param designContext
+     *            the design context
+     */
+    public void writeDesign(Element tableSectionElement,
+            DesignContext designContext) {
+        for (ROW row : getRows()) {
+            Element tr = tableSectionElement.appendElement("tr");
+            row.writeDesign(tr, designContext);
+        }
+    }
+
+    /**
+     * Reads the declarative design from the given table section element.
+     *
+     * @since 7.5.0
+     * @param tableSectionElement
+     *            Element to read design from
+     * @param designContext
+     *            the design context
+     * @throws DesignException
+     *             if the table section contains unexpected children
+     */
+    public void readDesign(Element tableSectionElement,
+            DesignContext designContext) throws DesignException {
+        while (getRowCount() > 0) {
+            removeRow(0);
+        }
+
+        for (Element row : tableSectionElement.children()) {
+            if (!row.tagName().equals("tr")) {
+                throw new DesignException("Unexpected element in "
+                        + tableSectionElement.tagName() + ": " + row.tagName());
+            }
+            addRowAt(getRowCount()).readDesign(row, designContext);
+        }
+    }
+
     /**
      * Returns an unmodifiable list of the rows in this section.
      *
@@ -448,4 +666,5 @@ public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
     protected List<ROW> getRows() {
         return Collections.unmodifiableList(rows);
     }
+
 }
index 1bab760530e7a38f4f52eaa210b5bccd9c55223f..041df2deee7c9d814b2ca1a9123f49c98c8624cf 100644 (file)
@@ -221,9 +221,9 @@ public abstract class DeclarativeTestBaseBase<T extends Component> {
         return context;
     }
 
-    public void testWrite(String design, T expected) {
+    public void testWrite(String expected, T component) {
         TestLogHandler l = new TestLogHandler();
-        testWrite(design, expected, false);
+        testWrite(expected, component, false);
         Assert.assertEquals("", l.getMessages());
     }
 
index 7733855e95fd6d4cde3728d07c80caba761e1d2d..ea7e9c77a327b2d3b75de8cad6389c3970bf5f1d 100644 (file)
@@ -92,16 +92,24 @@ public abstract class AbstractComponentDeclarativeTestBase<T extends AbstractCom
         boolean visible = false;
         boolean requiredIndicator = true;
 
+        T component = getComponentClass().newInstance();
+
+        boolean hasReadOnly = callBooleanSetter(readOnly, "setReadOnly",
+                component);
+        boolean hasRequiredIndicator = callBooleanSetter(requiredIndicator,
+                "setRequiredIndicatorVisible", component);
+
         String design = String.format(
                 "<%s id='%s' caption='%s' caption-as-html description='%s' "
                         + "error='%s' enabled='false' width='%s' height='%s' "
                         + "icon='%s' locale='%s' primary-style-name='%s' "
-                        + "readonly responsive style-name='%s' visible='false' "
-                        + "required-indicator-visible/>",
+                        + "%s responsive style-name='%s' visible='false' "
+                        + "%s/>",
                 getComponentTag(), id, caption, description, error, width,
-                height, icon, locale.toString(), primaryStyle, styleName);
+                height, icon, locale.toString(), primaryStyle,
+                hasReadOnly ? "readonly" : "", styleName,
+                hasRequiredIndicator ? "required-indicator-visible" : "");
 
-        T component = getComponentClass().newInstance();
         component.setId(id);
         component.setCaption(caption);
         component.setCaptionAsHtml(captionAsHtml);
@@ -115,9 +123,6 @@ public abstract class AbstractComponentDeclarativeTestBase<T extends AbstractCom
         component.setIcon(new FileResource(new File(icon)));
         component.setLocale(locale);
         component.setPrimaryStyleName(primaryStyle);
-        callBooleanSetter(readOnly, "setReadOnly", component);
-        callBooleanSetter(requiredIndicator, "setRequiredIndicatorVisible",
-                component);
         component.setResponsive(responsive);
         component.setStyleName(styleName);
         component.setVisible(visible);
@@ -126,15 +131,17 @@ public abstract class AbstractComponentDeclarativeTestBase<T extends AbstractCom
         testWrite(design, component);
     }
 
-    private void callBooleanSetter(boolean value, String setterName,
+    private boolean callBooleanSetter(boolean value, String setterName,
             T component)
             throws IllegalAccessException, InvocationTargetException {
         try {
             Method method = component.getClass().getMethod(setterName,
                     new Class[] { boolean.class });
             method.invoke(component, value);
+            return true;
         } catch (NoSuchMethodException ignore) {
             // ignore if there is no such method
+            return false;
         }
     }
 
diff --git a/server/src/test/java/com/vaadin/tests/server/component/grid/GridDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/grid/GridDeclarativeTest.java
new file mode 100644 (file)
index 0000000..9cdab40
--- /dev/null
@@ -0,0 +1,731 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.server.component.grid;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Locale;
+
+import org.jsoup.nodes.Element;
+import org.jsoup.parser.Tag;
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.data.SelectionModel.Multi;
+import com.vaadin.data.SelectionModel.Single;
+import com.vaadin.server.data.DataProvider;
+import com.vaadin.shared.ui.grid.HeightMode;
+import com.vaadin.shared.ui.label.ContentMode;
+import com.vaadin.tests.data.bean.Person;
+import com.vaadin.tests.server.component.abstractlisting.AbstractListingDeclarativeTest;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.Grid.Column;
+import com.vaadin.ui.Grid.FooterCell;
+import com.vaadin.ui.Grid.FooterRow;
+import com.vaadin.ui.Grid.HeaderCell;
+import com.vaadin.ui.Grid.HeaderRow;
+import com.vaadin.ui.Grid.SelectionMode;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.ui.declarative.DesignException;
+
+/**
+ * @author Vaadin Ltd
+ *
+ */
+public class GridDeclarativeTest extends AbstractListingDeclarativeTest<Grid> {
+
+    @Test
+    public void gridAttributes() {
+        Grid<Person> grid = new Grid<>();
+        int frozenColumns = 1;
+        HeightMode heightMode = HeightMode.ROW;
+        double heightByRows = 13.7d;
+
+        grid.addColumn(Person::getFirstName);
+        grid.addColumn("id", Person::getLastName);
+
+        grid.setFrozenColumnCount(frozenColumns);
+        grid.setSelectionMode(SelectionMode.MULTI);
+        grid.setHeightMode(heightMode);
+        grid.setHeightByRows(heightByRows);
+
+        String design = String.format(
+                "<%s height-mode='%s' frozen-columns='%d' rows='%s' selection-mode='%s'><table><colgroup>"
+                        + "<col column-id='generatedColumn0' sortable>"
+                        + "<col column-id='id' sortable>" + "</colgroup><thead>"
+                        + "<tr default><th plain-text column-ids='generatedColumn0'>Generated Column0</th>"
+                        + "<th plain-text column-ids='id'>Id</th></tr>"
+                        + "</thead></table></%s>",
+                getComponentTag(),
+                heightMode.toString().toLowerCase(Locale.ENGLISH),
+                frozenColumns, heightByRows,
+                SelectionMode.MULTI.toString().toLowerCase(Locale.ENGLISH),
+                getComponentTag());
+
+        testRead(design, grid);
+        testWrite(design, grid);
+    }
+
+    @Test
+    public void mergedHeaderCells() {
+        Grid<Person> grid = new Grid<>();
+
+        Column<Person, String> column1 = grid.addColumn(Person::getFirstName);
+        Column<Person, String> column2 = grid.addColumn("id",
+                Person::getLastName);
+        Column<Person, String> column3 = grid.addColumn("mail",
+                Person::getEmail);
+
+        HeaderRow header = grid.addHeaderRowAt(1);
+        String headerRowText1 = "foo";
+        header.getCell(column1).setText(headerRowText1);
+        HeaderCell cell2 = header.getCell(column2);
+        HeaderCell join = header.join(cell2, header.getCell(column3));
+        String headerRowText3 = "foobar";
+        join.setText(headerRowText3);
+
+        String design = String.format("<%s><table><colgroup>"
+                + "<col column-id='generatedColumn0' sortable>"
+                + "<col column-id='id' sortable>"
+                + "<col column-id='mail' sortable>" + "</colgroup><thead>"
+                + "<tr default><th plain-text column-ids='generatedColumn0'>Generated Column0</th>"
+                + "<th plain-text column-ids='id'>Id</th>"
+                + "<th plain-text column-ids='mail'>Mail</th></tr>"
+                + "<tr><th plain-text column-ids='generatedColumn0'>%s</th>"
+                + "<th colspan='2' plain-text column-ids='id,mail'>foobar</th></tr>"
+                + "</thead></table></%s>", getComponentTag(), headerRowText1,
+                headerRowText3, getComponentTag());
+
+        testRead(design, grid);
+        testWrite(design, grid);
+    }
+
+    @Test
+    public void mergedFooterCells() {
+        Grid<Person> grid = new Grid<>();
+
+        Column<Person, String> column1 = grid.addColumn(Person::getFirstName);
+        Column<Person, String> column2 = grid.addColumn("id",
+                Person::getLastName);
+        Column<Person, String> column3 = grid.addColumn("mail",
+                Person::getEmail);
+
+        FooterRow footer = grid.addFooterRowAt(0);
+
+        FooterCell cell1 = footer.getCell(column1);
+        String footerRowText1 = "foo";
+        cell1.setText(footerRowText1);
+
+        FooterCell cell2 = footer.getCell(column2);
+
+        FooterCell cell3 = footer.getCell(column3);
+        String footerRowText2 = "foobar";
+        footer.join(cell2, cell3).setHtml(footerRowText2);
+
+        String design = String.format("<%s><table><colgroup>"
+                + "<col column-id='generatedColumn0' sortable>"
+                + "<col column-id='id' sortable>"
+                + "<col column-id='mail' sortable>" + "</colgroup><thead>"
+                + "<tr default><th plain-text column-ids='generatedColumn0'>Generated Column0</th>"
+                + "<th plain-text column-ids='id'>Id</th>"
+                + "<th plain-text column-ids='mail'>Mail</th></tr></thead>"
+                + "<tfoot><tr><td plain-text column-ids='generatedColumn0'>%s</td>"
+                + "<td colspan='2' column-ids='id,mail'>%s</td></tr></tfoot>"
+                + "</table></%s>", getComponentTag(), footerRowText1,
+                footerRowText2, getComponentTag());
+
+        testRead(design, grid);
+        testWrite(design, grid);
+    }
+
+    @Test
+    public void columnAttributes() {
+        Grid<Person> grid = new Grid<>();
+
+        String secondColumnId = "id";
+        Column<Person, String> column1 = grid.addColumn(Person::getFirstName);
+        Column<Person, String> column2 = grid.addColumn(secondColumnId,
+                Person::getLastName);
+
+        String caption = "test-caption";
+        column1.setCaption(caption);
+        boolean sortable = false;
+        column1.setSortable(sortable);
+        boolean editable = true;
+        column1.setEditorComponentGenerator(component -> null);
+        column1.setEditable(editable);
+        boolean resizable = false;
+        column1.setResizable(resizable);
+        boolean hidable = true;
+        column1.setHidable(hidable);
+        boolean hidden = true;
+        column1.setHidden(hidden);
+
+        String hidingToggleCaption = "toggle-caption";
+        column2.setHidingToggleCaption(hidingToggleCaption);
+        double width = 17.3;
+        column2.setWidth(width);
+        double minWidth = 37.3;
+        column2.setMinimumWidth(minWidth);
+        double maxWidth = 63.4;
+        column2.setMaximumWidth(maxWidth);
+        int expandRatio = 83;
+        column2.setExpandRatio(expandRatio);
+
+        String design = String.format(
+                "<%s><table><colgroup>"
+                        + "<col column-id='generatedColumn0' sortable='%s' editable resizable='%s' hidable hidden>"
+                        + "<col column-id='id' sortable hiding-toggle-caption='%s' width='%s' min-width='%s' max-width='%s' expand='%s'>"
+                        + "</colgroup><thead>"
+                        + "<tr default><th plain-text column-ids='generatedColumn0'>%s</th>"
+                        + "<th plain-text column-ids='id'>%s</th>"
+                        + "</tr></thead>" + "</table></%s>",
+                getComponentTag(), sortable, resizable, hidingToggleCaption,
+                width, minWidth, maxWidth, expandRatio, caption, "Id",
+                getComponentTag());
+
+        testRead(design, grid, true);
+        testWrite(design, grid);
+    }
+
+    @Test
+    public void headerFooterSerialization() {
+        Grid<Person> grid = new Grid<>();
+
+        Column<Person, String> column1 = grid.addColumn(Person::getFirstName);
+        Column<Person, String> column2 = grid.addColumn("id",
+                Person::getLastName);
+
+        FooterRow footerRow = grid.addFooterRowAt(0);
+        footerRow.getCell(column1).setText("x");
+        footerRow.getCell(column2).setHtml("y");
+
+        String design = String.format("<%s><table><colgroup>"
+                + "<col column-id='generatedColumn0' sortable>"
+                + "<col column-id='id' sortable></colgroup><thead>"
+                + "<tr default><th plain-text column-ids='generatedColumn0'>Generated Column0</th>"
+                + "<th plain-text column-ids='id'>Id</th></tr>"
+                + "</thead><tbody></tbody>"
+                + "<tfoot><tr><td plain-text column-ids='generatedColumn0'>x</td>"
+                + "<td column-ids='id'>y</td></tr></tfoot>" + "</table></%s>",
+                getComponentTag(), getComponentTag());
+
+        testRead(design, grid);
+        testWrite(design, grid, true);
+    }
+
+    @Override
+    public void dataSerialization() throws InstantiationException,
+            IllegalAccessException, InvocationTargetException {
+        Grid<Person> grid = new Grid<>();
+
+        Person person1 = createPerson("foo", "bar");
+        Person person2 = createPerson("name", "last-name");
+        grid.setItems(person1, person2);
+
+        grid.addColumn(Person::getFirstName);
+        grid.addColumn("id", Person::getLastName);
+
+        String design = String.format(
+                "<%s><table><colgroup>"
+                        + "<col column-id='generatedColumn0' sortable>"
+                        + "<col column-id='id' sortable></colgroup><thead>"
+                        + "<tr default><th plain-text column-ids='generatedColumn0'>Generated Column0</th>"
+                        + "<th plain-text column-ids='id'>Id</th></tr>"
+                        + "</thead><tbody>"
+                        + "<tr item='%s'><td>%s</td><td>%s</td></tr>"
+                        + "<tr item='%s'><td>%s</td><td>%s</td></tr>"
+                        + "</tbody></table></%s>",
+                getComponentTag(), person1.toString(), person1.getFirstName(),
+                person1.getLastName(), person2.toString(),
+                person2.getFirstName(), person2.getLastName(),
+                getComponentTag());
+
+        testRead(design, grid);
+        testWrite(design, grid, true);
+    }
+
+    /**
+     * Value for single select
+     */
+    @Override
+    @Test
+    public void valueSerialization() throws InstantiationException,
+            IllegalAccessException, InvocationTargetException {
+        valueSingleSelectSerialization();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void valueMultiSelectSerialization() throws InstantiationException,
+            IllegalAccessException, InvocationTargetException {
+        Grid<Person> grid = new Grid<>();
+
+        Person person1 = createPerson("foo", "bar");
+        Person person2 = createPerson("name", "last-name");
+        Person person3 = createPerson("foo", "last-name");
+        grid.setItems(person1, person2, person3);
+
+        grid.addColumn(Person::getFirstName);
+        grid.addColumn("id", Person::getLastName);
+
+        Multi<Person> model = (Multi<Person>) grid
+                .setSelectionMode(SelectionMode.MULTI);
+        model.selectItems(person1, person3);
+
+        String design = String.format(
+                "<%s selection-mode='multi'><table><colgroup>"
+                        + "<col column-id='generatedColumn0' sortable>"
+                        + "<col column-id='id' sortable></colgroup><thead>"
+                        + "<tr default><th plain-text column-ids='generatedColumn0'>Generated Column0</th>"
+                        + "<th plain-text column-ids='id'>Id</th></tr>"
+                        + "</thead><tbody>"
+                        + "<tr item='%s' selected><td>%s</td><td>%s</td></tr>"
+                        + "<tr item='%s'><td>%s</td><td>%s</td></tr>"
+                        + "<tr item='%s' selected><td>%s</td><td>%s</td></tr>"
+                        + "</tbody></table></%s>",
+                getComponentTag(), person1.toString(), person1.getFirstName(),
+                person1.getLastName(), person2.toString(),
+                person2.getFirstName(), person2.getLastName(),
+                person3.toString(), person3.getFirstName(),
+                person3.getLastName(), getComponentTag());
+
+        testRead(design, grid);
+        testWrite(design, grid, true);
+    }
+
+    @SuppressWarnings("unchecked")
+    private void valueSingleSelectSerialization() throws InstantiationException,
+            IllegalAccessException, InvocationTargetException {
+        Grid<Person> grid = new Grid<>();
+
+        Person person1 = createPerson("foo", "bar");
+        Person person2 = createPerson("name", "last-name");
+        grid.setItems(person1, person2);
+
+        grid.addColumn(Person::getFirstName);
+        grid.addColumn("id", Person::getLastName);
+
+        Single<Person> model = (Single<Person>) grid
+                .setSelectionMode(SelectionMode.SINGLE);
+        model.select(person2);
+
+        String design = String.format(
+                "<%s><table><colgroup>"
+                        + "<col column-id='generatedColumn0' sortable>"
+                        + "<col column-id='id' sortable></colgroup><thead>"
+                        + "<tr default><th plain-text column-ids='generatedColumn0'>Generated Column0</th>"
+                        + "<th plain-text column-ids='id'>Id</th></tr>"
+                        + "</thead><tbody>"
+                        + "<tr item='%s'><td>%s</td><td>%s</td></tr>"
+                        + "<tr item='%s' selected><td>%s</td><td>%s</td></tr>"
+                        + "</tbody></table></%s>",
+                getComponentTag(), person1.toString(), person1.getFirstName(),
+                person1.getLastName(), person2.toString(),
+                person2.getFirstName(), person2.getLastName(),
+                getComponentTag());
+
+        testRead(design, grid);
+        testWrite(design, grid, true);
+    }
+
+    @Override
+    public void readOnlySelection() throws InstantiationException,
+            IllegalAccessException, InvocationTargetException {
+        Grid<Person> grid = new Grid<>();
+
+        Person person1 = createPerson("foo", "bar");
+        Person person2 = createPerson("name", "last-name");
+        grid.setItems(person1, person2);
+
+        grid.addColumn(Person::getFirstName);
+        grid.addColumn("id", Person::getLastName);
+
+        grid.setSelectionMode(SelectionMode.MULTI);
+        grid.asMultiSelect().setReadOnly(true);
+
+        String formatString = "<%s %s selection-allowed><table><colgroup>"
+                + "<col column-id='generatedColumn0' sortable>"
+                + "<col column-id='id' sortable>" + "</colgroup><thead>"
+                + "<tr default><th plain-text column-ids='generatedColumn0'>Generated Column0</th>"
+                + "<th plain-text column-ids='id'>Id</th></tr>"
+                + "</thead><tbody>"
+                + "<tr item='%s'><td>%s</td><td>%s</td></tr>"
+                + "<tr item='%s'><td>%s</td><td>%s</td></tr>"
+                + "</tbody></table></%s>";
+
+        String design = String.format(formatString, getComponentTag(),
+                "selection-mode='multi'", person1.toString(),
+                person1.getFirstName(), person1.getLastName(),
+                person2.toString(), person2.getFirstName(),
+                person2.getLastName(), getComponentTag());
+
+        testRead(design, grid);
+        testWrite(design, grid, true);
+
+        grid.setSelectionMode(SelectionMode.SINGLE);
+        grid.asSingleSelect().setReadOnly(true);
+
+        design = String.format(formatString, getComponentTag(), "",
+                person1.toString(), person1.getFirstName(),
+                person1.getLastName(), person2.toString(),
+                person2.getFirstName(), person2.getLastName(),
+                getComponentTag());
+
+        testRead(design, grid);
+        testWrite(design, grid, true);
+    }
+
+    @Test
+    public void testComponentInGridHeader() {
+        Grid<Person> grid = new Grid<>();
+        Column<Person, String> column = grid.addColumn(Person::getFirstName);
+
+        String html = "<b>Foo</b>";
+        Label component = new Label(html);
+        component.setContentMode(ContentMode.HTML);
+
+        //@formatter:off
+        String design = String.format( "<%s><table>"
+                + "<colgroup>"
+                + "   <col sortable column-id='generatedColumn0'>"
+                + "</colgroup>"
+                + "<thead>"
+                + "<tr default><th column-ids='generatedColumn0'><vaadin-label>%s</vaadin-label></th></tr>"
+                + "</thead>"
+                + "</table></%s>", getComponentTag(), html, getComponentTag());
+        //@formatter:on
+
+        grid.getDefaultHeaderRow().getCell(column.getId())
+                .setComponent(component);
+
+        testRead(design, grid, true);
+        testWrite(design, grid);
+    }
+
+    @Test
+    public void testComponentInGridFooter() {
+        Grid<Person> grid = new Grid<>();
+        Column<Person, String> column = grid.addColumn(Person::getFirstName);
+
+        String html = "<b>Foo</b>";
+        Label component = new Label(html);
+        component.setContentMode(ContentMode.HTML);
+
+        grid.prependFooterRow().getCell(column).setComponent(component);
+        grid.removeHeaderRow(grid.getDefaultHeaderRow());
+
+      //@formatter:off
+        String design = String.format( "<%s><table>"
+                + "<colgroup>"
+                + "   <col sortable column-id='generatedColumn0'>"
+                + "</colgroup>"
+                + "<thead>"
+                +"<tfoot>"
+                + "<tr><td column-ids='generatedColumn0'><vaadin-label>%s</vaadin-label></td></tr>"
+                + "</tfoot>"
+                + "</table>"
+                + "</%s>", getComponentTag(), html, getComponentTag());
+        //@formatter:on
+
+        testRead(design, grid, true);
+        testWrite(design, grid);
+    }
+
+    @Test
+    public void testNoHeaderRows() {
+        //@formatter:off
+        String design = "<vaadin-grid><table>"
+                + "<colgroup>"
+                + "   <col sortable column-id='generatedColumn0'>"
+                + "</colgroup>"
+                + "<thead />"
+                + "</table>"
+                + "</vaadin-grid>";
+        //@formatter:on
+        Grid<Person> grid = new Grid<>();
+        grid.addColumn(Person::getFirstName);
+        grid.removeHeaderRow(grid.getDefaultHeaderRow());
+
+        testWrite(design, grid);
+        testRead(design, grid, true);
+    }
+
+    @Test
+    public void testReadEmptyGrid() {
+        String design = "<vaadin-grid />";
+        testRead(design, new Grid<String>(), false);
+    }
+
+    @Test
+    public void testEmptyGrid() {
+        String design = "<vaadin-grid></vaadin-grid>";
+        Grid<String> expected = new Grid<>();
+        testWrite(design, expected);
+        testRead(design, expected, true);
+    }
+
+    @Test(expected = DesignException.class)
+    public void testMalformedGrid() {
+        String design = "<vaadin-grid><vaadin-label /></vaadin-grid>";
+        testRead(design, new Grid<String>());
+    }
+
+    @Test(expected = DesignException.class)
+    public void testGridWithNoColGroup() {
+        String design = "<vaadin-grid><table><thead><tr><th>Foo</tr></thead></table></vaadin-grid>";
+        testRead(design, new Grid<String>());
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testHtmlEntitiesinGridHeaderFooter() {
+        String id = "> id";
+        String plainText = "plain-text";
+        //@formatter:off
+        String design = String.format( "<%s><table>"
+                + "<colgroup>"
+                + "  <col sortable column-id='%s'>"
+                + "</colgroup>"
+                + "<thead>"
+                +"   <tr default><th %s column-ids='%s'>&gt; Test</th>"
+                + "</thead>"
+                + "<tfoot>"
+                + "<tr><td %s column-ids='%s'>&gt; Test</td></tr>"
+                + "</tfoot>"
+                + "<tbody />"
+                + "</table></%s>", 
+                getComponentTag() , id, plainText, id, plainText, id, getComponentTag());
+        //@formatter:on
+
+        Grid<Person> grid = read(design);
+        String actualHeader = grid.getHeaderRow(0).getCell(id).getText();
+        String actualFooter = grid.getFooterRow(0).getCell(id).getText();
+        String expected = "> Test";
+
+        Assert.assertEquals(expected, actualHeader);
+        Assert.assertEquals(expected, actualFooter);
+
+        design = design.replace(plainText, "");
+        grid = read(design);
+        actualHeader = grid.getHeaderRow(0).getCell(id).getHtml();
+        actualFooter = grid.getFooterRow(0).getCell(id).getHtml();
+        expected = "&gt; Test";
+
+        Assert.assertEquals(expected, actualHeader);
+        Assert.assertEquals(expected, actualFooter);
+
+        grid = new Grid<>();
+        Column<Person, String> column = grid.addColumn(id,
+                Person::getFirstName);
+        HeaderRow header = grid.addHeaderRowAt(0);
+        FooterRow footer = grid.addFooterRowAt(0);
+        grid.removeHeaderRow(grid.getDefaultHeaderRow());
+
+        // entities should be encoded when writing back, not interpreted as HTML
+        header.getCell(column).setText("&amp; Test");
+        footer.getCell(column).setText("&amp; Test");
+
+        Element root = new Element(Tag.valueOf(getComponentTag()), "");
+        grid.writeDesign(root, new DesignContext());
+
+        Assert.assertEquals("&amp;amp; Test",
+                root.getElementsByTag("th").get(0).html());
+        Assert.assertEquals("&amp;amp; Test",
+                root.getElementsByTag("td").get(0).html());
+
+        header = grid.addHeaderRowAt(0);
+        footer = grid.addFooterRowAt(0);
+
+        // entities should not be encoded, this is already given as HTML
+        header.getCell(id).setHtml("&amp; Test");
+        footer.getCell(id).setHtml("&amp; Test");
+
+        root = new Element(Tag.valueOf(getComponentTag()), "");
+        grid.writeDesign(root, new DesignContext());
+
+        Assert.assertEquals("&amp; Test",
+                root.getElementsByTag("th").get(0).html());
+        Assert.assertEquals("&amp; Test",
+                root.getElementsByTag("td").get(0).html());
+
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Override
+    public Grid<?> testRead(String design, Grid expected) {
+        return testRead(design, expected, false);
+    }
+
+    @SuppressWarnings("rawtypes")
+    public Grid<?> testRead(String design, Grid expected, boolean retestWrite) {
+        return testRead(design, expected, retestWrite, false);
+    }
+
+    @SuppressWarnings("rawtypes")
+    public Grid<?> testRead(String design, Grid expected, boolean retestWrite,
+            boolean writeData) {
+        Grid<?> actual = super.testRead(design, expected);
+
+        compareGridColumns(expected, actual);
+        compareHeaders(expected, actual);
+        compareFooters(expected, actual);
+
+        if (retestWrite) {
+            testWrite(design, actual, writeData);
+        }
+
+        return actual;
+    }
+
+    private void compareHeaders(Grid<?> expected, Grid<?> actual) {
+        Assert.assertEquals("Different header row count",
+                expected.getHeaderRowCount(), actual.getHeaderRowCount());
+        for (int i = 0; i < expected.getHeaderRowCount(); ++i) {
+            HeaderRow expectedRow = expected.getHeaderRow(i);
+            HeaderRow actualRow = actual.getHeaderRow(i);
+
+            if (expectedRow.equals(expected.getDefaultHeaderRow())) {
+                Assert.assertEquals("Different index for default header row",
+                        actual.getDefaultHeaderRow(), actualRow);
+            }
+
+            for (Column<?, ?> column : expected.getColumns()) {
+                String baseError = "Difference when comparing cell for "
+                        + column.toString() + " on header row " + i + ": ";
+                HeaderCell expectedCell = expectedRow.getCell(column);
+                HeaderCell actualCell = actualRow.getCell(column);
+
+                switch (expectedCell.getCellType()) {
+                case TEXT:
+                    Assert.assertEquals(baseError + "Text content",
+                            expectedCell.getText(), actualCell.getText());
+                    break;
+                case HTML:
+                    Assert.assertEquals(baseError + "HTML content",
+                            expectedCell.getHtml(), actualCell.getHtml());
+                    break;
+                case WIDGET:
+                    assertEquals(baseError + "Component content",
+                            expectedCell.getComponent(),
+                            actualCell.getComponent());
+                    break;
+                }
+            }
+        }
+    }
+
+    private void compareFooters(Grid<?> expected, Grid<?> actual) {
+        Assert.assertEquals("Different footer row count",
+                expected.getFooterRowCount(), actual.getFooterRowCount());
+        for (int i = 0; i < expected.getFooterRowCount(); ++i) {
+            FooterRow expectedRow = expected.getFooterRow(i);
+            FooterRow actualRow = actual.getFooterRow(i);
+
+            for (Column<?, ?> column : expected.getColumns()) {
+                String baseError = "Difference when comparing cell for "
+                        + column.toString() + " on footer row " + i + ": ";
+                FooterCell expectedCell = expectedRow.getCell(column);
+                FooterCell actualCell = actualRow.getCell(column);
+
+                switch (expectedCell.getCellType()) {
+                case TEXT:
+                    Assert.assertEquals(baseError + "Text content",
+                            expectedCell.getText(), actualCell.getText());
+                    break;
+                case HTML:
+                    Assert.assertEquals(baseError + "HTML content",
+                            expectedCell.getHtml(), actualCell.getHtml());
+                    break;
+                case WIDGET:
+                    assertEquals(baseError + "Component content",
+                            expectedCell.getComponent(),
+                            actualCell.getComponent());
+                    break;
+                }
+            }
+        }
+    }
+
+    private void compareGridColumns(Grid<?> expected, Grid<?> actual) {
+        List<?> columns = expected.getColumns();
+        List<?> actualColumns = actual.getColumns();
+        Assert.assertEquals("Different amount of columns", columns.size(),
+                actualColumns.size());
+        for (int i = 0; i < columns.size(); ++i) {
+            Column<?, ?> col1 = (Column<?, ?>) columns.get(i);
+            Column<?, ?> col2 = (Column<?, ?>) actualColumns.get(i);
+            String baseError = "Error when comparing columns for property "
+                    + col1.getId() + ": ";
+            assertEquals(baseError + "Width", col1.getWidth(), col2.getWidth());
+            assertEquals(baseError + "Maximum width", col1.getMaximumWidth(),
+                    col2.getMaximumWidth());
+            assertEquals(baseError + "Minimum width", col1.getMinimumWidth(),
+                    col2.getMinimumWidth());
+            assertEquals(baseError + "Expand ratio", col1.getExpandRatio(),
+                    col2.getExpandRatio());
+            assertEquals(baseError + "Sortable", col1.isSortable(),
+                    col2.isSortable());
+            assertEquals(baseError + "Editable", col1.isEditable(),
+                    col2.isEditable());
+            assertEquals(baseError + "Hidable", col1.isHidable(),
+                    col2.isHidable());
+            assertEquals(baseError + "Hidden", col1.isHidden(),
+                    col2.isHidden());
+            assertEquals(baseError + "HidingToggleCaption",
+                    col1.getHidingToggleCaption(),
+                    col2.getHidingToggleCaption());
+        }
+    }
+
+    @Override
+    protected String getComponentTag() {
+        return "vaadin-grid";
+    }
+
+    @Override
+    protected Class<? extends Grid> getComponentClass() {
+        return Grid.class;
+    }
+
+    @Override
+    protected boolean acceptProperty(Class<?> clazz, Method readMethod,
+            Method writeMethod) {
+        if (readMethod != null) {
+            Class<?> returnType = readMethod.getReturnType();
+            if (HeaderRow.class.equals(returnType)
+                    || DataProvider.class.equals(returnType)) {
+                return false;
+            }
+        }
+        return super.acceptProperty(clazz, readMethod, writeMethod);
+    }
+
+    private Person createPerson(String name, String lastName) {
+        Person person = new Person() {
+            @Override
+            public String toString() {
+                return getFirstName() + " " + getLastName();
+            }
+        };
+        person.setFirstName(name);
+        person.setLastName(lastName);
+        return person;
+    }
+
+}
index 4f9d733f44f097cd59962e1036ca0c14da9b8a3a..c9c5614205b665f0c06de57e37dc664d04db7799 100644 (file)
@@ -66,7 +66,7 @@ public class GridBasics extends AbstractTestUIWithLog {
     public static final String CELL_STYLE_GENERATOR_EMPTY = "Empty string";
     public static final String CELL_STYLE_GENERATOR_NULL = "Null";
 
-    private boolean isUserSelectionAllowed = true;
+    private boolean isUserSelectionDisallowed;
 
     public static final String[] COLUMN_CAPTIONS = { "Column 0", "Column 1",
             "Column 2", "Row Number", "Date", "HTML String", "Big Random",
@@ -408,20 +408,20 @@ public class GridBasics extends AbstractTestUIWithLog {
         }).setCheckable(true);
 
         MenuItem selectionAllowedItem = stateMenu
-                .addItem("Allow user selection", item -> {
-                    isUserSelectionAllowed = !isUserSelectionAllowed;
+                .addItem("Disallow user selection", item -> {
+                    isUserSelectionDisallowed = !isUserSelectionDisallowed;
                     if (grid.getSelectionModel() instanceof MultiSelectionModelImpl) {
                         MultiSelect<DataObject> multiSelect = grid
                                 .asMultiSelect();
-                        multiSelect.setReadOnly(isUserSelectionAllowed);
+                        multiSelect.setReadOnly(isUserSelectionDisallowed);
                     }
                     if (grid.getSelectionModel() instanceof SingleSelectionModelImpl) {
                         SingleSelect<DataObject> singleSelect = grid
                                 .asSingleSelect();
-                        singleSelect.setReadOnly(isUserSelectionAllowed);
+                        singleSelect.setReadOnly(isUserSelectionDisallowed);
                     }
                 });
-        selectionAllowedItem.setChecked(true);
+        selectionAllowedItem.setChecked(false);
         selectionAllowedItem.setCheckable(true);
 
         stateMenu.addItem("Column reorder listener",
@@ -513,7 +513,7 @@ public class GridBasics extends AbstractTestUIWithLog {
             selectionListenerRegistration = ((SingleSelectionModelImpl<DataObject>) grid
                     .getSelectionModel())
                             .addSingleSelectionListener(this::onSingleSelect);
-            grid.asSingleSelect().setReadOnly(isUserSelectionAllowed);
+            grid.asSingleSelect().setReadOnly(isUserSelectionDisallowed);
         });
         selectionModelItem.addItem("multi", menuItem -> {
             switchToMultiSelect();
@@ -559,7 +559,7 @@ public class GridBasics extends AbstractTestUIWithLog {
             MultiSelectionModelImpl<DataObject> model = (MultiSelectionModelImpl<DataObject>) grid
                     .setSelectionMode(SelectionMode.MULTI);
             model.addMultiSelectionListener(this::onMultiSelect);
-            grid.asMultiSelect().setReadOnly(isUserSelectionAllowed);
+            grid.asMultiSelect().setReadOnly(isUserSelectionDisallowed);
             selectionListenerRegistration = model
                     .addMultiSelectionListener(this::onMultiSelect);
         }
index 699edf462b3ece2e6ab19896b8dfd952c116123f..97dbf07d1fd28c0b07de3ab51de6c4155a95351a 100644 (file)
@@ -404,7 +404,7 @@ public class GridSelectionTest extends GridBasicsTest {
     }
 
     private void toggleUserSelectionAllowed() {
-        selectMenuPath("Component", "State", "Allow user selection");
+        selectMenuPath("Component", "State", "Disallow user selection");
     }
 
     private WebElement getSelectionCheckbox(int row) {