/*
* Copyright 2009-2011 the original author or authors.
*
* 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 org.jdal.vaadin.ui.table;
import java.beans.PropertyEditor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.jdal.beans.PropertyUtils;
import org.jdal.util.BeanUtils;
import org.jdal.vaadin.data.ListBeanContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import com.vaadin.data.Container;
import com.vaadin.data.Property;
import com.vaadin.data.util.AbstractBeanContainer;
import com.vaadin.ui.Component;
import com.vaadin.ui.Table;
/**
* Add methods to vaadin Table for friendly configuration on spring bean defintion file.
* Use <code>info.joseluismartin.ui.table.Column</code> as inner bean for configure columns:
* <code>
* <property name="columns">
* <list>
* <bean class="info.joseluismartin.ui.table.Column"/>
* <property name="name" value="a_bean_property_name"/>
* <property name="displayName" value="String_to_show_in_table_header"/>
* <property name="width" value="column_width"/>
* <property name="cellEditor" value="cellEditorClass"/>
* <property name="cellComponent" value="cellComponent"/>
* ...
* </bean>
* <bean class="info.joseluismartin.vaadin.ui.table.Column">
* ...
* </bean>
* </list>
* <property name="service" value="a_persistent_service"/>
* <property>
* </code>
*
* @author Jose Luis Martin - (jlm@joseluismartin.info)
*/
@SuppressWarnings("serial")
@Configurable
public class ConfigurableTable extends Table {
private boolean usingChecks = false;
private List<Column> columns;
private TableSorter sorter;
private Map<String, Column> columnMap = new HashMap<String, Column>();
@Autowired
private transient MessageSource messageSource;
private List<String> properties = new ArrayList<String>();
/**
* Configure Vaadin table with column definitions.
* Method useful for use from context bean definition file.
* @param columns
*/
public void setColumns(List<Column> columns) {
this.columns = columns;
columnMap.clear();
for (Column c : columns) {
columnMap.put(c.getName(), c);
}
configure();
}
@Override
public void setContainerDataSource(Container newDataSource,
Collection<?> visibleIds) {
if (visibleIds != null && visibleIds.size() > 0) {
for (String id : properties) {
if (PropertyUtils.isNested(id)) {
addNestedPropertyIfPossible(newDataSource, id);
}
if (!visibleIds.contains(id) && !PropertyUtils.isNested(id)) {
throw new IllegalStateException("Unknown property [" + id + "]");
}
}
}
super.setContainerDataSource(newDataSource, properties);
}
@Override
public void setVisibleColumns(Object... visibleColumns) {
this.properties = new ArrayList<String>();
for (Object id : visibleColumns)
this.properties.add(id.toString());
if (this.columns == null)
createDefaultColumns();
super.setVisibleColumns(visibleColumns);
}
/**
* Create default columns from properties
*/
private void createDefaultColumns() {
this.columns = new ArrayList<Column>();
for (String propertyName : this.properties) {
Column c = new Column();
c.setName(propertyName);
this.columns.add(c);
this.columnMap.put(propertyName, c);
}
}
/**
* Configure table
*/
protected void configure() {
if (columns == null)
return;
int size = columns.size();
String[] visibleColumns = new String[size];
String[] displayNames = new String[size];
Align[] alignments = new Align[size];
for (int i = 0; i < size; i++) {
visibleColumns[i] = columns.get(i).getName();
displayNames[i] = getMessage(columns.get(i).getDisplayName());
alignments[i] = columns.get(i).getAlign();
}
// Vaadin Table throw an exception when seting this properties
// with an empty Container datasource.
this.setVisibleColumns((Object[]) visibleColumns);
this.setColumnHeaders(displayNames);
this.setColumnAlignments(alignments);
for (Column c : columns) {
if ( c.getWidth() != -1)
setColumnWidth(c.getName(), c.getWidth());
if (c.getExpandRatio() != null)
setColumnExpandRatio(c.getName(), c.getExpandRatio());
if (c.getColumnGenerator() != null)
addGeneratedColumn(c.getName(), c.getColumnGenerator());
}
}
/**
* Try to resolve string as i18n code, return string if none.
* @param name
* @return translated code or the string
*/
private String getMessage(String name) {
if (messageSource == null)
return name;
try {
return messageSource.getMessage(name, null, Locale.getDefault());
}
catch (NoSuchMessageException nsme) {
return name;
}
}
/**
* Add nested property to datasource if is possilbe.
* @param name of nested property
*/
private void addNestedPropertyIfPossible(Container cds, String name) {
if (cds instanceof AbstractBeanContainer<?,?>) {
((AbstractBeanContainer<?,?>) cds).addNestedContainerProperty(name);
}
else if (cds instanceof ListBeanContainer)
((ListBeanContainer) cds).addProperty(name);
}
/**
* Gets usingChecks property, if true, table show checkboxes for row selection
* @return the usingChecks
*/
public boolean isUsingChecks() {
return usingChecks;
}
/**
* Sets usingChecks property, if true, table show checkboxes for row selection
* @param usingChecks the usingChecks to set
*/
public void setUsingChecks(boolean usingChecks) {
this.usingChecks = usingChecks;
}
/**
* Sort on container or on sorter
*/
@Override
public void sort(Object[] propertyId, boolean[] ascending) {
if (sorter != null)
sorter.sort(propertyId, ascending);
else {
super.sort(propertyId, ascending);
}
}
/**
* Override to handle server side sorting
* {@inheritDoc}
*/
@Override
public Collection<?> getSortableContainerPropertyIds() {
List<Object> sortableIds = new LinkedList<Object>();
for (Column c :columns ) {
if (c.isSortable())
sortableIds.add(c.getName());
}
return sortableIds;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("rawtypes")
@Override
protected Object getPropertyValue(Object rowId, Object colId, Property property) {
Column column = columnMap.get(colId);
if (column != null) {
if (isEditable()) {
Class<?extends Component> editorClass = column.getCellEditor();
if (editorClass == null)
return super.getPropertyValue(rowId, colId, property);
else {
return getComponentForProperty(property, editorClass);
}
}
// Test cell component
Class<?extends Component> cellComponentClass = column.getCellComponent();
if (cellComponentClass != null) {
return getComponentForProperty(property, cellComponentClass);
}
// Last try, test property editor
PropertyEditor pe = column.getPropertyEditor();
if (pe != null) {
pe.setValue(property.getValue());
return pe.getAsText();
}
}
// Default behavior
return super.getPropertyValue(rowId, colId, property);
}
/**
* Instantiate editor Class and set property as DataSource if
* the editor is Property.Viewer (it should be).
* @param property property to set
* @param editorClass class to instantiate
* @return editor instance
*/
private Component getComponentForProperty(Property<?> property, Class<? extends Component> editorClass) {
Component editor = BeanUtils.instantiate(editorClass);
if (editor instanceof Property.Viewer) {
((Property.Viewer) editor).setPropertyDataSource(property);
}
return editor;
}
/**
* Override to allow setting visible columns with a empty data source
*/
@Override
public Collection<?> getContainerPropertyIds() {
return properties;
}
public Column getColumn(String name) {
return columnMap.get(name);
}
public List<Column> getColumns() {
return this.columns;
}
public TableSorter getSorter() {
return sorter;
}
public void setSorter(TableSorter sorter) {
this.sorter = sorter;
}
/**
* Override to set immediate to true when the table is selectable.
*/
public void setSelectable(boolean selectable) {
super.setSelectable(selectable);
if (selectable)
setImmediate(true);
}
}