Ver código fonte

Provide declarative support for Grid. (#7961)

Fixes vaadin/framework8-issues#390
tags/8.0.0.alpha10
Denis 7 anos atrás
pai
commit
a43fd9003a

+ 5
- 3
all/src/main/templates/release-notes.html Ver arquivo

@@ -176,11 +176,13 @@
<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>

+ 38
- 1
server/src/main/java/com/vaadin/ui/AbstractListing.java Ver arquivo

@@ -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

+ 52
- 0
server/src/main/java/com/vaadin/ui/DeclarativeValueProvider.java Ver arquivo

@@ -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);
}

}

+ 360
- 7
server/src/main/java/com/vaadin/ui/Grid.java Ver arquivo

@@ -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;
}

}

+ 17
- 12
server/src/main/java/com/vaadin/ui/components/grid/Footer.java Ver arquivo

@@ -15,12 +15,12 @@
*/
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();
}

}

+ 47
- 16
server/src/main/java/com/vaadin/ui/components/grid/Header.java Ver arquivo

@@ -15,12 +15,16 @@
*/
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();
}

}

+ 2
- 2
server/src/main/java/com/vaadin/ui/components/grid/MultiSelectionModelImpl.java Ver arquivo

@@ -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

+ 2
- 2
server/src/main/java/com/vaadin/ui/components/grid/SingleSelectionModelImpl.java Ver arquivo

@@ -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();
}
};
}

+ 221
- 2
server/src/main/java/com/vaadin/ui/components/grid/StaticSection.java Ver arquivo

@@ -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);
}

}

+ 2
- 2
server/src/test/java/com/vaadin/tests/design/DeclarativeTestBaseBase.java Ver arquivo

@@ -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());
}


+ 15
- 8
server/src/test/java/com/vaadin/tests/server/component/abstractcomponent/AbstractComponentDeclarativeTestBase.java Ver arquivo

@@ -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;
}
}


+ 731
- 0
server/src/test/java/com/vaadin/tests/server/component/grid/GridDeclarativeTest.java Ver arquivo

@@ -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;
}

}

+ 8
- 8
uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java Ver arquivo

@@ -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);
}

+ 1
- 1
uitest/src/test/java/com/vaadin/tests/components/grid/GridSelectionTest.java Ver arquivo

@@ -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) {

Carregando…
Cancelar
Salvar