From 23eb95af9780447222934d1ded4e013d6a90cd48 Mon Sep 17 00:00:00 2001 From: Henri Sara Date: Wed, 25 Nov 2009 12:02:04 +0000 Subject: [PATCH] #3755 Select threw IllegalArgumentException when browsing pages quickly svn changeset:10012/svn branch:6.2 --- src/com/vaadin/ui/Select.java | 863 +++++++++++++++++----------------- 1 file changed, 435 insertions(+), 428 deletions(-) diff --git a/src/com/vaadin/ui/Select.java b/src/com/vaadin/ui/Select.java index af812e4abb..48877d00f1 100644 --- a/src/com/vaadin/ui/Select.java +++ b/src/com/vaadin/ui/Select.java @@ -24,13 +24,13 @@ import com.vaadin.terminal.gwt.client.ui.VFilterSelect; * set of choices is presented as a set of {@link com.vaadin.data.Item}s in a * {@link com.vaadin.data.Container}. *

- * + * *

* A Select component may be in single- or multiselect mode. * Multiselect mode means that more than one item can be selected * simultaneously. *

- * + * * @author IT Mill Ltd. * @version * @VERSION@ @@ -40,431 +40,438 @@ import com.vaadin.terminal.gwt.client.ui.VFilterSelect; @ClientWidget(VFilterSelect.class) public class Select extends AbstractSelect implements AbstractSelect.Filtering { - /** - * Holds value of property pageLength. 0 disables paging. - */ - protected int pageLength = 10; - - private int columns = 0; - - // Current page when the user is 'paging' trough options - private int currentPage = -1; - - private int filteringMode = FILTERINGMODE_STARTSWITH; - - private String filterstring; - private String prevfilterstring; - private List filteredOptions; - - /** - * Flag to indicate that request repaint is called by filter request only - */ - private boolean optionRequest; - - /* Constructors */ - - /* Component methods */ - - public Select() { - super(); - } - - public Select(String caption, Collection options) { - super(caption, options); - } - - public Select(String caption, Container dataSource) { - super(caption, dataSource); - } - - public Select(String caption) { - super(caption); - } - - /** - * Paints the content of this component. - * - * @param target - * the Paint Event. - * @throws PaintException - * if the paint operation failed. - */ - @Override - public void paintContent(PaintTarget target) throws PaintException { - if (isMultiSelect()) { - // background compatibility hack. This object shouldn't be used for - // multiselect lists anymore (ListSelect instead). This fallbacks to - // a simpler paint method in super class. - super.paintContent(target); - return; - } - - // clear caption change listeners - getCaptionChangeListener().clear(); - - // The tab ordering number - if (getTabIndex() != 0) { - target.addAttribute("tabindex", getTabIndex()); - } - - // If the field is modified, but not committed, set modified attribute - if (isModified()) { - target.addAttribute("modified", true); - } - - // Adds the required attribute - if (isRequired()) { - target.addAttribute("required", true); - } - - if (isNewItemsAllowed()) { - target.addAttribute("allownewitem", true); - } - - boolean needNullSelectOption = false; - if (isNullSelectionAllowed()) { - target.addAttribute("nullselect", true); - needNullSelectOption = (getNullSelectionItemId() == null); - if (!needNullSelectOption) { - target.addAttribute("nullselectitem", true); - } - } - - // Constructs selected keys array - String[] selectedKeys; - if (isMultiSelect()) { - selectedKeys = new String[((Set) getValue()).size()]; - } else { - selectedKeys = new String[(getValue() == null - && getNullSelectionItemId() == null ? 0 : 1)]; - } - - target.addAttribute("pagelength", pageLength); - - target.addAttribute("filteringmode", getFilteringMode()); - - // Paints the options and create array of selected id keys - // TODO Also use conventional rendering if lazy loading is not supported - // by terminal - int keyIndex = 0; - - target.startTag("options"); - - if (currentPage < 0) { - optionRequest = false; - currentPage = 0; - filterstring = ""; - } - - List options = getFilteredOptions(); - options = sanitetizeList(options, needNullSelectOption); - - final boolean paintNullSelection = needNullSelectOption - && (currentPage == 0 && (getFilteringMode() == FILTERINGMODE_OFF - || filterstring == null || filterstring.equals(""))); - - if (paintNullSelection) { - target.startTag("so"); - target.addAttribute("caption", ""); - target.addAttribute("key", ""); - target.endTag("so"); - } - - final Iterator i = options.iterator(); - // Paints the available selection options from data source - - while (i.hasNext()) { - - final Object id = i.next(); - - if (!isNullSelectionAllowed() && id != null - && id.equals(getNullSelectionItemId()) && !isSelected(id)) { - continue; - } - - // Gets the option attribute values - final String key = itemIdMapper.key(id); - final String caption = getItemCaption(id); - final Resource icon = getItemIcon(id); - getCaptionChangeListener().addNotifierForItem(id); - - // Paints the option - target.startTag("so"); - if (icon != null) { - target.addAttribute("icon", icon); - } - target.addAttribute("caption", caption); - if (id != null && id.equals(getNullSelectionItemId())) { - target.addAttribute("nullselection", true); - } - target.addAttribute("key", key); - if (isSelected(id) && keyIndex < selectedKeys.length) { - target.addAttribute("selected", true); - selectedKeys[keyIndex++] = key; - } - target.endTag("so"); - } - target.endTag("options"); - - target.addAttribute("totalitems", size() - + (needNullSelectOption ? 1 : 0)); - if (filteredOptions != null) { - target.addAttribute("totalMatches", filteredOptions.size() - + (needNullSelectOption ? 1 : 0)); - } - - // Paint variables - target.addVariable(this, "selected", selectedKeys); - if (isNewItemsAllowed()) { - target.addVariable(this, "newitem", ""); - } - - target.addVariable(this, "filter", filterstring); - target.addVariable(this, "page", currentPage); - - currentPage = -1; // current page is always set by client - - optionRequest = true; - } - - /** - * Makes correct sublist of given list of options. - * - * If paint is not an option request (affected by page or filter change), - * page will be the one where possible selection exists. - * - * Detects proper first and last item in list to return right page of - * options. - * - * @param options - * @param needNullSelectOption - * flag to indicate if nullselect option needs to be taken into - * consideration - */ - private List sanitetizeList(List options, boolean needNullSelectOption) { - - if (options.size() > pageLength) { - int first = currentPage * pageLength; - int last = first + pageLength; - if (needNullSelectOption) { - if (currentPage > 0) { - first--; - } - last--; - } - if (options.size() < last) { - last = options.size(); - } - if (!optionRequest) { - // TODO ensure proper page - if (!isMultiSelect()) { - Object selection = getValue(); - if (selection != null) { - int index = options.indexOf(selection); - if (index != -1 && (index < first || index >= last)) { - int newPage = (index + (needNullSelectOption ? 1 - : 0)) - / pageLength; - currentPage = newPage; - return sanitetizeList(options, needNullSelectOption); - } - } - } - } - - return options.subList(first, last); - } else { - return options; - } - } - - protected List getFilteredOptions() { - if (filterstring == null || filterstring.equals("") - || filteringMode == FILTERINGMODE_OFF) { - prevfilterstring = null; - filteredOptions = new LinkedList(getItemIds()); - return filteredOptions; - } - - if (filterstring.equals(prevfilterstring)) { - return filteredOptions; - } - - Collection items; - if (prevfilterstring != null - && filterstring.startsWith(prevfilterstring)) { - items = filteredOptions; - } else { - items = getItemIds(); - } - prevfilterstring = filterstring; - - filteredOptions = new LinkedList(); - for (final Iterator it = items.iterator(); it.hasNext();) { - final Object itemId = it.next(); - String caption = getItemCaption(itemId); - if (caption == null || caption.equals("")) { - continue; - } else { - caption = caption.toLowerCase(); - } - switch (filteringMode) { - case FILTERINGMODE_CONTAINS: - if (caption.indexOf(filterstring) > -1) { - filteredOptions.add(itemId); - } - break; - case FILTERINGMODE_STARTSWITH: - default: - if (caption.startsWith(filterstring)) { - filteredOptions.add(itemId); - } - break; - } - } - - return filteredOptions; - } - - /** - * Invoked when the value of a variable has changed. - * - * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object, - * java.util.Map) - */ - @Override - public void changeVariables(Object source, Map variables) { - - // Selection change - if (variables.containsKey("selected")) { - final String[] ka = (String[]) variables.get("selected"); - - if (isMultiSelect()) { - // Multiselect mode - - // TODO Optimize by adding repaintNotNeeded whan applicaple - - // Converts the key-array to id-set - final LinkedList s = new LinkedList(); - for (int i = 0; i < ka.length; i++) { - final Object id = itemIdMapper.get(ka[i]); - if (id != null && containsId(id)) { - s.add(id); - } - } - - // Limits the deselection to the set of visible items - // (non-visible items can not be deselected) - final Collection visible = getVisibleItemIds(); - if (visible != null) { - Set newsel = (Set) getValue(); - if (newsel == null) { - newsel = new HashSet(); - } else { - newsel = new HashSet(newsel); - } - newsel.removeAll(visible); - newsel.addAll(s); - setValue(newsel, true); - } - } else { - // Single select mode - if (ka.length == 0) { - - // Allows deselection only if the deselected item is visible - final Object current = getValue(); - final Collection visible = getVisibleItemIds(); - if (visible != null && visible.contains(current)) { - setValue(null, true); - } - } else { - final Object id = itemIdMapper.get(ka[0]); - if (id != null && id.equals(getNullSelectionItemId())) { - setValue(null, true); - } else { - setValue(id, true); - } - } - } - } - - String newFilter; - if ((newFilter = (String) variables.get("filter")) != null) { - // this is a filter request - currentPage = ((Integer) variables.get("page")).intValue(); - filterstring = newFilter; - if (filterstring != null) { - filterstring = filterstring.toLowerCase(); - } - optionRepaint(); - return; - } - - // New option entered (and it is allowed) - if (isNewItemsAllowed()) { - final String newitem = (String) variables.get("newitem"); - if (newitem != null && newitem.length() > 0) { - getNewItemHandler().addNewItem(newitem); - // rebuild list - filterstring = null; - prevfilterstring = null; - } - } - - } - - @Override - public void requestRepaint() { - super.requestRepaint(); - optionRequest = false; - prevfilterstring = filterstring; - filterstring = null; - } - - private void optionRepaint() { - super.requestRepaint(); - } - - public void setFilteringMode(int filteringMode) { - this.filteringMode = filteringMode; - } - - public int getFilteringMode() { - return filteringMode; - } - - /** - * Note, one should use more generic setWidth(String) method instead of - * this. This now days actually converts columns to width with em css unit. - * - * Sets the number of columns in the editor. If the number of columns is set - * 0, the actual number of displayed columns is determined implicitly by the - * adapter. - * - * @deprecated - * - * @param columns - * the number of columns to set. - */ - @Deprecated - public void setColumns(int columns) { - if (columns < 0) { - columns = 0; - } - if (this.columns != columns) { - this.columns = columns; - setWidth(columns, Select.UNITS_EM); - requestRepaint(); - } - } - - /** - * @deprecated see setter function - * @return - */ - @Deprecated - public int getColumns() { - return columns; - } + /** + * Holds value of property pageLength. 0 disables paging. + */ + protected int pageLength = 10; + + private int columns = 0; + + // Current page when the user is 'paging' trough options + private int currentPage = -1; + + private int filteringMode = FILTERINGMODE_STARTSWITH; + + private String filterstring; + private String prevfilterstring; + private List filteredOptions; + + /** + * Flag to indicate that request repaint is called by filter request only + */ + private boolean optionRequest; + + /* Constructors */ + + /* Component methods */ + + public Select() { + super(); + } + + public Select(String caption, Collection options) { + super(caption, options); + } + + public Select(String caption, Container dataSource) { + super(caption, dataSource); + } + + public Select(String caption) { + super(caption); + } + + /** + * Paints the content of this component. + * + * @param target + * the Paint Event. + * @throws PaintException + * if the paint operation failed. + */ + @Override + public void paintContent(PaintTarget target) throws PaintException { + if (isMultiSelect()) { + // background compatibility hack. This object shouldn't be used for + // multiselect lists anymore (ListSelect instead). This fallbacks to + // a simpler paint method in super class. + super.paintContent(target); + return; + } + + // clear caption change listeners + getCaptionChangeListener().clear(); + + // The tab ordering number + if (getTabIndex() != 0) { + target.addAttribute("tabindex", getTabIndex()); + } + + // If the field is modified, but not committed, set modified attribute + if (isModified()) { + target.addAttribute("modified", true); + } + + // Adds the required attribute + if (isRequired()) { + target.addAttribute("required", true); + } + + if (isNewItemsAllowed()) { + target.addAttribute("allownewitem", true); + } + + boolean needNullSelectOption = false; + if (isNullSelectionAllowed()) { + target.addAttribute("nullselect", true); + needNullSelectOption = (getNullSelectionItemId() == null); + if (!needNullSelectOption) { + target.addAttribute("nullselectitem", true); + } + } + + // Constructs selected keys array + String[] selectedKeys; + if (isMultiSelect()) { + selectedKeys = new String[((Set) getValue()).size()]; + } else { + selectedKeys = new String[(getValue() == null + && getNullSelectionItemId() == null ? 0 : 1)]; + } + + target.addAttribute("pagelength", pageLength); + + target.addAttribute("filteringmode", getFilteringMode()); + + // Paints the options and create array of selected id keys + // TODO Also use conventional rendering if lazy loading is not supported + // by terminal + int keyIndex = 0; + + target.startTag("options"); + + if (currentPage < 0) { + optionRequest = false; + currentPage = 0; + filterstring = ""; + } + + List options = getFilteredOptions(); + options = sanitetizeList(options, needNullSelectOption); + + final boolean paintNullSelection = needNullSelectOption + && (currentPage == 0 && (getFilteringMode() == FILTERINGMODE_OFF + || filterstring == null || filterstring.equals(""))); + + if (paintNullSelection) { + target.startTag("so"); + target.addAttribute("caption", ""); + target.addAttribute("key", ""); + target.endTag("so"); + } + + final Iterator i = options.iterator(); + // Paints the available selection options from data source + + while (i.hasNext()) { + + final Object id = i.next(); + + if (!isNullSelectionAllowed() && id != null + && id.equals(getNullSelectionItemId()) && !isSelected(id)) { + continue; + } + + // Gets the option attribute values + final String key = itemIdMapper.key(id); + final String caption = getItemCaption(id); + final Resource icon = getItemIcon(id); + getCaptionChangeListener().addNotifierForItem(id); + + // Paints the option + target.startTag("so"); + if (icon != null) { + target.addAttribute("icon", icon); + } + target.addAttribute("caption", caption); + if (id != null && id.equals(getNullSelectionItemId())) { + target.addAttribute("nullselection", true); + } + target.addAttribute("key", key); + if (isSelected(id) && keyIndex < selectedKeys.length) { + target.addAttribute("selected", true); + selectedKeys[keyIndex++] = key; + } + target.endTag("so"); + } + target.endTag("options"); + + target.addAttribute("totalitems", size() + + (needNullSelectOption ? 1 : 0)); + if (filteredOptions != null) { + target.addAttribute("totalMatches", filteredOptions.size() + + (needNullSelectOption ? 1 : 0)); + } + + // Paint variables + target.addVariable(this, "selected", selectedKeys); + if (isNewItemsAllowed()) { + target.addVariable(this, "newitem", ""); + } + + target.addVariable(this, "filter", filterstring); + target.addVariable(this, "page", currentPage); + + currentPage = -1; // current page is always set by client + + optionRequest = true; + } + + /** + * Makes correct sublist of given list of options. + * + * If paint is not an option request (affected by page or filter change), + * page will be the one where possible selection exists. + * + * Detects proper first and last item in list to return right page of + * options. Also, if the current page is beyond the end of the list, it will + * be adjusted. + * + * @param options + * @param needNullSelectOption + * flag to indicate if nullselect option needs to be taken into + * consideration + */ + private List sanitetizeList(List options, boolean needNullSelectOption) { + + if (options.size() > pageLength) { + int first = currentPage * pageLength; + int last = first + pageLength; + if (needNullSelectOption) { + if (currentPage > 0) { + first--; + } + last--; + } + if (options.size() < last) { + last = options.size(); + } + if (!optionRequest) { + // TODO ensure proper page + if (!isMultiSelect()) { + Object selection = getValue(); + if (selection != null) { + int index = options.indexOf(selection); + if (index != -1 && (index < first || index >= last)) { + int newPage = (index + (needNullSelectOption ? 1 + : 0)) + / pageLength; + currentPage = newPage; + return sanitetizeList(options, needNullSelectOption); + } + } + } + } + + // adjust the current page if beyond the end of the list + if (first >= last && currentPage > 0) { + currentPage -= (first - last + pageLength) / pageLength; + return sanitetizeList(options, needNullSelectOption); + } + + return options.subList(first, last); + } else { + return options; + } + } + + protected List getFilteredOptions() { + if (filterstring == null || filterstring.equals("") + || filteringMode == FILTERINGMODE_OFF) { + prevfilterstring = null; + filteredOptions = new LinkedList(getItemIds()); + return filteredOptions; + } + + if (filterstring.equals(prevfilterstring)) { + return filteredOptions; + } + + Collection items; + if (prevfilterstring != null + && filterstring.startsWith(prevfilterstring)) { + items = filteredOptions; + } else { + items = getItemIds(); + } + prevfilterstring = filterstring; + + filteredOptions = new LinkedList(); + for (final Iterator it = items.iterator(); it.hasNext();) { + final Object itemId = it.next(); + String caption = getItemCaption(itemId); + if (caption == null || caption.equals("")) { + continue; + } else { + caption = caption.toLowerCase(); + } + switch (filteringMode) { + case FILTERINGMODE_CONTAINS: + if (caption.indexOf(filterstring) > -1) { + filteredOptions.add(itemId); + } + break; + case FILTERINGMODE_STARTSWITH: + default: + if (caption.startsWith(filterstring)) { + filteredOptions.add(itemId); + } + break; + } + } + + return filteredOptions; + } + + /** + * Invoked when the value of a variable has changed. + * + * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object, + * java.util.Map) + */ + @Override + public void changeVariables(Object source, Map variables) { + + // Selection change + if (variables.containsKey("selected")) { + final String[] ka = (String[]) variables.get("selected"); + + if (isMultiSelect()) { + // Multiselect mode + + // TODO Optimize by adding repaintNotNeeded whan applicaple + + // Converts the key-array to id-set + final LinkedList s = new LinkedList(); + for (int i = 0; i < ka.length; i++) { + final Object id = itemIdMapper.get(ka[i]); + if (id != null && containsId(id)) { + s.add(id); + } + } + + // Limits the deselection to the set of visible items + // (non-visible items can not be deselected) + final Collection visible = getVisibleItemIds(); + if (visible != null) { + Set newsel = (Set) getValue(); + if (newsel == null) { + newsel = new HashSet(); + } else { + newsel = new HashSet(newsel); + } + newsel.removeAll(visible); + newsel.addAll(s); + setValue(newsel, true); + } + } else { + // Single select mode + if (ka.length == 0) { + + // Allows deselection only if the deselected item is visible + final Object current = getValue(); + final Collection visible = getVisibleItemIds(); + if (visible != null && visible.contains(current)) { + setValue(null, true); + } + } else { + final Object id = itemIdMapper.get(ka[0]); + if (id != null && id.equals(getNullSelectionItemId())) { + setValue(null, true); + } else { + setValue(id, true); + } + } + } + } + + String newFilter; + if ((newFilter = (String) variables.get("filter")) != null) { + // this is a filter request + currentPage = ((Integer) variables.get("page")).intValue(); + filterstring = newFilter; + if (filterstring != null) { + filterstring = filterstring.toLowerCase(); + } + optionRepaint(); + return; + } + + // New option entered (and it is allowed) + if (isNewItemsAllowed()) { + final String newitem = (String) variables.get("newitem"); + if (newitem != null && newitem.length() > 0) { + getNewItemHandler().addNewItem(newitem); + // rebuild list + filterstring = null; + prevfilterstring = null; + } + } + + } + + @Override + public void requestRepaint() { + super.requestRepaint(); + optionRequest = false; + prevfilterstring = filterstring; + filterstring = null; + } + + private void optionRepaint() { + super.requestRepaint(); + } + + public void setFilteringMode(int filteringMode) { + this.filteringMode = filteringMode; + } + + public int getFilteringMode() { + return filteringMode; + } + + /** + * Note, one should use more generic setWidth(String) method instead of + * this. This now days actually converts columns to width with em css unit. + * + * Sets the number of columns in the editor. If the number of columns is set + * 0, the actual number of displayed columns is determined implicitly by the + * adapter. + * + * @deprecated + * + * @param columns + * the number of columns to set. + */ + @Deprecated + public void setColumns(int columns) { + if (columns < 0) { + columns = 0; + } + if (this.columns != columns) { + this.columns = columns; + setWidth(columns, Select.UNITS_EM); + requestRepaint(); + } + } + + /** + * @deprecated see setter function + * @return + */ + @Deprecated + public int getColumns() { + return columns; + } } -- 2.39.5