<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>
@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);
@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,
* <p>
* Default implementation delegates a call to {@code item.toString()}.
*
- * @see #serializeDeclarativeRepresentation(Object)
+ * @see #deserializeDeclarativeRepresentation(String)
*
* @param item
* a data item
--- /dev/null
+/*
+ * 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);
+ }
+
+}
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;
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;
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;
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;
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;
"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));
+ }
+ }
+ }
}
/**
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();
}
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;
+ }
+
}
*/
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.
*
}
/**
- * 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...)
}
/**
- * 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)
*/
@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();
}
+
}
*/
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.
*
/**
* 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.
*/
}
/**
- * 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...)
}
/**
- * 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)
*/
@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
* @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);
}
/**
markAsDirty();
}
+
}
@Override
public void setReadOnly(boolean readOnly) {
- getState().selectionAllowed = readOnly;
+ getState().selectionAllowed = !readOnly;
}
@Override
public boolean isReadOnly() {
- return isUserSelectionAllowed();
+ return !isUserSelectionAllowed();
}
@Override
@Override
public void setReadOnly(boolean readOnly) {
- getState().selectionAllowed = readOnly;
+ getState().selectionAllowed = !readOnly;
}
@Override
public boolean isReadOnly() {
- return isUserSelectionAllowed();
+ return !isUserSelectionAllowed();
}
};
}
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;
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.
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();
}
}
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();
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) {
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.
*
protected List<ROW> getRows() {
return Collections.unmodifiableList(rows);
}
+
}
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());
}
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);
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);
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;
}
}
--- /dev/null
+/*
+ * 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'>> Test</th>"
+ + "</thead>"
+ + "<tfoot>"
+ + "<tr><td %s column-ids='%s'>> 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 = "> 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("& Test");
+ footer.getCell(column).setText("& Test");
+
+ Element 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());
+
+ header = grid.addHeaderRowAt(0);
+ footer = grid.addFooterRowAt(0);
+
+ // entities should not be encoded, this is already given as HTML
+ header.getCell(id).setHtml("& Test");
+ footer.getCell(id).setHtml("& Test");
+
+ root = new Element(Tag.valueOf(getComponentTag()), "");
+ grid.writeDesign(root, new DesignContext());
+
+ Assert.assertEquals("& Test",
+ root.getElementsByTag("th").get(0).html());
+ Assert.assertEquals("& 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;
+ }
+
+}
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",
}).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",
selectionListenerRegistration = ((SingleSelectionModelImpl<DataObject>) grid
.getSelectionModel())
.addSingleSelectionListener(this::onSingleSelect);
- grid.asSingleSelect().setReadOnly(isUserSelectionAllowed);
+ grid.asSingleSelect().setReadOnly(isUserSelectionDisallowed);
});
selectionModelItem.addItem("multi", menuItem -> {
switchToMultiSelect();
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);
}
}
private void toggleUserSelectionAllowed() {
- selectMenuPath("Component", "State", "Allow user selection");
+ selectMenuPath("Component", "State", "Disallow user selection");
}
private WebElement getSelectionCheckbox(int row) {