de.symeda.sormas.ui.utils.CommitDiscardWrapperComponent.java Source code

Java tutorial

Introduction

Here is the source code for de.symeda.sormas.ui.utils.CommitDiscardWrapperComponent.java

Source

/*******************************************************************************
 * SORMAS - Surveillance Outbreak Response Management & Analysis System
 * Copyright  2016-2018 Helmholtz-Zentrum fr Infektionsforschung GmbH (HZI)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *******************************************************************************/
package de.symeda.sormas.ui.utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;

import com.vaadin.event.Action.Notifier;
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.server.Page;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Component;
import com.vaadin.ui.Notification;
import com.vaadin.ui.Notification.Type;
import com.vaadin.ui.Panel;
import com.vaadin.ui.themes.ValoTheme;
import com.vaadin.v7.data.Buffered;
import com.vaadin.v7.data.Validator.InvalidValueException;
import com.vaadin.v7.data.fieldgroup.FieldGroup;
import com.vaadin.v7.data.fieldgroup.FieldGroup.CommitException;
import com.vaadin.v7.ui.AbstractLegacyComponent;
import com.vaadin.v7.ui.Field;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.v7.ui.RichTextArea;
import com.vaadin.v7.ui.TextArea;
import com.vaadin.ui.VerticalLayout;

import de.symeda.sormas.api.i18n.Captions;
import de.symeda.sormas.api.i18n.I18nProperties;
import de.symeda.sormas.api.i18n.Strings;

public class CommitDiscardWrapperComponent<C extends Component> extends VerticalLayout implements Buffered {

    private static final long serialVersionUID = 1L;

    public static interface CommitListener {
        void onCommit();
    }

    public static interface DiscardListener {
        void onDiscard();
    }

    public static interface DoneListener {
        void onDone();
    }

    public static interface DeleteListener {
        void onDelete();
    }

    private transient List<CommitListener> commitListeners = new ArrayList<>();
    private transient List<DiscardListener> discardListeners = new ArrayList<>();
    private transient List<DoneListener> doneListeners = new ArrayList<>();
    private transient List<DeleteListener> deleteListeners = new ArrayList<>();
    // only to check if it's set
    private transient CommitListener primaryCommitListener;

    private Panel contentPanel;

    private C wrappedComponent;
    private FieldGroup[] fieldGroups;

    private HorizontalLayout buttonsPanel;
    private Button commitButton;
    private Button discardButton;

    private Button deleteButton;

    private boolean commited = false;

    private boolean shortcutsEnabled = false;
    protected transient List<ClickShortcut> actions;

    protected CommitDiscardWrapperComponent() {

    }

    public CommitDiscardWrapperComponent(C component, FieldGroup... fieldGroups) {
        setWrappedComponent(component, fieldGroups);
    }

    protected void setWrappedComponent(C component, FieldGroup... fieldGroups) {

        this.wrappedComponent = component;
        this.fieldGroups = fieldGroups;

        if (contentPanel != null) {
            contentPanel.setContent(wrappedComponent);
            return;
        }

        setSpacing(false);
        setMargin(true);
        setSizeUndefined();

        contentPanel = new Panel(component);
        updateInternalWidth();
        updateInternalHeight();
        addComponent(contentPanel);
        setExpandRatio(contentPanel, 1);

        buttonsPanel = new HorizontalLayout();
        buttonsPanel.setMargin(false);
        buttonsPanel.setSpacing(true);
        buttonsPanel.setWidth(100, Unit.PERCENTAGE);

        Button discardButton = getDiscardButton();
        buttonsPanel.addComponent(discardButton);
        buttonsPanel.setComponentAlignment(discardButton, Alignment.BOTTOM_RIGHT);
        buttonsPanel.setExpandRatio(discardButton, 1);

        Button commitButton = getCommitButton();
        buttonsPanel.addComponent(commitButton);
        buttonsPanel.setComponentAlignment(commitButton, Alignment.BOTTOM_RIGHT);
        buttonsPanel.setExpandRatio(commitButton, 0);

        addComponent(buttonsPanel);
        setComponentAlignment(buttonsPanel, Alignment.BOTTOM_RIGHT);

        setShortcutsEnabled(shortcutsEnabled);

        if (fieldGroups != null && fieldGroups.length > 0) {
            // convention: set wrapper to read-only when all wrapped field groups are read-only
            boolean allReadOnly = true;
            for (FieldGroup fieldGroup : fieldGroups) {
                if (!fieldGroup.isReadOnly()) {
                    allReadOnly = false;
                    break;
                }
            }
            if (allReadOnly) {
                setReadOnly(true);
            }
        } else if (wrappedComponent != null) {
            if (wrappedComponent instanceof AbstractLegacyComponent
                    && ((AbstractLegacyComponent) wrappedComponent).isReadOnly()) {
                setReadOnly(true);
            }
        }
    }

