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.

ComboBox.java 32KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  1. /*
  2. * Copyright 2000-2014 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.ui;
  17. import java.io.Serializable;
  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Iterator;
  21. import java.util.LinkedList;
  22. import java.util.List;
  23. import java.util.Map;
  24. import com.vaadin.data.Container;
  25. import com.vaadin.data.util.filter.SimpleStringFilter;
  26. import com.vaadin.event.FieldEvents;
  27. import com.vaadin.event.FieldEvents.BlurEvent;
  28. import com.vaadin.event.FieldEvents.BlurListener;
  29. import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl;
  30. import com.vaadin.event.FieldEvents.FocusEvent;
  31. import com.vaadin.event.FieldEvents.FocusListener;
  32. import com.vaadin.server.PaintException;
  33. import com.vaadin.server.PaintTarget;
  34. import com.vaadin.server.Resource;
  35. import com.vaadin.shared.ui.combobox.ComboBoxServerRpc;
  36. import com.vaadin.shared.ui.combobox.ComboBoxState;
  37. import com.vaadin.shared.ui.combobox.FilteringMode;
  38. /**
  39. * A filtering dropdown single-select. Suitable for newItemsAllowed, but it's
  40. * turned of by default to avoid mistakes. Items are filtered based on user
  41. * input, and loaded dynamically ("lazy-loading") from the server. You can turn
  42. * on newItemsAllowed and change filtering mode (and also turn it off), but you
  43. * can not turn on multi-select mode.
  44. *
  45. */
  46. @SuppressWarnings("serial")
  47. public class ComboBox extends AbstractSelect implements
  48. AbstractSelect.Filtering, FieldEvents.BlurNotifier,
  49. FieldEvents.FocusNotifier {
  50. /**
  51. * ItemStyleGenerator can be used to add custom styles to combo box items
  52. * shown in the popup. The CSS class name that will be added to the item
  53. * style names is <tt>v-filterselect-item-[style name]</tt>.
  54. *
  55. * @since 7.5.6
  56. * @see ComboBox#setItemStyleGenerator(ItemStyleGenerator)
  57. */
  58. public interface ItemStyleGenerator extends Serializable {
  59. /**
  60. * Called by ComboBox when an item is painted.
  61. *
  62. * @param source
  63. * the source combo box
  64. * @param itemId
  65. * The itemId of the item to be painted. Can be
  66. * <code>null</code> if null selection is allowed.
  67. * @return The style name to add to this item. (the CSS class name will
  68. * be v-filterselect-item-[style name]
  69. */
  70. public String getStyle(ComboBox source, Object itemId);
  71. }
  72. private ComboBoxServerRpc rpc = new ComboBoxServerRpc() {
  73. @Override
  74. public void createNewItem(String itemValue) {
  75. if (isNewItemsAllowed()) {
  76. // New option entered (and it is allowed)
  77. if (itemValue != null && itemValue.length() > 0) {
  78. getNewItemHandler().addNewItem(itemValue);
  79. // rebuild list
  80. filterstring = null;
  81. prevfilterstring = null;
  82. }
  83. }
  84. }
  85. @Override
  86. public void setSelectedItem(String item) {
  87. if (item == null) {
  88. setValue(null, true);
  89. } else {
  90. final Object id = itemIdMapper.get(item);
  91. if (id != null && id.equals(getNullSelectionItemId())) {
  92. setValue(null, true);
  93. } else {
  94. setValue(id, true);
  95. }
  96. }
  97. }
  98. @Override
  99. public void requestPage(String filter, int page) {
  100. filterstring = filter;
  101. if (filterstring != null) {
  102. filterstring = filterstring.toLowerCase(getLocale());
  103. }
  104. currentPage = page;
  105. // TODO this should trigger a data-only update instead of a full
  106. // repaint
  107. requestRepaint();
  108. }
  109. };
  110. FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(this) {
  111. @Override
  112. protected void fireEvent(LegacyEvent event) {
  113. ComboBox.this.fireEvent(event);
  114. }
  115. };
  116. // Current page when the user is 'paging' trough options
  117. private int currentPage = -1;
  118. private String filterstring;
  119. private String prevfilterstring;
  120. /**
  121. * Number of options that pass the filter, excluding the null item if any.
  122. */
  123. private int filteredSize;
  124. /**
  125. * Cache of filtered options, used only by the in-memory filtering system.
  126. */
  127. private List<Object> filteredOptions;
  128. /**
  129. * Flag to indicate that request repaint is called by filter request only
  130. */
  131. private boolean optionRequest;
  132. /**
  133. * True while painting to suppress item set change notifications that could
  134. * be caused by temporary filtering.
  135. */
  136. private boolean isPainting;
  137. /**
  138. * Flag to indicate whether to scroll the selected item visible (select the
  139. * page on which it is) when opening the popup or not. Only applies to
  140. * single select mode.
  141. *
  142. * This requires finding the index of the item, which can be expensive in
  143. * many large lazy loading containers.
  144. */
  145. private boolean scrollToSelectedItem = true;
  146. private String suggestionPopupWidth = null;
  147. /**
  148. * If text input is not allowed, the ComboBox behaves like a pretty
  149. * NativeSelect - the user can not enter any text and clicking the text
  150. * field opens the drop down with options
  151. */
  152. private boolean textInputAllowed = true;
  153. private ItemStyleGenerator itemStyleGenerator = null;
  154. public ComboBox() {
  155. init();
  156. }
  157. public ComboBox(String caption, Collection<?> options) {
  158. super(caption, options);
  159. init();
  160. }
  161. public ComboBox(String caption, Container dataSource) {
  162. super(caption, dataSource);
  163. init();
  164. }
  165. public ComboBox(String caption) {
  166. super(caption);
  167. init();
  168. }
  169. /**
  170. * Initialize the ComboBox with default settings and register client to
  171. * server RPC implementation.
  172. */
  173. private void init() {
  174. registerRpc(rpc);
  175. registerRpc(focusBlurRpc);
  176. setNewItemsAllowed(false);
  177. setImmediate(true);
  178. }
  179. /**
  180. * Gets the current input prompt.
  181. *
  182. * @see #setInputPrompt(String)
  183. * @return the current input prompt, or null if not enabled
  184. */
  185. public String getInputPrompt() {
  186. return getState(false).inputPrompt;
  187. }
  188. /**
  189. * Sets the input prompt - a textual prompt that is displayed when the
  190. * select would otherwise be empty, to prompt the user for input.
  191. *
  192. * @param inputPrompt
  193. * the desired input prompt, or null to disable
  194. */
  195. public void setInputPrompt(String inputPrompt) {
  196. getState().inputPrompt = inputPrompt;
  197. }
  198. private boolean isFilteringNeeded() {
  199. return filterstring != null && filterstring.length() > 0
  200. && getFilteringMode() != FilteringMode.OFF;
  201. }
  202. @Override
  203. public void paintContent(PaintTarget target) throws PaintException {
  204. isPainting = true;
  205. try {
  206. // clear caption change listeners
  207. getCaptionChangeListener().clear();
  208. // If the field is modified, but not committed, set modified
  209. // attribute
  210. if (isModified()) {
  211. target.addAttribute("modified", true);
  212. }
  213. if (isNewItemsAllowed()) {
  214. target.addAttribute("allownewitem", true);
  215. }
  216. boolean needNullSelectOption = false;
  217. if (isNullSelectionAllowed()) {
  218. target.addAttribute("nullselect", true);
  219. needNullSelectOption = (getNullSelectionItemId() == null);
  220. if (!needNullSelectOption) {
  221. target.addAttribute("nullselectitem", true);
  222. }
  223. }
  224. // Constructs selected keys array
  225. String[] selectedKeys = new String[(getValue() == null
  226. && getNullSelectionItemId() == null ? 0 : 1)];
  227. if (suggestionPopupWidth != null) {
  228. target.addAttribute("suggestionPopupWidth",
  229. suggestionPopupWidth);
  230. }
  231. // Paints the options and create array of selected id keys
  232. int keyIndex = 0;
  233. target.startTag("options");
  234. if (currentPage < 0) {
  235. optionRequest = false;
  236. currentPage = 0;
  237. filterstring = "";
  238. }
  239. boolean nullFilteredOut = isFilteringNeeded();
  240. // null option is needed and not filtered out, even if not on
  241. // current page
  242. boolean nullOptionVisible = needNullSelectOption
  243. && !nullFilteredOut;
  244. // first try if using container filters is possible
  245. List<?> options = getOptionsWithFilter(nullOptionVisible);
  246. if (null == options) {
  247. // not able to use container filters, perform explicit in-memory
  248. // filtering
  249. options = getFilteredOptions();
  250. filteredSize = options.size();
  251. options = sanitetizeList(options, nullOptionVisible);
  252. }
  253. final boolean paintNullSelection = needNullSelectOption
  254. && currentPage == 0 && !nullFilteredOut;
  255. if (paintNullSelection) {
  256. target.startTag("so");
  257. target.addAttribute("caption", "");
  258. target.addAttribute("key", "");
  259. paintItemStyle(target, null);
  260. target.endTag("so");
  261. }
  262. final Iterator<?> i = options.iterator();
  263. // Paints the available selection options from data source
  264. while (i.hasNext()) {
  265. final Object id = i.next();
  266. if (!isNullSelectionAllowed() && id != null
  267. && id.equals(getNullSelectionItemId())
  268. && !isSelected(id)) {
  269. continue;
  270. }
  271. // Gets the option attribute values
  272. final String key = itemIdMapper.key(id);
  273. final String caption = getItemCaption(id);
  274. final Resource icon = getItemIcon(id);
  275. getCaptionChangeListener().addNotifierForItem(id);
  276. // Paints the option
  277. target.startTag("so");
  278. if (icon != null) {
  279. target.addAttribute("icon", icon);
  280. }
  281. target.addAttribute("caption", caption);
  282. if (id != null && id.equals(getNullSelectionItemId())) {
  283. target.addAttribute("nullselection", true);
  284. }
  285. target.addAttribute("key", key);
  286. if (keyIndex < selectedKeys.length && isSelected(id)) {
  287. // at most one item can be selected at a time
  288. selectedKeys[keyIndex++] = key;
  289. }
  290. paintItemStyle(target, id);
  291. target.endTag("so");
  292. }
  293. target.endTag("options");
  294. target.addAttribute("totalitems", size()
  295. + (needNullSelectOption ? 1 : 0));
  296. if (filteredSize > 0 || nullOptionVisible) {
  297. target.addAttribute("totalMatches", filteredSize
  298. + (nullOptionVisible ? 1 : 0));
  299. }
  300. // Paint variables
  301. target.addVariable(this, "selected", selectedKeys);
  302. if (getValue() != null && selectedKeys[0] == null) {
  303. // not always available, e.g. scrollToSelectedIndex=false
  304. // Give the caption for selected item still, not to make it look
  305. // like there is no selection at all
  306. target.addAttribute("selectedCaption",
  307. getItemCaption(getValue()));
  308. }
  309. if (isNewItemsAllowed()) {
  310. target.addVariable(this, "newitem", "");
  311. }
  312. target.addVariable(this, "filter", filterstring);
  313. target.addVariable(this, "page", currentPage);
  314. currentPage = -1; // current page is always set by client
  315. optionRequest = true;
  316. } finally {
  317. isPainting = false;
  318. }
  319. }
  320. private void paintItemStyle(PaintTarget target, Object itemId)
  321. throws PaintException {
  322. if (itemStyleGenerator != null) {
  323. String style = itemStyleGenerator.getStyle(this, itemId);
  324. if (style != null && !style.isEmpty()) {
  325. target.addAttribute("style", style);
  326. }
  327. }
  328. }
  329. /**
  330. * Sets whether it is possible to input text into the field or whether the
  331. * field area of the component is just used to show what is selected. By
  332. * disabling text input, the comboBox will work in the same way as a
  333. * {@link NativeSelect}
  334. *
  335. * @see #isTextInputAllowed()
  336. *
  337. * @param textInputAllowed
  338. * true to allow entering text, false to just show the current
  339. * selection
  340. */
  341. public void setTextInputAllowed(boolean textInputAllowed) {
  342. getState().textInputAllowed = textInputAllowed;
  343. }
  344. /**
  345. * Returns true if the user can enter text into the field to either filter
  346. * the selections or enter a new value if {@link #isNewItemsAllowed()}
  347. * returns true. If text input is disabled, the comboBox will work in the
  348. * same way as a {@link NativeSelect}
  349. *
  350. * @return
  351. */
  352. public boolean isTextInputAllowed() {
  353. return getState(false).textInputAllowed;
  354. }
  355. @Override
  356. protected ComboBoxState getState() {
  357. return (ComboBoxState) super.getState();
  358. }
  359. @Override
  360. protected ComboBoxState getState(boolean markAsDirty) {
  361. return (ComboBoxState) super.getState(markAsDirty);
  362. }
  363. /**
  364. * Returns the filtered options for the current page using a container
  365. * filter.
  366. *
  367. * As a size effect, {@link #filteredSize} is set to the total number of
  368. * items passing the filter.
  369. *
  370. * The current container must be {@link Filterable} and {@link Indexed}, and
  371. * the filtering mode must be suitable for container filtering (tested with
  372. * {@link #canUseContainerFilter()}).
  373. *
  374. * Use {@link #getFilteredOptions()} and
  375. * {@link #sanitetizeList(List, boolean)} if this is not the case.
  376. *
  377. * @param needNullSelectOption
  378. * @return filtered list of options (may be empty) or null if cannot use
  379. * container filters
  380. */
  381. protected List<?> getOptionsWithFilter(boolean needNullSelectOption) {
  382. Container container = getContainerDataSource();
  383. if (getPageLength() == 0 && !isFilteringNeeded()) {
  384. // no paging or filtering: return all items
  385. filteredSize = container.size();
  386. assert filteredSize >= 0;
  387. return new ArrayList<Object>(container.getItemIds());
  388. }
  389. if (!(container instanceof Filterable)
  390. || !(container instanceof Indexed)
  391. || getItemCaptionMode() != ITEM_CAPTION_MODE_PROPERTY) {
  392. return null;
  393. }
  394. Filterable filterable = (Filterable) container;
  395. Filter filter = buildFilter(filterstring, getFilteringMode());
  396. // adding and removing filters leads to extraneous item set
  397. // change events from the underlying container, but the ComboBox does
  398. // not process or propagate them based on the flag filteringContainer
  399. if (filter != null) {
  400. filterable.addContainerFilter(filter);
  401. }
  402. // try-finally to ensure that the filter is removed from container even
  403. // if a exception is thrown...
  404. try {
  405. Indexed indexed = (Indexed) container;
  406. int indexToEnsureInView = -1;
  407. // if not an option request (item list when user changes page), go
  408. // to page with the selected item after filtering if accepted by
  409. // filter
  410. Object selection = getValue();
  411. if (isScrollToSelectedItem() && !optionRequest && selection != null) {
  412. // ensure proper page
  413. indexToEnsureInView = indexed.indexOfId(selection);
  414. }
  415. filteredSize = container.size();
  416. assert filteredSize >= 0;
  417. currentPage = adjustCurrentPage(currentPage, needNullSelectOption,
  418. indexToEnsureInView, filteredSize);
  419. int first = getFirstItemIndexOnCurrentPage(needNullSelectOption,
  420. filteredSize);
  421. int last = getLastItemIndexOnCurrentPage(needNullSelectOption,
  422. filteredSize, first);
  423. // Compute the number of items to fetch from the indexes given or
  424. // based on the filtered size of the container
  425. int lastItemToFetch = Math.min(last, filteredSize - 1);
  426. int nrOfItemsToFetch = (lastItemToFetch + 1) - first;
  427. List<?> options = indexed.getItemIds(first, nrOfItemsToFetch);
  428. return options;
  429. } finally {
  430. // to the outside, filtering should not be visible
  431. if (filter != null) {
  432. filterable.removeContainerFilter(filter);
  433. }
  434. }
  435. }
  436. /**
  437. * Constructs a filter instance to use when using a Filterable container in
  438. * the <code>ITEM_CAPTION_MODE_PROPERTY</code> mode.
  439. *
  440. * Note that the client side implementation expects the filter string to
  441. * apply to the item caption string it sees, so changing the behavior of
  442. * this method can cause problems.
  443. *
  444. * @param filterString
  445. * @param filteringMode
  446. * @return
  447. */
  448. protected Filter buildFilter(String filterString,
  449. FilteringMode filteringMode) {
  450. Filter filter = null;
  451. if (null != filterString && !"".equals(filterString)) {
  452. switch (filteringMode) {
  453. case OFF:
  454. break;
  455. case STARTSWITH:
  456. filter = new SimpleStringFilter(getItemCaptionPropertyId(),
  457. filterString, true, true);
  458. break;
  459. case CONTAINS:
  460. filter = new SimpleStringFilter(getItemCaptionPropertyId(),
  461. filterString, true, false);
  462. break;
  463. }
  464. }
  465. return filter;
  466. }
  467. @Override
  468. public void containerItemSetChange(Container.ItemSetChangeEvent event) {
  469. if (!isPainting) {
  470. super.containerItemSetChange(event);
  471. }
  472. }
  473. /**
  474. * Makes correct sublist of given list of options.
  475. *
  476. * If paint is not an option request (affected by page or filter change),
  477. * page will be the one where possible selection exists.
  478. *
  479. * Detects proper first and last item in list to return right page of
  480. * options. Also, if the current page is beyond the end of the list, it will
  481. * be adjusted.
  482. *
  483. * @param options
  484. * @param needNullSelectOption
  485. * flag to indicate if nullselect option needs to be taken into
  486. * consideration
  487. */
  488. private List<?> sanitetizeList(List<?> options, boolean needNullSelectOption) {
  489. if (getPageLength() != 0 && options.size() > getPageLength()) {
  490. int indexToEnsureInView = -1;
  491. // if not an option request (item list when user changes page), go
  492. // to page with the selected item after filtering if accepted by
  493. // filter
  494. Object selection = getValue();
  495. if (isScrollToSelectedItem() && !optionRequest && selection != null) {
  496. // ensure proper page
  497. indexToEnsureInView = options.indexOf(selection);
  498. }
  499. int size = options.size();
  500. currentPage = adjustCurrentPage(currentPage, needNullSelectOption,
  501. indexToEnsureInView, size);
  502. int first = getFirstItemIndexOnCurrentPage(needNullSelectOption,
  503. size);
  504. int last = getLastItemIndexOnCurrentPage(needNullSelectOption,
  505. size, first);
  506. return options.subList(first, last + 1);
  507. } else {
  508. return options;
  509. }
  510. }
  511. /**
  512. * Returns the index of the first item on the current page. The index is to
  513. * the underlying (possibly filtered) contents. The null item, if any, does
  514. * not have an index but takes up a slot on the first page.
  515. *
  516. * @param needNullSelectOption
  517. * true if a null option should be shown before any other options
  518. * (takes up the first slot on the first page, not counted in
  519. * index)
  520. * @param size
  521. * number of items after filtering (not including the null item,
  522. * if any)
  523. * @return first item to show on the UI (index to the filtered list of
  524. * options, not taking the null item into consideration if any)
  525. */
  526. private int getFirstItemIndexOnCurrentPage(boolean needNullSelectOption,
  527. int size) {
  528. // Not all options are visible, find out which ones are on the
  529. // current "page".
  530. int first = currentPage * getPageLength();
  531. if (needNullSelectOption && currentPage > 0) {
  532. first--;
  533. }
  534. return first;
  535. }
  536. /**
  537. * Returns the index of the last item on the current page. The index is to
  538. * the underlying (possibly filtered) contents. If needNullSelectOption is
  539. * true, the null item takes up the first slot on the first page,
  540. * effectively reducing the first page size by one.
  541. *
  542. * @param needNullSelectOption
  543. * true if a null option should be shown before any other options
  544. * (takes up the first slot on the first page, not counted in
  545. * index)
  546. * @param size
  547. * number of items after filtering (not including the null item,
  548. * if any)
  549. * @param first
  550. * index in the filtered view of the first item of the page
  551. * @return index in the filtered view of the last item on the page
  552. */
  553. private int getLastItemIndexOnCurrentPage(boolean needNullSelectOption,
  554. int size, int first) {
  555. // page length usable for non-null items
  556. int effectivePageLength = getPageLength()
  557. - (needNullSelectOption && (currentPage == 0) ? 1 : 0);
  558. return Math.min(size - 1, first + effectivePageLength - 1);
  559. }
  560. /**
  561. * Adjusts the index of the current page if necessary: make sure the current
  562. * page is not after the end of the contents, and optionally go to the page
  563. * containg a specific item. There are no side effects but the adjusted page
  564. * index is returned.
  565. *
  566. * @param page
  567. * page number to use as the starting point
  568. * @param needNullSelectOption
  569. * true if a null option should be shown before any other options
  570. * (takes up the first slot on the first page, not counted in
  571. * index)
  572. * @param indexToEnsureInView
  573. * index of an item that should be included on the page (in the
  574. * data set, not counting the null item if any), -1 for none
  575. * @param size
  576. * number of items after filtering (not including the null item,
  577. * if any)
  578. */
  579. private int adjustCurrentPage(int page, boolean needNullSelectOption,
  580. int indexToEnsureInView, int size) {
  581. if (indexToEnsureInView != -1) {
  582. int newPage = (indexToEnsureInView + (needNullSelectOption ? 1 : 0))
  583. / getPageLength();
  584. page = newPage;
  585. }
  586. // adjust the current page if beyond the end of the list
  587. if (page * getPageLength() > size) {
  588. page = (size + (needNullSelectOption ? 1 : 0)) / getPageLength();
  589. }
  590. return page;
  591. }
  592. /**
  593. * Filters the options in memory and returns the full filtered list.
  594. *
  595. * This can be less efficient than using container filters, so use
  596. * {@link #getOptionsWithFilter(boolean)} if possible (filterable container
  597. * and suitable item caption mode etc.).
  598. *
  599. * @return
  600. */
  601. protected List<?> getFilteredOptions() {
  602. if (!isFilteringNeeded()) {
  603. prevfilterstring = null;
  604. filteredOptions = new LinkedList<Object>(getItemIds());
  605. return filteredOptions;
  606. }
  607. if (filterstring.equals(prevfilterstring)) {
  608. return filteredOptions;
  609. }
  610. Collection<?> items;
  611. if (prevfilterstring != null
  612. && filterstring.startsWith(prevfilterstring)) {
  613. items = filteredOptions;
  614. } else {
  615. items = getItemIds();
  616. }
  617. prevfilterstring = filterstring;
  618. filteredOptions = new LinkedList<Object>();
  619. for (final Iterator<?> it = items.iterator(); it.hasNext();) {
  620. final Object itemId = it.next();
  621. String caption = getItemCaption(itemId);
  622. if (caption == null || caption.equals("")) {
  623. continue;
  624. } else {
  625. caption = caption.toLowerCase(getLocale());
  626. }
  627. switch (getFilteringMode()) {
  628. case CONTAINS:
  629. if (caption.indexOf(filterstring) > -1) {
  630. filteredOptions.add(itemId);
  631. }
  632. break;
  633. case STARTSWITH:
  634. default:
  635. if (caption.startsWith(filterstring)) {
  636. filteredOptions.add(itemId);
  637. }
  638. break;
  639. }
  640. }
  641. return filteredOptions;
  642. }
  643. /**
  644. * Invoked when the value of a variable has changed.
  645. *
  646. * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object,
  647. * java.util.Map)
  648. */
  649. @Override
  650. public void changeVariables(Object source, Map<String, Object> variables) {
  651. // Not calling super.changeVariables due the history of select
  652. // component hierarchy
  653. // all the client to server requests are now handled by RPC
  654. }
  655. @Override
  656. public void setFilteringMode(FilteringMode filteringMode) {
  657. getState().filteringMode = filteringMode;
  658. }
  659. @Override
  660. public FilteringMode getFilteringMode() {
  661. return getState(false).filteringMode;
  662. }
  663. @Override
  664. public void addBlurListener(BlurListener listener) {
  665. addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
  666. BlurListener.blurMethod);
  667. }
  668. /**
  669. * @deprecated As of 7.0, replaced by {@link #addBlurListener(BlurListener)}
  670. **/
  671. @Override
  672. @Deprecated
  673. public void addListener(BlurListener listener) {
  674. addBlurListener(listener);
  675. }
  676. @Override
  677. public void removeBlurListener(BlurListener listener) {
  678. removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
  679. }
  680. /**
  681. * @deprecated As of 7.0, replaced by
  682. * {@link #removeBlurListener(BlurListener)}
  683. **/
  684. @Override
  685. @Deprecated
  686. public void removeListener(BlurListener listener) {
  687. removeBlurListener(listener);
  688. }
  689. @Override
  690. public void addFocusListener(FocusListener listener) {
  691. addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
  692. FocusListener.focusMethod);
  693. }
  694. /**
  695. * @deprecated As of 7.0, replaced by
  696. * {@link #addFocusListener(FocusListener)}
  697. **/
  698. @Override
  699. @Deprecated
  700. public void addListener(FocusListener listener) {
  701. addFocusListener(listener);
  702. }
  703. @Override
  704. public void removeFocusListener(FocusListener listener) {
  705. removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
  706. }
  707. /**
  708. * @deprecated As of 7.0, replaced by
  709. * {@link #removeFocusListener(FocusListener)}
  710. **/
  711. @Override
  712. @Deprecated
  713. public void removeListener(FocusListener listener) {
  714. removeFocusListener(listener);
  715. }
  716. /**
  717. * ComboBox does not support multi select mode.
  718. *
  719. * @deprecated As of 7.0, use {@link ListSelect}, {@link OptionGroup} or
  720. * {@link TwinColSelect} instead
  721. * @see com.vaadin.ui.AbstractSelect#setMultiSelect(boolean)
  722. * @throws UnsupportedOperationException
  723. * if trying to activate multiselect mode
  724. */
  725. @Deprecated
  726. @Override
  727. public void setMultiSelect(boolean multiSelect) {
  728. if (multiSelect) {
  729. throw new UnsupportedOperationException("Multiselect not supported");
  730. }
  731. }
  732. /**
  733. * ComboBox does not support multi select mode.
  734. *
  735. * @deprecated As of 7.0, use {@link ListSelect}, {@link OptionGroup} or
  736. * {@link TwinColSelect} instead
  737. *
  738. * @see com.vaadin.ui.AbstractSelect#isMultiSelect()
  739. *
  740. * @return false
  741. */
  742. @Deprecated
  743. @Override
  744. public boolean isMultiSelect() {
  745. return false;
  746. }
  747. /**
  748. * Returns the page length of the suggestion popup.
  749. *
  750. * @return the pageLength
  751. */
  752. public int getPageLength() {
  753. return getState(false).pageLength;
  754. }
  755. /**
  756. * Returns the suggestion pop-up's width as a CSS string.
  757. *
  758. * @see #setPopupWidth
  759. * @since 7.7
  760. */
  761. public String getPopupWidth() {
  762. return suggestionPopupWidth;
  763. }
  764. /**
  765. * Sets the page length for the suggestion popup. Setting the page length to
  766. * 0 will disable suggestion popup paging (all items visible).
  767. *
  768. * @param pageLength
  769. * the pageLength to set
  770. */
  771. public void setPageLength(int pageLength) {
  772. getState().pageLength = pageLength;
  773. }
  774. /**
  775. * Sets the suggestion pop-up's width as a CSS string. By using relative
  776. * units (e.g. "50%") it's possible to set the popup's width relative to the
  777. * ComboBox itself.
  778. *
  779. * @see #getPopupWidth()
  780. * @since 7.7
  781. * @param width
  782. * the width
  783. */
  784. public void setPopupWidth(String width) {
  785. suggestionPopupWidth = width;
  786. markAsDirty();
  787. }
  788. /**
  789. * Sets whether to scroll the selected item visible (directly open the page
  790. * on which it is) when opening the combo box popup or not. Only applies to
  791. * single select mode.
  792. *
  793. * This requires finding the index of the item, which can be expensive in
  794. * many large lazy loading containers.
  795. *
  796. * @param scrollToSelectedItem
  797. * true to find the page with the selected item when opening the
  798. * selection popup
  799. */
  800. public void setScrollToSelectedItem(boolean scrollToSelectedItem) {
  801. this.scrollToSelectedItem = scrollToSelectedItem;
  802. }
  803. /**
  804. * Returns true if the select should find the page with the selected item
  805. * when opening the popup (single select combo box only).
  806. *
  807. * @see #setScrollToSelectedItem(boolean)
  808. *
  809. * @return true if the page with the selected item will be shown when
  810. * opening the popup
  811. */
  812. public boolean isScrollToSelectedItem() {
  813. return scrollToSelectedItem;
  814. }
  815. /**
  816. * Sets the item style generator that is used to produce custom styles for
  817. * showing items in the popup. The CSS class name that will be added to the
  818. * item style names is <tt>v-filterselect-item-[style name]</tt>.
  819. *
  820. * @param itemStyleGenerator
  821. * the item style generator to set, or <code>null</code> to not
  822. * use any custom item styles
  823. * @since 7.5.6
  824. */
  825. public void setItemStyleGenerator(ItemStyleGenerator itemStyleGenerator) {
  826. this.itemStyleGenerator = itemStyleGenerator;
  827. markAsDirty();
  828. }
  829. /**
  830. * Gets the currently used item style generator.
  831. *
  832. * @return the itemStyleGenerator the currently used item style generator,
  833. * or <code>null</code> if no generator is used
  834. * @since 7.5.6
  835. */
  836. public ItemStyleGenerator getItemStyleGenerator() {
  837. return itemStyleGenerator;
  838. }
  839. }