Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

ComboBox.java 32KB

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