/*! * 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 ); } }