    protected Stream<Field<?>> getFieldsStream() {

        if (fieldGroups != null) {
            return Arrays.stream(fieldGroups).map(FieldGroup::getFields).flatMap(Collection::stream);
        } else {
            return Stream.empty();
        }
    }

    /**
     * Whether the buttons can be operated by ENTER and ESC
     * 
     * @param shortcutsEnabled
     */
    public void setShortcutsEnabled(boolean shortcutsEnabled) {

        if (shortcutsEnabled == this.shortcutsEnabled) {
            if (!shortcutsEnabled || actions != null)
                return;
        }

        this.shortcutsEnabled = shortcutsEnabled;

        if (actions == null) {
            actions = new ArrayList<>();
        }

        Collection<Notifier> notifiers = Arrays.asList((Notifier) contentPanel);

        if (shortcutsEnabled) {
            for (Notifier notifier : notifiers) {
                registerActions(notifier);
            }

        } else {
            for (ClickShortcut action : actions) {
                action.remove();
            }
            actions.clear();
        }
    }

    protected void registerActions(Notifier notifier) {
        actions.add(new ClickShortcut(notifier, commitButton, KeyCode.ENTER));
        actions.add(new ClickShortcut(notifier, discardButton, KeyCode.ESCAPE));
    }

    public C getWrappedComponent() {
        return wrappedComponent;
    }

    public HorizontalLayout getButtonsPanel() {
        return buttonsPanel;
    }

    /**
     * Durch das Aufrufen dieser Methode wird ein Button zum Speichern erzeugt, aber nicht eingefgt.
     * Das passiert in setWrappedComponent().
     * @return
     */
    public Button getCommitButton() {
        if (commitButton == null) {
            commitButton = new Button(I18nProperties.getCaption(Captions.actionSave));
            commitButton.setId("commit");
            commitButton.addStyleName(ValoTheme.BUTTON_PRIMARY);

            commitButton.addClickListener(new ClickListener() {
                private static final long serialVersionUID = 1L;

                @Override
                public void buttonClick(ClickEvent event) {
                    commitAndHandle();
                }
            });
        }
        return commitButton;
    }

    /**
     * Durch das Aufrufen dieser Methode wird ein Button zum Verwerfen erzeugt aber nicht eingefgt.
     * Das passiert in setWrappedComponent().
     * @return
     */
    public Button getDiscardButton() {
        if (discardButton == null) {
            discardButton = new Button(I18nProperties.getCaption(Captions.actionDiscard));
            discardButton.setId("discard");

            discardButton.addClickListener(new ClickListener() {
                private static final long serialVersionUID = 1L;

                @Override
                public void buttonClick(ClickEvent event) {
                    discard();
                }
            });
        }
        return discardButton;
    }

    public Button getDeleteButton(String entityName) {
        if (deleteButton == null) {
            deleteButton = new Button(I18nProperties.getCaption(Captions.actionDelete));
            deleteButton.setId("delete");
            CssStyles.style(deleteButton, ValoTheme.BUTTON_DANGER, CssStyles.BUTTON_BORDER_NEUTRAL);
            deleteButton.addClickListener(new ClickListener() {
                private static final long serialVersionUID = 1L;

                @Override
                public void buttonClick(ClickEvent event) {
                    VaadinUiUtil.showDeleteConfirmationWindow(
                            String.format(I18nProperties.getString(Strings.confirmationDeleteEntity), entityName),
                            new Runnable() {
                                public void run() {
                                    onDelete();
                                }
                            });
                }
            });
        }

        return deleteButton;
    }

    @Override
    public boolean isModified() {
        if (fieldGroups != null) {
            for (FieldGroup fieldGroup : fieldGroups) {
                if (fieldGroup.isModified()) {
                    return true;
                }
            }
        } else if (wrappedComponent instanceof Buffered) {
            return ((Buffered) wrappedComponent).isModified();
        }
        return false;
    }

