All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.holonplatform.vaadin7.internal.components.AbstractSelectField Maven / Gradle / Ivy

/*
 * Copyright 2016-2017 Axioma srl.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.holonplatform.vaadin7.internal.components;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import com.holonplatform.core.i18n.Caption;
import com.holonplatform.core.i18n.Localizable;
import com.holonplatform.core.i18n.LocalizationContext;
import com.holonplatform.core.internal.utils.AnnotationUtils;
import com.holonplatform.core.internal.utils.ObjectUtils;
import com.holonplatform.vaadin7.Registration;
import com.holonplatform.vaadin7.components.ItemSet;
import com.holonplatform.vaadin7.components.Selectable;
import com.holonplatform.vaadin7.components.builders.BaseSelectInputBuilder.RenderingMode;
import com.holonplatform.vaadin7.data.ItemDataSource;
import com.holonplatform.vaadin7.data.ItemDataSource.Configuration;
import com.vaadin.data.Container;
import com.vaadin.data.Item;
import com.vaadin.server.Resource;
import com.vaadin.ui.AbstractSelect;
import com.vaadin.ui.AbstractSelect.NewItemHandler;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Field;
import com.vaadin.ui.ListSelect;
import com.vaadin.ui.NativeSelect;
import com.vaadin.ui.OptionGroup;

/**
 * Abstract select {@link Field} implementation.
 * 
 * @param  Actual field type
 * @param  Internal select type
 * @param  Selection items type
 */
