--- /dev/null
- // FIXME: This makes tab sheet tabs go to 1px width on every update
- // and then back to original width
- // update width later, in updateTabScroller();
- DOM.setStyleAttribute(getWidget().tabs, "width", "1px");
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.client.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.terminal.gwt.client.ApplicationConnection;
+import com.vaadin.terminal.gwt.client.ComponentConnector;
+import com.vaadin.terminal.gwt.client.UIDL;
+import com.vaadin.ui.TabSheet;
+
+@Component(TabSheet.class)
+public class TabsheetConnector extends TabsheetBaseConnector implements
+ SimpleManagedLayout {
+
+ @Override
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+
+ if (isRealUpdate(uidl)) {
+ // Handle stylename changes before generics (might affect size
+ // calculations)
+ getWidget().handleStyleNames(uidl, getState());
+ }
+
+ super.updateFromUIDL(uidl, client);
+ if (!isRealUpdate(uidl)) {
+ return;
+ }
+
+ // tabs; push or not
+ if (!isUndefinedWidth()) {
+ DOM.setStyleAttribute(getWidget().tabs, "overflow", "hidden");
+ } else {
+ getWidget().showAllTabs();
+ DOM.setStyleAttribute(getWidget().tabs, "width", "");
+ DOM.setStyleAttribute(getWidget().tabs, "overflow", "visible");
+ getWidget().updateDynamicWidth();
+ }
+
+ if (!isUndefinedHeight()) {
+ // Must update height after the styles have been set
+ getWidget().updateContentNodeHeight();
+ getWidget().updateOpenTabSize();
+ }
+
+ getWidget().iLayout();
+
+ // Re run relative size update to ensure optimal scrollbars
+ // TODO isolate to situation that visible tab has undefined height
+ try {
+ client.handleComponentRelativeSize(getWidget().tp
+ .getWidget(getWidget().tp.getVisibleWidget()));
+ } catch (Exception e) {
+ // Ignore, most likely empty tabsheet
+ }
+
+ getWidget().waitingForResponse = false;
+ }
+
+ @Override
+ protected Widget createWidget() {
+ return GWT.create(VTabsheet.class);
+ }
+
+ @Override
+ public VTabsheet getWidget() {
+ return (VTabsheet) super.getWidget();
+ }
+
+ public void updateCaption(ComponentConnector component) {
+ /* Tabsheet does not render its children's captions */
+ }
+
+ public void layout() {
+ VTabsheet tabsheet = getWidget();
+
+ tabsheet.updateContentNodeHeight();
+
+ if (isUndefinedWidth()) {
+ tabsheet.contentNode.getStyle().setProperty("width", "");
+ } else {
+ int contentWidth = tabsheet.getOffsetWidth()
+ - tabsheet.getContentAreaBorderWidth();
+ if (contentWidth < 0) {
+ contentWidth = 0;
+ }
+ tabsheet.contentNode.getStyle().setProperty("width",
+ contentWidth + "px");
+ }
+
+ tabsheet.updateOpenTabSize();
+ if (isUndefinedWidth()) {
+ tabsheet.updateDynamicWidth();
+ }
+
+ tabsheet.iLayout();
+
+ }
+
+}
--- /dev/null
- getWidget().multiSelectMode = uidl
- .getIntAttribute("multiselectmode");
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.terminal.gwt.client.ui;
+
+import java.util.Iterator;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.terminal.gwt.client.AbstractFieldState;
+import com.vaadin.terminal.gwt.client.ApplicationConnection;
++import com.vaadin.terminal.gwt.client.BrowserInfo;
+import com.vaadin.terminal.gwt.client.Paintable;
+import com.vaadin.terminal.gwt.client.TooltipInfo;
+import com.vaadin.terminal.gwt.client.UIDL;
+import com.vaadin.terminal.gwt.client.ui.VTree.TreeNode;
+import com.vaadin.ui.Tree;
+
+@Component(Tree.class)
+public class TreeConnector extends AbstractComponentConnector implements
+ Paintable {
+
+ public static final String ATTRIBUTE_NODE_STYLE = "style";
+ public static final String ATTRIBUTE_NODE_CAPTION = "caption";
+ public static final String ATTRIBUTE_NODE_ICON = "icon";
+
+ public static final String ATTRIBUTE_ACTION_CAPTION = "caption";
+ public static final String ATTRIBUTE_ACTION_ICON = ATTRIBUTE_NODE_ICON;
+
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+ if (!isRealUpdate(uidl)) {
+ return;
+ }
+
+ getWidget().rendering = true;
+
+ getWidget().client = client;
+
+ if (uidl.hasAttribute("partialUpdate")) {
+ handleUpdate(uidl);
+ getWidget().rendering = false;
+ return;
+ }
+
+ getWidget().paintableId = uidl.getId();
+
+ getWidget().immediate = getState().isImmediate();
+
+ getWidget().disabled = !isEnabled();
+ getWidget().readonly = isReadOnly();
+
+ getWidget().dragMode = uidl.hasAttribute("dragMode") ? uidl
+ .getIntAttribute("dragMode") : 0;
+
+ getWidget().isNullSelectionAllowed = uidl
+ .getBooleanAttribute("nullselect");
+
+ if (uidl.hasAttribute("alb")) {
+ getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb");
+ }
+
+ getWidget().body.clear();
+ // clear out any references to nodes that no longer are attached
+ getWidget().clearNodeToKeyMap();
+ TreeNode childTree = null;
+ UIDL childUidl = null;
+ for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
+ childUidl = (UIDL) i.next();
+ if ("actions".equals(childUidl.getTag())) {
+ updateActionMap(childUidl);
+ continue;
+ } else if ("-ac".equals(childUidl.getTag())) {
+ getWidget().updateDropHandler(childUidl);
+ continue;
+ }
+ childTree = getWidget().new TreeNode();
+ updateNodeFromUIDL(childTree, childUidl);
+ getWidget().body.add(childTree);
+ childTree.addStyleDependentName("root");
+ childTree.childNodeContainer.addStyleDependentName("root");
+ }
+ if (childTree != null && childUidl != null) {
+ boolean leaf = !childUidl.getTag().equals("node");
+ childTree.addStyleDependentName(leaf ? "leaf-last" : "last");
+ childTree.childNodeContainer.addStyleDependentName("last");
+ }
+ final String selectMode = uidl.getStringAttribute("selectmode");
+ getWidget().selectable = !"none".equals(selectMode);
+ getWidget().isMultiselect = "multi".equals(selectMode);
+
+ if (getWidget().isMultiselect) {
++ if (BrowserInfo.get().isTouchDevice()) {
++ // Always use the simple mode for touch devices that do not have
++ // shift/ctrl keys (#8595)
++ getWidget().multiSelectMode = VTree.MULTISELECT_MODE_SIMPLE;
++ } else {
++ getWidget().multiSelectMode = uidl
++ .getIntAttribute("multiselectmode");
++ }
+ }
+
+ getWidget().selectedIds = uidl.getStringArrayVariableAsSet("selected");
+
+ // Update lastSelection and focusedNode to point to *actual* nodes again
+ // after the old ones have been cleared from the body. This fixes focus
+ // and keyboard navigation issues as described in #7057 and other
+ // tickets.
+ if (getWidget().lastSelection != null) {
+ getWidget().lastSelection = getWidget().getNodeByKey(
+ getWidget().lastSelection.key);
+ }
+ if (getWidget().focusedNode != null) {
+ getWidget().setFocusedNode(
+ getWidget().getNodeByKey(getWidget().focusedNode.key));
+ }
+
+ if (getWidget().lastSelection == null
+ && getWidget().focusedNode == null
+ && !getWidget().selectedIds.isEmpty()) {
+ getWidget().setFocusedNode(
+ getWidget().getNodeByKey(
+ getWidget().selectedIds.iterator().next()));
+ getWidget().focusedNode.setFocused(false);
+ }
+
+ getWidget().rendering = false;
+
+ }
+
+ @Override
+ protected Widget createWidget() {
+ return GWT.create(VTree.class);
+ }
+
+ @Override
+ public VTree getWidget() {
+ return (VTree) super.getWidget();
+ }
+
+ private void handleUpdate(UIDL uidl) {
+ final TreeNode rootNode = getWidget().getNodeByKey(
+ uidl.getStringAttribute("rootKey"));
+ if (rootNode != null) {
+ if (!rootNode.getState()) {
+ // expanding node happened server side
+ rootNode.setState(true, false);
+ }
+ renderChildNodes(rootNode, (Iterator) uidl.getChildIterator());
+ }
+ }
+
+ /**
+ * Registers action for the root and also for individual nodes
+ *
+ * @param uidl
+ */
+ private void updateActionMap(UIDL uidl) {
+ final Iterator<?> it = uidl.getChildIterator();
+ while (it.hasNext()) {
+ final UIDL action = (UIDL) it.next();
+ final String key = action.getStringAttribute("key");
+ final String caption = action
+ .getStringAttribute(ATTRIBUTE_ACTION_CAPTION);
+ String iconUrl = null;
+ if (action.hasAttribute(ATTRIBUTE_ACTION_ICON)) {
+ iconUrl = getConnection().translateVaadinUri(
+ action.getStringAttribute(ATTRIBUTE_ACTION_ICON));
+ }
+ getWidget().registerAction(key, caption, iconUrl);
+ }
+
+ }
+
+ public void updateNodeFromUIDL(TreeNode treeNode, UIDL uidl) {
+ String nodeKey = uidl.getStringAttribute("key");
+ treeNode.setText(uidl.getStringAttribute(ATTRIBUTE_NODE_CAPTION));
+ treeNode.key = nodeKey;
+
+ getWidget().registerNode(treeNode);
+
+ if (uidl.hasAttribute("al")) {
+ treeNode.actionKeys = uidl.getStringArrayAttribute("al");
+ }
+
+ if (uidl.getTag().equals("node")) {
+ if (uidl.getChildCount() == 0) {
+ treeNode.childNodeContainer.setVisible(false);
+ } else {
+ renderChildNodes(treeNode, (Iterator) uidl.getChildIterator());
+ treeNode.childrenLoaded = true;
+ }
+ } else {
+ treeNode.addStyleName(TreeNode.CLASSNAME + "-leaf");
+ }
+ if (uidl.hasAttribute(ATTRIBUTE_NODE_STYLE)) {
+ treeNode.setNodeStyleName(uidl
+ .getStringAttribute(ATTRIBUTE_NODE_STYLE));
+ }
+
+ String description = uidl.getStringAttribute("descr");
+ if (description != null && getConnection() != null) {
+ // Set tooltip
+ TooltipInfo info = new TooltipInfo(description);
+ getConnection().registerTooltip(this, nodeKey, info);
+ } else {
+ // Remove possible previous tooltip
+ getConnection().registerTooltip(this, nodeKey, null);
+ }
+
+ if (uidl.getBooleanAttribute("expanded") && !treeNode.getState()) {
+ treeNode.setState(true, false);
+ }
+
+ if (uidl.getBooleanAttribute("selected")) {
+ treeNode.setSelected(true);
+ // ensure that identifier is in selectedIds array (this may be a
+ // partial update)
+ getWidget().selectedIds.add(nodeKey);
+ }
+
+ treeNode.setIcon(uidl.getStringAttribute(ATTRIBUTE_NODE_ICON));
+ }
+
+ void renderChildNodes(TreeNode containerNode, Iterator<UIDL> i) {
+ containerNode.childNodeContainer.clear();
+ containerNode.childNodeContainer.setVisible(true);
+ while (i.hasNext()) {
+ final UIDL childUidl = i.next();
+ // actions are in bit weird place, don't mix them with children,
+ // but current node's actions
+ if ("actions".equals(childUidl.getTag())) {
+ updateActionMap(childUidl);
+ continue;
+ }
+ final TreeNode childTree = getWidget().new TreeNode();
+ updateNodeFromUIDL(childTree, childUidl);
+ containerNode.childNodeContainer.add(childTree);
+ if (!i.hasNext()) {
+ childTree
+ .addStyleDependentName(childTree.isLeaf() ? "leaf-last"
+ : "last");
+ childTree.childNodeContainer.addStyleDependentName("last");
+ }
+ }
+ containerNode.childrenLoaded = true;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return super.isReadOnly() || getState().isPropertyReadOnly();
+ }
+
+ @Override
+ public AbstractFieldState getState() {
+ return (AbstractFieldState) super.getState();
+ }
+
+}
}
addStyleDependentName("loading");
- // run updating variables in deferred command to bypass some FF
- // optimization issues
- Scheduler.get().scheduleDeferred(new Command() {
- public void execute() {
- previousVisibleWidget = tp.getWidget(tp.getVisibleWidget());
- DOM.setStyleAttribute(
- DOM.getParent(previousVisibleWidget.getElement()),
- "visibility", "hidden");
- client.updateVariable(id, "selected", tabKeys.get(tabIndex)
- .toString(), true);
- }
- });
+ // Hide the current contents so a loading indicator can be shown
+ // instead
+ Widget currentlyDisplayedWidget = tp.getWidget(tp
+ .getVisibleWidget());
+ currentlyDisplayedWidget.getElement().getParentElement().getStyle()
+ .setVisibility(Visibility.HIDDEN);
+ client.updateVariable(id, "selected", tabKeys.get(tabIndex)
+ .toString(), true);
waitingForResponse = true;
-
- return true;
}
- return false;
+ // Note that we return true when tabIndex == activeTabIndex; the active
+ // tab could be selected, it's just a no-op.
+ return true;
}
public ApplicationConnection getApplicationConnection() {
import com.vaadin.data.Validatable;
import com.vaadin.data.Validator;
import com.vaadin.data.Validator.InvalidValueException;
+import com.vaadin.data.util.converter.Converter;
+import com.vaadin.data.util.converter.ConverterFactory;
import com.vaadin.event.Action;
-import com.vaadin.event.ActionManager;
import com.vaadin.event.ShortcutAction;
import com.vaadin.event.ShortcutListener;
+import com.vaadin.terminal.AbstractErrorMessage;
import com.vaadin.terminal.CompositeErrorMessage;
import com.vaadin.terminal.ErrorMessage;
--import com.vaadin.terminal.PaintException;
--import com.vaadin.terminal.PaintTarget;
+import com.vaadin.terminal.gwt.client.AbstractFieldState;
/**
* <p>
public void setPropertyDataSource(Property newDataSource) {
// Saves the old value
- final Object oldValue = value;
+ final Object oldValue = getInternalValue();
- // Stops listening the old data source changes
- if (dataSource != null
- && Property.ValueChangeNotifier.class
- .isAssignableFrom(dataSource.getClass())) {
- ((Property.ValueChangeNotifier) dataSource).removeListener(this);
- }
- if (dataSource != null
- && Property.ReadOnlyStatusChangeNotifier.class
- .isAssignableFrom(dataSource.getClass())) {
- ((Property.ReadOnlyStatusChangeNotifier) dataSource)
- .removeListener(this);
- }
+ // Stop listening to the old data source
+ removePropertyListeners();
// Sets the new data source
dataSource = newDataSource;
// Gets the value from source
try {
if (dataSource != null) {
- setInternalValue(String.class == getType() ? dataSource
- .toString() : dataSource.getValue());
+ T fieldValue = convertFromDataSource(getDataSourceValue());
+ setInternalValue(fieldValue);
+ }
+ setModified(false);
+ if (getCurrentBufferedSourceException() != null) {
+ setCurrentBufferedSourceException(null);
}
- modified = false;
} catch (final Throwable e) {
- currentBufferedSourceException = new Buffered.SourceException(this,
- e);
- modified = true;
+ setCurrentBufferedSourceException(new Buffered.SourceException(
+ this, e));
+ setModified(true);
}
- // Listens the new data source if possible
- if (dataSource instanceof Property.ValueChangeNotifier) {
- ((Property.ValueChangeNotifier) dataSource).addListener(this);
- }
- if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) {
- ((Property.ReadOnlyStatusChangeNotifier) dataSource)
- .addListener(this);
- }
+ // Listen to new data source if possible
+ addPropertyListeners();
// Copy the validators from the data source
if (dataSource instanceof Validatable) {
}
}
- if (actionManager != null) {
- actionManager.setViewer(getWindow());
- }
+ /**
+ * Notifies the component that it is connected to an application.
+ *
+ * @see com.vaadin.ui.Component#attach()
+ */
+ @Override
+ public void attach() {
+ super.attach();
- if (actionManager != null) {
- actionManager.setViewer((Window) null);
- }
+ // No-op if listeners already registered
+ addPropertyListeners();
+ }
+
+ @Override
+ public void detach() {
+ super.detach();
+ // Stop listening to data source events on detach to avoid a potential
+ // memory leak. See #6155.
+ removePropertyListeners();
+ }
+
/**
* Is this field required. Required fields must filled by the user.
*
}
}
-}
+ /**
+ * Gets the converter used to convert the property data source value to the
+ * field value.
+ *
+ * @return The converter or null if none is set.
+ */
+ public Converter<T, Object> getConverter() {
+ return converter;
+ }
+
+ /**
+ * Sets the converter used to convert the field value to property data
+ * source type. The converter must have a presentation type that matches the
+ * field type.
+ *
+ * @param converter
+ * The new converter to use.
+ */
+ public void setConverter(Converter<T, ?> converter) {
+ this.converter = (Converter<T, Object>) converter;
+ requestRepaint();
+ }
+
+ @Override
+ public AbstractFieldState getState() {
+ return (AbstractFieldState) super.getState();
+ }
+
+ @Override
+ public void updateState() {
+ super.updateState();
+
+ // Hide the error indicator if needed
+ getState().setHideErrors(shouldHideErrors());
+ }
+
+ private void addPropertyListeners() {
+ if (!isListening) {
+ if (dataSource instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) dataSource).addListener(this);
+ }
+ if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) {
+ ((Property.ReadOnlyStatusChangeNotifier) dataSource)
+ .addListener(this);
+ }
+ isListening = true;
+ }
+ }
+
+ private void removePropertyListeners() {
+ if (isListening) {
+ if (dataSource instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) dataSource)
+ .removeListener(this);
+ }
+ if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) {
+ ((Property.ReadOnlyStatusChangeNotifier) dataSource)
+ .removeListener(this);
+ }
+ isListening = false;
+ }
+ }
+}
}
}
+ /**
+ * Sets the selected tab in the TabSheet. Ensures that the selected tab is
+ * repainted if needed.
+ *
+ * @param c
+ * The new selection or null for no selection
+ */
+ private void setSelected(Component c) {
+ selected = c;
+ // Repaint of the selected component is needed as only the selected
+ // component is communicated to the client. Otherwise this will be a
+ // "cached" update even though the client knows nothing about the
+ // connector
+ if (selected instanceof ComponentContainer) {
+ ((ComponentContainer) selected).requestRepaintAll();
+ } else if (selected != null) {
+ selected.requestRepaint();
+ }
+
+ }
+
+ /**
+ * Sets the selected tab. The tab is identified by the corresponding
+ * {@link Tab Tab} instance. Does nothing if the tabsheet doesn't contain
+ * the given tab.
+ *
+ * @param tab
+ */
+ public void setSelectedTab(Tab tab) {
+ setSelectedTab(tab.getComponent());
+ }
+
+ /**
+ * Sets the selected tab, identified by its position. Does nothing if
+ * <code>index < 0 || index > {@link #getComponentCount()}</code>.
+ *
+ * @param index
+ */
+ public void setSelectedTab(int index) {
+ setSelectedTab(getTab(index));
+ }
+
/**
* Checks if the current selection is valid, and updates the selection if
* the previously selected component is not visible and enabled. The first
--- /dev/null
-import org.junit.Test;
-
+ package com.vaadin.tests.server.component.abstractfield;
+
+ import static org.junit.Assert.assertEquals;
+
++import org.junit.Test;
++
+ import com.vaadin.data.Property;
+ import com.vaadin.data.util.AbstractProperty;
++import com.vaadin.data.util.converter.Converter.ConversionException;
+ import com.vaadin.ui.AbstractField;
+
+ public class RemoveListenersOnDetach {
+
+ int numValueChanges = 0;
+ int numReadOnlyChanges = 0;
+
+ AbstractField field = new AbstractField() {
+ @Override
+ public Class<?> getType() {
+ return null;
+ }
+
+ @Override
+ public void valueChange(Property.ValueChangeEvent event) {
+ super.valueChange(event);
+ numValueChanges++;
+ }
+
+ @Override
+ public void readOnlyStatusChange(
+ Property.ReadOnlyStatusChangeEvent event) {
+ super.readOnlyStatusChange(event);
+ numReadOnlyChanges++;
+ }
+ };
+
+ Property property = new AbstractProperty() {
+ public Object getValue() {
+ return null;
+ }
+
+ public void setValue(Object newValue) throws ReadOnlyException,
+ ConversionException {
+ fireValueChange();
+ }
+
+ public Class<?> getType() {
+ return null;
+ }
+ };
+
+ @Test
+ public void testAttachDetach() {
+ field.setPropertyDataSource(property);
+
+ property.setValue(null);
+ property.setReadOnly(true);
+ assertEquals(1, numValueChanges);
+ assertEquals(1, numReadOnlyChanges);
+
+ field.attach();
+ property.setValue(null);
+ property.setReadOnly(false);
+ assertEquals(2, numValueChanges);
+ assertEquals(2, numReadOnlyChanges);
+
+ field.detach();
+ property.setValue(null);
+ property.setReadOnly(true);
+ assertEquals(2, numValueChanges);
+ assertEquals(2, numReadOnlyChanges);
+ }
+ }