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 15KB

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