(edit - 5/28) I've discovered a couple of issues already with this component, so don't use this component except for reference purposes. I promise to post a more complete version soon.
Here's a Table component I've been working on. It is not at all finished, but does have a pretty solid minimal level of functionality.
The table was designed for the following:
- you have a collection of data objects (ex: List<MyDataObject> )
- you want to display one object per row
- allow the user to select a table row and have an action be fired on the server side delivering the Object that was selected, so if user selects row #0, we should get List.get( 0 )
- table sorts by column on the client side
- allow the the server side to determine the table's sort order
- support Firefox 3 and IE8
The code posted here does the above. It also handles larger data sets well (2000 rows, 30 columns).
Though, this table component has many problems:
- Too much of the formatting information is set and or is only available in the javascript code.
- does not use stylesheets
- major display bug in Opera (does not load initially for some reason, requires the screen to be resized before it renders)
- the table does not support client side paging
- the table does not sort Date columns correctly (sorts them alphabetically which is not correct for Jan 13, 2009 and Dec 14, 1999)
- the table header row really should stay fixed, so it is always visible.
- table is limited by Integer.MAX_VALUE
- Does not support paging, but paging could be done on the server side. (The table performs really well when it is small, I see myself adding client side paging support in the near future)
- in Firefox, the left hand side table border does not show up.
- You can't specify column widths (<colgroup> tag is ignored for some reason)
- you can not specify column text alignment, or specify text fonts on a per column nor row basis.
- no support for editing table data
Here's some sample code, I've attached a screenshot, source code for the table is after the sample code:
--------------------------------
SAMPLE CODE
--------------------------------
public DataTable<SampleAccount> getTable() {
// create some data for the table
List<SampleAccount> dataForTable = new ArrayList<SampleAccount>();
for( int i = 0; i < 10; i ++ ) {
String acctName = "account" + i;
double amountOwed = genRandNumber();
double amountPayed = genRandNumber();
boolean active = ((i %3) == 0);
dataForTable.add( new SampleAccount( acctName, active, amountOwed, amountPayed ) );
}
// define the columns for the table, specifying the data type, either: text, boolean, decimal, integer, or date (but date does not sort
// correctly, sorts as alpha-numeric/text)
List<DataColumn<SampleAccount>> columns = new ArrayList<DataColumn<SampleAccount>>();
columns.add( new AbstractTextColumn<SampleAccount>( "Account Name") {
@Override public String getDisplayValue(SampleAccount data) {
return data.getAccountName();
}
});
columns.add( new AbstractBooleanColumn<SampleAccount>( "Original Balance") {
@Override public String getDisplayValue(SampleAccount data) {
return (new DecimalFormat("#.##")).format( data.getAmountOwed() );
}
});
columns.add( new AbstractDecimalColumn<SampleAccount>( "Amount Payed") {
@Override public String getDisplayValue(SampleAccount data) {
return (new DecimalFormat("#.##")).format( data.getAmountPayed() );
}
});
columns.add( new AbstractDecimalColumn<SampleAccount>("Outstanding Balance") {
@Override public String getDisplayValue(SampleAccount data) {
return (new DecimalFormat("#.##")).format( data.getBalance() );
}
});
columns.add( new AbstractBooleanColumn<SampleAccount>( "At risk account") {
@Override
public String getDisplayValue(SampleAccount data) {
return String.valueOf( data.getBalance() > 10.00 );
}
});
DataTable<SampleAccount> table = new DataTable<SampleAccount>( dataForTable , columns );
return table;
}
private class SampleAccount {
private String accountName = "hello";
private boolean active = true;
private double amountOwed = 252.00;
private double amountPayed = 242.00;
public SampleAccount( String name, boolean active, double amountOwed, double amountPayed ) {
this.accountName = name;
this.active = active;
this.amountOwed = amountOwed;
this.amountPayed = amountPayed;
}
public boolean isAccountActive() {
return active;
}
public double getAmountPayed() {
return amountPayed;
}
public double getAmountOwed() {
return amountOwed;
}
public String getAccountName() {
return accountName;
}
public double getBalance() {
return amountOwed - amountPayed;
}
}
private double genRandNumber() {
return Math.random() * 1000;
}
I've the following folder structure (when deployed to tomcat),
classes = tomcat-root/webapps/myapp/WEB-INF/classes
classes/Application.DataTable.js
classes/Sync.DataTable.js
classes/META-INF/nextapp/echo/SynchronizePeerBindings.properties
classes/echo/uicomponent/AbstractBooleanColumn.class
classes/echo/uicomponent/AbstractColumn.class
classes/echo/uicomponent/AbstractDateColumn.class
classes/echo/uicomponent/AbstractIntegerColumn.class
classes/echo/uicomponent/DataColumn.class
classes/echo/uicomponent/DataColumntype.class
classes/echo/uicomponent/DataTable.class
classes/echo/uicomponent/DataTablePeer.class
--------------------------------
AbstractBooleanColumn.java
package echo.uicomponent.table;
public abstract class AbstractBooleanColumn<T> extends AbstractColumn<T> {
public AbstractBooleanColumn( String columnHeader ) {
super( columnHeader );
}
public DataColumnType getColumnType() {
return DataColumnType.TEXT;
}
public String format( String value ) {
if( value.equals( String.valueOf( Boolean.TRUE ))) {
return "Yes";
} else if( value.equals( String.valueOf( Boolean.FALSE ))) {
return "No";
} else {
return value;
}
}
}
-------------------------------------------
AbstractColumn.java
package echo.uicomponent.table;
public abstract class AbstractColumn<T> implements DataColumn<T> {
private String columnHeader;
public AbstractColumn( String columnHeader ) {
if( columnHeader == null || columnHeader.trim().equals( "" ) ) {
throw new IllegalArgumentException("Illegal constructor argument to AbstractColumn, parameter: columnHeader, must be non-null and non-empty.");
}
this.columnHeader = columnHeader;
}
@Override
public String getColumnHeaderLabel() {
return columnHeader;
}
public String format( String value ) {
return value;
}
}
----------------------------------------------
Abstract DateColumn.java
package echo.uicomponent.table;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public abstract class AbstractDateColumn<T> extends AbstractColumn<T> {
public AbstractDateColumn( String columnHeader ) {
super( columnHeader );
}
public DataColumnType getColumnType() {
return DataColumnType.TEXT;
// return HtmlColumnType.DATE;
}
/*
public String format( String value ) {
DateFormat format = new SimpleDateFormat( "MMM d, yyyy");
// DateFormat format = DateFormat.SHORT;
format.setLenient( true );
try {
Date d = format.parse( value );
return format.format( d );
} catch( ParseException e ) {
throw new RuntimeException( e );
}
this.getDisplayValue( )
}
*/
public abstract Date getDateValue( T value );
public String getDisplayValue( T value ) {
DateFormat format = new SimpleDateFormat( "MMM d, yyyy");
Date d = getDateValue( value );
if( d == null ) {
return "-";
} else {
return format.format( getDateValue( value ) );
}
}
}
------------------------------
AbstractDecimalcolumn.java
package echo.uicomponent.table;
public abstract class AbstractDecimalColumn<T> extends AbstractColumn<T> {
public AbstractDecimalColumn( String columnHeader ) {
super( columnHeader );
}
public DataColumnType getColumnType() {
return DataColumnType.DECIMAL;
}
}
-------------------------------
AbstractIntegerColumn.java
package echo.uicomponent.table;
public abstract class AbstractIntegerColumn<T> extends AbstractColumn<T> {
public AbstractIntegerColumn( String columnHeader ) {
super( columnHeader );
}
public DataColumnType getColumnType() {
return DataColumnType.INTEGER;
}
}
--------------------------------
AbstractTextColumn.java
package echo.uicomponent.table;
public abstract class AbstractTextColumn<T> extends AbstractColumn<T> {
public AbstractTextColumn( String columnHeader ) {
super( columnHeader );
}
public DataColumnType getColumnType() {
return DataColumnType.TEXT;
}
}
--------------------------------------
DataColumn.java
package echo.uicomponent.table;
/**
* Class that defines the data that will be used to populate a single column for a single row.
* Use a list of these to define how a list of data objects will be rendered into a table.
* The interface methods are defined for a generic single row, which is then replicated for
* each row in the table.
*
* @author DVanatta
* @param <T> The data type to be rendered. This should match the data type of the HtmlTable.
* @see HtmlTable
* @see DataColumnType
*/
public interface DataColumn <T> {
/**
* @return The text value that will be displayed at the top of the column as a header.
*/
abstract String getColumnHeaderLabel( ); // the table header row label
/**
* @return The sort key that classifies the column data type, for use with sorting.
*/
abstract DataColumnType getColumnType( );
/**
* A method to extract the cell value for this column for a particular data object.
*
* @param data A reference to the data object that is displayed on the current table row.
* @return A String value that will be displayed in the cell
* corresponding to this column, corresponding to the row that is currently
* displaying the parameter data object.
*/
abstract String getDisplayValue( T data ); // the text to disply in the table cell
abstract String format( String value );
}
------------------------------
DataColumnType.java
package echo.uicomponent.table;
/**
* Java style enumeration for column type labels to be used by HtmlTable's javascript sort function.
* @author DVanatta
*/
public final class DataColumnType {
/**
* Internal id string, used to differentiate between instances.
*/
private String id;
/**
* Text type, use this type for columns that should be alpha numerical sorted. Using this
* type, the list "10", "11", "100" will be sorted as "10, 100, 11", and "a" "c" "b", would
* be sorted to "a,b,c"
*/
public static final DataColumnType TEXT = new DataColumnType("TEXT");
/**
* Use this type for numerical values that do not contain fractional parts.
*/
public static final DataColumnType INTEGER = new DataColumnType("INTEGER");
/**
* Use this type for numerical values that will not contain a decimal or fractional part.
*/
public static final DataColumnType DECIMAL = new DataColumnType("DECIMAL");
// public static final HtmlColumnType DATE = new HtmlColumnType("DATE");
/**
* Private constructor to disallow inheritance and external construction of this class.
* @param id
*/
private DataColumnType( String id ) {
this.id = id;
}
/**
* Returns the data type label for this instance.
*/
public String toString() {
return id;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DataColumnType other = (DataColumnType) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
-------------------------------
DataTable.java
package echo.uicomponent.table;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;
import nextapp.echo.app.Border;
import nextapp.echo.app.Color;
import nextapp.echo.app.Component;
import nextapp.echo.app.event.ActionEvent;
import nextapp.echo.app.event.ActionListener;
public class DataTable<T> extends Component {
private static final long serialVersionUID = 2015476399313418076L;
public static final String SELECTED_ROW_CLIENT_PROPERTY = "selectedRowProperty";
public static final String TABLE_SORT_ORDER_CLIENT_PROPERTY = "tableSortOrder";
public static final String ARRAY_ELEMENT_DELIM_TOKEN_CLIENT_PROPERTY = "#";
public static final String ROW_SELECTED_ACTION = "rowSelectedAction";
/**
* The split token between rows.
*/
private static final String ROW_SPLIT_TOKEN = "<!R!>";
/**
* The split token between cells
*/
private static final String CELL_SPLIT_TOKEN = "<!C!>";
/**
* Property key for a columns sort type. This property should be defined for
* each column, the property name should be appended with the column number
* (starting with zero)
*
* @see addColumnSortType
*/
private static final String COLUMN_SORT_TYPE_PROPERTY = "COL_SORT_TYPE_";
// then the column number is appended to this arg
private Integer selectedRowNumber = null;
private List<Integer> clientTableSortOrder = null;
public static final int STYLE_BORDER_COLLAPSE = 4231;
public static final int STYLE_BORDER_SEPERATE = 3261;
public static final String ACTION_LISTENERS_CHANGED_PROPERTY = "actionListeners";
public static final String ACTION_COMMAND_CHANGED_PROPERTY = "actionCommand";
private String actionCommand;
private final List<T> data;
@Override
public void processInput(String inputName, Object inputValue) {
super.processInput(inputName, inputValue);
if (inputName.equals(ROW_SELECTED_ACTION)) {
fireAction();
} else if (inputName.equals(SELECTED_ROW_CLIENT_PROPERTY)) {
Integer value = (Integer) inputValue;
this.setSelectedRowNumber(value);
} else if (inputName.equals(TABLE_SORT_ORDER_CLIENT_PROPERTY)) {
String value = (String) inputValue;
this.setClientTableSortOrder(value);
}
}
private void setClientTableSortOrder( String encodedValue ) {
if( encodedValue == null ) {
throw new IllegalStateException("enocded VALUE IS NULL>..");
}
String delimToken =ARRAY_ELEMENT_DELIM_TOKEN_CLIENT_PROPERTY;
String[] split = encodedValue.split( delimToken );
this.clientTableSortOrder = new ArrayList<Integer>();
for( int i = 0; i < split.length; i ++ ) {
clientTableSortOrder.add( Integer.valueOf( split[i] ) );
}
}
public List<Integer> getClientTableSortOrder() {
if( clientTableSortOrder == null ) {
throw new IllegalStateException("clienTableSortOrder has yet to be set by the client (and is still null).");
}
return clientTableSortOrder;
}
public List<T> getSortedDataOrder() {
List<T> sortedData = new ArrayList<T>();
List<Integer> sortKey = getClientTableSortOrder();
for( int i = 0, n = sortKey.size(); i < n ; i ++ ) {
sortedData.add( data.get( sortKey.get( i )));
}
return sortedData;
}
private void setSelectedRowNumber(Integer value) {
this.selectedRowNumber = value;
}
public Integer getSelectedRowNumber() {
return selectedRowNumber;
}
public void setBorderCollapseStyle( int styleCode ) {
if( styleCode != STYLE_BORDER_COLLAPSE && styleCode != STYLE_BORDER_SEPERATE ) {
throw new IllegalArgumentException("Invalid style code, use one of public static final int fields of this class, either STYLE_BORDER_COLLAPSE, or STYLE_BORDER_SEPERATE.");
}
if( styleCode == STYLE_BORDER_COLLAPSE ) {
this.set("borderCollapse", "collapse" );
} else { // styleCode == STYLE_BORDER_SEPERATE ) {
this.set("borderCollapse", "seperate" );
}
}
public List<T> getTableData() {
return data;
}
public DataTable( List<T> data, List<DataColumn<T>> columnData ) {
super();
this.data = data;
int colWidth = columnData.size();
String[][] tableData = new String[data.size() + 1 ][ colWidth ];
// build the first/header row
String[] firstRow = new String[ colWidth ];
for( int i = 0; i < colWidth; i ++ ) {
DataColumn<T> colData = columnData.get( i );
firstRow[i] = colData.getColumnHeaderLabel();
addColumnSortType( i , colData.getColumnType() );
};
tableData[0] = firstRow;
// build the rest of the table
for( int i = 1; i < tableData.length; i ++ ) {
tableData[i] = buildRow( data.get( i -1 ) , columnData );
}
this.set( "tableData", encode( tableData ));
Border b = new Border( 1, Color.BLACK, Border.STYLE_SOLID );
this.set( "border", b );
}
public void addActionListener(ActionListener l) {
getEventListenerList().addListener(ActionListener.class, l);
firePropertyChange(ACTION_LISTENERS_CHANGED_PROPERTY, null, l);
}
public void removeActionListener(ActionListener l) {
getEventListenerList().removeListener(ActionListener.class, l);
firePropertyChange(ACTION_LISTENERS_CHANGED_PROPERTY, l, null);
}
public boolean hasActionListeners() {
return hasEventListenerList()
&& getEventListenerList()
.getListenerCount(ActionListener.class) > 0;
}
public String getActionCommand() {
return actionCommand;
}
public void setActionCommand(String newValue) {
String oldValue = actionCommand;
actionCommand = newValue;
firePropertyChange(ACTION_COMMAND_CHANGED_PROPERTY, oldValue, newValue);
}
private void fireAction() {
EventListener[] actionListeners
= getEventListenerList().getListeners(ActionListener.class);
ActionEvent e = new ActionEvent(this, getActionCommand());
for (int i = 0; i < actionListeners.length; ++i) {
((ActionListener) actionListeners[i]).actionPerformed(e);
}
}
/**
* Constructs a single row in the table.
* @param data The data to be drawn in the row.
* @param colData A list of column descriptors to render the row.
* @return An array of Strings, each element corresponds to a table cell value for the row,
* in order from left to right starting at zero.
*/
private String[] buildRow( T data, List<DataColumn<T>> colData ) {
String[] row = new String[ colData.size() ];
for( int i = 0; i < row.length; i ++ ) {
row[i] = colData.get( i ).getDisplayValue( data );
row[i] = colData.get( i ).format( row[i] );
if( row[i] == null || row[i].trim().equals("null") ) {
row[i] = " - ";
}
}
return row;
}
/**
* Converts the table's data from a two dimensional String array into a flat String delimited by split tokens.
* @param dataGrid A 2 dimensional array representing the entirety of the text content of the table
* (so include the header row as the first array, ie: dataGrid[0] = header row array).
* @return A specially formated String representing the table's data.
*/
private String encode( String[][] dataGrid ) {
StringBuilder sb = new StringBuilder();
for( int i = 0 ; i < dataGrid.length; i ++ ) {
for( int j = 0; j < dataGrid[i].length; j ++ ) {
sb.append( dataGrid[i][j] );
if( j != dataGrid[i].length -1 ) {
sb.append( CELL_SPLIT_TOKEN );
}
}
if( i != dataGrid.length -1 ) {
sb.append( ROW_SPLIT_TOKEN );
}
}
return sb.toString();
}
/**
* Set a columns sort type flag, for use in javascript for sorting.
* @param colIndex The column number (zero is the left hand most column)
* @param sortType The sort type flag.
*/
private void addColumnSortType( int colIndex, DataColumnType sortType ) {
String propertyName = COLUMN_SORT_TYPE_PROPERTY + String.valueOf( colIndex );
set( propertyName , sortType.toString() );
}
}
-------------------------------
DataTablePeer.java
package echo.uicomponent.table;
import nextapp.echo.app.Component;
import nextapp.echo.app.update.ClientUpdateManager;
import nextapp.echo.app.util.Context;
import nextapp.echo.webcontainer.AbstractComponentSynchronizePeer;
import nextapp.echo.webcontainer.ServerMessage;
import nextapp.echo.webcontainer.Service;
import nextapp.echo.webcontainer.WebContainerServlet;
import nextapp.echo.webcontainer.service.JavaScriptService;
public class DataTablePeer extends AbstractComponentSynchronizePeer {
// Create a JavaScriptService containing the SpinButton JavaScript code.
private static final Service JS_SERVICE = JavaScriptService.forResources(
"Vlst.DataTable" , new String[] {
"/Application.DataTable.js",
"/Sync.DataTable.js"
}
);
static {
// Register JavaScriptService with the global service registry.
WebContainerServlet.getServiceRegistry().add(JS_SERVICE);
}
@SuppressWarnings("unchecked")
public DataTablePeer() {
super();
// addOutputProperty(DataTable.TABLE_DATA_SERVER_PROPERTY );.
addEvent(new EventPeer(DataTable.ROW_SELECTED_ACTION,
DataTable.ACTION_LISTENERS_CHANGED_PROPERTY) {
public boolean hasListeners(Context context, Component c) {
return ((DataTable) c).hasActionListeners();
}
});
}
@Override
public String getClientComponentType(boolean shortType) {
// Return client-side component type name.
return "Vlst.DataTable";
}
@Override
@SuppressWarnings("unchecked")
public Class getComponentClass() {
// Return server-side Java class.
return DataTable.class;
}
@Override
public void init(Context context, Component component) {
super.init(context, component);
// Obtain outgoing 'ServerMessage' for initial render.
ServerMessage serverMessage = (ServerMessage) context
.get(ServerMessage.class);
// Add SpinButton JavaScript library to client.
serverMessage.addLibrary(JS_SERVICE.getId());
}
@Override
public Object getOutputProperty(Context context, Component component,
String propertyName, int propertyIndex) {
// if ( propertyName.equals( DataTable.TABLE_DATA_SERVER_PROPERTY )) {
// DataTable dataTable = (DataTable) component;
// return dataTable.getEncodedTableData();
// } else {
return super.getOutputProperty(context, component, propertyName, propertyIndex);
// }
}
@Override
@SuppressWarnings("unchecked")
public Class getInputPropertyClass(String propertyName) {
if( propertyName.equals( DataTable.SELECTED_ROW_CLIENT_PROPERTY )) {
return Integer.class;
} else if( propertyName.equals( DataTable.TABLE_SORT_ORDER_CLIENT_PROPERTY )) {
return String.class;
} else {
return null;
}
}
public void storeInputProperty(Context context, Component component,
String propertyName, int propertyIndex, Object newValue) {
if (propertyName.equals(DataTable.SELECTED_ROW_CLIENT_PROPERTY)
|| propertyName.equals(DataTable.TABLE_SORT_ORDER_CLIENT_PROPERTY) ) {
ClientUpdateManager clientUpdateManager = (ClientUpdateManager) context
.get(ClientUpdateManager.class);
clientUpdateManager.setComponentProperty(component, propertyName, newValue);
}
}
}
-----------------------------------
Application.DataTable.js
if (!Core.get(window, [ "Vlst", "DataTable" ])) {
Core.set(window, [ "Vlst", "DataTable" ], {});
}
Vlst.DataTable = Core.extend(Echo.Component, {
$load: function() {
Echo.ComponentFactory.registerType("Vlst.DataTable", this);
},
componentType: "Vlst.DataTable",
fireRowSelectedAction: function() {
this.fireEvent( {type: "rowSelectedAction", source:this });
}
});
-----------------------------------------------
Sync.DataTable.js
if (!Core.get(window, [ "Vlst", "DataTableSync" ])) {
Core.set(window, [ "Vlst", "DataTableSync" ], {});
}
Vlst.DataTableSync = Core.extend(Echo.Render.ComponentSync, {
$load : function() {
// this._prototypeTable = this._createPrototypeTable();
Echo.Render.registerPeer("Vlst.DataTable", this);
},
$static : {
DEFAULTS : {
headerBgColor :"#A5AEFF",
zebraStripeRowColor : "#EBF0F0",
rowBgColor : "#FFFFFF",
rowHoverColor: "#D6DDF7",
rowSelectedColor: "#DDDD00"
// tableBorder:
// cellBorder, (only left right?)
// fontColor
// fontType // TODO how to handle per column attribs
}
},
_table :null,
_headers :null,
_tableData :null,
_actualTableOrder: null,
_sortColumn: null,
$construct : function() {
_headers = [];
_tableData = [];
_actualTableOrder = [];
_sortColumn = 0;
},
renderAdd : function(update, parentElement) {
this._table = document.createElement("table");
Echo.Sync.Border.render(this.component.render("border"), this._table);
// this._table.style.outlineStyle = "none";
this._table.tabIndex = "-1";
this._table.style.borderCollapse = "collapse";
this._table.cellPadding=2;
this._table.id = this.component.renderId;
this._table.rules="COLS";
//////////////////////////////////////////////////////////////////////////////////////////////
/*
* decode table data
*/
// decode the table data from a String to a String[][] by splitting on row and cell tokens
var encodedData = this.component.render("tableData", "no data");
var rowSplitToken = "<!R!>"; // keep in sync with the server side
var cellSplitToken = "<!C!>"; // keep in sync with the server side
var rows = encodedData.split(rowSplitToken);
this._headers = rows[0].split(cellSplitToken);
var TABLE_WIDTH = this._headers.length;
this._tableData = [];
for (i = 1; i < rows.length; i++) {
// create the table body (implicit 2D array)
// skip the header row (hence i=1 initially)
this._tableData[i - 1] = rows[i].split(cellSplitToken);
}
// initialize the 'real table order' if not already initialized
// initialize to the same sequence as the original data to start
if( this._actualTableOrder === null || this._actualTableOrder.length === 0 ) {
this._actualTableOrder = [];
for( i = 0; i < this._tableData.length; i ++ ) {
this._actualTableOrder[i] = i;
}
}
////////////////////////////////////////////////////////////////////////////
// var totalWidth = 100* TABLE_WIDTH;
// this._table.style.width = totalWidth + "px";
// Render column widths into colgroup element.
// increases table rendering speed (client agent can avoid scanning the table twice,
// since it will do so in it's first pass to determine the column and table width)
var colGroup = document.createElement("colgroup");
for( i = 0 ; i < TABLE_WIDTH ; i ++ ) {
var col = document.createElement("col");
col.style.width = "250px";
colGroup.appendChild( col );
}
this._table.appendChild( colGroup );
var thead = document.createElement("thead");
this._table.appendChild( thead );
var headerRow = document.createElement("tr");
thead.appendChild( headerRow );
var headerBgColor = this.component.render("headerBgColor", Vlst.DataTableSync.DEFAULTS.headerBgColor );
Echo.Sync.Color.render( headerBgColor, headerRow, "backgroundColor" );
for (i = 0; i < TABLE_WIDTH; i++) {
var headerCell = document.createElement("th");
headerRow.appendChild( headerCell );
headerCell.style.cursor="pointer";
headerCell.sortFunc = this.interpretSortType( this.component.render( "COL_SORT_TYPE_" + i , "TEXT" ) );
headerCell.columnNumber = i;
// Echo.Sync.Border.render(this.component.render("Border"), headerCell);
var headerText = document.createTextNode(this._headers[i]);
headerCell.appendChild(headerText);
Core.Web.Event.add( headerCell, "click", Core.method(this, this._processHeaderSort), false);
}
// create the table body
var tbody = document.createElement("tbody");
this._table.appendChild(tbody);
for (i = 0; i < this._tableData.length; i++) {
var tRow = document.createElement("tr");
tbody.appendChild(tRow);
tRow.originalRowNumber = this._actualTableOrder[i];
tRow.actualRowNumber = i;
this._setRowColor( tRow );
Core.Web.Event.add( tRow, "click", Core.method( this, this._processRowClicked), false );
Core.Web.Event.add( tRow, "mouseover", Core.method( this, this._processRowMouseOver), false );
Core.Web.Event.add( tRow, "mouseout" , Core.method( this, this._processRowMouseOut), false );
// create row cell elements
for (j = 0; j < TABLE_WIDTH; j++) {
var td = document.createElement("td");
tRow.appendChild(td);
// Echo.Sync.Border.render(this.component.render("Border"), td);
var cellData = this._tableData[ this._actualTableOrder[i] ][j];
td.appendChild( document.createTextNode( cellData ));
// var cellDataLines = cellData.split("\n");
// replace cell content newlines characters with html line breaks
// for (k = 0; k < cellDataLines.length; k++) {
// // td.appendChild(document.createTextNode(cellDataLines[k]));
//
// if( k+1 < cellDataLines.length ) {
// // td.appendChild(document.createElement("br"));
// }
//
// }
}
}
// traverseDOMTree( document, this._table, 1 );
parentElement.appendChild(this._table);
},
_removeListeners: function( node, depth ) {
if( depth > 0 ) {
if( node.hasChildNodes() ) {
for( i = 0; i < node.childNodes.length; i ++ ) {
this._removeListeners( node.childNodes.item( i ), depth -1 );
}
}
}
Core.Web.Event.removeAll( node );
},
renderDispose : function(update) {
this._removeListeners( this._table.childNodes.item( 1 ) ,2 ); // removes table header cell listeners
this._removeListeners( this._table, 2 ); // removes table row listeners
this._table = null;
this._headers = null;
this._tableData = null;
this._actualTableOrder = null;
},
renderUpdate : function(update) {
/* var element = this._table;
var containerElement = element.parentNode;
if( update !== null ) {
Echo.Render.renderComponentDispose(update, update.parent);
}
containerElement.removeChild(element);
this.renderAdd(update, containerElement);
return true;
*/
},
redrawTable: function() {
var element = this._table;
var containerElement = element.parentNode;
containerElement.removeChild(element);
this.renderAdd(null, containerElement);
return true;
},
_processHeaderSort: function( e ) {
if (!this.client || !this.client.verifyInput(this.component)) {
return true;
}
var newSortColumn = e.target.columnNumber;
var sortFunction = e.target.sortFunc;
// construct what the new table order should be
var newTableOrder = [];
// if sorting on the same column, simply reverse the column rows
if( this._sortColumn == newSortColumn ) {
for( i = 0; i < this._actualTableOrder.length; i ++ ) {
newTableOrder[i] = this._actualTableOrder[ this._actualTableOrder.length-i-1];
}
// otherwise actually sort the rows
} else {
this._sortColumn = newSortColumn;
var row_array = [];
for( i = 0; i < this._tableData.length; i ++ ) {
// Construct a sort array, a 2 element array, first element is the sort key,
// the second element is the original row number.
// After sorting this array, the contained array's second element will
// be the tables correct sort order
row_array[ row_array.length] = [ this._tableData[i][ this._sortColumn ], i ];
}
row_array.sort( sortFunction );
for( i = 0; i < row_array.length; i ++ ) {
newTableOrder[i] = row_array[i][1];
}
}
// update the table order
this._actualTableOrder = newTableOrder;
// redraw the table
this.redrawTable();
},
_processRowMouseOver: function( e ) {
var hoverRow = e.target.parentNode;
var color = this.component.render("rowHoverColor", Vlst.DataTableSync.DEFAULTS.rowHoverColor );
Echo.Sync.Color.render( color, hoverRow, "backgroundColor" );
},
_processRowMouseOut: function( e ) {
//alert("moust out, row num = " + rowNum + " row num %2 = " + (rowNum %2) + " row num %2 == 0 ? " + ( (rowNum %2) == 0 ));
this._setRowColor( e.target.parentNode );
},
_setRowColor: function( row ) {
var rowNum = row.actualRowNumber;
var colorToUse;
if( rowNum %2 == 0 ) {
colorToUse = this.component.render("bgRowRowColor", Vlst.DataTableSync.DEFAULTS.rowBgColor );
} else {
colorToUse = this.component.render("zebraStripeRowColor", Vlst.DataTableSync.DEFAULTS.zebraStripeRowColor );
}
Echo.Sync.Color.render( colorToUse , row, "backgroundColor" );
},
_processRowClicked: function( e ) {
var selectedRow = e.target.parentNode;
var color = this.component.render("rowSelectedColor", Vlst.DataTableSync.DEFAULTS.rowSelectedColor );
Echo.Sync.Color.render( color , selectedRow, "backgroundColor" );
var rowNumber = selectedRow.originalRowNumber;
// alert("row clicked, and your actual table order is : " + this._actualTableOrder );
this.component.set("tableSortOrder", this._encodeArrayAsString( this._actualTableOrder ));
this.component.set("selectedRowProperty", rowNumber );
this.component.fireRowSelectedAction();
},
_encodeArrayAsString: function( array ) {
// alert("encoding the following param: "+ array );
var str = "";
for( i = 0; i < array.length; i ++ ) {
str = str + array[i] + "#";
}
// alert("encoded the actual table order into: "+ str );
//row clicked, and your actual table order is : " + this._actualTableOrder );
return str;
},
/**
* Returns a sort function based upon the 'type' of the parameter.
*/
interpretSortType: function( sortTypeKey ) {
if( sortTypeKey === "TEXT" ) {
return this.sort_alpha;
} else if( sortTypeKey === "INTEGER" ) {
return this.sort_integer;
} else if( sortTypeKey === "DECIMAL" ) {
return this.sort_numeric;
} else {
return this.sort_alpha;
}
},
/**
* Sort function for text types
*/
sort_alpha: function(a,b) {
if (a[0]==b[0]) {
return 0;
} else if (a[0]<b[0]) {
return -1;
} else {
return 1;
}
},
/**
* Sort function for integer types
*/
sort_integer: function(a,b) {
var intA = parseInt( a, 10 );
var intB = parseInt( b, 10 );
if( intA == intB ) {
return 0;
} else if( intA > intB ) {
return 1;
} else {
return -1;
}
},
/**
* Sort function for floating point (decimal) types
*/
sort_numeric: function(a,b) {
aa = parseFloat(a[0] );
if (isNaN(aa)) {
aa = 0;
}
bb = parseFloat(b[0] );
//.replace(/[^0-9.-]/g,''));
if (isNaN(bb)) {
bb = 0;
}
return aa-bb;
},
/**
* Not yet used, a sort function for dates
*/
sort_mmdd: function(a,b) {
mtch = a[0].match( _DATE_REGEX );
y = mtch[3]; d = mtch[2]; m = mtch[1];
if (m.length == 1) {
m = '0'+m;
}
if (d.length == 1) {
d = '0'+d;
}
dt1 = y+m+d;
mtch = b[0].match(sorttable.DATE_RE);
y = mtch[3]; d = mtch[2]; m = mtch[1];
if (m.length == 1) {
m = '0'+m;
}
if (d.length == 1) {
d = '0'+d;
}
dt2 = y+m+d;
if (dt1==dt2) {
return 0;
}
if (dt1<dt2) {
return -1;
}
return 1;
}
});
------------------------------------
SynchronizePeerBindings.properties
echo.uicomponent.table.DataTable echo.uicomponent.table.DataTablePeer