]> source.dussan.org Git - vaadin-framework.git/commitdiff
Clear items in ComboBox only if changed (#10924)
authorHenri Sara <hesara@vaadin.com>
Fri, 10 May 2013 13:25:55 +0000 (16:25 +0300)
committerVaadin Code Review <review@vaadin.com>
Tue, 14 May 2013 13:15:22 +0000 (13:15 +0000)
Selection is now sent only as a key, removed redundant attribute on the
item.

Change-Id: I882d4ae17a1dc91f7a55a0b4a94e47c078ffc022

client/src/com/vaadin/client/ui/VFilterSelect.java
client/src/com/vaadin/client/ui/combobox/ComboBoxConnector.java
server/src/com/vaadin/ui/ComboBox.java
uitest/src/com/vaadin/tests/components/combobox/ComboPushTiming.java [new file with mode: 0644]

index 11f89ee232d304245be69fc509097f8d740d5c33..a3156221b91457d8a98b2fef792254bce9caa09b 100644 (file)
@@ -168,6 +168,27 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
         public void execute() {
             onSuggestionSelected(this);
         }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof FilterSelectSuggestion)) {
+                return false;
+            }
+            FilterSelectSuggestion other = (FilterSelectSuggestion) obj;
+            if ((key == null && other.key != null)
+                    || (key != null && !key.equals(other.key))) {
+                return false;
+            }
+            if ((caption == null && other.caption != null)
+                    || (caption != null && !caption.equals(other.caption))) {
+                return false;
+            }
+            if ((iconUri == null && other.iconUri != null)
+                    || (iconUri != null && !iconUri.equals(other.iconUri))) {
+                return false;
+            }
+            return true;
+        }
     }
 
     /**
index 7c82db1b004590fa369026d277c69f8d732281e4..345bdc0cbb40b9af7e9b1be3eaaaa5500b240ab8 100644 (file)
@@ -15,7 +15,9 @@
  */
 package com.vaadin.client.ui.combobox;
 
+import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.List;
 
 import com.vaadin.client.ApplicationConnection;
 import com.vaadin.client.Paintable;
@@ -98,24 +100,6 @@ public class ComboBoxConnector extends AbstractFieldConnector implements
         getWidget().allowNewItem = uidl.hasAttribute("allownewitem");
         getWidget().lastNewItemString = null;
 
-        getWidget().currentSuggestions.clear();
-        if (!getWidget().waitingForFilteringResponse) {
-            /*
-             * Clear the current suggestions as the server response always
-             * includes the new ones. Exception is when filtering, then we need
-             * to retain the value if the user does not select any of the
-             * options matching the filter.
-             */
-            getWidget().currentSuggestion = null;
-            /*
-             * Also ensure no old items in menu. Unless cleared the old values
-             * may cause odd effects on blur events. Suggestions in menu might
-             * not necessary exist in select at all anymore.
-             */
-            getWidget().suggestionPopup.menu.clearItems();
-
-        }
-
         final UIDL options = uidl.getChildUIDL(0);
         if (uidl.hasAttribute("totalMatches")) {
             getWidget().totalMatches = uidl.getIntAttribute("totalMatches");
@@ -123,54 +107,52 @@ public class ComboBoxConnector extends AbstractFieldConnector implements
             getWidget().totalMatches = 0;
         }
 
