/*
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
version: 2.6.0
*/
/**
* Mechanism to execute a series of callbacks in a non-blocking queue. Each callback is executed via setTimout unless configured with a negative timeout, in which case it is run in blocking mode in the same execution thread as the previous callback. Callbacks can be function references or object literals with the following keys:
*
*
method - {Function} REQUIRED the callback function.
*
scope - {Object} the scope from which to execute the callback. Default is the global window scope.
*
argument - {Array} parameters to be passed to method as individual arguments.
*
timeout - {number} millisecond delay to wait after previous callback completion before executing this callback. Negative values cause immediate blocking execution. Default 0.
*
until - {Function} boolean function executed before each iteration. Return true to indicate completion and proceed to the next callback.
*
iterations - {Number} number of times to execute the callback before proceeding to the next callback in the chain. Incompatible with until.
*
*
* @namespace YAHOO.util
* @class Chain
* @constructor
* @param callback* {Function|Object} Any number of callbacks to initialize the queue
*/
YAHOO.util.Chain = function () {
/**
* The callback queue
* @property q
* @type {Array}
* @private
*/
this.q = [].slice.call(arguments);
/**
* Event fired when the callback queue is emptied via execution (not via
* a call to chain.stop().
* @event end
*/
this.createEvent('end');
};
YAHOO.util.Chain.prototype = {
/**
* Timeout id used to pause or stop execution and indicate the execution state of the Chain. 0 indicates paused or stopped, -1 indicates blocking execution, and any positive number indicates non-blocking execution.
* @property id
* @type {number}
* @private
*/
id : 0,
/**
* Begin executing the chain, or resume execution from the last paused position.
* @method run
* @return {Chain} the Chain instance
*/
run : function () {
// Grab the first callback in the queue
var c = this.q[0],
fn;
// If there is no callback in the queue or the Chain is currently
// in an execution mode, return
if (!c) {
this.fireEvent('end');
return this;
} else if (this.id) {
return this;
}
fn = c.method || c;
if (typeof fn === 'function') {
var o = c.scope || {},
args = c.argument || [],
ms = c.timeout || 0,
me = this;
if (!(args instanceof Array)) {
args = [args];
}
// Execute immediately if the callback timeout is negative.
if (ms < 0) {
this.id = ms;
if (c.until) {
for (;!c.until();) {
// Execute the callback from scope, with argument
fn.apply(o,args);
}
} else if (c.iterations) {
for (;c.iterations-- > 0;) {
fn.apply(o,args);
}
} else {
fn.apply(o,args);
}
this.q.shift();
this.id = 0;
return this.run();
} else {
// If the until condition is set, check if we're done
if (c.until) {
if (c.until()) {
// Shift this callback from the queue and execute the next
// callback
this.q.shift();
return this.run();
}
// Otherwise if either iterations is not set or we're
// executing the last iteration, shift callback from the queue
} else if (!c.iterations || !--c.iterations) {
this.q.shift();
}
// Otherwise set to execute after the configured timeout
this.id = setTimeout(function () {
// Execute the callback from scope, with argument
fn.apply(o,args);
// Check if the Chain was not paused from inside the callback
if (me.id) {
// Indicate ready to run state
me.id = 0;
// Start the fun all over again
me.run();
}
},ms);
}
}
return this;
},
/**
* Add a callback to the end of the queue
* @method add
* @param c {Function|Object} the callback function ref or object literal
* @return {Chain} the Chain instance
*/
add : function (c) {
this.q.push(c);
return this;
},
/**
* Pause the execution of the Chain after the current execution of the
* current callback completes. If called interstitially, clears the
* timeout for the pending callback. Paused Chains can be restarted with
* chain.run()
* @method pause
* @return {Chain} the Chain instance
*/
pause: function () {
clearTimeout(this.id);
this.id = 0;
return this;
},
/**
* Stop and clear the Chain's queue after the current execution of the
* current callback completes.
* @method stop
* @return {Chain} the Chain instance
*/
stop : function () {
this.pause();
this.q = [];
return this;
}
};
YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
/**
* The ColumnSet class defines and manages a DataTable's Columns,
* including nested hierarchies and access to individual Column instances.
*
* @namespace YAHOO.widget
* @class ColumnSet
* @uses YAHOO.util.EventProvider
* @constructor
* @param aDefinitions {Object[]} Array of object literals that define cells in
* the THEAD.
*/
YAHOO.widget.ColumnSet = function(aDefinitions) {
this._sId = "yui-cs" + YAHOO.widget.ColumnSet._nCount;
// First clone the defs
aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions);
this._init(aDefinitions);
YAHOO.widget.ColumnSet._nCount++;
YAHOO.log("ColumnSet initialized", "info", this.toString());
};
/////////////////////////////////////////////////////////////////////////////
//
// Private member variables
//
/////////////////////////////////////////////////////////////////////////////
/**
* Internal class variable to index multiple ColumnSet instances.
*
* @property ColumnSet._nCount
* @type Number
* @private
* @static
*/
YAHOO.widget.ColumnSet._nCount = 0;
YAHOO.widget.ColumnSet.prototype = {
/**
* Unique instance name.
*
* @property _sId
* @type String
* @private
*/
_sId : null,
/**
* Array of object literal Column definitions passed to the constructor.
*
* @property _aDefinitions
* @type Object[]
* @private
*/
_aDefinitions : null,
/////////////////////////////////////////////////////////////////////////////
//
// Public member variables
//
/////////////////////////////////////////////////////////////////////////////
/**
* Top-down tree representation of Column hierarchy.
*
* @property tree
* @type YAHOO.widget.Column[]
*/
tree : null,
/**
* Flattened representation of all Columns.
*
* @property flat
* @type YAHOO.widget.Column[]
* @default []
*/
flat : null,
/**
* Array of Columns that map one-to-one to a table column.
*
* @property keys
* @type YAHOO.widget.Column[]
* @default []
*/
keys : null,
/**
* ID index of nested parent hierarchies for HEADERS accessibility attribute.
*
* @property headers
* @type String[]
* @default []
*/
headers : null,
/////////////////////////////////////////////////////////////////////////////
//
// Private methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Initializes ColumnSet instance with data from Column definitions.
*
* @method _init
* @param aDefinitions {Object[]} Array of object literals that define cells in
* the THEAD .
* @private
*/
_init : function(aDefinitions) {
// DOM tree representation of all Columns
var tree = [];
// Flat representation of all Columns
var flat = [];
// Flat representation of only Columns that are meant to display data
var keys = [];
// Array of HEADERS attribute values for all keys in the "keys" array
var headers = [];
// Tracks current node list depth being tracked
var nodeDepth = -1;
// Internal recursive function to define Column instances
var parseColumns = function(nodeList, parent) {
// One level down
nodeDepth++;
// Create corresponding tree node if not already there for this depth
if(!tree[nodeDepth]) {
tree[nodeDepth] = [];
}
// Parse each node at this depth for attributes and any children
for(var j=0; j maxRowDepth) {
maxRowDepth = tmpRowDepth;
}
}
}
};
// Count max row depth for each row
for(var m=0; m-1; i--) {
if(allColumns[i]._sId === column) {
return allColumns[i];
}
}
}
return null;
},
/**
* Returns Column instance with given key or ColumnSet key index.
*
* @method getColumn
* @param column {String | Number} Column key or ColumnSet key index.
* @return {YAHOO.widget.Column} Column instance.
*/
getColumn : function(column) {
if(YAHOO.lang.isNumber(column) && this.keys[column]) {
return this.keys[column];
}
else if(YAHOO.lang.isString(column)) {
var allColumns = this.flat;
var aColumns = [];
for(var i=0; i 1) {
return aColumns;
}
}
return null;
},
/**
* Public accessor returns array of given Column's desendants (if any), including itself.
*
* @method getDescendants
* @parem {YAHOO.widget.Column} Column instance.
* @return {Array} Array including the Column itself and all descendants (if any).
*/
getDescendants : function(oColumn) {
var oSelf = this;
var allDescendants = [];
var i;
// Recursive function to loop thru all children
var parse = function(oParent) {
allDescendants.push(oParent);
// This Column has children
if(oParent.children) {
for(i=0; i b) {
return (desc) ? -1 : 1;
}
else {
return 0;
}
}
};
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
/**
* ColumnDD subclasses DragDrop to support rearrangeable Columns.
*
* @namespace YAHOO.util
* @class ColumnDD
* @extends YAHOO.util.DDProxy
* @constructor
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param elTh {HTMLElement} TH element reference.
* @param elTarget {HTMLElement} Drag target element.
*/
YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
if(oDataTable && oColumn && elTh && elTarget) {
this.datatable = oDataTable;
this.table = oDataTable.getTableEl();
this.column = oColumn;
this.headCell = elTh;
this.pointer = elTarget;
this.newIndex = null;
this.init(elTh);
this.initFrame(); // Needed for DDProxy
this.invalidHandleTypes = {};
// Set top/bottom padding to account for children of nested columns
this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
YAHOO.util.Event.on(window, 'resize', function() {
this.initConstraints();
}, this, true);
}
else {
YAHOO.log("Column dragdrop could not be created","warn",oDataTable.toString());
}
};
if(YAHOO.util.DDProxy) {
YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
initConstraints: function() {
//Get the top, right, bottom and left positions
var region = YAHOO.util.Dom.getRegion(this.table),
//Get the element we are working on
el = this.getEl(),
//Get the xy position of it
xy = YAHOO.util.Dom.getXY(el),
//Get the width and height
width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
//Set left to x minus left
left = ((xy[0] - region.left) + 15), //Buffer of 15px
//Set right to right minus x minus width
right = ((region.right - xy[0] - width) + 15);
//Set the constraints based on the above calculations
this.setXConstraint(left, right);
this.setYConstraint(10, 10);
},
_resizeProxy: function() {
this.constructor.superclass._resizeProxy.apply(this, arguments);
var dragEl = this.getDragEl(),
el = this.getEl();
YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
var xy = YAHOO.util.Dom.getXY(el);
YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
YAHOO.util.Dom.setXY(this.dragEl, xy);
},
onMouseDown: function() {
this.initConstraints();
this.resetConstraints();
},
clickValidator: function(e) {
if(!this.column.hidden) {
var target = YAHOO.util.Event.getTarget(e);
return ( this.isValidHandleChild(target) &&
(this.id == this.handleElId ||
this.DDM.handleWasClicked(target, this.id)) );
}
},
onDragOver: function(ev, id) {
// Validate target as a Column
var target = this.datatable.getColumn(id);
if(target) {
// Validate target as a top-level parent
var targetIndex = target.getTreeIndex();
while((targetIndex === null) && target.getParent()) {
target = target.getParent();
targetIndex = target.getTreeIndex();
}
if(targetIndex !== null) {
// Are we placing to left or right of target?
var elTarget = target.getThEl();
var newIndex = targetIndex;
var mouseX = YAHOO.util.Event.getPageX(ev),
targetX = YAHOO.util.Dom.getX(elTarget),
midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
currentIndex = this.column.getTreeIndex();
if (mouseX < midX) {
YAHOO.util.Dom.setX(this.pointer, targetX);
} else {
var targetWidth = parseInt(elTarget.offsetWidth, 10);
YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
newIndex++;
}
if (targetIndex > currentIndex) {
newIndex--;
}
if(newIndex < 0) {
newIndex = 0;
}
else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
newIndex = this.datatable.getColumnSet().tree[0].length;
}
this.newIndex = newIndex;
}
}
},
onDragDrop: function() {
this.datatable.reorderColumn(this.column, this.newIndex);
},
endDrag: function() {
this.newIndex = null;
YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
}
});
}
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
/**
* ColumnResizer subclasses DragDrop to support resizeable Columns.
*
* @namespace YAHOO.util
* @class ColumnResizer
* @extends YAHOO.util.DDProxy
* @constructor
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param elTh {HTMLElement} TH element reference.
* @param sHandleElId {String} DOM ID of the handle element that causes the resize.
* @param elProxy {HTMLElement} Resizer proxy element.
*/
YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
if(oDataTable && oColumn && elTh && sHandleId) {
this.datatable = oDataTable;
this.column = oColumn;
this.headCell = elTh;
this.headCellLiner = oColumn.getThLinerEl();
this.resizerLiner = elTh.firstChild;
this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
this.initFrame(); // Needed for proxy
this.resetResizerEl(); // Needed when rowspan > 0
// Set right padding for bug 1858462
this.setPadding(0, 1, 0, 0);
}
else {
YAHOO.log("Column resizer could not be created","warn",oDataTable.toString());
}
};
if(YAHOO.util.DD) {
YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
/////////////////////////////////////////////////////////////////////////////
//
// Public methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Resets resizer element.
*
* @method resetResizerEl
*/
resetResizerEl : function() {
var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
resizerStyle.left = "auto";
resizerStyle.right = 0;
resizerStyle.top = "auto";
resizerStyle.bottom = 0;
resizerStyle.height = this.headCell.offsetHeight+"px";
},
/////////////////////////////////////////////////////////////////////////////
//
// Public DOM event handlers
//
/////////////////////////////////////////////////////////////////////////////
/**
* Handles mouseup events on the Column resizer.
*
* @method onMouseUp
* @param e {string} The mouseup event
*/
onMouseUp : function(e) {
// Reset height of all resizer els in case TH's have changed height
var allKeys = this.datatable.getColumnSet().keys,
col;
for(var i=0, len=allKeys.length; i YAHOO.util.Dom.getX(this.headCellLiner)) {
var offsetX = newX - this.startX;
var newWidth = this.startWidth + offsetX - this.nLinerPadding;
if(newWidth > 0) {
this.datatable.setColumnWidth(this.column, newWidth);
}
}
}
});
}
/////////////////////////////////////////////////////////////////////////////
//
// Deprecated
//
/////////////////////////////////////////////////////////////////////////////
/**
* @property editorOptions
* @deprecated Pass configs directly to CellEditor constructor.
*/
(function () {
var lang = YAHOO.lang,
util = YAHOO.util,
widget = YAHOO.widget,
Dom = util.Dom,
Ev = util.Event,
DT = widget.DataTable;
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
/**
* A RecordSet defines and manages a set of Records.
*
* @namespace YAHOO.widget
* @class RecordSet
* @param data {Object || Object[]} An object literal or an array of data.
* @constructor
*/
YAHOO.widget.RecordSet = function(data) {
// Internal variables
this._sId = "yui-rs" + widget.RecordSet._nCount;
widget.RecordSet._nCount++;
this._records = [];
//this._length = 0;
if(data) {
if(lang.isArray(data)) {
this.addRecords(data);
}
else if(lang.isObject(data)) {
this.addRecord(data);
}
}
YAHOO.log("RecordSet initialized", "info", this.toString());
};
var RS = widget.RecordSet;
/**
* Internal class variable to name multiple Recordset instances.
*
* @property RecordSet._nCount
* @type Number
* @private
* @static
*/
RS._nCount = 0;
RS.prototype = {
/////////////////////////////////////////////////////////////////////////////
//
// Private member variables
//
/////////////////////////////////////////////////////////////////////////////
/**
* Unique String identifier assigned at instantiation.
*
* @property _sId
* @type String
* @private
*/
_sId : null,
/**
* Internal counter of how many Records are in the RecordSet.
*
* @property _length
* @type Number
* @private
* @deprecated No longer used
*/
//_length : null,
/////////////////////////////////////////////////////////////////////////////
//
// Private methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Adds one Record to the RecordSet at the given index. If index is null,
* then adds the Record to the end of the RecordSet.
*
* @method _addRecord
* @param oData {Object} An object literal of data.
* @param index {Number} (optional) Position index.
* @return {YAHOO.widget.Record} A Record instance.
* @private
*/
_addRecord : function(oData, index) {
var oRecord = new YAHOO.widget.Record(oData);
if(YAHOO.lang.isNumber(index) && (index > -1)) {
this._records.splice(index,0,oRecord);
}
else {
//index = this.getLength();
//this._records[index] = oRecord;
this._records[this._records.length] = oRecord;
}
//this._length++;
return oRecord;
},
/**
* Sets/replaces one Record to the RecordSet at the given index. Existing
* Records with higher indexes are not shifted. If no index specified, the
* Record is added to the end of the RecordSet.
*
* @method _setRecord
* @param oData {Object} An object literal of data.
* @param index {Number} (optional) Position index.
* @return {YAHOO.widget.Record} A Record instance.
* @private
*/
_setRecord : function(oData, index) {
if (!lang.isNumber(index) || index < 0) {
index = this._records.length;
}
return (this._records[index] = new widget.Record(oData));
/*
if(lang.isNumber(index) && (index > -1)) {
this._records[index] = oRecord;
if((index+1) > this.getLength()) {
this._length = index+1;
}
}
else {
this._records[this.getLength()] = oRecord;
this._length++;
}
return oRecord;
*/
},
/**
* Deletes Records from the RecordSet at the given index. If range is null,
* then only one Record is deleted.
*
* @method _deleteRecord
* @param index {Number} Position index.
* @param range {Number} (optional) How many Records to delete
* @private
*/
_deleteRecord : function(index, range) {
if(!lang.isNumber(range) || (range < 0)) {
range = 1;
}
this._records.splice(index, range);
//this._length = this._length - range;
},
/////////////////////////////////////////////////////////////////////////////
//
// Public methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Returns unique name of the RecordSet instance.
*
* @method getId
* @return {String} Unique name of the RecordSet instance.
*/
getId : function() {
return this._sId;
},
/**
* Public accessor to the unique name of the RecordSet instance.
*
* @method toString
* @return {String} Unique name of the RecordSet instance.
*/
toString : function() {
return "RecordSet instance " + this._sId;
},
/**
* Returns the number of Records held in the RecordSet.
*
* @method getLength
* @return {Number} Number of records in the RecordSet.
*/
getLength : function() {
//return this._length;
return this._records.length;
},
/**
* Returns Record by ID or RecordSet position index.
*
* @method getRecord
* @param record {YAHOO.widget.Record | Number | String} Record instance,
* RecordSet position index, or Record ID.
* @return {YAHOO.widget.Record} Record object.
*/
getRecord : function(record) {
var i;
if(record instanceof widget.Record) {
for(i=0; i -1) && (record < this.getLength())) {
return this._records[record];
}
}
else if(lang.isString(record)) {
for(i=0; i-1; i--) {
if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
return i;
}
}
}
return null;
},
/**
* Adds one Record to the RecordSet at the given index. If index is null,
* then adds the Record to the end of the RecordSet.
*
* @method addRecord
* @param oData {Object} An object literal of data.
* @param index {Number} (optional) Position index.
* @return {YAHOO.widget.Record} A Record instance.
*/
addRecord : function(oData, index) {
if(lang.isObject(oData)) {
var oRecord = this._addRecord(oData, index);
this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
YAHOO.log("Added Record at index " + index +
" with data " + lang.dump(oData), "info", this.toString());
return oRecord;
}
else {
YAHOO.log("Could not add Record with data" +
lang.dump(oData), "info", this.toString());
return null;
}
},
/**
* Adds multiple Records at once to the RecordSet at the given index with the
* given object literal data. If index is null, then the new Records are
* added to the end of the RecordSet.
*
* @method addRecords
* @param aData {Object[]} An object literal data or an array of data object literals.
* @param index {Number} (optional) Position index.
* @return {YAHOO.widget.Record[]} An array of Record instances.
*/
addRecords : function(aData, index) {
if(lang.isArray(aData)) {
var newRecords = [],
idx,i,len;
index = lang.isNumber(index) ? index : this._records.length;
idx = index;
// Can't go backwards bc we need to preserve order
for(i=0,len=aData.length; i 1 ? added : added[0];
},
/**
* Updates given Record with given data.
*
* @method updateRecord
* @param record {YAHOO.widget.Record | Number | String} A Record instance,
* a RecordSet position index, or a Record ID.
* @param oData {Object} Object literal of new data.
* @return {YAHOO.widget.Record} Updated Record, or null.
*/
updateRecord : function(record, oData) {
var oRecord = this.getRecord(record);
if(oRecord && lang.isObject(oData)) {
// Copy data from the Record for the event that gets fired later
var oldData = {};
for(var key in oRecord._oData) {
if(lang.hasOwnProperty(oRecord._oData, key)) {
oldData[key] = oRecord._oData[key];
}
}
oRecord._oData = oData;
this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
YAHOO.log("Record at index " + this.getRecordIndex(oRecord) +
" updated with data " + lang.dump(oData), "info", this.toString());
return oRecord;
}
else {
YAHOO.log("Could not update Record " + record, "error", this.toString());
return null;
}
},
/**
* @method updateKey
* @deprecated Use updateRecordValue
*/
updateKey : function(record, sKey, oData) {
this.updateRecordValue(record, sKey, oData);
},
/**
* Sets given Record at given key to given data.
*
* @method updateRecordValue
* @param record {YAHOO.widget.Record | Number | String} A Record instance,
* a RecordSet position index, or a Record ID.
* @param sKey {String} Key name.
* @param oData {Object} New data.
*/
updateRecordValue : function(record, sKey, oData) {
var oRecord = this.getRecord(record);
if(oRecord) {
var oldData = null;
var keyValue = oRecord._oData[sKey];
// Copy data from the Record for the event that gets fired later
if(keyValue && lang.isObject(keyValue)) {
oldData = {};
for(var key in keyValue) {
if(lang.hasOwnProperty(keyValue, key)) {
oldData[key] = keyValue[key];
}
}
}
// Copy by value
else {
oldData = keyValue;
}
oRecord._oData[sKey] = oData;
this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
YAHOO.log("Key \"" + sKey +
"\" for Record at index " + this.getRecordIndex(oRecord) +
" updated to \"" + lang.dump(oData) + "\"", "info", this.toString());
}
else {
YAHOO.log("Could not update key " + sKey + " for Record " + record, "error", this.toString());
}
},
/**
* Replaces all Records in RecordSet with new object literal data.
*
* @method replaceRecords
* @param data {Object || Object[]} An object literal of data or an array of
* data object literals.
* @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
* an array of Records.
*/
replaceRecords : function(data) {
this.reset();
return this.addRecords(data);
},
/**
* Sorts all Records by given function. Records keep their unique IDs but will
* have new RecordSet position indexes.
*
* @method sortRecords
* @param fnSort {Function} Reference to a sort function.
* @param desc {Boolean} True if sort direction is descending, false if sort
* direction is ascending.
* @return {YAHOO.widget.Record[]} Sorted array of Records.
*/
sortRecords : function(fnSort, desc) {
return this._records.sort(function(a, b) {return fnSort(a, b, desc);});
},
/**
* Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
*
* @method reverseRecords
* @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
*/
reverseRecords : function() {
return this._records.reverse();
},
/**
* Removes the Record at the given position index from the RecordSet. If a range
* is also provided, removes that many Records, starting from the index. Length
* of RecordSet is correspondingly shortened.
*
* @method deleteRecord
* @param index {Number} Record's RecordSet position index.
* @param range {Number} (optional) How many Records to delete.
* @return {Object} A copy of the data held by the deleted Record.
*/
deleteRecord : function(index) {
if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
// Copy data from the Record for the event that gets fired later
var oData = widget.DataTable._cloneObject(this.getRecord(index).getData());
this._deleteRecord(index);
this.fireEvent("recordDeleteEvent",{data:oData,index:index});
YAHOO.log("Record deleted at index " + index +
" and containing data " + lang.dump(oData), "info", this.toString());
return oData;
}
else {
YAHOO.log("Could not delete Record at index " + index, "error", this.toString());
return null;
}
},
/**
* Removes the Record at the given position index from the RecordSet. If a range
* is also provided, removes that many Records, starting from the index. Length
* of RecordSet is correspondingly shortened.
*
* @method deleteRecords
* @param index {Number} Record's RecordSet position index.
* @param range {Number} (optional) How many Records to delete.
* @return {Object[]} An array of copies of the data held by the deleted Records.
*/
deleteRecords : function(index, range) {
if(!lang.isNumber(range)) {
range = 1;
}
if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
var recordsToDelete = this.getRecords(index, range);
// Copy data from each Record for the event that gets fired later
var deletedData = [];
for(var i=0; i" + sValue + "";
//}
},
/**
* Formats a CHECKBOX element.
*
* @method DataTable.formatCheckbox
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object | Boolean} Data value for the cell. Can be a simple
* Boolean to indicate whether checkbox is checked or not. Can be object literal
* {checked:bBoolean, label:sLabel}. Other forms of oData require a custom
* formatter.
* @static
*/
formatCheckbox : function(el, oRecord, oColumn, oData) {
var bChecked = oData;
bChecked = (bChecked) ? " checked=\"checked\"" : "";
el.innerHTML = "";
},
/**
* Formats currency. Default unit is USD.
*
* @method DataTable.formatCurrency
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Number} Data value for the cell.
* @static
*/
formatCurrency : function(el, oRecord, oColumn, oData) {
el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || this.get("currencyOptions"));
},
/**
* Formats JavaScript Dates.
*
* @method DataTable.formatDate
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} Data value for the cell, or null.
* @static
*/
formatDate : function(el, oRecord, oColumn, oData) {
var oConfig = oColumn.dateOptions || this.get("dateOptions");
el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
},
/**
* Formats SELECT elements.
*
* @method DataTable.formatDropdown
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} Data value for the cell, or null.
* @static
*/
formatDropdown : function(el, oRecord, oColumn, oData) {
var selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field);
var options = (lang.isArray(oColumn.dropdownOptions)) ?
oColumn.dropdownOptions : null;
var selectEl;
var collection = el.getElementsByTagName("select");
// Create the form element only once, so we can attach the onChange listener
if(collection.length === 0) {
// Create SELECT element
selectEl = document.createElement("select");
selectEl.className = DT.CLASS_DROPDOWN;
selectEl = el.appendChild(selectEl);
// Add event listener
Ev.addListener(selectEl,"change",this._onDropdownChange,this);
}
selectEl = collection[0];
// Update the form element
if(selectEl) {
// Clear out previous options
selectEl.innerHTML = "";
// We have options to populate
if(options) {
// Create OPTION elements
for(var i=0; i" + selectedValue + "";
}
}
else {
el.innerHTML = lang.isValue(oData) ? oData : "";
}
},
/**
* Formats emails.
*
* @method DataTable.formatEmail
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} Data value for the cell, or null.
* @static
*/
formatEmail : function(el, oRecord, oColumn, oData) {
if(lang.isString(oData)) {
el.innerHTML = "" + oData + "";
}
else {
el.innerHTML = lang.isValue(oData) ? oData : "";
}
},
/**
* Formats links.
*
* @method DataTable.formatLink
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} Data value for the cell, or null.
* @static
*/
formatLink : function(el, oRecord, oColumn, oData) {
if(lang.isString(oData)) {
el.innerHTML = "" + oData + "";
}
else {
el.innerHTML = lang.isValue(oData) ? oData : "";
}
},
/**
* Formats numbers.
*
* @method DataTable.formatNumber
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} Data value for the cell, or null.
* @static
*/
formatNumber : function(el, oRecord, oColumn, oData) {
el.innerHTML = util.Number.format(oData, oColumn.numberOptions || this.get("numberOptions"));
},
/**
* Formats INPUT TYPE=RADIO elements.
*
* @method DataTable.formatRadio
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} (Optional) Data value for the cell.
* @static
*/
formatRadio : function(el, oRecord, oColumn, oData) {
var bChecked = oData;
bChecked = (bChecked) ? " checked=\"checked\"" : "";
el.innerHTML = "";
},
/**
* Formats text strings.
*
* @method DataTable.formatText
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} (Optional) Data value for the cell.
* @static
*/
formatText : function(el, oRecord, oColumn, oData) {
var value = (lang.isValue(oRecord.getData(oColumn.field))) ?
oRecord.getData(oColumn.field) : "";
//TODO: move to util function
el.innerHTML = value.toString().replace(/&/g, "&").replace(//g, ">");
},
/**
* Formats TEXTAREA elements.
*
* @method DataTable.formatTextarea
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} (Optional) Data value for the cell.
* @static
*/
formatTextarea : function(el, oRecord, oColumn, oData) {
var value = (lang.isValue(oRecord.getData(oColumn.field))) ?
oRecord.getData(oColumn.field) : "";
var markup = "";
el.innerHTML = markup;
},
/**
* Formats INPUT TYPE=TEXT elements.
*
* @method DataTable.formatTextbox
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} (Optional) Data value for the cell.
* @static
*/
formatTextbox : function(el, oRecord, oColumn, oData) {
var value = (lang.isValue(oRecord.getData(oColumn.field))) ?
oRecord.getData(oColumn.field) : "";
var markup = "";
el.innerHTML = markup;
},
/**
* Default cell formatter
*
* @method DataTable.formatDefault
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} (Optional) Data value for the cell.
* @static
*/
formatDefault : function(el, oRecord, oColumn, oData) {
el.innerHTML = oData === undefined ||
oData === null ||
(typeof oData === 'number' && isNaN(oData)) ?
" " : oData.toString();
},
/**
* Validates data value to type Number, doing type conversion as
* necessary. A valid Number value is return, else null is returned
* if input value does not validate.
*
*
* @method DataTable.validateNumber
* @param oData {Object} Data to validate.
* @static
*/
validateNumber : function(oData) {
//Convert to number
var number = oData * 1;
// Validate
if(lang.isNumber(number)) {
return number;
}
else {
YAHOO.log("Could not validate data " + lang.dump(oData) + " to type Number", "warn", this.toString());
return undefined;
}
}
});
// Done in separate step so referenced functions are defined.
/**
* Cell formatting functions.
* @property DataTable.Formatter
* @type Object
* @static
*/
DT.Formatter = {
button : DT.formatButton,
checkbox : DT.formatCheckbox,
currency : DT.formatCurrency,
"date" : DT.formatDate,
dropdown : DT.formatDropdown,
email : DT.formatEmail,
link : DT.formatLink,
"number" : DT.formatNumber,
radio : DT.formatRadio,
text : DT.formatText,
textarea : DT.formatTextarea,
textbox : DT.formatTextbox,
defaultFormatter : DT.formatDefault
};
lang.extend(DT, util.Element, {
/////////////////////////////////////////////////////////////////////////////
//
// Superclass methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Implementation of Element's abstract method. Sets up config values.
*
* @method initAttributes
* @param oConfigs {Object} (Optional) Object literal definition of configuration values.
* @private
*/
initAttributes : function(oConfigs) {
oConfigs = oConfigs || {};
DT.superclass.initAttributes.call(this, oConfigs);
/**
* @attribute summary
* @description Value for the SUMMARY attribute.
* @type String
* @default ""
*/
this.setAttributeConfig("summary", {
value: "",
validator: lang.isString,
method: function(sSummary) {
if(this._elTable) {
this._elTable.summary = sSummary;
}
}
});
/**
* @attribute selectionMode
* @description Specifies row or cell selection mode. Accepts the following strings:
*
*
"standard"
*
Standard row selection with support for modifier keys to enable
* multiple selections.
*
*
"single"
*
Row selection with modifier keys disabled to not allow
* multiple selections.
*
*
"singlecell"
*
Cell selection with modifier keys disabled to not allow
* multiple selections.
*
*
"cellblock"
*
Cell selection with support for modifier keys to enable multiple
* selections in a block-fashion, like a spreadsheet.
*
*
"cellrange"
*
Cell selection with support for modifier keys to enable multiple
* selections in a range-fashion, like a calendar.
*
*
* @default "standard"
* @type String
*/
this.setAttributeConfig("selectionMode", {
value: "standard",
validator: lang.isString
});
/**
* @attribute sortedBy
* @description Object literal provides metadata for initial sort values if
* data will arrive pre-sorted:
*
*
sortedBy.key
*
{String} Key of sorted Column
*
sortedBy.dir
*
{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC
*
* @type Object | null
*/
this.setAttributeConfig("sortedBy", {
value: null,
// TODO: accepted array for nested sorts
validator: function(oNewSortedBy) {
if(oNewSortedBy) {
return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
}
else {
return (oNewSortedBy === null);
}
},
method: function(oNewSortedBy) {
// Stash the previous value
var oOldSortedBy = this.get("sortedBy");
// Workaround for bug 1827195
this._configs.sortedBy.value = oNewSortedBy;
// Remove ASC/DESC from TH
var oOldColumn,
nOldColumnKeyIndex,
oNewColumn,
nNewColumnKeyIndex;
if(this._elThead) {
if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
nOldColumnKeyIndex = oOldColumn.getKeyIndex();
// Remove previous UI from THEAD
var elOldTh = oOldColumn.getThEl();
Dom.removeClass(elOldTh, oOldSortedBy.dir);
this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
}
if(oNewSortedBy) {
oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
nNewColumnKeyIndex = oNewColumn.getKeyIndex();
// Update THEAD with new UI
var elNewTh = oNewColumn.getThEl();
// Backward compatibility
if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") || (oNewSortedBy.dir == "desc"))) {
var newClass = (oNewSortedBy.dir == "desc") ?
DT.CLASS_DESC :
DT.CLASS_ASC;
Dom.addClass(elNewTh, newClass);
}
else {
var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
Dom.addClass(elNewTh, sortClass);
}
this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
}
}
if(this._elTbody) {
// Update TBODY UI
this._elTbody.style.display = "none";
var allRows = this._elTbody.rows,
allCells;
for(var i=allRows.length-1; i>-1; i--) {
allCells = allRows[i].childNodes;
if(allCells[nOldColumnKeyIndex]) {
Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
}
if(allCells[nNewColumnKeyIndex]) {
Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
}
}
this._elTbody.style.display = "";
}
this._clearTrTemplateEl();
}
});
/**
* @attribute paginator
* @description An instance of YAHOO.widget.Paginator.
* @default null
* @type {Object|YAHOO.widget.Paginator}
*/
this.setAttributeConfig("paginator", {
value : null,
validator : function (val) {
return val === null || val instanceof widget.Paginator;
},
method : function () { this._updatePaginator.apply(this,arguments); }
});
/**
* @attribute caption
* @description Value for the CAPTION element. NB: Not supported in
* ScrollingDataTable.
* @type String
*/
this.setAttributeConfig("caption", {
value: null,
validator: lang.isString,
method: function(sCaption) {
this._initCaptionEl(sCaption);
}
});
/**
* @attribute draggableColumns
* @description True if Columns are draggable to reorder, false otherwise.
* The Drag & Drop Utility is required to enable this feature. Only top-level
* and non-nested Columns are draggable. Write once.
* @default false
* @type Boolean
*/
this.setAttributeConfig("draggableColumns", {
value: false,
validator: lang.isBoolean,
method: function(oParam) {
if(this._elThead) {
if(oParam) {
this._initDraggableColumns();
}
else {
this._destroyDraggableColumns();
}
}
}
});
/**
* @attribute renderLoopSize
* @description A value greater than 0 enables DOM rendering of rows to be
* executed from a non-blocking timeout queue and sets how many rows to be
* rendered per timeout. Recommended for very large data sets.
* @type Number
* @default 0
*/
this.setAttributeConfig("renderLoopSize", {
value: 0,
validator: lang.isNumber
});
/**
* @attribute formatRow
* @description A function that accepts a TR element and its associated Record
* for custom formatting. The function must return TRUE in order to automatically
* continue formatting of child TD elements, else TD elements will not be
* automatically formatted.
* @type function
* @default null
*/
this.setAttributeConfig("formatRow", {
value: null,
validator: lang.isFunction
});
/**
* @attribute generateRequest
* @description A function that converts an object literal of desired DataTable
* states into a request value which is then passed to the DataSource's
* sendRequest method in order to retrieve data for those states. This
* function is passed an object literal of state data and a reference to the
* DataTable instance:
*
*
*
pagination
*
*
offsetRecord
*
{Number} Index of the first Record of the desired page
*
rowsPerPage
*
{Number} Number of rows per page
*
*
sortedBy
*
*
key
*
{String} Key of sorted Column
*
dir
*
{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC
*
*
self
*
The DataTable instance
*
*
* and by default returns a String of syntax:
* "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
* @type function
* @default HTMLFunction
*/
this.setAttributeConfig("generateRequest", {
value: function(oState, oSelf) {
// Set defaults
oState = oState || {pagination:null, sortedBy:null};
var sort = (oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey();
var dir = (oState.sortedBy && oState.sortedBy.dir === DT.CLASS_DESC) ? "desc" : "asc";
var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
// Build the request
return "sort=" + sort +
"&dir=" + dir +
"&startIndex=" + startIndex +
((results !== null) ? "&results=" + results : "");
},
validator: lang.isFunction
});
/**
* @attribute initialRequest
* @description Defines the initial request that gets sent to the DataSource
* during initialization. Value is ignored if initialLoad is set to any value
* other than true.
* @type MIXED
* @default null
*/
this.setAttributeConfig("initialRequest", {
value: null
});
/**
* @attribute initialLoad
* @description Determines whether or not to load data at instantiation. By
* default, will trigger a sendRequest() to the DataSource and pass in the
* request defined by initialRequest. If set to false, data will not load
* at instantiation. Alternatively, implementers who wish to work with a
* custom payload may pass in an object literal with the following values:
*
*
*
request (MIXED)
*
Request value.
*
*
argument (MIXED)
*
Custom data that will be passed through to the callback function.
*
*
*
* @type Boolean | Object
* @default true
*/
this.setAttributeConfig("initialLoad", {
value: true
});
/**
* @attribute dynamicData
* @description If true, sorting and pagination are relegated to the DataSource
* for handling, using the request returned by the "generateRequest" function.
* Each new DataSource response blows away all previous Records. False by default, so
* sorting and pagination will be handled directly on the client side, without
* causing any new requests for data from the DataSource.
* @type Boolean
* @default false
*/
this.setAttributeConfig("dynamicData", {
value: false,
validator: lang.isBoolean
});
/**
* @attribute MSG_EMPTY
* @description Message to display if DataTable has no data.
* @type String
* @default "No records found."
*/
this.setAttributeConfig("MSG_EMPTY", {
value: "No records found.",
validator: lang.isString
});
/**
* @attribute MSG_LOADING
* @description Message to display while DataTable is loading data.
* @type String
* @default "Loading..."
*/
this.setAttributeConfig("MSG_LOADING", {
value: "Loading...",
validator: lang.isString
});
/**
* @attribute MSG_ERROR
* @description Message to display while DataTable has data error.
* @type String
* @default "Data error."
*/
this.setAttributeConfig("MSG_ERROR", {
value: "Data error.",
validator: lang.isString
});
/**
* @attribute MSG_SORTASC
* @description Message to display in tooltip to sort Column in ascending order.
* @type String
* @default "Click to sort ascending"
*/
this.setAttributeConfig("MSG_SORTASC", {
value: "Click to sort ascending",
validator: lang.isString,
method: function(sParam) {
if(this._elThead) {
for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i aKeyIndexes[aKeyIndexes.length-1])) {
var i,
tmpCols = [];
// Remove COL
for(i=aKeyIndexes.length-1; i>-1; i--) {
tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
}
// Insert COL
var nextSibling = this._elColgroup.childNodes[newIndex] || null;
for(i=tmpCols.length-1; i>-1; i--) {
this._elColgroup.insertBefore(tmpCols[i], nextSibling);
}
}
},
/**
* Destroy's the DataTable THEAD element, if available.
*
* @method _destroyTheadEl
* @private
*/
_destroyTheadEl : function() {
var elThead = this._elThead;
if(elThead) {
var elTable = elThead.parentNode;
Ev.purgeElement(elThead, true);
this._destroyColumnHelpers();
elTable.removeChild(elThead);
this._elThead = null;
}
},
/**
* Initializes THEAD element.
*
* @method _initTheadEl
* @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
* @param {HTMLElement} Initialized THEAD element.
* @private
*/
_initTheadEl : function(elTable) {
elTable = elTable || this._elTable;
if(elTable) {
// Destroy previous
this._destroyTheadEl();
//TODO: append to DOM later for performance
var elThead = (this._elColgroup) ?
elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
elTable.appendChild(document.createElement("thead"));
// Set up DOM events for THEAD
Ev.addListener(elThead, "focus", this._onTheadFocus, this);
Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
Ev.addListener(elThead, "mouseover", this._onTableMouseover, this);
Ev.addListener(elThead, "mouseout", this._onTableMouseout, this);
Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
Ev.addListener(elThead, "click", this._onTheadClick, this);
// Since we can't listen for click and dblclick on the same element...
// Attach separately to THEAD and TBODY
///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
var oColumnSet = this._oColumnSet,
oColumn, i,j, l;
// Add TRs to the THEAD
var colTree = oColumnSet.tree;
var elTh;
for(i=0; i" + sLabel + "";
}
// Just display the label for non-sortable Columns
else {
elCellLabel.innerHTML = sLabel;
}
},
/**
* Disables DD from top-level Column TH elements.
*
* @method _destroyDraggableColumns
* @private
*/
_destroyDraggableColumns : function() {
var oColumn, elTh;
for(var i=0, len=this._oColumnSet.tree[0].length; i 1
// Create a separate resizer liner with position:relative
elThResizerLiner = elTh.appendChild(document.createElement("div"));
elThResizerLiner.className = DT.CLASS_RESIZERLINER;
// Move TH contents into the new resizer liner
elThResizerLiner.appendChild(elThLiner);
// Create the resizer
elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
elThResizer.className = DT.CLASS_RESIZER;
oColumn._elResizer = elThResizer;
// Create the resizer proxy, once globally
elResizerProxy = DT._initColumnResizerProxyEl();
oColumn._ddResizer = new YAHOO.util.ColumnResizer(
this, oColumn, elTh, elThResizer, elResizerProxy);
cancelClick = function(e) {
Ev.stopPropagation(e);
};
Ev.addListener(elThResizer,"click",cancelClick);
}
}
}
else {
YAHOO.log("Could not find DragDrop for resizeable Columns", "warn", this.toString());
}
},
/**
* Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
*
* @method _destroyColumnHelpers
* @private
*/
_destroyColumnHelpers : function() {
this._destroyDraggableColumns();
this._destroyResizeableColumns();
},
/**
* Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
*
* @method _initColumnHelpers
* @private
*/
_initColumnHelpers : function() {
if(this.get("draggableColumns")) {
this._initDraggableColumns();
}
this._initResizeableColumns();
},
/**
* Destroy's the DataTable TBODY element, if available.
*
* @method _destroyTbodyEl
* @private
*/
_destroyTbodyEl : function() {
var elTbody = this._elTbody;
if(elTbody) {
var elTable = elTbody.parentNode;
Ev.purgeElement(elTbody, true);
elTable.removeChild(elTbody);
this._elTbody = null;
}
},
/**
* Initializes TBODY element for data.
*
* @method _initTbodyEl
* @param elTable {HTMLElement} TABLE element into which to create TBODY .
* @private
*/
_initTbodyEl : function(elTable) {
if(elTable) {
// Destroy previous
this._destroyTbodyEl();
// Create TBODY
var elTbody = elTable.appendChild(document.createElement("tbody"));
elTbody.tabIndex = 0;
elTbody.className = DT.CLASS_DATA;
// Set up DOM events for TBODY
Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
Ev.addListener(elTbody, "mouseover", this._onTableMouseover, this);
Ev.addListener(elTbody, "mouseout", this._onTableMouseout, this);
Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
Ev.addListener(elTbody, "keypress", this._onTableKeypress, this);
Ev.addListener(elTbody, "click", this._onTbodyClick, this);
// Since we can't listen for click and dblclick on the same element...
// Attach separately to THEAD and TBODY
///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
// IE puts focus outline in the wrong place
if(ua.ie) {
elTbody.hideFocus=true;
}
this._elTbody = elTbody;
}
},
/**
* Destroy's the DataTable message TBODY element, if available.
*
* @method _destroyMsgTbodyEl
* @private
*/
_destroyMsgTbodyEl : function() {
var elMsgTbody = this._elMsgTbody;
if(elMsgTbody) {
var elTable = elMsgTbody.parentNode;
Ev.purgeElement(elMsgTbody, true);
elTable.removeChild(elMsgTbody);
this._elTbody = null;
}
},
/**
* Initializes TBODY element for messaging.
*
* @method _initMsgTbodyEl
* @param elTable {HTMLElement} TABLE element into which to create TBODY
* @private
*/
_initMsgTbodyEl : function(elTable) {
if(elTable) {
var elMsgTbody = document.createElement("tbody");
elMsgTbody.className = DT.CLASS_MESSAGE;
var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
this._elMsgTr = elMsgTr;
var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
elMsgTd.colSpan = this._oColumnSet.keys.length;
elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
this._elMsgTd = elMsgTd;
elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
elMsgLiner.className = DT.CLASS_LINER;
this._elMsgTbody = elMsgTbody;
}
},
/**
* Initialize internal event listeners
*
* @method _initEvents
* @private
*/
_initEvents : function () {
// Initialize Column sort
this._initColumnSort();
// Add the document level click listener
YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
// Paginator integration
this.subscribe("paginatorChange",function () {
this._handlePaginatorChange.apply(this,arguments);
});
this.subscribe("initEvent",function () {
this.renderPaginator();
});
// Initialize CellEditor integration
this._initCellEditing();
},
/**
* Initializes Column sorting.
*
* @method _initColumnSort
* @private
*/
_initColumnSort : function() {
this.subscribe("theadCellClickEvent", this.onEventSortColumn);
// Backward compatibility
var oSortedBy = this.get("sortedBy");
if(oSortedBy) {
if(oSortedBy.dir == "desc") {
this._configs.sortedBy.value.dir = DT.CLASS_DESC;
}
else if(oSortedBy.dir == "asc") {
this._configs.sortedBy.value.dir = DT.CLASS_ASC;
}
}
},
/**
* Initializes CellEditor integration.
*
* @method _initCellEditing
* @private
*/
_initCellEditing : function() {
this.subscribe("editorBlurEvent",function () {
this.onEditorBlurEvent.apply(this,arguments);
});
this.subscribe("editorBlockEvent",function () {
this.onEditorBlockEvent.apply(this,arguments);
});
this.subscribe("editorUnblockEvent",function () {
this.onEditorUnblockEvent.apply(this,arguments);
});
},
// DOM MUTATION FUNCTIONS
/**
* Retruns classnames to represent current Column states.
* @method _getColumnClassnames
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param aAddClasses {String[]} An array of additional classnames to add to the
* return value.
* @return {String} A String of classnames to be assigned to TH or TD elements
* for given Column.
* @private
*/
_getColumnClassNames : function (oColumn, aAddClasses) {
var allClasses;
// Add CSS classes
if(lang.isString(oColumn.className)) {
// Single custom class
allClasses = [oColumn.className];
}
else if(lang.isArray(oColumn.className)) {
// Array of custom classes
allClasses = oColumn.className;
}
else {
// no custom classes
allClasses = [];
}
// Hook for setting width with via dynamic style uses key since ID is too disposable
allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
// Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
var isSortedBy = this.get("sortedBy") || {};
// Sorted
if(oColumn.key === isSortedBy.key) {
allClasses[allClasses.length] = isSortedBy.dir || '';
}
// Hidden
if(oColumn.hidden) {
allClasses[allClasses.length] = DT.CLASS_HIDDEN;
}
// Selected
if(oColumn.selected) {
allClasses[allClasses.length] = DT.CLASS_SELECTED;
}
// Sortable
if(oColumn.sortable) {
allClasses[allClasses.length] = DT.CLASS_SORTABLE;
}
// Resizeable
if(oColumn.resizeable) {
allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
}
// Editable
if(oColumn.editor) {
allClasses[allClasses.length] = DT.CLASS_EDITABLE;
}
// Addtnl classes, including First/Last
if(aAddClasses) {
allClasses = allClasses.concat(aAddClasses);
}
return allClasses.join(' ');
},
/**
* Clears TR element template in response to any Column state change.
* @method _clearTrTemplateEl
* @private
*/
_clearTrTemplateEl : function () {
this._elTrTemplate = null;
},
/**
* Returns a new TR element template with TD elements classed with current
* Column states.
* @method _getTrTemplateEl
* @return {HTMLElement} A TR element to be cloned and added to the DOM.
* @private
*/
_getTrTemplateEl : function (oRecord, index) {
// Template is already available
if(this._elTrTemplate) {
return this._elTrTemplate;
}
// Template needs to be created
else {
var d = document,
tr = d.createElement('tr'),
td = d.createElement('td'),
div = d.createElement('div');
// Append the liner element
td.appendChild(div);
// Create TD elements into DOCUMENT FRAGMENT
var df = document.createDocumentFragment(),
allKeys = this._oColumnSet.keys,
elTd;
// Set state for each TD;
var aAddClasses;
for(var i=0, keysLen=allKeys.length; i -2) && (rowIndex < this._elTbody.rows.length)) {
// Cannot use tbody.deleteRow due to IE6 instability
//return this._elTbody.deleteRow(rowIndex);
return this._elTbody.removeChild(this.getTrEl(row));
}
else {
return null;
}
},
// CSS/STATE FUNCTIONS
/**
* Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
* of the DataTable page and updates internal tracker.
*
* @method _unsetFirstRow
* @private
*/
_unsetFirstRow : function() {
// Remove FIRST
if(this._sFirstTrId) {
Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
this._sFirstTrId = null;
}
},
/**
* Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
* of the DataTable page and updates internal tracker.
*
* @method _setFirstRow
* @private
*/
_setFirstRow : function() {
this._unsetFirstRow();
var elTr = this.getFirstTrEl();
if(elTr) {
// Set FIRST
Dom.addClass(elTr, DT.CLASS_FIRST);
this._sFirstTrId = elTr.id;
}
},
/**
* Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
* of the DataTable page and updates internal tracker.
*
* @method _unsetLastRow
* @private
*/
_unsetLastRow : function() {
// Unassign previous class
if(this._sLastTrId) {
Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
this._sLastTrId = null;
}
},
/**
* Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
* of the DataTable page and updates internal tracker.
*
* @method _setLastRow
* @private
*/
_setLastRow : function() {
this._unsetLastRow();
var elTr = this.getLastTrEl();
if(elTr) {
// Assign class
Dom.addClass(elTr, DT.CLASS_LAST);
this._sLastTrId = elTr.id;
}
},
/**
* Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
*
* @method _setRowStripes
* @param row {HTMLElement | String | Number} (optional) HTML TR element reference
* or string ID, or page row index of where to start striping.
* @param range {Number} (optional) If given, how many rows to stripe, otherwise
* stripe all the rows until the end.
* @private
*/
_setRowStripes : function(row, range) {
// Default values stripe all rows
var allRows = this._elTbody.rows,
nStartIndex = 0,
nEndIndex = allRows.length,
aOdds = [], nOddIdx = 0,
aEvens = [], nEvenIdx = 0;
// Stripe a subset
if((row !== null) && (row !== undefined)) {
// Validate given start row
var elStartRow = this.getTrEl(row);
if(elStartRow) {
nStartIndex = elStartRow.sectionRowIndex;
// Validate given range
if(lang.isNumber(range) && (range > 1)) {
nEndIndex = nStartIndex + range;
}
}
}
for(var i=nStartIndex; i0) || (allSelectedCells.length > 0)) {
var oColumnSet = this._oColumnSet,
el;
// Loop over each row
for(var i=0; i
*