package au.com.vaadinutils.ui; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.Vector; import javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.Metamodel; import javax.persistence.metamodel.SingularAttribute; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import au.com.vaadinutils.crud.CrudEntity; import au.com.vaadinutils.crud.HeadingPropertySet; import au.com.vaadinutils.crud.SearchableSelectableEntityTable; import au.com.vaadinutils.dao.EntityManagerProvider; import au.com.vaadinutils.dao.JpaBaseDao; import au.com.vaadinutils.dao.NullFilter; import com.google.common.base.Preconditions; import com.vaadin.addon.jpacontainer.JPAContainer; import com.vaadin.addon.jpacontainer.util.DefaultQueryModifierDelegate; import com.vaadin.data.Buffered; import com.vaadin.data.Container.Filter; import com.vaadin.data.Container.Filterable; import com.vaadin.data.Property; import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.data.util.BeanContainer; import com.vaadin.data.util.filter.Compare; import com.vaadin.data.util.filter.Not; import com.vaadin.data.util.filter.Or; import com.vaadin.data.util.filter.SimpleStringFilter; import com.vaadin.event.ItemClickEvent; import com.vaadin.event.ItemClickEvent.ItemClickListener; import com.vaadin.server.FontAwesome; 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.CustomField; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Table; import com.vaadin.ui.Table.ColumnGenerator; import com.vaadin.ui.VerticalLayout; /** * @deprecated Replaced by {@link TwinColumnSelect} */ @Deprecated public class TwinColumnSearchableSelect<C extends CrudEntity> extends CustomField<Collection<C>> { private static final long serialVersionUID = -4316521010865902678L; private Logger logger = LogManager.getLogger(); private SingularAttribute<C, ?> listField; protected Collection<C> sourceValue; @SuppressWarnings("rawtypes") private Class<? extends Collection> valueClass; protected SearchableSelectableEntityTable<C> availableTable; protected JPAContainer<C> availableContainer; protected Table selectedTable; protected BeanContainer<Long, C> beans; private SingularAttribute<C, Long> beanIdField; protected HorizontalLayout mainLayout; protected Button addNewButton = new Button(FontAwesome.PLUS); protected Button addButton = new Button(">"); protected Button removeButton = new Button("<"); protected Button removeAllButton = new Button("<<"); protected Button addAllButton = new Button(">>"); protected Filter baselineFilter; protected Filter selectedFilter; protected String availableColumnHeader; private String itemLabel; protected ValueChangeListener<C> listener; private CreateNewCallback<C> createNewCallback; private boolean isAscending; private boolean showAddRemoveAll; protected boolean isRemoveAllClicked = false; private static final float BUTTON_LAYOUT_WIDTH = 50; private static final float BUTTON_WIDTH = 45; /** * Unfortunately TwinColumnSelect wont work with large sets, it isn't * searchable and it doesn't lazy load, it also isn't sortable. * * Hopefully I'll address all of these issues here. */ public TwinColumnSearchableSelect(String fieldName, SingularAttribute<C, ?> listField) { this(fieldName, listField, null, true); } public TwinColumnSearchableSelect(String fieldName, SingularAttribute<C, ?> listField, boolean isAscending) { this(fieldName, listField, null, isAscending); } public TwinColumnSearchableSelect(final String fieldName, final SingularAttribute<C, ?> listField, final String itemLabel) { this(fieldName, listField, itemLabel, true); } public TwinColumnSearchableSelect(final String fieldName, final SingularAttribute<C, ?> listField, String itemLabel, final boolean isAscending) { this.setCaption(fieldName); mainLayout = new HorizontalLayout(); this.isAscending = isAscending; this.listField = listField; beans = new BeanContainer<Long, C>(listField.getDeclaringType().getJavaType()); Metamodel metaModel = EntityManagerProvider.getEntityManager().getMetamodel(); EntityType<C> type = metaModel.entity(listField.getDeclaringType().getJavaType()); beanIdField = type.getDeclaredId(Long.class); availableContainer = JpaBaseDao.getGenericDao(listField.getDeclaringType().getJavaType()) .createVaadinContainer(); availableContainer.sort(new Object[] { listField.getName() }, new boolean[] { isAscending }); if (itemLabel == null) { itemLabel = listField.getName(); } this.itemLabel = itemLabel; selectedTable = new Table(); selectedTable.setContainerDataSource(createBeanContainer()); if (!selectedTable.getContainerPropertyIds().contains(itemLabel)) { logger.error("you need to define a getter for the field {} in {}, valid fields are {}", itemLabel, listField.getDeclaringType().getJavaType(), Arrays.toString(selectedTable.getContainerPropertyIds().toArray())); } selectedTable.addItemClickListener(new ItemClickListener() { private static final long serialVersionUID = 1L; @Override public void itemClick(ItemClickEvent event) { if (event.isDoubleClick()) removeButton.click(); } }); selectedTable.setVisibleColumns(itemLabel); selectedTable.setColumnHeaders("Selected"); selectedTable.setWidth(200, Unit.PIXELS); selectedTable.setHeight(300, Unit.PIXELS); selectedTable.setSelectable(true); selectedTable.setMultiSelect(true); // setting value of header here so that subclasses can // modify header if needed setAvailableColumnHeader(assignAvailableHeaderValue()); createAvailableTable(); addSelectedColumnTooltip(); addNewButton.setVisible(false); refreshSelected(); } public void allowAddNew(CreateNewCallback<C> createNewCallback) { addNewButton.setVisible(true); this.createNewCallback = createNewCallback; } public interface ValueChangeListener<C> { void valueChanged(Collection<C> value); } @Override @Deprecated public void addValueChangeListener(Property.ValueChangeListener listener) { } public void addValueChangeListener(ValueChangeListener<C> listener) { this.listener = listener; } @Override public void setHeight(String height) { super.setHeight(height); selectedTable.setHeight(height); availableTable.setHeight(height); mainLayout.setHeight(height); } @Override protected Component initContent() { mainLayout.addComponent(availableTable); mainLayout.addComponent(buildButtons()); mainLayout.addComponent(selectedTable); mainLayout.setExpandRatio(availableTable, 1); mainLayout.setExpandRatio(selectedTable, 1); return mainLayout; } public void setSelectedColumnGenerator(ColumnGenerator generatedColumn) { selectedTable.addGeneratedColumn(itemLabel, generatedColumn); } private void createAvailableTable() { availableTable = new SearchableSelectableEntityTable<C>(this.getClass().getSimpleName()) { private static final long serialVersionUID = 1L; @Override public HeadingPropertySet getHeadingPropertySet() { return new HeadingPropertySet.Builder<C>().addColumn(availableColumnHeader, itemLabel).build(); } @Override public Filterable getContainer() { return availableContainer; } @Override protected Filter getContainerFilter(String filterString, boolean advancedSearchActive) { Filter searchFilter = null; if (filterString != null && filterString.length() > 0) searchFilter = getSearchFilter(filterString); return NullFilter.and(baselineFilter, selectedFilter, searchFilter); } @Override protected String getTitle() { return ""; } }; availableTable.addItemClickListener(new ItemClickListener() { private static final long serialVersionUID = 1L; @Override public void itemClick(ItemClickEvent event) { if (event.isDoubleClick()) addButton.click(); } }); availableTable.disableSelectable(); availableTable.setWidth(200, Unit.PIXELS); availableTable.setHeight(300, Unit.PIXELS); } private Component buildButtons() { VerticalLayout layout = new VerticalLayout(); layout.setWidth(BUTTON_LAYOUT_WIDTH, Unit.PIXELS); removeButton.setWidth(BUTTON_WIDTH, Unit.PIXELS); removeAllButton.setWidth(BUTTON_WIDTH, Unit.PIXELS); addButton.setWidth(BUTTON_WIDTH, Unit.PIXELS); addAllButton.setWidth(BUTTON_WIDTH, Unit.PIXELS); addNewButton.setWidth(BUTTON_WIDTH, Unit.PIXELS); removeButton.addClickListener(removeClickListener()); removeAllButton.addClickListener(removeAllClickListener()); addButton.addClickListener(addClickListener()); addAllButton.addClickListener(addAllClickListener()); addNewButton.addClickListener(addNewClickListener()); layout.addComponent(addButton); layout.addComponent(removeButton); layout.addComponent(addAllButton); layout.addComponent(removeAllButton); layout.addComponent(addNewButton); layout.setComponentAlignment(removeButton, Alignment.MIDDLE_CENTER); layout.setComponentAlignment(addButton, Alignment.MIDDLE_CENTER); layout.setComponentAlignment(removeAllButton, Alignment.MIDDLE_CENTER); layout.setComponentAlignment(addAllButton, Alignment.MIDDLE_CENTER); layout.setComponentAlignment(addNewButton, Alignment.MIDDLE_CENTER); return layout; } private BeanContainer<Long, C> createBeanContainer() { beans.setBeanIdProperty(beanIdField.getName()); return beans; } @SuppressWarnings("unchecked") @Override public void commit() throws Buffered.SourceException, InvalidValueException { super.commit(); Collection<C> tmp = (Collection<C>) getConvertedValue(); // avoid possible npe if (sourceValue == null) { sourceValue = tmp; } // add missing for (C c : tmp) { if (!sourceValue.contains(c)) { sourceValue.add(c); } } // remove unneeded Set<C> toRemove = new HashSet<>(); for (C c : sourceValue) { if (!tmp.contains(c)) { toRemove.add(c); } } sourceValue.removeAll(toRemove); } @SuppressWarnings("unchecked") @Override public boolean isModified() { Collection<C> convertedValue = (Collection<C>) getConvertedValue(); Preconditions.checkNotNull(convertedValue, "If you look at getConvertedValue, you'll see convertedValue can never be null"); if ((sourceValue == null || sourceValue.size() == 0) && (convertedValue.size() > 0)) return true; if ((sourceValue == null || sourceValue.size() == 0) && (convertedValue.size() == 0)) return false; boolean equal = convertedValue.containsAll(sourceValue) && sourceValue.containsAll(convertedValue); return !equal; } @Override public void setReadOnly(boolean b) { selectedTable.setReadOnly(b); super.setReadOnly(b); // hide the add/remove and available list setAddButtonVisibility(!b); removeButton.setVisible(!b); if (showAddRemoveAll) { addAllButton.setVisible(!b); removeAllButton.setVisible(!b); } availableTable.setVisible(!b); } @SuppressWarnings("unchecked") @Override protected void setInternalValue(Collection<C> newValue) { if (newValue != null) { valueClass = newValue.getClass(); } super.setInternalValue(newValue); beans.removeAllItems(); if (newValue != null) { beans.addAll(newValue); } sourceValue = (Collection<C>) getConvertedValue(); beans.sort(new Object[] { listField.getName() }, new boolean[] { isAscending }); refreshSelected(); } @Override @SuppressWarnings("unchecked") public Collection<C> getValue() { return (Collection<C>) getConvertedValue(); } @SuppressWarnings("unchecked") public Collection<C> getFieldValue() { return (Collection<C>) getConvertedValue(); } @Override @SuppressWarnings("unchecked") public Collection<C> getInternalValue() { return (Collection<C>) getConvertedValue(); } @Override public Object getConvertedValue() { Collection<C> selected; if (valueClass != null && List.class.isAssignableFrom(valueClass)) { selected = new LinkedList<>(); } else { selected = new HashSet<>(); } // Just to be clear that this method will NEVER return null Preconditions.checkNotNull(selected, "If you look at getConvertedValue, you'll see this can never be null"); if (beans != null) { for (Long id : beans.getItemIds()) { selected.add(beans.getItem(id).getBean()); } } return selected; } @SuppressWarnings("unchecked") @Override public Class<Collection<C>> getType() { // Had to remove this as maven can't compile it. // return (Class<? extends Collection<C>>) a.getClass(); return (Class<Collection<C>>) (Class<?>) Collection.class; } public void setFilter(Filter filter) { baselineFilter = filter; availableContainer.setFireContainerItemSetChangeEvents(false); availableContainer.removeAllContainerFilters(); availableContainer.setFireContainerItemSetChangeEvents(true); availableContainer.addContainerFilter(filter); } private void refreshSelected() { final List<Long> selectedIds = beans.getItemIds(); if (selectedIds.size() == 1) { selectedFilter = new Not(new Compare.Equal(beanIdField.getName(), selectedIds.get(0))); availableTable.triggerFilter(); return; } final Vector<Filter> filters = new Vector<>(); for (Long id : selectedIds) { filters.add(new Compare.Equal(beanIdField.getName(), id)); } selectedFilter = new Not(new Or(filters.toArray(new Filter[filters.size()]))); availableTable.triggerFilter(); } public void setFilterDelegate(DefaultQueryModifierDelegate defaultQueryModifierDelegate) { availableContainer.setQueryModifierDelegate(defaultQueryModifierDelegate); } /** * defaults to true (visible) * * @param show */ public void showAddAndRemoveAllButtons(boolean show) { showAddRemoveAll = false; addAllButton.setVisible(show); removeAllButton.setVisible(show); } public Collection<C> getSourceValue() { return sourceValue; } public boolean isRemoveAllowed() { return true; } protected Table getSelectedCols() { return selectedTable; } public void handleRemoveValidation() { } protected void handleAddAction(Long id) { JpaBaseDao<C, Long> dao = JpaBaseDao.getGenericDao(listField.getDeclaringType().getJavaType()); C cust = dao.findById(id); if (cust != null) { beans.addBean(cust); if (listener != null) { listener.valueChanged(getFieldValue()); } } refreshSelected(); } public boolean isPreAddActionRequired() { return false; } public void handlePreAddAction(Long id) { } protected void addSelectedColumnTooltip() { } public void refreshSelectedColumn() { selectedTable.refreshRowCache(); } protected void postRemoveAction() { } protected void postAddAction() { } protected JPAContainer<C> getAvailableContainer() { return availableContainer; } public void refreshAvailableContainer() { availableContainer.refresh(); } protected void resetAvailableContainer(JPAContainer<C> newContainer) { this.availableContainer = newContainer; } public void resetSelected() { beans.removeAllItems(); if (listener != null) { listener.valueChanged(getFieldValue()); } } public void setSelectedColumnHeader(String header) { this.selectedTable.setColumnHeaders(header); } public String getAvailableColumnHeader() { return availableColumnHeader; } public void setAvailableColumnHeader(String availableColumnHeader) { this.availableColumnHeader = availableColumnHeader; } protected String assignAvailableHeaderValue() { return "Available"; } protected ClickListener addAllClickListener() { return new ClickListener() { private static final long serialVersionUID = 1L; @SuppressWarnings("unchecked") @Override public void buttonClick(ClickEvent event) { List<Long> ids = new LinkedList<>(); ids.addAll((Collection<? extends Long>) availableTable.getContainer().getItemIds()); for (Long id : ids) { JpaBaseDao<C, Long> dao = JpaBaseDao.getGenericDao(listField.getDeclaringType().getJavaType()); C cust = dao.findById(id); if (cust != null) { beans.addBean(cust); if (listener != null) { listener.valueChanged(getFieldValue()); } } } refreshSelected(); } }; } protected ClickListener addClickListener() { return new ClickListener() { private static final long serialVersionUID = 1L; @SuppressWarnings("unchecked") @Override public void buttonClick(ClickEvent event) { List<Long> ids = new LinkedList<>(); ids.addAll((Collection<Long>) availableTable.getSelectedItems()); if (ids.size() > 0) { for (Long id : ids) { if (id != null) { if (isPreAddActionRequired()) handlePreAddAction(id); else handleAddAction(id); postAddAction(); } } } beans.sort(new Object[] { listField.getName() }, new boolean[] { isAscending }); } }; } protected ClickListener addNewClickListener() { return new ClickListener() { private static final long serialVersionUID = 1L; @Override public void buttonClick(ClickEvent event) { createNewCallback.createNew(new RefreshCallback() { @Override public void refresh() { availableContainer.refresh(); } }); } }; } protected ClickListener removeClickListener() { return new ClickListener() { private static final long serialVersionUID = 1L; @SuppressWarnings("unchecked") @Override public void buttonClick(ClickEvent event) { try { final List<Long> ids = new LinkedList<>(); ids.addAll((Collection<? extends Long>) selectedTable.getValue()); for (Long id : ids) { if (isRemoveAllowed()) { beans.removeItem(id); if (listener != null) { listener.valueChanged(getFieldValue()); } postRemoveAction(); refreshSelected(); } else { handleRemoveValidation(); } } } catch (Exception e) { logger.error(e, e); } } }; } protected ClickListener removeAllClickListener() { return new ClickListener() { private static final long serialVersionUID = 1L; @Override public void buttonClick(ClickEvent event) { try { isRemoveAllClicked = true; if (isRemoveAllowed()) { beans.removeAllItems(); if (listener != null) { listener.valueChanged(getFieldValue()); } refreshSelected(); } else { handleRemoveValidation(); } isRemoveAllClicked = false; } catch (Exception e) { logger.error(e, e); } } }; } protected void setAddButtonVisibility(boolean visible) { addButton.setVisible(visible); } public void setSizeFull() { super.setSizeFull(); mainLayout.setSizeFull(); selectedTable.setSizeFull(); availableTable.setSizeFull(); } protected Filter getSearchFilter(final String filterString) { return new SimpleStringFilter(listField.getName(), filterString, true, false); } @Override public void setWidth(float width, Unit unit) { super.setWidth(width, unit); if (mainLayout != null && selectedTable != null && availableTable != null) { mainLayout.setWidth(width, unit); selectedTable.setWidth(((width - 5) / 2) - (BUTTON_LAYOUT_WIDTH / 2), unit); availableTable.setWidth(((width - 5) / 2) - (BUTTON_LAYOUT_WIDTH / 2), unit); } } }