package com.constellio.app.ui.framework.components.fields.list; import static com.constellio.app.ui.i18n.i18n.$; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; import org.vaadin.dialogs.ConfirmDialog; import com.constellio.app.ui.application.ConstellioUI; import com.constellio.app.ui.framework.buttons.AddButton; import com.constellio.app.ui.framework.buttons.DeleteButton; import com.constellio.app.ui.framework.buttons.EditButton; import com.constellio.app.ui.framework.containers.ButtonsContainer; import com.constellio.app.ui.framework.containers.ButtonsContainer.ContainerButton; import com.constellio.app.ui.handlers.OnEnterKeyHandler; import com.vaadin.data.Property; import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.data.util.IndexedContainer; import com.vaadin.data.util.ObjectProperty; import com.vaadin.data.util.converter.Converter; import com.vaadin.data.util.converter.Converter.ConversionException; import com.vaadin.server.ErrorMessage; import com.vaadin.shared.ui.label.ContentMode; import com.vaadin.ui.AbstractField; import com.vaadin.ui.AbstractSelect.ItemCaptionMode; import com.vaadin.ui.Button; import com.vaadin.ui.ComboBox; import com.vaadin.ui.Component; import com.vaadin.ui.CustomField; import com.vaadin.ui.DateField; import com.vaadin.ui.Field; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.Table; import com.vaadin.ui.Table.ColumnHeaderMode; import com.vaadin.ui.TextField; import com.vaadin.ui.VerticalLayout; public abstract class ListAddRemoveField<T extends Serializable, F extends AbstractField<?>> extends CustomField<List<T>> { public static final String STYLE_NAME = "list-add-remove"; private static final String ERROR_STYLE_NAME = STYLE_NAME + "-error"; public static final String ADD_EDIT_FIELD_LAYOUT_STYLE_NAME = STYLE_NAME + "-add-edit-field-layout"; public static final String ADD_EDIT_FIELD_STYLE_NAME = STYLE_NAME + "-add-edit-field"; public static final String ADD_BUTTON_STYLE_NAME = STYLE_NAME + "-add-button"; public static final String EDIT_BUTTON_STYLE_NAME = STYLE_NAME + "-edit-button"; public static final String REMOVE_BUTTON_STYLE_NAME = STYLE_NAME + "-remove-button"; public static final String TABLE_STYLE_NAME = STYLE_NAME + "-table"; public static final String VALUES_STYLE_NAME = STYLE_NAME + "-values"; private static final String CAPTION_PROPERTY_ID = "caption"; private VerticalLayout mainLayout; private HorizontalLayout addEditFieldLayout; protected F addEditField; private Button addButton; private ButtonsContainer<ValuesContainer> valuesAndButtonsContainer; protected Table valuesTable; private Converter<String, T> itemConverter; private boolean delayedFocus = false; private Boolean delayedReadOnly = null; private Boolean delayedEnabled = null; private List<T> delayedNewFieldValue; public ListAddRemoveField() { setWidth("100%"); setHeightUndefined(); } @Override public String getRequiredError() { return addEditField.getRequiredError(); } @Override public void setRequiredError(String requiredMessage) { addEditField.setRequiredError(requiredMessage); addStyleName(ERROR_STYLE_NAME); } @Override public String getConversionError() { return addEditField.getConversionError(); } @Override public void setConversionError(String valueConversionError) { addEditField.setConversionError(valueConversionError); addStyleName(ERROR_STYLE_NAME); } @Override public ErrorMessage getComponentError() { return addEditField.getComponentError(); } @Override public void setComponentError(ErrorMessage componentError) { addEditField.setComponentError(componentError); } @Override public boolean isValid() { boolean valid; if (!addEditField.isValid() || !super.isValid()) { valid = false; } else valid = !(isRequired() && isRequiredValueMissing()); return valid; } private boolean isRequiredValueMissing() { boolean requiredValueMissing; if (isRequired()) { if (getValue() == null) { requiredValueMissing = true; } else if (isRequired() && getInternalValue() instanceof List) { requiredValueMissing = getInternalValue().isEmpty(); } else { requiredValueMissing = false; } } else { requiredValueMissing = false; } return requiredValueMissing; } @Override public void validate() throws InvalidValueException { try { addEditField.validate(); super.validate(); if (isRequiredValueMissing()) { throw new InvalidValueException($("requiredField")); } removeStyleName(ERROR_STYLE_NAME); } catch (InvalidValueException e) { throw e; } } public final Button getAddButton() { return addButton; } public final F getAddEditField() { return addEditField; } public final HorizontalLayout getAddEditFieldLayout() { return addEditFieldLayout; } @Override public void focus() { if (addEditField != null) { addEditField.focus(); delayedFocus = false; } else { delayedFocus = true; } } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public Class getType() { return List.class; } public Converter<String, T> getItemConverter() { return itemConverter; } public void setItemConverter(Converter<String, T> itemConverter) { this.itemConverter = itemConverter; } @SuppressWarnings("unchecked") protected void tryAdd() { addEditField.commit(); T value = (T) addEditField.getPropertyDataSource().getValue(); addValue(value); removeStyleName(ERROR_STYLE_NAME); } protected void addValue(T value) { if (value != null) { valuesAndButtonsContainer.addItem(value); addEditField.setValue(null); notifyValueChange(); } } protected void removeValue(Object value) { if (value != null) { valuesAndButtonsContainer.removeItem(value); notifyValueChange(); } } protected void removeValue(T value) { if (value != null) { valuesAndButtonsContainer.removeItem(value); notifyValueChange(); } } @SuppressWarnings("unchecked") private void notifyValueChange() { if (!isBuffered() && getPropertyDataSource() != null) { getPropertyDataSource().setValue(getInternalValue()); } else { valueChange(new ValueChangeEvent(this)); } } protected boolean isEditPossible() { return true; } protected boolean isEditButtonVisible(T item) { return true; } protected boolean isDeleteButtonVisible(T item) { return true; } protected List<?> getExtraColumnPropertyIds() { return null; } @SuppressWarnings("unchecked") protected T getListElementValue(Object itemId) { return (T) itemId; } protected Property<?> getExtraColumnProperty(Object itemId, Object propertyId) { throw new UnsupportedOperationException("No extra property"); } protected Class<?> getExtraColumnType(Object propertyId) { throw new UnsupportedOperationException("No extra property"); } protected int getExtraColumnWidth(Object propertyId) { return -1; } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override protected Component initContent() { addStyleName(STYLE_NAME); mainLayout = new VerticalLayout(); mainLayout.addStyleName(STYLE_NAME + "-main-layout"); mainLayout.setHeightUndefined(); mainLayout.setSpacing(true); setMainLayoutWidth(mainLayout); addEditFieldLayout = new HorizontalLayout(); addEditFieldLayout.addStyleName(ADD_EDIT_FIELD_LAYOUT_STYLE_NAME); addEditFieldLayout.setSpacing(true); addEditField = newAddEditField(); if (delayedFocus) { addEditField.focus(); } addEditField.setPropertyDataSource(new ObjectProperty(null, Object.class)); addEditField.addStyleName(ADD_EDIT_FIELD_STYLE_NAME); OnEnterKeyHandler onEnterKeyHandler = new OnEnterKeyHandler() { @Override public void onEnterKeyPressed() { tryAdd(); } }; if (addEditField instanceof TextField) { onEnterKeyHandler.installOn((TextField) addEditField); } else if (addEditField instanceof DateField) { onEnterKeyHandler.installOn((DateField) addEditField); } else if (addEditField instanceof ComboBox) { onEnterKeyHandler.installOn((ComboBox) addEditField); } addButton = new AddButton(false) { @Override protected void buttonClick(ClickEvent event) { tryAdd(); } }; addButton.addStyleName(ADD_BUTTON_STYLE_NAME); addButton.setVisible(isAddButtonVisible()); ValuesContainer valuesContainer = new ValuesContainer(new ArrayList<T>()); valuesAndButtonsContainer = new ButtonsContainer(valuesContainer); if (isEditPossible()) { valuesAndButtonsContainer.addButton(addEditButton()); } valuesAndButtonsContainer.addButton(new ContainerButton() { @Override protected Button newButtonInstance(final Object itemId, ButtonsContainer<?> container) { DeleteButton deleteButton = new DeleteButton() { @Override protected void confirmButtonClick(ConfirmDialog dialog) { removeValue((T) itemId); } }; if (!isDeleteButtonVisible((T) itemId)) { deleteButton.setVisible(false); } deleteButton.setEnabled(!ListAddRemoveField.this.isReadOnly() && ListAddRemoveField.this.isEnabled()); deleteButton.addStyleName(REMOVE_BUTTON_STYLE_NAME); return deleteButton; } }); valuesTable = new Table(); valuesTable.addStyleName(TABLE_STYLE_NAME); valuesTable.setContainerDataSource(valuesAndButtonsContainer); valuesTable.setPageLength(0); valuesTable.setWidth("100%"); valuesTable.setNullSelectionAllowed(false); valuesTable.setColumnHeaderMode(ColumnHeaderMode.HIDDEN); valuesTable.setItemCaptionMode(ItemCaptionMode.PROPERTY); valuesTable.setColumnExpandRatio(CAPTION_PROPERTY_ID, 1); Collection<?> extraColumnPropertyIds = getExtraColumnPropertyIds(); if (extraColumnPropertyIds != null) { for (Object extraPropertyIds : extraColumnPropertyIds) { int extraColumnWidth = getExtraColumnWidth(extraPropertyIds); if (extraColumnWidth > 0) { valuesTable.setColumnWidth(extraPropertyIds, extraColumnWidth); } } } int buttonsWidth; if (isEditPossible()) { buttonsWidth = 80; } else { buttonsWidth = 47; } valuesTable.setColumnWidth(ButtonsContainer.DEFAULT_BUTTONS_PROPERTY_ID, buttonsWidth); mainLayout.addComponents(addEditFieldLayout, valuesTable); addEditFieldLayout.addComponents(addEditField, addButton); addEditFieldLayout.setExpandRatio(addEditField, 1); if (delayedReadOnly != null) { setReadOnly(delayedReadOnly); } if (delayedEnabled != null) { setEnabled(delayedEnabled); } if (delayedNewFieldValue != null) { List<T> newFieldValue = delayedNewFieldValue; delayedNewFieldValue = null; setValue(newFieldValue); } return mainLayout; } protected void setMainLayoutWidth(VerticalLayout mainLayout) { mainLayout.setWidth("99%"); } protected ContainerButton addEditButton() { return new ContainerButton() { @Override protected Button newButtonInstance(final Object itemId, ButtonsContainer<?> container) { EditButton editButton = new EditButton() { @Override protected void buttonClick(ClickEvent event) { removeValue((T) itemId); ((Field<T>) addEditField).setValue((T) itemId); addEditField.focus(); } }; if (!isEditButtonVisible((T) itemId)) { editButton.setVisible(false); } editButton.setEnabled(!ListAddRemoveField.this.isReadOnly() && ListAddRemoveField.this.isEnabled()); editButton.addStyleName(EDIT_BUTTON_STYLE_NAME); return editButton; } }; } @SuppressWarnings("unchecked") @Override protected List<T> getInternalValue() { List<T> internalValue; if (valuesAndButtonsContainer != null) { internalValue = (List<T>) valuesAndButtonsContainer.getItemIds(); } else { internalValue = null; } return internalValue; } @Override protected void setInternalValue(List<T> newValue) { if (valuesAndButtonsContainer != null) { List<T> newValueCopy = new ArrayList<>(); if (newValue != null) { newValueCopy.addAll(newValue); } valuesAndButtonsContainer.removeAllItems(); for (T t : newValueCopy) { valuesAndButtonsContainer.addItem(t); } } else { delayedNewFieldValue = newValue; } } @Override public List<T> getValue() { List<T> value = getInternalValue(); if (value != null && value.isEmpty()) { // value = null; } return value; } @Override public void setValue(List<T> newFieldValue) throws ReadOnlyException, ConversionException { super.setValue(newFieldValue); } protected boolean isAddButtonVisible() { return true; } @Override public void discard() throws SourceException { addEditField.discard(); super.discard(); } @Override public void setReadOnly(boolean readOnly) { super.setReadOnly(readOnly); if (addEditField != null) { addEditField.setReadOnly(readOnly); addButton.setEnabled(!readOnly); for (Button containerButton : getContainerButtons()) { containerButton.setEnabled(!readOnly); } } else { delayedReadOnly = readOnly; } } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); if (addEditField != null) { addEditField.setEnabled(enabled); addButton.setEnabled(enabled); for (Button containerButton : getContainerButtons()) { containerButton.setEnabled(enabled); } } else { delayedEnabled = enabled; } } private List<Button> getContainerButtons() { List<Button> containerButtons = new ArrayList<Button>(); Collection<?> itemIds = valuesAndButtonsContainer.getItemIds(); for (Object itemId : itemIds) { List<Button> itemButtons = valuesAndButtonsContainer.getButtons(itemId); containerButtons.addAll(itemButtons); } return containerButtons; } @Override public void commit() throws SourceException, InvalidValueException { List<T> internalValue = getInternalValue(); if (internalValue != null && internalValue.isEmpty()) { setInternalValue(null); } tryAdd(); super.commit(); } protected abstract F newAddEditField(); protected Class<?> getCaptionComponentClass() { return Label.class; } protected Component newCaptionComponent(T itemId, String caption) { Label captionLabel = new Label(caption); captionLabel.setContentMode(ContentMode.HTML); return captionLabel; } protected String getItemCaption(Object itemId) { String caption; if (itemConverter != null) { Locale locale = ConstellioUI.getCurrentSessionContext().getCurrentLocale(); caption = itemConverter.convertToPresentation((T) itemId, String.class, locale); } else { caption = itemId.toString(); } return caption; } private class ValuesContainer extends IndexedContainer { public ValuesContainer(List<T> values) { super(values); addContainerProperty(CAPTION_PROPERTY_ID, getCaptionComponentClass(), null); List<?> extraPropertyIds = getExtraColumnPropertyIds(); if (extraPropertyIds != null) { for (Object extraPropertyId : extraPropertyIds) { Class<?> extraPropertyType = getExtraColumnType(extraPropertyId); addContainerProperty(extraPropertyId, extraPropertyType, null); } } } @SuppressWarnings("unchecked") @Override public Property<?> getContainerProperty(final Object itemId, Object propertyId) { if (itemId != null) { if (CAPTION_PROPERTY_ID.equals(propertyId)) { String caption = getItemCaption(itemId); Component captionComponent = newCaptionComponent((T) itemId, caption); return new ObjectProperty<Component>(captionComponent, Component.class); } else { return getExtraColumnProperty(itemId, propertyId); } } else { return new ObjectProperty<>(null); } } } }