*/
@SuppressWarnings("serial")
public abstract class PropertyFormatter extends AbstractProperty implements
- Property.ValueChangeListener, Property.ReadOnlyStatusChangeListener {
+ Property.Viewer, Property.ValueChangeListener,
+ Property.ReadOnlyStatusChangeListener {
/** Datasource that stores the actual value. */
Property dataSource;
*/
@Override
public String toString() {
- Object value = dataSource == null ? false : dataSource.getValue();
+ if (dataSource == null) {
+ return null;
+ }
+ Object value = dataSource.getValue();
if (value == null) {
return null;
}
}
/** Reflects the read-only status of the datasource. */
+ @Override
public boolean isReadOnly() {
return dataSource == null ? false : dataSource.isReadOnly();
}
* @param newStatus
* the new read-only status of the Property.
*/
+ @Override
public void setReadOnly(boolean newStatus) {
if (dataSource != null) {
dataSource.setReadOnly(newStatus);
}
} else {
try {
- dataSource.setValue(parse((String) newValue));
+ dataSource.setValue(parse(newValue.toString()));
if (!newValue.equals(toString())) {
fireValueChange();
}
+ } catch (ConversionException e) {
+ throw e;
} catch (Exception e) {
- if (e instanceof ConversionException) {
- throw (ConversionException) e;
- } else {
- throw new ConversionException(e);
- }
+ throw new ConversionException(e);
}
}
}
if (this.width.equals(width)) {
return;
}
+ if (!isVisible()) {
+ /*
+ * Do not update size when the table is hidden as all column widths
+ * will be set to zero and they won't be recalculated when the table
+ * is set visible again (until the size changes again)
+ */
+ return;
+ }
this.width = width;
if (width != null && !"".equals(width)) {
}
@Override
- /*
+ /**
* Width is set to the out-most element (v-window).
*
* This function should never be called with percentage values (it will
}
@Override
- /*
+ /**
* Height is set to the out-most element (v-window).
*
* This function should never be called with percentage values (it will
* throw an exception)
+ *
+ * @param height A CSS string specifying the new height of the window.
+ * An empty string or null clears the height and lets
+ * the browser to compute it based on the window contents.
*/
public void setHeight(String height) {
- this.height = height;
- if (!isAttached()) {
+ if (!isAttached() || (height == null
+ ? this.height == null
+ : height.equals(this.height))) {
return;
}
- if (height != null && !"".equals(height)) {
- DOM.setStyleAttribute(getElement(), "height", height);
- int pixels = getElement().getOffsetHeight() - getExtraHeight();
- if (pixels < MIN_CONTENT_AREA_HEIGHT) {
- pixels = MIN_CONTENT_AREA_HEIGHT;
- int rootHeight = pixels + getExtraHeight();
- DOM.setStyleAttribute(getElement(), "height", (rootHeight)
- + "px");
-
+ if (height == null || "".equals(height)) {
+ getElement().getStyle().clearHeight();
+ contentPanel.getElement().getStyle().clearHeight();
+ // Reset to default, the exact value does not actually
+ // matter as an undefined-height parent should not have
+ // a relative-height child anyway.
+ renderSpace.setHeight(MIN_CONTENT_AREA_HEIGHT);
+ } else {
+ getElement().getStyle().setProperty("height", height);
+ int contentHeight =
+ getElement().getOffsetHeight() - getExtraHeight();
+ if (contentHeight < MIN_CONTENT_AREA_HEIGHT) {
+ contentHeight = MIN_CONTENT_AREA_HEIGHT;
+ int rootHeight = contentHeight + getExtraHeight();
+ getElement().getStyle().setProperty(
+ "height", rootHeight + "px");
}
- renderSpace.setHeight(pixels);
- height = pixels + "px";
- contentPanel.getElement().getStyle().setProperty("height", height);
- updateShadowSizeAndPosition();
-
+ renderSpace.setHeight(contentHeight);
+ contentPanel.getElement().getStyle().setProperty(
+ "height", contentHeight + "px");
}
+ this.height = height;
+ updateShadowSizeAndPosition();
}
private int extraH = 0;
}
// Configures the field
- field.setPropertyDataSource(property);
+ bindPropertyToField(id, property, field);
// Register and attach the created field
addField(id, field);
final Field f = fieldFactory.createField(itemDatasource, id,
this);
if (f != null) {
- f.setPropertyDataSource(property);
+ bindPropertyToField(id, property, f);
addField(id, f);
}
}
}
}
+ /**
+ * Binds an item property to a field. The default behavior is to bind
+ * property straight to Field. If Property.Viewer type property (e.g.
+ * PropertyFormatter) is already set for field, the property is bound to
+ * that Property.Viewer.
+ *
+ * @param propertyId
+ * @param property
+ * @param field
+ * @since 6.7.3
+ */
+ protected void bindPropertyToField(final Object propertyId,
+ final Property property, final Field field) {
+ // check if field has a property that is Viewer set. In that case we
+ // expect developer has e.g. PropertyFormatter that he wishes to use and
+ // assign the property to the Viewer instead.
+ boolean hasFilterProperty = field.getPropertyDataSource() != null
+ && (field.getPropertyDataSource() instanceof Property.Viewer);
+ if (hasFilterProperty) {
+ ((Property.Viewer) field.getPropertyDataSource())
+ .setPropertyDataSource(property);
+ } else {
+ field.setPropertyDataSource(property);
+ }
+ }
+
/**
* Gets the layout of the form.
*
// Remember that we have made this association so we can remove
// it when the component is removed
associatedProperties.put(f, property);
- f.setPropertyDataSource(property);
+ bindPropertyToField(rowId, colId, property, f);
return f;
}
}
return formatPropertyValue(rowId, colId, property);
}
+ /**
+ * Binds an item property to a field generated by TableFieldFactory. The
+ * default behavior is to bind property straight to Field. If
+ * Property.Viewer type property (e.g. PropertyFormatter) is already set for
+ * field, the property is bound to that Property.Viewer.
+ *
+ * @param rowId
+ * @param colId
+ * @param property
+ * @param field
+ * @since 6.7.3
+ */
+ protected void bindPropertyToField(Object rowId, Object colId,
+ Property property, Field field) {
+ // check if field has a property that is Viewer set. In that case we
+ // expect developer has e.g. PropertyFormatter that he wishes to use and
+ // assign the property to the Viewer instead.
+ boolean hasFilterProperty = field.getPropertyDataSource() != null
+ && (field.getPropertyDataSource() instanceof Property.Viewer);
+ if (hasFilterProperty) {
+ ((Property.Viewer) field.getPropertyDataSource())
+ .setPropertyDataSource(property);
+ } else {
+ field.setPropertyDataSource(property);
+ }
+ }
+
/**
* Formats table cell property values. By default the property.toString()
* and return a empty string for null properties.
* <p>
* Note, that some due to historical reasons the name of the method is bit
* misleading. Some items may be partly or totally out of the viewport of
- * the table's scrollable area. Actully detecting rows which can be actually
- * seen by the end user may be problematic due to the client server
+ * the table's scrollable area. Actually detecting rows which can be
+ * actually seen by the end user may be problematic due to the client server
* architecture. Using {@link #getCurrentPageFirstItemId()} combined with
* {@link #getPageLength()} may produce good enough estimates in some
* situations.
* com.vaadin.event.dd.acceptcriteria.AcceptCriterion#accepts(com.vaadin
* .event.dd.DragAndDropEvent)
*/
+ @SuppressWarnings("unchecked")
public boolean accept(DragAndDropEvent dragEvent) {
AbstractSelectTargetDetails dropTargetData = (AbstractSelectTargetDetails) dragEvent
.getTargetDetails();
table = (Table) dragEvent.getTargetDetails().getTarget();
- ArrayList<Object> visibleItemIds = new ArrayList<Object>(
- table.getPageLength());
- visibleItemIds.size();
- Object id = table.getCurrentPageFirstItemId();
- for (int i = 0; i < table.getPageLength() && id != null; i++) {
- visibleItemIds.add(id);
- id = table.nextItemId(id);
- }
- allowedItemIds = getAllowedItemIds(dragEvent, table, visibleItemIds);
+ Collection<?> visibleItemIds = table.getVisibleItemIds();
+ allowedItemIds = getAllowedItemIds(dragEvent, table,
+ (Collection<Object>) visibleItemIds);
return allowedItemIds.contains(dropTargetData.getItemIdOver());
}
--- /dev/null
+package com.vaadin.tests.server;
+
+import java.util.Date;
+
+import junit.framework.TestCase;
+
+import org.junit.Test;
+
+import com.vaadin.data.util.ObjectProperty;
+import com.vaadin.data.util.PropertyFormatter;
+
+@SuppressWarnings("unchecked")
+public class TestPropertyFormatter extends TestCase {
+
+ class TestFormatter extends PropertyFormatter {
+
+ @Override
+ public String format(Object value) {
+ boolean isCorrectType = getExpectedClass().isAssignableFrom(
+ value.getClass());
+ assertTrue(isCorrectType);
+ return "FOO";
+ }
+
+ @Override
+ public Object parse(String formattedValue) throws Exception {
+ return getExpectedClass().newInstance();
+ }
+ };
+ @SuppressWarnings("rawtypes")
+ private Class expectedClass;
+
+ @SuppressWarnings("rawtypes")
+ private Class getExpectedClass() {
+ return expectedClass;
+ }
+
+ /**
+ * The object passed to format should be same as property's type.
+ * @throws IllegalAccessException
+ * @throws InstantiationException
+ */
+ @Test
+ @SuppressWarnings({ "rawtypes" })
+ public void testCorrectTypeForFormat() throws InstantiationException, IllegalAccessException {
+ Class[] testedTypes = new Class[] {Integer.class, Boolean.class, Double.class, String.class, Date.class};
+ Object[] testValues = new Object[] {new Integer(3), Boolean.FALSE, new Double(3.3), "bar", new Date()};
+
+ int i = 0;
+ for (Class class1 : testedTypes) {
+ expectedClass = class1;
+
+ TestFormatter formatter = new TestFormatter();
+
+ // Should just return null, without formatting
+ Object value = formatter.getValue();
+
+ // test with property which value is null
+ formatter.setPropertyDataSource(new ObjectProperty(null, expectedClass));
+ formatter.getValue(); // calls format
+
+ // test with a value
+ formatter.setPropertyDataSource(new ObjectProperty(testValues[i++], expectedClass));
+ formatter.getValue(); // calls format
+ }
+
+
+ }
+}
--- /dev/null
+package com.vaadin.tests.components.form;\r
+\r
+import com.vaadin.data.Item;\r
+import com.vaadin.data.util.BeanItem;\r
+import com.vaadin.data.util.PropertyFormatter;\r
+import com.vaadin.tests.components.TestBase;\r
+import com.vaadin.ui.AbstractField;\r
+import com.vaadin.ui.Button;\r
+import com.vaadin.ui.Component;\r
+import com.vaadin.ui.DefaultFieldFactory;\r
+import com.vaadin.ui.Field;\r
+import com.vaadin.ui.Form;\r
+import com.vaadin.ui.FormFieldFactory;\r
+\r
+public class FormWithPropertyFormatterConnected extends TestBase {\r
+ @Override\r
+ protected void setup() {\r
+ Form form2 = new Form();\r
+ form2.setFormFieldFactory(new FormFieldFactory() {\r
+\r
+ public Field createField(Item item, Object propertyId,\r
+ Component uiContext) {\r
+ AbstractField f = (AbstractField) DefaultFieldFactory.get()\r
+ .createField(item, propertyId, uiContext);\r
+ if (propertyId.equals("age")) {\r
+ f.setPropertyDataSource(new PropertyFormatter() {\r
+\r
+ @Override\r
+ public Object parse(String formattedValue)\r
+ throws Exception {\r
+ String str = formattedValue\r
+ .replaceAll("[^0-9.]", "");\r
+ if (formattedValue.toLowerCase().contains("months")) {\r
+ return Double.parseDouble(str) / 12;\r
+ }\r
+ return Double.parseDouble(str);\r
+ }\r
+\r
+ @Override\r
+ public String format(Object value) {\r
+ Double dValue = (Double) value;\r
+ if (dValue < 1) {\r
+ return ((int)(dValue * 12)) + " months";\r
+ }\r
+ return dValue + " years";\r
+ }\r
+ });\r
+ f.setImmediate(true);\r
+ }\r
+ return f;\r
+ }\r
+ });\r
+ form2.setItemDataSource(createItem());\r
+ \r
+ addComponent(form2);\r
+ addComponent(new Button("B"));\r
+ }\r
+\r
+ private Item createItem() {\r
+ return new BeanItem<Person>(new Person(0.5));\r
+ }\r
+\r
+ public class Person {\r
+ public Person(double age) {\r
+ super();\r
+ this.age = age;\r
+ }\r
+\r
+ public double getAge() {\r
+ return age;\r
+ }\r
+\r
+ public void setAge(double age) {\r
+ this.age = age;\r
+ }\r
+\r
+ private double age;\r
+ }\r
+\r
+ @Override\r
+ protected String getDescription() {\r
+ return "It should be possible to inject PropertyFormatter and similar classses to fields in form. The test app hooks formatter that displays age in years or months and also accepts value in both (years by default, months if mentioned in the field)";\r
+ }\r
+\r
+ @Override\r
+ protected Integer getTicketNumber() {\r
+ return null;\r
+ }\r
+\r
+}\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="" />
+<title>SubWindowWithUndefinedHeight</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">SubWindowWithUndefinedHeight</td></tr>
+</thead><tbody>
+<tr>
+ <td>open</td>
+ <td>/run/com.vaadin.tests.components.window.SubWindowWithUndefinedHeight?restartApplication</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>vaadin=runcomvaadintestscomponentswindowSubWindowWithUndefinedHeight::/VVerticalLayout[0]/ChildComponentContainer[2]/VButton[0]/domChild[0]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>screenCapture</td>
+ <td></td>
+ <td>initial-tab1</td>
+</tr>
+<tr>
+ <td>mouseClick</td>
+ <td>vaadin=runcomvaadintestscomponentswindowSubWindowWithUndefinedHeight::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/ChildComponentContainer[0]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[1]/domChild[0]/domChild[0]/domChild[0]</td>
+ <td>17,13</td>
+</tr>
+<tr>
+ <td>screenCapture</td>
+ <td></td>
+ <td>select-tab2</td>
+</tr>
+<tr>
+ <td>mouseClick</td>
+ <td>vaadin=runcomvaadintestscomponentswindowSubWindowWithUndefinedHeight::/VWindow[0]/FocusableScrollPanel[0]/VVerticalLayout[0]/ChildComponentContainer[0]/VTabsheet[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]/domChild[0]</td>
+ <td>8,9</td>
+</tr>
+<tr>
+ <td>screenCapture</td>
+ <td></td>
+ <td>select-tab1</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
--- /dev/null
+package com.vaadin.tests.components.window;
+
+import com.vaadin.tests.components.TestBase;
+import com.vaadin.ui.Window;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Table;
+import com.vaadin.ui.TabSheet;
+
+public class SubWindowWithUndefinedHeight extends TestBase {
+
+ @Override
+ protected String getDescription() {
+ return "Setting subwindow height to undefined after initial rendering does not update visual height";
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 7916;
+ }
+
+ @Override
+ protected void setup() {
+ final Window subwindow = new Window("subwindow");
+ subwindow.center();
+ subwindow.setSizeUndefined();
+ subwindow.getContent().setSizeUndefined();
+
+ final Button tabButton = new Button("A button");
+ tabButton.setCaption("Tab 1");
+ tabButton.setWidth("200px");
+
+ final Table table = new Table();
+ table.setCaption("tab 2");
+ table.setWidth("100%");
+ table.setHeight("100%");
+
+ final TabSheet tabsheet = new TabSheet();
+ tabsheet.addComponent(tabButton);
+ tabsheet.addComponent(table);
+ tabsheet.addListener(new TabSheet.SelectedTabChangeListener() {
+ public void selectedTabChange(
+ TabSheet.SelectedTabChangeEvent event) {
+ if (tabsheet.getSelectedTab() == tabButton) {
+ tabsheet.setSizeUndefined();
+ subwindow.getContent().setSizeUndefined();
+ subwindow.setSizeUndefined();
+ } else if (tabsheet.getSelectedTab() == table) {
+ subwindow.setWidth("500px");
+ subwindow.setHeight("500px");
+ subwindow.getContent().setSizeFull();
+ tabsheet.setSizeFull();
+ }
+ }
+ });
+ subwindow.addComponent(tabsheet);
+
+ Button button = new Button("click me", new Button.ClickListener() {
+ public void buttonClick(Button.ClickEvent event) {
+ getMainWindow().addWindow(subwindow);
+ }
+ });
+ getMainWindow().addComponent(button);
+ }
+}