Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * 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.haulmont.cuba.web.gui.components; import com.haulmont.chile.core.datatypes.impl.EnumClass; import com.haulmont.chile.core.model.Instance; import com.haulmont.chile.core.model.MetaPropertyPath; import com.haulmont.chile.core.model.utils.InstanceUtils; import com.haulmont.cuba.client.ClientConfig; import com.haulmont.cuba.core.entity.BaseUuidEntity; import com.haulmont.cuba.core.entity.Entity; import com.haulmont.cuba.core.global.AppBeans; import com.haulmont.cuba.core.global.Configuration; import com.haulmont.cuba.core.global.Messages; import com.haulmont.cuba.gui.components.CaptionMode; import com.haulmont.cuba.gui.components.LookupField; import com.haulmont.cuba.gui.data.CollectionDatasource; import com.haulmont.cuba.gui.data.Datasource; import com.haulmont.cuba.web.gui.data.EnumerationContainer; import com.haulmont.cuba.web.gui.data.ObjectContainer; import com.haulmont.cuba.web.gui.data.OptionsDsWrapper; import com.haulmont.cuba.web.gui.data.UnsubscribableDsWrapper; import com.haulmont.cuba.web.toolkit.ui.CubaComboBox; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.event.ShortcutListener; import com.vaadin.server.ErrorMessage; import org.apache.commons.lang.StringUtils; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.util.*; import static com.vaadin.event.ShortcutAction.KeyCode; import static com.vaadin.event.ShortcutAction.ModifierKey; import static com.vaadin.ui.AbstractSelect.ItemCaptionMode; public class WebLookupField extends WebAbstractOptionsField<CubaComboBox> implements LookupField { protected Object nullOption; protected Entity nullEntity; protected NewOptionHandler newOptionHandler; protected Object missingValue = null; protected ComponentErrorHandler componentErrorHandler; protected boolean nullOptionVisible = true; protected OptionIconProvider optionIconProvider; public WebLookupField() { createComponent(); attachListener(component); component.setImmediate(true); component.setItemCaptionMode(ItemCaptionMode.ITEM); component.setInvalidAllowed(false); component.setInvalidCommitted(true); Configuration configuration = AppBeans.get(Configuration.NAME); setPageLength(configuration.getConfig(ClientConfig.class).getLookupFieldPageLength()); setFilterMode(FilterMode.CONTAINS); setNewOptionAllowed(false); component.setNewItemHandler(newItemCaption -> { if (newOptionHandler == null) { throw new IllegalStateException("New item handler cannot be NULL"); } newOptionHandler.addNewOption(newItemCaption); }); component.addShortcutListener( new ShortcutListener("clearShortcut", KeyCode.DELETE, new int[] { ModifierKey.SHIFT }) { @Override public void handleAction(Object sender, Object target) { if (!isRequired() && isEnabled() && isEditable()) { setValue(null); } } }); } protected void createComponent() { this.component = new CubaComboBox() { @Override public void setPropertyDataSource(Property newDataSource) { if (newDataSource == null) super.setPropertyDataSource(null); else super.setPropertyDataSource(new LookupPropertyAdapter(newDataSource)); } @Override public void setComponentError(ErrorMessage componentError) { boolean handled = false; if (componentErrorHandler != null) handled = componentErrorHandler.handleError(componentError); if (!handled) super.setComponentError(componentError); } }; } @Override public boolean isMultiSelect() { return false; } @Override public void setMultiSelect(boolean multiselect) { // Don't support multiselect for LookupField } protected Object getValueFromOptions(Object value) { if (optionsDatasource != null && value instanceof Entity) { if (Datasource.State.INVALID == optionsDatasource.getState()) { optionsDatasource.refresh(); } Object itemId = ((Entity) value).getId(); if (optionsDatasource.containsItem(itemId)) { value = optionsDatasource.getItem(itemId); } } return value; } @Override public void setValue(@Nullable Object value) { super.setValue(getValueFromOptions(value)); checkMissingValue(); } protected void checkMissingValue() { if (missingValue != null && component.getValue() != missingValue) { missingValue = null; if (component.getContainerDataSource() instanceof LookupFieldDsWrapper) { ((LookupFieldDsWrapper) component.getContainerDataSource()).forceItemSetNotification(); } } } @SuppressWarnings("unchecked") @Override public <T> T getValue() { final Object value = super.getValue(); return (T) getValueFromOptions(value); } @Override public void setFilterMode(FilterMode mode) { component.setFilteringMode(WebWrapperUtils.toVaadinFilterMode(mode)); } @Override public FilterMode getFilterMode() { return WebWrapperUtils.toFilterMode(component.getFilteringMode()); } @Override public void setRequired(boolean required) { super.setRequired(required); component.setNullSelectionAllowed(!required && nullOptionVisible); } @Override protected void attachListener(CubaComboBox component) { component.addValueChangeListener(vEvent -> { final Object value = getValue(); final Object oldValue = prevValue; prevValue = value; if (hasValidationError()) { setValidationError(null); } if (optionsDatasource != null) { //noinspection unchecked optionsDatasource.setItem((Entity) value); } if (!InstanceUtils.propertyValueEquals(oldValue, value)) { ValueChangeEvent event = new ValueChangeEvent(this, oldValue, value); getEventRouter().fireEvent(ValueChangeListener.class, ValueChangeListener::valueChanged, event); } checkMissingValue(); }); } @Override public Object getNullOption() { return nullOption; } @Override public void setNullOption(Object nullOption) { this.nullOption = nullOption; if (nullOption != null) { setInputPrompt(null); if (optionsDatasource != null) { initNullEntity(); } else { component.setNullSelectionItemId(nullOption); } } else { component.setNullSelectionItemId(null); } } protected void initNullEntity() { //noinspection IncorrectCreateEntity nullEntity = new BaseUuidEntity() { @Override public String getInstanceName() { if (nullOption instanceof Instance) { return InstanceUtils.getInstanceName((Instance) nullOption); } if (nullOption == null) { return ""; } else { return nullOption.toString(); } } // Used for captionProperty of null entity @Override public <T> T getValue(String s) { return (T) getInstanceName(); } }; component.setNullSelectionItemId(nullEntity); } @Override public void setCaptionProperty(String captionProperty) { this.captionProperty = captionProperty; tryToAssignCaptionProperty(); } @Override public void setCaptionMode(CaptionMode captionMode) { super.setCaptionMode(captionMode); tryToAssignCaptionProperty(); } protected void setupDsAutoRefresh(OptionsDsWrapper ds) { } @Override public void setOptionsDatasource(CollectionDatasource datasource) { if (this.datasource == datasource) return; if (this.optionsDatasource != null) { com.vaadin.data.Container container = component.getContainerDataSource(); if (container instanceof UnsubscribableDsWrapper) { UnsubscribableDsWrapper wrapper = (UnsubscribableDsWrapper) container; wrapper.unsubscribe(); } component.setContainerDataSource(null); } this.optionsDatasource = datasource; if (datasource != null) { LookupOptionsDsWrapper optionsDsWrapper = new LookupOptionsDsWrapper(datasource, true); setupDsAutoRefresh(optionsDsWrapper); component.setContainerDataSource(optionsDsWrapper); tryToAssignCaptionProperty(); if (nullOption != null) initNullEntity(); checkMissingValue(); assignAutoDebugId(); } } protected void tryToAssignCaptionProperty() { if (optionsDatasource != null && captionProperty != null && getCaptionMode() == CaptionMode.PROPERTY) { MetaPropertyPath propertyPath = optionsDatasource.getMetaClass().getPropertyPath(captionProperty); if (propertyPath != null && component.getContainerDataSource() != null) { ((LookupOptionsDsWrapper) component.getContainerDataSource()).addProperty(propertyPath); component.setItemCaptionPropertyId(propertyPath); } else { throw new IllegalArgumentException( String.format("Can't find property for given caption property: %s", captionProperty)); } } } @Override public void setOptionsList(List optionsList) { super.setOptionsList(optionsList); checkMissingValue(); } @Override public void setOptionsMap(Map<String, ?> options) { super.setOptionsMap(options); checkMissingValue(); } @Override public void setOptionsEnum(Class<? extends EnumClass> optionsEnum) { super.setOptionsEnum(optionsEnum); checkMissingValue(); } @Override protected String getAlternativeDebugId() { if (id != null) { return id; } if (datasource != null && StringUtils.isNotEmpty(datasource.getId()) && metaPropertyPath != null) { return getClass().getSimpleName() + "_" + datasource.getId() + "_" + metaPropertyPath.toString(); } if (optionsDatasource != null && StringUtils.isNotEmpty(optionsDatasource.getId())) { return getClass().getSimpleName() + "_" + optionsDatasource.getId(); } return getClass().getSimpleName(); } @Override protected EnumerationContainer createEnumContainer(List options) { return new NullNameAwareEnumContainer(options); } @Override protected ObjectContainer createObjectContainer(List opts) { return new NullNameAwareObjectContainer(opts); } @Override public boolean isNewOptionAllowed() { return component.isNewItemsAllowed(); } @Override public void setNewOptionAllowed(boolean newItemAllowed) { component.setNewItemsAllowed(newItemAllowed); } @Override public boolean isTextInputAllowed() { return component.isTextInputAllowed(); } @Override public void setTextInputAllowed(boolean textInputAllowed) { component.setTextInputAllowed(textInputAllowed); } @Override public NewOptionHandler getNewOptionHandler() { return newOptionHandler; } @Override public void setNewOptionHandler(NewOptionHandler newItemHandler) { this.newOptionHandler = newItemHandler; } @Override public int getPageLength() { return component.getPageLength(); } @Override public void setPageLength(int pageLength) { component.setPageLength(pageLength); } @Override public void setNullOptionVisible(boolean nullOptionVisible) { this.nullOptionVisible = nullOptionVisible; component.setNullSelectionAllowed(!isRequired() && nullOptionVisible); } @Override public boolean isNullOptionVisible() { return nullOptionVisible; } @SuppressWarnings("unchecked") public void setOptionIconProvider(OptionIconProvider<?> optionIconProvider) { setOptionIconProvider(Object.class, (OptionIconProvider) optionIconProvider); } @Override public <T> void setOptionIconProvider(Class<T> optionClass, OptionIconProvider<T> optionIconProvider) { if (this.optionIconProvider != optionIconProvider) { // noinspection unchecked this.optionIconProvider = optionIconProvider; if (optionIconProvider == null) { component.setOptionIconProvider(null); } else { component.setOptionIconProvider(itemId -> { T typedItem = optionClass.cast(itemId); String resourceId; try { // noinspection unchecked resourceId = optionIconProvider.getItemIcon(typedItem); } catch (Exception e) { LoggerFactory.getLogger(WebLookupField.class) .warn("Error invoking OptionIconProvider getItemIcon method", e); return null; } return WebComponentsHelper.getIcon(resourceId); }); } } } public OptionIconProvider<?> getOptionIconProvider() { return optionIconProvider; } @Override public String getInputPrompt() { return component.getInputPrompt(); } @Override public void setInputPrompt(String inputPrompt) { if (StringUtils.isNotBlank(inputPrompt)) { setNullOption(null); } component.setInputPrompt(inputPrompt); } @Override public void setLookupSelectHandler(Runnable selectHandler) { // do nothing } @Override public Collection getLookupSelectedItems() { return Collections.singleton(getValue()); } @Override public void commit() { super.commit(); } @Override public void discard() { super.discard(); } @Override public boolean isBuffered() { return super.isBuffered(); } @Override public void setBuffered(boolean buffered) { super.setBuffered(buffered); } @Override public boolean isModified() { return super.isModified(); } protected interface LookupFieldDsWrapper { void forceItemSetNotification(); } protected class LookupOptionsDsWrapper extends OptionsDsWrapper implements LookupFieldDsWrapper { public LookupOptionsDsWrapper(CollectionDatasource datasource, boolean autoRefresh) { super(datasource, autoRefresh); } public void addProperty(MetaPropertyPath property) { if (!properties.contains(property)) { properties.add(property); } } @Override public void forceItemSetNotification() { fireItemSetChanged(); } @Override public boolean containsId(Object itemId) { boolean optionsContainItem = super.containsId(itemId); if (!optionsContainItem) { missingValue = itemId; // refresh all item captions, forget old items itemsCache.clear(); } return true; } @Override public Collection getItemIds() { Collection items = super.getItemIds(); List<Object> optionsWithNullOrMissing = null; if (nullOption != null) { optionsWithNullOrMissing = new ArrayList<>(items.size() + 2); optionsWithNullOrMissing.add(nullEntity); } if (missingValue != null && !items.contains(missingValue)) { if (optionsWithNullOrMissing == null) { optionsWithNullOrMissing = new ArrayList<>(items.size() + 1); } optionsWithNullOrMissing.add(missingValue); } if (optionsWithNullOrMissing == null) { return items; } optionsWithNullOrMissing.addAll(items); return optionsWithNullOrMissing; } @Override public Item getItem(Object itemId) { if (Objects.equals(missingValue, itemId)) { return getItemWrapper(missingValue); } if (Objects.equals(nullEntity, itemId)) { return getItemWrapper(nullEntity); } return super.getItem(itemId); } @Override public int size() { int size = super.size(); if (missingValue != null) { size++; } if (nullOption != null) { size++; } return size; } @Override public Object firstItemId() { if (nullEntity != null) { return nullEntity; } if (missingValue != null) { return missingValue; } return super.firstItemId(); } @Override public Object lastItemId() { int size = size(); if (size == 1) { if (nullEntity != null) { return nullEntity; } if (missingValue != null) { return missingValue; } } else if (size == 2) { if (missingValue != null && nullEntity != null) { return missingValue; } } return super.lastItemId(); } @Override public Object nextItemId(Object itemId) { if (Objects.equals(nullEntity, itemId)) { if (missingValue != null) { return missingValue; } return super.firstItemId(); } if (Objects.equals(missingValue, itemId)) { return super.firstItemId(); } return super.nextItemId(itemId); } @Override public Object prevItemId(Object itemId) { if (Objects.equals(nullEntity, itemId)) { return null; } if (Objects.equals(missingValue, itemId)) { if (nullEntity != null) { return nullEntity; } return null; } return super.prevItemId(itemId); } @Override public boolean isFirstId(Object itemId) { if (Objects.equals(nullEntity, itemId)) { return true; } if (nullEntity == null) { if (Objects.equals(missingValue, itemId)) { return true; } } return super.isFirstId(itemId); } @Override public boolean isLastId(Object itemId) { int size = size(); if (size == 1) { if (Objects.equals(nullEntity, itemId)) { return true; } if (Objects.equals(missingValue, itemId)) { return true; } } else if (size == 2) { if (missingValue != null && nullEntity != null && Objects.equals(missingValue, itemId)) { return true; } } return super.isLastId(itemId); } } protected class NullNameAwareEnumContainer extends EnumerationContainer implements LookupFieldDsWrapper { public NullNameAwareEnumContainer(List<Enum> values) { super(values); } @Override public void forceItemSetNotification() { fireItemSetChanged(); } @Override public boolean containsId(Object itemId) { boolean containsFlag = super.containsId(itemId); if (!containsFlag) { missingValue = itemId; } return true; } @Override public Collection getItemIds() { //noinspection unchecked Collection<Object> itemIds = super.getItemIds(); Collection<Object> additionalItemIds = null; if (nullOption != null) { additionalItemIds = new LinkedHashSet<>(); additionalItemIds.add(nullOption); } if (missingValue != null && !itemIds.contains(missingValue)) { if (additionalItemIds == null) { additionalItemIds = new LinkedHashSet<>(); } additionalItemIds.add(missingValue); } if (additionalItemIds != null) { additionalItemIds.addAll(itemIds); return additionalItemIds; } return itemIds; } @Override public Item getItem(Object itemId) { if (Objects.equals(nullOption, itemId)) { return new NullOptionItem(); } if (Objects.equals(missingValue, itemId)) { return new EnumerationItem(missingValue); } return super.getItem(itemId); } @Override public int size() { int size = super.size(); if (missingValue != null) size++; if (nullOption != null) size++; return size; } } protected class NullNameAwareObjectContainer extends ObjectContainer implements LookupFieldDsWrapper { public NullNameAwareObjectContainer(List values) { super(values); } @Override public void forceItemSetNotification() { fireItemSetChanged(); } @Override public boolean containsId(Object itemId) { boolean containsFlag = super.containsId(itemId); if (!containsFlag) { missingValue = itemId; } return true; } @Override public Collection getItemIds() { //noinspection unchecked Collection<Object> itemIds = super.getItemIds(); Collection<Object> additionalItemIds = null; if (nullOption != null) { additionalItemIds = new LinkedHashSet<>(); additionalItemIds.add(nullOption); } if (missingValue != null && !itemIds.contains(missingValue)) { if (additionalItemIds == null) { additionalItemIds = new LinkedHashSet<>(); } additionalItemIds.add(missingValue); } if (additionalItemIds != null) { additionalItemIds.addAll(itemIds); return additionalItemIds; } return itemIds; } @Override public Item getItem(Object itemId) { if (Objects.equals(nullOption, itemId)) { return new NullOptionItem(); } if (Objects.equals(missingValue, itemId)) { return new ObjectItem(missingValue); } return super.getItem(itemId); } @Override public int size() { int size = super.size(); if (missingValue != null) size++; if (nullOption != null) size++; return size; } } protected class LookupPropertyAdapter extends PropertyAdapter { public LookupPropertyAdapter(Property itemProperty) { super(itemProperty); } @Override public Object getValue() { Object value = itemProperty.getValue(); adoptMissingValue(value); return value; } @Override public void setValue(Object newValue) throws ReadOnlyException { adoptMissingValue(newValue); itemProperty.setValue(newValue); } protected void adoptMissingValue(Object value) { if (!Objects.equals(missingValue, value)) { Object itemId = null; if (value instanceof Entity) { itemId = ((Entity) value).getId(); } if (optionsDatasource != null && !optionsDatasource.containsItem(itemId)) { missingValue = value; } else if (optionsList != null && !optionsList.contains(value)) { missingValue = value; } else if (optionsMap != null && !optionsMap.containsValue(value)) { missingValue = value; } } } } protected class NullOptionItem implements Item { protected Messages messages = AppBeans.get(Messages.NAME); @Override public Property getItemProperty(Object id) { return null; } @Override public Collection<?> getItemPropertyIds() { return Collections.emptyList(); } @Override public boolean addItemProperty(Object id, Property property) throws UnsupportedOperationException { return false; } @Override public boolean removeItemProperty(Object id) throws UnsupportedOperationException { return false; } @Override public String toString() { if (nullOption == null) { return ""; } if (nullOption instanceof Enum) { return messages.getMessage((Enum) nullOption); } if (nullOption instanceof Entity) { return InstanceUtils.getInstanceName((Instance) nullOption); } return nullOption.toString(); } } protected interface ComponentErrorHandler { boolean handleError(ErrorMessage message); } }