+        List<FilterSelectSuggestion> newSuggestions = new ArrayList<FilterSelectSuggestion>();
+
         for (final Iterator<?> i = options.getChildIterator(); i.hasNext();) {
             final UIDL optionUidl = (UIDL) i.next();
             final FilterSelectSuggestion suggestion = getWidget().new FilterSelectSuggestion(
                     optionUidl);
-            getWidget().currentSuggestions.add(suggestion);
-            if (optionUidl.hasAttribute("selected")) {
-                if (!getWidget().waitingForFilteringResponse
-                        || getWidget().popupOpenerClicked) {
-                    String newSelectedOptionKey = suggestion.getOptionKey();
-                    if (!newSelectedOptionKey
-                            .equals(getWidget().selectedOptionKey)
-                            || suggestion.getReplacementString().equals(
-                                    getWidget().tb.getText())) {
-                        // Update text field if we've got a new selection
-                        // Also update if we've got the same text to retain old
-                        // text selection behavior
-                        getWidget().setPromptingOff(
-                                suggestion.getReplacementString());
-                        getWidget().selectedOptionKey = newSelectedOptionKey;
-                    }
-                }
-                getWidget().currentSuggestion = suggestion;
-                getWidget().setSelectedItemIcon(suggestion.getIconUri());
+            newSuggestions.add(suggestion);
+        }
+
+        // only close the popup if the suggestions list has actually changed
+        boolean suggestionsChanged = !getWidget().initDone
+                || !newSuggestions.equals(getWidget().currentSuggestions);
+
+        if (suggestionsChanged) {
+            getWidget().currentSuggestions.clear();
+
+            if (!getWidget().waitingForFilteringResponse) {
+                /*
+                 * Clear the current suggestions as the server response always
+                 * includes the new ones. Exception is when filtering, then we
+                 * need to retain the value if the user does not select any of
+                 * the options matching the filter.
+                 */
+                getWidget().currentSuggestion = null;
+                /*
+                 * Also ensure no old items in menu. Unless cleared the old
+                 * values may cause odd effects on blur events. Suggestions in
+                 * menu might not necessary exist in select at all anymore.
+                 */
+                getWidget().suggestionPopup.menu.clearItems();
+
+            }
+
+            for (FilterSelectSuggestion suggestion : newSuggestions) {
+                getWidget().currentSuggestions.add(suggestion);
             }
         }
 
-        if ((!getWidget().waitingForFilteringResponse || getWidget().popupOpenerClicked)
-                && uidl.hasVariable("selected")
-                && uidl.getStringArrayVariable("selected").length == 0) {
-            // select nulled
-            if (!getWidget().waitingForFilteringResponse
-                    || !getWidget().popupOpenerClicked) {
-                if (!getWidget().focused) {
-                    /*
-                     * client.updateComponent overwrites all styles so we must
-                     * ALWAYS set the prompting style at this point, even though
-                     * we think it has been set already...
-                     */
-                    getWidget().prompting = false;
-                    getWidget().setPromptingOn();
-                } else {
-                    // we have focus in field, prompting can't be set on,
-                    // instead just clear the input
-                    getWidget().tb.setValue("");
-                }
+        // handle selection (null or a single value)
+        if (uidl.hasVariable("selected")) {
+            String[] selectedKeys = uidl.getStringArrayVariable("selected");
+            if (selectedKeys.length > 0) {
+                performSelection(selectedKeys[0]);
+            } else {
+                resetSelection();
             }
-            getWidget().setSelectedItemIcon(null);
-            getWidget().selectedOptionKey = null;
         }
 
         if (getWidget().waitingForFilteringResponse
@@ -229,6 +211,55 @@ public class ComboBoxConnector extends AbstractFieldConnector implements
         getWidget().initDone = true;
     }
 
+    private void performSelection(String selectedKey) {
+        // some item selected
+        for (FilterSelectSuggestion suggestion : getWidget().currentSuggestions) {
+            String suggestionKey = suggestion.getOptionKey();
+            if (suggestionKey.equals(selectedKey)) {
+                if (!getWidget().waitingForFilteringResponse
+                        || getWidget().popupOpenerClicked) {
+                    if (!suggestionKey.equals(getWidget().selectedOptionKey)
+                            || suggestion.getReplacementString().equals(
+                                    getWidget().tb.getText())) {
+                        // Update text field if we've got a new
+                        // selection
+                        // Also update if we've got the same text to
+                        // retain old text selection behavior
+                        getWidget().setPromptingOff(
+                                suggestion.getReplacementString());
+                        getWidget().selectedOptionKey = suggestionKey;
+                    }
+                }
+                getWidget().currentSuggestion = suggestion;
+                getWidget().setSelectedItemIcon(suggestion.getIconUri());
+                // only a single item can be selected
+                break;
+            }
+        }
+    }
+
+    private void resetSelection() {
+        if (!getWidget().waitingForFilteringResponse
+                || getWidget().popupOpenerClicked) {
+            // select nulled
+            if (!getWidget().focused) {
+                /*
+                 * client.updateComponent overwrites all styles so we must
+                 * ALWAYS set the prompting style at this point, even though we
+                 * think it has been set already...
+                 */
+                getWidget().prompting = false;
+                getWidget().setPromptingOn();
+            } else {
+                // we have focus in field, prompting can't be set on, instead
+                // just clear the input
+                getWidget().tb.setValue("");
+            }
+            getWidget().setSelectedItemIcon(null);
+            getWidget().selectedOptionKey = null;
+        }
+    }
+
     @Override
     public VFilterSelect getWidget() {
         return (VFilterSelect) super.getWidget();
index 0f42749acd5f7e86b6ea50036d434a7bcac87d00..88e895df8207f704ff17cc1fea4e8414f0624d7d 100644 (file)
@@ -262,8 +262,8 @@ public class ComboBox extends AbstractSelect implements
                     target.addAttribute("nullselection", true);
                 }
                 target.addAttribute("key", key);
-                if (isSelected(id) && keyIndex < selectedKeys.length) {
-                    target.addAttribute("selected", true);
+                if (keyIndex < selectedKeys.length && isSelected(id)) {
+                    // at most one item can be selected at a time
                     selectedKeys[keyIndex++] = key;
                 }
                 target.endTag("so");
diff --git a/uitest/src/com/vaadin/tests/components/combobox/ComboPushTiming.java b/uitest/src/com/vaadin/tests/components/combobox/ComboPushTiming.java
new file mode 100644 (file)
index 0000000..fe2cffd
--- /dev/null
@@ -0,0 +1,110 @@
+package com.vaadin.tests.components.combobox;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import com.vaadin.data.util.ObjectProperty;
+import com.vaadin.event.FieldEvents;
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.ui.label.ContentMode;
+import com.vaadin.tests.components.TestBase;
+import com.vaadin.ui.ComboBox;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.ProgressIndicator;
+import com.vaadin.ui.TextField;
+
+public class ComboPushTiming extends TestBase {
+
+    private int counter = 0;
+    private final MyExecutor executor = new MyExecutor();
+
+    @Override
+    protected void setup() {
+
+        List<String> list = new ArrayList<String>();
+        for (int i = 0; i < 100; i++) {
+            list.add("Item " + i);
+        }
+
+        final ComboBox cb = new ComboBox("Combobox", list);
+        cb.setImmediate(true);
+        cb.setInputPrompt("Enter text");
+        cb.setDescription("Some Combobox");
+        addComponent(cb);
+
+        final ObjectProperty<String> log = new ObjectProperty<String>("");
+
+        cb.addListener(new FieldEvents.FocusListener() {
+            @Override
+            public void focus(FocusEvent event) {
+                log.setValue(log.getValue().toString() + "<br>" + counter
+                        + ": Focus event!");
+                counter++;
+                changeValue(cb);
+            }
+        });
+
+        cb.addListener(new FieldEvents.BlurListener() {
+            @Override
+            public void blur(BlurEvent event) {
+                log.setValue(log.getValue().toString() + "<br>" + counter
+                        + ": Blur event!");
+                counter++;
+            }
+        });
+
+        TextField field = new TextField("Some textfield");
+        field.setImmediate(true);
+        addComponent(field);
+
+        Label output = new Label(log);
+        output.setCaption("Events:");
+
+        output.setContentMode(ContentMode.HTML);
+        addComponent(output);
+
+        ProgressIndicator progressIndicator = new ProgressIndicator();
+        addComponent(progressIndicator);
+        progressIndicator.setPollingInterval(3000);
+    }
+
+    private void changeValue(final ComboBox cb) {
+        executor.execute(new Runnable() {
+            public void run() {
+                VaadinSession.getCurrent().lock();
+                try {
+                    cb.setEnabled(true);
+                    cb.setValue("B");
+                    cb.setEnabled(true);
+
+                    // If this isn't sent by push or poll in the background, the
+                    // problem will go away
+                } finally {
+                    VaadinSession.getCurrent().unlock();
+                }
+            }
+        });
+    }
+
+    class MyExecutor extends ThreadPoolExecutor {
+        public MyExecutor() {
+            super(5, 20, 20, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
+        }
+    }
+
+    @Override
+    protected String getDescription() {
+        return "When an update is received while the popup is open, the suggestion popup blurs away";
+    }
+
+    @Override
+    protected Integer getTicketNumber() {
+        return 10924;
+    }
+
+}