    public boolean isCommited() {
        return commited;
    }

    @Override
    public void commit() throws InvalidValueException, SourceException, CommitRuntimeException {

        if (fieldGroups != null) {
            if (fieldGroups.length > 1) {
                // validate all fields first, so commit will likely work for all fieldGroups
                // this is basically only needed when we have multiple field groups
                // FIXME this leads to problem #537 for AbstractEditForm with hideValidationUntilNextCommit 
                // can hopefully be fixed easier with Vaadin 8 architecture change
                getFieldsStream().forEach(field -> {
                    if (!field.isInvalidCommitted()) {
                        field.validate();
                    }
                });
            }

            try {
                for (FieldGroup fieldGroup : fieldGroups) {
                    fieldGroup.commit();
                }
            } catch (CommitException e) {
                if (e.getCause() instanceof InvalidValueException)
                    throw (InvalidValueException) e.getCause();
                else if (e.getCause() instanceof SourceException)
                    throw (SourceException) e.getCause();
                else
                    throw new CommitRuntimeException(e);
            }
        } else if (wrappedComponent instanceof Buffered) {
            ((Buffered) wrappedComponent).commit();
        } else {
            // NOOP
        }

        onCommit();
        commited = true;

        onDone();
    }

    private String findHtmlMessage(InvalidValueException exception) {
        if (!(exception.getMessage() == null || exception.getMessage().isEmpty()))
            return exception.getHtmlMessage();

        for (InvalidValueException cause : exception.getCauses()) {
            String message = findHtmlMessage(cause);
            if (message != null)
                return message;
        }

        return null;
    }

    public void commitAndHandle() {
        try {
            commit();
        } catch (InvalidValueException ex) {
            StringBuilder htmlMsg = new StringBuilder();
            String message = ex.getMessage();
            if (message != null && !message.isEmpty()) {
                htmlMsg.append(ex.getHtmlMessage());
            } else {

                InvalidValueException[] causes = ex.getCauses();
                if (causes != null) {

                    InvalidValueException firstCause = null;
                    boolean multipleCausesFound = false;
                    for (int i = 0; i < causes.length; i++) {
                        if (!causes[i].isInvisible()) {
                            if (firstCause == null) {
                                firstCause = causes[i];
                            } else {
                                multipleCausesFound = true;
                                break;
                            }
                        }
                    }
                    if (multipleCausesFound) {
                        htmlMsg.append("<ul>");
                        // Alle nochmal
                        for (int i = 0; i < causes.length; i++) {
                            if (!causes[i].isInvisible()) {
                                htmlMsg.append("<li style=\"color: #FFF;\">").append(findHtmlMessage(causes[i]))
                                        .append("</li>");
                            }
                        }
                        htmlMsg.append("</ul>");
                    } else if (firstCause != null) {
                        htmlMsg.append(findHtmlMessage(firstCause));
                    }

                }
            }

            new Notification(I18nProperties.getString(Strings.messageCheckInputData), htmlMsg.toString(),
                    Type.ERROR_MESSAGE, true).show(Page.getCurrent());
        }
    }

    @Override
    public void discard() {
        if (fieldGroups != null) {
            for (FieldGroup fieldGroup : fieldGroups) {
                fieldGroup.discard();
            }
        } else if (wrappedComponent instanceof Buffered) {
            ((Buffered) wrappedComponent).discard();
        } else {
            // NOOP
        }
        onDiscard();
        onDone();
    }

    @Override
    public void setBuffered(boolean buffered) {
        if (fieldGroups != null) {
            for (FieldGroup fieldGroup : fieldGroups) {
                fieldGroup.setBuffered(buffered);
            }
        } else if (wrappedComponent instanceof Buffered) {
            ((Buffered) wrappedComponent).setBuffered(buffered);
        } else {
            // NOOP
        }
    }

    @Override
    public boolean isBuffered() {
        if (fieldGroups != null) {
            Boolean buffered = null;
            for (FieldGroup fieldGroup : fieldGroups) {
                if (buffered != null && buffered.booleanValue() != fieldGroup.isBuffered())
                    throw new IllegalStateException("FieldGroups have different isBuffered states");
                buffered = fieldGroup.isBuffered();
            }
            return Boolean.TRUE.equals(buffered);
        } else if (wrappedComponent instanceof Buffered) {
            return ((Buffered) wrappedComponent).isBuffered();
        } else {
            return false;
        }
    }

