import com.vaadin.client.widgets.Grid;
import com.vaadin.client.widgets.Grid.Column;
import com.vaadin.client.widgets.Grid.FooterRow;
+import com.vaadin.client.widgets.Grid.HeaderCell;
import com.vaadin.client.widgets.Grid.HeaderRow;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.data.DataCommunicatorConstants;
import com.vaadin.shared.ui.grid.GridServerRpc;
import com.vaadin.shared.ui.grid.GridState;
import com.vaadin.shared.ui.grid.SectionState;
+import com.vaadin.shared.ui.grid.SectionState.CellState;
import com.vaadin.shared.ui.grid.SectionState.RowState;
import elemental.json.JsonObject;
for (RowState rowState : state.rows) {
HeaderRow row = grid.appendHeaderRow();
+ if (rowState.defaultHeader) {
+ grid.setDefaultHeaderRow(row);
+ }
+
rowState.cells.forEach((columnId, cellState) -> {
- row.getCell(getColumn(columnId)).setText(cellState.text);
+ updateHeaderCellFromState(row.getCell(getColumn(columnId)),
+ cellState);
});
+ }
+ }
- if (rowState.defaultHeader) {
- grid.setDefaultHeaderRow(row);
+ private void updateHeaderCellFromState(HeaderCell cell,
+ CellState cellState) {
+ switch (cellState.type) {
+ case TEXT:
+ cell.setText(cellState.text);
+ break;
+ case HTML:
+ cell.setHtml(cellState.html);
+ break;
+ case WIDGET:
+ ComponentConnector connector = (ComponentConnector) cellState.connector;
+ if (connector != null) {
+ cell.setWidget(connector.getWidget());
+ } else {
+ // This happens if you do setVisible(false) on the component on
+ // the server side
+ cell.setWidget(null);
}
+ break;
+ default:
+ throw new IllegalStateException(
+ "unexpected cell type: " + cellState.type);
}
+ cell.setStyleName(cellState.styleName);
}
/**
import com.vaadin.shared.ui.grid.GridConstants.Section;
import com.vaadin.shared.ui.grid.GridServerRpc;
import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.shared.ui.grid.GridStaticCellType;
import com.vaadin.shared.ui.grid.HeightMode;
import com.vaadin.shared.ui.grid.SectionState;
import com.vaadin.shared.util.SharedUtil;
* the header caption to set, not null
*/
public void setText(String text);
+
+ /**
+ * Returns the HTML content displayed in this cell.
+ *
+ * @return the html
+ *
+ */
+ public String getHtml();
+
+ /**
+ * Sets the HTML content displayed in this cell.
+ *
+ * @param html
+ * the html to set
+ */
+ public void setHtml(String html);
+
+ /**
+ * Returns the component displayed in this cell.
+ *
+ * @return the component
+ */
+ public Component getComponent();
+
+ /**
+ * Sets the component displayed in this cell.
+ *
+ * @param component
+ * the component to set
+ */
+ public void setComponent(Component component);
+
+ /**
+ * Returns the type of content stored in this cell.
+ *
+ * @return cell content type
+ */
+ public GridStaticCellType getCellType();
}
/**
private class HeaderImpl extends Header {
+ @Override
+ protected Grid<T> getGrid() {
+ return Grid.this;
+ }
+
@Override
protected SectionState getState(boolean markAsDirty) {
return Grid.this.getState(markAsDirty).header;
private class FooterImpl extends Footer {
+ @Override
+ protected Grid<T> getGrid() {
+ return Grid.this;
+ }
+
@Override
protected SectionState getState(boolean markAsDirty) {
return Grid.this.getState(markAsDirty).footer;
@Override
public Iterator<Component> iterator() {
- return Collections.unmodifiableSet(extensionComponents).iterator();
+ Set<Component> componentSet = new LinkedHashSet<>(extensionComponents);
+ Header header = getHeader();
+ for (int i = 0; i < header.getRowCount(); ++i) {
+ HeaderRow row = header.getRow(i);
+ getColumns().forEach(column -> {
+ HeaderCell cell = row.getCell(column);
+ if (cell.getCellType() == GridStaticCellType.WIDGET) {
+ componentSet.add(cell.getComponent());
+ }
+ });
+ }
+ return Collections.unmodifiableSet(componentSet).iterator();
}
/**
/*
* 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
import java.util.Map;
import java.util.Objects;
+import com.vaadin.shared.ui.grid.GridStaticCellType;
import com.vaadin.shared.ui.grid.SectionState;
import com.vaadin.shared.ui.grid.SectionState.CellState;
import com.vaadin.shared.ui.grid.SectionState.RowState;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Grid;
import com.vaadin.ui.Grid.Column;
/**
* Represents the header or footer section of a Grid.
*
* @author Vaadin Ltd.
- *
+ *
* @param <ROW>
* the type of the rows in the section
*
/**
* Creates a new row belonging to the given section.
- *
+ *
* @param section
* the section of the row
*/
/**
* Returns the declarative tag name used for the cells in this row.
- *
+ *
* @return the cell tag name
*/
protected abstract String getCellTagName();
/**
* Adds a cell to this section, corresponding to the given column id.
- *
+ *
* @param columnId
* the id of the column for which to add a cell
*/
/**
* Removes the cell from this section that corresponds to the given
* column id. If there is no such cell, does nothing.
- *
+ *
* @param columnId
* the id of the column from which to remove the cell
*/
/**
* Returns the shared state of this row.
- *
+ *
* @return the row state
*/
protected RowState getRowState() {
* @param columnId
* the id of the column
* @return the cell for the given column
- *
+ *
* @throws IllegalArgumentException
* if no cell was found for the column id
*/
}
return cell;
}
+
+ void detach() {
+ for (CELL cell : cells.values()) {
+ cell.detach();
+ }
+ }
}
/**
/**
* Returns the shared state of this cell.
- *
+ *
* @return the cell state
*/
protected CellState getCellState() {
*/
public void setText(String text) {
Objects.requireNonNull(text, "text cannot be null");
+ removeComponentIfPresent();
cellState.text = text;
+ cellState.type = GridStaticCellType.TEXT;
row.section.markAsDirty();
}
public String getText() {
return cellState.text;
}
+
+ /**
+ * Returns the HTML content displayed in this cell.
+ *
+ * @return the html
+ *
+ */
+ public String getHtml() {
+ if (cellState.type != GridStaticCellType.HTML) {
+ throw new IllegalStateException(
+ "Cannot fetch HTML from a cell with type "
+ + cellState.type);
+ }
+ return cellState.html;
+ }
+
+ /**
+ * Sets the HTML content displayed in this cell.
+ *
+ * @param html
+ * the html to set, not null
+ */
+ public void setHtml(String html) {
+ Objects.requireNonNull(html, "html cannot be null");
+ removeComponentIfPresent();
+ cellState.html = html;
+ cellState.type = GridStaticCellType.HTML;
+ row.section.markAsDirty();
+ }
+
+ /**
+ * Returns the component displayed in this cell.
+ *
+ * @return the component
+ */
+ public Component getComponent() {
+ if (cellState.type != GridStaticCellType.WIDGET) {
+ throw new IllegalStateException(
+ "Cannot fetch Component from a cell with type "
+ + cellState.type);
+ }
+ return (Component) cellState.connector;
+ }
+
+ /**
+ * Sets the component displayed in this cell.
+ *
+ * @param component
+ * the component to set, not null
+ */
+ public void setComponent(Component component) {
+ Objects.requireNonNull(component, "component cannot be null");
+ removeComponentIfPresent();
+ component.setParent(row.section.getGrid());
+ cellState.connector = component;
+ cellState.type = GridStaticCellType.WIDGET;
+ row.section.markAsDirty();
+ }
+
+ /**
+ * Returns the type of content stored in this cell.
+ *
+ * @return cell content type
+ */
+ public GridStaticCellType getCellType() {
+ return cellState.type;
+ }
+
+ private void removeComponentIfPresent() {
+ Component component = (Component) cellState.connector;
+ if (component != null) {
+ component.setParent(null);
+ cellState.connector = null;
+ }
+ }
+
+ void detach() {
+ removeComponentIfPresent();
+ }
}
private final List<ROW> rows = new ArrayList<>();
/**
* Creates a new row instance.
- *
+ *
* @return the new row
*/
protected abstract ROW createRow();
/**
* Returns the shared state of this section.
- *
+ *
* @param markAsDirty
* {@code true} to mark the state as modified, {@code false}
* otherwise
*/
protected abstract SectionState getState(boolean markAsDirty);
+ protected abstract Grid<?> getGrid();
+
protected abstract Collection<? extends Column<?, ?>> getColumns();
/**
/**
* Adds a new row at the given index.
- *
+ *
* @param index
* the index of the new row
* @return the added row
/**
* Removes the row at the given index.
- *
+ *
* @param index
* the index of the row to remove
* @throws IndexOutOfBoundsException
* if {@code index < 0 || index >= getRowCount()}
*/
public void removeRow(int index) {
- rows.remove(index);
+ ROW row = rows.remove(index);
+ row.detach();
getState(true).rows.remove(index);
}
/**
* Removes the given row from this section.
- *
+ *
* @param row
* the row to remove, not null
* @throws IllegalArgumentException
/**
* Returns the row at the given index.
- *
+ *
* @param index
* the index of the row
* @return the row at the index
/**
* Returns an unmodifiable list of the rows in this section.
- *
+ *
* @return the rows in this section
*/
protected List<ROW> getRows() {
import java.util.List;
import java.util.Map;
+import com.vaadin.shared.Connector;
+
/**
* Shared state for Grid headers and footers.
*
/** The state of a header or footer cell. */
public static class CellState implements Serializable {
+ public GridStaticCellType type = GridStaticCellType.TEXT;
+
+ /** The style name for this cell. Null if none. */
+ public String styleName = null;
+
/** The textual caption of this cell. */
public String text;
+ /** The html content of this cell. */
+ public String html;
+
+ /**
+ * The connector for the component that is set to be displayed in this
+ * cell. Null if none.
+ */
+ public Connector connector = null;
+
/** The id of the column that this cell belongs to. */
public String columnId;
}
import com.vaadin.server.VaadinRequest;
import com.vaadin.shared.Registration;
import com.vaadin.shared.ui.grid.HeightMode;
-import com.vaadin.tests.components.AbstractReindeerTestUIWithLog;
+import com.vaadin.tests.components.AbstractTestUIWithLog;
import com.vaadin.ui.Button;
import com.vaadin.ui.Component;
import com.vaadin.ui.Grid;
import com.vaadin.ui.renderers.ProgressBarRenderer;
@Widgetset("com.vaadin.DefaultWidgetSet")
-public class GridBasics extends AbstractReindeerTestUIWithLog {
+public class GridBasics extends AbstractTestUIWithLog {
public static final String ROW_STYLE_GENERATOR_ROW_NUMBERS_FOR_3_OF_4 = "Row numbers for 3/4";
public static final String ROW_STYLE_GENERATOR_NONE = "None";
.toArray(new Column[columnOrder.size()]));
}
});
+
+ MenuItem headerTypeMenu = columnMenu.addItem("Header Type", null);
+ headerTypeMenu.addItem("Text Header", selectedItem -> grid
+ .getDefaultHeaderRow().getCell(col).setText("Text Header"));
+ headerTypeMenu
+ .addItem("HTML Header",
+ selectedItem -> grid.getDefaultHeaderRow()
+ .getCell(col)
+ .setHtml("<b>HTML Header</b>"));
+ headerTypeMenu.addItem("Widget Header", selectedItem -> {
+ final Button button = new Button("Button Header");
+ button.addClickListener(clickEvent -> log("Button clicked!"));
+ grid.getDefaultHeaderRow().getCell(col).setComponent(button);
+ });
+
columnMenu
.addItem("Sortable",
selectedItem -> col
: null))
.setCheckable(true);
stateMenu
- .addItem("Cell description generator", item -> grid.getColumns()
- .stream().findFirst()
- .ifPresent(c -> c.setDescriptionGenerator(
- item.isChecked() ? t -> "Cell tooltip for row "
- + t.getRowNumber() + ", Column 0"
- : null)))
+ .addItem("Cell description generator",
+ item -> grid.getColumns().stream().findFirst()
+ .ifPresent(
+ c -> c.setDescriptionGenerator(
+ item.isChecked()
+ ? t -> "Cell tooltip for row "
+ + t.getRowNumber()
+ + ", Column 0"
+ : null)))
.setCheckable(true);
stateMenu.addItem("Item click listener", new Command() {
private void createFooterMenu(MenuItem footerMenu) {
footerMenu.addItem("Add default footer row", menuItem -> {
FooterRow defaultFooter = grid.appendFooterRow();
- grid.getColumns().forEach(
- column -> defaultFooter.getCell(column).setText(grid
- .getDefaultHeaderRow().getCell(column).getText()));
+ grid.getColumns()
+ .forEach(column -> defaultFooter.getCell(column)
+ .setText(grid.getDefaultHeaderRow().getCell(column)
+ .getText()));
footerMenu.removeChild(menuItem);
});
footerMenu.addItem("Append footer row", menuItem -> {
getGridElement().getCell(row, column).click();
}
+ protected WebElement getSidebarPopup() {
+ List<WebElement> elements = findElements(
+ By.className("v-grid-sidebar-popup"));
+ if (elements.isEmpty()) {
+ getSidebarOpenButton().click();
+ elements = findElements(By.className("v-grid-sidebar-popup"));
+ }
+ return elements.isEmpty() ? null : elements.get(0);
+ }
+
+ protected WebElement getSidebarPopupIfPresent() {
+ List<WebElement> elements = findElements(
+ By.className("v-grid-sidebar-popup"));
+ return elements.isEmpty() ? null : elements.get(0);
+ }
+
+ protected WebElement getSidebarOpenButton() {
+ List<WebElement> elements = findElements(
+ By.className("v-grid-sidebar-button"));
+ return elements.isEmpty() ? null : elements.get(0);
+ }
+
+ /**
+ * Returns the toggle inside the sidebar for hiding the column at the given
+ * index, or null if not found.
+ */
+ protected WebElement getColumnHidingToggle(int columnIndex) {
+ WebElement sidebar = getSidebarPopup();
+ List<WebElement> elements = sidebar
+ .findElements(By.className("column-hiding-toggle"));
+ for (WebElement e : elements) {
+ if ((e.getText().toLowerCase())
+ .startsWith("column " + columnIndex)) {
+ return e;
+ }
+ }
+ return null;
+ }
+
}
import com.vaadin.testbench.By;
import com.vaadin.testbench.elements.GridElement.GridCellElement;
+import com.vaadin.testbench.elements.NotificationElement;
public class GridHeaderFooterTest extends GridBasicsTest {
selectMenuPath("Component", "Header", "Prepend header row");
selectMenuPath("Component", "Header", "Set first row as default");
- assertHeaderTexts(0, GridBasics.COLUMN_CAPTIONS);
+ assertHeaderTexts(0, HEADER_TEXTS);
}
@Test
assertFooterTexts(1, GridBasics.COLUMN_CAPTIONS);
}
+ public void testDynamicallyChangingCellType() throws Exception {
+ openTestURL();
+
+ selectMenuPath("Component", "Columns", "Column 0", "Header Type",
+ "Widget Header");
+ GridCellElement widgetCell = getGridElement().getHeaderCell(0, 0);
+ assertTrue(widgetCell.isElementPresent(By.className("v-button")));
+
+ selectMenuPath("Component", "Columns", "Column 1", "Header Type",
+ "HTML Header");
+ GridCellElement htmlCell = getGridElement().getHeaderCell(0, 1);
+ assertEquals("<b>HTML Header</b>",
+ htmlCell.findElement(
+ By.className("v-grid-column-header-content"))
+ .getAttribute("innerHTML"));
+
+ selectMenuPath("Component", "Columns", "Column 2", "Header Type",
+ "Text Header");
+ GridCellElement textCell = getGridElement().getHeaderCell(0, 2);
+
+ assertEquals("text header", textCell.getText().toLowerCase());
+ }
+
+ @Test
+ public void testButtonInHeader() throws Exception {
+ openTestURL();
+
+ selectMenuPath("Component", "Columns", "Column 1", "Header Type",
+ "Widget Header");
+
+ getGridElement().findElements(By.className("v-button")).get(0).click();
+
+ assertTrue("Button click should be logged",
+ logContainsText("Button clicked!"));
+ }
+
+ @Test
+ public void testRemoveComponentFromHeader() throws Exception {
+ openTestURL();
+ selectMenuPath("Component", "Columns", "Column 1", "Header Type",
+ "Widget Header");
+ selectMenuPath("Component", "Columns", "Column 1", "Header Type",
+ "Text Header");
+ assertTrue("No notifications should've been shown",
+ !$(NotificationElement.class).exists());
+ assertEquals("Header should've been reverted back to text header",
+ "text header",
+ getGridElement().getHeaderCell(0, 1).getText().toLowerCase());
+ }
+
+ @Test
+ public void testColumnHidingToggleCaption_settingWidgetToHeader_toggleCaptionStays() {
+ toggleColumnHidable(1);
+ getSidebarOpenButton().click();
+ assertEquals("column 1",
+ getGridElement().getHeaderCell(0, 1).getText().toLowerCase());
+ assertEquals("Column 1", getColumnHidingToggle(1).getText());
+
+ selectMenuPath("Component", "Columns", "Column 1", "Header Type",
+ "Widget Header");
+
+ getSidebarOpenButton().click();
+ assertEquals("Column 1", getColumnHidingToggle(1).getText());
+ }
+
+ private void toggleColumnHidable(int index) {
+ selectMenuPath("Component", "Columns", "Column " + index, "Hidable");
+ }
+
protected static void assertText(String expected, GridCellElement e) {
// TBE.getText returns "" if the element is scrolled out of view
String actual = e.findElement(By.tagName("div"))