Change-Id: I3db51521320767a28bc3acd9586b1453764a15bctags/8.0.0.alpha5
@@ -53,6 +53,7 @@ import com.vaadin.client.widget.grid.sort.SortEvent; | |||
import com.vaadin.client.widget.grid.sort.SortOrder; | |||
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.HeaderRow; | |||
import com.vaadin.shared.MouseEventDetails; | |||
import com.vaadin.shared.data.DataCommunicatorConstants; | |||
@@ -264,6 +265,27 @@ public class GridConnector | |||
} | |||
} | |||
/** | |||
* Updates the grid footer section on state change. | |||
*/ | |||
@OnStateChange("footer") | |||
void updateFooter() { | |||
final Grid<JsonObject> grid = getWidget(); | |||
final SectionState state = getState().footer; | |||
while (grid.getFooterRowCount() > 0) { | |||
grid.removeFooterRow(0); | |||
} | |||
for (RowState rowState : state.rows) { | |||
FooterRow row = grid.appendFooterRow(); | |||
rowState.cells.forEach((columnId, cellState) -> { | |||
row.getCell(getColumn(columnId)).setText(cellState.text); | |||
}); | |||
} | |||
} | |||
@Override | |||
public void setDataSource(DataSource<JsonObject> dataSource) { | |||
super.setDataSource(dataSource); |
@@ -54,6 +54,8 @@ import com.vaadin.shared.ui.grid.GridState; | |||
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.components.grid.Footer; | |||
import com.vaadin.ui.components.grid.Header; | |||
import com.vaadin.ui.components.grid.Header.Row; | |||
import com.vaadin.ui.renderers.AbstractRenderer; | |||
@@ -1562,6 +1564,57 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { | |||
public void setText(String text); | |||
} | |||
/** | |||
* A footer row in a Grid. | |||
*/ | |||
public interface FooterRow extends Serializable { | |||
/** | |||
* Returns the cell on this row corresponding to the given column id. | |||
* | |||
* @param columnId | |||
* the id of the column whose footer cell to get, not null | |||
* @return the footer cell | |||
* @throws IllegalArgumentException | |||
* if there is no such column in the grid | |||
*/ | |||
public FooterCell getCell(String columnId); | |||
/** | |||
* Returns the cell on this row corresponding to the given column. | |||
* | |||
* @param column | |||
* the column whose footer cell to get, not null | |||
* @return the footer cell | |||
* @throws IllegalArgumentException | |||
* if there is no such column in the grid | |||
*/ | |||
public default FooterCell getCell(Column<?, ?> column) { | |||
return getCell(column.getId()); | |||
} | |||
} | |||
/** | |||
* An individual cell on a Grid footer row. | |||
*/ | |||
public interface FooterCell extends Serializable { | |||
/** | |||
* Returns the textual caption of this cell. | |||
* | |||
* @return the footer caption | |||
*/ | |||
public String getText(); | |||
/** | |||
* Sets the textual caption of this cell. | |||
* | |||
* @param text | |||
* the footer caption to set, not null | |||
*/ | |||
public void setText(String text); | |||
} | |||
private class HeaderImpl extends Header { | |||
@Override | |||
@@ -1575,6 +1628,19 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { | |||
} | |||
}; | |||
private class FooterImpl extends Footer { | |||
@Override | |||
protected SectionState getState(boolean markAsDirty) { | |||
return Grid.this.getState(markAsDirty).footer; | |||
} | |||
@Override | |||
protected Collection<Column<T, ?>> getColumns() { | |||
return Grid.this.getColumns(); | |||
} | |||
}; | |||
private Set<Column<T, ?>> columnSet = new LinkedHashSet<>(); | |||
private Map<String, Column<T, ?>> columnKeys = new HashMap<>(); | |||
@@ -1585,6 +1651,7 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { | |||
private DescriptionGenerator<T> descriptionGenerator; | |||
private Header header = new HeaderImpl(); | |||
private Footer footer = new FooterImpl(); | |||
private int counter = 0; | |||
@@ -2155,6 +2222,128 @@ public class Grid<T> extends AbstractSingleSelect<T> implements HasComponents { | |||
return header; | |||
} | |||
/** | |||
* Returns the footer row at the given index. | |||
* | |||
* @param index | |||
* the index of the row, where the topmost row has index zero | |||
* @return the footer row at the index | |||
* @throws IndexOutOfBoundsException | |||
* if {@code rowIndex < 0 || rowIndex >= getFooterRowCount()} | |||
*/ | |||
public FooterRow getFooterRow(int index) { | |||
return getFooter().getRow(index); | |||
} | |||
/** | |||
* Gets the number of rows in the footer section. | |||
* | |||
* @return the number of footer rows | |||
*/ | |||
public int getFooterRowCount() { | |||
return getFooter().getRowCount(); | |||
} | |||
/** | |||
* Inserts a new row at the given position to the footer section. Shifts the | |||
* row currently at that position and any subsequent rows down (adds one to | |||
* their indices). Inserting at {@link #getFooterRowCount()} appends the row | |||
* at the bottom of the footer. | |||
* | |||
* @param index | |||
* the index at which to insert the row, where the topmost row | |||
* has index zero | |||
* @return the inserted footer row | |||
* | |||
* @throws IndexOutOfBoundsException | |||
* if {@code rowIndex < 0 || rowIndex > getFooterRowCount()} | |||
* | |||
* @see #appendFooterRow() | |||
* @see #prependFooterRow() | |||
* @see #removeFooterRow(FooterRow) | |||
* @see #removeFooterRow(int) | |||
*/ | |||
public FooterRow addFooterRowAt(int index) { | |||
return getFooter().addRowAt(index); | |||
} | |||
/** | |||
* Adds a new row at the bottom of the footer section. | |||
* | |||
* @return the appended footer row | |||
* | |||
* @see #prependFooterRow() | |||
* @see #addFooterRowAt(int) | |||
* @see #removeFooterRow(FooterRow) | |||
* @see #removeFooterRow(int) | |||
*/ | |||
public FooterRow appendFooterRow() { | |||
return addFooterRowAt(getFooterRowCount()); | |||
} | |||
/** | |||
* Adds a new row at the top of the footer section. | |||
* | |||
* @return the prepended footer row | |||
* | |||
* @see #appendFooterRow() | |||
* @see #addFooterRowAt(int) | |||
* @see #removeFooterRow(FooterRow) | |||
* @see #removeFooterRow(int) | |||
*/ | |||
public FooterRow prependFooterRow() { | |||
return addFooterRowAt(0); | |||
} | |||
/** | |||
* Removes the given row from the footer section. Removing a default row | |||
* sets the Grid to have no default row. | |||
* | |||
* @param row | |||
* the footer row to be removed, not null | |||
* | |||
* @throws IllegalArgumentException | |||
* if the footer does not contain the row | |||
* | |||
* @see #removeFooterRow(int) | |||
* @see #addFooterRowAt(int) | |||
* @see #appendFooterRow() | |||
* @see #prependFooterRow() | |||
*/ | |||
public void removeFooterRow(FooterRow row) { | |||
getFooter().removeRow(row); | |||
} | |||
/** | |||
* Removes the row at the given position from the footer section. | |||
* | |||
* @param index | |||
* the index of the row to remove, where the topmost row has | |||
* index zero | |||
* | |||
* @throws IndexOutOfBoundsException | |||
* if {@code index < 0 || index >= getFooterRowCount()} | |||
* | |||
* @see #removeFooterRow(FooterRow) | |||
* @see #addFooterRowAt(int) | |||
* @see #appendFooterRow() | |||
* @see #prependFooterRow() | |||
*/ | |||
public void removeFooterRow(int index) { | |||
getFooter().removeRow(index); | |||
} | |||
/** | |||
* Returns the footer section of this grid. The default footer contains a | |||
* single row, set as the {@linkplain #setDefaultFooterRow(FooterRow) | |||
* default row}. | |||
* | |||
* @return the footer section | |||
*/ | |||
protected Footer getFooter() { | |||
return footer; | |||
} | |||
/** | |||
* Registers a new column reorder listener. | |||
* |
@@ -0,0 +1,70 @@ | |||
/* | |||
* 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.components.grid; | |||
import com.vaadin.ui.Grid; | |||
/** | |||
* Represents the footer section of a Grid. | |||
* | |||
* @author Vaadin Ltd. | |||
* | |||
* @since 8.0 | |||
*/ | |||
public abstract class Footer extends StaticSection<Footer.Row> { | |||
/** | |||
* A row in a Grid Footer. | |||
*/ | |||
public class Row extends StaticSection.StaticRow<Row.Cell> | |||
implements Grid.FooterRow { | |||
/** | |||
* A cell in a Grid footer row. | |||
*/ | |||
public class Cell extends StaticSection.StaticCell | |||
implements Grid.FooterCell { | |||
/** | |||
* Creates a new footer cell. | |||
*/ | |||
protected Cell() { | |||
super(Row.this); | |||
} | |||
} | |||
/** | |||
* Creates a new footer row. | |||
*/ | |||
protected Row() { | |||
super(Footer.this); | |||
} | |||
@Override | |||
protected Cell createCell() { | |||
return new Cell(); | |||
} | |||
@Override | |||
protected String getCellTagName() { | |||
return "td"; | |||
} | |||
} | |||
@Override | |||
public Row createRow() { | |||
return new Row(); | |||
} | |||
} |
@@ -102,6 +102,9 @@ public class GridState extends AbstractSingleSelectState { | |||
/** The state of the header section. */ | |||
public SectionState header = new SectionState(); | |||
/** The state of the footer section. */ | |||
public SectionState footer = new SectionState(); | |||
/** | |||
* Column order in grid. | |||
*/ |
@@ -20,6 +20,7 @@ import com.vaadin.ui.Component; | |||
import com.vaadin.ui.Grid; | |||
import com.vaadin.ui.Grid.Column; | |||
import com.vaadin.ui.Grid.DetailsGenerator; | |||
import com.vaadin.ui.Grid.FooterRow; | |||
import com.vaadin.ui.Grid.HeaderRow; | |||
import com.vaadin.ui.Label; | |||
import com.vaadin.ui.MenuBar; | |||
@@ -199,6 +200,7 @@ public class GridBasics extends AbstractReindeerTestUIWithLog { | |||
createDetailsMenu(componentMenu.addItem("Details", null)); | |||
createBodyMenu(componentMenu.addItem("Body rows", null)); | |||
createHeaderMenu(componentMenu.addItem("Header", null)); | |||
createFooterMenu(componentMenu.addItem("Footer", null)); | |||
createColumnsMenu(componentMenu.addItem("Columns", null)); | |||
return menu; | |||
} | |||
@@ -409,6 +411,35 @@ public class GridBasics extends AbstractReindeerTestUIWithLog { | |||
}); | |||
} | |||
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())); | |||
footerMenu.removeChild(menuItem); | |||
}); | |||
footerMenu.addItem("Append footer row", menuItem -> { | |||
FooterRow row = grid.appendFooterRow(); | |||
int i = 0; | |||
for (Column<?, ?> column : grid.getColumns()) { | |||
row.getCell(column).setText("Footer cell " + i++); | |||
} | |||
}); | |||
footerMenu.addItem("Prepend footer row", menuItem -> { | |||
FooterRow row = grid.prependFooterRow(); | |||
int i = 0; | |||
for (Column<?, ?> column : grid.getColumns()) { | |||
row.getCell(column).setText("Footer cell " + i++); | |||
} | |||
}); | |||
footerMenu.addItem("Remove first footer row", menuItem -> { | |||
grid.removeFooterRow(0); | |||
}); | |||
} | |||
/* DetailsGenerator related things */ | |||
private void createDetailsMenu(MenuItem detailsMenu) { |
@@ -31,8 +31,18 @@ public class GridHeaderFooterTest extends GridBasicsTest { | |||
protected static final String[] HEADER_TEXTS = IntStream | |||
.range(0, GridBasics.COLUMN_CAPTIONS.length) | |||
.mapToObj(i -> "Header cell " + i) | |||
.toArray(String[]::new); | |||
.mapToObj(i -> "Header cell " + i).toArray(String[]::new); | |||
protected static final String[] FOOTER_TEXTS = IntStream | |||
.range(0, GridBasics.COLUMN_CAPTIONS.length) | |||
.mapToObj(i -> "Footer cell " + i).toArray(String[]::new); | |||
@Override | |||
public void setUp() { | |||
super.setUp(); | |||
selectMenuPath("Component", "Footer", "Add default footer row"); | |||
} | |||
@Test | |||
public void initialState_defaultHeaderPresent() { | |||
@@ -103,16 +113,40 @@ public class GridHeaderFooterTest extends GridBasicsTest { | |||
assertNoSortIndicator(headerCell, "sort-desc"); | |||
} | |||
@Test | |||
public void initialState_defaultFooterPresent() { | |||
assertEquals(1, getGridElement().getFooterCount()); | |||
assertFooterTexts(0, GridBasics.COLUMN_CAPTIONS); | |||
} | |||
@Test | |||
public void appendFooterRow_addedToBottom() { | |||
selectMenuPath("Component", "Footer", "Append footer row"); | |||
assertEquals(2, getGridElement().getFooterCount()); | |||
assertFooterTexts(0, GridBasics.COLUMN_CAPTIONS); | |||
assertFooterTexts(1, FOOTER_TEXTS); | |||
} | |||
@Test | |||
public void prependFooterRow_addedToTop() { | |||
selectMenuPath("Component", "Footer", "Prepend footer row"); | |||
assertEquals(2, getGridElement().getFooterCount()); | |||
assertFooterTexts(0, FOOTER_TEXTS); | |||
assertFooterTexts(1, GridBasics.COLUMN_CAPTIONS); | |||
} | |||
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")).getAttribute( | |||
"innerHTML"); | |||
String actual = e.findElement(By.tagName("div")) | |||
.getAttribute("innerHTML"); | |||
assertEquals(expected, actual); | |||
} | |||
protected void assertHeaderTexts(int rowIndex, String[] texts) { | |||
List<GridCellElement> headerCells = getGridElement().getHeaderCells( | |||
rowIndex); | |||
List<GridCellElement> headerCells = getGridElement() | |||
.getHeaderCells(rowIndex); | |||
assertEquals(texts.length, headerCells.size()); | |||
for (int i = 0; i < headerCells.size(); i++) { | |||
@@ -120,9 +154,19 @@ public class GridHeaderFooterTest extends GridBasicsTest { | |||
} | |||
} | |||
protected void assertFooterTexts(int rowIndex, String[] texts) { | |||
List<GridCellElement> footerCells = getGridElement() | |||
.getFooterCells(rowIndex); | |||
assertEquals(texts.length, footerCells.size()); | |||
for (int i = 0; i < footerCells.size(); i++) { | |||
assertText(texts[i], footerCells.get(i)); | |||
} | |||
} | |||
protected void assertSortIndicator(GridCellElement cell, String classname) { | |||
assertTrue("Header cell should have sort indicator " + classname, cell | |||
.getAttribute("class").contains(classname)); | |||
assertTrue("Header cell should have sort indicator " + classname, | |||
cell.getAttribute("class").contains(classname)); | |||
} | |||
protected void assertNoSortIndicator(GridCellElement cell, |