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

com.ocs.dynamo.ui.component.EntityLookupField Maven / Gradle / Ivy

The newest version!
/*
   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.ocs.dynamo.ui.component;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.ArrayUtils;

import com.ocs.dynamo.dao.FetchJoinInformation;
import com.ocs.dynamo.domain.AbstractEntity;
import com.ocs.dynamo.domain.model.AttributeModel;
import com.ocs.dynamo.domain.model.EntityModel;
import com.ocs.dynamo.domain.model.VisibilityType;
import com.ocs.dynamo.service.BaseService;
import com.ocs.dynamo.ui.composite.dialog.ModelBasedSearchDialog;
import com.ocs.dynamo.ui.composite.layout.SearchOptions;
import com.ocs.dynamo.ui.utils.VaadinUtils;
import com.ocs.dynamo.util.SystemPropertyUtils;
import com.ocs.dynamo.utils.EntityModelUtils;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.data.provider.SortOrder;
import com.vaadin.flow.function.SerializablePredicate;

import lombok.Getter;

/**
 * A composite component that displays a selected entity and offers a search
 * dialog to search for another one
 *
 * @param  the type of the primary key
 * @param   the type of the entity
 * @author bas.rutten
 */
public class EntityLookupField>
        extends QuickAddEntityField {

    private static final long serialVersionUID = 5377765863515463622L;

    /**
     * Indicates whether it is allowed to add items
     */
    private final boolean addAllowed;

    /**
     * Indicates whether it is allowed to clear the selection
     */
    private final boolean clearAllowed;

    /**
     * The button used to clear the current selection
     */
    @Getter
    private Button clearButton;

    /**
     * Whether direct navigation via internal link is allowed
     */
    private final boolean directNavigationAllowed;

    /**
     * The joins to apply to the search in the search dialog
     */
    @Getter
    private FetchJoinInformation[] joins;

    /**
     * The label that displays the currently selected items
     */
    private Span label;

    /**
     * The pop-up search dialog
     */
    private ModelBasedSearchDialog searchDialog;

    /**
     * The options to use for the search dialog
     */
    private final SearchOptions searchOptions;

    /**
     * The button that brings up the search dialog
     */
    @Getter
    private Button selectButton;

    /**
     * The sort orders that are used in the pop-up dialog
     */
    private final List> sortOrders;

    /**
     * The current value of the component. This can either be a single item or a set
     */
    private Object value;

    /**
     * Constructor
     *
     * @param service        the service used to query the database
     * @param entityModel    the entity model
     * @param attributeModel the attribute mode
     * @param filter         the filter to apply when searching
     * @param search         whether the component is used in a search screen
     * @param searchOptions  the search options
     * @param sortOrders     the sort order
     * @param joins          the joins to use when fetching data when filling the
     *                       pop-up dialog
     */
    public EntityLookupField(BaseService service, EntityModel entityModel, AttributeModel attributeModel,
                             SerializablePredicate filter, boolean search, SearchOptions searchOptions, List> sortOrders,
                             FetchJoinInformation... joins) {
        super(service, entityModel, attributeModel, filter);
        this.sortOrders = sortOrders != null ? sortOrders : new ArrayList<>();
        this.joins = joins;
        this.clearAllowed = true;
        this.addAllowed = !search && (attributeModel != null && attributeModel.isQuickAddAllowed());
        this.directNavigationAllowed = !search && (attributeModel != null && attributeModel.isNavigable());
        this.searchOptions = searchOptions;
        initContent();
    }

    /**
     * Adds additional fetch joins
     *
     * @param fetchJoinInformation the joins to add
     */
    public void addFetchJoinInformation(FetchJoinInformation... fetchJoinInformation) {
        joins = ArrayUtils.addAll(joins, fetchJoinInformation);
    }

    /**
     * Adds a sort order
     *
     * @param sortOrder the sort order that must be added
     */
    public void addSortOrder(SortOrder sortOrder) {
        this.sortOrders.add(sortOrder);
    }

    @Override
    @SuppressWarnings("unchecked")
    protected void afterNewEntityAdded(T entity) {
        if (searchOptions.isMultiSelect()) {
            if (getValue() == null) {
                // create new collection
                setValue(new ArrayList<>(List.of(entity)));
            } else {
                // add new entity to existing collection
                Collection col = (Collection) getValue();
                col.add(entity);
                setValue(col);
            }
        } else {
            setValue(entity);
        }
    }

    /**
     * Select the currently selected values in the pop-up search dialog
     * This uses a bit of a work-around of waiting until the dialog has
     * been opened first and then selecting the values because the checkbox dialog does not play nice
     */
    private void afterOpen() {
        if (searchOptions.isMultiSelect()) {
            selectValuesInDialog(searchDialog);
        }
    }

    /**
     * Clears the current value of the component
     */
    public void clearValue() {
        if (Set.class.isAssignableFrom(getAttributeModel().getType())) {
            setValue(new HashSet<>());
        } else if (List.class.isAssignableFrom(getAttributeModel().getType())) {
            setValue(new ArrayList<>());
        } else {
            setValue(null);
        }
    }

    /**
     * Gets the value that must be displayed on the label that shows which items are
     * currently selected
     *
     * @param newValue the new value of the component
     * @return the contents of the label
     */
    @SuppressWarnings("unchecked")
    protected String constructLabelValue(Object newValue) {
        String caption = getMessageService().getMessage(
                searchOptions.isMultiSelect() ? "ocs.no.items.selected" : "ocs.no.item.selected",
                VaadinUtils.getLocale());
        if (newValue instanceof Collection) {
            Collection col = (Collection) newValue;
            if (!col.isEmpty()) {
                caption = EntityModelUtils.getDisplayPropertyValue(col, getEntityModel(),
                        SystemPropertyUtils.getDefaultLookupFieldMaxItems(), getMessageService(),
                        VaadinUtils.getLocale());
            }
        } else {
            T entity = (T) newValue;
            if (newValue != null) {
                caption = EntityModelUtils.getDisplayPropertyValue(entity, getEntityModel());
            }
        }
        return caption;
    }

    private void constructSelectButton(HorizontalLayout bar, boolean showCaption) {
        selectButton = new Button(showCaption ? getMessageService().getMessage("ocs.select", VaadinUtils.getLocale()) : "");
        selectButton.setIcon(VaadinIcon.SEARCH.create());
        VaadinUtils.setTooltip(selectButton, getMessageService().getMessage("ocs.select", VaadinUtils.getLocale()));
        selectButton.addClickListener(event -> showSearchDialog());
        bar.add(selectButton);
    }

    @Override
    protected Object generateModelValue() {
        return convertToCorrectCollection(value);
    }

    public List> getSortOrders() {
        return Collections.unmodifiableList(sortOrders);
    }

    @Override
    public Object getValue() {
        return convertToCorrectCollection(value);
    }

    protected void initContent() {
        HorizontalLayout bar = new HorizontalLayout();
        bar.setSizeFull();

        if (this.getAttributeModel() != null) {
            this.setLabel(getAttributeModel().getDisplayName(VaadinUtils.getLocale()));
        }

        // label for displaying selected values
        label = new Span("");
        updateLabel(getValue());
        bar.add(label);
        bar.setFlexGrow(5, label);

        boolean showCaption = getAttributeModel() != null && VisibilityType.SHOW.equals(getAttributeModel().getLookupFieldCaptions());
        constructSelectButton(bar, showCaption);

        if (clearAllowed) {
            clearButton = new Button(showCaption ? getMessageService().getMessage("ocs.clear", VaadinUtils.getLocale()) : "");
            VaadinUtils.setTooltip(bar, getMessageService().getMessage("ocs.clear", VaadinUtils.getLocale()));
            clearButton.setIcon(VaadinIcon.ERASER.create());
            clearButton.addClickListener(event -> clearValue());
            bar.add(clearButton);
        }

        if (addAllowed) {
            Button addButton = constructAddButton();
            bar.add(addButton);
        }

        if (directNavigationAllowed) {
            Button directNavigationButton = constructDirectNavigationButton();
            bar.add(directNavigationButton);
        }

        bar.setSizeFull();
        add(bar);
    }

    protected boolean isAddAllowed() {
        return addAllowed;
    }

    protected boolean isClearAllowed() {
        return clearAllowed;
    }

    protected boolean isDirectNavigationAllowed() {
        return directNavigationAllowed;
    }

    private Boolean onDialogClose() {
        if (searchOptions.isMultiSelect()) {
            if (EntityLookupField.this.getValue() == null) {
                EntityLookupField.this.setValue(searchDialog.getSelectedItems());
            } else {
                // add selected items to already selected items
                @SuppressWarnings("unchecked")
                Collection cumulative = (Collection) EntityLookupField.this.getValue();

                for (T selectedItem : searchDialog.getSelectedItems()) {
                    if (!cumulative.contains(selectedItem)) {
                        cumulative.add(selectedItem);
                    }
                }
                EntityLookupField.this.setValue(cumulative);
            }
        } else {
            // single value select
            EntityLookupField.this.setValue(searchDialog.getSelectedItem());
        }
        return true;
    }

    @Override
    public void refresh(SerializablePredicate filter) {
        setFilter(filter);
    }

    /**
     * Makes sure any currently selected values are highlighted in the search dialog
     *
     * @param dialog the dialog
     */
    public void selectValuesInDialog(ModelBasedSearchDialog dialog) {
        if (getValue() != null) {
            dialog.select(getValue());
        }
    }

    @Override
    public void setAdditionalFilter(SerializablePredicate additionalFilter) {
        setValue(null);
        super.setAdditionalFilter(additionalFilter);
    }

    @Override
    public void setClearButtonVisible(boolean visible) {
        // do nothing
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        if (selectButton != null) {
            selectButton.setEnabled(enabled);
            if (getClearButton() != null) {
                getClearButton().setEnabled(enabled);
            }
            if (getAddButton() != null) {
                getAddButton().setEnabled(enabled);
            }
        }
    }

    @Override
    public void setPlaceholder(String placeholder) {
        // do nothing
    }

    @Override
    protected void setPresentationValue(Object value) {
        this.value = value;
        updateLabel(value);
    }

    @Override
    public void setReadOnly(boolean readOnly) {
        setEnabled(!readOnly);
    }

    @Override
    @SuppressWarnings("unchecked")
    public void setValue(Object value) {
        if (value == null) {
            super.setValue(null);
        } else if (Set.class.isAssignableFrom(getAttributeModel().getType())) {
            Collection col = (Collection) value;
            super.setValue(new HashSet<>(col));
        } else if (List.class.isAssignableFrom(getAttributeModel().getType())) {
            Collection col = (Collection) value;
            super.setValue(new ArrayList<>(col));
        } else {
            super.setValue(value);
        }
        updateLabel(value);
    }

    private void showSearchDialog() {
        List> filterList = new ArrayList<>();
        if (getFilter() != null) {
            filterList.add(getFilter());
        }
        if (getAdditionalFilter() != null) {
            filterList.add(getAdditionalFilter());
        }

        searchDialog = new ModelBasedSearchDialog<>(getService(), getEntityModel(), filterList, sortOrders,
                searchOptions, getJoins());
        searchDialog.setOnClose(this::onDialogClose);
        searchDialog.setAfterOpen(this::afterOpen);
        searchDialog.buildAndOpen();
    }

    /**
     * Updates the value that is displayed in the label
     *
     * @param newValue the new value
     */
    private void updateLabel(Object newValue) {
        if (label != null) {
            String caption = constructLabelValue(newValue);
            label.setText(caption);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy