import com.vaadin.event.SortOrderChangeEvent.SortOrderChangeNotifier;
import com.vaadin.server.AbstractClientConnector;
import com.vaadin.server.AbstractExtension;
-import com.vaadin.server.ErrorHandler;
import com.vaadin.server.ErrorMessage;
import com.vaadin.server.JsonCodec;
import com.vaadin.server.KeyMapper;
public class Grid extends AbstractComponent implements SelectionChangeNotifier,
SortOrderChangeNotifier, SelectiveRenderer {
+ /**
+ * Custom field group that allows finding property types before an item has
+ * been bound.
+ */
+ private final class CustomFieldGroup extends FieldGroup {
+ @Override
+ protected Class<?> getPropertyType(Object propertyId)
+ throws BindException {
+ if (getItemDataSource() == null) {
+ return datasource.getType(propertyId);
+ } else {
+ return super.getPropertyType(propertyId);
+ }
+ }
+ }
+
/**
* Selection modes representing built-in {@link SelectionModel
* SelectionModels} that come bundled with {@link Grid}.
private final Footer footer = new Footer(this);
private Object editedItemId = null;
- private FieldGroup editorRowFieldGroup = new FieldGroup();
- private HashSet<Object> uneditableProperties = new HashSet<Object>();
- private ErrorHandler editorRowErrorHandler;
+ private FieldGroup editorRowFieldGroup = new CustomFieldGroup();
private CellStyleGenerator cellStyleGenerator;
}
private void handleError(Exception e) {
- ErrorHandler handler = getEditorRowErrorHandler();
- if (handler == null) {
- handler = com.vaadin.server.ErrorEvent
- .findErrorHandler(Grid.this);
- }
- handler.error(new ConnectorErrorEvent(Grid.this, e));
+ com.vaadin.server.ErrorEvent.findErrorHandler(Grid.this).error(
+ new ConnectorErrorEvent(Grid.this, e));
}
});
}
* The property id of column to be removed
*/
public void removeColumn(Object propertyId) {
+ setEditorRowField(propertyId, null);
header.removeColumn(propertyId);
footer.removeColumn(propertyId);
Column column = columns.remove(propertyId);
return editedItemId != null;
}
- /**
- * Sets a property editable or not.
- * <p>
- * In order for a user to edit a particular value with a Field, it needs to
- * be both non-readonly and editable.
- * <p>
- * The difference between read-only and uneditable is that the read-only
- * state is propagated back into the property, while the editable property
- * is internal metadata for the editor row.
- *
- * @param propertyId
- * the id of the property to set as editable state
- * @param editable
- * whether or not {@code propertyId} chould be editable
- */
- public void setPropertyEditable(Object propertyId, boolean editable) {
- checkPropertyExists(propertyId);
- if (getEditorRowField(propertyId) != null) {
- getEditorRowField(propertyId).setReadOnly(!editable);
- }
- if (editable) {
- uneditableProperties.remove(propertyId);
- } else {
- uneditableProperties.add(propertyId);
- }
- }
-
- /**
- * Checks whether a property is editable through the editor row.
- * <p>
- * This only checks whether the property is configured as uneditable in the
- * editor row. The property's or field's readonly status will ultimately
- * decide whether the value can be edited or not.
- *
- * @param propertyId
- * the id of the property to check for editable status
- * @return <code>true</code> iff the property is editable according to this
- * editor row
- */
- public boolean isPropertyEditable(Object propertyId) {
- checkPropertyExists(propertyId);
- return !uneditableProperties.contains(propertyId);
- }
-
- private void checkPropertyExists(Object propertyId) {
- if (!getContainerDataSource().getContainerPropertyIds().contains(
- propertyId)) {
- throw new IllegalArgumentException("Property with id " + propertyId
- + " is not in the current Container");
+ private void checkColumnExists(Object propertyId) {
+ if (getColumn(propertyId) == null) {
+ throw new IllegalArgumentException(
+ "There is no column with the property id " + propertyId);
}
}
/**
- * Gets the field component that represents a property in the editor row. If
- * the property is not yet bound to a field, null is returned.
+ * Gets the field component that represents a property in the editor row.
* <p>
* When {@link #editItem(Object) editItem} is called, fields are
* automatically created and bound for any unbound properties.
+ * <p>
+ * Getting a field before the editor row has been opened depends on special
+ * support from the {@link FieldGroup} in use. Using this method with a
+ * user-provided <code>FieldGroup</code> might cause {@link BindException}
+ * to be thrown.
*
* @param propertyId
* the property id of the property for which to find the field
- * @return the bound field or null if not bound
+ * @return the bound field, never null
+ *
+ * @throws IllegalArgumentException
+ * if there is no column for the provided property id
+ * @throws BindException
+ * if no field has been configured and there is a problem
+ * building or binding
*/
public Field<?> getEditorRowField(Object propertyId) {
- return editorRowFieldGroup.getField(propertyId);
+ checkColumnExists(propertyId);
+
+ Field<?> editor = editorRowFieldGroup.getField(propertyId);
+ if (editor == null) {
+ editor = editorRowFieldGroup.buildAndBind(propertyId);
+ }
+
+ if (editor.getParent() != Grid.this) {
+ assert editor.getParent() == null;
+ editor.setParent(this);
+ }
+ return editor;
}
/**
editorRowFieldGroup.setItemDataSource(item);
editedItemId = itemId;
- for (Object propertyId : item.getItemPropertyIds()) {
+ for (Column column : getColumns()) {
+ Object propertyId = column.getColumnProperty();
- final Field<?> editor;
- if (editorRowFieldGroup.getUnboundPropertyIds()
- .contains(propertyId)) {
- editor = editorRowFieldGroup.buildAndBind(propertyId);
- } else {
- editor = editorRowFieldGroup.getField(propertyId);
- }
+ Field<?> editor = getEditorRowField(propertyId);
getColumn(propertyId).getState().editorConnector = editor;
-
- if (editor != null) {
- editor.setReadOnly(!isPropertyEditable(propertyId));
-
- if (editor.getParent() != Grid.this) {
- assert editor.getParent() == null;
- editor.setParent(Grid.this);
- }
- }
}
}
/**
- * Binds the field with the given propertyId from the current item. If an
- * item has not been set then the binding is postponed until the item is set
- * using {@link #editItem(Object)}.
+ * Binds the field to the given propertyId. If an item has not been set,
+ * then the binding is postponed until the item is set using
+ * {@link #editItem(Object)}.
* <p>
- * This method also adds validators when applicable.
- * <p>
- * <em>Note:</em> This is a pass-through call to the backing field group.
+ * Setting the field to <code>null</code> clears any previously set field,
+ * causing a new field to be created the next time the editor row is opened.
*
* @param field
* The field to bind
* @param propertyId
- * The propertyId to bind to the field
- * @throws BindException
- * If the property id is already bound to another field by this
- * field binder
+ * The propertyId to bind the field to
*/
- public void bindEditorRowField(Object propertyId, Field<?> field)
- throws BindException {
- editorRowFieldGroup.bind(field, propertyId);
+ public void setEditorRowField(Object propertyId, Field<?> field) {
+ checkColumnExists(propertyId);
+
+ Field<?> oldField = editorRowFieldGroup.getField(propertyId);
+ if (oldField != null) {
+ editorRowFieldGroup.unbind(oldField);
+ oldField.setParent(null);
+ }
+
+ if (field != null) {
+ field.setParent(this);
+ editorRowFieldGroup.bind(field, propertyId);
+ }
}
/**
}
editedItemId = null;
- editorRowFieldGroup = new FieldGroup();
- uneditableProperties = new HashSet<Object>();
+ editorRowFieldGroup = new CustomFieldGroup();
}
/**
* Gets a collection of all fields bound to the editor row of this grid.
* <p>
- * All non-editable fields (either readonly or uneditable) are in read-only
- * mode.
- * <p>
* When {@link #editItem(Object) editItem} is called, fields are
* automatically created and bound to any unbound properties.
*
* @return a collection of all the fields bound to this editor row
*/
Collection<Field<?>> getEditorRowFields() {
- return editorRowFieldGroup.getFields();
+ Collection<Field<?>> fields = editorRowFieldGroup.getFields();
+ assert allAttached(fields);
+ return fields;
+ }
+
+ private boolean allAttached(Collection<? extends Component> components) {
+ for (Component component : components) {
+ if (component.getParent() != this) {
+ return false;
+ }
+ }
+ return true;
}
/**
public void setEditorRowFieldFactory(FieldGroupFieldFactory fieldFactory) {
editorRowFieldGroup.setFieldFactory(fieldFactory);
}
-
- /**
- * Returns the error handler of this editor row.
- *
- * @return the error handler or null if there is no dedicated error handler
- *
- * @see #setEditorRowErrorHandler(ErrorHandler)
- * @see ClientConnector#getErrorHandler()
- */
- public ErrorHandler getEditorRowErrorHandler() {
- return editorRowErrorHandler;
- }
-
- /**
- * Sets the error handler for this editor row. The error handler is invoked
- * for exceptions thrown while processing client requests; specifically when
- * {@link #saveEditorRow()} triggered by the client throws a
- * CommitException. If the error handler is not set, one is looked up via
- * Grid.
- *
- * @param errorHandler
- * the error handler to use
- *
- * @see ClientConnector#setErrorHandler(ErrorHandler)
- * @see ErrorEvent#findErrorHandler(ClientConnector)
- */
- public void setEditorRowErrorHandler(ErrorHandler errorHandler) {
- editorRowErrorHandler = errorHandler;
- }
-
- /**
- * Builds a field using the given caption and binds it to the given property
- * id using the field binder. Ensures the new field is of the given type.
- * <p>
- * <em>Note:</em> This is a pass-through call to the backing field group.
- *
- * @param propertyId
- * The property id to bind to. Must be present in the field
- * finder
- * @param fieldType
- * The type of field that we want to create
- * @throws BindException
- * If the field could not be created
- * @return The created and bound field. Can be any type of {@link Field} .
- */
- public <T extends Field<?>> T buildAndBind(Object propertyId,
- Class<T> fieldType) throws BindException {
- return editorRowFieldGroup.buildAndBind(null, propertyId, fieldType);
- }
-
}
assertFalse(oldFieldGroup == grid.getEditorRowFieldGroup());
}
- @Test
- public void propertyUneditable() throws Exception {
- assertTrue(grid.isPropertyEditable(PROPERTY_NAME));
- grid.setPropertyEditable(PROPERTY_NAME, false);
- assertFalse(grid.isPropertyEditable(PROPERTY_NAME));
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void nonexistentPropertyUneditable() throws Exception {
- grid.setPropertyEditable(new Object(), false);
- }
-
@Test(expected = IllegalStateException.class)
public void disabledEditItem() throws Exception {
grid.editItem(ITEM_ID);
@Test
public void getFieldWithoutItem() throws Exception {
grid.setEditorRowEnabled(true);
- assertNull(grid.getEditorRowField(PROPERTY_NAME));
- }
-
- @Test
- public void getFieldAfterReSettingFieldAsEditable() throws Exception {
- startEdit();
-
- grid.setPropertyEditable(PROPERTY_NAME, false);
- grid.setPropertyEditable(PROPERTY_NAME, true);
assertNotNull(grid.getEditorRowField(PROPERTY_NAME));
}
- @Test
- public void isEditable() {
- assertTrue(grid.isPropertyEditable(PROPERTY_NAME));
- }
-
- @Test
- public void isUneditable() {
- grid.setPropertyEditable(PROPERTY_NAME, false);
- assertFalse(grid.isPropertyEditable(PROPERTY_NAME));
- }
-
- @Test
- public void isEditableAgain() {
- grid.setPropertyEditable(PROPERTY_NAME, false);
- grid.setPropertyEditable(PROPERTY_NAME, true);
- assertTrue(grid.isPropertyEditable(PROPERTY_NAME));
- }
-
- @Test
- public void isUneditableAgain() {
- grid.setPropertyEditable(PROPERTY_NAME, false);
- grid.setPropertyEditable(PROPERTY_NAME, true);
- grid.setPropertyEditable(PROPERTY_NAME, false);
- assertFalse(grid.isPropertyEditable(PROPERTY_NAME));
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void isNonexistentEditable() {
- grid.isPropertyEditable(new Object());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void setNonexistentUneditable() {
- grid.setPropertyEditable(new Object(), false);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void setNonexistentEditable() {
- grid.setPropertyEditable(new Object(), true);
- }
-
@Test
public void customBinding() {
TextField textField = new TextField();
- grid.bindEditorRowField(PROPERTY_NAME, textField);
+ grid.setEditorRowField(PROPERTY_NAME, textField);
startEdit();
}
@Test
- public void fieldIsReadonlyWhenPropertyIsNotEditable() {
- startEdit();
-
- grid.setPropertyEditable(PROPERTY_NAME, false);
+ public void columnRemoved() {
Field<?> field = grid.getEditorRowField(PROPERTY_NAME);
- assertTrue(field.isReadOnly());
+
+ assertSame("field should be attached to grid.", grid, field.getParent());
+
+ grid.removeColumn(PROPERTY_NAME);
+
+ assertNull("field should be detached from grid.", field.getParent());
+ }
+
+ @Test
+ public void setFieldAgain() {
+ TextField field = new TextField();
+ grid.setEditorRowField(PROPERTY_NAME, field);
+
+ field = new TextField();
+ grid.setEditorRowField(PROPERTY_NAME, field);
+
+ assertSame("new field should be used.", field,
+ grid.getEditorRowField(PROPERTY_NAME));
}
private void startEdit() {
--- /dev/null
+/*
+ * Copyright 2000-2014 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.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.interactions.Actions;
+
+import com.vaadin.testbench.elements.GridElement;
+import com.vaadin.testbench.elements.GridElement.GridCellElement;
+import com.vaadin.testbench.elements.NotificationElement;
+import com.vaadin.testbench.elements.PasswordFieldElement;
+import com.vaadin.tests.annotations.TestCategory;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+@TestCategory("grid")
+public class EditorRowUITest extends MultiBrowserTest {
+
+ @Test
+ public void testEditorRow() {
+ setDebug(true);
+ openTestURL();
+
+ assertFalse("Sanity check",
+ isElementPresent(PasswordFieldElement.class));
+
+ openEditorRow(5);
+ assertFalse("Remove this when grid is fixed",
+ isElementPresent(PasswordFieldElement.class));
+ new Actions(getDriver()).sendKeys(Keys.ESCAPE).perform();
+
+ openEditorRow(10);
+
+ assertTrue("Edtor row should be opened with a password field",
+ isElementPresent(PasswordFieldElement.class));
+
+ assertFalse("Notification was present",
+ isElementPresent(NotificationElement.class));
+ }
+
+ private void openEditorRow(int rowIndex) {
+ GridElement grid = $(GridElement.class).first();
+
+ GridCellElement cell = grid.getCell(rowIndex, 1);
+
+ new Actions(driver).moveToElement(cell).doubleClick().build().perform();
+ }
+
+}