    public void addCommitListener(CommitListener listener) {
        if (!commitListeners.contains(listener))
            commitListeners.add(listener);
    }

    public void setPrimaryCommitListener(CommitListener listener) {
        if (primaryCommitListener != null)
            throw new UnsupportedOperationException("primary listener already set");
        if (!commitListeners.contains(listener))
            commitListeners.add(0, listener);
        primaryCommitListener = null;
    }

    public void removeCommitListener(CommitListener listener) {
        commitListeners.remove(listener);
        if (primaryCommitListener != null && primaryCommitListener.equals(listener))
            primaryCommitListener = null;
    }

    private void onCommit() {

        for (CommitListener listener : commitListeners)
            listener.onCommit();
    }

    /**
     * Fgt einen Listener zum Abbrechen hinzu.
     * Blendet den Abbrechen-Button aber nicht ein.
     * @param listener
     */
    public void addDiscardListener(DiscardListener listener) {
        if (!discardListeners.contains(listener))
            discardListeners.add(listener);
    }

    public void removeDiscardListener(DiscardListener listener) {
        discardListeners.remove(listener);
    }

    private void onDiscard() {
        for (DiscardListener listener : discardListeners)
            listener.onDiscard();
    }

    public void addDoneListener(DoneListener listener) {
        if (!doneListeners.contains(listener))
            doneListeners.add(listener);
    }

    public void removeDoneListener(DoneListener listener) {
        doneListeners.remove(listener);
    }

    private void onDone() {
        for (DoneListener listener : doneListeners)
            listener.onDone();
    }

    public void addDeleteListener(DeleteListener listener, String entityName) {
        if (deleteListeners.isEmpty())
            buttonsPanel.addComponent(getDeleteButton(entityName), 0);
        if (!deleteListeners.contains(listener))
            deleteListeners.add(listener);
    }

    public boolean hasDeleteListener() {
        return !deleteListeners.isEmpty();
    }

    private void onDelete() {
        for (DeleteListener listener : deleteListeners)
            listener.onDelete();
    }

    @Override
    public void setReadOnly(boolean readOnly) {
        super.setReadOnly(readOnly);
        //
        //      getWrappedComponent().setReadOnly(readOnly);
        //      if (fieldGroups != null) {
        //         for (FieldGroup fieldGroup : fieldGroups) {
        //            fieldGroup.setReadOnly(readOnly);
        //         }
        //      }
        //
        //      buttonsPanel.setVisible(!readOnly);
    }

    //   @Override
    //   public boolean isReadOnly() {
    //      return getWrappedComponent().isReadOnly();
    //   }

    protected static class ClickShortcut extends Button.ClickShortcut {
        private static final long serialVersionUID = 1L;

        private final Notifier notifier;

        public ClickShortcut(Notifier notifier, Button button, int keyCode) {
            super(button, keyCode, null);
            this.notifier = notifier;
            notifier.addAction(this);
        }

        public void remove() {
            notifier.removeAction(this);
        }

        @Override
        public void handleAction(Object sender, Object target) {

            if (target instanceof TextArea || target instanceof RichTextArea) {
                // NOOP
            } else {
                super.handleAction(sender, target);
            }
        }

        @Override
        public String toString() {
            return notifier + "[" + getKeyCode() + "] =>" + button.getCaption();
        }
    }

    public static class CommitRuntimeException extends RuntimeException {
        private static final long serialVersionUID = 1L;

        public CommitRuntimeException(CommitException e) {
            super(e.getMessage(), e);
        }
    }

    @Override
    public void setWidth(float width, Unit unit) {
        super.setWidth(width, unit);
        updateInternalWidth();
    }

    @Override
    public void setHeight(float height, Unit unit) {
        super.setHeight(height, unit);
        updateInternalHeight();
    }

    private void updateInternalWidth() {
        if (contentPanel == null) {
            return;
        }
        if (getWidth() < 0) {
            contentPanel.setWidth(-1, Unit.PIXELS);
        } else {
            contentPanel.setWidth(100, Unit.PERCENTAGE);
        }
    }

    private void updateInternalHeight() {
        if (contentPanel == null) {
            return;
        }
        if (getHeight() < 0) {
            contentPanel.setHeight(-1, Unit.PIXELS);
        } else {
            contentPanel.setHeight(100, Unit.PERCENTAGE);
        }
    }
}