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.

Select.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.ui;
  5. import java.util.Collection;
  6. import java.util.HashSet;
  7. import java.util.Iterator;
  8. import java.util.LinkedList;
  9. import java.util.List;
  10. import java.util.Map;
  11. import java.util.Set;
  12. import com.vaadin.data.Container;
  13. import com.vaadin.event.FieldEvents;
  14. import com.vaadin.event.FieldEvents.BlurEvent;
  15. import com.vaadin.event.FieldEvents.BlurListener;
  16. import com.vaadin.event.FieldEvents.FocusEvent;
  17. import com.vaadin.event.FieldEvents.FocusListener;
  18. import com.vaadin.terminal.PaintException;
  19. import com.vaadin.terminal.PaintTarget;
  20. import com.vaadin.terminal.Resource;
  21. import com.vaadin.terminal.gwt.client.ui.VFilterSelect;
  22. /**
  23. * <p>
  24. * A class representing a selection of items the user has selected in a UI. The
  25. * set of choices is presented as a set of {@link com.vaadin.data.Item}s in a
  26. * {@link com.vaadin.data.Container}.
  27. * </p>
  28. *
  29. * <p>
  30. * A <code>Select</code> component may be in single- or multiselect mode.
  31. * Multiselect mode means that more than one item can be selected
  32. * simultaneously.
  33. * </p>
  34. *
  35. * @author IT Mill Ltd.
  36. * @version
  37. * @VERSION@
  38. * @since 3.0
  39. */
  40. @SuppressWarnings("serial")
  41. @ClientWidget(VFilterSelect.class)
  42. public class Select extends AbstractSelect implements AbstractSelect.Filtering,
  43. FieldEvents.BlurNotifier, FieldEvents.FocusNotifier {
  44. /**
  45. * Holds value of property pageLength. 0 disables paging.
  46. */
  47. protected int pageLength = 10;
  48. private int columns = 0;
  49. // Current page when the user is 'paging' trough options
  50. private int currentPage = -1;
  51. private int filteringMode = FILTERINGMODE_STARTSWITH;
  52. private String filterstring;
  53. private String prevfilterstring;
  54. private List filteredOptions;
  55. /**
  56. * Flag to indicate that request repaint is called by filter request only
  57. */
  58. private boolean optionRequest;
  59. /* Constructors */
  60. /* Component methods */
  61. public Select() {
  62. super();
  63. }
  64. public Select(String caption, Collection options) {
  65. super(caption, options);
  66. }
  67. public Select(String caption, Container dataSource) {
  68. super(caption, dataSource);
  69. }
  70. public Select(String caption) {
  71. super(caption);
  72. }
  73. /**
  74. * Paints the content of this component.
  75. *
  76. * @param target
  77. * the Paint Event.
  78. * @throws PaintException
  79. * if the paint operation failed.
  80. */
  81. @Override
  82. public void paintContent(PaintTarget target) throws PaintException {
  83. if (isMultiSelect()) {
  84. // background compatibility hack. This object shouldn't be used for
  85. // multiselect lists anymore (ListSelect instead). This fallbacks to
  86. // a simpler paint method in super class.
  87. super.paintContent(target);
  88. // Fix for #4553
  89. target.addAttribute("type", "legacy-multi");
  90. return;
  91. }
  92. // clear caption change listeners
  93. getCaptionChangeListener().clear();
  94. // The tab ordering number
  95. if (getTabIndex() != 0) {
  96. target.addAttribute("tabindex", getTabIndex());
  97. }
  98. // If the field is modified, but not committed, set modified attribute
  99. if (isModified()) {
  100. target.addAttribute("modified", true);
  101. }
  102. // Adds the required attribute
  103. if (isRequired()) {
  104. target.addAttribute("required", true);
  105. }
  106. if (isNewItemsAllowed()) {
  107. target.addAttribute("allownewitem", true);
  108. }
  109. boolean needNullSelectOption = false;
  110. if (isNullSelectionAllowed()) {
  111. target.addAttribute("nullselect", true);
  112. needNullSelectOption = (getNullSelectionItemId() == null);
  113. if (!needNullSelectOption) {
  114. target.addAttribute("nullselectitem", true);
  115. }
  116. }
  117. // Constructs selected keys array
  118. String[] selectedKeys;
  119. if (isMultiSelect()) {
  120. selectedKeys = new String[((Set) getValue()).size()];
  121. } else {
  122. selectedKeys = new String[(getValue() == null
  123. && getNullSelectionItemId() == null ? 0 : 1)];
  124. }
  125. target.addAttribute("pagelength", pageLength);
  126. target.addAttribute("filteringmode", getFilteringMode());
  127. // Paints the options and create array of selected id keys
  128. // TODO Also use conventional rendering if lazy loading is not supported
  129. // by terminal
  130. int keyIndex = 0;
  131. target.startTag("options");
  132. if (currentPage < 0) {
  133. optionRequest = false;
  134. currentPage = 0;
  135. filterstring = "";
  136. }
  137. List options = getFilteredOptions();
  138. boolean nullFilteredOut = filterstring != null
  139. && !"".equals(filterstring)
  140. && filteringMode != FILTERINGMODE_OFF;
  141. options = sanitetizeList(options, needNullSelectOption
  142. && !nullFilteredOut);
  143. final boolean paintNullSelection = needNullSelectOption
  144. && currentPage == 0 && !nullFilteredOut;
  145. if (paintNullSelection) {
  146. target.startTag("so");
  147. target.addAttribute("caption", "");
  148. target.addAttribute("key", "");
  149. target.endTag("so");
  150. }
  151. final Iterator i = options.iterator();
  152. // Paints the available selection options from data source
  153. while (i.hasNext()) {
  154. final Object id = i.next();
  155. if (!isNullSelectionAllowed() && id != null
  156. && id.equals(getNullSelectionItemId()) && !isSelected(id)) {
  157. continue;
  158. }
  159. // Gets the option attribute values
  160. final String key = itemIdMapper.key(id);
  161. final String caption = getItemCaption(id);
  162. final Resource icon = getItemIcon(id);
  163. getCaptionChangeListener().addNotifierForItem(id);
  164. // Paints the option
  165. target.startTag("so");
  166. if (icon != null) {
  167. target.addAttribute("icon", icon);
  168. }
  169. target.addAttribute("caption", caption);
  170. if (id != null && id.equals(getNullSelectionItemId())) {
  171. target.addAttribute("nullselection", true);
  172. }
  173. target.addAttribute("key", key);
  174. if (isSelected(id) && keyIndex < selectedKeys.length) {
  175. target.addAttribute("selected", true);
  176. selectedKeys[keyIndex++] = key;
  177. }
  178. target.endTag("so");
  179. }
  180. target.endTag("options");
  181. target.addAttribute("totalitems", size()
  182. + (needNullSelectOption ? 1 : 0));
  183. if (filteredOptions != null) {
  184. target.addAttribute("totalMatches", filteredOptions.size()
  185. + (needNullSelectOption && !nullFilteredOut ? 1 : 0));
  186. }
  187. // Paint variables
  188. target.addVariable(this, "selected", selectedKeys);
  189. if (isNewItemsAllowed()) {
  190. target.addVariable(this, "newitem", "");
  191. }
  192. target.addVariable(this, "filter", filterstring);
  193. target.addVariable(this, "page", currentPage);
  194. currentPage = -1; // current page is always set by client
  195. optionRequest = true;
  196. // Hide the error indicator if needed
  197. if (isRequired() && isEmpty() && getComponentError() == null
  198. && getErrorMessage() != null) {
  199. target.addAttribute("hideErrors", true);
  200. }
  201. }
  202. /**
  203. * Makes correct sublist of given list of options.
  204. *
  205. * If paint is not an option request (affected by page or filter change),
  206. * page will be the one where possible selection exists.
  207. *
  208. * Detects proper first and last item in list to return right page of
  209. * options. Also, if the current page is beyond the end of the list, it will
  210. * be adjusted.
  211. *
  212. * @param options
  213. * @param needNullSelectOption
  214. * flag to indicate if nullselect option needs to be taken into
  215. * consideration
  216. */
  217. private List sanitetizeList(List options, boolean needNullSelectOption) {
  218. if (pageLength != 0 && options.size() > pageLength) {
  219. // Not all options are visible, find out which ones are on the
  220. // current "page".
  221. int first = currentPage * pageLength;
  222. int last = first + pageLength;
  223. if (needNullSelectOption) {
  224. if (currentPage > 0) {
  225. first--;
  226. }
  227. last--;
  228. }
  229. if (options.size() < last) {
  230. last = options.size();
  231. }
  232. if (!optionRequest) {
  233. // TODO ensure proper page
  234. if (!isMultiSelect()) {
  235. Object selection = getValue();
  236. if (selection != null) {
  237. int index = options.indexOf(selection);
  238. if (index != -1 && (index < first || index >= last)) {
  239. int newPage = (index + (needNullSelectOption ? 1
  240. : 0)) / pageLength;
  241. currentPage = newPage;
  242. return sanitetizeList(options, needNullSelectOption);
  243. }
  244. }
  245. }
  246. }
  247. // adjust the current page if beyond the end of the list
  248. if (first >= last && currentPage > 0) {
  249. currentPage -= (first - last + pageLength) / pageLength;
  250. return sanitetizeList(options, needNullSelectOption);
  251. }
  252. return options.subList(first, last);
  253. } else {
  254. return options;
  255. }
  256. }
  257. protected List getFilteredOptions() {
  258. if (filterstring == null || filterstring.equals("")
  259. || filteringMode == FILTERINGMODE_OFF) {
  260. prevfilterstring = null;
  261. filteredOptions = new LinkedList(getItemIds());
  262. return filteredOptions;
  263. }
  264. if (filterstring.equals(prevfilterstring)) {
  265. return filteredOptions;
  266. }
  267. Collection items;
  268. if (prevfilterstring != null
  269. && filterstring.startsWith(prevfilterstring)) {
  270. items = filteredOptions;
  271. } else {
  272. items = getItemIds();
  273. }
  274. prevfilterstring = filterstring;
  275. filteredOptions = new LinkedList();
  276. for (final Iterator it = items.iterator(); it.hasNext();) {
  277. final Object itemId = it.next();
  278. String caption = getItemCaption(itemId);
  279. if (caption == null || caption.equals("")) {
  280. continue;
  281. } else {
  282. caption = caption.toLowerCase();
  283. }
  284. switch (filteringMode) {
  285. case FILTERINGMODE_CONTAINS:
  286. if (caption.indexOf(filterstring) > -1) {
  287. filteredOptions.add(itemId);
  288. }
  289. break;
  290. case FILTERINGMODE_STARTSWITH:
  291. default:
  292. if (caption.startsWith(filterstring)) {
  293. filteredOptions.add(itemId);
  294. }
  295. break;
  296. }
  297. }
  298. return filteredOptions;
  299. }
  300. /**
  301. * Invoked when the value of a variable has changed.
  302. *
  303. * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object,
  304. * java.util.Map)
  305. */
  306. @Override
  307. public void changeVariables(Object source, Map variables) {
  308. // Not calling super.changeVariables due the history of select
  309. // component hierarchy
  310. // Selection change
  311. if (variables.containsKey("selected")) {
  312. final String[] ka = (String[]) variables.get("selected");
  313. if (isMultiSelect()) {
  314. // Multiselect mode
  315. // TODO Optimize by adding repaintNotNeeded whan applicaple
  316. // Converts the key-array to id-set
  317. final LinkedList s = new LinkedList();
  318. for (int i = 0; i < ka.length; i++) {
  319. final Object id = itemIdMapper.get(ka[i]);
  320. if (id != null && containsId(id)) {
  321. s.add(id);
  322. }
  323. }
  324. // Limits the deselection to the set of visible items
  325. // (non-visible items can not be deselected)
  326. final Collection visible = getVisibleItemIds();
  327. if (visible != null) {
  328. Set newsel = (Set) getValue();
  329. if (newsel == null) {
  330. newsel = new HashSet();
  331. } else {
  332. newsel = new HashSet(newsel);
  333. }
  334. newsel.removeAll(visible);
  335. newsel.addAll(s);
  336. setValue(newsel, true);
  337. }
  338. } else {
  339. // Single select mode
  340. if (ka.length == 0) {
  341. // Allows deselection only if the deselected item is visible
  342. final Object current = getValue();
  343. final Collection visible = getVisibleItemIds();
  344. if (visible != null && visible.contains(current)) {
  345. setValue(null, true);
  346. }
  347. } else {
  348. final Object id = itemIdMapper.get(ka[0]);
  349. if (id != null && id.equals(getNullSelectionItemId())) {
  350. setValue(null, true);
  351. } else {
  352. setValue(id, true);
  353. }
  354. }
  355. }
  356. }
  357. String newFilter;
  358. if ((newFilter = (String) variables.get("filter")) != null) {
  359. // this is a filter request
  360. currentPage = ((Integer) variables.get("page")).intValue();
  361. filterstring = newFilter;
  362. if (filterstring != null) {
  363. filterstring = filterstring.toLowerCase();
  364. }
  365. optionRepaint();
  366. } else if (isNewItemsAllowed()) {
  367. // New option entered (and it is allowed)
  368. final String newitem = (String) variables.get("newitem");
  369. if (newitem != null && newitem.length() > 0) {
  370. getNewItemHandler().addNewItem(newitem);
  371. // rebuild list
  372. filterstring = null;
  373. prevfilterstring = null;
  374. }
  375. }
  376. if (variables.containsKey(FocusEvent.EVENT_ID)) {
  377. fireEvent(new FocusEvent(this));
  378. }
  379. if (variables.containsKey(BlurEvent.EVENT_ID)) {
  380. fireEvent(new BlurEvent(this));
  381. }
  382. }
  383. @Override
  384. public void requestRepaint() {
  385. super.requestRepaint();
  386. optionRequest = false;
  387. prevfilterstring = filterstring;
  388. filterstring = null;
  389. }
  390. private void optionRepaint() {
  391. super.requestRepaint();
  392. }
  393. public void setFilteringMode(int filteringMode) {
  394. this.filteringMode = filteringMode;
  395. }
  396. public int getFilteringMode() {
  397. return filteringMode;
  398. }
  399. /**
  400. * Note, one should use more generic setWidth(String) method instead of
  401. * this. This now days actually converts columns to width with em css unit.
  402. *
  403. * Sets the number of columns in the editor. If the number of columns is set
  404. * 0, the actual number of displayed columns is determined implicitly by the
  405. * adapter.
  406. *
  407. * @deprecated
  408. *
  409. * @param columns
  410. * the number of columns to set.
  411. */
  412. @Deprecated
  413. public void setColumns(int columns) {
  414. if (columns < 0) {
  415. columns = 0;
  416. }
  417. if (this.columns != columns) {
  418. this.columns = columns;
  419. setWidth(columns, Select.UNITS_EM);
  420. requestRepaint();
  421. }
  422. }
  423. /**
  424. * @deprecated see setter function
  425. * @return
  426. */
  427. @Deprecated
  428. public int getColumns() {
  429. return columns;
  430. }
  431. public void addListener(BlurListener listener) {
  432. addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
  433. BlurListener.blurMethod);
  434. }
  435. public void removeListener(BlurListener listener) {
  436. removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
  437. }
  438. public void addListener(FocusListener listener) {
  439. addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
  440. FocusListener.focusMethod);
  441. }
  442. public void removeListener(FocusListener listener) {
  443. removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
  444. }
  445. }