Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.vaadin.v7.ui.AbstractSelect Maven / Gradle / Ivy
/*
* Copyright (C) 2000-2024 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See for the full
* license.
*/
package com.vaadin.v7.ui;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EventObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jsoup.nodes.Element;
import com.vaadin.event.Transferable;
import com.vaadin.event.dd.DragAndDropEvent;
import com.vaadin.event.dd.DropTarget;
import com.vaadin.event.dd.TargetDetailsImpl;
import com.vaadin.event.dd.acceptcriteria.ClientSideCriterion;
import com.vaadin.event.dd.acceptcriteria.ContainsDataFlavor;
import com.vaadin.event.dd.acceptcriteria.TargetDetailIs;
import com.vaadin.server.KeyMapper;
import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
import com.vaadin.server.Resource;
import com.vaadin.server.VaadinSession;
import com.vaadin.shared.ui.dd.VerticalDropLocation;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.AbstractListing;
import com.vaadin.ui.Component;
import com.vaadin.ui.LegacyComponent;
import com.vaadin.ui.declarative.DesignAttributeHandler;
import com.vaadin.ui.declarative.DesignContext;
import com.vaadin.ui.declarative.DesignException;
import com.vaadin.ui.declarative.DesignFormatter;
import com.vaadin.v7.data.Container;
import com.vaadin.v7.data.Item;
import com.vaadin.v7.data.Property;
import com.vaadin.v7.data.Validator.InvalidValueException;
import com.vaadin.v7.data.util.IndexedContainer;
import com.vaadin.v7.data.util.converter.Converter;
import com.vaadin.v7.data.util.converter.Converter.ConversionException;
import com.vaadin.v7.data.util.converter.ConverterUtil;
import com.vaadin.v7.event.DataBoundTransferable;
import com.vaadin.v7.shared.ui.combobox.FilteringMode;
import com.vaadin.v7.shared.ui.select.AbstractSelectState;
/**
*
* A class representing a selection of items the user has selected in a UI. The
* set of choices is presented as a set of {@link Item}s in a {@link Container}.
*
*
*
* A Select
component may be in single- or multiselect mode.
* Multiselect mode means that more than one item can be selected
* simultaneously.
*
*
* @author Vaadin Ltd.
* @since 5.0
* @deprecated As of 8.0, replaced by {@link AbstractListing}
*/
@SuppressWarnings("serial")
@Deprecated
// TODO currently cannot specify type more precisely in case of multi-select
public abstract class AbstractSelect extends AbstractField implements
Container, Container.Viewer, Container.PropertySetChangeListener,
Container.PropertySetChangeNotifier, Container.ItemSetChangeNotifier,
Container.ItemSetChangeListener, LegacyComponent {
@Deprecated
public enum ItemCaptionMode {
/**
* Item caption mode: Item's ID converted to a String using
* {@link VaadinSession#getConverterFactory()} is used as caption.
*/
ID,
/**
* Item caption mode: Item's ID's String
representation is
* used as caption.
*
* @since 7.5.6
*/
ID_TOSTRING,
/**
* Item caption mode: Item's String
representation is used
* as caption.
*/
ITEM,
/**
* Item caption mode: Index of the item is used as caption. The index
* mode can only be used with the containers implementing the
* {@link Container.Indexed} interface.
*/
INDEX,
/**
* Item caption mode: If an Item has a caption it's used, if not, Item's
* ID converted to a String using
* {@link VaadinSession#getConverterFactory()} is used as caption.
* This is the default .
*/
EXPLICIT_DEFAULTS_ID,
/**
* Item caption mode: Captions must be explicitly specified.
*/
EXPLICIT,
/**
* Item caption mode: Only icons are shown, captions are hidden.
*/
ICON_ONLY,
/**
* Item caption mode: Item captions are read from property specified
* with setItemCaptionPropertyId
.
*/
PROPERTY;
}
/**
* @deprecated As of 7.0, use {@link ItemCaptionMode#ID} instead
*/
@Deprecated
public static final ItemCaptionMode ITEM_CAPTION_MODE_ID = ItemCaptionMode.ID;
/**
* @deprecated As of 7.0, use {@link ItemCaptionMode#ITEM} instead
*/
@Deprecated
public static final ItemCaptionMode ITEM_CAPTION_MODE_ITEM = ItemCaptionMode.ITEM;
/**
* @deprecated As of 7.0, use {@link ItemCaptionMode#INDEX} instead
*/
@Deprecated
public static final ItemCaptionMode ITEM_CAPTION_MODE_INDEX = ItemCaptionMode.INDEX;
/**
* @deprecated As of 7.0, use {@link ItemCaptionMode#EXPLICIT_DEFAULTS_ID}
* instead
*/
@Deprecated
public static final ItemCaptionMode ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID = ItemCaptionMode.EXPLICIT_DEFAULTS_ID;
/**
* @deprecated As of 7.0, use {@link ItemCaptionMode#EXPLICIT} instead
*/
@Deprecated
public static final ItemCaptionMode ITEM_CAPTION_MODE_EXPLICIT = ItemCaptionMode.EXPLICIT;
/**
* @deprecated As of 7.0, use {@link ItemCaptionMode#ICON_ONLY} instead
*/
@Deprecated
public static final ItemCaptionMode ITEM_CAPTION_MODE_ICON_ONLY = ItemCaptionMode.ICON_ONLY;
/**
* @deprecated As of 7.0, use {@link ItemCaptionMode#PROPERTY} instead
*/
@Deprecated
public static final ItemCaptionMode ITEM_CAPTION_MODE_PROPERTY = ItemCaptionMode.PROPERTY;
/**
* Interface for option filtering, used to filter options based on user
* entered value. The value is matched to the item caption.
* FilteringMode.OFF
(0) turns the filtering off.
* FilteringMode.STARTSWITH
(1) matches from the start of the
* caption. FilteringMode.CONTAINS
(1) matches anywhere in the
* caption.
*/
@Deprecated
public interface Filtering extends Serializable {
/**
* @deprecated As of 7.0, use {@link FilteringMode#OFF} instead
*/
@Deprecated
public static final FilteringMode FILTERINGMODE_OFF = FilteringMode.OFF;
/**
* @deprecated As of 7.0, use {@link FilteringMode#STARTSWITH} instead
*/
@Deprecated
public static final FilteringMode FILTERINGMODE_STARTSWITH = FilteringMode.STARTSWITH;
/**
* @deprecated As of 7.0, use {@link FilteringMode#CONTAINS} instead
*/
@Deprecated
public static final FilteringMode FILTERINGMODE_CONTAINS = FilteringMode.CONTAINS;
/**
* Sets the option filtering mode.
*
* @param filteringMode
* the filtering mode to use
*/
public void setFilteringMode(FilteringMode filteringMode);
/**
* Gets the current filtering mode.
*
* @return the filtering mode in use
*/
public FilteringMode getFilteringMode();
}
/**
* Is the select in multiselect mode?
*/
private boolean multiSelect = false;
/**
* Select options.
*/
protected Container items;
/**
* Is the user allowed to add new options?
*/
private boolean allowNewOptions;
/**
* Keymapper used to map key values.
*/
protected KeyMapper itemIdMapper = new KeyMapper();
/**
* Item icons.
*/
private final Map itemIcons = new HashMap();
/**
* Item captions.
*/
private final Map itemCaptions = new HashMap();
/**
* Item caption mode.
*/
private ItemCaptionMode itemCaptionMode = ItemCaptionMode.EXPLICIT_DEFAULTS_ID;
/**
* Item caption source property id.
*/
private Object itemCaptionPropertyId = null;
/**
* Item icon source property id.
*/
private Object itemIconPropertyId = null;
/**
* List of property set change event listeners.
*/
private Set propertySetEventListeners = null;
/**
* List of item set change event listeners.
*/
private Set itemSetEventListeners = null;
/**
* Item id that represents null selection of this select.
*
*
* Data interface does not support nulls as item ids. Selecting the item
* identified by this id is the same as selecting no items at all. This
* setting only affects the single select mode.
*
*/
private Object nullSelectionItemId = null;
// Null (empty) selection is enabled by default
private boolean nullSelectionAllowed = true;
private NewItemHandler newItemHandler;
// Caption (Item / Property) change listeners
CaptionChangeListener captionChangeListener;
/* Constructors */
/**
* Creates an empty Select. The caption is not used.
*/
public AbstractSelect() {
setContainerDataSource(new IndexedContainer());
}
/**
* Creates an empty Select with caption.
*/
public AbstractSelect(String caption) {
setContainerDataSource(new IndexedContainer());
setCaption(caption);
}
/**
* Creates a new select that is connected to a data-source.
*
* @param caption
* the Caption of the component.
* @param dataSource
* the Container datasource to be selected from by this select.
*/
public AbstractSelect(String caption, Container dataSource) {
setCaption(caption);
setContainerDataSource(dataSource);
}
/**
* Creates a new select that is filled from a collection of option values.
*
* @param caption
* the Caption of this field.
* @param options
* the Collection containing the options.
*/
public AbstractSelect(String caption, Collection> options) {
// Creates the options container and add given options to it
final Container c = new IndexedContainer();
if (options != null) {
for (final Object item : options) {
c.addItem(item);
}
}
setCaption(caption);
setContainerDataSource(c);
}
/* Component methods */
/**
* 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 {
// Paints select attributes
if (isMultiSelect()) {
target.addAttribute("selectmode", "multi");
}
if (isNewItemsAllowed()) {
target.addAttribute("allownewitem", true);
}
if (isNullSelectionAllowed()) {
target.addAttribute("nullselect", true);
if (getNullSelectionItemId() != null) {
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)];
}
// ==
// first remove all previous item/property listeners
getCaptionChangeListener().clear();
// Paints the options and create array of selected id keys
target.startTag("options");
int keyIndex = 0;
// Support for external null selection item id
final Collection> ids = getItemIds();
if (isNullSelectionAllowed() && getNullSelectionItemId() != null
&& !ids.contains(getNullSelectionItemId())) {
final Object id = getNullSelectionItemId();
// Paints option
target.startTag("so");
paintItem(target, id);
if (isSelected(id)) {
selectedKeys[keyIndex++] = itemIdMapper.key(id);
}
target.endTag("so");
}
// Paints the available selection options from data source
for (final Object id : getItemIds()) {
// Gets the option attribute values
if (!isNullSelectionAllowed() && id != null
&& id.equals(getNullSelectionItemId())) {
// Remove item if it's the null selection item but null
// selection is not allowed
continue;
}
final String key = itemIdMapper.key(id);
// add listener for each item, to cause repaint if an item changes
getCaptionChangeListener().addNotifierForItem(id);
target.startTag("so");
paintItem(target, id);
if (isSelected(id) && keyIndex < selectedKeys.length) {
selectedKeys[keyIndex++] = key;
}
target.endTag("so");
}
target.endTag("options");
// ==
// Paint variables
target.addVariable(this, "selected", selectedKeys);
if (isNewItemsAllowed()) {
target.addVariable(this, "newitem", "");
}
}
protected void paintItem(PaintTarget target, Object itemId)
throws PaintException {
final String key = itemIdMapper.key(itemId);
final String caption = getItemCaption(itemId);
final Resource icon = getItemIcon(itemId);
if (icon != null) {
target.addAttribute("icon", icon);
}
target.addAttribute("caption", caption);
if (itemId != null && itemId.equals(getNullSelectionItemId())) {
target.addAttribute("nullselection", true);
}
target.addAttribute("key", key);
if (isSelected(itemId)) {
target.addAttribute("selected", true);
}
}
@Override
public void changeVariables(Object source, Map variables) {
// New option entered (and it is allowed)
if (isNewItemsAllowed()) {
final String newitem = (String) variables.get("newitem");
if (newitem != null && !newitem.isEmpty()) {
getNewItemHandler().addNewItem(newitem);
}
}
// Selection change
if (variables.containsKey("selected")) {
final String[] clientSideSelectedKeys = (String[]) variables
.get("selected");
// Multiselect mode
if (isMultiSelect()) {
// TODO Optimize by adding repaintNotNeeded when applicable
// Converts the key-array to id-set
final LinkedList acceptedSelections = new LinkedList();
for (String key : clientSideSelectedKeys) {
final Object id = itemIdMapper.get(key);
if (!isNullSelectionAllowed()
&& (id == null || id == getNullSelectionItemId())) {
// skip empty selection if nullselection is not allowed
markAsDirty();
} else if (id != null && containsId(id)) {
acceptedSelections.add(id);
}
}
if (!isNullSelectionAllowed() && acceptedSelections.isEmpty()) {
// empty selection not allowed, keep old value
markAsDirty();
return;
}
// Limits the deselection to the set of visible items
// (non-visible items can not be deselected)
Collection> visibleNotSelected = getVisibleItemIds();
if (visibleNotSelected != null) {
visibleNotSelected = new HashSet(
visibleNotSelected);
// Don't remove those that will be added to preserve order
visibleNotSelected.removeAll(acceptedSelections);
@SuppressWarnings("unchecked")
Set newsel = (Set) getValue();
if (newsel == null) {
newsel = new LinkedHashSet();
} else {
newsel = new LinkedHashSet(newsel);
}
newsel.removeAll(visibleNotSelected);
newsel.addAll(acceptedSelections);
setValue(newsel, true);
}
} else {
// Single select mode
if (!isNullSelectionAllowed()
&& (clientSideSelectedKeys.length == 0
|| clientSideSelectedKeys[0] == null
|| clientSideSelectedKeys[0] == getNullSelectionItemId())) {
markAsDirty();
return;
}
if (clientSideSelectedKeys.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 {
String clientSelectedKey = clientSideSelectedKeys[0];
if ("null".equals(clientSelectedKey)
|| itemIdMapper.containsKey(clientSelectedKey)) {
// Happens to work for nullselection
// (get ("null") -> null))
final Object id = itemIdMapper.get(clientSelectedKey);
if (!isNullSelectionAllowed() && id == null) {
markAsDirty();
} else if (id != null
&& id.equals(getNullSelectionItemId())) {
setValue(null, true);
} else {
setValue(id, true);
}
}
}
}
}
}
/**
* TODO refine doc Setter for new item handler, which is called when user
* adds new item in {@code newItemAllowed} mode.
*
* @param newItemHandler
* The new item handler
*/
public void setNewItemHandler(NewItemHandler newItemHandler) {
this.newItemHandler = newItemHandler;
}
/**
* Returns the new item handler, which is called when user adds new item in
* {@code newItemAllowed} mode.
*
* @return NewItemHandler
*/
public NewItemHandler getNewItemHandler() {
if (newItemHandler == null) {
newItemHandler = new DefaultNewItemHandler();
}
return newItemHandler;
}
@Deprecated
public interface NewItemHandler extends Serializable {
void addNewItem(String newItemCaption);
}
/**
* TODO refine doc
*
* This is a default class that handles adding new items that are typed by
* user to selects container.
*
* By extending this class one may implement some logic on new item addition
* like database inserts.
*
*/
@Deprecated
public class DefaultNewItemHandler implements NewItemHandler {
@Override
public void addNewItem(String newItemCaption) {
// Checks for readonly
if (isReadOnly()) {
throw new Property.ReadOnlyException();
}
// Adds new option
if (addItem(newItemCaption) != null) {
// Sets the caption property, if used
if (getItemCaptionPropertyId() != null) {
getContainerProperty(newItemCaption,
getItemCaptionPropertyId())
.setValue(newItemCaption);
}
if (isMultiSelect()) {
Set values = new HashSet((Collection) getValue());
values.add(newItemCaption);
setValue(values);
} else {
setValue(newItemCaption);
}
}
}
}
/**
* Gets the visible item ids. In Select, this returns list of all item ids,
* but can be overridden in subclasses if they paint only part of the items
* to the terminal or null if no items is visible.
*/
public Collection> getVisibleItemIds() {
return getItemIds();
}
/* Property methods */
/**
* Returns the type of the property. getValue
and
* setValue
methods must be compatible with this type: one can
* safely cast getValue
to given type and pass any variable
* assignable to this type as a parameter to setValue
.
*
* @return the Type of the property.
*/
@Override
public Class> getType() {
if (isMultiSelect()) {
return Set.class;
} else {
return Object.class;
}
}
/**
* Gets the selected item id or in multiselect mode a set of selected ids.
*
* @see AbstractField#getValue()
*/
@Override
public Object getValue() {
final Object retValue = super.getValue();
if (isMultiSelect()) {
// If the return value is not a set
if (retValue == null) {
return new HashSet();
}
if (retValue instanceof Set) {
return Collections.unmodifiableSet((Set>) retValue);
} else if (retValue instanceof Collection) {
return new HashSet((Collection>) retValue);
} else {
final Set s = new HashSet();
if (items.containsId(retValue)) {
s.add(retValue);
}
return s;
}
} else {
return retValue;
}
}
/**
* Sets the visible value of the property.
*
*
* The value of the select is the selected item id. If the select is in
* multiselect-mode, the value is a set of selected item keys. In
* multiselect mode all collections of id:s can be assigned.
*
*
* @param newValue
* the New selected item or collection of selected items.
* @see AbstractField#setValue(java.lang.Object)
*/
@Override
public void setValue(Object newValue) throws Property.ReadOnlyException {
if (newValue == getNullSelectionItemId()) {
newValue = null;
}
setValue(newValue, false);
}
/**
* Sets the visible value of the property.
*
*
* The value of the select is the selected item id. If the select is in
* multiselect-mode, the value is a set of selected item keys. In
* multiselect mode all collections of id:s can be assigned.
*
*
* @since 7.5.7
* @param newFieldValue
* the New selected item or collection of selected items.
* @param repaintIsNotNeeded
* True if caller is sure that repaint is not needed.
* @param ignoreReadOnly
* True if read-only check should be omitted.
*/
@Override
protected void setValue(Object newFieldValue, boolean repaintIsNotNeeded,
boolean ignoreReadOnly) throws Property.ReadOnlyException,
ConversionException, InvalidValueException {
if (isMultiSelect()) {
if (newFieldValue == null) {
super.setValue(new LinkedHashSet(), repaintIsNotNeeded,
ignoreReadOnly);
} else if (Collection.class
.isAssignableFrom(newFieldValue.getClass())) {
super.setValue(
new LinkedHashSet(
(Collection>) newFieldValue),
repaintIsNotNeeded, ignoreReadOnly);
}
} else if (newFieldValue == null || items.containsId(newFieldValue)) {
super.setValue(newFieldValue, repaintIsNotNeeded, ignoreReadOnly);
}
}
/* Container methods */
/**
* Gets the item from the container with given id. If the container does not
* contain the requested item, null is returned.
*
* @param itemId
* the item id.
* @return the item from the container.
*/
@Override
public Item getItem(Object itemId) {
return items.getItem(itemId);
}
/**
* Gets the item Id collection from the container.
*
* @return the Collection of item ids.
*/
@Override
public Collection> getItemIds() {
return items.getItemIds();
}
/**
* Gets the property Id collection from the container.
*
* @return the Collection of property ids.
*/
@Override
public Collection> getContainerPropertyIds() {
return items.getContainerPropertyIds();
}
/**
* Gets the property type.
*
* @param propertyId
* the Id identifying the property.
* @see Container#getType(java.lang.Object)
*/
@Override
public Class> getType(Object propertyId) {
return items.getType(propertyId);
}
/**
* Gets the number of items in the container.
*
* @return the Number of items in the container.
*
* @see Container#size()
*/
@Override
public int size() {
int size = items.size();
assert size >= 0;
return size;
}
/**
* Tests, if the collection contains an item with given id.
*
* @param itemId
* the Id the of item to be tested.
*/
@Override
public boolean containsId(Object itemId) {
if (itemId != null) {
return items.containsId(itemId);
} else {
return false;
}
}
/**
* Gets the Property identified by the given itemId and propertyId from the
* Container.
*
* @see Container#getContainerProperty(Object, Object)
*/
@Override
public Property getContainerProperty(Object itemId, Object propertyId) {
return items.getContainerProperty(itemId, propertyId);
}
/**
* Adds the new property to all items. Adds a property with given id, type
* and default value to all items in the container.
*
* This functionality is optional. If the function is unsupported, it always
* returns false.
*
* @return True if the operation succeeded.
* @see Container#addContainerProperty(java.lang.Object, java.lang.Class,
* java.lang.Object)
*/
@Override
public boolean addContainerProperty(Object propertyId, Class> type,
Object defaultValue) throws UnsupportedOperationException {
final boolean retval = items.addContainerProperty(propertyId, type,
defaultValue);
if (retval && !(items instanceof Container.PropertySetChangeNotifier)) {
firePropertySetChange();
}
return retval;
}
/**
* Removes all items from the container.
*
* This functionality is optional. If the function is unsupported, it always
* returns false.
*
* @return True if the operation succeeded.
* @see Container#removeAllItems()
*/
@Override
public boolean removeAllItems() throws UnsupportedOperationException {
final boolean retval = items.removeAllItems();
itemIdMapper.removeAll();
if (retval) {
setValue(null);
if (!(items instanceof Container.ItemSetChangeNotifier)) {
fireItemSetChange();
}
}
return retval;
}
/**
* Creates a new item into container with container managed id. The id of
* the created new item is returned. The item can be fetched with getItem()
* method. if the creation fails, null is returned.
*
* @return the Id of the created item or null in case of failure.
* @see Container#addItem()
*/
@Override
public Object addItem() throws UnsupportedOperationException {
final Object retval = items.addItem();
if (retval != null
&& !(items instanceof Container.ItemSetChangeNotifier)) {
fireItemSetChange();
}
return retval;
}
/**
* Create a new item into container. The created new item is returned and
* ready for setting property values. if the creation fails, null is
* returned. In case the container already contains the item, null is
* returned.
*
* This functionality is optional. If the function is unsupported, it always
* returns null.
*
* @param itemId
* the Identification of the item to be created.
* @return the Created item with the given id, or null in case of failure.
* @see Container#addItem(java.lang.Object)
*/
@Override
public Item addItem(Object itemId) throws UnsupportedOperationException {
final Item retval = items.addItem(itemId);
if (retval != null
&& !(items instanceof Container.ItemSetChangeNotifier)) {
fireItemSetChange();
}
return retval;
}
/**
* Adds given items with given item ids to container.
*
* @since 7.2
* @param itemId
* item identifiers to be added to underlying container
* @throws UnsupportedOperationException
* if the underlying container don't support adding items with
* identifiers
*/
public void addItems(Object... itemId)
throws UnsupportedOperationException {
for (Object id : itemId) {
addItem(id);
}
}
/**
* Adds given items with given item ids to container.
*
* @since 7.2
* @param itemIds
* item identifiers to be added to underlying container
* @throws UnsupportedOperationException
* if the underlying container don't support adding items with
* identifiers
*/
public void addItems(Collection> itemIds)
throws UnsupportedOperationException {
addItems(itemIds.toArray());
}
@Override
public boolean removeItem(Object itemId)
throws UnsupportedOperationException {
unselect(itemId);
final boolean retval = items.removeItem(itemId);
itemIdMapper.remove(itemId);
if (retval && !(items instanceof Container.ItemSetChangeNotifier)) {
fireItemSetChange();
}
return retval;
}
/**
* Checks that the current selection is valid, i.e. the selected item ids
* exist in the container. Updates the selection if one or several selected
* item ids are no longer available in the container.
*/
@SuppressWarnings("unchecked")
public void sanitizeSelection() {
Object value = getValue();
if (value == null) {
return;
}
boolean changed = false;
if (isMultiSelect()) {
Collection valueAsCollection = (Collection) value;
List newSelection = new ArrayList(
valueAsCollection.size());
for (Object subValue : valueAsCollection) {
if (containsId(subValue)) {
newSelection.add(subValue);
} else {
changed = true;
}
}
if (changed) {
setValue(newSelection);
}
} else {
if (!containsId(value)) {
setValue(null);
}
}
}
/**
* Removes the property from all items. Removes a property with given id
* from all the items in the container.
*
* This functionality is optional. If the function is unsupported, it always
* returns false.
*
* @return True if the operation succeeded.
* @see Container#removeContainerProperty(java.lang.Object)
*/
@Override
public boolean removeContainerProperty(Object propertyId)
throws UnsupportedOperationException {
final boolean retval = items.removeContainerProperty(propertyId);
if (retval && !(items instanceof Container.PropertySetChangeNotifier)) {
firePropertySetChange();
}
return retval;
}
/* Container.Viewer methods */
/**
* Sets the Container that serves as the data source of the viewer.
*
* As a side-effect the fields value (selection) is set to null due old
* selection not necessary exists in new Container.
*
* @see Container.Viewer#setContainerDataSource(Container)
*
* @param newDataSource
* the new data source.
*/
@Override
public void setContainerDataSource(Container newDataSource) {
if (newDataSource == null) {
newDataSource = new IndexedContainer();
}
getCaptionChangeListener().clear();
if (items != newDataSource) {
// Removes listeners from the old datasource
if (items != null) {
if (items instanceof Container.ItemSetChangeNotifier) {
((Container.ItemSetChangeNotifier) items)
.removeItemSetChangeListener(this);
}
if (items instanceof Container.PropertySetChangeNotifier) {
((Container.PropertySetChangeNotifier) items)
.removePropertySetChangeListener(this);
}
}
// Assigns new data source
items = newDataSource;
// Clears itemIdMapper also
itemIdMapper.removeAll();
// Adds listeners
if (items != null) {
if (items instanceof Container.ItemSetChangeNotifier) {
((Container.ItemSetChangeNotifier) items)
.addItemSetChangeListener(this);
}
if (items instanceof Container.PropertySetChangeNotifier) {
((Container.PropertySetChangeNotifier) items)
.addPropertySetChangeListener(this);
}
}
/*
* We expect changing the data source should also clean value. See
* #810, #4607, #5281
*/
setValue(null);
markAsDirty();
}
}
/**
* Gets the viewing data-source container.
*
* @see Container.Viewer#getContainerDataSource()
*/
@Override
public Container getContainerDataSource() {
return items;
}
/* Select attributes */
/**
* Is the select in multiselect mode? In multiselect mode
*
* @return the Value of property multiSelect.
*/
public boolean isMultiSelect() {
return multiSelect;
}
/**
* Sets the multiselect mode. Setting multiselect mode false may lose
* selection information: if selected items set contains one or more
* selected items, only one of the selected items is kept as selected.
*
* Subclasses of AbstractSelect can choose not to support changing the
* multiselect mode, and may throw {@link UnsupportedOperationException}.
*
* @param multiSelect
* the New value of property multiSelect.
*/
public void setMultiSelect(boolean multiSelect) {
if (multiSelect && getNullSelectionItemId() != null) {
throw new IllegalStateException(
"Multiselect and NullSelectionItemId can not be set at the same time.");
}
if (multiSelect != this.multiSelect) {
// Selection before mode change
final Object oldValue = getValue();
this.multiSelect = multiSelect;
// Convert the value type
if (multiSelect) {
final Set s = new HashSet();
if (oldValue != null) {
s.add(oldValue);
}
setValue(s);
} else {
final Set> s = (Set>) oldValue;
if (s == null || s.isEmpty()) {
setValue(null);
} else {
// Set the single select to contain only the first
// selected value in the multiselect
setValue(s.iterator().next());
}
}
markAsDirty();
}
}
/**
* Does the select allow adding new options by the user. If true, the new
* options can be added to the Container. The text entered by the user is
* used as id. Note that data-source must allow adding new items.
*
* @return True if additions are allowed.
*/
public boolean isNewItemsAllowed() {
return allowNewOptions;
}
/**
* Enables or disables possibility to add new options by the user.
*
* @param allowNewOptions
* the New value of property allowNewOptions.
*/
public void setNewItemsAllowed(boolean allowNewOptions) {
// Only handle change requests
if (this.allowNewOptions != allowNewOptions) {
this.allowNewOptions = allowNewOptions;
markAsDirty();
}
}
/**
* Override the caption of an item. Setting caption explicitly overrides id,
* item and index captions.
*
* @param itemId
* the id of the item to be recaptioned.
* @param caption
* the New caption.
*/
public void setItemCaption(Object itemId, String caption) {
if (itemId != null) {
itemCaptions.put(itemId, caption);
markAsDirty();
}
}
/**
* Gets the caption of an item. The caption is generated as specified by the
* item caption mode. See setItemCaptionMode()
for more
* details.
*
* @param itemId
* the id of the item to be queried.
* @return the caption for specified item.
*/
public String getItemCaption(Object itemId) {
// Null items can not be found
if (itemId == null) {
return null;
}
String caption = null;
switch (getItemCaptionMode()) {
case ID:
caption = idToCaption(itemId);
break;
case ID_TOSTRING:
caption = itemId.toString();
break;
case INDEX:
if (items instanceof Container.Indexed) {
caption = String
.valueOf(((Container.Indexed) items).indexOfId(itemId));
} else {
caption = "ERROR: Container is not indexed";
}
break;
case ITEM:
final Item i = getItem(itemId);
if (i != null) {
caption = i.toString();
}
break;
case EXPLICIT:
caption = itemCaptions.get(itemId);
break;
case EXPLICIT_DEFAULTS_ID:
caption = itemCaptions.get(itemId);
if (caption == null) {
caption = idToCaption(itemId);
}
break;
case PROPERTY:
final Property> p = getContainerProperty(itemId,
getItemCaptionPropertyId());
if (p != null) {
Object value = p.getValue();
if (value != null) {
caption = value.toString();
}
}
break;
}
// All items must have some captions
return caption != null ? caption : "";
}
private String idToCaption(Object itemId) {
try {
Converter c = (Converter) ConverterUtil
.getConverter(String.class, itemId.getClass(),
getSession());
return ConverterUtil.convertFromModel(itemId, String.class, c,
getLocale());
} catch (Exception e) {
return itemId.toString();
}
}
/**
* Sets the icon for an item.
*
* @param itemId
* the id of the item to be assigned an icon.
* @param icon
* the icon to use or null.
*/
public void setItemIcon(Object itemId, Resource icon) {
if (itemId != null) {
if (icon == null) {
itemIcons.remove(itemId);
} else {
itemIcons.put(itemId, icon);
}
markAsDirty();
}
}
/**
* Gets the item icon.
*
* @param itemId
* the id of the item to be assigned an icon.
* @return the icon for the item or null, if not specified.
*/
public Resource getItemIcon(Object itemId) {
final Resource explicit = itemIcons.get(itemId);
if (explicit != null) {
return explicit;
}
if (getItemIconPropertyId() == null) {
return null;
}
final Property> ip = getContainerProperty(itemId,
getItemIconPropertyId());
if (ip == null) {
return null;
}
final Object icon = ip.getValue();
if (icon instanceof Resource) {
return (Resource) icon;
}
return null;
}
/**
* Sets the item caption mode.
*
* See {@link ItemCaptionMode} for a description of the modes.
*
* {@link ItemCaptionMode#EXPLICIT_DEFAULTS_ID} is the default mode.
*
*
* @param mode
* the One of the modes listed above.
*/
public void setItemCaptionMode(ItemCaptionMode mode) {
if (mode != null) {
itemCaptionMode = mode;
markAsDirty();
}
}
/**
*
* Gets the item caption mode.
*
*
* The mode can be one of the following ones:
*
*
* ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID
: Items
* Id-objects toString
is used as item caption. If caption is
* explicitly specified, it overrides the id-caption.
* ITEM_CAPTION_MODE_ID
: Items Id-objects
* toString
is used as item caption.
* ITEM_CAPTION_MODE_ITEM
: Item-objects
* toString
is used as item caption.
* ITEM_CAPTION_MODE_INDEX
: The index of the item is used
* as item caption. The index mode can only be used with the containers
* implementing Container.Indexed
interface.
* ITEM_CAPTION_MODE_EXPLICIT
: The item captions must be
* explicitly specified.
* ITEM_CAPTION_MODE_PROPERTY
: The item captions are read
* from property, that must be specified with
* setItemCaptionPropertyId
.
*
*
* The ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID
is the default
* mode.
*
* @return the One of the modes listed above.
*/
public ItemCaptionMode getItemCaptionMode() {
return itemCaptionMode;
}
/**
* Sets the item caption property.
*
*
* Setting the id to a existing property implicitly sets the item caption
* mode to ITEM_CAPTION_MODE_PROPERTY
. If the object is in
* ITEM_CAPTION_MODE_PROPERTY
mode, setting caption property id
* null resets the item caption mode to
* ITEM_CAPTION_EXPLICIT_DEFAULTS_ID
.
*
*
* Note that the type of the property used for caption must be String
*
*
* Setting the property id to null disables this feature. The id is null by
* default
*
* .
*
* @param propertyId
* the id of the property.
*
*/
public void setItemCaptionPropertyId(Object propertyId) {
if (propertyId != null) {
itemCaptionPropertyId = propertyId;
setItemCaptionMode(ITEM_CAPTION_MODE_PROPERTY);
markAsDirty();
} else {
itemCaptionPropertyId = null;
if (getItemCaptionMode() == ITEM_CAPTION_MODE_PROPERTY) {
setItemCaptionMode(ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID);
}
markAsDirty();
}
}
/**
* Gets the item caption property.
*
* @return the Id of the property used as item caption source.
*/
public Object getItemCaptionPropertyId() {
return itemCaptionPropertyId;
}
/**
* Sets the item icon property.
*
*
* If the property id is set to a valid value, each item is given an icon
* got from the given property of the items. The type of the property must
* be assignable to Resource.
*
*
*
* Note : The icons set with setItemIcon
function override the
* icons from the property.
*
*
*
* Setting the property id to null disables this feature. The id is null by
* default
*
* .
*
* @param propertyId
* the id of the property that specifies icons for items or null
* @throws IllegalArgumentException
* If the propertyId is not in the container or is not of a
* valid type
*/
public void setItemIconPropertyId(Object propertyId)
throws IllegalArgumentException {
if (propertyId == null) {
itemIconPropertyId = null;
} else if (!getContainerPropertyIds().contains(propertyId)) {
throw new IllegalArgumentException(
"Property id not found in the container");
} else if (Resource.class.isAssignableFrom(getType(propertyId))) {
itemIconPropertyId = propertyId;
} else {
throw new IllegalArgumentException(
"Property type must be assignable to Resource");
}
markAsDirty();
}
/**
* Gets the item icon property.
*
*
* If the property id is set to a valid value, each item is given an icon
* got from the given property of the items. The type of the property must
* be assignable to Icon.
*
*
*
* Note : The icons set with setItemIcon
function override the
* icons from the property.
*
*
*
* Setting the property id to null disables this feature. The id is null by
* default
*
* .
*
* @return the Id of the property containing the item icons.
*/
public Object getItemIconPropertyId() {
return itemIconPropertyId;
}
/**
* Tests if an item is selected.
*
*
* In single select mode testing selection status of the item identified by
* {@link #getNullSelectionItemId()} returns true if the value of the
* property is null.
*
*
* @param itemId
* the Id the of the item to be tested.
* @see #getNullSelectionItemId()
* @see #setNullSelectionItemId(Object)
*
*/
public boolean isSelected(Object itemId) {
if (itemId == null) {
return false;
}
if (isMultiSelect()) {
return ((Set>) getValue()).contains(itemId);
} else {
final Object value = getValue();
return itemId
.equals(value == null ? getNullSelectionItemId() : value);
}
}
/**
* Selects an item.
*
*
* In single select mode selecting item identified by
* {@link #getNullSelectionItemId()} sets the value of the property to null.
*
*
* @param itemId
* the identifier of Item to be selected.
* @see #getNullSelectionItemId()
* @see #setNullSelectionItemId(Object)
*
*/
public void select(Object itemId) {
if (!isMultiSelect()) {
setValue(itemId);
} else if (!isSelected(itemId) && itemId != null
&& items.containsId(itemId)) {
final Set s = new HashSet((Set>) getValue());
s.add(itemId);
setValue(s);
}
}
/**
* Unselects an item.
*
* @param itemId
* the identifier of the Item to be unselected.
* @see #getNullSelectionItemId()
* @see #setNullSelectionItemId(Object)
*
*/
public void unselect(Object itemId) {
if (isSelected(itemId)) {
if (isMultiSelect()) {
final Set s = new HashSet((Set>) getValue());
s.remove(itemId);
setValue(s);
} else {
setValue(null);
}
}
}
/**
* Notifies this listener that the Containers contents has changed.
*
* @see Container.PropertySetChangeListener#containerPropertySetChange(Container.PropertySetChangeEvent)
*/
@Override
public void containerPropertySetChange(
Container.PropertySetChangeEvent event) {
firePropertySetChange();
}
/**
* Adds a new Property set change listener for this Container.
*
* @see Container.PropertySetChangeNotifier#addListener(Container.PropertySetChangeListener)
*/
@Override
public void addPropertySetChangeListener(
Container.PropertySetChangeListener listener) {
if (propertySetEventListeners == null) {
propertySetEventListeners = new LinkedHashSet();
}
propertySetEventListeners.add(listener);
}
/**
* @deprecated As of 7.0, replaced by
* {@link #addPropertySetChangeListener(Container.PropertySetChangeListener)}
*/
@Override
@Deprecated
public void addListener(Container.PropertySetChangeListener listener) {
addPropertySetChangeListener(listener);
}
/**
* Removes a previously registered Property set change listener.
*
* @see Container.PropertySetChangeNotifier#removeListener(Container.PropertySetChangeListener)
*/
@Override
public void removePropertySetChangeListener(
Container.PropertySetChangeListener listener) {
if (propertySetEventListeners != null) {
propertySetEventListeners.remove(listener);
if (propertySetEventListeners.isEmpty()) {
propertySetEventListeners = null;
}
}
}
/**
* @deprecated As of 7.0, replaced by
* {@link #removePropertySetChangeListener(Container.PropertySetChangeListener)}
*/
@Override
@Deprecated
public void removeListener(Container.PropertySetChangeListener listener) {
removePropertySetChangeListener(listener);
}
/**
* Adds an Item set change listener for the object.
*
* @see Container.ItemSetChangeNotifier#addListener(Container.ItemSetChangeListener)
*/
@Override
public void addItemSetChangeListener(
Container.ItemSetChangeListener listener) {
if (itemSetEventListeners == null) {
itemSetEventListeners = new LinkedHashSet();
}
itemSetEventListeners.add(listener);
}
/**
* @deprecated As of 7.0, replaced by
* {@link #addItemSetChangeListener(Container.ItemSetChangeListener)}
*/
@Override
@Deprecated
public void addListener(Container.ItemSetChangeListener listener) {
addItemSetChangeListener(listener);
}
/**
* Removes the Item set change listener from the object.
*
* @see Container.ItemSetChangeNotifier#removeListener(Container.ItemSetChangeListener)
*/
@Override
public void removeItemSetChangeListener(
Container.ItemSetChangeListener listener) {
if (itemSetEventListeners != null) {
itemSetEventListeners.remove(listener);
if (itemSetEventListeners.isEmpty()) {
itemSetEventListeners = null;
}
}
}
/**
* @deprecated As of 7.0, replaced by
* {@link #removeItemSetChangeListener(Container.ItemSetChangeListener)}
*/
@Override
@Deprecated
public void removeListener(Container.ItemSetChangeListener listener) {
removeItemSetChangeListener(listener);
}
@Override
public Collection> getListeners(Class> eventType) {
if (Container.ItemSetChangeEvent.class.isAssignableFrom(eventType)) {
if (itemSetEventListeners == null) {
return Collections.EMPTY_LIST;
} else {
return Collections
.unmodifiableCollection(itemSetEventListeners);
}
} else if (Container.PropertySetChangeEvent.class
.isAssignableFrom(eventType)) {
if (propertySetEventListeners == null) {
return Collections.EMPTY_LIST;
} else {
return Collections
.unmodifiableCollection(propertySetEventListeners);
}
}
return super.getListeners(eventType);
}
/**
* Lets the listener know a Containers Item set has changed.
*
* @see Container.ItemSetChangeListener#containerItemSetChange(Container.ItemSetChangeEvent)
*/
@Override
public void containerItemSetChange(Container.ItemSetChangeEvent event) {
// Clears the item id mapping table
itemIdMapper.removeAll();
// Notify all listeners
fireItemSetChange();
}
/**
* Fires the property set change event.
*/
protected void firePropertySetChange() {
if (propertySetEventListeners != null
&& !propertySetEventListeners.isEmpty()) {
final Container.PropertySetChangeEvent event = new PropertySetChangeEvent(
this);
for (Object l : propertySetEventListeners.toArray()) {
((Container.PropertySetChangeListener) l)
.containerPropertySetChange(event);
}
}
markAsDirty();
}
/**
* Fires the item set change event.
*/
protected void fireItemSetChange() {
if (itemSetEventListeners != null && !itemSetEventListeners.isEmpty()) {
final Container.ItemSetChangeEvent event = new ItemSetChangeEvent(
this);
for (Object l : itemSetEventListeners.toArray()) {
((Container.ItemSetChangeListener) l)
.containerItemSetChange(event);
}
}
markAsDirty();
}
/**
* Implementation of item set change event.
*/
private static class ItemSetChangeEvent extends EventObject
implements Container.ItemSetChangeEvent {
private ItemSetChangeEvent(Container source) {
super(source);
}
/**
* Gets the Property where the event occurred.
*
* @see Container.ItemSetChangeEvent#getContainer()
*/
@Override
public Container getContainer() {
return (Container) getSource();
}
}
/**
* Implementation of property set change event.
*/
private static class PropertySetChangeEvent extends EventObject
implements Container.PropertySetChangeEvent {
private PropertySetChangeEvent(Container source) {
super(source);
}
/**
* Retrieves the Container whose contents have been modified.
*
* @see Container.PropertySetChangeEvent#getContainer()
*/
@Override
public Container getContainer() {
return (Container) getSource();
}
}
/**
* For multi-selectable fields, also an empty collection of values is
* considered to be an empty field.
*
* @see AbstractField#isEmpty()
*/
@Override
public boolean isEmpty() {
if (!multiSelect) {
return super.isEmpty();
} else {
Object value = getValue();
return super.isEmpty() || (value instanceof Collection
&& ((Collection>) value).isEmpty());
}
}
/**
* Allow or disallow empty selection by the user. If the select is in
* single-select mode, you can make an item represent the empty selection by
* calling setNullSelectionItemId()
. This way you can for
* instance set an icon and caption for the null selection item.
*
* @param nullSelectionAllowed
* whether or not to allow empty selection
* @see #setNullSelectionItemId(Object)
* @see #isNullSelectionAllowed()
*/
public void setNullSelectionAllowed(boolean nullSelectionAllowed) {
if (nullSelectionAllowed != this.nullSelectionAllowed) {
this.nullSelectionAllowed = nullSelectionAllowed;
markAsDirty();
}
}
/**
* Checks if null empty selection is allowed by the user.
*
* @return whether or not empty selection is allowed
* @see #setNullSelectionAllowed(boolean)
*/
public boolean isNullSelectionAllowed() {
return nullSelectionAllowed;
}
/**
* Returns the item id that represents null value of this select in single
* select mode.
*
*
* Data interface does not support nulls as item ids. Selecting the item
* identified by this id is the same as selecting no items at all. This
* setting only affects the single select mode.
*
*
* @return the Object Null value item id.
* @see #setNullSelectionItemId(Object)
* @see #isSelected(Object)
* @see #select(Object)
*/
public Object getNullSelectionItemId() {
return nullSelectionItemId;
}
/**
* Sets the item id that represents null value of this select.
*
*
* Data interface does not support nulls as item ids. Selecting the item
* identified by this id is the same as selecting no items at all. This
* setting only affects the single select mode.
*
*
* @param nullSelectionItemId
* the nullSelectionItemId to set.
* @see #getNullSelectionItemId()
* @see #isSelected(Object)
* @see #select(Object)
*/
public void setNullSelectionItemId(Object nullSelectionItemId) {
if (nullSelectionItemId != null && isMultiSelect()) {
throw new IllegalStateException(
"Multiselect and NullSelectionItemId can not be set at the same time.");
}
this.nullSelectionItemId = nullSelectionItemId;
}
/**
* Notifies the component that it is connected to an application.
*
* @see AbstractField#attach()
*/
@Override
public void attach() {
super.attach();
}
/**
* Detaches the component from application.
*
* @see AbstractComponent#detach()
*/
@Override
public void detach() {
getCaptionChangeListener().clear();
super.detach();
}
// Caption change listener
protected CaptionChangeListener getCaptionChangeListener() {
if (captionChangeListener == null) {
captionChangeListener = new CaptionChangeListener();
}
return captionChangeListener;
}
/**
* This is a listener helper for Item and Property changes that should cause
* a repaint. It should be attached to all items that are displayed, and the
* default implementation does this in paintContent(). Especially
* "lazyloading" components should take care to add and remove listeners as
* appropriate. Call addNotifierForItem() for each painted item (and
* remember to clear).
*
* NOTE: singleton, use getCaptionChangeListener().
*
*/
@Deprecated
protected class CaptionChangeListener implements
Item.PropertySetChangeListener, Property.ValueChangeListener {
// TODO clean this up - type is either Item.PropertySetChangeNotifier or
// Property.ValueChangeNotifier
HashSet captionChangeNotifiers = new HashSet();
public void addNotifierForItem(Object itemId) {
switch (getItemCaptionMode()) {
case ITEM:
final Item i = getItem(itemId);
if (i == null) {
return;
}
if (i instanceof Item.PropertySetChangeNotifier) {
((Item.PropertySetChangeNotifier) i)
.addPropertySetChangeListener(
getCaptionChangeListener());
captionChangeNotifiers.add(i);
}
Collection> pids = i.getItemPropertyIds();
if (pids != null) {
for (Object id : pids) {
Property> p = i.getItemProperty(id);
if (p instanceof Property.ValueChangeNotifier) {
((Property.ValueChangeNotifier) p)
.addValueChangeListener(
getCaptionChangeListener());
captionChangeNotifiers.add(p);
}
}
}
break;
case PROPERTY:
final Property> p = getContainerProperty(itemId,
getItemCaptionPropertyId());
if (p instanceof Property.ValueChangeNotifier) {
((Property.ValueChangeNotifier) p)
.addValueChangeListener(getCaptionChangeListener());
captionChangeNotifiers.add(p);
}
break;
}
if (getItemIconPropertyId() != null) {
final Property p = getContainerProperty(itemId,
getItemIconPropertyId());
if (p instanceof Property.ValueChangeNotifier) {
((Property.ValueChangeNotifier) p)
.addValueChangeListener(getCaptionChangeListener());
captionChangeNotifiers.add(p);
}
}
}
public void clear() {
for (Object notifier : captionChangeNotifiers) {
if (notifier instanceof Item.PropertySetChangeNotifier) {
((Item.PropertySetChangeNotifier) notifier)
.removePropertySetChangeListener(
getCaptionChangeListener());
} else {
((Property.ValueChangeNotifier) notifier)
.removeValueChangeListener(
getCaptionChangeListener());
}
}
captionChangeNotifiers.clear();
}
@Override
public void valueChange(Property.ValueChangeEvent event) {
markAsDirty();
}
@Override
public void itemPropertySetChange(Item.PropertySetChangeEvent event) {
markAsDirty();
}
}
/**
* Criterion which accepts a drop only if the drop target is (one of) the
* given Item identifier(s). Criterion can be used only on a drop targets
* that extends AbstractSelect like {@link Table} and {@link Tree}. The
* target and identifiers of valid Items are given in constructor.
*
* @since 6.3
*/
@Deprecated
public static class TargetItemIs extends AbstractItemSetCriterion {
/**
* @param select
* the select implementation that is used as a drop target
* @param itemId
* the identifier(s) that are valid drop locations
*/
public TargetItemIs(AbstractSelect select, Object... itemId) {
super(select, itemId);
}
@Override
public boolean accept(DragAndDropEvent dragEvent) {
AbstractSelectTargetDetails dropTargetData = (AbstractSelectTargetDetails) dragEvent
.getTargetDetails();
if (dropTargetData.getTarget() != select) {
return false;
}
return itemIds.contains(dropTargetData.getItemIdOver());
}
}
/**
* Abstract helper class to implement item id based criterion.
*
* Note, inner class used not to open itemIdMapper for public access.
*
* @since 6.3
*
*/
private abstract static class AbstractItemSetCriterion
extends ClientSideCriterion {
protected final Collection itemIds = new HashSet();
protected AbstractSelect select;
public AbstractItemSetCriterion(AbstractSelect select,
Object... itemId) {
if (itemIds == null || select == null) {
throw new IllegalArgumentException(
"Accepted item identifiers must be accepted.");
}
Collections.addAll(itemIds, itemId);
this.select = select;
}
@Override
public void paintContent(PaintTarget target) throws PaintException {
super.paintContent(target);
String[] keys = new String[itemIds.size()];
int i = 0;
for (Object itemId : itemIds) {
String key = select.itemIdMapper.key(itemId);
keys[i++] = key;
}
target.addAttribute("keys", keys);
target.addAttribute("s", select);
}
}
/**
* This criterion accepts a only a {@link Transferable} that contains given
* Item (practically its identifier) from a specific AbstractSelect.
*
* @since 6.3
*/
@Deprecated
public static class AcceptItem extends AbstractItemSetCriterion {
/**
* @param select
* the select from which the item id's are checked
* @param itemId
* the item identifier(s) of the select that are accepted
*/
public AcceptItem(AbstractSelect select, Object... itemId) {
super(select, itemId);
}
@Override
public boolean accept(DragAndDropEvent dragEvent) {
DataBoundTransferable transferable = (DataBoundTransferable) dragEvent
.getTransferable();
if (transferable.getSourceComponent() != select) {
return false;
}
return itemIds.contains(transferable.getItemId());
}
/**
* A simple accept criterion which ensures that {@link Transferable}
* contains an {@link Item} (or actually its identifier). In other words
* the criterion check that drag is coming from a {@link Container} like
* {@link Tree} or {@link Table}.
*/
public static final ClientSideCriterion ALL = new ContainsDataFlavor(
"itemId");
}
/**
* TargetDetails implementation for subclasses of {@link AbstractSelect}
* that implement {@link DropTarget}.
*
* @since 6.3
*/
@Deprecated
public class AbstractSelectTargetDetails extends TargetDetailsImpl {
/**
* The item id over which the drag event happened.
*/
protected Object idOver;
/**
* Constructor that automatically converts itemIdOver key to
* corresponding item Id.
*
*/
protected AbstractSelectTargetDetails(
Map rawVariables) {
super(rawVariables, (DropTarget) AbstractSelect.this);
// eagar fetch itemid, mapper may be emptied
String keyover = (String) getData("itemIdOver");
if (keyover != null) {
idOver = itemIdMapper.get(keyover);
}
}
/**
* If the drag operation is currently over an {@link Item}, this method
* returns the identifier of that {@link Item}.
*
*/
public Object getItemIdOver() {
return idOver;
}
/**
* Returns a detailed vertical location where the drop happened on Item.
*/
public VerticalDropLocation getDropLocation() {
String detail = (String) getData("detail");
if (detail == null) {
return null;
}
return VerticalDropLocation.valueOf(detail);
}
}
/**
* An accept criterion to accept drops only on a specific vertical location
* of an item.
*
* This accept criterion is currently usable in Tree and Table
* implementations.
*/
@Deprecated
public static class VerticalLocationIs extends TargetDetailIs {
public static VerticalLocationIs TOP = new VerticalLocationIs(
VerticalDropLocation.TOP);
public static VerticalLocationIs BOTTOM = new VerticalLocationIs(
VerticalDropLocation.BOTTOM);
public static VerticalLocationIs MIDDLE = new VerticalLocationIs(
VerticalDropLocation.MIDDLE);
private VerticalLocationIs(VerticalDropLocation l) {
super("detail", l.name());
}
}
/**
* Implement this interface and pass it to Tree.setItemDescriptionGenerator
* or Table.setItemDescriptionGenerator to generate mouse over descriptions
* ("tooltips") for the rows and cells in Table or for the items in Tree.
*/
@Deprecated
public interface ItemDescriptionGenerator extends Serializable {
/**
* Called by Table when a cell (and row) is painted or a item is painted
* in Tree.
*
* @param source
* The source of the generator, the Tree or Table the
* generator is attached to
* @param itemId
* The itemId of the painted cell
* @param propertyId
* The propertyId of the cell, null when getting row
* description
* @return The description or "tooltip" of the item.
*/
public String generateDescription(Component source, Object itemId,
Object propertyId);
}
@Override
public void readDesign(Element design, DesignContext context) {
// handle default attributes
super.readDesign(design, context);
// handle children specifying selectable items ()
readItems(design, context);
}
protected void readItems(Element design, DesignContext context) {
Set selected = new HashSet();
for (Element child : design.children()) {
readItem(child, selected, context);
}
if (!selected.isEmpty()) {
if (isMultiSelect()) {
setValue(selected, false, true);
} else if (selected.size() == 1) {
setValue(selected.iterator().next(), false, true);
} else {
throw new DesignException(
"Multiple values selected for a single select component");
}
}
}
/**
* Reads an Item from a design and inserts it into the data source.
* Hierarchical select components should override this method to recursively
* recursively read any child items as well.
*
* @since 7.5.0
* @param child
* a child element representing the item
* @param selected
* A set accumulating selected items. If the item that is read is
* marked as selected, its item id should be added to this set.
* @param context
* the DesignContext instance used in parsing
* @return the item id of the new item
*
* @throws DesignException
* if the tag name of the {@code child} element is not
* {@code option}.
*/
protected Object readItem(Element child, Set selected,
DesignContext context) {
if (!"option".equals(child.tagName())) {
throw new DesignException("Unrecognized child element in "
+ getClass().getSimpleName() + ": " + child.tagName());
}
String itemId;
String caption = DesignFormatter.decodeFromTextNode(child.html());
if (child.hasAttr("item-id")) {
itemId = child.attr("item-id");
addItem(itemId);
setItemCaption(itemId, caption);
} else {
addItem(itemId = caption);
}
if (child.hasAttr("icon")) {
setItemIcon(itemId, DesignAttributeHandler.readAttribute("icon",
child.attributes(), Resource.class));
}
if (child.hasAttr("selected")) {
selected.add(itemId);
}
return itemId;
}
@Override
public void writeDesign(Element design, DesignContext context) {
// Write default attributes
super.writeDesign(design, context);
// Write options if warranted
if (context.shouldWriteData(this)) {
writeItems(design, context);
}
}
/**
* Writes the data source items to a design. Hierarchical select components
* should override this method to only write the root items.
*
* @since 7.5.0
* @param design
* the element into which to insert the items
* @param context
* the DesignContext instance used in writing
*/
protected void writeItems(Element design, DesignContext context) {
for (Object itemId : getItemIds()) {
writeItem(design, itemId, context);
}
}
/**
* Writes a data source Item to a design. Hierarchical select components
* should override this method to recursively write any child items as well.
*
* @since 7.5.0
* @param design
* the element into which to insert the item
* @param itemId
* the id of the item to write
* @param context
* the DesignContext instance used in writing
* @return
*/
protected Element writeItem(Element design, Object itemId,
DesignContext context) {
Element element = design.appendElement("option");
String caption = getItemCaption(itemId);
if (caption != null && !caption.equals(itemId.toString())) {
element.html(DesignFormatter.encodeForTextNode(caption));
element.attr("item-id", itemId.toString());
} else {
element.html(DesignFormatter.encodeForTextNode(itemId.toString()));
}
Resource icon = getItemIcon(itemId);
if (icon != null) {
DesignAttributeHandler.writeAttribute("icon", element.attributes(),
icon, null, Resource.class, context);
}
if (isSelected(itemId)) {
element.attr("selected", "");
}
return element;
}
@Override
protected AbstractSelectState getState() {
return (AbstractSelectState) super.getState();
}
@Override
protected AbstractSelectState getState(boolean markAsDirty) {
return (AbstractSelectState) super.getState(markAsDirty);
}
}