- Multiple headers shouldn't stack behind each other. - Body rows shouldn't get stuck to default row height. - Compatibility version's hidable row selector shouldn't try to calculate row heights based on rows that haven't been added to DOM yet. Fixes #7674tags/8.7.0.alpha1
*/ | */ | ||||
public int getRowCount(); | public int getRowCount(); | ||||
/** | |||||
* For internal use only. May be removed or replaced in the future. | |||||
* | |||||
* @since | |||||
* @return {@code true} if row height calculations have been scheduled | |||||
*/ | |||||
public boolean isAutodetectingRowHeightLater(); | |||||
/** | /** | ||||
* The default height of the rows in this RowContainer. | * The default height of the rows in this RowContainer. | ||||
* | * |
private boolean initialColumnSizesCalculated = false; | private boolean initialColumnSizesCalculated = false; | ||||
private boolean autodetectingRowHeightLater = false; | |||||
public AbstractRowContainer( | public AbstractRowContainer( | ||||
final TableSectionElement rowContainerElement) { | final TableSectionElement rowContainerElement) { | ||||
root = rowContainerElement; | root = rowContainerElement; | ||||
} | } | ||||
public void autodetectRowHeightLater() { | public void autodetectRowHeightLater() { | ||||
autodetectingRowHeightLater = true; | |||||
Scheduler.get().scheduleFinally(() -> { | Scheduler.get().scheduleFinally(() -> { | ||||
if (defaultRowHeightShouldBeAutodetected && isAttached()) { | if (defaultRowHeightShouldBeAutodetected && isAttached()) { | ||||
autodetectRowHeightNow(); | autodetectRowHeightNow(); | ||||
defaultRowHeightShouldBeAutodetected = false; | defaultRowHeightShouldBeAutodetected = false; | ||||
} | } | ||||
autodetectingRowHeightLater = false; | |||||
}); | }); | ||||
} | } | ||||
@Override | |||||
public boolean isAutodetectingRowHeightLater() { | |||||
return autodetectingRowHeightLater; | |||||
} | |||||
private void fireRowHeightChangedEventFinally() { | private void fireRowHeightChangedEventFinally() { | ||||
if (!rowHeightChangedEventFired) { | if (!rowHeightChangedEventFired) { | ||||
rowHeightChangedEventFired = true; | rowHeightChangedEventFired = true; |
import com.google.gwt.user.client.ui.PopupPanel; | import com.google.gwt.user.client.ui.PopupPanel; | ||||
import com.google.gwt.user.client.ui.ResizeComposite; | import com.google.gwt.user.client.ui.ResizeComposite; | ||||
import com.google.gwt.user.client.ui.Widget; | import com.google.gwt.user.client.ui.Widget; | ||||
import com.vaadin.client.BrowserInfo; | |||||
import com.vaadin.client.DeferredWorker; | |||||
import com.vaadin.client.Focusable; | |||||
import com.vaadin.client.WidgetUtil; | |||||
import com.vaadin.client.*; | |||||
import com.vaadin.client.WidgetUtil.Reference; | import com.vaadin.client.WidgetUtil.Reference; | ||||
import com.vaadin.client.data.DataChangeHandler; | import com.vaadin.client.data.DataChangeHandler; | ||||
import com.vaadin.client.data.DataSource; | import com.vaadin.client.data.DataSource; | ||||
*/ | */ | ||||
private class AutoColumnWidthsRecalculator { | private class AutoColumnWidthsRecalculator { | ||||
private double lastCalculatedInnerWidth = -1; | private double lastCalculatedInnerWidth = -1; | ||||
private double lastCalculatedInnerHeight = -1; | |||||
private final ScheduledCommand calculateCommand = new ScheduledCommand() { | private final ScheduledCommand calculateCommand = new ScheduledCommand() { | ||||
// Update latest width to prevent recalculate on height change. | // Update latest width to prevent recalculate on height change. | ||||
lastCalculatedInnerWidth = escalator.getInnerWidth(); | lastCalculatedInnerWidth = escalator.getInnerWidth(); | ||||
lastCalculatedInnerHeight = getEscalatorInnerHeight(); | |||||
} | } | ||||
private boolean columnsAreGuaranteedToBeWiderThanGrid() { | private boolean columnsAreGuaranteedToBeWiderThanGrid() { | ||||
recalculateColumnWidths(); | recalculateColumnWidths(); | ||||
} | } | ||||
if (getEscalatorInnerHeight() != autoColumnWidthsRecalculator.lastCalculatedInnerHeight) { | |||||
Scheduler.get().scheduleFinally(() -> { | |||||
// Trigger re-calculation of all row positions. | |||||
RowContainer.BodyRowContainer body = getEscalator() | |||||
.getBody(); | |||||
if (!body.isAutodetectingRowHeightLater()) { | |||||
body.setDefaultRowHeight(body.getDefaultRowHeight()); | |||||
} | |||||
}); | |||||
} | |||||
// Vertical resizing could make editor positioning invalid so it | // Vertical resizing could make editor positioning invalid so it | ||||
// needs to be recalculated on resize | // needs to be recalculated on resize | ||||
if (isEditorActive()) { | if (isEditorActive()) { | ||||
}); | }); | ||||
} | } | ||||
private double getEscalatorInnerHeight() { | |||||
return new ComputedStyle(getEscalator().getTableWrapper()) | |||||
.getHeightIncludingBorderPadding(); | |||||
} | |||||
/** | /** | ||||
* Grid does not support adding Widgets this way. | * Grid does not support adding Widgets this way. | ||||
* <p> | * <p> |
*/ | */ | ||||
public int getRowCount(); | public int getRowCount(); | ||||
/** | |||||
* This method calculates the current row count directly from the DOM. | |||||
* <p> | |||||
* While the container is stable, this value should equal to | |||||
* {@link #getRowCount()}, but while row counts are being updated, these two | |||||
* values might differ for a short while. | |||||
* <p> | |||||
* Any extra content, such as spacers for the body, should not be included | |||||
* in this count. | |||||
* | |||||
* @since | |||||
* | |||||
* @return the actual DOM count of rows | |||||
*/ | |||||
public int getDomRowCount(); | |||||
/** | /** | ||||
* The default height of the rows in this RowContainer. | * The default height of the rows in this RowContainer. | ||||
* | * |
return rows; | return rows; | ||||
} | } | ||||
/** | |||||
* This method calculates the current row count directly from the DOM. | |||||
* <p> | |||||
* While Escalator is stable, this value should equal to | |||||
* {@link #getRowCount()}, but while row counts are being updated, these | |||||
* two values might differ for a short while. | |||||
* <p> | |||||
* Any extra content, such as spacers for the body, should not be | |||||
* included in this count. | |||||
* | |||||
* @since 7.5.0 | |||||
* | |||||
* @return the actual DOM count of rows | |||||
*/ | |||||
public abstract int getDomRowCount(); | |||||
/** | /** | ||||
* {@inheritDoc} | * {@inheritDoc} | ||||
* <p> | * <p> |
private void setHeightToHeaderCellHeight() { | private void setHeightToHeaderCellHeight() { | ||||
RowContainer header = grid.escalator.getHeader(); | RowContainer header = grid.escalator.getHeader(); | ||||
if (header.getRowCount() == 0 | |||||
if (header.getDomRowCount() == 0 | |||||
|| !header.getRowElement(0).hasChildNodes()) { | || !header.getRowElement(0).hasChildNodes()) { | ||||
getLogger().info( | getLogger().info( | ||||
"No header cell available when calculating sidebar button height"); | "No header cell available when calculating sidebar button height"); |
top: 0; | top: 0; | ||||
left: 0; | left: 0; | ||||
} | } | ||||
.#{$primaryStyleName}-header > .#{$primaryStyleName}-row { | |||||
position: relative; | |||||
} | |||||
} | } | ||||
.#{$primaryStyleName}-row { | .#{$primaryStyleName}-row { |
package com.vaadin.tests.components.grid; | |||||
import com.vaadin.server.VaadinRequest; | |||||
import com.vaadin.tests.components.AbstractTestUI; | |||||
import com.vaadin.ui.Component; | |||||
import com.vaadin.v7.event.ItemClickEvent; | |||||
import com.vaadin.v7.event.ItemClickEvent.ItemClickListener; | |||||
import com.vaadin.v7.ui.Grid; | |||||
import com.vaadin.v7.ui.Grid.RowReference; | |||||
@SuppressWarnings("deprecation") | |||||
public class CompatibilityGridInDetailsRow extends AbstractTestUI { | |||||
@Override | |||||
protected void setup(VaadinRequest request) { | |||||
Grid fg = new Grid(); | |||||
fg.setId("grid1"); | |||||
fg.setSizeFull(); | |||||
fg.addColumn("col1", String.class); | |||||
fg.addColumn("col2", String.class); | |||||
fg.addRow("Temp 1", "Temp 2"); | |||||
fg.addRow("Temp 3", "Temp 4"); | |||||
fg.setDetailsGenerator(new Grid.DetailsGenerator() { | |||||
@Override | |||||
public Component getDetails(RowReference rowReference) { | |||||
Grid gd = new Grid(); | |||||
gd.setId("grid2"); | |||||
gd.setSizeFull(); | |||||
gd.addHeaderRowAt(0); | |||||
gd.addColumn("Column 1", String.class); | |||||
gd.addColumn("Column 2", String.class); | |||||
gd.getColumn("Column 2").setHidable(true); | |||||
gd.addColumn("Column 3", String.class); | |||||
gd.addColumn("Column 4", String.class); | |||||
gd.addColumn("id", Integer.class); | |||||
gd.addRow("Nicolaus Copernicus", "Nicolaus Copernicus", | |||||
"Nicolaus Copernicus", "Nicolaus Copernicus", 1543); | |||||
gd.addRow("Nicolaus Copernicus", "Nicolaus Copernicus", | |||||
"Nicolaus Copernicus", "Nicolaus Copernicus", 1543); | |||||
gd.addRow("Nicolaus Copernicus", "Nicolaus Copernicus", | |||||
"Nicolaus Copernicus", "Nicolaus Copernicus", 1543); | |||||
gd.addRow("Nicolaus Copernicus", "Nicolaus Copernicus", | |||||
"Nicolaus Copernicus", "Nicolaus Copernicus", 1543); | |||||
return gd; | |||||
} | |||||
}); | |||||
fg.addItemClickListener(new ItemClickListener() { | |||||
@Override | |||||
public void itemClick(ItemClickEvent event) { | |||||
if (event.isDoubleClick()) { | |||||
Object itemId = event.getItemId(); | |||||
fg.setDetailsVisible(itemId, !fg.isDetailsVisible(itemId)); | |||||
} | |||||
} | |||||
}); | |||||
getLayout().addComponent(fg); | |||||
} | |||||
@Override | |||||
protected String getTestDescription() { | |||||
return "A nested Grid with multirow header should display all headers and " | |||||
+ "opening the details row shouldn't cause a client-side exception " | |||||
+ "when the nested Grid has hideable rows."; | |||||
} | |||||
@Override | |||||
protected Integer getTicketNumber() { | |||||
return 7674; | |||||
} | |||||
} |
package com.vaadin.tests.components.grid; | |||||
import com.vaadin.annotations.Widgetset; | |||||
import com.vaadin.server.VaadinRequest; | |||||
import com.vaadin.shared.ui.grid.HeightMode; | |||||
import com.vaadin.tests.data.bean.Person; | |||||
import com.vaadin.ui.Component; | |||||
import com.vaadin.ui.Grid; | |||||
import com.vaadin.ui.Grid.ItemClick; | |||||
import com.vaadin.ui.VerticalLayout; | |||||
import com.vaadin.ui.components.grid.DetailsGenerator; | |||||
import com.vaadin.ui.components.grid.HeaderRow; | |||||
import com.vaadin.ui.components.grid.ItemClickListener; | |||||
@Widgetset("com.vaadin.DefaultWidgetSet") | |||||
public class GridInDetailsRow extends SimpleGridUI { | |||||
int index = 0; | |||||
@Override | |||||
protected void setup(VaadinRequest request) { | |||||
getLayout().addComponent(createGrid()); | |||||
} | |||||
@Override | |||||
protected Grid<Person> createGrid() { | |||||
Grid<Person> grid = super.createGrid(); | |||||
grid.setId("grid" + index); | |||||
++index; | |||||
grid.setSizeFull(); | |||||
grid.setHeightUndefined(); | |||||
grid.setHeightMode(HeightMode.UNDEFINED); | |||||
HeaderRow hr0 = grid.addHeaderRowAt(0); | |||||
hr0.getCell(grid.getColumns().get(0)).setText("Name"); | |||||
hr0.getCell(grid.getColumns().get(1)).setText("Age"); | |||||
HeaderRow hr1 = grid.getDefaultHeaderRow(); | |||||
hr1.getCell(grid.getColumns().get(0)).setText("Foo"); | |||||
hr1.getCell(grid.getColumns().get(1)).setText("Bar"); | |||||
grid.getColumns().get(1).setHidable(true); | |||||
grid.setDetailsGenerator(new DetailsGenerator<Person>() { | |||||
@Override | |||||
public Component apply(Person t) { | |||||
VerticalLayout layout = new VerticalLayout(); | |||||
layout.setMargin(true); | |||||
Grid<Person> gd = createGrid(); | |||||
layout.addComponent(gd); | |||||
return layout; | |||||
} | |||||
}); | |||||
grid.addItemClickListener(new ItemClickListener<Person>() { | |||||
@Override | |||||
public void itemClick(ItemClick<Person> event) { | |||||
if (event.getMouseEventDetails().isDoubleClick()) { | |||||
Person item = event.getItem(); | |||||
grid.setDetailsVisible(item, !grid.isDetailsVisible(item)); | |||||
} | |||||
} | |||||
}); | |||||
return grid; | |||||
} | |||||
@Override | |||||
protected String getTestDescription() { | |||||
return "A nested Grid with multirow header should display all headers and " | |||||
+ "the body rows shouldn't get stuck to default row height."; | |||||
} | |||||
@Override | |||||
protected Integer getTicketNumber() { | |||||
return 7674; | |||||
} | |||||
} |
return rowContainer.getRowCount(); | return rowContainer.getRowCount(); | ||||
} | } | ||||
@Override | |||||
public boolean isAutodetectingRowHeightLater() { | |||||
return rowContainer.isAutodetectingRowHeightLater(); | |||||
} | |||||
@Override | @Override | ||||
public void setDefaultRowHeight(double px) | public void setDefaultRowHeight(double px) | ||||
throws IllegalArgumentException { | throws IllegalArgumentException { |
package com.vaadin.tests.components.grid; | |||||
import static org.hamcrest.Matchers.greaterThan; | |||||
import static org.junit.Assert.assertEquals; | |||||
import static org.junit.Assert.assertNotNull; | |||||
import static org.junit.Assert.assertThat; | |||||
import java.util.List; | |||||
import org.junit.Test; | |||||
import org.openqa.selenium.By; | |||||
import org.openqa.selenium.WebElement; | |||||
import com.vaadin.testbench.elements.GridElement; | |||||
import com.vaadin.testbench.elements.GridElement.GridCellElement; | |||||
import com.vaadin.testbench.elements.GridElement.GridRowElement; | |||||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||||
public class CompatibilityGridInDetailsRowTest extends MultiBrowserTest { | |||||
@Test | |||||
public void testNestedGridMultiRowHeaderPositions() { | |||||
openTestURL(); | |||||
GridElement grid = $(GridElement.class).first(); | |||||
GridRowElement row = grid.getRow(1); | |||||
row.doubleClick(); | |||||
waitForElementPresent(By.className("v-grid-spacer")); | |||||
GridElement nestedGrid = $(GridElement.class).id("grid2"); | |||||
assertEquals("Incorrect header row count.", 2, | |||||
nestedGrid.getHeaderCount()); | |||||
GridCellElement headerCell00 = nestedGrid.getHeaderCell(0, 0); | |||||
GridCellElement headerCell11 = nestedGrid.getHeaderCell(1, 1); | |||||
assertThat("Incorrect X-position.", headerCell11.getLocation().getX(), | |||||
greaterThan(headerCell00.getLocation().getX())); | |||||
assertThat("Incorrect Y-position.", headerCell11.getLocation().getY(), | |||||
greaterThan(headerCell00.getLocation().getY())); | |||||
} | |||||
@Test | |||||
public void testNestedGridRowHeights() { | |||||
openTestURL(); | |||||
GridElement grid = $(GridElement.class).first(); | |||||
GridRowElement row = grid.getRow(1); | |||||
row.doubleClick(); | |||||
waitForElementPresent(By.className("v-grid-spacer")); | |||||
GridElement nestedGrid = $(GridElement.class).id("grid2"); | |||||
grid.findElement(By.className("v-grid-sidebar-button")).click(); | |||||
assertNotNull( | |||||
"There are no options for toggling column visibility but there should be.", | |||||
getColumnHidingToggle(nestedGrid)); | |||||
} | |||||
/** | |||||
* Returns the first toggle inside the sidebar for hiding a column, or null | |||||
* if not found. | |||||
*/ | |||||
protected WebElement getColumnHidingToggle(GridElement grid) { | |||||
WebElement sidebar = findElement(By.className("v-grid-sidebar-popup")); | |||||
List<WebElement> elements = sidebar | |||||
.findElements(By.className("column-hiding-toggle")); | |||||
for (WebElement e : elements) { | |||||
return e; | |||||
} | |||||
return null; | |||||
} | |||||
} |
package com.vaadin.tests.components.grid; | |||||
import static org.hamcrest.Matchers.greaterThan; | |||||
import static org.junit.Assert.assertThat; | |||||
import org.junit.Test; | |||||
import org.openqa.selenium.By; | |||||
import com.vaadin.testbench.elements.GridElement; | |||||
import com.vaadin.testbench.elements.GridElement.GridCellElement; | |||||
import com.vaadin.testbench.elements.GridElement.GridRowElement; | |||||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||||
public class GridInDetailsRowTest extends MultiBrowserTest { | |||||
@Test | |||||
public void testNestedGridMultiRowHeaderPositions() { | |||||
openTestURL(); | |||||
GridElement grid = $(GridElement.class).first(); | |||||
GridRowElement row = grid.getRow(2); | |||||
row.doubleClick(); | |||||
waitForElementPresent(By.className("v-grid-spacer")); | |||||
GridElement nestedGrid = $(GridElement.class).id("grid1"); | |||||
GridCellElement headerCell00 = nestedGrid.getHeaderCell(0, 0); | |||||
GridCellElement headerCell11 = nestedGrid.getHeaderCell(1, 1); | |||||
assertThat("Incorrect X-position.", headerCell11.getLocation().getX(), | |||||
greaterThan(headerCell00.getLocation().getX())); | |||||
assertThat("Incorrect Y-position.", headerCell11.getLocation().getY(), | |||||
greaterThan(headerCell00.getLocation().getY())); | |||||
} | |||||
@Test | |||||
public void testNestedGridRowHeights() { | |||||
openTestURL(); | |||||
GridElement grid = $(GridElement.class).first(); | |||||
GridRowElement row = grid.getRow(2); | |||||
row.doubleClick(); | |||||
waitForElementPresent(By.className("v-grid-spacer")); | |||||
GridElement nestedGrid = $(GridElement.class).id("grid1"); | |||||
GridCellElement cell = nestedGrid.getCell(0, 0); | |||||
assertThat("Incorrect row height.", cell.getSize().height, | |||||
greaterThan(30)); | |||||
} | |||||
} |