You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ComboBoxConnector.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /*
  2. * Copyright 2000-2016 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. import java.util.List;
  18. import com.vaadin.client.Profiler;
  19. import com.vaadin.client.annotations.OnStateChange;
  20. import com.vaadin.client.communication.StateChangeEvent;
  21. import com.vaadin.client.connectors.AbstractListingConnector;
  22. import com.vaadin.client.connectors.data.HasDataSource;
  23. import com.vaadin.client.data.DataSource;
  24. import com.vaadin.client.ui.HasErrorIndicator;
  25. import com.vaadin.client.ui.HasRequiredIndicator;
  26. import com.vaadin.client.ui.SimpleManagedLayout;
  27. import com.vaadin.client.ui.VComboBox;
  28. import com.vaadin.client.ui.VComboBox.ComboBoxSuggestion;
  29. import com.vaadin.client.ui.VComboBox.DataReceivedHandler;
  30. import com.vaadin.shared.EventId;
  31. import com.vaadin.shared.Registration;
  32. import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc;
  33. import com.vaadin.shared.data.DataCommunicatorConstants;
  34. import com.vaadin.shared.data.selection.SelectionServerRpc;
  35. import com.vaadin.shared.ui.Connect;
  36. import com.vaadin.shared.ui.combobox.ComboBoxConstants;
  37. import com.vaadin.shared.ui.combobox.ComboBoxServerRpc;
  38. import com.vaadin.shared.ui.combobox.ComboBoxState;
  39. import com.vaadin.ui.ComboBox;
  40. import elemental.json.JsonObject;
  41. @Connect(ComboBox.class)
  42. public class ComboBoxConnector extends AbstractListingConnector
  43. implements HasRequiredIndicator, HasDataSource, SimpleManagedLayout,
  44. HasErrorIndicator {
  45. private ComboBoxServerRpc rpc = getRpcProxy(ComboBoxServerRpc.class);
  46. private SelectionServerRpc selectionRpc = getRpcProxy(
  47. SelectionServerRpc.class);
  48. private FocusAndBlurServerRpc focusAndBlurRpc = getRpcProxy(
  49. FocusAndBlurServerRpc.class);
  50. private Registration dataChangeHandlerRegistration;
  51. @Override
  52. protected void init() {
  53. super.init();
  54. getWidget().connector = this;
  55. }
  56. @Override
  57. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  58. super.onStateChanged(stateChangeEvent);
  59. Profiler.enter("ComboBoxConnector.onStateChanged update content");
  60. getWidget().readonly = isReadOnly();
  61. getWidget().updateReadOnly();
  62. // not a FocusWidget -> needs own tabindex handling
  63. getWidget().tb.setTabIndex(getState().tabIndex);
  64. getWidget().suggestionPopup.updateStyleNames(getState());
  65. getWidget().nullSelectionAllowed = getState().emptySelectionAllowed;
  66. // TODO having this true would mean that the empty selection item comes
  67. // from the data source so none needs to be added - currently
  68. // unsupported
  69. getWidget().nullSelectItem = false;
  70. // make sure the input prompt is updated
  71. getWidget().updatePlaceholder();
  72. getDataReceivedHandler().serverReplyHandled();
  73. Profiler.leave("ComboBoxConnector.onStateChanged update content");
  74. }
  75. @OnStateChange("emptySelectionCaption")
  76. private void onEmptySelectionCaptionChange() {
  77. List<ComboBoxSuggestion> suggestions = getWidget().currentSuggestions;
  78. if (!suggestions.isEmpty() && isFirstPage()) {
  79. suggestions.remove(0);
  80. addEmptySelectionItem();
  81. }
  82. }
  83. @OnStateChange({ "selectedItemKey", "selectedItemCaption" })
  84. private void onSelectionChange() {
  85. getDataReceivedHandler().updateSelectionFromServer(
  86. getState().selectedItemKey, getState().selectedItemCaption);
  87. }
  88. @Override
  89. public VComboBox getWidget() {
  90. return (VComboBox) super.getWidget();
  91. }
  92. private DataReceivedHandler getDataReceivedHandler() {
  93. return getWidget().getDataReceivedHandler();
  94. }
  95. @Override
  96. public ComboBoxState getState() {
  97. return (ComboBoxState) super.getState();
  98. }
  99. @Override
  100. public void layout() {
  101. VComboBox widget = getWidget();
  102. if (widget.initDone) {
  103. widget.updateRootWidth();
  104. }
  105. }
  106. @Override
  107. public void setWidgetEnabled(boolean widgetEnabled) {
  108. super.setWidgetEnabled(widgetEnabled);
  109. getWidget().enabled = widgetEnabled;
  110. getWidget().tb.setEnabled(widgetEnabled);
  111. }
  112. /*
  113. * These methods exist to move communications out of VComboBox, and may be
  114. * refactored/removed in the future
  115. */
  116. /**
  117. * Send a message about a newly created item to the server.
  118. *
  119. * This method is for internal use only and may be removed in future
  120. * versions.
  121. *
  122. * @since 8.0
  123. * @param itemValue
  124. * user entered string value for the new item
  125. */
  126. public void sendNewItem(String itemValue) {
  127. rpc.createNewItem(itemValue);
  128. getDataReceivedHandler().clearPendingNavigation();
  129. }
  130. /**
  131. * Send a message to the server set the current filter.
  132. *
  133. * This method is for internal use only and may be removed in future
  134. * versions.
  135. *
  136. * @since 8.0
  137. * @param filter
  138. * the current filter string
  139. */
  140. public void setFilter(String filter) {
  141. if (filter != getWidget().lastFilter) {
  142. getDataReceivedHandler().clearPendingNavigation();
  143. }
  144. rpc.setFilter(filter);
  145. }
  146. /**
  147. * Send a message to the server to request a page of items with the current
  148. * filter.
  149. *
  150. * This method is for internal use only and may be removed in future
  151. * versions.
  152. *
  153. * @since 8.0
  154. * @param page
  155. * the page number to get or -1 to let the server/connector
  156. * decide based on current selection (possibly loading more data
  157. * from the server)
  158. */
  159. public void requestPage(int page) {
  160. if (page < 0) {
  161. if (getState().scrollToSelectedItem) {
  162. getDataSource().ensureAvailability(0, 10000);
  163. return;
  164. } else {
  165. page = 0;
  166. }
  167. }
  168. int adjustment = getWidget().nullSelectionAllowed
  169. && "".equals(getWidget().lastFilter) ? 1 : 0;
  170. int startIndex = Math.max(0,
  171. page * getWidget().pageLength - adjustment);
  172. int pageLength = getWidget().pageLength > 0 ? getWidget().pageLength
  173. : 10000;
  174. getDataSource().ensureAvailability(startIndex, pageLength);
  175. }
  176. /**
  177. * Send a message to the server updating the current selection.
  178. *
  179. * This method is for internal use only and may be removed in future
  180. * versions.
  181. *
  182. * @since 8.0
  183. * @param selectionKey
  184. * the current selected item key
  185. */
  186. public void sendSelection(String selectionKey) {
  187. // map also the special empty string option key (from data change
  188. // handler below) to null
  189. selectionRpc.select("".equals(selectionKey) ? null : selectionKey);
  190. getDataReceivedHandler().clearPendingNavigation();
  191. }
  192. /**
  193. * Notify the server that the combo box received focus.
  194. *
  195. * For timing reasons, ConnectorFocusAndBlurHandler is not used at the
  196. * moment.
  197. *
  198. * This method is for internal use only and may be removed in future
  199. * versions.
  200. *
  201. * @since 8.0
  202. */
  203. public void sendFocusEvent() {
  204. boolean registeredListeners = hasEventListener(EventId.FOCUS);
  205. if (registeredListeners) {
  206. focusAndBlurRpc.focus();
  207. getDataReceivedHandler().clearPendingNavigation();
  208. }
  209. }
  210. /**
  211. * Notify the server that the combo box lost focus.
  212. *
  213. * For timing reasons, ConnectorFocusAndBlurHandler is not used at the
  214. * moment.
  215. *
  216. * This method is for internal use only and may be removed in future
  217. * versions.
  218. *
  219. * @since 8.0
  220. */
  221. public void sendBlurEvent() {
  222. boolean registeredListeners = hasEventListener(EventId.BLUR);
  223. if (registeredListeners) {
  224. focusAndBlurRpc.blur();
  225. getDataReceivedHandler().clearPendingNavigation();
  226. }
  227. }
  228. @Override
  229. public void setDataSource(DataSource<JsonObject> dataSource) {
  230. super.setDataSource(dataSource);
  231. dataChangeHandlerRegistration = dataSource
  232. .addDataChangeHandler(range -> refreshData());
  233. }
  234. @Override
  235. public void onUnregister() {
  236. super.onUnregister();
  237. dataChangeHandlerRegistration.remove();
  238. }
  239. @Override
  240. public boolean isRequiredIndicatorVisible() {
  241. return getState().required && !isReadOnly();
  242. }
  243. private void refreshData() {
  244. updateCurrentPage();
  245. getWidget().currentSuggestions.clear();
  246. int start = getWidget().currentPage * getWidget().pageLength;
  247. int end = getWidget().pageLength > 0 ? start + getWidget().pageLength
  248. : getDataSource().size();
  249. if (getWidget().nullSelectionAllowed
  250. && "".equals(getWidget().lastFilter)) {
  251. // add special null selection item...
  252. if (isFirstPage()) {
  253. addEmptySelectionItem();
  254. } else {
  255. // ...or leave space for it
  256. start = start - 1;
  257. }
  258. // in either case, the last item to show is
  259. // shifted by one
  260. end = end - 1;
  261. }
  262. updateSuggestions(start, end);
  263. getWidget().totalMatches = getDataSource().size()
  264. + (getState().emptySelectionAllowed ? 1 : 0);
  265. getDataReceivedHandler().dataReceived();
  266. }
  267. private void updateSuggestions(int start, int end) {
  268. for (int i = start; i < end; ++i) {
  269. JsonObject row = getDataSource().getRow(i);
  270. if (row != null) {
  271. String key = getRowKey(row);
  272. String caption = row.getString(DataCommunicatorConstants.NAME);
  273. String style = row.getString(ComboBoxConstants.STYLE);
  274. String untranslatedIconUri = row
  275. .getString(ComboBoxConstants.ICON);
  276. getWidget().currentSuggestions
  277. .add(getWidget().new ComboBoxSuggestion(key, caption,
  278. style, untranslatedIconUri));
  279. }
  280. }
  281. }
  282. private boolean isFirstPage() {
  283. return getWidget().currentPage == 0;
  284. }
  285. private void addEmptySelectionItem() {
  286. if (isFirstPage()) {
  287. getWidget().currentSuggestions.add(0,
  288. getWidget().new ComboBoxSuggestion("",
  289. getState().emptySelectionCaption, null, null));
  290. }
  291. }
  292. private void updateCurrentPage() {
  293. // try to find selected item if requested
  294. if (getState().scrollToSelectedItem && getState().pageLength > 0
  295. && getWidget().currentPage < 0
  296. && getWidget().selectedOptionKey != null) {
  297. // search for the item with the selected key
  298. getWidget().currentPage = 0;
  299. for (int i = 0; i < getDataSource().size(); ++i) {
  300. JsonObject row = getDataSource().getRow(i);
  301. if (row != null) {
  302. String key = getRowKey(row);
  303. if (getWidget().selectedOptionKey.equals(key)) {
  304. if (getWidget().nullSelectionAllowed) {
  305. getWidget().currentPage = (i + 1)
  306. / getState().pageLength;
  307. } else {
  308. getWidget().currentPage = i / getState().pageLength;
  309. }
  310. break;
  311. }
  312. }
  313. }
  314. } else if (getWidget().currentPage < 0) {
  315. getWidget().currentPage = 0;
  316. }
  317. }
  318. }