blob: 2aaeba1d42b3176497ab49f9194c1f8bfe98d4c5 [file] [log] [blame]
package org.tepi.filtertable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.tepi.filtertable.FilterFieldGenerator.IFilterTable;
import org.tepi.filtertable.datefilter.DateInterval;
import com.vaadin.data.Container;
import com.vaadin.data.util.converter.Converter.ConversionException;
import com.vaadin.server.LegacyPaint;
import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
import com.vaadin.ui.AbstractField;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomTable;
import com.vaadin.ui.HasComponents;
import com.vaadin.ui.TextField;
/**
* FilterTable is an extension of the Vaadin Table component that provides
* automatically generated filter fields for each column.
*
* @author Teppo Kurki
*
*/
@SuppressWarnings("serial")
public class FilterTable extends CustomTable implements IFilterTable {
/* Maps property id's to column filter components */
private final Map<Object, Component> columnIdToFilterMap = new HashMap<Object, Component>();
/* Internal list of currently collapsed column id:s */
private final Set<Object> collapsedColumnIds = new HashSet<Object>();
/* Set to true to show the filter components */
private boolean filtersVisible;
/* Filter Generator and Decorator */
private FilterGenerator filterGenerator;
private FilterDecorator decorator;
/* FilterFieldGenerator instance */
private final FilterFieldGenerator generator;
/* Is initialization done */
private final boolean initDone;
/* Force-render filter fields */
private boolean reRenderFilterFields;
/* Wrap filters with additional div for styling? */
private boolean wrapFilters = false;
/* Are filters run immediately, or only on demand? */
private boolean filtersRunOnDemand = false;
/* Custom column header style names */
private final HashMap<Object, String> columnHeaderStylenames = new HashMap<Object, String>();
/**
* Creates a new empty FilterTable
*/
public FilterTable() {
this(null);
}
/**
* Creates a new empty FilterTable with the given caption
*
* @param caption
* Caption to set for the FilterTable
*/
public FilterTable(String caption) {
super(caption);
generator = new FilterFieldGenerator(this);
initDone = true;
}
@Override
public void paintContent(PaintTarget target) throws PaintException {
super.paintContent(target);
/* Add filter components to UIDL */
target.startTag("filters");
target.addAttribute("filtersvisible", filtersVisible);
target.addAttribute("forceRender", reRenderFilterFields);
target.addAttribute("wrapFilters", wrapFilters);
reRenderFilterFields = false;
/* if (filtersVisible) { */
for (Object key : getColumnIdToFilterMap().keySet()) {
/* Make sure parent is set properly */
if (columnIdToFilterMap.get(key) != null
&& columnIdToFilterMap.get(key).getParent() == null) {
continue;// columnIdToFilterMap.get(key).setParent(this);
}
/* Paint the filter field */
target.startTag("filtercomponent-" + columnIdMap.key(key));
target.addAttribute("columnid", columnIdMap.key(key));
Component c = getColumnIdToFilterMap().get(key);
LegacyPaint.paint(c, target);
target.endTag("filtercomponent-" + columnIdMap.key(key));
// } else {
// if (columnIdToFilterMap.get(key) != null) {
// columnIdToFilterMap.get(key).setParent(null);
// }
}
// }
target.endTag("filters");
String[] headerStylenames = getColumnHeaderStylenamesForPaint();
if (headerStylenames != null) {
target.addAttribute("columnheaderstylenames", headerStylenames);
}
}
@Override
public void setColumnCollapsed(Object propertyId, boolean collapsed)
throws IllegalStateException {
super.setColumnCollapsed(propertyId, collapsed);
Component c = getColumnIdToFilterMap().get(propertyId);
if (collapsed) {
collapsedColumnIds.add(propertyId);
if (c != null) {
c.setParent(null);
if (c instanceof TextField) {
((TextField) c).setValue("");
} else if (c instanceof AbstractField<?>) {
((AbstractField<?>) c).setValue(null);
}
}
} else {
if (c != null) {
c.setParent(this);
}
collapsedColumnIds.remove(propertyId);
}
reRenderFilterFields = true;
markAsDirty();
}
@Override
public void setContainerDataSource(Container newDataSource) {
super.setContainerDataSource(newDataSource);
resetFilters();
}
@Override
public void setContainerDataSource(Container newDataSource,
Collection<?> visibleIds) {
super.setContainerDataSource(newDataSource, visibleIds);
resetFilters();
}
/**
* Resets all filters.
*
* Note: Recreates the filter fields also!
*/
public void resetFilters() {
if (initDone) {
disableContentRefreshing();
for (Component c : columnIdToFilterMap.values()) {
c.setParent(null);
}
collapsedColumnIds.clear();
columnIdToFilterMap.clear();
generator.destroyFilterComponents();
generator.initializeFilterFields();
reRenderFilterFields = true;
enableContentRefreshing(true);
}
}
/**
* Clears all filters without recreating the filter fields.
*/
public void clearFilters() {
if (initDone) {
generator.clearFilterData();
}
}
/**
* Sets the FilterDecorator for this FilterTable. FilterDecorator may be
* used to provide proper translated display names and icons for the enum,
* boolean and date values used in the filters.
*
* Note: Recreates the filter fields also!
*
* @param decorator
* An implementation of FilterDecorator to use with this
* FilterTable. Remove by giving null as this parameter.
*/
public void setFilterDecorator(FilterDecorator decorator) {
this.decorator = decorator;
resetFilters();
}
/**
* Sets the FilterGenerator to use for providing custom Filters to the
* container for one or more properties.
*
* @param generator
* FilterGenerator to use with this FilterTable. Remove by giving
* null as this parameter.
*/
public void setFilterGenerator(FilterGenerator generator) {
filterGenerator = generator;
}
/**
* Sets the Filter bar visible or hidden.
*
* @param filtersVisible
* true to set the Filter bar visible.
*/
public void setFilterBarVisible(boolean filtersVisible) {
this.filtersVisible = filtersVisible;
for (Object key : columnIdToFilterMap.keySet()) {
columnIdToFilterMap.get(key)
.setParent(filtersVisible ? this : null);
}
reRenderFilterFields = true;
markAsDirty();
}
/**
* Returns the current visibility state of the filter bar.
*
* @return true if the filter bar is visible
*/
public boolean isFilterBarVisible() {
return filtersVisible;
}
/**
* Toggles the visibility of the filter field defined for the give column
* ID.
*
* @param columnId
* Column/Property ID of the filter to toggle
* @param visible
* true to set visible, false to set hidden
*/
public void setFilterFieldVisible(Object columnId, boolean visible) {
Component component = columnIdToFilterMap.get(columnId);
if (component != null) {
component.setVisible(visible);
reRenderFilterFields = true;
markAsDirty();
}
}
/**
* Returns visibility state of the filter field for the given column ID
*
* @param columnId
* Column/Property ID of the filter field to query
* @return true if filter is visible, false if it's hidden
*/
public boolean isFilterFieldVisible(Object columnId) {
Component component = columnIdToFilterMap.get(columnId);
if (component != null) {
return component.isVisible();
}
return false;
}
/**
* Set a value of a filter field. Note that for Date filters you need to
* provide a value of {@link DateInterval} type.
*
* @param propertyId
* Property id for which to set the value
* @param value
* New value
* @return true if setting succeeded, false if field was not found
* @throws ConversionException
* exception from the underlying field
*/
public boolean setFilterFieldValue(Object propertyId, Object value)
throws ConversionException {
Component field = getColumnIdToFilterMap().get(propertyId);
boolean retVal = field != null;
if (field != null) {
((AbstractField<?>) field).setConvertedValue(value);
}
return retVal;
}
/**
* Get the current value of a filter field
*
* @param propertyId
* Property id from which to get the value
* @return Current value
*/
public Object getFilterFieldValue(Object propertyId) {
Component field = getColumnIdToFilterMap().get(propertyId);
if (field != null) {
return ((AbstractField<?>) field).getValue();
} else {
return null;
}
}
/**
* Returns the filter component instance associated with the given property
* ID.
*
* @param propertyId
* Property id for which to find the filter component.
* @return Related component instance or null if not found.
*/
public Component getFilterField(Object propertyId) {
return getColumnIdToFilterMap().get(propertyId);
}
@Override
public Filterable getFilterable() {
return getContainerDataSource() instanceof Filterable ? (Filterable) getContainerDataSource()
: null;
}
@Override
public FilterGenerator getFilterGenerator() {
return filterGenerator;
}
@Override
public FilterDecorator getFilterDecorator() {
return decorator;
}
@Override
public Map<Object, Component> getColumnIdToFilterMap() {
return columnIdToFilterMap;
}
@Override
public HasComponents getAsComponent() {
return this;
}
@Override
public Iterator<Component> iterator() {
Set<Component> children = new HashSet<Component>();
if (visibleComponents != null) {
children.addAll(visibleComponents);
}
if (initDone && filtersVisible) {
for (Object key : columnIdToFilterMap.keySet()) {
Component filter = columnIdToFilterMap.get(key);
if (equals(filter.getParent()) && filter.isVisible()) {
children.add(filter);
}
}
}
return children.iterator();
}
@Override
public void setVisibleColumns(Object... visibleColumns) {
reRenderFilterFields = true;
if (visibleColumns != null && columnIdToFilterMap != null) {
/* First clear all parent references */
for (Object key : columnIdToFilterMap.keySet()) {
columnIdToFilterMap.get(key).setParent(null);
}
/* Set this as parent to visible columns */
for (Object key : visibleColumns) {
Component filter = columnIdToFilterMap.get(key);
if (filter != null) {
filter.setParent(this);
}
}
}
super.setVisibleColumns(visibleColumns);
}
@Override
public void setRefreshingEnabled(boolean enabled) {
if (enabled) {
enableContentRefreshing(true);
} else {
disableContentRefreshing();
}
}
public void setWrapFilters(boolean wrapFilters) {
if (this.wrapFilters == wrapFilters) {
return;
} else {
this.wrapFilters = wrapFilters;
reRenderFilterFields = true;
markAsDirty();
}
}
public boolean isWrapFilters() {
return wrapFilters;
}
public void setFilterOnDemand(boolean filterOnDemand) {
if (filtersRunOnDemand == filterOnDemand) {
return;
} else {
filtersRunOnDemand = filterOnDemand;
reRenderFilterFields = true;
generator.setFilterOnDemandMode(filtersRunOnDemand);
}
}
public boolean isFilterOnDemand() {
return filtersRunOnDemand;
}
public void runFilters() {
if (!filtersRunOnDemand) {
throw new IllegalStateException(
"Can't run filters on demand when filtersRunOnDemand is set to false");
}
generator.runFiltersNow();
}
private String[] getColumnHeaderStylenamesForPaint() {
String[] allStyleNames = getColumnHeaderStylenames();
if (allStyleNames == null) {
return null;
}
List<String> stylenamesForPaint = new ArrayList<String>();
Object[] visibleColumns = getVisibleColumns();
for (int i = 0; i < allStyleNames.length; i++) {
Object colId = visibleColumns[i];
String stylename = allStyleNames[i];
// don't add collapsed columns
if (!collapsedColumnIds.contains(colId)) {
if (stylename == null) {
// replace nulls with empty strings
stylenamesForPaint.add("");
} else {
stylenamesForPaint.add(stylename);
}
}
}
return stylenamesForPaint
.toArray(new String[stylenamesForPaint.size()]);
}
/**
* Gets the header style names of the columns.
*
* <p>
* The header style names match the property ids given by the set visible
* column headers. The table must be set in either
* {@link #COLUMN_HEADER_MODE_EXPLICIT} or
* {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the
* headers.
* </p>
*
* @return an array of the column header style names or null if there aren't
* style names set.
*/
public String[] getColumnHeaderStylenames() {
if (columnHeaderStylenames.size() == 0) {
return null;
}
Object[] visibleColumns = getVisibleColumns();
final String[] headerStylenames = new String[visibleColumns.length];
for (int i = 0; i < visibleColumns.length; i++) {
headerStylenames[i] = columnHeaderStylenames.get(visibleColumns[i]);
}
return headerStylenames;
}
/**
* Sets the header style names of columns.
*
* <p>
* The headers match the property ids given by the set visible column
* headers. The table must be set in either
* {@link #COLUMN_HEADER_MODE_EXPLICIT} or
* {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the
* headers.
*
* @param headerStylenames
* an array of the column header style names that match the
* {@link #getVisibleColumns()} method
*/
public void setColumnHeaderStylenames(String... headerStylenames) {
Object[] visibleColumns = getVisibleColumns();
if (headerStylenames.length != visibleColumns.length) {
throw new IllegalArgumentException(
"The length of the header style names array must match the number of visible columns");
}
columnHeaderStylenames.clear();
for (int i = 0; i < visibleColumns.length; i++) {
columnHeaderStylenames.put(visibleColumns[i], headerStylenames[i]);
}
markAsDirty();
}
/**
* Sets the column header style name for the specified column.
*
* @param propertyId
* the propertyId identifying the column
* @param headerStylename
* the header style name to set
*/
public void setColumnHeaderStylename(Object propertyId,
String headerStylename) {
if (headerStylename == null) {
columnHeaderStylenames.remove(propertyId);
} else {
columnHeaderStylenames.put(propertyId, headerStylename);
}
markAsDirty();
}
/**
* Gets the header style name for the specified column.
*
* @param propertyId
* the propertyId identifying the column.
* @return the header style name for the specified column if it has one.
*/
public String getColumnHeaderStylename(Object propertyId) {
if (getColumnHeaderMode() == ColumnHeaderMode.HIDDEN) {
return null;
}
return columnHeaderStylenames.get(propertyId);
}
}