- new height more for undefined height that works like in Table and resizes the grid when details row opens or closes Change-Id: I2dc817140308093865be30de72edcd6494e4a44bfeature/vaadin8-book-vol2
@@ -1148,6 +1148,9 @@ public class Escalator extends Widget implements RequiresResize, | |||
assertArgumentsAreValidAndWithinRange(index, numberOfRows); | |||
rows -= numberOfRows; | |||
if (heightMode == HeightMode.UNDEFINED) { | |||
heightByRows = rows; | |||
} | |||
if (!isAttached()) { | |||
return; | |||
@@ -1271,6 +1274,9 @@ public class Escalator extends Widget implements RequiresResize, | |||
} | |||
rows += numberOfRows; | |||
if (heightMode == HeightMode.UNDEFINED) { | |||
heightByRows = rows; | |||
} | |||
/* | |||
* only add items in the DOM if the widget itself is attached to the | |||
@@ -5826,7 +5832,13 @@ public class Escalator extends Widget implements RequiresResize, | |||
if (height != null && !height.isEmpty()) { | |||
heightByCss = height; | |||
} else { | |||
heightByCss = DEFAULT_HEIGHT; | |||
if (getHeightMode() == HeightMode.UNDEFINED) { | |||
heightByRows = body.getRowCount(); | |||
applyHeightByRows(); | |||
return; | |||
} else { | |||
heightByCss = DEFAULT_HEIGHT; | |||
} | |||
} | |||
if (getHeightMode() == HeightMode.CSS) { | |||
@@ -5840,7 +5852,16 @@ public class Escalator extends Widget implements RequiresResize, | |||
if (height != null && !height.isEmpty()) { | |||
super.setHeight(height); | |||
} else { | |||
super.setHeight(DEFAULT_HEIGHT); | |||
if (getHeightMode() == HeightMode.UNDEFINED) { | |||
int newHeightByRows = body.getRowCount(); | |||
if (heightByRows != newHeightByRows) { | |||
heightByRows = newHeightByRows; | |||
applyHeightByRows(); | |||
} | |||
return; | |||
} else { | |||
super.setHeight(DEFAULT_HEIGHT); | |||
} | |||
} | |||
recalculateElementSizes(); | |||
@@ -6318,7 +6339,7 @@ public class Escalator extends Widget implements RequiresResize, | |||
* define its height that way. | |||
*/ | |||
private void applyHeightByRows() { | |||
if (heightMode != HeightMode.ROW) { | |||
if (heightMode != HeightMode.ROW && heightMode != HeightMode.UNDEFINED) { | |||
return; | |||
} | |||
@@ -6327,9 +6348,13 @@ public class Escalator extends Widget implements RequiresResize, | |||
double bodyHeight = body.getDefaultRowHeight() * heightByRows; | |||
double scrollbar = horizontalScrollbar.showsScrollHandle() ? horizontalScrollbar | |||
.getScrollbarThickness() : 0; | |||
double spacerHeight = 0; // ignored if HeightMode.ROW | |||
if (heightMode == HeightMode.UNDEFINED) { | |||
spacerHeight = body.spacerContainer.getSpacerHeightsSum(); | |||
} | |||
double totalHeight = headerHeight + bodyHeight + scrollbar | |||
+ footerHeight; | |||
double totalHeight = headerHeight + bodyHeight + spacerHeight | |||
+ scrollbar + footerHeight; | |||
setHeightInternal(totalHeight + "px"); | |||
} | |||
@@ -6370,6 +6395,9 @@ public class Escalator extends Widget implements RequiresResize, | |||
case ROW: | |||
setHeightByRows(heightByRows); | |||
break; | |||
case UNDEFINED: | |||
setHeightByRows(body.getRowCount()); | |||
break; | |||
default: | |||
throw new IllegalStateException("Unimplemented feature " | |||
+ "- unknown HeightMode: " + this.heightMode); |
@@ -3600,6 +3600,9 @@ public class Grid<T> extends ResizeComposite implements | |||
} | |||
escalator.getBody().setSpacer(rowIndex, spacerHeight); | |||
if (getHeightMode() == HeightMode.UNDEFINED) { | |||
setHeightByRows(getEscalator().getBody().getRowCount()); | |||
} | |||
} | |||
@Override | |||
@@ -3628,6 +3631,11 @@ public class Grid<T> extends ResizeComposite implements | |||
setParent(detailsWidget, null); | |||
spacerElement.removeAllChildren(); | |||
if (getHeightMode() == HeightMode.UNDEFINED) { | |||
// update spacer height | |||
escalator.getBody().setSpacer(spacer.getRow(), 0); | |||
setHeightByRows(getEscalator().getBody().getRowCount()); | |||
} | |||
} | |||
} | |||
@@ -38,5 +38,11 @@ public enum HeightMode { | |||
* The height of the Component or Widget in question is defined by a number | |||
* of rows. | |||
*/ | |||
ROW; | |||
ROW, | |||
/** | |||
* The height of the Component or Widget in question is defined by its | |||
* contents. | |||
*/ | |||
UNDEFINED; | |||
} |
@@ -0,0 +1,189 @@ | |||
/* | |||
* 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.components.grid; | |||
import java.util.Arrays; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import com.vaadin.data.Property.ValueChangeEvent; | |||
import com.vaadin.data.Property.ValueChangeListener; | |||
import com.vaadin.event.ItemClickEvent; | |||
import com.vaadin.event.ItemClickEvent.ItemClickListener; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.shared.ui.grid.HeightMode; | |||
import com.vaadin.tests.components.AbstractTestUI; | |||
import com.vaadin.ui.Alignment; | |||
import com.vaadin.ui.Component; | |||
import com.vaadin.ui.Grid; | |||
import com.vaadin.ui.Grid.DetailsGenerator; | |||
import com.vaadin.ui.Grid.RowReference; | |||
import com.vaadin.ui.HorizontalLayout; | |||
import com.vaadin.ui.Label; | |||
import com.vaadin.ui.OptionGroup; | |||
import com.vaadin.ui.VerticalLayout; | |||
/** | |||
* Tests that Grid gets correct height based on height mode, and resizes | |||
* properly with details row if height is undefined. | |||
* | |||
* @author Vaadin Ltd | |||
*/ | |||
public class GridHeight extends AbstractTestUI { | |||
static final String FULL = "Full"; | |||
static final String UNDEFINED = "Undefined"; | |||
static final String PX100 = "100px"; | |||
static final Integer ROW3 = 3; | |||
static final Object[] gridHeights = { FULL, UNDEFINED, ROW3 }; | |||
static final String[] gridWidths = { FULL, UNDEFINED }; | |||
static final String[] detailsRowHeights = { FULL, UNDEFINED, PX100 }; | |||
private Grid grid; | |||
private Map<Object, VerticalLayout> detailsLayouts = new HashMap<Object, VerticalLayout>(); | |||
private OptionGroup detailsHeightSelector; | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
grid = new Grid(); | |||
grid.addColumn("name", String.class); | |||
grid.addColumn("born", Integer.class); | |||
grid.addRow("Nicolaus Copernicus", 1543); | |||
grid.addRow("Galileo Galilei", 1564); | |||
for (int i = 0; i < 1; ++i) { | |||
grid.addRow("Johannes Kepler", 1571); | |||
} | |||
grid.setDetailsGenerator(new DetailsGenerator() { | |||
@Override | |||
public Component getDetails(final RowReference rowReference) { | |||
if (!detailsLayouts.containsKey(rowReference.getItemId())) { | |||
createDetailsLayout(rowReference.getItemId()); | |||
} | |||
return detailsLayouts.get(rowReference.getItemId()); | |||
} | |||
}); | |||
grid.addItemClickListener(new ItemClickListener() { | |||
@Override | |||
public void itemClick(final ItemClickEvent event) { | |||
final Object itemId = event.getItemId(); | |||
grid.setDetailsVisible(itemId, !grid.isDetailsVisible(itemId)); | |||
} | |||
}); | |||
addComponent(createOptionLayout()); | |||
addComponent(grid); | |||
} | |||
private void createDetailsLayout(Object itemId) { | |||
VerticalLayout detailsLayout = new VerticalLayout(); | |||
setDetailsHeight(detailsLayout, detailsHeightSelector.getValue()); | |||
detailsLayout.setWidth("100%"); | |||
Label lbl1 = new Label("details row"); | |||
lbl1.setId("lbl1"); | |||
lbl1.setSizeUndefined(); | |||
detailsLayout.addComponent(lbl1); | |||
detailsLayout.setComponentAlignment(lbl1, Alignment.MIDDLE_CENTER); | |||
detailsLayouts.put(itemId, detailsLayout); | |||
} | |||
private Component createOptionLayout() { | |||
HorizontalLayout optionLayout = new HorizontalLayout(); | |||
OptionGroup gridHeightSelector = new OptionGroup("Grid height", | |||
Arrays.<Object> asList(gridHeights)); | |||
gridHeightSelector.setId("gridHeightSelector"); | |||
gridHeightSelector.setItemCaption(ROW3, ROW3 + " rows"); | |||
gridHeightSelector.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
Object value = event.getProperty().getValue(); | |||
if (UNDEFINED.equals(value)) { | |||
grid.setHeightUndefined(); | |||
grid.setHeightMode(HeightMode.UNDEFINED); | |||
} else if (FULL.equals(value)) { | |||
grid.setHeight("100%"); | |||
grid.setHeightMode(HeightMode.CSS); | |||
} else if (ROW3.equals(value)) { | |||
grid.setHeightByRows(ROW3); | |||
grid.setHeightMode(HeightMode.ROW); | |||
} | |||
} | |||
}); | |||
gridHeightSelector.setValue(UNDEFINED); | |||
optionLayout.addComponent(gridHeightSelector); | |||
OptionGroup gridWidthSelector = new OptionGroup("Grid width", | |||
Arrays.asList(gridWidths)); | |||
gridWidthSelector.setId("gridWidthSelector"); | |||
gridWidthSelector.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
Object value = event.getProperty().getValue(); | |||
if (UNDEFINED.equals(value)) { | |||
grid.setWidthUndefined(); | |||
} else if (FULL.equals(value)) { | |||
grid.setWidth("100%"); | |||
} | |||
} | |||
}); | |||
gridWidthSelector.setValue(UNDEFINED); | |||
optionLayout.addComponent(gridWidthSelector); | |||
detailsHeightSelector = new OptionGroup("Details row height", | |||
Arrays.asList(detailsRowHeights)); | |||
detailsHeightSelector.setId("detailsHeightSelector"); | |||
detailsHeightSelector.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
Object value = event.getProperty().getValue(); | |||
for (VerticalLayout detailsLayout : detailsLayouts.values()) { | |||
setDetailsHeight(detailsLayout, value); | |||
} | |||
} | |||
}); | |||
detailsHeightSelector.setValue(PX100); | |||
optionLayout.addComponent(detailsHeightSelector); | |||
return optionLayout; | |||
} | |||
private void setDetailsHeight(VerticalLayout detailsLayout, Object value) { | |||
if (UNDEFINED.equals(value)) { | |||
detailsLayout.setHeightUndefined(); | |||
} else if (FULL.equals(value)) { | |||
detailsLayout.setHeight("100%"); | |||
} else if (PX100.equals(value)) { | |||
detailsLayout.setHeight(PX100); | |||
} | |||
} | |||
@Override | |||
protected String getTestDescription() { | |||
return "Grid with undefined height should display all rows and resize when details row is opened." | |||
+ "<br>Grid with full height is always 400px high regardless or details row." | |||
+ "<br>Grid with row height should always be the height of those rows regardless of details row."; | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return 19690; | |||
} | |||
} |
@@ -0,0 +1,199 @@ | |||
/* | |||
* 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.components.grid; | |||
import static org.hamcrest.MatcherAssert.assertThat; | |||
import static org.hamcrest.Matchers.is; | |||
import static org.hamcrest.number.IsCloseTo.closeTo; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.Map.Entry; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
import com.vaadin.testbench.elements.GridElement; | |||
import com.vaadin.testbench.elements.OptionGroupElement; | |||
import com.vaadin.testbench.parallel.BrowserUtil; | |||
import com.vaadin.testbench.parallel.TestCategory; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
/** | |||
* Tests that Grid gets correct height based on height mode, and resizes | |||
* properly with details row if height is undefined. | |||
* | |||
* @author Vaadin Ltd | |||
*/ | |||
@TestCategory("grid") | |||
public class GridHeightTest extends MultiBrowserTest { | |||
@Override | |||
public void setup() throws Exception { | |||
super.setup(); | |||
openTestURL(); | |||
waitForElementPresent(By.className("v-grid")); | |||
} | |||
@Test | |||
public void testGridHeightAndResizingUndefined() | |||
throws InterruptedException { | |||
assertNoErrors(testGridHeightAndResizing(GridHeight.UNDEFINED)); | |||
} | |||
@Test | |||
public void testGridHeightAndResizingRow() throws InterruptedException { | |||
if (isIE8orIE9()) { | |||
/* | |||
* with IE8 and IE9 and this height mode grid resizes when it | |||
* shouldn't and doesn't resize when it should, pre-existing problem | |||
* that isn't within the scope of this ticket | |||
*/ | |||
return; | |||
} | |||
assertNoErrors(testGridHeightAndResizing(GridHeight.ROW3)); | |||
} | |||
@Test | |||
public void testGridHeightAndResizingFull() throws InterruptedException { | |||
assertNoErrors(testGridHeightAndResizing(GridHeight.FULL)); | |||
} | |||
private Map<AssertionError, Object[]> testGridHeightAndResizing( | |||
Object gridHeight) throws InterruptedException { | |||
Map<AssertionError, Object[]> errors = new HashMap<AssertionError, Object[]>(); | |||
String caption; | |||
if (GridHeight.ROW3.equals(gridHeight)) { | |||
caption = gridHeight + " rows"; | |||
} else { | |||
caption = (String) gridHeight; | |||
} | |||
$(OptionGroupElement.class).id("gridHeightSelector").selectByText( | |||
caption); | |||
for (String gridWidth : GridHeight.gridWidths) { | |||
$(OptionGroupElement.class).id("gridWidthSelector").selectByText( | |||
gridWidth); | |||
for (String detailsRowHeight : GridHeight.detailsRowHeights) { | |||
$(OptionGroupElement.class).id("detailsHeightSelector") | |||
.selectByText(detailsRowHeight); | |||
sleep(500); | |||
GridElement grid = $(GridElement.class).first(); | |||
int initialHeight = grid.getSize().getHeight(); | |||
try { | |||
// check default height | |||
assertGridHeight(getExpectedInitialHeight(gridHeight), | |||
initialHeight); | |||
} catch (AssertionError e) { | |||
errors.put(e, new Object[] { gridHeight, gridWidth, | |||
detailsRowHeight, "initial" }); | |||
} | |||
grid.getRow(2).click(5, 5); | |||
waitForElementPresent(By.id("lbl1")); | |||
int openHeight = grid.getSize().getHeight(); | |||
try { | |||
// check height with details row opened | |||
assertGridHeight( | |||
getExpectedOpenedHeight(gridHeight, | |||
detailsRowHeight), openHeight); | |||
} catch (AssertionError e) { | |||
errors.put(e, new Object[] { gridHeight, gridWidth, | |||
detailsRowHeight, "opened" }); | |||
} | |||
grid.getRow(2).click(5, 5); | |||
waitForElementNotPresent(By.id("lbl1")); | |||
int afterHeight = grid.getSize().getHeight(); | |||
try { | |||
// check height with details row closed again | |||
assertThat("Unexpected Grid Height", afterHeight, | |||
is(initialHeight)); | |||
} catch (AssertionError e) { | |||
errors.put(e, new Object[] { gridHeight, gridWidth, | |||
detailsRowHeight, "closed" }); | |||
} | |||
} | |||
} | |||
return errors; | |||
} | |||
private void assertNoErrors(Map<AssertionError, Object[]> errors) { | |||
if (!errors.isEmpty()) { | |||
StringBuilder sb = new StringBuilder("Exceptions: "); | |||
for (Entry<AssertionError, Object[]> entry : errors.entrySet()) { | |||
sb.append("\n"); | |||
for (Object value : entry.getValue()) { | |||
sb.append(value); | |||
sb.append(" - "); | |||
} | |||
sb.append(entry.getKey().getMessage()); | |||
} | |||
Assert.fail(sb.toString()); | |||
} | |||
} | |||
private int getExpectedInitialHeight(Object gridHeight) { | |||
int result = 0; | |||
if (GridHeight.UNDEFINED.equals(gridHeight) | |||
|| GridHeight.ROW3.equals(gridHeight)) { | |||
result = 81; | |||
} else if (GridHeight.FULL.equals(gridHeight)) { | |||
// pre-existing issue | |||
result = 400; | |||
} | |||
return result; | |||
} | |||
private int getExpectedOpenedHeight(Object gridHeight, | |||
Object detailsRowHeight) { | |||
int result = 0; | |||
if (GridHeight.UNDEFINED.equals(gridHeight)) { | |||
if (GridHeight.PX100.equals(detailsRowHeight)) { | |||
result = 182; | |||
} else if (GridHeight.FULL.equals(detailsRowHeight)) { | |||
if (isIE8orIE9()) { | |||
// pre-existing bug in IE8 & IE9, details row doesn't layout | |||
// itself properly | |||
result = 100; | |||
} else { | |||
result = 131; | |||
} | |||
} else if (GridHeight.UNDEFINED.equals(detailsRowHeight)) { | |||
result = 100; | |||
} | |||
} else if (GridHeight.ROW3.equals(gridHeight) | |||
|| GridHeight.FULL.equals(gridHeight)) { | |||
result = getExpectedInitialHeight(gridHeight); | |||
} | |||
return result; | |||
} | |||
private boolean isIE8orIE9() { | |||
DesiredCapabilities desiredCapabilities = getDesiredCapabilities(); | |||
return BrowserUtil.isIE8(desiredCapabilities) | |||
|| BrowserUtil.isIE(desiredCapabilities, 9); | |||
} | |||
private void assertGridHeight(int expected, int actual) { | |||
assertThat("Unexpected Grid Height", (double) actual, | |||
closeTo(expected, 1)); | |||
} | |||
} |