blob: 18569ec1a25cae250014ddb8d4e00a045734e137 [file] [log] [blame]
/*
Copyright (c) 2004-2006, The Dojo Foundation
All Rights Reserved.
Licensed under the Academic Free License version 2.1 or above OR the
modified BSD license. For more information on Dojo licensing, see:
http://dojotoolkit.org/community/licensing.shtml
*/
dojo.provide("dojo.widget.FilteringTable");
dojo.require("dojo.date.format");
dojo.require("dojo.collections.Store");
dojo.require("dojo.html.*");
dojo.require("dojo.html.util");
dojo.require("dojo.html.style");
dojo.require("dojo.html.selection");
dojo.require("dojo.event.*");
dojo.require("dojo.widget.*");
dojo.require("dojo.widget.HtmlWidget");
dojo.widget.defineWidget(
"dojo.widget.FilteringTable",
dojo.widget.HtmlWidget,
function(){
// summary: A basic tabular data widget that supports sorting and filtering mechanisms.
// description:
// FilteringTable is a 2D data view that supports multiple column sorting and filtering
// functionality. It can get its data in one of two ways: via HTML (i.e. degradable
// data), or from an external JSON source through widget.store.setData. Records in
// a FilteringTable can be selected as if it were a select list.
// store: dojo.collections.Store
// The underlying Store for all data represented by the widget.
// valueField: String
// The name of the field used as a unique key for each row, defaults to "Id".
// multiple: boolean
// Allow multiple selections.
// maxSelect: Integer
// Maximum number of rows that can be selected at once. 0 == no limit.
// maxSortable: Integer
// Maximum number of columns allowed for sorting at one time.
// minRows: Integer
// The minimum number of rows to show. Default is 0.
// defaultDateFormat: String
// The default format for a date column, as used by dojo.date.format.
// alternateRows: Boolean
// Use alternate row CSS classes to show zebra striping.
// headClass: String
// CSS Class name for the head of the table.
// tbodyClass: String
// CSS Class name for the body of the table.
// headerClass: String
// CSS Class name for headers that are not sorted.
// headerUpClass: String
// CSS Class name for headers that are for ascending sorted columns. Default is "selectedUp".
// headerDownClass: String
// CSS Class name for headers that are for descending sorted columns. Default is "selectedDown".
// rowClass: String
// CSS Class name for body rows.
// rowAlternateClass: String
// CSS Class name for alternate rows. Default is "alt".
// rowSelectedClass: String
// CSS Class name for selected rows. Default is "selected".
// columnSelectedClass: String
// CSS Class name for any columns being sorted on. Unimplemented.
this.store=new dojo.collections.Store();
//declare per instance changeable widget properties
this.valueField="Id";
this.multiple=false;
this.maxSelect=0;
this.maxSortable=1; // how many columns can be sorted at once.
this.minRows=0;
this.defaultDateFormat = "%D";
this.isInitialized=false;
this.alternateRows=false;
this.columns=[];
this.sortInformation=[{
index:0,
direction:0
}];
// CSS definitions
this.headClass="";
this.tbodyClass="";
this.headerClass="";
this.headerUpClass="selectedUp";
this.headerDownClass="selectedDown";
this.rowClass="";
this.rowAlternateClass="alt";
this.rowSelectedClass="selected";
this.columnSelected="sorted-column";
},
{
// dojo widget properties
isContainer: false,
templatePath: null,
templateCssPath: null,
// methods.
getTypeFromString: function(/* string */s){
// summary
// Gets a function based on the passed string.
var parts = s.split("."), i = 0, obj = dj_global;
do{
obj = obj[parts[i++]];
} while (i < parts.length && obj);
return (obj != dj_global) ? obj : null; // function
},
// custom data access.
getByRow: function(/*HTMLTableRow*/row){
// summary
// Returns the data object based on the passed row.
return this.store.getByKey(dojo.html.getAttribute(row, "value")); // object
},
getDataByRow: function(/*HTMLTableRow*/row){
// summary
// Returns the source data object based on the passed row.
return this.store.getDataByKey(dojo.html.getAttribute(row, "value")); // object
},
getRow: function(/* Object */ obj){
// summary
// Finds the row in the table based on the passed data object.
var rows = this.domNode.tBodies[0].rows;
for(var i=0; i<rows.length; i++){
if(this.store.getDataByKey(dojo.html.getAttribute(rows[i], "value")) == obj){
return rows[i]; // HTMLTableRow
}
}
return null; // HTMLTableRow
},
getColumnIndex: function(/* string */fieldPath){
// summary
// Returns index of the column that represents the passed field path.
for(var i=0; i<this.columns.length; i++){
if(this.columns[i].getField() == fieldPath){
return i; // integer
}
}
return -1; // integer
},
getSelectedData: function(){
// summary
// returns all objects that are selected.
var data=this.store.get();
var a=[];
for(var i=0; i<data.length; i++){
if(data[i].isSelected){
a.push(data[i].src);
}
}
if(this.multiple){
return a; // array
} else {
return a[0]; // object
}
},
isSelected: function(/* object */obj){
// summary
// Returns whether the passed object is currently selected.
var data = this.store.get();
for(var i=0; i<data.length; i++){
if(data[i].src == obj){
return true; // boolean
}
}
return false; // boolean
},
isValueSelected: function(/* string */val){
// summary
// Returns the object represented by key "val" is selected.
var v = this.store.getByKey(val);
if(v){
return v.isSelected; // boolean
}
return false; // boolean
},
isIndexSelected: function(/* number */idx){
// summary
// Returns the object represented by integer "idx" is selected.
var v = this.store.getByIndex(idx);
if(v){
return v.isSelected; // boolean
}
return false; // boolean
},
isRowSelected: function(/* HTMLTableRow */row){
// summary
// Returns if the passed row is selected.
var v = this.getByRow(row);
if(v){
return v.isSelected; // boolean
}
return false; // boolean
},
reset: function(){
// summary
// Resets the widget to its initial internal state.
this.store.clearData();
this.columns = [];
this.sortInformation = [ {index:0, direction:0} ];
this.resetSelections();
this.isInitialized = false;
this.onReset();
},
resetSelections: function(){
// summary
// Unselects all data objects.
this.store.forEach(function(element){
element.isSelected = false;
});
},
onReset:function(){
// summary
// Stub for onReset event.
},
// selection and toggle functions
select: function(/*object*/ obj){
// summary
// selects the passed object.
var data = this.store.get();
for(var i=0; i<data.length; i++){
if(data[i].src == obj){
data[i].isSelected = true;
break;
}
}
this.onDataSelect(obj);
},
selectByValue: function(/*string*/ val){
// summary
// selects the object represented by key "val".
this.select(this.store.getDataByKey(val));
},
selectByIndex: function(/*number*/ idx){
// summary
// selects the object represented at index "idx".
this.select(this.store.getDataByIndex(idx));
},
selectByRow: function(/*HTMLTableRow*/ row){
// summary
// selects the object represented by HTMLTableRow row.
this.select(this.getDataByRow(row));
},
selectAll: function(){
// summary
// selects all objects.
this.store.forEach(function(element){
element.isSelected = true;
});
},
onDataSelect: function(/* object */obj){
// summary
// Stub for onDataSelect event.
},
toggleSelection: function(/*object*/obj){
// summary
// Flips the selection state of passed obj.
var data = this.store.get();
for(var i=0; i<data.length; i++){
if(data[i].src == obj){
data[i].isSelected = !data[i].isSelected;
break;
}
}
this.onDataToggle(obj);
},
toggleSelectionByValue: function(/*string*/val){
// summary
// Flips the selection state of object represented by val.
this.toggleSelection(this.store.getDataByKey(val));
},
toggleSelectionByIndex: function(/*number*/idx){
// summary
// Flips the selection state of object at index idx.
this.toggleSelection(this.store.getDataByIndex(idx));
},
toggleSelectionByRow: function(/*HTMLTableRow*/row){
// summary
// Flips the selection state of object represented by row.
this.toggleSelection(this.getDataByRow(row));
},
toggleAll: function(){
// summary
// Flips the selection state of all objects.
this.store.forEach(function(element){
element.isSelected = !element.isSelected;
});
},
onDataToggle: function(/* object */obj){
// summary
// Stub for onDataToggle event.
},
// parsing functions, from HTML to metadata/SimpleStore
_meta:{
field:null,
format:null,
filterer:null,
noSort:false,
sortType:"String",
dataType:String,
sortFunction:null,
filterFunction:null,
label:null,
align:"left",
valign:"middle",
getField:function(){
return this.field || this.label;
},
getType:function(){
return this.dataType;
}
},
createMetaData: function(/* object */obj){
// summary
// Take a JSON-type structure and make it into a ducktyped metadata object.
for(var p in this._meta){
// rudimentary mixin
if(!obj[p]){
obj[p] = this._meta[p];
}
}
if(!obj.label){
obj.label=obj.field;
}
if(!obj.filterFunction){
obj.filterFunction=this._defaultFilter;
}
return obj; // object
},
parseMetadata: function(/* HTMLTableHead */head){
// summary
// Parses the passed HTMLTableHead element to create meta data.
this.columns=[];
this.sortInformation=[];
var row = head.getElementsByTagName("tr")[0];
var cells = row.getElementsByTagName("td");
if (cells.length == 0){
cells = row.getElementsByTagName("th");
}
for(var i=0; i<cells.length; i++){
var o = this.createMetaData({ });
// presentation attributes
if(dojo.html.hasAttribute(cells[i], "align")){
o.align = dojo.html.getAttribute(cells[i],"align");
}
if(dojo.html.hasAttribute(cells[i], "valign")){
o.valign = dojo.html.getAttribute(cells[i],"valign");
}
if(dojo.html.hasAttribute(cells[i], "nosort")){
o.noSort = (dojo.html.getAttribute(cells[i],"nosort")=="true");
}
if(dojo.html.hasAttribute(cells[i], "sortusing")){
var trans = dojo.html.getAttribute(cells[i],"sortusing");
var f = this.getTypeFromString(trans);
if (f != null && f != window && typeof(f)=="function"){
o.sortFunction=f;
}
}
o.label = dojo.html.renderedTextContent(cells[i]);
if(dojo.html.hasAttribute(cells[i], "field")){
o.field=dojo.html.getAttribute(cells[i],"field");
} else if(o.label.length > 0){
o.field=o.label;
} else {
o.field = "field" + i;
}
if(dojo.html.hasAttribute(cells[i], "format")){
o.format=dojo.html.getAttribute(cells[i],"format");
}
if(dojo.html.hasAttribute(cells[i], "dataType")){
var sortType = dojo.html.getAttribute(cells[i],"dataType");
if(sortType.toLowerCase()=="html" || sortType.toLowerCase()=="markup"){
o.sortType = "__markup__"; // always convert to "__markup__"
}else{
var type = this.getTypeFromString(sortType);
if(type){
o.sortType = sortType;
o.dataType = type;
}
}
}
// TODO: set up filtering mechanisms here.
if(dojo.html.hasAttribute(cells[i], "filterusing")){
var trans = dojo.html.getAttribute(cells[i],"filterusing");
var f = this.getTypeFromString(trans);
if (f != null && f != window && typeof(f)=="function"){
o.filterFunction=f;
}
}
this.columns.push(o);
// check to see if there's a default sort, and set the properties necessary
if(dojo.html.hasAttribute(cells[i], "sort")){
var info = {
index:i,
direction:0
};
var dir = dojo.html.getAttribute(cells[i], "sort");
if(!isNaN(parseInt(dir))){
dir = parseInt(dir);
info.direction = (dir != 0) ? 1 : 0;
}else{
info.direction = (dir.toLowerCase() == "desc") ? 1 : 0;
}
this.sortInformation.push(info);
}
}
if(this.sortInformation.length == 0){
this.sortInformation.push({
index:0,
direction:0
});
} else if (this.sortInformation.length > this.maxSortable){
this.sortInformation.length = this.maxSortable;
}
},
parseData: function(/* HTMLTableBody */body){
// summary
// Parse HTML data into native JSON structure for the store.
if(body.rows.length == 0 && this.columns.length == 0){
return; // there's no data, ignore me.
}
// create a data constructor based on what we've got for the fields.
var self=this;
this["__selected__"] = [];
var arr = this.store.getFromHtml(this.columns, body, function(obj, row){
if(typeof(obj[self.valueField])=="undefined" || obj[self.valueField]==null){
obj[self.valueField] = dojo.html.getAttribute(row, "value");
}
if(dojo.html.getAttribute(row, "selected")=="true"){
self["__selected__"].push(obj);
}
});
this.store.setData(arr, true);
this.render();
for(var i=0; i<this["__selected__"].length; i++){
this.select(this["__selected__"][i]);
}
this.renderSelections();
delete this["__selected__"];
// say that we are already initialized so that we don't kill anything
this.isInitialized=true;
},
// standard events
onSelect: function(/* HTMLEvent */e){
// summary
// Handles the onclick event of any element.
var row = dojo.html.getParentByType(e.target,"tr");
if(dojo.html.hasAttribute(row,"emptyRow")){
return;
}
var body = dojo.html.getParentByType(row,"tbody");
if(this.multiple){
if(e.shiftKey){
var startRow;
var rows=body.rows;
for(var i=0;i<rows.length;i++){
if(rows[i]==row){
break;
}
if(this.isRowSelected(rows[i])){
startRow=rows[i];
}
}
if(!startRow){
startRow = row;
for(; i<rows.length; i++){
if(this.isRowSelected(rows[i])){
row = rows[i];
break;
}
}
}
this.resetSelections();
if(startRow == row){
this.toggleSelectionByRow(row);
} else {
var doSelect = false;
for(var i=0; i<rows.length; i++){
if(rows[i] == startRow){
doSelect=true;
}
if(doSelect){
this.selectByRow(rows[i]);
}
if(rows[i] == row){
doSelect = false;
}
}
}
} else {
this.toggleSelectionByRow(row);
}
} else {
this.resetSelections();
this.toggleSelectionByRow(row);
}
this.renderSelections();
},
onSort: function(/* HTMLEvent */e){
// summary
// Sort the table based on the column selected.
var oldIndex=this.sortIndex;
var oldDirection=this.sortDirection;
var source=e.target;
var row=dojo.html.getParentByType(source,"tr");
var cellTag="td";
if(row.getElementsByTagName(cellTag).length==0){
cellTag="th";
}
var headers=row.getElementsByTagName(cellTag);
var header=dojo.html.getParentByType(source,cellTag);
for(var i=0; i<headers.length; i++){
dojo.html.setClass(headers[i], this.headerClass);
if(headers[i]==header){
if(this.sortInformation[0].index != i){
this.sortInformation.unshift({
index:i,
direction:0
});
} else {
this.sortInformation[0] = {
index:i,
direction:(~this.sortInformation[0].direction)&1
};
}
}
}
this.sortInformation.length = Math.min(this.sortInformation.length, this.maxSortable);
for(var i=0; i<this.sortInformation.length; i++){
var idx=this.sortInformation[i].index;
var dir=(~this.sortInformation[i].direction)&1;
dojo.html.setClass(headers[idx], dir==0?this.headerDownClass:this.headerUpClass);
}
this.render();
},
onFilter: function(){
// summary
// show or hide rows based on the parameters of the passed filter.
},
// Filtering methods
_defaultFilter: function(/* Object */obj){
// summary
// Always return true as the result of the default filter.
return true;
},
setFilter: function(/* string */field, /* function */fn){
// summary
// set a filtering function on the passed field.
for(var i=0; i<this.columns.length; i++){
if(this.columns[i].getField() == field){
this.columns[i].filterFunction=fn;
break;
}
}
this.applyFilters();
},
setFilterByIndex: function(/* number */idx, /* function */fn){
// summary
// set a filtering function on the passed column index.
this.columns[idx].filterFunction=fn;
this.applyFilters();
},
clearFilter: function(/* string */field){
// summary
// clear a filtering function on the passed field.
for(var i=0; i<this.columns.length; i++){
if(this.columns[i].getField() == field){
this.columns[i].filterFunction=this._defaultFilter;
break;
}
}
this.applyFilters();
},
clearFilterByIndex: function(/* number */idx){
// summary
// clear a filtering function on the passed column index.
this.columns[idx].filterFunction=this._defaultFilter;
this.applyFilters();
},
clearFilters: function(){
// summary
// clears all filters.
for(var i=0; i<this.columns.length; i++){
this.columns[i].filterFunction=this._defaultFilter;
}
// we'll do the clear manually, it will be faster.
var rows=this.domNode.tBodies[0].rows;
for(var i=0; i<rows.length; i++){
rows[i].style.display="";
if(this.alternateRows){
dojo.html[((i % 2 == 1)?"addClass":"removeClass")](rows[i], this.rowAlternateClass);
}
}
this.onFilter();
},
applyFilters: function(){
// summary
// apply all filters to the table.
var alt=0;
var rows=this.domNode.tBodies[0].rows;
for(var i=0; i<rows.length; i++){
var b=true;
var row=rows[i];
for(var j=0; j<this.columns.length; j++){
var value = this.store.getField(this.getDataByRow(row), this.columns[j].getField());
if(this.columns[j].getType() == Date && value != null && !value.getYear){
value = new Date(value);
}
if(!this.columns[j].filterFunction(value)){
b=false;
break;
}
}
row.style.display=(b?"":"none");
if(b && this.alternateRows){
dojo.html[((alt++ % 2 == 1)?"addClass":"removeClass")](row, this.rowAlternateClass);
}
}
this.onFilter();
},
// sorting functionality
createSorter: function(/* array */info){
// summary
// creates a custom function to be used for sorting.
var self=this;
var sortFunctions=[]; // our function stack.
function createSortFunction(fieldIndex, dir){
var meta=self.columns[fieldIndex];
var field=meta.getField();
return function(rowA, rowB){
if(dojo.html.hasAttribute(rowA,"emptyRow")){ return 1; }
if(dojo.html.hasAttribute(rowB,"emptyRow")){ return -1; }
// TODO: check for markup and compare by rendered text.
var a = self.store.getField(self.getDataByRow(rowA), field);
var b = self.store.getField(self.getDataByRow(rowB), field);
var ret = 0;
if(a > b) ret = 1;
if(a < b) ret = -1;
return dir * ret;
}
}
var current=0;
var max = Math.min(info.length, this.maxSortable, this.columns.length);
while(current < max){
var direction = (info[current].direction == 0) ? 1 : -1;
sortFunctions.push(
createSortFunction(info[current].index, direction)
);
current++;
}
return function(rowA, rowB){
var idx=0;
while(idx < sortFunctions.length){
var ret = sortFunctions[idx++](rowA, rowB);
if(ret != 0) return ret;
}
// if we got here then we must be equal.
return 0;
}; // function
},
// rendering
createRow: function(/* object */obj){
// summary
// Create an HTML row based on the passed object
var row=document.createElement("tr");
dojo.html.disableSelection(row);
if(obj.key != null){
row.setAttribute("value", obj.key);
}
for(var j=0; j<this.columns.length; j++){
var cell=document.createElement("td");
cell.setAttribute("align", this.columns[j].align);
cell.setAttribute("valign", this.columns[j].valign);
dojo.html.disableSelection(cell);
var val = this.store.getField(obj.src, this.columns[j].getField());
if(typeof(val)=="undefined"){
val="";
}
this.fillCell(cell, this.columns[j], val);
row.appendChild(cell);
}
return row; // HTMLTableRow
},
fillCell: function(/* HTMLTableCell */cell, /* object */meta, /* object */val){
// summary
// Fill the passed cell with value, based on the passed meta object.
if(meta.sortType=="__markup__"){
cell.innerHTML=val;
} else {
if(meta.getType()==Date) {
val=new Date(val);
if(!isNaN(val)){
var format = this.defaultDateFormat;
if(meta.format){
format = meta.format;
}
cell.innerHTML = dojo.date.strftime(val, format);
} else {
cell.innerHTML = val;
}
} else if ("Number number int Integer float Float".indexOf(meta.getType())>-1){
// TODO: number formatting
if(val.length == 0){
val="0";
}
var n = parseFloat(val, 10) + "";
// TODO: numeric formatting + rounding :)
if(n.indexOf(".")>-1){
n = dojo.math.round(parseFloat(val,10),2);
}
cell.innerHTML = n;
}else{
cell.innerHTML = val;
}
}
},
prefill: function(){
// summary
// if there's no data in the table, then prefill it with this.minRows.
this.isInitialized = false;
var body = this.domNode.tBodies[0];
while (body.childNodes.length > 0){
body.removeChild(body.childNodes[0]);
}
if(this.minRows>0){
for(var i=0; i < this.minRows; i++){
var row = document.createElement("tr");
if(this.alternateRows){
dojo.html[((i % 2 == 1)?"addClass":"removeClass")](row, this.rowAlternateClass);
}
row.setAttribute("emptyRow","true");
for(var j=0; j<this.columns.length; j++){
var cell = document.createElement("td");
cell.innerHTML = "&nbsp;";
row.appendChild(cell);
}
body.appendChild(row);
}
}
},
init: function(){
// summary
// initializes the table of data
this.isInitialized=false;
// if there is no thead, create it now.
var head=this.domNode.getElementsByTagName("thead")[0];
if(head.getElementsByTagName("tr").length == 0){
// render the column code.
var row=document.createElement("tr");
for(var i=0; i<this.columns.length; i++){
var cell=document.createElement("td");
cell.setAttribute("align", this.columns[i].align);
cell.setAttribute("valign", this.columns[i].valign);
dojo.html.disableSelection(cell);
cell.innerHTML=this.columns[i].label;
row.appendChild(cell);
// attach the events.
if(!this.columns[i].noSort){
dojo.event.connect(cell, "onclick", this, "onSort");
}
}
dojo.html.prependChild(row, head);
}
if(this.store.get().length == 0){
return false;
}
var idx=this.domNode.tBodies[0].rows.length;
if(!idx || idx==0 || this.domNode.tBodies[0].rows[0].getAttribute("emptyRow")=="true"){
idx = 0;
var body = this.domNode.tBodies[0];
while(body.childNodes.length>0){
body.removeChild(body.childNodes[0]);
}
var data = this.store.get();
for(var i=0; i<data.length; i++){
var row = this.createRow(data[i]);
body.appendChild(row);
idx++;
}
}
// add empty rows
if(this.minRows > 0 && idx < this.minRows){
idx = this.minRows - idx;
for(var i=0; i<idx; i++){
row=document.createElement("tr");
row.setAttribute("emptyRow","true");
for(var j=0; j<this.columns.length; j++){
cell=document.createElement("td");
cell.innerHTML="&nbsp;";
row.appendChild(cell);
}
body.appendChild(row);
}
}
// last but not least, show any columns that have sorting already on them.
var row=this.domNode.getElementsByTagName("thead")[0].rows[0];
var cellTag="td";
if(row.getElementsByTagName(cellTag).length==0) cellTag="th";
var headers=row.getElementsByTagName(cellTag);
for(var i=0; i<headers.length; i++){
dojo.html.setClass(headers[i], this.headerClass);
}
for(var i=0; i<this.sortInformation.length; i++){
var idx=this.sortInformation[i].index;
var dir=(~this.sortInformation[i].direction)&1;
dojo.html.setClass(headers[idx], dir==0?this.headerDownClass:this.headerUpClass);
}
this.isInitialized=true;
return this.isInitialized;
},
render: function(){
// summary
// Renders the actual table data.
/* The method that should be called once underlying changes
* are made, including sorting, filtering, data changes.
* Rendering the selections themselves are a different method,
* which render() will call as the last step.
****************************************************************/
if(!this.isInitialized){
var b = this.init();
if(!b){
this.prefill();
return;
}
}
// do the sort
var rows=[];
var body=this.domNode.tBodies[0];
var emptyRowIdx=-1;
for(var i=0; i<body.rows.length; i++){
rows.push(body.rows[i]);
}
// build the sorting function, and do the sorting.
var sortFunction = this.createSorter(this.sortInformation);
if(sortFunction){
rows.sort(sortFunction);
}
// append the rows without killing them, this should help with the HTML problems.
for(var i=0; i<rows.length; i++){
if(this.alternateRows){
dojo.html[((i%2==1)?"addClass":"removeClass")](rows[i], this.rowAlternateClass);
}
dojo.html[(this.isRowSelected(body.rows[i])?"addClass":"removeClass")](body.rows[i], this.rowSelectedClass);
body.appendChild(rows[i]);
}
},
renderSelections: function(){
// summary
// Render all selected objects using CSS.
var body=this.domNode.tBodies[0];
for(var i=0; i<body.rows.length; i++){
dojo.html[(this.isRowSelected(body.rows[i])?"addClass":"removeClass")](body.rows[i], this.rowSelectedClass);
}
},
// widget lifetime handlers
initialize: function(){
// summary
// Initializes the widget.
var self=this;
// connect up binding listeners here.
dojo.event.connect(this.store, "onSetData", function(){
self.store.forEach(function(element){
element.isSelected = false;
});
self.isInitialized=false;
var body = self.domNode.tBodies[0];
if(body){
while(body.childNodes.length>0){
body.removeChild(body.childNodes[0]);
}
}
self.render();
});
dojo.event.connect(this.store, "onClearData", function(){
self.isInitialized = false;
self.render();
});
dojo.event.connect(this.store, "onAddData", function(addedObject){
var row=self.createRow(addedObject);
self.domNode.tBodies[0].appendChild(row);
self.render();
});
dojo.event.connect(this.store, "onAddDataRange", function(arr){
for(var i=0; i<arr.length; i++){
arr[i].isSelected=false;
var row=self.createRow(arr[i]);
self.domNode.tBodies[0].appendChild(row);
};
self.render();
});
dojo.event.connect(this.store, "onRemoveData", function(removedObject){
var rows = self.domNode.tBodies[0].rows;
for(var i=0; i<rows.length; i++){
if(self.getDataByRow(rows[i]) == removedObject.src){
rows[i].parentNode.removeChild(rows[i]);
break;
}
}
self.render();
});
dojo.event.connect(this.store, "onUpdateField", function(obj, fieldPath, val){
var row = self.getRow(obj);
var idx = self.getColumnIndex(fieldPath);
if(row && row.cells[idx] && self.columns[idx]){
self.fillCell(row.cells[idx], self.columns[idx], val);
}
});
},
postCreate: function(){
// summary
// finish widget initialization.
this.store.keyField = this.valueField;
if(this.domNode){
// start by making sure domNode is a table element;
if(this.domNode.nodeName.toLowerCase() != "table"){
}
// see if there is columns set up already
if(this.domNode.getElementsByTagName("thead")[0]){
var head=this.domNode.getElementsByTagName("thead")[0];
if(this.headClass.length > 0){
head.className = this.headClass;
}
dojo.html.disableSelection(this.domNode);
this.parseMetadata(head);
var header="td";
if(head.getElementsByTagName(header).length==0){
header="th";
}
var headers = head.getElementsByTagName(header);
for(var i=0; i<headers.length; i++){
if(!this.columns[i].noSort){
dojo.event.connect(headers[i], "onclick", this, "onSort");
}
}
} else {
this.domNode.appendChild(document.createElement("thead"));
}
// if the table doesn't have a tbody already, add one and grab a reference to it
if (this.domNode.tBodies.length < 1) {
var body = document.createElement("tbody");
this.domNode.appendChild(body);
} else {
var body = this.domNode.tBodies[0];
}
if (this.tbodyClass.length > 0){
body.className = this.tbodyClass;
}
dojo.event.connect(body, "onclick", this, "onSelect");
this.parseData(body);
}
}
});