View Javadoc
1   /*
2    * Copyright 2000-2013 Vaadin Ltd.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at
7    * 
8    * http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package com.vaadin.client.ui.combobox;
17  
18  import java.util.ArrayList;
19  import java.util.Iterator;
20  import java.util.List;
21  
22  import com.vaadin.client.ApplicationConnection;
23  import com.vaadin.client.Paintable;
24  import com.vaadin.client.UIDL;
25  import com.vaadin.client.ui.AbstractFieldConnector;
26  import com.vaadin.client.ui.SimpleManagedLayout;
27  import com.vaadin.client.ui.VFilterSelectPatched;
28  import com.vaadin.client.ui.VFilterSelectPatched.FilterSelectSuggestion;
29  import com.vaadin.client.ui.menubar.MenuItem;
30  import com.vaadin.shared.ui.combobox.ComboBoxConstants;
31  import com.vaadin.shared.ui.combobox.ComboBoxState;
32  import com.vaadin.shared.ui.combobox.FilteringMode;
33  
34  //@Connect(ComboBox.class)
35  public class ComboBoxConnectorPatched extends AbstractFieldConnector implements
36          Paintable, SimpleManagedLayout {
37  
38      /*
39       * (non-Javadoc)
40       * 
41       * @see com.vaadin.client.Paintable#updateFromUIDL(com.vaadin.client.UIDL,
42       * com.vaadin.client.ApplicationConnection)
43       */
44      @Override
45      public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
46          // Save details
47          getWidget().client = client;
48          getWidget().paintableId = uidl.getId();
49  
50          getWidget().readonly = isReadOnly();
51          getWidget().updateReadOnly();
52  
53          if (!isRealUpdate(uidl)) {
54              return;
55          }
56  
57          // Inverse logic here to make the default case (text input enabled)
58          // work without additional UIDL messages
59          boolean noTextInput = uidl
60                  .hasAttribute(ComboBoxConstants.ATTR_NO_TEXT_INPUT)
61                  && uidl.getBooleanAttribute(ComboBoxConstants.ATTR_NO_TEXT_INPUT);
62          getWidget().setTextInputEnabled(!noTextInput);
63  
64          // not a FocusWidget -> needs own tabindex handling
65          getWidget().tb.setTabIndex(getState().tabIndex);
66  
67          if (uidl.hasAttribute("filteringmode")) {
68              getWidget().filteringmode = FilteringMode.valueOf(uidl
69                      .getStringAttribute("filteringmode"));
70          }
71  
72          getWidget().immediate = getState().immediate;
73  
74          getWidget().nullSelectionAllowed = uidl.hasAttribute("nullselect");
75  
76          getWidget().nullSelectItem = uidl.hasAttribute("nullselectitem")
77                  && uidl.getBooleanAttribute("nullselectitem");
78  
79          getWidget().currentPage = uidl.getIntVariable("page");
80  
81          if (uidl.hasAttribute("pagelength")) {
82              getWidget().pageLength = uidl.getIntAttribute("pagelength");
83          }
84  
85          if (uidl.hasAttribute(ComboBoxConstants.ATTR_INPUTPROMPT)) {
86              // input prompt changed from server
87              getWidget().inputPrompt = uidl
88                      .getStringAttribute(ComboBoxConstants.ATTR_INPUTPROMPT);
89          } else {
90              getWidget().inputPrompt = "";
91          }
92  
93          getWidget().suggestionPopup.updateStyleNames(uidl, getState());
94  
95          getWidget().allowNewItem = uidl.hasAttribute("allownewitem");
96          getWidget().lastNewItemString = null;
97  
98          final UIDL options = uidl.getChildUIDL(0);
99          if (uidl.hasAttribute("totalMatches")) {
100             getWidget().totalMatches = uidl.getIntAttribute("totalMatches");
101         } else {
102             getWidget().totalMatches = 0;
103         }
104 
105         List<FilterSelectSuggestion> newSuggestions = new ArrayList<FilterSelectSuggestion>();
106 
107         for (final Iterator<?> i = options.getChildIterator(); i.hasNext();) {
108             final UIDL optionUidl = (UIDL) i.next();
109             final FilterSelectSuggestion suggestion = getWidget().new FilterSelectSuggestion(
110                     optionUidl);
111             newSuggestions.add(suggestion);
112         }
113 
114         // only close the popup if the suggestions list has actually changed
115         boolean suggestionsChanged = !getWidget().initDone
116                 || !newSuggestions.equals(getWidget().currentSuggestions);
117 
118         if (suggestionsChanged) {
119             getWidget().currentSuggestions.clear();
120 
121             if (!getWidget().waitingForFilteringResponse) {
122                 /*
123                  * Clear the current suggestions as the server response always
124                  * includes the new ones. Exception is when filtering, then we
125                  * need to retain the value if the user does not select any of
126                  * the options matching the filter.
127                  */
128                 getWidget().currentSuggestion = null;
129                 /*
130                  * Also ensure no old items in menu. Unless cleared the old
131                  * values may cause odd effects on blur events. Suggestions in
132                  * menu might not necessary exist in select at all anymore.
133                  */
134                 getWidget().suggestionPopup.menu.clearItems();
135 
136             }
137 
138             for (FilterSelectSuggestion suggestion : newSuggestions) {
139                 getWidget().currentSuggestions.add(suggestion);
140             }
141         }
142 
143         // handle selection (null or a single value)
144         if (uidl.hasVariable("selected")) {
145             String[] selectedKeys = uidl.getStringArrayVariable("selected");
146             if (selectedKeys.length > 0) {
147                 performSelection(selectedKeys[0]);
148             } else {
149                 resetSelection();
150             }
151         }
152 
153         if (getWidget().waitingForFilteringResponse
154                 && getWidget().lastFilter.toLowerCase().equals(
155                         uidl.getStringVariable("filter"))) {
156             getWidget().suggestionPopup.showSuggestions(
157                     getWidget().currentSuggestions, getWidget().currentPage,
158                     getWidget().totalMatches);
159             getWidget().waitingForFilteringResponse = false;
160             if (!getWidget().popupOpenerClicked
161                     && getWidget().selectPopupItemWhenResponseIsReceived != VFilterSelectPatched.Select.NONE) {
162                 // we're paging w/ arrows
163                 if (getWidget().selectPopupItemWhenResponseIsReceived == VFilterSelectPatched.Select.LAST) {
164                     getWidget().suggestionPopup.menu.selectLastItem();
165                 } else {
166                     getWidget().suggestionPopup.menu.selectFirstItem();
167                 }
168 
169                 // This is used for paging so we update the keyboard selection
170                 // variable as well.
171                 MenuItem activeMenuItem = getWidget().suggestionPopup.menu
172                         .getSelectedItem();
173                 getWidget().suggestionPopup.menu
174                         .setKeyboardSelectedItem(activeMenuItem);
175 
176                 // Update text field to contain the correct text
177                 getWidget().setTextboxText(activeMenuItem.getText());
178                 getWidget().tb.setSelectionRange(
179                         getWidget().lastFilter.length(),
180                         activeMenuItem.getText().length()
181                                 - getWidget().lastFilter.length());
182 
183                 getWidget().selectPopupItemWhenResponseIsReceived = VFilterSelectPatched.Select.NONE; // reset
184             }
185             if (getWidget().updateSelectionWhenReponseIsReceived) {
186                 getWidget().suggestionPopup.menu
187                         .doPostFilterSelectedItemAction();
188             }
189         }
190 
191         // Calculate minimum textarea width
192         getWidget().updateSuggestionPopupMinWidth();
193 
194         getWidget().popupOpenerClicked = false;
195 
196         if (!getWidget().initDone) {
197             getWidget().updateRootWidth();
198         }
199 
200         // Focus dependent style names are lost during the update, so we add
201         // them here back again
202         if (getWidget().focused) {
203             getWidget().addStyleDependentName("focus");
204         }
205 
206         getWidget().initDone = true;
207     }
208 
209     private void performSelection(String selectedKey) {
210         // some item selected
211         for (FilterSelectSuggestion suggestion : getWidget().currentSuggestions) {
212             String suggestionKey = suggestion.getOptionKey();
213             if (suggestionKey.equals(selectedKey)) {
214                 if (!getWidget().waitingForFilteringResponse
215                         || getWidget().popupOpenerClicked) {
216                     if (!suggestionKey.equals(getWidget().selectedOptionKey)
217                             || suggestion.getReplacementString().equals(
218                                     getWidget().tb.getText())) {
219                         // Update text field if we've got a new
220                         // selection
221                         // Also update if we've got the same text to
222                         // retain old text selection behavior
223                         getWidget().setPromptingOff(
224                                 suggestion.getReplacementString());
225                         getWidget().selectedOptionKey = suggestionKey;
226                     }
227                 }
228                 getWidget().currentSuggestion = suggestion;
229                 getWidget().setSelectedItemIcon(suggestion.getIconUri());
230                 // only a single item can be selected
231                 break;
232             }
233         }
234     }
235 
236     private void resetSelection() {
237         if (!getWidget().waitingForFilteringResponse
238                 || getWidget().popupOpenerClicked) {
239             // select nulled
240             if (!getWidget().focused) {
241                 /*
242                  * client.updateComponent overwrites all styles so we must
243                  * ALWAYS set the prompting style at this point, even though we
244                  * think it has been set already...
245                  */
246                 getWidget().prompting = false;
247                 getWidget().setPromptingOn();
248             } else {
249                 // we have focus in field, prompting can't be set on, instead
250                 // just clear the input if the value has changed from something
251                 // else to null
252                 if (getWidget().selectedOptionKey != null) {
253                     getWidget().tb.setValue("");
254                 }
255             }
256             getWidget().setSelectedItemIcon(null);
257             getWidget().selectedOptionKey = null;
258         }
259     }
260 
261     @Override
262     public VFilterSelectPatched getWidget() {
263         return (VFilterSelectPatched) super.getWidget();
264     }
265 
266     @Override
267     public ComboBoxState getState() {
268         return (ComboBoxState) super.getState();
269     }
270 
271     @Override
272     public void layout() {
273         VFilterSelectPatched widget = getWidget();
274         if (widget.initDone) {
275             widget.updateRootWidth();
276         }
277     }
278 
279     @Override
280     public void setWidgetEnabled(boolean widgetEnabled) {
281         super.setWidgetEnabled(widgetEnabled);
282         getWidget().enabled = widgetEnabled;
283         getWidget().tb.setEnabled(widgetEnabled);
284     }
285 }