public abstract class AbstractSelectField extends AbstractCustomField
		implements Selectable, ItemSet, Container.Viewer {

	private static final long serialVersionUID = -2069614658878818456L;

	/**
	 * Extract to obtain selection item data from container item
	 * @param  Selection item data type
	 */
	@FunctionalInterface
	interface ItemDataExtractor {

		/**
		 * Get selection item data from container item
		 * @param configuration Data source configuration
		 * @param itemId Item id
		 * @param item Container item
		 * @return Itemd data
		 */
		ITEM extractItemData(Configuration configuration, Object itemId, Item item);

	}

	/**
	 * Item data extractor
	 */
	private ItemDataExtractor itemDataExtractor;

	/**
	 * Rendering mode
	 */
	private final RenderingMode renderingMode;

	/**
	 * Selection listeners
	 */
	private final List> selectionListeners = new LinkedList<>();

	/**
	 * Item caption generator
	 */
	private ItemCaptionGenerator itemCaptionGenerator;

	/**
	 * Item icon generator
	 */
	private ItemIconGenerator itemIconGenerator;

	/**
	 * Explicit item captions
	 */
	protected final Map explicitItemCaptions = new HashMap<>(8);

	/**
	 * Explicit item icons
	 */
	protected final Map explicitItemIcons = new HashMap<>(8);

	/**
	 * Constructor
	 * @param type Select field type
	 * @param renderingMode UI rendering mode
	 */
	public AbstractSelectField(Class type, RenderingMode renderingMode) {
		super(type, false);
		this.renderingMode = renderingMode;

		addStyleName("h-select", false);

		init();
	}

	/*
	 * (non-Javadoc)
	 * @see com.holonplatform.vaadin.internal.components.AbstractCustomField#buildInternalField(java.lang.Class)
	 */
	@Override
	@SuppressWarnings("serial")
	protected AbstractSelect buildInternalField(Class type) {

		boolean multiSelect = getSelectionMode() == SelectionMode.MULTI;

		RenderingMode mode = getRenderingMode();
		if (mode == null) {
			mode = multiSelect ? RenderingMode.OPTIONS : RenderingMode.SELECT;
		}
		if (multiSelect && mode == RenderingMode.NATIVE_SELECT) {
			mode = RenderingMode.SELECT;
		}

		AbstractSelect field = null;

		switch (mode) {
		case NATIVE_SELECT: {
			field = new NativeSelect() {

				@Override
				public String getItemCaption(Object itemId) {
					return generateItemCaption(extractItemData(itemId, getItem(itemId)));
				}

				@Override
				public Resource getItemIcon(Object itemId) {
					return generateItemIcon(extractItemData(itemId, getItem(itemId)));
				}

			};
		}
			break;
		case OPTIONS: {
			field = new OptionGroup() {

				@Override
				public String getItemCaption(Object itemId) {
					return generateItemCaption(extractItemData(itemId, getItem(itemId)));
				}

				@Override
				public Resource getItemIcon(Object itemId) {
					return generateItemIcon(extractItemData(itemId, getItem(itemId)));
				}

			};
			((OptionGroup) field).setMultiSelect(multiSelect);
		}
			break;
		case SELECT:
		default: {
			if (multiSelect) {
				field = new ListSelect() {

					@Override
					public String getItemCaption(Object itemId) {
						return generateItemCaption(extractItemData(itemId, getItem(itemId)));
					}

					@Override
					public Resource getItemIcon(Object itemId) {
						return generateItemIcon(extractItemData(itemId, getItem(itemId)));
					}

				};
			} else {
				field = new ComboBox() {

					@Override
					public String getItemCaption(Object itemId) {
						return generateItemCaption(extractItemData(itemId, getItem(itemId)));
					}

					@Override
					public Resource getItemIcon(Object itemId) {
						return generateItemIcon(extractItemData(itemId, getItem(itemId)));
					}

				};
			}
		}
			break;
		}

		// selection notifier
		field.addValueChangeListener(e -> fireSelectionListeners(buildSelectionEvent(e)));

		return field;
	}

	@SuppressWarnings("unchecked")
	protected SelectionEvent buildSelectionEvent(com.vaadin.data.Property.ValueChangeEvent event) {
		Object value = event.getProperty().getValue();
		if (value != null) {
			if (Set.class.isAssignableFrom(value.getClass())) {
				return new DefaultSelectionEvent<>((Set) value);
			} else {
				return new DefaultSelectionEvent<>((S) value);
			}
		}
		return new DefaultSelectionEvent<>(null);
	}

	/**
	 * Get the item data extractor
	 * @return Item data extractor
	 */
	protected Optional> getItemDataExtractor() {
		return Optional.ofNullable(itemDataExtractor);
	}

	/**
	 * Set the item data extractor
	 * @param itemDataExtractor the item data extractor to set
	 */
	protected void setItemDataExtractor(ItemDataExtractor itemDataExtractor) {
		this.itemDataExtractor = itemDataExtractor;
	}

	/**
	 * Extract selection item data from container item
	 * @param itemId Item id
	 * @param item Container item
	 * @return Selection item data
	 */
	protected ITEM extractItemData(Object itemId, Item item) {
		return getItemDataExtractor().map(e -> e.extractItemData(getDataSourceConfiguration(), itemId, item))
				.orElseThrow(() -> new IllegalStateException("Missing ItemDataExtractor"));
	}

	/**
	 * Get data source configuration
	 * @return data source configuration
	 */
	public Configuration getDataSourceConfiguration() {
		if (getContainerDataSource() instanceof ItemDataSource) {
			return ((ItemDataSource) getContainerDataSource()).getConfiguration();
		}
		return null;
	}

	/**
	 * Get the select rendering mode
	 * @return Rendering mode
	 */
	protected RenderingMode getRenderingMode() {
		return renderingMode;
	}

	/**
	 * Get the item caption generator
	 * @return the ItemCaptionGenerator
	 */
	public Optional> getItemCaptionGenerator() {
		return Optional.ofNullable(itemCaptionGenerator);
	}

	/**
	 * Set the item caption generator
	 * @param itemCaptionGenerator the ItemCaptionGenerator to set
	 */
	public void setItemCaptionGenerator(ItemCaptionGenerator itemCaptionGenerator) {
		this.itemCaptionGenerator = itemCaptionGenerator;
	}

	/**
	 * Get the item icon generator
	 * @return the ItemIconGenerator
	 */
	public Optional> getItemIconGenerator() {
		return Optional.ofNullable(itemIconGenerator);
	}

	/**
	 * Set the item icon generator
	 * @param itemIconGenerator the ItemIconGenerator to set
	 */
	public void setItemIconGenerator(ItemIconGenerator itemIconGenerator) {
		this.itemIconGenerator = itemIconGenerator;
	}

	/**
	 * Set an explicit caption for given item.
	 * @param item Item to set the caption for
	 * @param caption Caption to set (not null)
	 */
	public void setItemCaption(ITEM item, Localizable caption) {
		ObjectUtils.argumentNotNull(item, "Item must be not null");
		if (caption != null) {
			explicitItemCaptions.put(item, caption);
		} else {
			explicitItemCaptions.remove(item);
		}
	}

	/**
	 * Set an explicit icon for given item.
	 * @param item Item to set the caption for
	 * @param icon Icon to set (not null)
	 */
	public void setItemIcon(ITEM item, Resource icon) {
		ObjectUtils.argumentNotNull(item, "Item must be not null");
		if (icon != null) {
			explicitItemIcons.put(item, icon);
		} else {
			explicitItemIcons.remove(item);
		}
	}

	/*
	 * (non-Javadoc)
	 * @see com.vaadin.data.Container.Viewer#setContainerDataSource(com.vaadin.data.Container)
	 */
	@Override
	public void setContainerDataSource(Container newDataSource) {
		getInternalField().setContainerDataSource(newDataSource);
		// by default, disable ComboBox scrolling to selected item, which can be expensive in many large lazy loading
		// containers
		if (getInternalField() instanceof ComboBox) {
			((ComboBox) getInternalField()).setScrollToSelectedItem(false);
		}
	}

	/*
	 * (non-Javadoc)
	 * @see com.vaadin.data.Container.Viewer#getContainerDataSource()
	 */
	@Override
	public Container getContainerDataSource() {
		return getInternalField().getContainerDataSource();
	}

	/**
	 * Gets the item Id collection from the container.
	 * @return the Collection of item ids.
	 */
	@SuppressWarnings("unchecked")
	public Collection getItemIds() {
		return (Collection) getInternalField().getItemIds();
	}

	/**
	 * Create a new selection item.
	 * @param item the item to add
	 */
	public void addItem(S item) throws UnsupportedOperationException {
		getInternalField().addItem(item);
	}

	/**
	 * Removes the Item identified by ItemId from the Container.
	 * @param itemId ID of the Item to remove
	 * @return true if the operation succeeded, false if not
	 * @throws UnsupportedOperationException if the container does not support removing individual items
	 */
	public boolean removeItem(S itemId) throws UnsupportedOperationException {
		return getInternalField().removeItem(itemId);
	}

	/**
	 * Removes all items from the container.
	 * @return True if the operation succeeded.
	 */
	public boolean removeAllItems() throws UnsupportedOperationException {
		return getInternalField().removeAllItems();
	}

	/**
	 * Adds an handler for new items
	 * @param newItemHandler New items handler
	 */
	public void setNewItemHandler(NewItemHandler newItemHandler) {
		getInternalField().setNewItemHandler(newItemHandler);
	}

	/**
	 * Gets the handler for new items
	 * @return the new items handler
	 */
	public NewItemHandler getNewItemHandler() {
		return getInternalField().getNewItemHandler();
	}

	/**
	 * 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 getInternalField().isNewItemsAllowed();
	}

	/**
	 * Enables or disables possibility to add new options by the user.
	 * @param allowNewOptions the New value of property allowNewOptions.
	 */
	public void setNewItemsAllowed(boolean allowNewOptions) {
		getInternalField().setNewItemsAllowed(allowNewOptions);
	}

	/**
	 * 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
	 */
	public void setNullSelectionAllowed(boolean nullSelectionAllowed) {
		getInternalField().setNullSelectionAllowed(nullSelectionAllowed);
	}

	/**
	 * Checks if null empty selection is allowed by the user.
	 * @return whether or not empty selection is allowed
	 */
	public boolean isNullSelectionAllowed() {
		return getInternalField().isNullSelectionAllowed();
	}

	/**
	 * 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. */ @SuppressWarnings("unchecked") public S getNullSelectionItemId() { return (S) getInternalField().getNullSelectionItemId(); } /** * 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. */ public void setNullSelectionItemId(S nullSelectionItemId) { getInternalField().setNullSelectionItemId(nullSelectionItemId); } /** * 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. */ public void sanitizeSelection() { getInternalField().sanitizeSelection(); } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.ItemSetComponent#refresh() */ @Override public void refresh() { Container dataSource = getContainerDataSource(); if (dataSource instanceof ItemDataSource) { ((ItemDataSource) dataSource).refresh(); } } /* * (non-Javadoc) * @see com.holonplatform.vaadin.components.Selectable#addSelectionListener(com.holonplatform.vaadin.components. * Selectable.SelectionListener) */ @Override public Registration addSelectionListener(SelectionListener selectionListener) { ObjectUtils.argumentNotNull(selectionListener, "SelectionListener must be not null"); selectionListeners.add(selectionListener); return () -> selectionListeners.remove(selectionListener); } /** * Triggers registered {@link SelectionListener}s. * @param event Selection event (not null) */ protected void fireSelectionListeners(SelectionEvent event) { selectionListeners.forEach(l -> l.onSelectionChange(event)); } /** * Generate the select item icon for given item. * @param item Item to generate the icon for * @return Item icon (may be null) */ protected Resource generateItemIcon(ITEM item) { if (item != null) { return getItemIconGenerator().map(g -> g.getItemIcon(item)).orElse(getDefaultItemIcon(item)); } return null; } /** * Get the default select item icon for given item. * @param item Item * @return Default item icon */ protected Resource getDefaultItemIcon(ITEM item) { if (item != null) { return explicitItemIcons.get(item); } return null; } /** * Generate the select item caption for given item. * @param item Item to generate the caption for * @return Item caption (not null) */ protected String generateItemCaption(ITEM item) { if (item != null) { return getItemCaptionGenerator().map(g -> g.getItemCaption(item)).orElse(getDefaultItemCaption(item)); } return ""; } /** * Get the default select item caption for given item. * @param item Item * @return Default item caption */ protected String getDefaultItemCaption(ITEM item) { if (item != null) { // check explicit caption Localizable caption = explicitItemCaptions.get(item); if (caption != null) { return LocalizationContext.translate(caption, true); } // check Localizable Localizable lv = null; if (Localizable.class.isAssignableFrom(item.getClass())) { lv = (Localizable) item; } else { // check Caption annotation on enums if (item.getClass().isEnum()) { Enum enm = (Enum) item; try { final java.lang.reflect.Field fld = item.getClass().getField(enm.name()); if (fld.isAnnotationPresent(Caption.class)) { lv = Localizable.builder().message(fld.getAnnotation(Caption.class).value()).messageCode( AnnotationUtils.getStringValue(fld.getAnnotation(Caption.class).messageCode())) .build(); } } catch (@SuppressWarnings("unused") Exception e) { // ignore } } } if (lv != null) { return LocalizationContext.translate(lv, true); } // ID toString return item.toString(); } return ""; } }