/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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 Lesser General Public License for more details.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.gwt.widgets.client.table;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.pentaho.gwt.widgets.client.i18n.WidgetsLocalizedMessages;
import org.pentaho.gwt.widgets.client.i18n.WidgetsLocalizedMessagesSingleton;
import org.pentaho.gwt.widgets.client.table.ColumnComparators.BaseColumnComparator;
import org.pentaho.gwt.widgets.client.table.ColumnComparators.ColumnComparatorTypes;
import org.pentaho.gwt.widgets.client.utils.ElementUtils;
import com.google.gwt.core.client.GWT;
import com.google.gwt.gen2.table.client.AbstractScrollTable;
import com.google.gwt.gen2.table.client.FixedWidthFlexTable;
import com.google.gwt.gen2.table.client.FixedWidthGrid;
import com.google.gwt.gen2.table.client.ScrollTable;
import com.google.gwt.gen2.table.client.SelectionGrid;
import com.google.gwt.gen2.table.client.SortableGrid;
import com.google.gwt.gen2.table.client.TableModelHelper;
import com.google.gwt.gen2.table.event.client.RowSelectionHandler;
import com.google.gwt.gen2.table.override.client.FlexTable;
import com.google.gwt.gen2.table.override.client.HTMLTable;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.SourcesTableEvents;
import com.google.gwt.user.client.ui.TableListener;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* <p>
* Core reusable, table widget for displaying tabular data that is based on a composite of a ScrollTable and a
* FixedWidthGrid.
*
* <p>
* Usage Notes:
*
* <p>
* <ul>
* <li>You must call the populateTable or populateTableWithSimpleMessage method AFTER having instanciated it.
* <li>It's always better to define the width and height right after instanciation.
* <li>Never set the resize policy to FILL_WIDTH or FireFox will experience an ever growing table.
* </ul>
* </p>
*/
@SuppressWarnings( "deprecation" )
public class BaseTable extends Composite {
private static final WidgetsLocalizedMessages MSGS = WidgetsLocalizedMessagesSingleton.getInstance().getMessages();
public static final BaseColumnComparator DEFAULT_COLUMN_COMPARATOR = BaseColumnComparator
.getInstance( ColumnComparatorTypes.STRING_NOCASE );
public static final String TABLE_NO_FILL = "table-no-fill";
protected Panel parentPanel = new VerticalPanel();
protected ScrollTable scrollTable;
private FixedWidthFlexTable tableHeader;
protected FixedWidthGrid dataGrid;
protected String scrollTableWidth;
protected String scrollTableHeight;
private int[] columnWidths;
private int numberOfColumns;
private SelectionGrid.SelectionPolicy selectionPolicy;
private BaseColumnComparator[] columnComparators;
private Collection objects;
private Map<Element, Object> objectElementMap;
private BaseTableColumnSorter baseTableColumnSorter;
private final TableListener internalDoubleClickListener = new TableListener() {
public void onCellClicked( SourcesTableEvents sender, int row, int cell ) {
for ( TableListener listener : doubleClickListeners ) {
listener.onCellClicked( sender, row, cell );
}
}
};
private List<TableListener> doubleClickListeners = new ArrayList<TableListener>();
private final TableListener internalTableListener = new TableListener() {
public void onCellClicked( SourcesTableEvents sender, int row, int cell ) {
for ( TableListener listener : tableListeners ) {
listener.onCellClicked( sender, row, cell );
}
}
};
private List<TableListener> tableListeners = new ArrayList<TableListener>();
/**
* Simple constructor.
*/
public BaseTable( String[] tableHeaderNames, int[] columnWidths ) {
this( tableHeaderNames, columnWidths, null );
}
/**
* Simple constructor.
*/
public BaseTable( String[] tableHeaderNames, int[] columnWidths, BaseColumnComparator[] columnComparators ) {
this( tableHeaderNames, columnWidths, columnComparators, null );
}
public BaseTable( String[] tableHeaderNames, int[] columnWidths, BaseColumnComparator[] columnComparators,
SelectionGrid.SelectionPolicy selectionPolicy, TableColumnSortListener sortListener ) {
this( tableHeaderNames, columnWidths, columnComparators, selectionPolicy );
baseTableColumnSorter.setTableColumnSortListener( sortListener );
}
/**
* Main constructor.
*
* Note: For column width values, use -1 to not specify a column width. Note: For column comparators individually, a
* null value will disable sorting for that column. If you set the columnComparators array to null, all columns will
* be populated with the default column comparator.
*/
public BaseTable( String[] tableHeaderNames, int[] columnWidths, BaseColumnComparator[] columnComparators,
SelectionGrid.SelectionPolicy selectionPolicy ) {
if ( tableHeaderNames != null ) {
this.columnWidths = columnWidths;
this.numberOfColumns = tableHeaderNames.length;
this.parentPanel.setWidth( "100%" );
if ( selectionPolicy != null ) {
this.selectionPolicy = selectionPolicy;
}
// Set column comparators to default if columnComparators is null
if ( columnComparators == null ) {
this.columnComparators = new BaseColumnComparator[tableHeaderNames.length];
for ( int i = 0; i < this.columnComparators.length; i++ ) {
this.columnComparators[i] = DEFAULT_COLUMN_COMPARATOR;
}
} else {
this.columnComparators = columnComparators;
}
createTable( tableHeaderNames, columnWidths, new Object[0][0], AbstractScrollTable.ResizePolicy.FIXED_WIDTH,
selectionPolicy );
this.parentPanel.add( scrollTable );
scrollTable.fillWidth();
initWidget( parentPanel );
} else {
System.err.println( MSGS.tableHeaderInputError() );
}
}
/**
* Creates a table with the given headers, column widths, and row/column values using the default resize policy of
* RESIZE_POLICY_FIXED_WIDTH.
*/
@SuppressWarnings( "unused" )
private void createTable( String[] tableHeaderNames, int[] columnWidths, Object[][] rowAndColumnValues ) {
createTable( tableHeaderNames, columnWidths, rowAndColumnValues, ScrollTable.ResizePolicy.FIXED_WIDTH,
selectionPolicy );
}
/**
* Creates a table with the given headers, column widths, row/column values, and resize policy.
*/
protected void createTable( String[] tableHeaderNames, int[] columnWidths, Object[][] rowAndColumnValues,
AbstractScrollTable.ResizePolicy resizePolicy, SelectionGrid.SelectionPolicy selectionPolicy ) {
createTableHeader( tableHeaderNames, columnWidths );
createDataGrid( selectionPolicy, tableHeaderNames.length );
createScrollTable( resizePolicy );
populateDataGrid( columnWidths, rowAndColumnValues );
}
/**
* Creates and initializes the header for the table.
*/
private void createTableHeader( String[] tableHeaderNames, final int[] columnWidths ) {
tableHeader = new FixedWidthFlexTable();
// Set header values and disable text selection
final FlexTable.FlexCellFormatter cellFormatter = tableHeader.getFlexCellFormatter();
for ( int i = 0; i < tableHeaderNames.length; i++ ) {
tableHeader.setHTML( 0, i, tableHeaderNames[i] );
cellFormatter.setHorizontalAlignment( 0, i, HasHorizontalAlignment.ALIGN_LEFT );
cellFormatter.setWordWrap( 0, i, false );
cellFormatter.setStylePrimaryName( 0, i, "overflowHide" );
}
if ( this.selectionPolicy == null ) {
tableHeader.setStylePrimaryName( "disabled" ); //$NON-NLS-1$
}
}
/**
* Creates and initializes the data grid.
*/
private void createDataGrid( SelectionGrid.SelectionPolicy selectionPolicy, int numOfColumns ) {
dataGrid = new FixedWidthGrid( 0, numOfColumns ) {
@Override
public void onBrowserEvent( Event event ) {
Element td = this.getEventTargetCell( event );
if ( td == null ) {
return;
}
Element tr = DOM.getParent( td );
Element body = DOM.getParent( tr );
int row = DOM.getChildIndex( body, tr ) - 1;
int column = DOM.getChildIndex( tr, td );
switch ( DOM.eventGetType( event ) ) {
case Event.ONDBLCLICK: {
internalDoubleClickListener.onCellClicked( dataGrid, row, column );
}
default: {
break;
}
}
super.onBrowserEvent( event );
}
};
// disable text highlighting on dataGrid
ElementUtils.killAllTextSelection( dataGrid.getElement() );
// Set style
if ( selectionPolicy == null ) {
dataGrid.setSelectionPolicy( SelectionGrid.SelectionPolicy.ONE_ROW );
} else {
dataGrid.setSelectionPolicy( selectionPolicy );
}
// Add table listeners
dataGrid.addTableListener( internalTableListener );
// Add table selection listeners
dataGrid.sinkEvents( Event.ONDBLCLICK );
baseTableColumnSorter = new BaseTableColumnSorter();
dataGrid.setColumnSorter( baseTableColumnSorter );
if ( this.selectionPolicy == null ) {
dataGrid.setStylePrimaryName( "disabled" ); //$NON-NLS-1$
}
}
/**
* Creates and initializes the scroll table.
*/
private void createScrollTable( AbstractScrollTable.ResizePolicy resizePolicy ) {
scrollTable = new ScrollTable( dataGrid, tableHeader, (BaseTableImages) GWT.create( BaseTableImages.class ) ) {
protected void resizeTablesVerticallyNow() {
// Give the data wrapper all remaining height
int totalHeight = DOM.getElementPropertyInt( getElement(), "clientHeight" );
if ( totalHeight == 0 ) {
return;
}
super.resizeTablesVerticallyNow();
}
};
scrollTable.setResizePolicy( AbstractScrollTable.ResizePolicy.FLOW );
scrollTable.setCellPadding( 0 );
scrollTable.setCellSpacing( 0 );
scrollTable.setScrollPolicy( ScrollTable.ScrollPolicy.BOTH );
// Set column comparators
if ( columnComparators != null ) {
for ( int i = 0; i < columnComparators.length; i++ ) {
if ( columnComparators[i] != null ) {
scrollTable.setColumnSortable( i, true );
} else {
scrollTable.setColumnSortable( i, false );
}
}
}
if ( this.scrollTableWidth != null ) {
this.setWidth( scrollTableWidth );
}
if ( this.scrollTableHeight != null ) {
this.setHeight( scrollTableHeight );
}
if ( columnWidths != null && columnWidths.length > 0 ) {
for ( int i = 0; i < columnWidths.length; i++ ) {
if ( columnWidths[i] > 0 ) {
scrollTable.setColumnWidth( i, columnWidths[i] );
}
}
}
if ( scrollTableWidth != null ) {
scrollTable.setWidth( scrollTableWidth );
}
scrollTable.fillWidth();
}
/**
* Populates the data grid with data then sets the column widths.
*/
protected void populateDataGrid( int[] columnWidths, Object[][] rowAndColumnValues, Collection objects ) {
this.objects = objects;
populateDataGrid( columnWidths, rowAndColumnValues );
}
/**
* Populates the data grid with data then sets the column widths.
*/
protected void populateDataGrid( int[] columnWidths, Object[][] rowAndColumnValues ) {
while ( dataGrid.getRowCount() > 0 ) {
dataGrid.removeRow( 0 );
}
// Set table values
//
dataGrid.resizeRows( rowAndColumnValues.length );
for ( int i = 0; i < rowAndColumnValues.length; i++ ) {
// For even rows, add background highlighting
if ( i % 2 != 0 ) {
dataGrid.getRowFormatter().setStyleName( i, "cellTableOddRow" );
}
for ( int j = 0; j < rowAndColumnValues[i].length; j++ ) {
Object value = rowAndColumnValues[i][j];
if ( value != null ) {
if ( value instanceof String ) {
dataGrid.setHTML( i, j, value.toString() );
} else if ( value instanceof Widget ) {
dataGrid.setWidget( i, j, (Widget) value );
} else {
System.err.print( MSGS.invalidDataGridTypeSet() );
Window.alert( MSGS.invalidDataGridTypeSet() );
return;
}
}
}
}
// Set column widths
if ( columnWidths != null ) {
for ( int i = 0; i < columnWidths.length; i++ ) {
if ( columnWidths[i] >= 0 ) {
dataGrid.setColumnWidth( i, columnWidths[i] );
scrollTable.setColumnWidth( i, columnWidths[i] );
}
}
}
// Set cell styles/tooltip for data grid cells
final HTMLTable.CellFormatter cellFormatter = dataGrid.getCellFormatter();
objectElementMap = new HashMap<Element, Object>();
Object[] objectArray = null;
if ( objects != null ) {
objectArray = objects.toArray();
}
for ( int i = 0; i < rowAndColumnValues.length; i++ ) {
Object object = null;
if ( objectArray != null ) {
object = objectArray[i];
}
for ( int j = 0; j < rowAndColumnValues[i].length; j++ ) {
Object value = rowAndColumnValues[i][j];
Element element = null;
try {
element = cellFormatter.getElement( i, j );
} catch ( Exception e ) {
//ignore
}
if ( element != null ) {
if ( value != null && value instanceof String && !value.equals( " " ) ) { //$NON-NLS-1$
element.setTitle( value.toString() );
}
}
if ( object != null ) {
objectElementMap.put( element, object );
}
}
}
baseTableColumnSorter.setObjectMap( objectElementMap );
scrollTable.redraw();
}
/**
* Makes this table fill all available width.
*/
public void fillWidth() {
scrollTable.fillWidth();
}
public void noFill() {
scrollTable.addStyleName( TABLE_NO_FILL );
}
/**
* Displays a message to the user in the table instead of data. Deprecated in favor of
* {@link BaseTable#showMessage(String)}
*/
@Deprecated
public void populateTableWithSimpleMessage( final String message ) {
showMessage( message );
}
/**
* Makes this table display a message instead of the column data.
*
* @param message
* The message to display.
*/
public void showMessage( String message ) {
parentPanel.clear();
String[] simpleMessageHeaderValues = new String[] { " " }; //$NON-NLS-1$ //$NON-NLS-2$
String[][] simpleMessageRowAndColumnValues = new String[][] { { message, " " } }; //$NON-NLS-1$
createTable( simpleMessageHeaderValues, null, simpleMessageRowAndColumnValues,
AbstractScrollTable.ResizePolicy.FIXED_WIDTH, selectionPolicy );
parentPanel.add( scrollTable );
fillWidth();
}
/**
* Creates the table using the default values specified in the constructor but with new data for the rows.
*/
public void populateTable( Object[][] rowAndColumnValues, Collection objects ) {
populateDataGrid( columnWidths, rowAndColumnValues, objects );
}
/**
* Creates the table using the default values specified in the constructor but with new data for the rows.
*/
public void populateTable( Object[][] rowAndColumnValues ) {
populateDataGrid( columnWidths, rowAndColumnValues );
}
/**
* Adds an additional table listener in addition to the default listener.
*/
public void addTableListener( TableListener listener ) {
tableListeners.add( listener );
}
/**
* Adds an additional table selection listener in addition to the default listener.
*/
public void addRowSelectionHandler( RowSelectionHandler handler ) {
dataGrid.addRowSelectionHandler( handler );
}
/**
* Adds a listener to fire when a user double-clicks on a table row.
*/
public void addDoubleClickListener( TableListener listener ) {
doubleClickListeners.add( listener );
}
/**
* Gets the text within the specified cell.
*/
public String getText( int row, int column ) {
return dataGrid.getText( row, column );
}
/**
* Returns the number of columns in the data table.
*/
public int getNumberOfColumns() {
return numberOfColumns;
}
/**
* Select a row in the data table.
*/
public void selectRow( int row ) {
dataGrid.selectRow( row, false );
}
/**
* Returns the set of selected row indexes.
*/
public Set<Integer> getSelectedRows() {
return dataGrid.getSelectedRows();
}
/**
* Deselect all selected rows in the data table.
*/
public void deselectRows() {
dataGrid.deselectAllRows();
}
/**
* Default column sorter for this class.
*/
final class BaseTableColumnSorter extends SortableGrid.ColumnSorter {
private Map<Element, Object> objMap;
private TableColumnSortListener sortListener;
public void setTableColumnSortListener( TableColumnSortListener sortListener ) {
this.sortListener = sortListener;
}
public void setObjectMap( Map<Element, Object> objMap ) {
this.objMap = objMap;
}
private List sortObjectCollection( List<Element> elements ) {
List objects = new ArrayList();
for ( Element element : elements ) {
if ( objMap.containsKey( element ) ) {
objects.add( objMap.get( element ) );
}
}
return objects;
}
public void onSortColumn( SortableGrid grid, TableModelHelper.ColumnSortList sortList,
SortableGrid.ColumnSorterCallback callback ) {
// Get the primary column and sort order
int column = sortList.getPrimaryColumn();
boolean ascending = sortList.isPrimaryAscending();
// Apply the default quicksort algorithm
// Element[] tdElems = new Element[grid.getRowCount()];
List<Element> tdElems = new ArrayList<Element>();
for ( int i = 0; i < grid.getRowCount(); i++ ) {
// first, clear out existing row styling for oddRow.
grid.getRowFormatter().setStyleName( i, "" );
tdElems.add( grid.getCellFormatter().getElement( i, column ) );
}
if ( grid.getColumnCount() > column ) {
Collections.sort( tdElems, columnComparators != null && columnComparators[column] != null
? columnComparators[column] : DEFAULT_COLUMN_COMPARATOR );
}
// Convert tdElems to trElems, reversing if needed
Element[] trElems = new Element[tdElems.size()];
List<Element> sortedTdElement = new ArrayList<Element>();
if ( ascending ) {
for ( int i = 0; i < tdElems.size(); i++ ) {
trElems[i] = DOM.getParent( tdElems.get( i ) );
sortedTdElement.add( tdElems.get( i ) );
}
} else {
int maxElem = tdElems.size() - 1;
for ( int i = 0; i <= maxElem; i++ ) {
trElems[i] = DOM.getParent( tdElems.get( maxElem - i ) );
sortedTdElement.add( tdElems.get( maxElem - i ) );
}
}
callback.onSortingComplete( trElems );
sortListener.onSortingComplete( sortObjectCollection( sortedTdElement ) );
// now that the sorting is done, set the alternating row color
for ( int i = 0; i < grid.getRowCount(); i++ ) {
if ( i % 2 != 0 ) {
grid.getRowFormatter().setStyleName( i, "cellTableOddRow" );
}
}
}
};
/*
* (non-Javadoc)
*
* @see com.google.gwt.user.client.ui.UIObject#setWidth(java.lang.String)
*/
@Override
public void setWidth( final String width ) {
super.setWidth( width );
this.scrollTableWidth = width;
scrollTable.getHeaderTable().setWidth( width );
scrollTable.getDataTable().setWidth( width );
}
/*
* (non-Javadoc)
*
* @see com.google.gwt.user.client.ui.UIObject#setHeight(java.lang.String)
*/
@Override
public void setHeight( final String height ) {
super.setHeight( height );
this.scrollTableHeight = height;
scrollTable.setHeight( height );
}
/**
* Sets this widget to the desired height. Deprecated signature. Use {@link BaseTable#setHeight(String)}
*/
@Deprecated
public void setTableHeight( final String height ) {
setHeight( height );
}
/**
* Sets this widget to the desired height. Deprecated signature. Use {@link BaseTable#setWidth(String)}
*/
@Deprecated
public void setTableWidth( final String width ) {
setWidth( width );
}
public void replaceRow( int row, Object[] data ) {
for ( int j = 0; j < data.length; j++ ) {
Object value = data[j];
if ( value != null ) {
if ( value instanceof String ) {
dataGrid.setHTML( row, j, value.toString() );
} else if ( value instanceof Widget ) {
dataGrid.setWidget( row, j, (Widget) value );
} else {
System.err.print( MSGS.invalidDataGridTypeSet() );
Window.alert( MSGS.invalidDataGridTypeSet() );
return;
}
}
}
}
public void suppressHorizontalScrolling() {
dataGrid.addStyleName( "hide-h-scrolling" );
}
public boolean isSortingEnabled() {
return true;
}
public void setSortingEnabled( boolean enabled ) {
}
public void setColumnSortable( int column, boolean sortable ) {
scrollTable.setColumnSortable( column, sortable );
}
public boolean isColumnSortable( int column ) {
return scrollTable.isColumnSortable( column );
}
public void sortColumn( int column, boolean ascending ) {
dataGrid.sortColumn( column, ascending );
}
public void sortColumn( int column ) {
dataGrid.sortColumn( column );
}
}