@@ -51,6 +51,37 @@ import com.vaadin.shared.util.SharedUtil; | |||
*/ | |||
public class WidgetUtil { | |||
/** | |||
* Simple object to store another object. | |||
* | |||
* @param <T> | |||
* the object type to store | |||
* @since | |||
*/ | |||
public static class Reference<T> { | |||
T reference = null; | |||
/** | |||
* Gets the current object. | |||
* | |||
* @return the stored object | |||
*/ | |||
public T get() { | |||
return reference; | |||
} | |||
/** | |||
* Sets the current object. | |||
* | |||
* @param reference | |||
* the object to store | |||
*/ | |||
public void set(T reference) { | |||
this.reference = reference; | |||
} | |||
} | |||
/** | |||
* Helper method for debugging purposes. | |||
* | |||
@@ -778,7 +809,7 @@ public class WidgetUtil { | |||
com.google.gwt.dom.client.Element el, String p) | |||
/*-{ | |||
try { | |||
if (el.currentStyle) { | |||
// IE | |||
return el.currentStyle[p]; | |||
@@ -793,7 +824,7 @@ public class WidgetUtil { | |||
} catch (e) { | |||
return ""; | |||
} | |||
}-*/; | |||
/** | |||
@@ -807,7 +838,7 @@ public class WidgetUtil { | |||
try { | |||
el.focus(); | |||
} catch (e) { | |||
} | |||
}-*/; | |||
@@ -1158,7 +1189,7 @@ public class WidgetUtil { | |||
if ($wnd.document.activeElement) { | |||
return $wnd.document.activeElement; | |||
} | |||
return null; | |||
}-*/; | |||
@@ -1229,11 +1260,11 @@ public class WidgetUtil { | |||
/*-{ | |||
var top = elem.offsetTop; | |||
var height = elem.offsetHeight; | |||
if (elem.parentNode != elem.offsetParent) { | |||
top -= elem.parentNode.offsetTop; | |||
} | |||
var cur = elem.parentNode; | |||
while (cur && (cur.nodeType == 1)) { | |||
if (top < cur.scrollTop) { | |||
@@ -1242,12 +1273,12 @@ public class WidgetUtil { | |||
if (top + height > cur.scrollTop + cur.clientHeight) { | |||
cur.scrollTop = (top + height) - cur.clientHeight; | |||
} | |||
var offsetTop = cur.offsetTop; | |||
if (cur.parentNode != cur.offsetParent) { | |||
offsetTop -= cur.parentNode.offsetTop; | |||
} | |||
top += offsetTop - cur.scrollTop; | |||
cur = cur.parentNode; | |||
} | |||
@@ -1696,7 +1727,7 @@ public class WidgetUtil { | |||
} | |||
var heightWithoutBorder = cloneElement.offsetHeight; | |||
parentElement.removeChild(cloneElement); | |||
return heightWithBorder - heightWithoutBorder; | |||
} | |||
}-*/; | |||
@@ -1873,7 +1904,7 @@ public class WidgetUtil { | |||
* ancestors has the style {@code display: none} applied. | |||
* | |||
* @param element | |||
* the element to test for visibility | |||
* the element to test for visibility | |||
* @return {@code true} if the element is displayed, {@code false} otherwise | |||
* @since 8.3.2 | |||
*/ |
@@ -66,12 +66,7 @@ public class EditorConnector extends AbstractExtensionConnector { | |||
public void bind(final int rowIndex) { | |||
// call this deferred to avoid issues with editing on init | |||
Scheduler.get().scheduleDeferred(() -> { | |||
currentEditedRow = rowIndex; | |||
// might need to wait for available data, | |||
// if data is available, ensureAvailability will immediately trigger the handler anyway, | |||
// so no need for alternative "immediately available" logic | |||
waitingForAvailableData = true; | |||
getParent().getDataSource().ensureAvailability(rowIndex, 1); | |||
getParent().getWidget().editRow(rowIndex); | |||
}); | |||
} | |||
@@ -218,13 +213,6 @@ public class EditorConnector extends AbstractExtensionConnector { | |||
protected void extend(ServerConnector target) { | |||
Grid<JsonObject> grid = getParent().getWidget(); | |||
grid.getEditor().setHandler(new CustomEditorHandler()); | |||
grid.addDataAvailableHandler((event) -> { | |||
Range range = event.getAvailableRows(); | |||
if (waitingForAvailableData && currentEditedRow != null && range.contains(currentEditedRow)) { | |||
getParent().getWidget().editRow(currentEditedRow); | |||
waitingForAvailableData = false; | |||
} | |||
}); | |||
} | |||
@Override |
@@ -80,6 +80,7 @@ import com.vaadin.client.BrowserInfo; | |||
import com.vaadin.client.DeferredWorker; | |||
import com.vaadin.client.Focusable; | |||
import com.vaadin.client.WidgetUtil; | |||
import com.vaadin.client.WidgetUtil.Reference; | |||
import com.vaadin.client.data.DataChangeHandler; | |||
import com.vaadin.client.data.DataSource; | |||
import com.vaadin.client.data.DataSource.RowHandle; | |||
@@ -1437,7 +1438,6 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, | |||
private String styleName = null; | |||
private HandlerRegistration hScrollHandler; | |||
private HandlerRegistration vScrollHandler; | |||
private final Button saveButton; | |||
private final Button cancelButton; | |||
@@ -1680,20 +1680,10 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, | |||
} | |||
state = State.ACTIVATING; | |||
final Escalator escalator = grid.getEscalator(); | |||
if (escalator.getVisibleRowRange().contains(rowIndex)) { | |||
show(rowIndex, columnIndexDOM); | |||
} else { | |||
vScrollHandler = grid.addScrollHandler(event -> { | |||
if (escalator.getVisibleRowRange().contains(rowIndex)) { | |||
show(rowIndex, columnIndexDOM); | |||
vScrollHandler.removeHandler(); | |||
} | |||
}); | |||
grid.scrollToRow(rowIndex, | |||
isBuffered() ? ScrollDestination.MIDDLE | |||
: ScrollDestination.ANY); | |||
} | |||
grid.scrollToRow(rowIndex, | |||
isBuffered() ? ScrollDestination.MIDDLE | |||
: ScrollDestination.ANY, | |||
() -> show(rowIndex, columnIndexDOM)); | |||
} | |||
/** | |||
@@ -7383,6 +7373,45 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, | |||
: GridConstants.DEFAULT_PADDING); | |||
} | |||
/** | |||
* Helper method for making sure desired row is visible and it is properly | |||
* rendered. | |||
* | |||
* @param rowIndex | |||
* the row to look for | |||
* @param destination | |||
* the desired scroll destination | |||
* @param callback | |||
* the callback command to execute when row is available | |||
* @since | |||
*/ | |||
public void scrollToRow(int rowIndex, ScrollDestination destination, | |||
Runnable callback) { | |||
waitUntilVisible(rowIndex, destination, () -> { | |||
Reference<HandlerRegistration> registration = new Reference<>(); | |||
registration.set(addDataAvailableHandler(event -> { | |||
if (event.getAvailableRows().contains(rowIndex)) { | |||
registration.get().removeHandler(); | |||
callback.run(); | |||
} | |||
})); | |||
}); | |||
} | |||
/** | |||
* Helper method for making sure desired row is visible and it is properly | |||
* rendered. | |||
* | |||
* @param rowIndex | |||
* the row to look for | |||
* @param whenRendered | |||
* the callback command to execute when row is available | |||
* @since | |||
*/ | |||
public void scrollToRow(int rowIndex, Runnable whenRendered) { | |||
scrollToRow(rowIndex, ScrollDestination.ANY, whenRendered); | |||
} | |||
/** | |||
* Scrolls to a certain row using only user-specified parameters. | |||
* <p> | |||
@@ -7420,6 +7449,43 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, | |||
escalator.scrollToRowAndSpacer(rowIndex, destination, paddingPx); | |||
} | |||
/** | |||
* Helper method for scrolling and making sure row is visible. | |||
* | |||
* @param rowIndex | |||
* the row index to make visible | |||
* @param destination | |||
* the desired scroll destination | |||
* @param whenVisible | |||
* the callback method to call when row is visible | |||
*/ | |||
private void waitUntilVisible(int rowIndex, ScrollDestination destination, | |||
Runnable whenVisible) { | |||
final Escalator escalator = getEscalator(); | |||
if (escalator.getVisibleRowRange().contains(rowIndex)) { | |||
TableRowElement rowElement = escalator.getBody() | |||
.getRowElement(rowIndex); | |||
long bottomBorder = Math.round(WidgetUtil.getBorderBottomThickness( | |||
rowElement.getFirstChildElement()) + 0.5d); | |||
if (rowElement.getAbsoluteTop() >= escalator.getHeader() | |||
.getElement().getAbsoluteBottom() | |||
&& rowElement.getAbsoluteBottom() <= escalator.getFooter() | |||
.getElement().getAbsoluteTop() + bottomBorder) { | |||
whenVisible.run(); | |||
return; | |||
} | |||
} | |||
Reference<HandlerRegistration> registration = new Reference<>(); | |||
registration.set(addScrollHandler(event -> { | |||
if (escalator.getVisibleRowRange().contains(rowIndex)) { | |||
registration.get().removeHandler(); | |||
whenVisible.run(); | |||
} | |||
})); | |||
scrollToRow(rowIndex, destination); | |||
} | |||
/** | |||
* Scrolls to the beginning of the very first row. | |||
*/ |
@@ -0,0 +1,97 @@ | |||
package com.vaadin.tests.components.grid; | |||
import java.util.ArrayList; | |||
import com.vaadin.annotations.Widgetset; | |||
import com.vaadin.data.provider.ListDataProvider; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUI; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Grid; | |||
import com.vaadin.ui.TextField; | |||
@Widgetset("com.vaadin.DefaultWidgetSet") | |||
public class GridEditRow extends AbstractTestUI { | |||
private ArrayList<TestBean> items = new ArrayList<>(); | |||
private TestBean bean = null; | |||
private int counter = 0; | |||
@SuppressWarnings("unchecked") | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
Grid<TestBean> grid = new Grid<TestBean>(TestBean.class); | |||
grid.setDataProvider(new ListDataProvider<>(items)); | |||
grid.setWidth("100%"); | |||
grid.setHeight("400px"); | |||
grid.getEditor().setEnabled(true); | |||
grid.getColumn("name").setEditorComponent(new TextField()); | |||
grid.getColumn("value").setEditorComponent(new TextField()); | |||
getLayout().addComponent(new Button("Add", event -> { | |||
bean = new TestBean(); | |||
items.add(bean); | |||
grid.getDataProvider().refreshAll(); | |||
})); | |||
getLayout().addComponent(new Button("Add, Select & Edit", event -> { | |||
bean = new TestBean(); | |||
items.add(bean); | |||
grid.getDataProvider().refreshAll(); | |||
grid.select(bean); | |||
int row = ((ArrayList<TestBean>) ((ListDataProvider<TestBean>) grid | |||
.getDataProvider()).getItems()).indexOf(bean); | |||
grid.getEditor().editRow(row); | |||
})); | |||
getLayout().addComponent(new Button("Edit", event -> { | |||
int row = ((ArrayList<TestBean>) ((ListDataProvider<TestBean>) grid | |||
.getDataProvider()).getItems()).indexOf(bean); | |||
grid.getEditor().editRow(row); | |||
})); | |||
getLayout().addComponent(new Button("Select & Edit", event -> { | |||
grid.select(bean); | |||
int row = ((ArrayList<TestBean>) ((ListDataProvider<TestBean>) grid | |||
.getDataProvider()).getItems()).indexOf(bean); | |||
grid.getEditor().editRow(row); | |||
})); | |||
getLayout().addComponent(grid); | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return 10558; | |||
} | |||
@Override | |||
protected String getTestDescription() { | |||
return "Calling editRow shouldn't cause any other rows to be emptied."; | |||
} | |||
public class TestBean { | |||
private String name = "name" + counter; | |||
private String value = "value" + counter; | |||
public TestBean() { | |||
++counter; | |||
} | |||
public String getName() { | |||
return name; | |||
} | |||
public void setName(String inName) { | |||
name = inName; | |||
} | |||
public String getValue() { | |||
return value; | |||
} | |||
public void setValue(String inValue) { | |||
value = inValue; | |||
} | |||
} | |||
} |
@@ -0,0 +1,116 @@ | |||
/* | |||
* 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.CoreMatchers.is; | |||
import static org.hamcrest.CoreMatchers.not; | |||
import static org.hamcrest.MatcherAssert.assertThat; | |||
import static org.hamcrest.Matchers.greaterThan; | |||
import org.junit.Test; | |||
import com.vaadin.testbench.By; | |||
import com.vaadin.testbench.elements.ButtonElement; | |||
import com.vaadin.testbench.elements.GridElement; | |||
import com.vaadin.testbench.elements.GridElement.GridEditorElement; | |||
import com.vaadin.testbench.parallel.TestCategory; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
/** | |||
* Tests for ensuring that the furthest away visible rows don't get emptied when | |||
* editRow is called, and that the editor doesn't open beyond the lower border | |||
* of the Grid. | |||
* | |||
*/ | |||
@TestCategory("grid") | |||
public class GridEditRowTest extends MultiBrowserTest { | |||
private GridElement grid; | |||
private ButtonElement addButton; | |||
private ButtonElement editButton; | |||
@Override | |||
public void setup() throws Exception { | |||
super.setup(); | |||
openTestURL(); | |||
grid = $(GridElement.class).first(); | |||
addButton = $(ButtonElement.class).caption("Add").first(); | |||
editButton = $(ButtonElement.class).caption("Edit").first(); | |||
} | |||
public void addRows(int count) { | |||
for (int i = 0; i < count; ++i) { | |||
addButton.click(); | |||
} | |||
} | |||
public void editLastRow() { | |||
editButton.click(); | |||
} | |||
private void assertRowContents(int rowIndex) { | |||
assertThat(grid.getCell(rowIndex, 0).getText(), is("name" + rowIndex)); | |||
} | |||
private void assertEditorWithinGrid() { | |||
GridEditorElement editor = grid.getEditor(); | |||
assertThat(editor.getLocation().y + editor.getSize().height, | |||
not(greaterThan(grid.getLocation().y + grid.getSize().height))); | |||
} | |||
@Test | |||
public void testEditWhenAllRowsVisible() { | |||
addRows(7); | |||
assertRowContents(0); | |||
editLastRow(); | |||
assertRowContents(0); | |||
waitForElementVisible(By.className("v-grid-editor")); | |||
assertEditorWithinGrid(); | |||
} | |||
@Test | |||
public void testEditWhenSomeRowsNotVisible() { | |||
addRows(11); | |||
assertRowContents(3); | |||
editLastRow(); | |||
waitForElementVisible(By.className("v-grid-editor")); | |||
assertRowContents(3); | |||
assertEditorWithinGrid(); | |||
} | |||
@Test | |||
public void testEditWhenSomeRowsOutsideOfCache() { | |||
addRows(100); | |||
assertRowContents(91); | |||
editLastRow(); | |||
waitForElementVisible(By.className("v-grid-editor")); | |||
assertRowContents(91); | |||
assertEditorWithinGrid(); | |||
} | |||
} |