YUI recommends YUI 3.
YUI 2 has been deprecated since 2011. This site acts as an archive for files and documentation.
(function () {
var lang = YAHOO.lang,
util = YAHOO.util,
widget = YAHOO.widget,
ua = YAHOO.env.ua,
Dom = util.Dom,
Ev = util.Event,
DS = util.DataSourceBase;
/**
* The DataTable widget provides a progressively enhanced DHTML control for
* displaying tabular data across A-grade browsers.
*
* @module datatable
* @requires yahoo, dom, event, element, datasource
* @optional dragdrop, dragdrop
* @title DataTable Widget
*/
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
/**
* DataTable class for the YUI DataTable widget.
*
* @namespace YAHOO.widget
* @class DataTable
* @extends YAHOO.util.Element
* @constructor
* @param elContainer {HTMLElement} Container element for the TABLE.
* @param aColumnDefs {Object[]} Array of object literal Column definitions.
* @param oDataSource {YAHOO.util.DataSource} DataSource instance.
* @param oConfigs {object} (optional) Object literal of configuration values.
*/
YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
var DT = widget.DataTable;
////////////////////////////////////////////////////////////////////////////
// Backward compatibility for SDT, but prevent infinite loops
if(oConfigs && oConfigs.scrollable) {
return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
}
////////////////////////////////////////////////////////////////////////////
// Initialization
// Internal vars
this._nIndex = DT._nCount;
this._sId = "yui-dt"+this._nIndex;
this._oChainRender = new YAHOO.util.Chain();
this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
// Initialize configs
this._initConfigs(oConfigs);
// Initialize DataSource
this._initDataSource(oDataSource);
if(!this._oDataSource) {
YAHOO.log("Could not instantiate DataTable due to an invalid DataSource", "error", this.toString());
return;
}
// Initialize ColumnSet
this._initColumnSet(aColumnDefs);
if(!this._oColumnSet) {
YAHOO.log("Could not instantiate DataTable due to an invalid ColumnSet", "error", this.toString());
return;
}
// Initialize RecordSet
this._initRecordSet();
if(!this._oRecordSet) {
}
// Initialize Attributes
DT.superclass.constructor.call(this, elContainer, this.configs);
// Initialize DOM elements
var okDom = this._initDomElements(elContainer);
if(!okDom) {
YAHOO.log("Could not instantiate DataTable due to an invalid DOM element", "error", this.toString());
return;
}
// Show message as soon as config is available
this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
////////////////////////////////////////////////////////////////////////////
// Once per instance
this._initEvents();
DT._nCount++;
DT._nCurrentCount++;
////////////////////////////////////////////////////////////////////////////
// Data integration
// Send a simple initial request
var oCallback = {
success : this.onDataReturnSetRows,
failure : this.onDataReturnSetRows,
scope : this,
argument: this.getState()
};
var initialLoad = this.get("initialLoad");
if(initialLoad === true) {
this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
}
// Do not send an initial request at all
else if(initialLoad === false) {
this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
}
// Send an initial request with a custom payload
else {
var oCustom = initialLoad || {};
oCallback.argument = oCustom.argument || {};
this._oDataSource.sendRequest(oCustom.request, oCallback);
}
};
var DT = widget.DataTable;
/////////////////////////////////////////////////////////////////////////////
//
// Public constants
//
/////////////////////////////////////////////////////////////////////////////
lang.augmentObject(DT, {
/**
* Class name assigned to outer DataTable container.
*
* @property DataTable.CLASS_DATATABLE
* @type String
* @static
* @final
* @default "yui-dt"
*/
CLASS_DATATABLE : "yui-dt",
/**
* Class name assigned to liner DIV elements.
*
* @property DataTable.CLASS_LINER
* @type String
* @static
* @final
* @default "yui-dt-liner"
*/
CLASS_LINER : "yui-dt-liner",
/**
* Class name assigned to display label elements.
*
* @property DataTable.CLASS_LABEL
* @type String
* @static
* @final
* @default "yui-dt-label"
*/
CLASS_LABEL : "yui-dt-label",
/**
* Class name assigned to messaging elements.
*
* @property DataTable.CLASS_MESSAGE
* @type String
* @static
* @final
* @default "yui-dt-message"
*/
CLASS_MESSAGE : "yui-dt-message",
/**
* Class name assigned to mask element when DataTable is disabled.
*
* @property DataTable.CLASS_MASK
* @type String
* @static
* @final
* @default "yui-dt-mask"
*/
CLASS_MASK : "yui-dt-mask",
/**
* Class name assigned to data elements.
*
* @property DataTable.CLASS_DATA
* @type String
* @static
* @final
* @default "yui-dt-data"
*/
CLASS_DATA : "yui-dt-data",
/**
* Class name assigned to Column drag target.
*
* @property DataTable.CLASS_COLTARGET
* @type String
* @static
* @final
* @default "yui-dt-coltarget"
*/
CLASS_COLTARGET : "yui-dt-coltarget",
/**
* Class name assigned to resizer handle elements.
*
* @property DataTable.CLASS_RESIZER
* @type String
* @static
* @final
* @default "yui-dt-resizer"
*/
CLASS_RESIZER : "yui-dt-resizer",
/**
* Class name assigned to resizer liner elements.
*
* @property DataTable.CLASS_RESIZERLINER
* @type String
* @static
* @final
* @default "yui-dt-resizerliner"
*/
CLASS_RESIZERLINER : "yui-dt-resizerliner",
/**
* Class name assigned to resizer proxy elements.
*
* @property DataTable.CLASS_RESIZERPROXY
* @type String
* @static
* @final
* @default "yui-dt-resizerproxy"
*/
CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
/**
* Class name assigned to CellEditor container elements.
*
* @property DataTable.CLASS_EDITOR
* @type String
* @static
* @final
* @default "yui-dt-editor"
*/
CLASS_EDITOR : "yui-dt-editor",
/**
* Class name assigned to paginator container elements.
*
* @property DataTable.CLASS_PAGINATOR
* @type String
* @static
* @final
* @default "yui-dt-paginator"
*/
CLASS_PAGINATOR : "yui-dt-paginator",
/**
* Class name assigned to page number indicators.
*
* @property DataTable.CLASS_PAGE
* @type String
* @static
* @final
* @default "yui-dt-page"
*/
CLASS_PAGE : "yui-dt-page",
/**
* Class name assigned to default indicators.
*
* @property DataTable.CLASS_DEFAULT
* @type String
* @static
* @final
* @default "yui-dt-default"
*/
CLASS_DEFAULT : "yui-dt-default",
/**
* Class name assigned to previous indicators.
*
* @property DataTable.CLASS_PREVIOUS
* @type String
* @static
* @final
* @default "yui-dt-previous"
*/
CLASS_PREVIOUS : "yui-dt-previous",
/**
* Class name assigned next indicators.
*
* @property DataTable.CLASS_NEXT
* @type String
* @static
* @final
* @default "yui-dt-next"
*/
CLASS_NEXT : "yui-dt-next",
/**
* Class name assigned to first elements.
*
* @property DataTable.CLASS_FIRST
* @type String
* @static
* @final
* @default "yui-dt-first"
*/
CLASS_FIRST : "yui-dt-first",
/**
* Class name assigned to last elements.
*
* @property DataTable.CLASS_LAST
* @type String
* @static
* @final
* @default "yui-dt-last"
*/
CLASS_LAST : "yui-dt-last",
/**
* Class name assigned to even elements.
*
* @property DataTable.CLASS_EVEN
* @type String
* @static
* @final
* @default "yui-dt-even"
*/
CLASS_EVEN : "yui-dt-even",
/**
* Class name assigned to odd elements.
*
* @property DataTable.CLASS_ODD
* @type String
* @static
* @final
* @default "yui-dt-odd"
*/
CLASS_ODD : "yui-dt-odd",
/**
* Class name assigned to selected elements.
*
* @property DataTable.CLASS_SELECTED
* @type String
* @static
* @final
* @default "yui-dt-selected"
*/
CLASS_SELECTED : "yui-dt-selected",
/**
* Class name assigned to highlighted elements.
*
* @property DataTable.CLASS_HIGHLIGHTED
* @type String
* @static
* @final
* @default "yui-dt-highlighted"
*/
CLASS_HIGHLIGHTED : "yui-dt-highlighted",
/**
* Class name assigned to hidden elements.
*
* @property DataTable.CLASS_HIDDEN
* @type String
* @static
* @final
* @default "yui-dt-hidden"
*/
CLASS_HIDDEN : "yui-dt-hidden",
/**
* Class name assigned to disabled elements.
*
* @property DataTable.CLASS_DISABLED
* @type String
* @static
* @final
* @default "yui-dt-disabled"
*/
CLASS_DISABLED : "yui-dt-disabled",
/**
* Class name assigned to empty indicators.
*
* @property DataTable.CLASS_EMPTY
* @type String
* @static
* @final
* @default "yui-dt-empty"
*/
CLASS_EMPTY : "yui-dt-empty",
/**
* Class name assigned to loading indicatorx.
*
* @property DataTable.CLASS_LOADING
* @type String
* @static
* @final
* @default "yui-dt-loading"
*/
CLASS_LOADING : "yui-dt-loading",
/**
* Class name assigned to error indicators.
*
* @property DataTable.CLASS_ERROR
* @type String
* @static
* @final
* @default "yui-dt-error"
*/
CLASS_ERROR : "yui-dt-error",
/**
* Class name assigned to editable elements.
*
* @property DataTable.CLASS_EDITABLE
* @type String
* @static
* @final
* @default "yui-dt-editable"
*/
CLASS_EDITABLE : "yui-dt-editable",
/**
* Class name assigned to draggable elements.
*
* @property DataTable.CLASS_DRAGGABLE
* @type String
* @static
* @final
* @default "yui-dt-draggable"
*/
CLASS_DRAGGABLE : "yui-dt-draggable",
/**
* Class name assigned to resizeable elements.
*
* @property DataTable.CLASS_RESIZEABLE
* @type String
* @static
* @final
* @default "yui-dt-resizeable"
*/
CLASS_RESIZEABLE : "yui-dt-resizeable",
/**
* Class name assigned to scrollable elements.
*
* @property DataTable.CLASS_SCROLLABLE
* @type String
* @static
* @final
* @default "yui-dt-scrollable"
*/
CLASS_SCROLLABLE : "yui-dt-scrollable",
/**
* Class name assigned to sortable elements.
*
* @property DataTable.CLASS_SORTABLE
* @type String
* @static
* @final
* @default "yui-dt-sortable"
*/
CLASS_SORTABLE : "yui-dt-sortable",
/**
* Class name assigned to ascending elements.
*
* @property DataTable.CLASS_ASC
* @type String
* @static
* @final
* @default "yui-dt-asc"
*/
CLASS_ASC : "yui-dt-asc",
/**
* Class name assigned to descending elements.
*
* @property DataTable.CLASS_DESC
* @type String
* @static
* @final
* @default "yui-dt-desc"
*/
CLASS_DESC : "yui-dt-desc",
/**
* Class name assigned to BUTTON elements and/or container elements.
*
* @property DataTable.CLASS_BUTTON
* @type String
* @static
* @final
* @default "yui-dt-button"
*/
CLASS_BUTTON : "yui-dt-button",
/**
* Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
*
* @property DataTable.CLASS_CHECKBOX
* @type String
* @static
* @final
* @default "yui-dt-checkbox"
*/
CLASS_CHECKBOX : "yui-dt-checkbox",
/**
* Class name assigned to SELECT elements and/or container elements.
*
* @property DataTable.CLASS_DROPDOWN
* @type String
* @static
* @final
* @default "yui-dt-dropdown"
*/
CLASS_DROPDOWN : "yui-dt-dropdown",
/**
* Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
*
* @property DataTable.CLASS_RADIO
* @type String
* @static
* @final
* @default "yui-dt-radio"
*/
CLASS_RADIO : "yui-dt-radio",
/////////////////////////////////////////////////////////////////////////
//
// Private static properties
//
/////////////////////////////////////////////////////////////////////////
/**
* Internal class variable for indexing multiple DataTable instances.
*
* @property DataTable._nCount
* @type Number
* @private
* @static
*/
_nCount : 0,
/**
* Internal class variable tracking current number of DataTable instances,
* so that certain class values can be reset when all instances are destroyed.
*
* @property DataTable._nCurrentCount
* @type Number
* @private
* @static
*/
_nCurrentCount : 0,
/**
* Reference to the STYLE node that is dynamically created and updated
* in order to manage Column widths.
*
* @property DataTable._elDynStyleNode
* @type HTMLElement
* @private
* @static
*/
_elDynStyleNode : null,
/**
* Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
*
* @property DataTable._bDynStylesFallback
* @type boolean
* @private
* @static
*/
_bDynStylesFallback : (ua.ie) ? true : false,
/**
* Object literal hash of Columns and their dynamically create style rules.
*
* @property DataTable._oDynStyles
* @type Object
* @private
* @static
*/
_oDynStyles : {},
/**
* Element reference to shared Column drag target.
*
* @property DataTable._elColumnDragTarget
* @type HTMLElement
* @private
* @static
*/
_elColumnDragTarget : null,
/**
* Element reference to shared Column resizer proxy.
*
* @property DataTable._elColumnResizerProxy
* @type HTMLElement
* @private
* @static
*/
_elColumnResizerProxy : null,
/////////////////////////////////////////////////////////////////////////
//
// Private static methods
//
/////////////////////////////////////////////////////////////////////////
/**
* Clones object literal or array of object literals.
*
* @method DataTable._cloneObject
* @param o {Object} Object.
* @private
* @static
*/
_cloneObject : function(o) {
if(!lang.isValue(o)) {
return o;
}
var copy = {};
if(o instanceof YAHOO.widget.BaseCellEditor) {
copy = o;
}
else if(lang.isFunction(o)) {
copy = o;
}
else if(lang.isArray(o)) {
var array = [];
for(var i=0,len=o.length;i<len;i++) {
array[i] = DT._cloneObject(o[i]);
}
copy = array;
}
else if(lang.isObject(o)) {
for (var x in o){
if(lang.hasOwnProperty(o, x)) {
if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
copy[x] = DT._cloneObject(o[x]);
}
else {
copy[x] = o[x];
}
}
}
}
else {
copy = o;
}
return copy;
},
/**
* Destroys shared Column drag target.
*
* @method DataTable._destroyColumnDragTargetEl
* @private
* @static
*/
_destroyColumnDragTargetEl : function() {
if(DT._elColumnDragTarget) {
var el = DT._elColumnDragTarget;
YAHOO.util.Event.purgeElement(el);
el.parentNode.removeChild(el);
DT._elColumnDragTarget = null;
}
},
/**
* Creates HTML markup for shared Column drag target.
*
* @method DataTable._initColumnDragTargetEl
* @return {HTMLElement} Reference to Column drag target.
* @private
* @static
*/
_initColumnDragTargetEl : function() {
if(!DT._elColumnDragTarget) {
// Attach Column drag target element as first child of body
var elColumnDragTarget = document.createElement('div');
elColumnDragTarget.className = DT.CLASS_COLTARGET;
elColumnDragTarget.style.display = "none";
document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
// Internal tracker of Column drag target
DT._elColumnDragTarget = elColumnDragTarget;
}
return DT._elColumnDragTarget;
},
/**
* Destroys shared Column resizer proxy.
*
* @method DataTable._destroyColumnResizerProxyEl
* @return {HTMLElement} Reference to Column resizer proxy.
* @private
* @static
*/
_destroyColumnResizerProxyEl : function() {
if(DT._elColumnResizerProxy) {
var el = DT._elColumnResizerProxy;
YAHOO.util.Event.purgeElement(el);
el.parentNode.removeChild(el);
DT._elColumnResizerProxy = null;
}
},
/**
* Creates HTML markup for shared Column resizer proxy.
*
* @method DataTable._initColumnResizerProxyEl
* @return {HTMLElement} Reference to Column resizer proxy.
* @private
* @static
*/
_initColumnResizerProxyEl : function() {
if(!DT._elColumnResizerProxy) {
// Attach Column resizer element as first child of body
var elColumnResizerProxy = document.createElement("div");
elColumnResizerProxy.id = "yui-dt-colresizerproxy"; // Needed for ColumnResizer
elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
// Internal tracker of Column resizer proxy
DT._elColumnResizerProxy = elColumnResizerProxy;
}
return DT._elColumnResizerProxy;
},
/**
* Formats a BUTTON element.
*
* @method DataTable.formatButton
* @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. By default, the value
* is what gets written to the BUTTON.
* @static
*/
formatButton : function(el, oRecord, oColumn, oData) {
var sValue = lang.isValue(oData) ? oData : "Click";
//TODO: support YAHOO.widget.Button
//if(YAHOO.widget.Button) {
//}
//else {
el.innerHTML = "<button type=\"button\" class=\""+
DT.CLASS_BUTTON + "\">" + sValue + "</button>";
//}
},
/**
* 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 = "<input type=\"checkbox\"" + bChecked +
" class=\"" + DT.CLASS_CHECKBOX + "\" />";
},
/**
* 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),
options = (lang.isArray(oColumn.dropdownOptions)) ?
oColumn.dropdownOptions : null,
selectEl,
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<options.length; i++) {
var option = options[i];
var optionEl = document.createElement("option");
optionEl.value = (lang.isValue(option.value)) ?
option.value : option;
// Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
optionEl.innerHTML = (lang.isValue(option.text)) ?
option.text : (lang.isValue(option.label)) ? option.label : option;
optionEl = selectEl.appendChild(optionEl);
if (optionEl.value == selectedValue) {
optionEl.selected = true;
}
}
}
// Selected value is our only option
else {
selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
}
}
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 = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
}
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 = "<a href=\"" + oData + "\">" + oData + "</a>";
}
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 = "<input type=\"radio\"" + bChecked +
" name=\""+this.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
" class=\"" + DT.CLASS_RADIO+ "\" />";
},
/**
* 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(oData)) ? oData : "";
//TODO: move to util function
el.innerHTML = value.toString().replace(/&/g, "&").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(oData)) ? oData : "",
markup = "<textarea>" + value + "</textarea>";
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(oData)) ? oData : "",
markup = "<input type=\"text\" value=\"" + value + "\" />";
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:
* <dl>
* <dt>"standard"</dt>
* <dd>Standard row selection with support for modifier keys to enable
* multiple selections.</dd>
*
* <dt>"single"</dt>
* <dd>Row selection with modifier keys disabled to not allow
* multiple selections.</dd>
*
* <dt>"singlecell"</dt>
* <dd>Cell selection with modifier keys disabled to not allow
* multiple selections.</dd>
*
* <dt>"cellblock"</dt>
* <dd>Cell selection with support for modifier keys to enable multiple
* selections in a block-fashion, like a spreadsheet.</dd>
*
* <dt>"cellrange"</dt>
* <dd>Cell selection with support for modifier keys to enable multiple
* selections in a range-fashion, like a calendar.</dd>
* </dl>
*
* @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:
* <dl>
* <dt>sortedBy.key</dt>
* <dd>{String} Key of sorted Column</dd>
* <dt>sortedBy.dir</dt>
* <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
* </dl>
* @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:
*
* <dl>
* <dt>pagination<dt>
* <dd>
* <dt>offsetRecord</dt>
* <dd>{Number} Index of the first Record of the desired page</dd>
* <dt>rowsPerPage</dt>
* <dd>{Number} Number of rows per page</dd>
* </dd>
* <dt>sortedBy</dt>
* <dd>
* <dt>key</dt>
* <dd>{String} Key of sorted Column</dd>
* <dt>dir</dt>
* <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
* </dd>
* <dt>self</dt>
* <dd>The DataTable instance</dd>
* </dl>
*
* 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 = encodeURIComponent((oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey());
var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.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:
*
* <dl>
* <dt>request (MIXED)</dt>
* <dd>Request value.</dd>
*
* <dt>argument (MIXED)</dt>
* <dd>Custom data that will be passed through to the callback function.</dd>
* </dl>
*
*
* @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<len; i++) {
if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
allKeys[i]._elThLabel.firstChild.title = sParam;
}
}
}
}
});
/**
* @attribute MSG_SORTDESC
* @description Message to display in tooltip to sort Column in descending order.
* @type String
* @default "Click to sort descending"
*/
this.setAttributeConfig("MSG_SORTDESC", {
value: "Click to sort descending",
validator: lang.isString,
method: function(sParam) {
if(this._elThead) {
for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
allKeys[i]._elThLabel.firstChild.title = sParam;
}
}
}
}
});
/**
* @attribute currencySymbol
* @deprecated
*/
this.setAttributeConfig("currencySymbol", {
value: "$",
validator: lang.isString
});
/**
* Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
* @attribute currencyOptions
* @type Object
* @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
*/
this.setAttributeConfig("currencyOptions", {
value: {
prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
decimalPlaces:2,
decimalSeparator:".",
thousandsSeparator:","
}
});
/**
* Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
* @attribute dateOptions
* @type Object
* @default {format:"%m/%d/%Y", locale:"en"}
*/
this.setAttributeConfig("dateOptions", {
value: {format:"%m/%d/%Y", locale:"en"}
});
/**
* Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
* @attribute numberOptions
* @type Object
* @default {decimalPlaces:0, thousandsSeparator:","}
*/
this.setAttributeConfig("numberOptions", {
value: {
decimalPlaces:0,
thousandsSeparator:","
}
});
},
/////////////////////////////////////////////////////////////////////////////
//
// Private member variables
//
/////////////////////////////////////////////////////////////////////////////
/**
* True if instance is initialized, so as to fire the initEvent after render.
*
* @property _bInit
* @type Boolean
* @default true
* @private
*/
_bInit : true,
/**
* Index assigned to instance.
*
* @property _nIndex
* @type Number
* @private
*/
_nIndex : null,
/**
* Counter for IDs assigned to TR elements.
*
* @property _nTrCount
* @type Number
* @private
*/
_nTrCount : 0,
/**
* Counter for IDs assigned to TD elements.
*
* @property _nTdCount
* @type Number
* @private
*/
_nTdCount : 0,
/**
* Unique id assigned to instance "yui-dtN", useful prefix for generating unique
* DOM ID strings and log messages.
*
* @property _sId
* @type String
* @private
*/
_sId : null,
/**
* Render chain.
*
* @property _oChainRender
* @type YAHOO.util.Chain
* @private
*/
_oChainRender : null,
/**
* DOM reference to the container element for the DataTable instance into which
* all other elements get created.
*
* @property _elContainer
* @type HTMLElement
* @private
*/
_elContainer : null,
/**
* DOM reference to the mask element for the DataTable instance which disables it.
*
* @property _elMask
* @type HTMLElement
* @private
*/
_elMask : null,
/**
* DOM reference to the TABLE element for the DataTable instance.
*
* @property _elTable
* @type HTMLElement
* @private
*/
_elTable : null,
/**
* DOM reference to the CAPTION element for the DataTable instance.
*
* @property _elCaption
* @type HTMLElement
* @private
*/
_elCaption : null,
/**
* DOM reference to the COLGROUP element for the DataTable instance.
*
* @property _elColgroup
* @type HTMLElement
* @private
*/
_elColgroup : null,
/**
* DOM reference to the THEAD element for the DataTable instance.
*
* @property _elThead
* @type HTMLElement
* @private
*/
_elThead : null,
/**
* DOM reference to the primary TBODY element for the DataTable instance.
*
* @property _elTbody
* @type HTMLElement
* @private
*/
_elTbody : null,
/**
* DOM reference to the secondary TBODY element used to display DataTable messages.
*
* @property _elMsgTbody
* @type HTMLElement
* @private
*/
_elMsgTbody : null,
/**
* DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
*
* @property _elMsgTr
* @type HTMLElement
* @private
*/
_elMsgTr : null,
/**
* DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
*
* @property _elMsgTd
* @type HTMLElement
* @private
*/
_elMsgTd : null,
/**
* DataSource instance for the DataTable instance.
*
* @property _oDataSource
* @type YAHOO.util.DataSource
* @private
*/
_oDataSource : null,
/**
* ColumnSet instance for the DataTable instance.
*
* @property _oColumnSet
* @type YAHOO.widget.ColumnSet
* @private
*/
_oColumnSet : null,
/**
* RecordSet instance for the DataTable instance.
*
* @property _oRecordSet
* @type YAHOO.widget.RecordSet
* @private
*/
_oRecordSet : null,
/**
* The active CellEditor instance for the DataTable instance.
*
* @property _oCellEditor
* @type YAHOO.widget.CellEditor
* @private
*/
_oCellEditor : null,
/**
* ID string of first TR element of the current DataTable page.
*
* @property _sFirstTrId
* @type String
* @private
*/
_sFirstTrId : null,
/**
* ID string of the last TR element of the current DataTable page.
*
* @property _sLastTrId
* @type String
* @private
*/
_sLastTrId : null,
/**
* Template row to create all new rows from.
* @property _elTrTemplate
* @type {HTMLElement}
* @private
*/
_elTrTemplate : null,
/**
* Sparse array of custom functions to set column widths for browsers that don't
* support dynamic CSS rules. Functions are added at the index representing
* the number of rows they update.
*
* @property _aDynFunctions
* @type Array
* @private
*/
_aDynFunctions : [],
/////////////////////////////////////////////////////////////////////////////
//
// Private methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Clears browser text selection. Useful to call on rowSelectEvent or
* cellSelectEvent to prevent clicks or dblclicks from selecting text in the
* browser.
*
* @method clearTextSelection
*/
clearTextSelection : function() {
var sel;
if(window.getSelection) {
sel = window.getSelection();
}
else if(document.getSelection) {
sel = document.getSelection();
}
else if(document.selection) {
sel = document.selection;
}
if(sel) {
if(sel.empty) {
sel.empty();
}
else if (sel.removeAllRanges) {
sel.removeAllRanges();
}
else if(sel.collapse) {
sel.collapse();
}
}
},
/**
* Sets focus on the given element.
*
* @method _focusEl
* @param el {HTMLElement} Element.
* @private
*/
_focusEl : function(el) {
el = el || this._elTbody;
// http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
// The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
// strange unexpected things as the user clicks on buttons and other controls.
setTimeout(function() {
try {
el.focus();
}
catch(e) {
}
},0);
},
/**
* Forces Gecko repaint.
*
* @method _repaintGecko
* @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
* @private
*/
_repaintGecko : (ua.gecko) ?
function(el) {
el = el || this._elContainer;
var parent = el.parentNode;
var nextSibling = el.nextSibling;
parent.insertBefore(parent.removeChild(el), nextSibling);
} : function() {},
/**
* Forces Opera repaint.
*
* @method _repaintOpera
* @private
*/
_repaintOpera : (ua.opera) ?
function() {
if(ua.opera) {
document.documentElement.className += " ";
document.documentElement.className = YAHOO.lang.trim(document.documentElement.className);
}
} : function() {} ,
/**
* Forces Webkit repaint.
*
* @method _repaintWebkit
* @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
* @private
*/
_repaintWebkit : (ua.webkit) ?
function(el) {
el = el || this._elContainer;
var parent = el.parentNode;
var nextSibling = el.nextSibling;
parent.insertBefore(parent.removeChild(el), nextSibling);
} : function() {},
// INIT FUNCTIONS
/**
* Initializes object literal of config values.
*
* @method _initConfigs
* @param oConfig {Object} Object literal of config values.
* @private
*/
_initConfigs : function(oConfigs) {
if(!oConfigs || !lang.isObject(oConfigs)) {
oConfigs = {};
}
this.configs = oConfigs;
},
/**
* Initializes ColumnSet.
*
* @method _initColumnSet
* @param aColumnDefs {Object[]} Array of object literal Column definitions.
* @private
*/
_initColumnSet : function(aColumnDefs) {
var oColumn, i, len;
if(this._oColumnSet) {
// First clear _oDynStyles for existing ColumnSet and
// uregister CellEditor Custom Events
for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
oColumn = this._oColumnSet.keys[i];
DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
oColumn.editor.unsubscribeAll();
}
}
this._oColumnSet = null;
this._clearTrTemplateEl();
}
if(lang.isArray(aColumnDefs)) {
this._oColumnSet = new YAHOO.widget.ColumnSet(aColumnDefs);
}
// Backward compatibility
else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
this._oColumnSet = aColumnDefs;
YAHOO.log("DataTable's constructor now requires an array" +
" of object literal Column definitions instead of a ColumnSet instance",
"warn", this.toString());
}
// Register CellEditor Custom Events
var allKeys = this._oColumnSet.keys;
for(i=0, len=allKeys.length; i<len; i++) {
oColumn = allKeys[i];
if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
}
}
},
/**
* Initializes DataSource.
*
* @method _initDataSource
* @param oDataSource {YAHOO.util.DataSource} DataSource instance.
* @private
*/
_initDataSource : function(oDataSource) {
this._oDataSource = null;
if(oDataSource && (lang.isFunction(oDataSource.sendRequest))) {
this._oDataSource = oDataSource;
}
// Backward compatibility
else {
var tmpTable = null;
var tmpContainer = this._elContainer;
var i=0;
//TODO: this will break if re-initing DS at runtime for SDT
// Peek in container child nodes to see if TABLE already exists
if(tmpContainer.hasChildNodes()) {
var tmpChildren = tmpContainer.childNodes;
for(i=0; i<tmpChildren.length; i++) {
if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
tmpTable = tmpChildren[i];
break;
}
}
if(tmpTable) {
var tmpFieldsArray = [];
for(; i<this._oColumnSet.keys.length; i++) {
tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
}
this._oDataSource = new DS(tmpTable);
this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
this._oDataSource.responseSchema = {fields: tmpFieldsArray};
YAHOO.log("Null DataSource for progressive enhancement from" +
" markup has been deprecated", "warn", this.toString());
}
}
}
},
/**
* Initializes RecordSet.
*
* @method _initRecordSet
* @private
*/
_initRecordSet : function() {
if(this._oRecordSet) {
this._oRecordSet.reset();
}
else {
this._oRecordSet = new YAHOO.widget.RecordSet();
}
},
/**
* Initializes DOM elements.
*
* @method _initDomElements
* @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
* return {Boolean} False in case of error, otherwise true
* @private
*/
_initDomElements : function(elContainer) {
// Outer container
this._initContainerEl(elContainer);
// TABLE
this._initTableEl(this._elContainer);
// COLGROUP
this._initColgroupEl(this._elTable);
// THEAD
this._initTheadEl(this._elTable);
// Message TBODY
this._initMsgTbodyEl(this._elTable);
// Primary TBODY
this._initTbodyEl(this._elTable);
if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody) {
return false;
}
else {
return true;
}
},
/**
* Destroy's the DataTable outer container element, if available.
*
* @method _destroyContainerEl
* @param elContainer {HTMLElement} Reference to the container element.
* @private
*/
_destroyContainerEl : function(elContainer) {
Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
Ev.purgeElement(elContainer, true);
elContainer.innerHTML = "";
this._elContainer = null;
this._elColgroup = null;
this._elThead = null;
this._elTbody = null;
},
/**
* Initializes the DataTable outer container element, including a mask.
*
* @method _initContainerEl
* @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
* @private
*/
_initContainerEl : function(elContainer) {
// Validate container
elContainer = Dom.get(elContainer);
if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
// Destroy previous
this._destroyContainerEl(elContainer);
Dom.addClass(elContainer, DT.CLASS_DATATABLE);
Ev.addListener(elContainer, "focus", this._onTableFocus, this);
Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
this._elContainer = elContainer;
var elMask = document.createElement("div");
elMask.className = DT.CLASS_MASK;
elMask.style.display = "none";
this._elMask = elContainer.appendChild(elMask);
}
},
/**
* Destroy's the DataTable TABLE element, if available.
*
* @method _destroyTableEl
* @private
*/
_destroyTableEl : function() {
var elTable = this._elTable;
if(elTable) {
Ev.purgeElement(elTable, true);
elTable.parentNode.removeChild(elTable);
this._elCaption = null;
this._elColgroup = null;
this._elThead = null;
this._elTbody = null;
}
},
/**
* Creates HTML markup CAPTION element.
*
* @method _initCaptionEl
* @param sCaption {String} Text for caption.
* @private
*/
_initCaptionEl : function(sCaption) {
if(this._elTable && sCaption) {
// Create CAPTION element
if(!this._elCaption) {
this._elCaption = this._elTable.createCaption();
}
// Set CAPTION value
this._elCaption.innerHTML = sCaption;
}
else if(this._elCaption) {
this._elCaption.parentNode.removeChild(this._elCaption);
}
},
/**
* Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
* container element.
*
* @method _initTableEl
* @param elContainer {HTMLElement} Container element into which to create TABLE.
* @private
*/
_initTableEl : function(elContainer) {
if(elContainer) {
// Destroy previous
this._destroyTableEl();
// Create TABLE
this._elTable = elContainer.appendChild(document.createElement("table"));
// Set SUMMARY attribute
this._elTable.summary = this.get("summary");
// Create CAPTION element
if(this.get("caption")) {
this._initCaptionEl(this.get("caption"));
}
}
},
/**
* Destroy's the DataTable COLGROUP element, if available.
*
* @method _destroyColgroupEl
* @private
*/
_destroyColgroupEl : function() {
var elColgroup = this._elColgroup;
if(elColgroup) {
var elTable = elColgroup.parentNode;
Ev.purgeElement(elColgroup, true);
elTable.removeChild(elColgroup);
this._elColgroup = null;
}
},
/**
* Initializes COLGROUP and COL elements for managing minWidth.
*
* @method _initColgroupEl
* @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
* @private
*/
_initColgroupEl : function(elTable) {
if(elTable) {
// Destroy previous
this._destroyColgroupEl();
// Add COLs to DOCUMENT FRAGMENT
var allCols = this._aColIds || [],
allKeys = this._oColumnSet.keys,
i = 0, len = allCols.length,
elCol, oColumn,
elFragment = document.createDocumentFragment(),
elColTemplate = document.createElement("col");
for(i=0,len=allKeys.length; i<len; i++) {
oColumn = allKeys[i];
elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
}
// Create COLGROUP
var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
elColgroup.appendChild(elFragment);
this._elColgroup = elColgroup;
}
},
/**
* Adds a COL element to COLGROUP at given index.
*
* @method _insertColgroupColEl
* @param index {Number} Index of new COL element.
* @private
*/
_insertColgroupColEl : function(index) {
if(lang.isNumber(index)&& this._elColgroup) {
var nextSibling = this._elColgroup.childNodes[index] || null;
this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
}
},
/**
* Removes a COL element to COLGROUP at given index.
*
* @method _removeColgroupColEl
* @param index {Number} Index of removed COL element.
* @private
*/
_removeColgroupColEl : function(index) {
if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
}
},
/**
* Reorders a COL element from old index(es) to new index.
*
* @method _reorderColgroupColEl
* @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
* @param newIndex {Number} New index.
* @private
*/
_reorderColgroupColEl : function(aKeyIndexes, newIndex) {
if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > 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<colTree.length; i++) {
var elTheadTr = elThead.appendChild(document.createElement("tr"));
// ...and create TH cells
for(j=0; j<colTree[i].length; j++) {
oColumn = colTree[i][j];
elTh = elTheadTr.appendChild(document.createElement("th"));
this._initThEl(elTh,oColumn);
}
// Set FIRST/LAST on THEAD rows
if(i === 0) {
Dom.addClass(elTheadTr, DT.CLASS_FIRST);
}
if(i === (colTree.length-1)) {
Dom.addClass(elTheadTr, DT.CLASS_LAST);
}
}
// Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
var aFirstHeaders = oColumnSet.headers[0] || [];
for(i=0; i<aFirstHeaders.length; i++) {
Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
}
var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
for(i=0; i<aLastHeaders.length; i++) {
Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
}
YAHOO.log("TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
///TODO: try _repaintGecko(this._elContainer) instead
// Bug 1806891
if(ua.webkit && ua.webkit < 420) {
var oSelf = this;
setTimeout(function() {
elThead.style.display = "";
},0);
elThead.style.display = 'none';
}
this._elThead = elThead;
// Column helpers needs _elThead to exist
this._initColumnHelpers();
}
},
/**
* Populates TH element as defined by Column.
*
* @method _initThEl
* @param elTh {HTMLElement} TH element reference.
* @param oColumn {YAHOO.widget.Column} Column object.
* @private
*/
_initThEl : function(elTh, oColumn) {
elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
elTh.innerHTML = "";
elTh.rowSpan = oColumn.getRowspan();
elTh.colSpan = oColumn.getColspan();
oColumn._elTh = elTh;
var elThLiner = elTh.appendChild(document.createElement("div"));
elThLiner.id = elTh.id + "-liner"; // Needed for resizer
elThLiner.className = DT.CLASS_LINER;
oColumn._elThLiner = elThLiner;
var elThLabel = elThLiner.appendChild(document.createElement("span"));
elThLabel.className = DT.CLASS_LABEL;
// Assign abbr attribute
if(oColumn.abbr) {
elTh.abbr = oColumn.abbr;
}
// Clear minWidth on hidden Columns
if(oColumn.hidden) {
this._clearMinWidth(oColumn);
}
elTh.className = this._getColumnClassNames(oColumn);
// Set Column width...
if(oColumn.width) {
// Validate minWidth
var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
oColumn.minWidth : oColumn.width;
// ...for fallback cases
if(DT._bDynStylesFallback) {
elTh.firstChild.style.overflow = 'hidden';
elTh.firstChild.style.width = nWidth + 'px';
}
// ...for non fallback cases
else {
this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
}
}
this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
oColumn._elThLabel = elThLabel;
},
/**
* Outputs markup into the given TH based on given Column.
*
* @method DataTable.formatTheadCell
* @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
* not the liner DIV element.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oSortedBy {Object} Sort state object literal.
*/
formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
var sKey = oColumn.getKey();
var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
// Add accessibility link for sortable Columns
if(oColumn.sortable) {
// Calculate the direction
var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
var bDesc = (sSortClass === DT.CLASS_DESC);
// This is the sorted Column
if(oSortedBy && (oColumn.key === oSortedBy.key)) {
bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
}
// Generate a unique HREF for visited status
var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
// Generate a dynamic TITLE for sort status
var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
// Format the element
elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
}
// 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<len; i++) {
oColumn = this._oColumnSet.tree[0][i];
if(oColumn._dd) {
oColumn._dd = oColumn._dd.unreg();
Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);
}
}
},
/**
* Initializes top-level Column TH elements into DD instances.
*
* @method _initDraggableColumns
* @private
*/
_initDraggableColumns : function() {
this._destroyDraggableColumns();
if(util.DD) {
var oColumn, elTh, elDragTarget;
for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
oColumn = this._oColumnSet.tree[0][i];
elTh = oColumn.getThEl();
Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
elDragTarget = DT._initColumnDragTargetEl();
oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
}
}
else {
YAHOO.log("Could not find DragDrop for draggable Columns", "warn", this.toString());
}
},
/**
* Disables resizeability on key Column TH elements.
*
* @method _destroyResizeableColumns
* @private
*/
_destroyResizeableColumns : function() {
var aKeys = this._oColumnSet.keys;
for(var i=0, len=aKeys.length; i<len; i++) {
if(aKeys[i]._ddResizer) {
aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
}
}
},
/**
* Initializes resizeability on key Column TH elements.
*
* @method _initResizeableColumns
* @private
*/
_initResizeableColumns : function() {
this._destroyResizeableColumns();
if(util.DD) {
var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
oColumn = this._oColumnSet.keys[i];
if(oColumn.resizeable) {
elTh = oColumn.getThEl();
Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
elThLiner = oColumn.getThLinerEl();
// Bug 1915349: So resizer is as tall as TH when rowspan > 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 || 1;
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;
// Set up DOM events for TBODY
Ev.addListener(elMsgTbody, "focus", this._onTbodyFocus, this);
Ev.addListener(elMsgTbody, "mouseover", this._onTableMouseover, this);
Ev.addListener(elMsgTbody, "mouseout", this._onTableMouseout, this);
Ev.addListener(elMsgTbody, "mousedown", this._onTableMousedown, this);
Ev.addListener(elMsgTbody, "mouseup", this._onTableMouseup, this);
Ev.addListener(elMsgTbody, "keydown", this._onTbodyKeydown, this);
Ev.addListener(elMsgTbody, "keypress", this._onTableKeypress, this);
Ev.addListener(elMsgTbody, "click", this._onTbodyClick, this);
}
},
/**
* 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<keysLen; i++) {
// Clone the TD template
elTd = td.cloneNode(true);
// Format the base TD
elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
df.appendChild(elTd);
}
tr.appendChild(df);
this._elTrTemplate = tr;
return tr;
}
},
/**
* Formats a basic TD element.
* @method _formatTdEl
* @param oColumn {YAHOO.widget.Column} Associated Column instance.
* @param elTd {HTMLElement} An unformatted TD element.
* @param index {Number} Column key index.
* @param isLast {Boolean} True if Column is last key of the ColumnSet.
* @return {HTMLElement} A formatted TD element.
* @private
*/
_formatTdEl : function (oColumn, elTd, index, isLast) {
var oColumnSet = this._oColumnSet;
// Set the TD's accessibility headers
var allHeaders = oColumnSet.headers,
allColHeaders = allHeaders[index],
sTdHeaders = "",
sHeader;
for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
sTdHeaders += sHeader;
}
elTd.headers = sTdHeaders;
// Class the TD element
var aAddClasses = [];
if(index === 0) {
aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
}
if(isLast) {
aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
}
elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
// Class the liner element
elTd.firstChild.className = DT.CLASS_LINER;
// Set Column width for fallback cases
if(oColumn.width && DT._bDynStylesFallback) {
// Validate minWidth
var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
oColumn.minWidth : oColumn.width;
elTd.firstChild.style.overflow = 'hidden';
elTd.firstChild.style.width = nWidth + 'px';
}
return elTd;
},
/**
* Create a new TR element for a given Record and appends it with the correct
* number of Column-state-classed TD elements. Striping is the responsibility of
* the calling function, which may decide to stripe the single row, a subset of
* rows, or all the rows.
* @method _createTrEl
* @param oRecord {YAHOO.widget.Record} Record instance
* @return {HTMLElement} The new TR element. This must be added to the DOM.
* @private
*/
_addTrEl : function (oRecord) {
var elTrTemplate = this._getTrTemplateEl();
// Clone the TR template.
var elTr = elTrTemplate.cloneNode(true);
// Populate content
return this._updateTrEl(elTr,oRecord);
},
/**
* Formats the contents of the given TR's TD elements with data from the given
* Record. Only innerHTML should change, nothing structural.
*
* @method _updateTrEl
* @param elTr {HTMLElement} The TR element to update.
* @param oRecord {YAHOO.widget.Record} The associated Record instance.
* @return {HTMLElement} DOM reference to the new TR element.
* @private
*/
_updateTrEl : function(elTr, oRecord) {
var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
if(ok) {
// Hide the row to prevent constant reflows
elTr.style.display = 'none';
// Update TD elements with new data
var allTds = elTr.childNodes,
elTd;
for(var i=0,len=allTds.length; i<len; ++i) {
elTd = allTds[i];
// Set the cell content
this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
}
// Redisplay the row for reflow
elTr.style.display = '';
}
elTr.id = oRecord.getId(); // Needed for Record association and tracking of FIRST/LAST
return elTr;
},
/**
* Deletes TR element by DOM reference or by DataTable page row index.
*
* @method _deleteTrEl
* @param row {HTMLElement | Number} TR element reference or Datatable page row index.
* @return {Boolean} Returns true if successful, else returns false.
* @private
*/
_deleteTrEl : function(row) {
var rowIndex;
// Get page row index for the element
if(!lang.isNumber(row)) {
rowIndex = Dom.get(row).sectionRowIndex;
}
else {
rowIndex = row;
}
if(lang.isNumber(rowIndex) && (rowIndex > -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; i<nEndIndex; i++) {
if(i%2) {
aOdds[nOddIdx++] = allRows[i];
} else {
aEvens[nEvenIdx++] = allRows[i];
}
}
if (aOdds.length) {
Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
}
if (aEvens.length) {
Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
}
},
/**
* Assigns the class DT.CLASS_SELECTED to TR and TD elements.
*
* @method _setSelections
* @private
*/
_setSelections : function() {
// Keep track of selected rows
var allSelectedRows = this.getSelectedRows();
// Keep track of selected cells
var allSelectedCells = this.getSelectedCells();
// Anything to select?
if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
var oColumnSet = this._oColumnSet,
el;
// Loop over each row
for(var i=0; i<allSelectedRows.length; i++) {
el = Dom.get(allSelectedRows[i]);
if(el) {
Dom.addClass(el, DT.CLASS_SELECTED);
}
}
// Loop over each cell
for(i=0; i<allSelectedCells.length; i++) {
el = Dom.get(allSelectedCells[i].recordId);
if(el) {
Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
}
}
}
},
/////////////////////////////////////////////////////////////////////////////
//
// Private DOM Event Handlers
//
/////////////////////////////////////////////////////////////////////////////
/**
* Validates minWidths whenever the render chain ends.
*
* @method _onRenderChainEnd
* @private
*/
_onRenderChainEnd : function() {
// Hide loading message
this.hideTableMessage();
// Show empty message
if(this._elTbody.rows.length === 0) {
this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
}
// Execute in timeout thread to give implementers a chance
// to subscribe after the constructor
var oSelf = this;
setTimeout(function() {
if((oSelf instanceof DT) && oSelf._sId) {
// Init event
if(oSelf._bInit) {
oSelf._bInit = false;
oSelf.fireEvent("initEvent");
}
// Render event
oSelf.fireEvent("renderEvent");
// Backward compatibility
oSelf.fireEvent("refreshEvent");
YAHOO.log("DataTable rendered", "info", oSelf.toString());
// Post-render routine
oSelf.validateColumnWidths();
// Post-render event
oSelf.fireEvent("postRenderEvent");
/*if(YAHOO.example.Performance.trialStart) {
YAHOO.log((new Date()).getTime() - YAHOO.example.Performance.trialStart.getTime() + " ms", "time");
YAHOO.example.Performance.trialStart = null;
}*/
YAHOO.log("Post-render routine executed", "info", oSelf.toString());
}
}, 0);
},
/**
* Handles click events on the DOCUMENT.
*
* @method _onDocumentClick
* @param e {HTMLEvent} The click event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onDocumentClick : function(e, oSelf) {
var elTarget = Ev.getTarget(e);
var elTag = elTarget.nodeName.toLowerCase();
if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
oSelf.fireEvent("tableBlurEvent");
// Fires editorBlurEvent when click is not within the TABLE.
// For cases when click is within the TABLE, due to timing issues,
// the editorBlurEvent needs to get fired by the lower-level DOM click
// handlers below rather than by the TABLE click handler directly.
if(oSelf._oCellEditor) {
if(oSelf._oCellEditor.getContainerEl) {
var elContainer = oSelf._oCellEditor.getContainerEl();
// Only if the click was not within the CellEditor container
if(!Dom.isAncestor(elContainer, elTarget) &&
(elContainer.id !== elTarget.id)) {
oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
}
}
// Backward Compatibility
else if(oSelf._oCellEditor.isActive) {
// Only if the click was not within the Cell Editor container
if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
(oSelf._oCellEditor.container.id !== elTarget.id)) {
oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
}
}
}
}
},
/**
* Handles focus events on the DataTable instance.
*
* @method _onTableFocus
* @param e {HTMLEvent} The focus event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onTableFocus : function(e, oSelf) {
oSelf.fireEvent("tableFocusEvent");
},
/**
* Handles focus events on the THEAD element.
*
* @method _onTheadFocus
* @param e {HTMLEvent} The focus event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onTheadFocus : function(e, oSelf) {
oSelf.fireEvent("theadFocusEvent");
oSelf.fireEvent("tableFocusEvent");
},
/**
* Handles focus events on the TBODY element.
*
* @method _onTbodyFocus
* @param e {HTMLEvent} The focus event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onTbodyFocus : function(e, oSelf) {
oSelf.fireEvent("tbodyFocusEvent");
oSelf.fireEvent("tableFocusEvent");
},
/**
* Handles mouseover events on the DataTable instance.
*
* @method _onTableMouseover
* @param e {HTMLEvent} The mouseover event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onTableMouseover : function(e, oSelf) {
var elTarget = Ev.getTarget(e);
var elTag = elTarget.nodeName.toLowerCase();
var bKeepBubbling = true;
while(elTarget && (elTag != "table")) {
switch(elTag) {
case "body":
return;
case "a":
break;
case "td":
bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
break;
case "span":
if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
}
break;
case "th":
bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
break;
case "tr":
if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
}
else {
bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
}
break;
default:
break;
}
if(bKeepBubbling === false) {
return;
}
else {
elTarget = elTarget.parentNode;
if(elTarget) {
elTag = elTarget.nodeName.toLowerCase();
}
}
}
oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
},
/**
* Handles mouseout events on the DataTable instance.
*
* @method _onTableMouseout
* @param e {HTMLEvent} The mouseout event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onTableMouseout : function(e, oSelf) {
var elTarget = Ev.getTarget(e);
var elTag = elTarget.nodeName.toLowerCase();
var bKeepBubbling = true;
while(elTarget && (elTag != "table")) {
switch(elTag) {
case "body":
return;
case "a":
break;
case "td":
bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
break;
case "span":
if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
}
break;
case "th":
bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
break;
case "tr":
if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
}
else {
bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
}
break;
default:
break;
}
if(bKeepBubbling === false) {
return;
}
else {
elTarget = elTarget.parentNode;
if(elTarget) {
elTag = elTarget.nodeName.toLowerCase();
}
}
}
oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
},
/**
* Handles mousedown events on the DataTable instance.
*
* @method _onTableMousedown
* @param e {HTMLEvent} The mousedown event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onTableMousedown : function(e, oSelf) {
var elTarget = Ev.getTarget(e);
var elTag = elTarget.nodeName.toLowerCase();
var bKeepBubbling = true;
while(elTarget && (elTag != "table")) {
switch(elTag) {
case "body":
return;
case "a":
break;
case "td":
bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
break;
case "span":
if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
}
break;
case "th":
bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
break;
case "tr":
if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
}
else {
bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
}
break;
default:
break;
}
if(bKeepBubbling === false) {
return;
}
else {
elTarget = elTarget.parentNode;
if(elTarget) {
elTag = elTarget.nodeName.toLowerCase();
}
}
}
oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
},
/**
* Handles mouseup events on the DataTable instance.
*
* @method _onTableMouseup
* @param e {HTMLEvent} The mouseup event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onTableMouseup : function(e, oSelf) {
var elTarget = Ev.getTarget(e);
var elTag = elTarget.nodeName.toLowerCase();
var bKeepBubbling = true;
while(elTarget && (elTag != "table")) {
switch(elTag) {
case "body":
return;
case "a":
break;
case "td":
bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
break;
case "span":
if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
}
break;
case "th":
bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
break;
case "tr":
if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
}
else {
bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
}
break;
default:
break;
}
if(bKeepBubbling === false) {
return;
}
else {
elTarget = elTarget.parentNode;
if(elTarget) {
elTag = elTarget.nodeName.toLowerCase();
}
}
}
oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
},
/**
* Handles dblclick events on the DataTable instance.
*
* @method _onTableDblclick
* @param e {HTMLEvent} The dblclick event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onTableDblclick : function(e, oSelf) {
var elTarget = Ev.getTarget(e);
var elTag = elTarget.nodeName.toLowerCase();
var bKeepBubbling = true;
while(elTarget && (elTag != "table")) {
switch(elTag) {
case "body":
return;
case "td":
bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
break;
case "span":
if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
}
break;
case "th":
bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
break;
case "tr":
if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
}
else {
bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
}
break;
default:
break;
}
if(bKeepBubbling === false) {
return;
}
else {
elTarget = elTarget.parentNode;
if(elTarget) {
elTag = elTarget.nodeName.toLowerCase();
}
}
}
oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
},
/**
* Handles keydown events on the THEAD element.
*
* @method _onTheadKeydown
* @param e {HTMLEvent} The key event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onTheadKeydown : function(e, oSelf) {
var elTarget = Ev.getTarget(e);
var elTag = elTarget.nodeName.toLowerCase();
var bKeepBubbling = true;
while(elTarget && (elTag != "table")) {
switch(elTag) {
case "body":
return;
case "input":
case "textarea":
// TODO: implement textareaKeyEvent
break;
case "thead":
bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
break;
default:
break;
}
if(bKeepBubbling === false) {
return;
}
else {
elTarget = elTarget.parentNode;
if(elTarget) {
elTag = elTarget.nodeName.toLowerCase();
}
}
}
oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
},
/**
* Handles keydown events on the TBODY element. Handles selection behavior,
* provides hooks for ENTER to edit functionality.
*
* @method _onTbodyKeydown
* @param e {HTMLEvent} The key event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onTbodyKeydown : function(e, oSelf) {
var sMode = oSelf.get("selectionMode");
if(sMode == "standard") {
oSelf._handleStandardSelectionByKey(e);
}
else if(sMode == "single") {
oSelf._handleSingleSelectionByKey(e);
}
else if(sMode == "cellblock") {
oSelf._handleCellBlockSelectionByKey(e);
}
else if(sMode == "cellrange") {
oSelf._handleCellRangeSelectionByKey(e);
}
else if(sMode == "singlecell") {
oSelf._handleSingleCellSelectionByKey(e);
}
if(oSelf._oCellEditor) {
if(oSelf._oCellEditor.fireEvent) {
oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
}
else if(oSelf._oCellEditor.isActive) {
oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
}
}
var elTarget = Ev.getTarget(e);
var elTag = elTarget.nodeName.toLowerCase();
var bKeepBubbling = true;
while(elTarget && (elTag != "table")) {
switch(elTag) {
case "body":
return;
case "tbody":
bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
break;
default:
break;
}
if(bKeepBubbling === false) {
return;
}
else {
elTarget = elTarget.parentNode;
if(elTarget) {
elTag = elTarget.nodeName.toLowerCase();
}
}
}
oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
},
/**
* Handles keypress events on the TABLE. Mainly to support stopEvent on Mac.
*
* @method _onTableKeypress
* @param e {HTMLEvent} The key event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onTableKeypress : function(e, oSelf) {
if(ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") !== -1) && (ua.webkit < 420)) {
var nKey = Ev.getCharCode(e);
// arrow down
if(nKey == 40) {
Ev.stopEvent(e);
}
// arrow up
else if(nKey == 38) {
Ev.stopEvent(e);
}
}
},
/**
* Handles click events on the THEAD element.
*
* @method _onTheadClick
* @param e {HTMLEvent} The click event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onTheadClick : function(e, oSelf) {
// This blurs the CellEditor
if(oSelf._oCellEditor) {
if(oSelf._oCellEditor.fireEvent) {
oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
}
// Backward compatibility
else if(oSelf._oCellEditor.isActive) {
oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
}
}
var elTarget = Ev.getTarget(e),
elTag = elTarget.nodeName.toLowerCase(),
bKeepBubbling = true;
while(elTarget && (elTag != "table")) {
switch(elTag) {
case "body":
return;
case "input":
var sType = elTarget.type.toLowerCase();
if(sType == "checkbox") {
bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
}
else if(sType == "radio") {
bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
}
else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
}
break;
case "a":
bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
break;
case "button":
bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
break;
case "span":
if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
}
break;
case "th":
bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
break;
case "tr":
bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
// Backward compatibility
bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
break;
default:
break;
}
if(bKeepBubbling === false) {
return;
}
else {
elTarget = elTarget.parentNode;
if(elTarget) {
elTag = elTarget.nodeName.toLowerCase();
}
}
}
oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
},
/**
* Handles click events on the primary TBODY element.
*
* @method _onTbodyClick
* @param e {HTMLEvent} The click event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onTbodyClick : function(e, oSelf) {
// This blurs the CellEditor
if(oSelf._oCellEditor) {
if(oSelf._oCellEditor.fireEvent) {
oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
}
else if(oSelf._oCellEditor.isActive) {
oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
}
}
// Fire Custom Events
var elTarget = Ev.getTarget(e),
elTag = elTarget.nodeName.toLowerCase(),
bKeepBubbling = true;
while(elTarget && (elTag != "table")) {
switch(elTag) {
case "body":
return;
case "input":
var sType = elTarget.type.toLowerCase();
if(sType == "checkbox") {
bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
}
else if(sType == "radio") {
bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
}
else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
}
break;
case "a":
bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
break;
case "button":
bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
break;
case "td":
bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
break;
case "tr":
bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
break;
default:
break;
}
if(bKeepBubbling === false) {
return;
}
else {
elTarget = elTarget.parentNode;
if(elTarget) {
elTag = elTarget.nodeName.toLowerCase();
}
}
}
oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
},
/**
* Handles change events on SELECT elements within DataTable.
*
* @method _onDropdownChange
* @param e {HTMLEvent} The change event.
* @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
* @private
*/
_onDropdownChange : function(e, oSelf) {
var elTarget = Ev.getTarget(e);
oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
},
/////////////////////////////////////////////////////////////////////////////
//
// Public member variables
//
/////////////////////////////////////////////////////////////////////////////
/**
* Returns object literal of initial configs.
*
* @property configs
* @type Object
* @default {}
*/
configs: null,
/////////////////////////////////////////////////////////////////////////////
//
// Public methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Returns unique id assigned to instance, which is a useful prefix for
* generating unique DOM ID strings.
*
* @method getId
* @return {String} Unique ID of the DataSource instance.
*/
getId : function() {
return this._sId;
},
/**
* DataSource instance name, for logging.
*
* @method toString
* @return {String} Unique name of the DataSource instance.
*/
toString : function() {
return "DataTable instance " + this._sId;
},
/**
* Returns the DataTable instance's DataSource instance.
*
* @method getDataSource
* @return {YAHOO.util.DataSource} DataSource instance.
*/
getDataSource : function() {
return this._oDataSource;
},
/**
* Returns the DataTable instance's ColumnSet instance.
*
* @method getColumnSet
* @return {YAHOO.widget.ColumnSet} ColumnSet instance.
*/
getColumnSet : function() {
return this._oColumnSet;
},
/**
* Returns the DataTable instance's RecordSet instance.
*
* @method getRecordSet
* @return {YAHOO.widget.RecordSet} RecordSet instance.
*/
getRecordSet : function() {
return this._oRecordSet;
},
/**
* Returns on object literal representing the DataTable instance's current
* state with the following properties:
* <dl>
* <dt>pagination</dt>
* <dd>Instance of YAHOO.widget.Paginator</dd>
*
* <dt>sortedBy</dt>
* <dd>
* <dl>
* <dt>sortedBy.key</dt>
* <dd>{String} Key of sorted Column</dd>
* <dt>sortedBy.dir</dt>
* <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
* </dl>
* </dd>
*
* <dt>selectedRows</dt>
* <dd>Array of selected rows by Record ID.</dd>
*
* <dt>selectedCells</dt>
* <dd>Selected cells as an array of object literals:
* {recordId:sRecordId, columnKey:sColumnKey}</dd>
* </dl>
*
* @method getState
* @return {Object} DataTable instance state object literal values.
*/
getState : function() {
return {
totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
pagination: this.get("paginator") ? this.get("paginator").getState() : null,
sortedBy: this.get("sortedBy"),
selectedRows: this.getSelectedRows(),
selectedCells: this.getSelectedCells()
};
},
// DOM ACCESSORS
/**
* Returns DOM reference to the DataTable's container element.
*
* @method getContainerEl
* @return {HTMLElement} Reference to DIV element.
*/
getContainerEl : function() {
return this._elContainer;
},
/**
* Returns DOM reference to the DataTable's TABLE element.
*
* @method getTableEl
* @return {HTMLElement} Reference to TABLE element.
*/
getTableEl : function() {
return this._elTable;
},
/**
* Returns DOM reference to the DataTable's THEAD element.
*
* @method getTheadEl
* @return {HTMLElement} Reference to THEAD element.
*/
getTheadEl : function() {
return this._elThead;
},
/**
* Returns DOM reference to the DataTable's primary TBODY element.
*
* @method getTbodyEl
* @return {HTMLElement} Reference to TBODY element.
*/
getTbodyEl : function() {
return this._elTbody;
},
/**
* Returns DOM reference to the DataTable's secondary TBODY element that is
* used to display messages.
*
* @method getMsgTbodyEl
* @return {HTMLElement} Reference to TBODY element.
*/
getMsgTbodyEl : function() {
return this._elMsgTbody;
},
/**
* Returns DOM reference to the TD element within the secondary TBODY that is
* used to display messages.
*
* @method getMsgTdEl
* @return {HTMLElement} Reference to TD element.
*/
getMsgTdEl : function() {
return this._elMsgTd;
},
/**
* Returns the corresponding TR reference for a given DOM element, ID string or
* directly page row index. If the given identifier is a child of a TR element,
* then DOM tree is traversed until a parent TR element is returned, otherwise
* null.
*
* @method getTrEl
* @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
* get: by element reference, ID string, page row index, or Record.
* @return {HTMLElement} Reference to TR element, or null.
*/
getTrEl : function(row) {
// By Record
if(row instanceof YAHOO.widget.Record) {
return document.getElementById(row.getId());
}
// By page row index
else if(lang.isNumber(row)) {
var allRows = this._elTbody.rows;
return ((row > -1) && (row < allRows.length)) ? allRows[row] : null;
}
// By ID string or element reference
else {
var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
// Validate HTML element
if(elRow && (elRow.ownerDocument == document)) {
// Validate TR element
if(elRow.nodeName.toLowerCase() != "tr") {
// Traverse up the DOM to find the corresponding TR element
elRow = Dom.getAncestorByTagName(elRow,"tr");
}
return elRow;
}
}
return null;
},
/**
* Returns DOM reference to the first TR element in the DataTable page, or null.
*
* @method getFirstTrEl
* @return {HTMLElement} Reference to TR element.
*/
getFirstTrEl : function() {
return this._elTbody.rows[0] || null;
},
/**
* Returns DOM reference to the last TR element in the DataTable page, or null.
*
* @method getLastTrEl
* @return {HTMLElement} Reference to last TR element.
*/
getLastTrEl : function() {
var allRows = this._elTbody.rows;
if(allRows.length > 0) {
return allRows[allRows.length-1] || null;
}
},
/**
* Returns DOM reference to the next TR element from the given TR element, or null.
*
* @method getNextTrEl
* @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
* reference, ID string, page row index, or Record from which to get next TR element.
* @return {HTMLElement} Reference to next TR element.
*/
getNextTrEl : function(row) {
var nThisTrIndex = this.getTrIndex(row);
if(nThisTrIndex !== null) {
var allRows = this._elTbody.rows;
if(nThisTrIndex < allRows.length-1) {
return allRows[nThisTrIndex+1];
}
}
YAHOO.log("Could not get next TR element for row " + row, "info", this.toString());
return null;
},
/**
* Returns DOM reference to the previous TR element from the given TR element, or null.
*
* @method getPreviousTrEl
* @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
* reference, ID string, page row index, or Record from which to get previous TR element.
* @return {HTMLElement} Reference to previous TR element.
*/
getPreviousTrEl : function(row) {
var nThisTrIndex = this.getTrIndex(row);
if(nThisTrIndex !== null) {
var allRows = this._elTbody.rows;
if(nThisTrIndex > 0) {
return allRows[nThisTrIndex-1];
}
}
YAHOO.log("Could not get previous TR element for row " + row, "info", this.toString());
return null;
},
/**
* Returns DOM reference to a TD liner element.
*
* @method getTdLinerEl
* @param cell {HTMLElement | Object} TD element or child of a TD element, or
* object literal of syntax {record:oRecord, column:oColumn}.
* @return {HTMLElement} Reference to TD liner element.
*/
getTdLinerEl : function(cell) {
var elCell = this.getTdEl(cell);
return elCell.firstChild || null;
},
/**
* Returns DOM reference to a TD element.
*
* @method getTdEl
* @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
* object literal of syntax {record:oRecord, column:oColumn}.
* @return {HTMLElement} Reference to TD element.
*/
getTdEl : function(cell) {
var elCell;
var el = Dom.get(cell);
// Validate HTML element
if(el && (el.ownerDocument == document)) {
// Validate TD element
if(el.nodeName.toLowerCase() != "td") {
// Traverse up the DOM to find the corresponding TR element
elCell = Dom.getAncestorByTagName(el, "td");
}
else {
elCell = el;
}
// Make sure the TD is in this TBODY
// Bug 2527707 and bug 2263558
if(elCell && ((elCell.parentNode.parentNode == this._elTbody) || (elCell.parentNode.parentNode === null))) {
// Now we can return the TD element
return elCell;
}
}
else if(cell) {
var oRecord, nColKeyIndex;
if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
oRecord = this.getRecord(cell.recordId);
var oColumn = this.getColumn(cell.columnKey);
if(oColumn) {
nColKeyIndex = oColumn.getKeyIndex();
}
}
if(cell.record && cell.column && cell.column.getKeyIndex) {
oRecord = cell.record;
nColKeyIndex = cell.column.getKeyIndex();
}
var elRow = this.getTrEl(oRecord);
if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
return elRow.cells[nColKeyIndex] || null;
}
}
return null;
},
/**
* Returns DOM reference to the first TD element in the DataTable page (by default),
* the first TD element of the optionally given row, or null.
*
* @method getFirstTdEl
* @param row {HTMLElement} (optional) row from which to get first TD
* @return {HTMLElement} Reference to TD element.
*/
getFirstTdEl : function(row) {
var elRow = this.getTrEl(row) || this.getFirstTrEl();
if(elRow && (elRow.cells.length > 0)) {
return elRow.cells[0];
}
YAHOO.log("Could not get first TD element for row " + elRow, "info", this.toString());
return null;
},
/**
* Returns DOM reference to the last TD element in the DataTable page (by default),
* the first TD element of the optionally given row, or null.
*
* @method getLastTdEl
* @return {HTMLElement} Reference to last TD element.
*/
getLastTdEl : function(row) {
var elRow = this.getTrEl(row) || this.getLastTrEl();
if(elRow && (elRow.cells.length > 0)) {
return elRow.cells[elRow.cells.length-1];
}
YAHOO.log("Could not get last TD element for row " + elRow, "info", this.toString());
return null;
},
/**
* Returns DOM reference to the next TD element from the given cell, or null.
*
* @method getNextTdEl
* @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
* object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
* @return {HTMLElement} Reference to next TD element, or null.
*/
getNextTdEl : function(cell) {
var elCell = this.getTdEl(cell);
if(elCell) {
var nThisTdIndex = elCell.cellIndex;
var elRow = this.getTrEl(elCell);
if(nThisTdIndex < elRow.cells.length-1) {
return elRow.cells[nThisTdIndex+1];
}
else {
var elNextRow = this.getNextTrEl(elRow);
if(elNextRow) {
return elNextRow.cells[0];
}
}
}
YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
return null;
},
/**
* Returns DOM reference to the previous TD element from the given cell, or null.
*
* @method getPreviousTdEl
* @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
* object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
* @return {HTMLElement} Reference to previous TD element, or null.
*/
getPreviousTdEl : function(cell) {
var elCell = this.getTdEl(cell);
if(elCell) {
var nThisTdIndex = elCell.cellIndex;
var elRow = this.getTrEl(elCell);
if(nThisTdIndex > 0) {
return elRow.cells[nThisTdIndex-1];
}
else {
var elPreviousRow = this.getPreviousTrEl(elRow);
if(elPreviousRow) {
return this.getLastTdEl(elPreviousRow);
}
}
}
YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
return null;
},
/**
* Returns DOM reference to the above TD element from the given cell, or null.
*
* @method getAboveTdEl
* @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
* object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
* @return {HTMLElement} Reference to next TD element, or null.
*/
getAboveTdEl : function(cell) {
var elCell = this.getTdEl(cell);
if(elCell) {
var elPreviousRow = this.getPreviousTrEl(elCell);
if(elPreviousRow) {
return elPreviousRow.cells[elCell.cellIndex];
}
}
YAHOO.log("Could not get above TD element for cell " + cell, "info", this.toString());
return null;
},
/**
* Returns DOM reference to the below TD element from the given cell, or null.
*
* @method getBelowTdEl
* @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
* object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
* @return {HTMLElement} Reference to previous TD element, or null.
*/
getBelowTdEl : function(cell) {
var elCell = this.getTdEl(cell);
if(elCell) {
var elNextRow = this.getNextTrEl(elCell);
if(elNextRow) {
return elNextRow.cells[elCell.cellIndex];
}
}
YAHOO.log("Could not get below TD element for cell " + cell, "info", this.toString());
return null;
},
/**
* Returns DOM reference to a TH liner element. Needed to normalize for resizeable
* Columns, which have an additional resizer liner DIV element between the TH
* element and the liner DIV element.
*
* @method getThLinerEl
* @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
* DOM element reference, or string ID.
* @return {HTMLElement} Reference to TH liner element.
*/
getThLinerEl : function(theadCell) {
var oColumn = this.getColumn(theadCell);
return (oColumn) ? oColumn.getThLinerEl() : null;
},
/**
* Returns DOM reference to a TH element.
*
* @method getThEl
* @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
* DOM element reference, or string ID.
* @return {HTMLElement} Reference to TH element.
*/
getThEl : function(theadCell) {
var elTh;
// Validate Column instance
if(theadCell instanceof YAHOO.widget.Column) {
var oColumn = theadCell;
elTh = oColumn.getThEl();
if(elTh) {
return elTh;
}
}
// Validate HTML element
else {
var el = Dom.get(theadCell);
if(el && (el.ownerDocument == document)) {
// Validate TH element
if(el.nodeName.toLowerCase() != "th") {
// Traverse up the DOM to find the corresponding TR element
elTh = Dom.getAncestorByTagName(el,"th");
}
else {
elTh = el;
}
return elTh;
}
}
return null;
},
/**
* Returns the page row index of given row. Returns null if the row is not on the
* current DataTable page.
*
* @method getTrIndex
* @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
* string reference to an element within the DataTable page, a Record instance,
* or a Record's RecordSet index.
* @return {Number} Page row index, or null if row does not exist or is not on current page.
*/
getTrIndex : function(row) {
var nRecordIndex;
// By Record
if(row instanceof YAHOO.widget.Record) {
nRecordIndex = this._oRecordSet.getRecordIndex(row);
if(nRecordIndex === null) {
// Not a valid Record
return null;
}
}
// Calculate page row index from Record index
else if(lang.isNumber(row)) {
nRecordIndex = row;
}
if(lang.isNumber(nRecordIndex)) {
// Validate the number
if((nRecordIndex > -1) && (nRecordIndex < this._oRecordSet.getLength())) {
// DataTable is paginated
var oPaginator = this.get('paginator');
if(oPaginator) {
// Check the record index is within the indices of the
// current page
var rng = oPaginator.getPageRecords();
if (rng && nRecordIndex >= rng[0] && nRecordIndex <= rng[1]) {
// This Record is on current page
return nRecordIndex - rng[0];
}
// This Record is not on current page
else {
return null;
}
}
// Not paginated, just return the Record index
else {
return nRecordIndex;
}
}
// RecordSet index is out of range
else {
return null;
}
}
// By element reference or ID string
else {
// Validate TR element
var elRow = this.getTrEl(row);
if(elRow && (elRow.ownerDocument == document) &&
(elRow.parentNode == this._elTbody)) {
return elRow.sectionRowIndex;
}
}
YAHOO.log("Could not get page row index for row " + row, "info", this.toString());
return null;
},
// TABLE FUNCTIONS
/**
* Resets a RecordSet with the given data and populates the page view
* with the new data. Any previous data, and selection and sort states are
* cleared. New data should be added as a separate step.
*
* @method initializeTable
*/
initializeTable : function() {
// Reset init flag
this._bInit = true;
// Clear the RecordSet
this._oRecordSet.reset();
// Clear the Paginator's totalRecords if paginating
var pag = this.get('paginator');
if (pag) {
pag.set('totalRecords',0);
}
// Clear selections
this._unselectAllTrEls();
this._unselectAllTdEls();
this._aSelections = null;
this._oAnchorRecord = null;
this._oAnchorCell = null;
// Clear sort
this.set("sortedBy", null);
},
/**
* Internal wrapper calls run() on render Chain instance.
*
* @method _runRenderChain
* @private
*/
_runRenderChain : function() {
this._oChainRender.run();
},
/**
* Renders the view with existing Records from the RecordSet while
* maintaining sort, pagination, and selection states. For performance, reuses
* existing DOM elements when possible while deleting extraneous elements.
*
* @method render
*/
render : function() {
//YAHOO.example.Performance.trialStart = new Date();
this._oChainRender.stop();
this.fireEvent("beforeRenderEvent");
YAHOO.log("DataTable rendering...", "info", this.toString());
var i, j, k, len, allRecords;
var oPaginator = this.get('paginator');
// Paginator is enabled, show a subset of Records and update Paginator UI
if(oPaginator) {
allRecords = this._oRecordSet.getRecords(
oPaginator.getStartIndex(),
oPaginator.getRowsPerPage());
}
// Not paginated, show all records
else {
allRecords = this._oRecordSet.getRecords();
}
// From the top, update in-place existing rows, so as to reuse DOM elements
var elTbody = this._elTbody,
loopN = this.get("renderLoopSize"),
nRecordsLength = allRecords.length;
// Table has rows
if(nRecordsLength > 0) {
elTbody.style.display = "none";
while(elTbody.lastChild) {
elTbody.removeChild(elTbody.lastChild);
}
elTbody.style.display = "";
// Set up the loop Chain to render rows
this._oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId) {
var i = oArg.nCurrentRecord,
endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
elRow, nextSibling;
elTbody.style.display = "none";
for(; i<endRecordIndex; i++) {
elRow = Dom.get(allRecords[i].getId());
elRow = elRow || this._addTrEl(allRecords[i]);
nextSibling = elTbody.childNodes[i] || null;
elTbody.insertBefore(elRow, nextSibling);
}
elTbody.style.display = "";
// Set up for the next loop
oArg.nCurrentRecord = i;
}
},
scope: this,
iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
argument: {
nCurrentRecord: 0,//nRecordsLength-1, // Start at first Record
nLoopLength: (loopN > 0) ? loopN : nRecordsLength
},
timeout: (loopN > 0) ? 0 : -1
});
// Post-render tasks
this._oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId) {
while(elTbody.rows.length > nRecordsLength) {
elTbody.removeChild(elTbody.lastChild);
}
this._setFirstRow();
this._setLastRow();
this._setRowStripes();
this._setSelections();
}
},
scope: this,
timeout: (loopN > 0) ? 0 : -1
});
}
// Table has no rows
else {
// Set up the loop Chain to delete rows
var nTotal = elTbody.rows.length;
if(nTotal > 0) {
this._oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId) {
var i = oArg.nCurrent,
loopN = oArg.nLoopLength,
nIterEnd = (i - loopN < 0) ? -1 : i - loopN;
elTbody.style.display = "none";
for(; i>nIterEnd; i--) {
elTbody.deleteRow(-1);
}
elTbody.style.display = "";
// Set up for the next loop
oArg.nCurrent = i;
}
},
scope: this,
iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
argument: {
nCurrent: nTotal,
nLoopLength: (loopN > 0) ? loopN : nTotal
},
timeout: (loopN > 0) ? 0 : -1
});
}
}
this._runRenderChain();
},
/**
* Disables DataTable UI.
*
* @method disable
*/
disable : function() {
var elTable = this._elTable;
var elMask = this._elMask;
elMask.style.width = elTable.offsetWidth + "px";
elMask.style.height = elTable.offsetHeight + "px";
elMask.style.display = "";
this.fireEvent("disableEvent");
},
/**
* Undisables DataTable UI.
*
* @method undisable
*/
undisable : function() {
this._elMask.style.display = "none";
this.fireEvent("undisableEvent");
},
/**
* Nulls out the entire DataTable instance and related objects, removes attached
* event listeners, and clears out DOM elements inside the container. After
* calling this method, the instance reference should be expliclitly nulled by
* implementer, as in myDataTable = null. Use with caution!
*
* @method destroy
*/
destroy : function() {
// Store for later
var instanceName = this.toString();
this._oChainRender.stop();
// Destroy static resizer proxy and column proxy
DT._destroyColumnDragTargetEl();
DT._destroyColumnResizerProxyEl();
// Destroy ColumnDD and ColumnResizers
this._destroyColumnHelpers();
// Destroy all CellEditors
var oCellEditor;
for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
oCellEditor = this._oColumnSet.flat[i].editor;
if(oCellEditor && oCellEditor.destroy) {
oCellEditor.destroy();
this._oColumnSet.flat[i].editor = null;
}
}
// Destroy Paginator
this._destroyPaginator();
// Unhook custom events
this._oRecordSet.unsubscribeAll();
this.unsubscribeAll();
// Unhook DOM events
Ev.removeListener(document, "click", this._onDocumentClick);
// Clear out the container
this._destroyContainerEl(this._elContainer);
// Null out objects
for(var param in this) {
if(lang.hasOwnProperty(this, param)) {
this[param] = null;
}
}
// Clean up static values
DT._nCurrentCount--;
if(DT._nCurrentCount < 1) {
if(DT._elDynStyleNode) {
document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
DT._elDynStyleNode = null;
}
}
YAHOO.log("DataTable instance destroyed: " + instanceName);
},
/**
* Displays message within secondary TBODY.
*
* @method showTableMessage
* @param sHTML {String} (optional) Value for innerHTMlang.
* @param sClassName {String} (optional) Classname.
*/
showTableMessage : function(sHTML, sClassName) {
var elCell = this._elMsgTd;
if(lang.isString(sHTML)) {
elCell.firstChild.innerHTML = sHTML;
}
if(lang.isString(sClassName)) {
elCell.className = sClassName;
}
this._elMsgTbody.style.display = "";
this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
},
/**
* Hides secondary TBODY.
*
* @method hideTableMessage
*/
hideTableMessage : function() {
if(this._elMsgTbody.style.display != "none") {
this._elMsgTbody.style.display = "none";
this._elMsgTbody.parentNode.style.width = "";
this.fireEvent("tableMsgHideEvent");
YAHOO.log("DataTable message hidden", "info", this.toString());
}
},
/**
* Brings focus to the TBODY element. Alias to focusTbodyEl.
*
* @method focus
*/
focus : function() {
this.focusTbodyEl();
},
/**
* Brings focus to the THEAD element.
*
* @method focusTheadEl
*/
focusTheadEl : function() {
this._focusEl(this._elThead);
},
/**
* Brings focus to the TBODY element.
*
* @method focusTbodyEl
*/
focusTbodyEl : function() {
this._focusEl(this._elTbody);
},
/**
* Setting display:none on DataTable or any parent may impact width validations.
* After setting display back to "", implementers should call this method to
* manually perform those validations.
*
* @method onShow
*/
onShow : function() {
this.validateColumnWidths();
for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
col = allKeys[i];
if(col._ddResizer) {
col._ddResizer.resetResizerEl();
}
}
},
// RECORDSET FUNCTIONS
/**
* Returns Record index for given TR element or page row index.
*
* @method getRecordIndex
* @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
* element reference or page row index.
* @return {Number} Record's RecordSet index, or null.
*/
getRecordIndex : function(row) {
var nTrIndex;
if(!lang.isNumber(row)) {
// By Record
if(row instanceof YAHOO.widget.Record) {
return this._oRecordSet.getRecordIndex(row);
}
// By element reference
else {
// Find the TR element
var el = this.getTrEl(row);
if(el) {
nTrIndex = el.sectionRowIndex;
}
}
}
// By page row index
else {
nTrIndex = row;
}
if(lang.isNumber(nTrIndex)) {
var oPaginator = this.get("paginator");
if(oPaginator) {
return oPaginator.get('recordOffset') + nTrIndex;
}
else {
return nTrIndex;
}
}
YAHOO.log("Could not get Record index for row " + row, "info", this.toString());
return null;
},
/**
* For the given identifier, returns the associated Record instance.
*
* @method getRecord
* @param row {HTMLElement | Number | String} DOM reference to a TR element (or
* child of a TR element), RecordSet position index, or Record ID.
* @return {YAHOO.widget.Record} Record instance.
*/
getRecord : function(row) {
var oRecord = this._oRecordSet.getRecord(row);
if(!oRecord) {
// Validate TR element
var elRow = this.getTrEl(row);
if(elRow) {
oRecord = this._oRecordSet.getRecord(elRow.id);
}
}
if(oRecord instanceof YAHOO.widget.Record) {
return this._oRecordSet.getRecord(oRecord);
}
else {
YAHOO.log("Could not get Record for row at " + row, "info", this.toString());
return null;
}
},
// COLUMN FUNCTIONS
/**
* For the given identifier, returns the associated Column instance. Note: For
* getting Columns by Column ID string, please use the method getColumnById().
*
* @method getColumn
* @param column {HTMLElement | String | Number} TH/TD element (or child of a
* TH/TD element), a Column key, or a ColumnSet key index.
* @return {YAHOO.widget.Column} Column instance.
*/
getColumn : function(column) {
var oColumn = this._oColumnSet.getColumn(column);
if(!oColumn) {
// Validate TD element
var elCell = this.getTdEl(column);
if(elCell) {
oColumn = this._oColumnSet.getColumn(elCell.cellIndex);
}
// Validate TH element
else {
elCell = this.getThEl(column);
if(elCell) {
// Find by TH el ID
var allColumns = this._oColumnSet.flat;
for(var i=0, len=allColumns.length; i<len; i++) {
if(allColumns[i].getThEl().id === elCell.id) {
oColumn = allColumns[i];
}
}
}
}
}
if(!oColumn) {
YAHOO.log("Could not get Column for column at " + column, "info", this.toString());
}
return oColumn;
},
/**
* For the given Column ID, returns the associated Column instance. Note: For
* getting Columns by key, please use the method getColumn().
*
* @method getColumnById
* @param column {String} Column ID string.
* @return {YAHOO.widget.Column} Column instance.
*/
getColumnById : function(column) {
return this._oColumnSet.getColumnById(column);
},
/**
* For the given Column instance, returns next direction to sort.
*
* @method getColumnSortDir
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oSortedBy {Object} (optional) Specify the state, or use current state.
* @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
*/
getColumnSortDir : function(oColumn, oSortedBy) {
// Backward compatibility
if(oColumn.sortOptions && oColumn.sortOptions.defaultOrder) {
if(oColumn.sortOptions.defaultOrder == "asc") {
oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
}
else if (oColumn.sortOptions.defaultOrder == "desc") {
oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
}
}
// What is the Column's default sort direction?
var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
// Is the Column currently sorted?
var bSorted = false;
oSortedBy = oSortedBy || this.get("sortedBy");
if(oSortedBy && (oSortedBy.key === oColumn.key)) {
bSorted = true;
if(oSortedBy.dir) {
sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
}
else {
sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
}
}
return sortDir;
},
/**
* Overridable method gives implementers a hook to show loading message before
* sorting Column.
*
* @method doBeforeSortColumn
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
* YAHOO.widget.DataTable.CLASS_DESC.
* @return {Boolean} Return true to continue sorting Column.
*/
doBeforeSortColumn : function(oColumn, sSortDir) {
this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
return true;
},
/**
* Sorts given Column. If "dynamicData" is true, current selections are purged before
* a request is sent to the DataSource for data for the new state (using the
* request returned by "generateRequest()").
*
* @method sortColumn
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
* YAHOO.widget.DataTable.CLASS_DESC
*/
sortColumn : function(oColumn, sDir) {
if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
if(!oColumn.sortable) {
Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
}
// Validate given direction
if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
sDir = null;
}
// Get the sort dir
var sSortDir = sDir || this.getColumnSortDir(oColumn);
// Is the Column currently sorted?
var oSortedBy = this.get("sortedBy") || {};
var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
var ok = this.doBeforeSortColumn(oColumn, sSortDir);
if(ok) {
// Server-side sort
if(this.get("dynamicData")) {
// Get current state
var oState = this.getState();
// Reset record offset, if paginated
if(oState.pagination) {
oState.pagination.recordOffset = 0;
}
// Update sortedBy to new values
oState.sortedBy = {
key: oColumn.key,
dir: sSortDir
};
// Get the request for the new state
var request = this.get("generateRequest")(oState, this);
// Purge selections
this.unselectAllRows();
this.unselectAllCells();
// Send request for new data
var callback = {
success : this.onDataReturnSetRows,
failure : this.onDataReturnSetRows,
argument : oState, // Pass along the new state to the callback
scope : this
};
this._oDataSource.sendRequest(request, callback);
}
// Client-side sort
else {
// Is there a custom sort handler function defined?
var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
// Custom sort function
oColumn.sortOptions.sortFunction : null;
// Sort the Records
if(!bSorted || sDir || sortFnc) {
// Shortcut for the frequently-used compare method
var compare = YAHOO.util.Sort.compare;
// Default sort function if necessary
sortFnc = sortFnc ||
function(a, b, desc, field) {
var sorted = compare(a.getData(field),b.getData(field), desc);
if(sorted === 0) {
return compare(a.getCount(),b.getCount(), desc); // Bug 1932978
}
else {
return sorted;
}
};
// Get the field to sort
var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
// Sort the Records
this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false), sField);
}
// Just reverse the Records
else {
this._oRecordSet.reverseRecords();
}
// Reset to first page if paginated
var oPaginator = this.get('paginator');
if (oPaginator) {
// Set page silently, so as not to fire change event.
oPaginator.setPage(1,true);
}
// Update UI via sortedBy
this.render();
this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn});
}
this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
YAHOO.log("Column \"" + oColumn.key + "\" sorted \"" + sSortDir + "\"", "info", this.toString());
return;
}
}
YAHOO.log("Could not sort Column \"" + oColumn.key + "\"", "warn", this.toString());
},
/**
* Sets given Column to given pixel width. If new width is less than minimum
* width, sets to minimum width. Updates oColumn.width value.
*
* @method setColumnWidth
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
* subject to minWidth and maxAutoWidth validations.
*/
setColumnWidth : function(oColumn, nWidth) {
if(!(oColumn instanceof YAHOO.widget.Column)) {
oColumn = this.getColumn(oColumn);
}
if(oColumn) {
// Validate new width against minimum width
if(lang.isNumber(nWidth)) {
// This is why we must require a Number... :-|
nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
// Save state
oColumn.width = nWidth;
// Resize the DOM elements
this._setColumnWidth(oColumn, nWidth+"px");
this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString());
}
// Unsets a width to auto-size
else if(nWidth === null) {
// Save state
oColumn.width = nWidth;
// Resize the DOM elements
this._setColumnWidth(oColumn, "auto");
this.validateColumnWidths(oColumn);
this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
YAHOO.log("Column " + oColumn + " width unset", "info", this.toString());
}
// Bug 2339454: resize then sort misaligment
this._clearTrTemplateEl();
}
else {
YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString());
}
},
/**
* Sets liner DIV elements of given Column to given width. When value should be
* auto-calculated to fit content overflow is set to visible, otherwise overflow
* is set to hidden. No validations against minimum width and no updating
* Column.width value.
*
* @method _setColumnWidth
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param sWidth {String} New width value.
* @param sOverflow {String} Should be "hidden" when Column width is explicitly
* being set to a value, but should be "visible" when Column is meant to auto-fit content.
* @private
*/
_setColumnWidth : function(oColumn, sWidth, sOverflow) {
if(oColumn && (oColumn.getKeyIndex() !== null)) {
sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
// Dynamic style algorithm
if(!DT._bDynStylesFallback) {
this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
}
// Dynamic function algorithm
else {
this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
}
}
else {
YAHOO.log("Could not set width of unknown Column " + oColumn + " to " + sWidth, "warn", this.toString());
}
},
/**
* Updates width of a Column's liner DIV elements by dynamically creating a
* STYLE node and writing and updating CSS style rules to it. If this fails during
* runtime, the fallback method _setColumnWidthDynFunction() will be called.
* Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
* nested within another TABLE element. For these cases, it is recommended to
* use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
*
* @method _setColumnWidthDynStyles
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param sWidth {String} New width value.
* @private
*/
_setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
var s = DT._elDynStyleNode,
rule;
// Create a new STYLE node
if(!s) {
s = document.createElement('style');
s.type = 'text/css';
s = document.getElementsByTagName('head').item(0).appendChild(s);
DT._elDynStyleNode = s;
}
// We have a STYLE node to update
if(s) {
// Use unique classname for this Column instance as a hook for resizing
var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
// Hide for performance
if(this._elTbody) {
this._elTbody.style.display = 'none';
}
rule = DT._oDynStyles[sClassname];
// The Column does not yet have a rule
if(!rule) {
if(s.styleSheet && s.styleSheet.addRule) {
s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
s.styleSheet.addRule(sClassname,'width:'+sWidth);
rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
DT._oDynStyles[sClassname] = rule;
}
else if(s.sheet && s.sheet.insertRule) {
s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
DT._oDynStyles[sClassname] = rule;
}
}
// We have a rule to update
else {
rule.style.overflow = sOverflow;
rule.style.width = sWidth;
}
// Unhide
if(this._elTbody) {
this._elTbody.style.display = '';
}
}
// That was not a success, we must call the fallback routine
if(!rule) {
DT._bDynStylesFallback = true;
this._setColumnWidthDynFunction(oColumn, sWidth);
}
},
/**
* Updates width of a Column's liner DIV elements by dynamically creating a
* function to update all element style properties in one pass. Note: This
* technique is not supported in sandboxed environments that prohibit EVALs.
*
* @method _setColumnWidthDynFunction
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param sWidth {String} New width value.
* @private
*/
_setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
// TODO: why is this here?
if(sWidth == 'auto') {
sWidth = '';
}
// Create one function for each value of rows.length
var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
// Dynamically create the function
if (!this._aDynFunctions[rowslen]) {
//Compile a custom function to do all the liner div width
//assignments at the same time. A unique function is required
//for each unique number of rows in _elTbody. This will
//result in a function declaration like:
//function (oColumn,sWidth,sOverflow) {
// var colIdx = oColumn.getKeyIndex();
// oColumn.getThLinerEl().style.overflow =
// this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
// this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
// ... (for all row indices in this._elTbody.rows.length - 1)
// this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
// sOverflow;
// oColumn.getThLinerEl().style.width =
// this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
// this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
// ... (for all row indices in this._elTbody.rows.length - 1)
// this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
// sWidth;
//}
var i,j,k;
var resizerDef = [
'var colIdx=oColumn.getKeyIndex();',
'oColumn.getThLinerEl().style.overflow='
];
for (i=rowslen-1, j=2; i >= 0; --i) {
resizerDef[j++] = 'this._elTbody.rows[';
resizerDef[j++] = i;
resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
}
resizerDef[j] = 'sOverflow;';
resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
for (i=rowslen-1, k=j+2; i >= 0; --i) {
resizerDef[k++] = 'this._elTbody.rows[';
resizerDef[k++] = i;
resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
}
resizerDef[k] = 'sWidth;';
this._aDynFunctions[rowslen] =
new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
}
// Get the function to execute
var resizerFn = this._aDynFunctions[rowslen];
// TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
if (resizerFn) {
resizerFn.call(this,oColumn,sWidth,sOverflow);
}
},
/**
* For one or all Columns, when Column is not hidden, width is not set, and minWidth
* and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
*
* @method validateColumnWidths
* @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
*/
validateColumnWidths : function(oColumn) {
var elColgroup = this._elColgroup;
var elColgroupClone = elColgroup.cloneNode(true);
var bNeedsValidation = false;
var allKeys = this._oColumnSet.keys;
var elThLiner;
// Validate just one Column's minWidth and/or maxAutoWidth
if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
elThLiner = oColumn.getThLinerEl();
if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width =
oColumn.minWidth +
(parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
(parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
bNeedsValidation = true;
}
else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
}
}
// Validate all Columns
else {
for(var i=0, len=allKeys.length; i<len; i++) {
oColumn = allKeys[i];
if(!oColumn.hidden && !oColumn.width) {
elThLiner = oColumn.getThLinerEl();
if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
elColgroupClone.childNodes[i].style.width =
oColumn.minWidth +
(parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
(parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
bNeedsValidation = true;
}
else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
}
}
}
}
if(bNeedsValidation) {
elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
this._elColgroup = elColgroupClone;
}
},
/**
* Clears minWidth.
*
* @method _clearMinWidth
* @param oColumn {YAHOO.widget.Column} Which Column.
* @private
*/
_clearMinWidth : function(oColumn) {
if(oColumn.getKeyIndex() !== null) {
this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
}
},
/**
* Restores minWidth.
*
* @method _restoreMinWidth
* @param oColumn {YAHOO.widget.Column} Which Column.
* @private
*/
_restoreMinWidth : function(oColumn) {
if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
}
},
/**
* Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
* hide/show non-nested Columns, and top-level parent Columns (which will
* hide/show all children Columns).
*
* @method hideColumn
* @param oColumn {YAHOO.widget.Column} Column instance.
*/
hideColumn : function(oColumn) {
if(!(oColumn instanceof YAHOO.widget.Column)) {
oColumn = this.getColumn(oColumn);
}
// Only top-level Columns can get hidden due to issues in FF2 and SF3
if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
var allrows = this.getTbodyEl().rows;
var l = allrows.length;
var allDescendants = this._oColumnSet.getDescendants(oColumn);
// Hide each nested Column
for(var i=0; i<allDescendants.length; i++) {
var thisColumn = allDescendants[i];
thisColumn.hidden = true;
// Style the head cell
Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
// Does this Column have body cells?
var thisKeyIndex = thisColumn.getKeyIndex();
if(thisKeyIndex !== null) {
// Clear minWidth
this._clearMinWidth(oColumn);
// Style the body cells
for(var j=0;j<l;j++) {
Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
}
}
this.fireEvent("columnHideEvent",{column:thisColumn});
YAHOO.log("Column \"" + oColumn.key + "\" hidden", "info", this.toString());
}
this._repaintOpera();
this._clearTrTemplateEl();
}
else {
YAHOO.log("Could not hide Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be hidden", "warn", this.toString());
}
},
/**
* Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
* hide/show non-nested Columns, and top-level parent Columns (which will
* hide/show all children Columns).
*
* @method showColumn
* @param oColumn {YAHOO.widget.Column} Column instance.
*/
showColumn : function(oColumn) {
if(!(oColumn instanceof YAHOO.widget.Column)) {
oColumn = this.getColumn(oColumn);
}
// Only top-level Columns can get hidden
if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
var allrows = this.getTbodyEl().rows;
var l = allrows.length;
var allDescendants = this._oColumnSet.getDescendants(oColumn);
// Show each nested Column
for(var i=0; i<allDescendants.length; i++) {
var thisColumn = allDescendants[i];
thisColumn.hidden = false;
// Unstyle the head cell
Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
// Does this Column have body cells?
var thisKeyIndex = thisColumn.getKeyIndex();
if(thisKeyIndex !== null) {
// Restore minWidth
this._restoreMinWidth(oColumn);
// Unstyle the body cells
for(var j=0;j<l;j++) {
Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
}
}
this.fireEvent("columnShowEvent",{column:thisColumn});
YAHOO.log("Column \"" + oColumn.key + "\" shown", "info", this.toString());
}
this._clearTrTemplateEl();
}
else {
YAHOO.log("Could not show Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be shown", "warn", this.toString());
}
},
/**
* Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
* non-nested Columns, and top-level parent Columns (which will remove all
* children Columns).
*
* @method removeColumn
* @param oColumn {YAHOO.widget.Column} Column instance.
* @return oColumn {YAHOO.widget.Column} Removed Column instance.
*/
removeColumn : function(oColumn) {
// Validate Column
if(!(oColumn instanceof YAHOO.widget.Column)) {
oColumn = this.getColumn(oColumn);
}
if(oColumn) {
var nColTreeIndex = oColumn.getTreeIndex();
if(nColTreeIndex !== null) {
// Which key index(es)
var i, len,
aKeyIndexes = oColumn.getKeyIndex();
// Must be a parent Column
if(aKeyIndexes === null) {
var descKeyIndexes = [];
var allDescendants = this._oColumnSet.getDescendants(oColumn);
for(i=0, len=allDescendants.length; i<len; i++) {
// Is this descendant a key Column?
var thisKey = allDescendants[i].getKeyIndex();
if(thisKey !== null) {
descKeyIndexes[descKeyIndexes.length] = thisKey;
}
}
if(descKeyIndexes.length > 0) {
aKeyIndexes = descKeyIndexes;
}
}
// Must be a key Column
else {
aKeyIndexes = [aKeyIndexes];
}
if(aKeyIndexes !== null) {
// Sort the indexes so we can remove from the right
aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
// Destroy previous THEAD
this._destroyTheadEl();
// Create new THEAD
var aOrigColumnDefs = this._oColumnSet.getDefinitions();
oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
this._initColumnSet(aOrigColumnDefs);
this._initTheadEl();
// Remove COL
for(i=aKeyIndexes.length-1; i>-1; i--) {
this._removeColgroupColEl(aKeyIndexes[i]);
}
// Remove TD
var allRows = this._elTbody.rows;
if(allRows.length > 0) {
var loopN = this.get("renderLoopSize"),
loopEnd = allRows.length;
this._oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId) {
var i = oArg.nCurrentRow,
len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
aIndexes = oArg.aIndexes,
j;
for(; i < len; ++i) {
for(j = aIndexes.length-1; j>-1; j--) {
allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
}
}
oArg.nCurrentRow = i;
}
},
iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
scope: this,
timeout: (loopN > 0) ? 0 : -1
});
this._runRenderChain();
}
this.fireEvent("columnRemoveEvent",{column:oColumn});
YAHOO.log("Column \"" + oColumn.key + "\" removed", "info", this.toString());
return oColumn;
}
}
}
YAHOO.log("Could not remove Column \"" + oColumn.key + "\". Only non-nested Columns can be removed", "warn", this.toString());
},
/**
* Inserts given Column at the index if given, otherwise at the end. NOTE: You
* can only add non-nested Columns and top-level parent Columns. You cannot add
* a nested Column to an existing parent.
*
* @method insertColumn
* @param oColumn {Object | YAHOO.widget.Column} Object literal Column
* definition or a Column instance.
* @param index {Number} (optional) New tree index.
* @return oColumn {YAHOO.widget.Column} Inserted Column instance.
*/
insertColumn : function(oColumn, index) {
// Validate Column
if(oColumn instanceof YAHOO.widget.Column) {
oColumn = oColumn.getDefinition();
}
else if(oColumn.constructor !== Object) {
YAHOO.log("Could not insert Column \"" + oColumn + "\" due to invalid argument", "warn", this.toString());
return;
}
// Validate index or append new Column to the end of the ColumnSet
var oColumnSet = this._oColumnSet;
if(!lang.isValue(index) || !lang.isNumber(index)) {
index = oColumnSet.tree[0].length;
}
// Destroy previous THEAD
this._destroyTheadEl();
// Create new THEAD
var aNewColumnDefs = this._oColumnSet.getDefinitions();
aNewColumnDefs.splice(index, 0, oColumn);
this._initColumnSet(aNewColumnDefs);
this._initTheadEl();
// Need to refresh the reference
oColumnSet = this._oColumnSet;
var oNewColumn = oColumnSet.tree[0][index];
// Get key index(es) for new Column
var i, len,
descKeyIndexes = [];
var allDescendants = oColumnSet.getDescendants(oNewColumn);
for(i=0, len=allDescendants.length; i<len; i++) {
// Is this descendant a key Column?
var thisKey = allDescendants[i].getKeyIndex();
if(thisKey !== null) {
descKeyIndexes[descKeyIndexes.length] = thisKey;
}
}
if(descKeyIndexes.length > 0) {
// Sort the indexes
var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
// Add COL
for(i=descKeyIndexes.length-1; i>-1; i--) {
this._insertColgroupColEl(descKeyIndexes[i]);
}
// Add TD
var allRows = this._elTbody.rows;
if(allRows.length > 0) {
var loopN = this.get("renderLoopSize"),
loopEnd = allRows.length;
// Get templates for each new TD
var aTdTemplates = [],
elTdTemplate;
for(i=0, len=descKeyIndexes.length; i<len; i++) {
var thisKeyIndex = descKeyIndexes[i];
elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
aTdTemplates[thisKeyIndex] = elTdTemplate;
}
this._oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId) {
var i = oArg.nCurrentRow, j,
descKeyIndexes = oArg.descKeyIndexes,
len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
nextSibling;
for(; i < len; ++i) {
nextSibling = allRows[i].childNodes[newIndex] || null;
for(j=descKeyIndexes.length-1; j>-1; j--) {
allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
}
}
oArg.nCurrentRow = i;
}
},
iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
scope: this,
timeout: (loopN > 0) ? 0 : -1
});
this._runRenderChain();
}
this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
YAHOO.log("Column \"" + oColumn.key + "\" inserted into index " + index, "info", this.toString());
return oNewColumn;
}
},
/**
* Removes given Column and inserts into given tree index. NOTE: You
* can only reorder non-nested Columns and top-level parent Columns. You cannot
* reorder a nested Column to an existing parent.
*
* @method reorderColumn
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param index {Number} New tree index.
* @return oColumn {YAHOO.widget.Column} Reordered Column instance.
*/
reorderColumn : function(oColumn, index) {
// Validate Column and new index
if(!(oColumn instanceof YAHOO.widget.Column)) {
oColumn = this.getColumn(oColumn);
}
if(oColumn && YAHOO.lang.isNumber(index)) {
var nOrigTreeIndex = oColumn.getTreeIndex();
if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
// Which key index(es)
var i, len,
aOrigKeyIndexes = oColumn.getKeyIndex(),
allDescendants,
descKeyIndexes = [],
thisKey;
// Must be a parent Column...
if(aOrigKeyIndexes === null) {
allDescendants = this._oColumnSet.getDescendants(oColumn);
for(i=0, len=allDescendants.length; i<len; i++) {
// Is this descendant a key Column?
thisKey = allDescendants[i].getKeyIndex();
if(thisKey !== null) {
descKeyIndexes[descKeyIndexes.length] = thisKey;
}
}
if(descKeyIndexes.length > 0) {
aOrigKeyIndexes = descKeyIndexes;
}
}
// ...or else must be a key Column
else {
aOrigKeyIndexes = [aOrigKeyIndexes];
}
if(aOrigKeyIndexes !== null) {
// Sort the indexes
aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
// Destroy previous THEAD
this._destroyTheadEl();
// Create new THEAD
var aColumnDefs = this._oColumnSet.getDefinitions();
var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
aColumnDefs.splice(index, 0, oColumnDef);
this._initColumnSet(aColumnDefs);
this._initTheadEl();
// Need to refresh the reference
var oNewColumn = this._oColumnSet.tree[0][index];
// What are new key index(es)
var aNewKeyIndexes = oNewColumn.getKeyIndex();
// Must be a parent Column
if(aNewKeyIndexes === null) {
descKeyIndexes = [];
allDescendants = this._oColumnSet.getDescendants(oNewColumn);
for(i=0, len=allDescendants.length; i<len; i++) {
// Is this descendant a key Column?
thisKey = allDescendants[i].getKeyIndex();
if(thisKey !== null) {
descKeyIndexes[descKeyIndexes.length] = thisKey;
}
}
if(descKeyIndexes.length > 0) {
aNewKeyIndexes = descKeyIndexes;
}
}
// Must be a key Column
else {
aNewKeyIndexes = [aNewKeyIndexes];
}
// Sort the new indexes and grab the first one for the new location
var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
// Reorder COL
this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
// Reorder TD
var allRows = this._elTbody.rows;
if(allRows.length > 0) {
var loopN = this.get("renderLoopSize"),
loopEnd = allRows.length;
this._oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId) {
var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
aIndexes = oArg.aIndexes, thisTr;
// For each row
for(; i < len; ++i) {
tmpTds = [];
thisTr = allRows[i];
// Remove each TD
for(j=aIndexes.length-1; j>-1; j--) {
tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
}
// Insert each TD
nextSibling = thisTr.childNodes[newIndex] || null;
for(j=tmpTds.length-1; j>-1; j--) {
thisTr.insertBefore(tmpTds[j], nextSibling);
}
}
oArg.nCurrentRow = i;
}
},
iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
scope: this,
timeout: (loopN > 0) ? 0 : -1
});
this._runRenderChain();
}
this.fireEvent("columnReorderEvent",{column:oNewColumn});
YAHOO.log("Column \"" + oNewColumn.key + "\" reordered", "info", this.toString());
return oNewColumn;
}
}
}
YAHOO.log("Could not reorder Column \"" + oColumn.key + "\". Only non-nested Columns can be reordered", "warn", this.toString());
},
/**
* Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
* select/unselect non-nested Columns, and bottom-level key Columns.
*
* @method selectColumn
* @param column {HTMLElement | String | Number} DOM reference or ID string to a
* TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
*/
selectColumn : function(oColumn) {
oColumn = this.getColumn(oColumn);
if(oColumn && !oColumn.selected) {
// Only bottom-level Columns can get hidden
if(oColumn.getKeyIndex() !== null) {
oColumn.selected = true;
// Update head cell
var elTh = oColumn.getThEl();
Dom.addClass(elTh,DT.CLASS_SELECTED);
// Update body cells
var allRows = this.getTbodyEl().rows;
var oChainRender = this._oChainRender;
oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);
}
oArg.rowIndex++;
},
scope: this,
iterations: allRows.length,
argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
});
this._clearTrTemplateEl();
this._elTbody.style.display = "none";
this._runRenderChain();
this._elTbody.style.display = "";
this.fireEvent("columnSelectEvent",{column:oColumn});
YAHOO.log("Column \"" + oColumn.key + "\" selected", "info", this.toString());
}
else {
YAHOO.log("Could not select Column \"" + oColumn.key + "\". Only non-nested Columns can be selected", "warn", this.toString());
}
}
},
/**
* Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
* select/unselect non-nested Columns, and bottom-level key Columns.
*
* @method unselectColumn
* @param column {HTMLElement | String | Number} DOM reference or ID string to a
* TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
*/
unselectColumn : function(oColumn) {
oColumn = this.getColumn(oColumn);
if(oColumn && oColumn.selected) {
// Only bottom-level Columns can get hidden
if(oColumn.getKeyIndex() !== null) {
oColumn.selected = false;
// Update head cell
var elTh = oColumn.getThEl();
Dom.removeClass(elTh,DT.CLASS_SELECTED);
// Update body cells
var allRows = this.getTbodyEl().rows;
var oChainRender = this._oChainRender;
oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);
}
oArg.rowIndex++;
},
scope: this,
iterations:allRows.length,
argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
});
this._clearTrTemplateEl();
this._elTbody.style.display = "none";
this._runRenderChain();
this._elTbody.style.display = "";
this.fireEvent("columnUnselectEvent",{column:oColumn});
YAHOO.log("Column \"" + oColumn.key + "\" unselected", "info", this.toString());
}
else {
YAHOO.log("Could not unselect Column \"" + oColumn.key + "\". Only non-nested Columns can be unselected", "warn", this.toString());
}
}
},
/**
* Returns an array selected Column instances.
*
* @method getSelectedColumns
* @return {YAHOO.widget.Column[]} Array of Column instances.
*/
getSelectedColumns : function(oColumn) {
var selectedColumns = [];
var aKeys = this._oColumnSet.keys;
for(var i=0,len=aKeys.length; i<len; i++) {
if(aKeys[i].selected) {
selectedColumns[selectedColumns.length] = aKeys[i];
}
}
return selectedColumns;
},
/**
* Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
* NOTE: You cannot highlight/unhighlight nested Columns. You can only
* highlight/unhighlight non-nested Columns, and bottom-level key Columns.
*
* @method highlightColumn
* @param column {HTMLElement | String | Number} DOM reference or ID string to a
* TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
*/
highlightColumn : function(column) {
var oColumn = this.getColumn(column);
// Only bottom-level Columns can get highlighted
if(oColumn && (oColumn.getKeyIndex() !== null)) {
// Update head cell
var elTh = oColumn.getThEl();
Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
// Update body cells
var allRows = this.getTbodyEl().rows;
var oChainRender = this._oChainRender;
oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
}
oArg.rowIndex++;
},
scope: this,
iterations:allRows.length,
argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
timeout: -1
});
this._elTbody.style.display = "none";
this._runRenderChain();
this._elTbody.style.display = "";
this.fireEvent("columnHighlightEvent",{column:oColumn});
YAHOO.log("Column \"" + oColumn.key + "\" highlighed", "info", this.toString());
}
else {
YAHOO.log("Could not highlight Column \"" + oColumn.key + "\". Only non-nested Columns can be highlighted", "warn", this.toString());
}
},
/**
* Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
* NOTE: You cannot highlight/unhighlight nested Columns. You can only
* highlight/unhighlight non-nested Columns, and bottom-level key Columns.
*
* @method unhighlightColumn
* @param column {HTMLElement | String | Number} DOM reference or ID string to a
* TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
*/
unhighlightColumn : function(column) {
var oColumn = this.getColumn(column);
// Only bottom-level Columns can get highlighted
if(oColumn && (oColumn.getKeyIndex() !== null)) {
// Update head cell
var elTh = oColumn.getThEl();
Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
// Update body cells
var allRows = this.getTbodyEl().rows;
var oChainRender = this._oChainRender;
oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
}
oArg.rowIndex++;
},
scope: this,
iterations:allRows.length,
argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
timeout: -1
});
this._elTbody.style.display = "none";
this._runRenderChain();
this._elTbody.style.display = "";
this.fireEvent("columnUnhighlightEvent",{column:oColumn});
YAHOO.log("Column \"" + oColumn.key + "\" unhighlighted", "info", this.toString());
}
else {
YAHOO.log("Could not unhighlight Column \"" + oColumn.key + "\". Only non-nested Columns can be unhighlighted", "warn", this.toString());
}
},
// ROW FUNCTIONS
/**
* Adds one new Record of data into the RecordSet at the index if given,
* otherwise at the end. If the new Record is in page view, the
* corresponding DOM elements are also updated.
*
* @method addRow
* @param oData {Object} Object literal of data for the row.
* @param index {Number} (optional) RecordSet position index at which to add data.
*/
addRow : function(oData, index) {
if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
return;
}
if(oData && lang.isObject(oData)) {
var oRecord = this._oRecordSet.addRecord(oData, index);
if(oRecord) {
var recIndex;
var oPaginator = this.get('paginator');
// Paginated
if (oPaginator) {
// Update the paginator's totalRecords
var totalRecords = oPaginator.get('totalRecords');
if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
oPaginator.set('totalRecords',totalRecords + 1);
}
recIndex = this.getRecordIndex(oRecord);
var endRecIndex = (oPaginator.getPageRecords())[1];
// New record affects the view
if (recIndex <= endRecIndex) {
// Defer UI updates to the render method
this.render();
}
this.fireEvent("rowAddEvent", {record:oRecord});
YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString());
return;
}
// Not paginated
else {
recIndex = this.getTrIndex(oRecord);
if(lang.isNumber(recIndex)) {
// Add the TR element
this._oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId) {
var oRecord = oArg.record;
var recIndex = oArg.recIndex;
var elNewTr = this._addTrEl(oRecord);
if(elNewTr) {
var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
this._elTbody.insertBefore(elNewTr, elNext);
// Set FIRST/LAST
if(recIndex === 0) {
this._setFirstRow();
}
if(elNext === null) {
this._setLastRow();
}
// Set EVEN/ODD
this._setRowStripes();
this.hideTableMessage();
this.fireEvent("rowAddEvent", {record:oRecord});
YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString());
}
}
},
argument: {record: oRecord, recIndex: recIndex},
scope: this,
timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
});
this._runRenderChain();
return;
}
}
}
}
YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
},
/**
* Convenience method to add multiple rows.
*
* @method addRows
* @param aData {Object[]} Array of object literal data for the rows.
* @param index {Number} (optional) RecordSet position index at which to add data.
*/
addRows : function(aData, index) {
if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());
return;
}
if(lang.isArray(aData)) {
var aRecords = this._oRecordSet.addRecords(aData, index);
if(aRecords) {
var recIndex = this.getRecordIndex(aRecords[0]);
// Paginated
var oPaginator = this.get('paginator');
if (oPaginator) {
// Update the paginator's totalRecords
var totalRecords = oPaginator.get('totalRecords');
if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
oPaginator.set('totalRecords',totalRecords + aRecords.length);
}
var endRecIndex = (oPaginator.getPageRecords())[1];
// At least one of the new records affects the view
if (recIndex <= endRecIndex) {
this.render();
}
this.fireEvent("rowsAddEvent", {records:aRecords});
YAHOO.log("Added " + aRecords.length +
" rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
" with data " + lang.dump(aData), "info", this.toString());
return;
}
// Not paginated
else {
// Add the TR elements
var loopN = this.get("renderLoopSize");
var loopEnd = recIndex + aData.length;
var nRowsNeeded = (loopEnd - recIndex); // how many needed
var isLast = (recIndex >= this._elTbody.rows.length);
this._oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId) {
var aRecords = oArg.aRecords,
i = oArg.nCurrentRow,
j = oArg.nCurrentRecord,
len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
df = document.createDocumentFragment(),
elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
for(; i < len; i++, j++) {
df.appendChild(this._addTrEl(aRecords[j]));
}
this._elTbody.insertBefore(df, elNext);
oArg.nCurrentRow = i;
oArg.nCurrentRecord = j;
}
},
iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
scope: this,
timeout: (loopN > 0) ? 0 : -1
});
this._oChainRender.add({
method: function(oArg) {
var recIndex = oArg.recIndex;
// Set FIRST/LAST
if(recIndex === 0) {
this._setFirstRow();
}
if(oArg.isLast) {
this._setLastRow();
}
// Set EVEN/ODD
this._setRowStripes();
this.fireEvent("rowsAddEvent", {records:aRecords});
YAHOO.log("Added " + aRecords.length +
" rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
" with data " + lang.dump(aData), "info", this.toString());
},
argument: {recIndex: recIndex, isLast: isLast},
scope: this,
timeout: -1 // Needs to run immediately after the DOM insertions above
});
this._runRenderChain();
this.hideTableMessage();
return;
}
}
}
YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());
},
/**
* For the given row, updates the associated Record with the given data. If the
* row is on current page, the corresponding DOM elements are also updated.
*
* @method updateRow
* @param row {YAHOO.widget.Record | Number | HTMLElement | String}
* Which row to update: By Record instance, by Record's RecordSet
* position index, by HTMLElement reference to the TR element, or by ID string
* of the TR element.
* @param oData {Object} Object literal of data for the row.
*/
updateRow : function(row, oData) {
var index = row;
if (!lang.isNumber(index)) {
index = this.getRecordIndex(row);
}
// Update the Record
if(lang.isNumber(index) && (index >= 0)) {
var oRecordSet = this._oRecordSet,
oldRecord = oRecordSet.getRecord(index);
if(oldRecord) {
var updatedRecord = this._oRecordSet.setRecord(oData, index),
elRow = this.getTrEl(oldRecord),
// Copy data from the Record for the event that gets fired later
oldData = oldRecord ? oldRecord.getData() : null;
if(updatedRecord) {
// Update selected rows as necessary
var tracker = this._aSelections || [],
i=0,
oldId = oldRecord.getId(),
newId = updatedRecord.getId();
for(; i<tracker.length; i++) {
if((tracker[i] === oldId)) {
tracker[i] = newId;
}
else if(tracker[i].recordId === oldId) {
tracker[i].recordId = newId;
}
}
// Update the TR only if row is on current page
this._oChainRender.add({
method: function() {
if((this instanceof DT) && this._sId) {
// Paginated
var oPaginator = this.get('paginator');
if (oPaginator) {
var pageStartIndex = (oPaginator.getPageRecords())[0],
pageLastIndex = (oPaginator.getPageRecords())[1];
// At least one of the new records affects the view
if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
this.render();
}
}
else {
if(elRow) {
this._updateTrEl(elRow, updatedRecord);
}
else {
this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
}
}
this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
YAHOO.log("DataTable row updated: Record ID = " + updatedRecord.getId() +
", Record index = " + this.getRecordIndex(updatedRecord) +
", page row index = " + this.getTrIndex(updatedRecord), "info", this.toString());
}
},
scope: this,
timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
});
this._runRenderChain();
return;
}
}
}
YAHOO.log("Could not update row " + row + " with the data : " + lang.dump(oData), "warn", this.toString());
return;
},
/**
* Starting with the given row, updates associated Records with the given data.
* The number of rows to update are determined by the array of data provided.
* Undefined data (i.e., not an object literal) causes a row to be skipped. If
* any of the rows are on current page, the corresponding DOM elements are also
* updated.
*
* @method updateRows
* @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
* Starting row to update: By Record instance, by Record's RecordSet
* position index, by HTMLElement reference to the TR element, or by ID string
* of the TR element.
* @param aData {Object[]} Array of object literal of data for the rows.
*/
updateRows : function(startrow, aData) {
if(lang.isArray(aData)) {
var startIndex = startrow,
oRecordSet = this._oRecordSet;
if (!lang.isNumber(startrow)) {
startIndex = this.getRecordIndex(startrow);
}
if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
var lastIndex = startIndex + aData.length,
aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
aNewRecords = oRecordSet.setRecords(aData, startIndex);
if(aNewRecords) {
// Update selected rows as necessary
var tracker = this._aSelections || [],
i=0, j, newId, oldId;
for(; i<tracker.length; i++) {
for(j=0; j<aOldRecords.length; j++) {
oldId = aOldRecords[j].getId();
if((tracker[i] === oldId)) {
tracker[i] = aNewRecords[j].getId();
}
else if(tracker[i].recordId === oldId) {
tracker[i].recordId = aNewRecords[j].getId();
}
}
}
// Paginated
var oPaginator = this.get('paginator');
if (oPaginator) {
var pageStartIndex = (oPaginator.getPageRecords())[0],
pageLastIndex = (oPaginator.getPageRecords())[1];
// At least one of the new records affects the view
if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
this.render();
}
this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
YAHOO.log("Added " + aNewRecords.length +
" rows starting at index " + startIndex +
" with data " + lang.dump(aData), "info", this.toString());
return;
}
// Not paginated
else {
// Update the TR elements
var loopN = this.get("renderLoopSize"),
rowCount = aData.length, // how many needed
lastRowIndex = this._elTbody.rows.length,
isLast = (lastIndex >= lastRowIndex),
isAdding = (lastIndex > lastRowIndex);
this._oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId) {
var aRecords = oArg.aRecords,
i = oArg.nCurrentRow,
j = oArg.nDataPointer,
len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
for(; i < len; i++,j++) {
if(isAdding && (i>=lastRowIndex)) {
this._elTbody.appendChild(this._addTrEl(aRecords[j]));
}
else {
this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
}
}
oArg.nCurrentRow = i;
oArg.nDataPointer = j;
}
},
iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
scope: this,
timeout: (loopN > 0) ? 0 : -1
});
this._oChainRender.add({
method: function(oArg) {
var recIndex = oArg.recIndex;
// Set FIRST/LAST
if(recIndex === 0) {
this._setFirstRow();
}
if(oArg.isLast) {
this._setLastRow();
}
// Set EVEN/ODD
this._setRowStripes();
this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
YAHOO.log("Added " + aNewRecords.length +
" rows starting at index " + startIndex +
" with data " + lang.dump(aData), "info", this.toString());
},
argument: {recIndex: startIndex, isLast: isLast},
scope: this,
timeout: -1 // Needs to run immediately after the DOM insertions above
});
this._runRenderChain();
this.hideTableMessage();
return;
}
}
}
}
YAHOO.log("Could not update rows at " + startrow + " with " + lang.dump(aData), "warn", this.toString());
},
/**
* Deletes the given row's Record from the RecordSet. If the row is on current page,
* the corresponding DOM elements are also deleted.
*
* @method deleteRow
* @param row {HTMLElement | String | Number} DOM element reference or ID string
* to DataTable page element or RecordSet index.
*/
deleteRow : function(row) {
var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
if(lang.isNumber(nRecordIndex)) {
var oRecord = this.getRecord(nRecordIndex);
if(oRecord) {
var nTrIndex = this.getTrIndex(nRecordIndex);
// Remove from selection tracker if there
var sRecordId = oRecord.getId();
var tracker = this._aSelections || [];
for(var j=tracker.length-1; j>-1; j--) {
if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
(lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
tracker.splice(j,1);
}
}
// Delete Record from RecordSet
var oData = this._oRecordSet.deleteRecord(nRecordIndex);
// Update the UI
if(oData) {
// If paginated and the deleted row was on this or a prior page, just
// re-render
var oPaginator = this.get('paginator');
if (oPaginator) {
// Update the paginator's totalRecords
var totalRecords = oPaginator.get('totalRecords'),
// must capture before the totalRecords change because
// Paginator shifts to previous page automatically
rng = oPaginator.getPageRecords();
if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
oPaginator.set('totalRecords',totalRecords - 1);
}
// The deleted record was on this or a prior page, re-render
if (!rng || nRecordIndex <= rng[1]) {
this.render();
}
this._oChainRender.add({
method: function() {
if((this instanceof DT) && this._sId) {
this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());
}
},
scope: this,
timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
});
this._runRenderChain();
}
// Not paginated
else {
if(lang.isNumber(nTrIndex)) {
this._oChainRender.add({
method: function() {
if((this instanceof DT) && this._sId) {
var isLast = (nRecordIndex === this._oRecordSet.getLength());//(nTrIndex == this.getLastTrEl().sectionRowIndex);
this._deleteTrEl(nTrIndex);
// Post-delete tasks
if(this._elTbody.rows.length > 0) {
// Set FIRST/LAST
if(nTrIndex === 0) {
this._setFirstRow();
}
if(isLast) {
this._setLastRow();
}
// Set EVEN/ODD
if(nTrIndex != this._elTbody.rows.length) {
this._setRowStripes(nTrIndex);
}
}
this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());
}
},
scope: this,
timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
});
this._runRenderChain();
return;
}
}
}
}
}
YAHOO.log("Could not delete row: " + row, "warn", this.toString());
return null;
},
/**
* Convenience method to delete multiple rows.
*
* @method deleteRows
* @param row {HTMLElement | String | Number} DOM element reference or ID string
* to DataTable page element or RecordSet index.
* @param count {Number} (optional) How many rows to delete. A negative value
* will delete towards the beginning.
*/
deleteRows : function(row, count) {
var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
if(lang.isNumber(nRecordIndex)) {
var oRecord = this.getRecord(nRecordIndex);
if(oRecord) {
var nTrIndex = this.getTrIndex(nRecordIndex);
// Remove from selection tracker if there
var sRecordId = oRecord.getId();
var tracker = this._aSelections || [];
for(var j=tracker.length-1; j>-1; j--) {
if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
(lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
tracker.splice(j,1);
}
}
// Delete Record from RecordSet
var highIndex = nRecordIndex;
var lowIndex = nRecordIndex;
// Validate count and account for negative value
if(count && lang.isNumber(count)) {
highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
count = (count > 0) ? count : count*-1;
if(lowIndex < 0) {
lowIndex = 0;
count = highIndex - lowIndex + 1;
}
}
else {
count = 1;
}
var aData = this._oRecordSet.deleteRecords(lowIndex, count);
// Update the UI
if(aData) {
var oPaginator = this.get('paginator'),
loopN = this.get("renderLoopSize");
// If paginated and the deleted row was on this or a prior page, just
// re-render
if (oPaginator) {
// Update the paginator's totalRecords
var totalRecords = oPaginator.get('totalRecords'),
// must capture before the totalRecords change because
// Paginator shifts to previous page automatically
rng = oPaginator.getPageRecords();
if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
oPaginator.set('totalRecords',totalRecords - aData.length);
}
// The records were on this or a prior page, re-render
if (!rng || lowIndex <= rng[1]) {
this.render();
}
this._oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId) {
this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
}
},
scope: this,
timeout: (loopN > 0) ? 0 : -1
});
this._runRenderChain();
return;
}
// Not paginated
else {
if(lang.isNumber(nTrIndex)) {
// Delete the TR elements starting with highest index
var loopEnd = lowIndex;
var nRowsNeeded = count; // how many needed
this._oChainRender.add({
method: function(oArg) {
if((this instanceof DT) && this._sId) {
var i = oArg.nCurrentRow,
len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
for(; i>len; --i) {
this._deleteTrEl(i);
}
oArg.nCurrentRow = i;
}
},
iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
argument: {nCurrentRow:highIndex},
scope: this,
timeout: (loopN > 0) ? 0 : -1
});
this._oChainRender.add({
method: function() {
// Post-delete tasks
if(this._elTbody.rows.length > 0) {
this._setFirstRow();
this._setLastRow();
this._setRowStripes();
}
this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
},
scope: this,
timeout: -1 // Needs to run immediately after the DOM deletions above
});
this._runRenderChain();
return;
}
}
}
}
}
YAHOO.log("Could not delete " + count + " rows at row " + row, "warn", this.toString());
return null;
},
// CELL FUNCTIONS
/**
* Outputs markup into the given TD based on given Record.
*
* @method formatCell
* @param elLiner {HTMLElement} The liner DIV element within the TD.
* @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
* @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
*/
formatCell : function(elLiner, oRecord, oColumn) {
if(!oRecord) {
oRecord = this.getRecord(elLiner);
}
if(!oColumn) {
oColumn = this.getColumn(elLiner.parentNode.cellIndex);
}
if(oRecord && oColumn) {
var sField = oColumn.field;
var oData = oRecord.getData(sField);
var fnFormatter = typeof oColumn.formatter === 'function' ?
oColumn.formatter :
DT.Formatter[oColumn.formatter+''] ||
DT.Formatter.defaultFormatter;
// Apply special formatter
if(fnFormatter) {
fnFormatter.call(this, elLiner, oRecord, oColumn, oData);
}
else {
elLiner.innerHTML = oData;
}
this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elLiner});
}
else {
YAHOO.log("Could not format cell " + elLiner, "error", this.toString());
}
},
/**
* For the given row and column, updates the Record with the given data. If the
* cell is on current page, the corresponding DOM elements are also updated.
*
* @method updateCell
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
* @param oData {Object} New data value for the cell.
*/
updateCell : function(oRecord, oColumn, oData) {
// Validate Column and Record
oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
if(oColumn && oColumn.getField() && (oRecord instanceof YAHOO.widget.Record)) {
var sKey = oColumn.getField(),
// Copy data from the Record for the event that gets fired later
//var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
oldData = oRecord.getData(sKey);
// Update Record with new data
this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
// Update the TD only if row is on current page
var elTd = this.getTdEl({record: oRecord, column: oColumn});
if(elTd) {
this._oChainRender.add({
method: function() {
if((this instanceof DT) && this._sId) {
this.formatCell(elTd.firstChild);
this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
", Record index = " + this.getRecordIndex(oRecord) +
", page row index = " + this.getTrIndex(oRecord) +
", Column key = " + oColumn.getKey(), "info", this.toString());
}
},
scope: this,
timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
});
this._runRenderChain();
}
else {
this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
", Record index = " + this.getRecordIndex(oRecord) +
", page row index = " + this.getTrIndex(oRecord) +
", Column key = " + oColumn.getKey(), "info", this.toString());
}
}
},
// PAGINATION
/**
* Method executed during set() operation for the "paginator" attribute.
* Adds and/or severs event listeners between DataTable and Paginator
*
* @method _updatePaginator
* @param newPag {Paginator} Paginator instance (or null) for DataTable to use
* @private
*/
_updatePaginator : function (newPag) {
var oldPag = this.get('paginator');
if (oldPag && newPag !== oldPag) {
oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
}
if (newPag) {
newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
}
},
/**
* Update the UI infrastructure in response to a "paginator" attribute change.
*
* @method _handlePaginatorChange
* @param e {Object} Change event object containing keys 'type','newValue',
* and 'prevValue'
* @private
*/
_handlePaginatorChange : function (e) {
if (e.prevValue === e.newValue) { return; }
var newPag = e.newValue,
oldPag = e.prevValue,
containers = this._defaultPaginatorContainers();
if (oldPag) {
if (oldPag.getContainerNodes()[0] == containers[0]) {
oldPag.set('containers',[]);
}
oldPag.destroy();
// Convenience: share the default containers if possible.
// Otherwise, remove the default containers from the DOM.
if (containers[0]) {
if (newPag && !newPag.getContainerNodes().length) {
newPag.set('containers',containers);
} else {
// No new Paginator to use existing containers, OR new
// Paginator has configured containers.
for (var i = containers.length - 1; i >= 0; --i) {
if (containers[i]) {
containers[i].parentNode.removeChild(containers[i]);
}
}
}
}
}
if (!this._bInit) {
this.render();
}
if (newPag) {
this.renderPaginator();
}
},
/**
* Returns the default containers used for Paginators. If create param is
* passed, the containers will be created and added to the DataTable container.
*
* @method _defaultPaginatorContainers
* @param create {boolean} Create the default containers if not found
* @private
*/
_defaultPaginatorContainers : function (create) {
var above_id = this._sId + '-paginator0',
below_id = this._sId + '-paginator1',
above = Dom.get(above_id),
below = Dom.get(below_id);
if (create && (!above || !below)) {
// One above and one below the table
if (!above) {
above = document.createElement('div');
above.id = above_id;
Dom.addClass(above, DT.CLASS_PAGINATOR);
this._elContainer.insertBefore(above,this._elContainer.firstChild);
}
if (!below) {
below = document.createElement('div');
below.id = below_id;
Dom.addClass(below, DT.CLASS_PAGINATOR);
this._elContainer.appendChild(below);
}
}
return [above,below];
},
/**
* Calls Paginator's destroy() method
*
* @method _destroyPaginator
* @private
*/
_destroyPaginator : function () {
var oldPag = this.get('paginator');
if (oldPag) {
oldPag.destroy();
}
},
/**
* Renders the Paginator to the DataTable UI
*
* @method renderPaginator
*/
renderPaginator : function () {
var pag = this.get("paginator");
if (!pag) { return; }
// Add the containers if the Paginator is not configured with containers
if (!pag.getContainerNodes().length) {
pag.set('containers',this._defaultPaginatorContainers(true));
}
pag.render();
},
/**
* Overridable method gives implementers a hook to show loading message before
* changing Paginator value.
*
* @method doBeforePaginatorChange
* @param oPaginatorState {Object} An object literal describing the proposed pagination state.
* @return {Boolean} Return true to continue changing Paginator value.
*/
doBeforePaginatorChange : function(oPaginatorState) {
this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
return true;
},
/**
* Responds to new Pagination states. By default, updates the UI to reflect the
* new state. If "dynamicData" is true, current selections are purged before
* a request is sent to the DataSource for data for the new state (using the
* request returned by "generateRequest()").
*
* @method onPaginatorChangeRequest
* @param oPaginatorState {Object} An object literal describing the proposed pagination state.
*/
onPaginatorChangeRequest : function (oPaginatorState) {
var ok = this.doBeforePaginatorChange(oPaginatorState);
if(ok) {
// Server-side pagination
if(this.get("dynamicData")) {
// Get the current state
var oState = this.getState();
// Update pagination values
oState.pagination = oPaginatorState;
// Get the request for the new state
var request = this.get("generateRequest")(oState, this);
// Purge selections
this.unselectAllRows();
this.unselectAllCells();
// Get the new data from the server
var callback = {
success : this.onDataReturnSetRows,
failure : this.onDataReturnSetRows,
argument : oState, // Pass along the new state to the callback
scope : this
};
this._oDataSource.sendRequest(request, callback);
}
// Client-side pagination
else {
// Set the core pagination values silently (the second param)
// to avoid looping back through the changeRequest mechanism
oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
// Update the UI
this.render();
}
}
else {
YAHOO.log("Could not change Paginator value \"" + oPaginatorState + "\"", "warn", this.toString());
}
},
// SELECTION/HIGHLIGHTING
/*
* Reference to last highlighted cell element
*
* @property _elLastHighlightedTd
* @type HTMLElement
* @private
*/
_elLastHighlightedTd : null,
/*
* ID string of last highlighted row element
*
* @property _sLastHighlightedTrElId
* @type String
* @private
*/
//_sLastHighlightedTrElId : null,
/**
* Array to track row selections (by sRecordId) and/or cell selections
* (by {recordId:sRecordId, columnKey:sColumnKey})
*
* @property _aSelections
* @type Object[]
* @private
*/
_aSelections : null,
/**
* Record instance of the row selection anchor.
*
* @property _oAnchorRecord
* @type YAHOO.widget.Record
* @private
*/
_oAnchorRecord : null,
/**
* Object literal representing cell selection anchor:
* {recordId:sRecordId, columnKey:sColumnKey}.
*
* @property _oAnchorCell
* @type Object
* @private
*/
_oAnchorCell : null,
/**
* Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
* from all TR elements on the page.
*
* @method _unselectAllTrEls
* @private
*/
_unselectAllTrEls : function() {
var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
},
/**
* Returns object literal of values that represent the selection trigger. Used
* to determine selection behavior resulting from a key event.
*
* @method _getSelectionTrigger
* @private
*/
_getSelectionTrigger : function() {
var sMode = this.get("selectionMode");
var oTrigger = {};
var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
// Cell mode
if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
oTriggerCell = this.getLastSelectedCell();
// No selected cells found
if(!oTriggerCell) {
return null;
}
else {
oTriggerRecord = this.getRecord(oTriggerCell.recordId);
nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
elTriggerRow = this.getTrEl(oTriggerRecord);
nTriggerTrIndex = this.getTrIndex(elTriggerRow);
// Selected cell not found on this page
if(nTriggerTrIndex === null) {
return null;
}
else {
oTrigger.record = oTriggerRecord;
oTrigger.recordIndex = nTriggerRecordIndex;
oTrigger.el = this.getTdEl(oTriggerCell);
oTrigger.trIndex = nTriggerTrIndex;
oTrigger.column = this.getColumn(oTriggerCell.columnKey);
oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
oTrigger.cell = oTriggerCell;
return oTrigger;
}
}
}
// Row mode
else {
oTriggerRecord = this.getLastSelectedRecord();
// No selected rows found
if(!oTriggerRecord) {
return null;
}
else {
// Selected row found, but is it on current page?
oTriggerRecord = this.getRecord(oTriggerRecord);
nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
elTriggerRow = this.getTrEl(oTriggerRecord);
nTriggerTrIndex = this.getTrIndex(elTriggerRow);
// Selected row not found on this page
if(nTriggerTrIndex === null) {
return null;
}
else {
oTrigger.record = oTriggerRecord;
oTrigger.recordIndex = nTriggerRecordIndex;
oTrigger.el = elTriggerRow;
oTrigger.trIndex = nTriggerTrIndex;
return oTrigger;
}
}
}
},
/**
* Returns object literal of values that represent the selection anchor. Used
* to determine selection behavior resulting from a user event.
*
* @method _getSelectionAnchor
* @param oTrigger {Object} (Optional) Object literal of selection trigger values
* (for key events).
* @private
*/
_getSelectionAnchor : function(oTrigger) {
var sMode = this.get("selectionMode");
var oAnchor = {};
var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
// Cell mode
if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
// Validate anchor cell
var oAnchorCell = this._oAnchorCell;
if(!oAnchorCell) {
if(oTrigger) {
oAnchorCell = this._oAnchorCell = oTrigger.cell;
}
else {
return null;
}
}
oAnchorRecord = this._oAnchorCell.record;
nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
// If anchor cell is not on this page...
if(nAnchorTrIndex === null) {
// ...set TR index equal to top TR
if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
nAnchorTrIndex = 0;
}
// ...set TR index equal to bottom TR
else {
nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
}
}
oAnchor.record = oAnchorRecord;
oAnchor.recordIndex = nAnchorRecordIndex;
oAnchor.trIndex = nAnchorTrIndex;
oAnchor.column = this._oAnchorCell.column;
oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
oAnchor.cell = oAnchorCell;
return oAnchor;
}
// Row mode
else {
oAnchorRecord = this._oAnchorRecord;
if(!oAnchorRecord) {
if(oTrigger) {
oAnchorRecord = this._oAnchorRecord = oTrigger.record;
}
else {
return null;
}
}
nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
// If anchor row is not on this page...
if(nAnchorTrIndex === null) {
// ...set TR index equal to top TR
if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
nAnchorTrIndex = 0;
}
// ...set TR index equal to bottom TR
else {
nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
}
}
oAnchor.record = oAnchorRecord;
oAnchor.recordIndex = nAnchorRecordIndex;
oAnchor.trIndex = nAnchorTrIndex;
return oAnchor;
}
},
/**
* Determines selection behavior resulting from a mouse event when selection mode
* is set to "standard".
*
* @method _handleStandardSelectionByMouse
* @param oArgs.event {HTMLEvent} Event object.
* @param oArgs.target {HTMLElement} Target element.
* @private
*/
_handleStandardSelectionByMouse : function(oArgs) {
var elTarget = oArgs.target;
// Validate target row
var elTargetRow = this.getTrEl(elTarget);
if(elTargetRow) {
var e = oArgs.event;
var bSHIFT = e.shiftKey;
var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
var oTargetRecord = this.getRecord(elTargetRow);
var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
var oAnchor = this._getSelectionAnchor();
var i;
// Both SHIFT and CTRL
if(bSHIFT && bCTRL) {
// Validate anchor
if(oAnchor) {
if(this.isSelected(oAnchor.record)) {
// Select all rows between anchor row and target row, including target row
if(oAnchor.recordIndex < nTargetRecordIndex) {
for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
if(!this.isSelected(i)) {
this.selectRow(i);
}
}
}
// Select all rows between target row and anchor row, including target row
else {
for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
if(!this.isSelected(i)) {
this.selectRow(i);
}
}
}
}
else {
// Unselect all rows between anchor row and target row
if(oAnchor.recordIndex < nTargetRecordIndex) {
for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
if(this.isSelected(i)) {
this.unselectRow(i);
}
}
}
// Unselect all rows between target row and anchor row
else {
for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
if(this.isSelected(i)) {
this.unselectRow(i);
}
}
}
// Select the target row
this.selectRow(oTargetRecord);
}
}
// Invalid anchor
else {
// Set anchor
this._oAnchorRecord = oTargetRecord;
// Toggle selection of target
if(this.isSelected(oTargetRecord)) {
this.unselectRow(oTargetRecord);
}
else {
this.selectRow(oTargetRecord);
}
}
}
// Only SHIFT
else if(bSHIFT) {
this.unselectAllRows();
// Validate anchor
if(oAnchor) {
// Select all rows between anchor row and target row,
// including the anchor row and target row
if(oAnchor.recordIndex < nTargetRecordIndex) {
for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
this.selectRow(i);
}
}
// Select all rows between target row and anchor row,
// including the target row and anchor row
else {
for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
this.selectRow(i);
}
}
}
// Invalid anchor
else {
// Set anchor
this._oAnchorRecord = oTargetRecord;
// Select target row only
this.selectRow(oTargetRecord);
}
}
// Only CTRL
else if(bCTRL) {
// Set anchor
this._oAnchorRecord = oTargetRecord;
// Toggle selection of target
if(this.isSelected(oTargetRecord)) {
this.unselectRow(oTargetRecord);
}
else {
this.selectRow(oTargetRecord);
}
}
// Neither SHIFT nor CTRL
else {
this._handleSingleSelectionByMouse(oArgs);
return;
}
}
},
/**
* Determines selection behavior resulting from a key event when selection mode
* is set to "standard".
*
* @method _handleStandardSelectionByKey
* @param e {HTMLEvent} Event object.
* @private
*/
_handleStandardSelectionByKey : function(e) {
var nKey = Ev.getCharCode(e);
if((nKey == 38) || (nKey == 40)) {
var bSHIFT = e.shiftKey;
// Validate trigger
var oTrigger = this._getSelectionTrigger();
// Arrow selection only works if last selected row is on current page
if(!oTrigger) {
return null;
}
Ev.stopEvent(e);
// Validate anchor
var oAnchor = this._getSelectionAnchor(oTrigger);
// Determine which direction we're going to
if(bSHIFT) {
// Selecting down away from anchor row
if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
this.selectRow(this.getNextTrEl(oTrigger.el));
}
// Selecting up away from anchor row
else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
this.selectRow(this.getPreviousTrEl(oTrigger.el));
}
// Unselect trigger
else {
this.unselectRow(oTrigger.el);
}
}
else {
this._handleSingleSelectionByKey(e);
}
}
},
/**
* Determines selection behavior resulting from a mouse event when selection mode
* is set to "single".
*
* @method _handleSingleSelectionByMouse
* @param oArgs.event {HTMLEvent} Event object.
* @param oArgs.target {HTMLElement} Target element.
* @private
*/
_handleSingleSelectionByMouse : function(oArgs) {
var elTarget = oArgs.target;
// Validate target row
var elTargetRow = this.getTrEl(elTarget);
if(elTargetRow) {
var oTargetRecord = this.getRecord(elTargetRow);
// Set anchor
this._oAnchorRecord = oTargetRecord;
// Select only target
this.unselectAllRows();
this.selectRow(oTargetRecord);
}
},
/**
* Determines selection behavior resulting from a key event when selection mode
* is set to "single".
*
* @method _handleSingleSelectionByKey
* @param e {HTMLEvent} Event object.
* @private
*/
_handleSingleSelectionByKey : function(e) {
var nKey = Ev.getCharCode(e);
if((nKey == 38) || (nKey == 40)) {
// Validate trigger
var oTrigger = this._getSelectionTrigger();
// Arrow selection only works if last selected row is on current page
if(!oTrigger) {
return null;
}
Ev.stopEvent(e);
// Determine the new row to select
var elNew;
if(nKey == 38) { // arrow up
elNew = this.getPreviousTrEl(oTrigger.el);
// Validate new row
if(elNew === null) {
//TODO: wrap around to last tr on current page
//elNew = this.getLastTrEl();
//TODO: wrap back to last tr of previous page
// Top row selection is sticky
elNew = this.getFirstTrEl();
}
}
else if(nKey == 40) { // arrow down
elNew = this.getNextTrEl(oTrigger.el);
// Validate new row
if(elNew === null) {
//TODO: wrap around to first tr on current page
//elNew = this.getFirstTrEl();
//TODO: wrap forward to first tr of previous page
// Bottom row selection is sticky
elNew = this.getLastTrEl();
}
}
// Unselect all rows
this.unselectAllRows();
// Select the new row
this.selectRow(elNew);
// Set new anchor
this._oAnchorRecord = this.getRecord(elNew);
}
},
/**
* Determines selection behavior resulting from a mouse event when selection mode
* is set to "cellblock".
*
* @method _handleCellBlockSelectionByMouse
* @param oArgs.event {HTMLEvent} Event object.
* @param oArgs.target {HTMLElement} Target element.
* @private
*/
_handleCellBlockSelectionByMouse : function(oArgs) {
var elTarget = oArgs.target;
// Validate target cell
var elTargetCell = this.getTdEl(elTarget);
if(elTargetCell) {
var e = oArgs.event;
var bSHIFT = e.shiftKey;
var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
var elTargetRow = this.getTrEl(elTargetCell);
var nTargetTrIndex = this.getTrIndex(elTargetRow);
var oTargetColumn = this.getColumn(elTargetCell);
var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
var oTargetRecord = this.getRecord(elTargetRow);
var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
var oAnchor = this._getSelectionAnchor();
var allRows = this.getTbodyEl().rows;
var startIndex, endIndex, currentRow, i, j;
// Both SHIFT and CTRL
if(bSHIFT && bCTRL) {
// Validate anchor
if(oAnchor) {
// Anchor is selected
if(this.isSelected(oAnchor.cell)) {
// All cells are on the same row
if(oAnchor.recordIndex === nTargetRecordIndex) {
// Select all cells between anchor cell and target cell, including target cell
if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
this.selectCell(elTargetRow.cells[i]);
}
}
// Select all cells between target cell and anchor cell, including target cell
else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
this.selectCell(elTargetRow.cells[i]);
}
}
}
// Anchor row is above target row
else if(oAnchor.recordIndex < nTargetRecordIndex) {
startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
// Select all cells from startIndex to endIndex on rows between anchor row and target row
for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
for(j=startIndex; j<=endIndex; j++) {
this.selectCell(allRows[i].cells[j]);
}
}
}
// Anchor row is below target row
else {
startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
// Select all cells from startIndex to endIndex on rows between target row and anchor row
for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
for(j=endIndex; j>=startIndex; j--) {
this.selectCell(allRows[i].cells[j]);
}
}
}
}
// Anchor cell is unselected
else {
// All cells are on the same row
if(oAnchor.recordIndex === nTargetRecordIndex) {
// Unselect all cells between anchor cell and target cell
if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
this.unselectCell(elTargetRow.cells[i]);
}
}
// Select all cells between target cell and anchor cell
else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
this.unselectCell(elTargetRow.cells[i]);
}
}
}
// Anchor row is above target row
if(oAnchor.recordIndex < nTargetRecordIndex) {
// Unselect all cells from anchor cell to target cell
for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
currentRow = allRows[i];
for(j=0; j<currentRow.cells.length; j++) {
// This is the anchor row, only unselect cells after the anchor cell
if(currentRow.sectionRowIndex === oAnchor.trIndex) {
if(j>oAnchor.colKeyIndex) {
this.unselectCell(currentRow.cells[j]);
}
}
// This is the target row, only unelect cells before the target cell
else if(currentRow.sectionRowIndex === nTargetTrIndex) {
if(j<nTargetColKeyIndex) {
this.unselectCell(currentRow.cells[j]);
}
}
// Unselect all cells on this row
else {
this.unselectCell(currentRow.cells[j]);
}
}
}
}
// Anchor row is below target row
else {
// Unselect all cells from target cell to anchor cell
for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
currentRow = allRows[i];
for(j=0; j<currentRow.cells.length; j++) {
// This is the target row, only unselect cells after the target cell
if(currentRow.sectionRowIndex == nTargetTrIndex) {
if(j>nTargetColKeyIndex) {
this.unselectCell(currentRow.cells[j]);
}
}
// This is the anchor row, only unselect cells before the anchor cell
else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
if(j<oAnchor.colKeyIndex) {
this.unselectCell(currentRow.cells[j]);
}
}
// Unselect all cells on this row
else {
this.unselectCell(currentRow.cells[j]);
}
}
}
}
// Select the target cell
this.selectCell(elTargetCell);
}
}
// Invalid anchor
else {
// Set anchor
this._oAnchorCell = oTargetCell;
// Toggle selection of target
if(this.isSelected(oTargetCell)) {
this.unselectCell(oTargetCell);
}
else {
this.selectCell(oTargetCell);
}
}
}
// Only SHIFT
else if(bSHIFT) {
this.unselectAllCells();
// Validate anchor
if(oAnchor) {
// All cells are on the same row
if(oAnchor.recordIndex === nTargetRecordIndex) {
// Select all cells between anchor cell and target cell,
// including the anchor cell and target cell
if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
this.selectCell(elTargetRow.cells[i]);
}
}
// Select all cells between target cell and anchor cell
// including the target cell and anchor cell
else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
this.selectCell(elTargetRow.cells[i]);
}
}
}
// Anchor row is above target row
else if(oAnchor.recordIndex < nTargetRecordIndex) {
// Select the cellblock from anchor cell to target cell
// including the anchor cell and the target cell
startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
for(j=startIndex; j<=endIndex; j++) {
this.selectCell(allRows[i].cells[j]);
}
}
}
// Anchor row is below target row
else {
// Select the cellblock from target cell to anchor cell
// including the target cell and the anchor cell
startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
for(j=startIndex; j<=endIndex; j++) {
this.selectCell(allRows[i].cells[j]);
}
}
}
}
// Invalid anchor
else {
// Set anchor
this._oAnchorCell = oTargetCell;
// Select target only
this.selectCell(oTargetCell);
}
}
// Only CTRL
else if(bCTRL) {
// Set anchor
this._oAnchorCell = oTargetCell;
// Toggle selection of target
if(this.isSelected(oTargetCell)) {
this.unselectCell(oTargetCell);
}
else {
this.selectCell(oTargetCell);
}
}
// Neither SHIFT nor CTRL
else {
this._handleSingleCellSelectionByMouse(oArgs);
}
}
},
/**
* Determines selection behavior resulting from a key event when selection mode
* is set to "cellblock".
*
* @method _handleCellBlockSelectionByKey
* @param e {HTMLEvent} Event object.
* @private
*/
_handleCellBlockSelectionByKey : function(e) {
var nKey = Ev.getCharCode(e);
var bSHIFT = e.shiftKey;
if((nKey == 9) || !bSHIFT) {
this._handleSingleCellSelectionByKey(e);
return;
}
if((nKey > 36) && (nKey < 41)) {
// Validate trigger
var oTrigger = this._getSelectionTrigger();
// Arrow selection only works if last selected row is on current page
if(!oTrigger) {
return null;
}
Ev.stopEvent(e);
// Validate anchor
var oAnchor = this._getSelectionAnchor(oTrigger);
var i, startIndex, endIndex, elNew, elNewRow;
var allRows = this.getTbodyEl().rows;
var elThisRow = oTrigger.el.parentNode;
// Determine which direction we're going to
if(nKey == 40) { // arrow down
// Selecting away from anchor cell
if(oAnchor.recordIndex <= oTrigger.recordIndex) {
// Select the horiz block on the next row...
// ...making sure there is room below the trigger row
elNewRow = this.getNextTrEl(oTrigger.el);
if(elNewRow) {
startIndex = oAnchor.colKeyIndex;
endIndex = oTrigger.colKeyIndex;
// ...going left
if(startIndex > endIndex) {
for(i=startIndex; i>=endIndex; i--) {
elNew = elNewRow.cells[i];
this.selectCell(elNew);
}
}
// ... going right
else {
for(i=startIndex; i<=endIndex; i++) {
elNew = elNewRow.cells[i];
this.selectCell(elNew);
}
}
}
}
// Unselecting towards anchor cell
else {
startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
// Unselect the horiz block on this row towards the next row
for(i=startIndex; i<=endIndex; i++) {
this.unselectCell(elThisRow.cells[i]);
}
}
}
// Arrow up
else if(nKey == 38) {
// Selecting away from anchor cell
if(oAnchor.recordIndex >= oTrigger.recordIndex) {
// Select the horiz block on the previous row...
// ...making sure there is room
elNewRow = this.getPreviousTrEl(oTrigger.el);
if(elNewRow) {
// Select in order from anchor to trigger...
startIndex = oAnchor.colKeyIndex;
endIndex = oTrigger.colKeyIndex;
// ...going left
if(startIndex > endIndex) {
for(i=startIndex; i>=endIndex; i--) {
elNew = elNewRow.cells[i];
this.selectCell(elNew);
}
}
// ... going right
else {
for(i=startIndex; i<=endIndex; i++) {
elNew = elNewRow.cells[i];
this.selectCell(elNew);
}
}
}
}
// Unselecting towards anchor cell
else {
startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
// Unselect the horiz block on this row towards the previous row
for(i=startIndex; i<=endIndex; i++) {
this.unselectCell(elThisRow.cells[i]);
}
}
}
// Arrow right
else if(nKey == 39) {
// Selecting away from anchor cell
if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
// Select the next vert block to the right...
// ...making sure there is room
if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
// Select in order from anchor to trigger...
startIndex = oAnchor.trIndex;
endIndex = oTrigger.trIndex;
// ...going up
if(startIndex > endIndex) {
for(i=startIndex; i>=endIndex; i--) {
elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
this.selectCell(elNew);
}
}
// ... going down
else {
for(i=startIndex; i<=endIndex; i++) {
elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
this.selectCell(elNew);
}
}
}
}
// Unselecting towards anchor cell
else {
// Unselect the vert block on this column towards the right
startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
for(i=startIndex; i<=endIndex; i++) {
this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
}
}
}
// Arrow left
else if(nKey == 37) {
// Selecting away from anchor cell
if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
//Select the previous vert block to the left
if(oTrigger.colKeyIndex > 0) {
// Select in order from anchor to trigger...
startIndex = oAnchor.trIndex;
endIndex = oTrigger.trIndex;
// ...going up
if(startIndex > endIndex) {
for(i=startIndex; i>=endIndex; i--) {
elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
this.selectCell(elNew);
}
}
// ... going down
else {
for(i=startIndex; i<=endIndex; i++) {
elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
this.selectCell(elNew);
}
}
}
}
// Unselecting towards anchor cell
else {
// Unselect the vert block on this column towards the left
startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
for(i=startIndex; i<=endIndex; i++) {
this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
}
}
}
}
},
/**
* Determines selection behavior resulting from a mouse event when selection mode
* is set to "cellrange".
*
* @method _handleCellRangeSelectionByMouse
* @param oArgs.event {HTMLEvent} Event object.
* @param oArgs.target {HTMLElement} Target element.
* @private
*/
_handleCellRangeSelectionByMouse : function(oArgs) {
var elTarget = oArgs.target;
// Validate target cell
var elTargetCell = this.getTdEl(elTarget);
if(elTargetCell) {
var e = oArgs.event;
var bSHIFT = e.shiftKey;
var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
var elTargetRow = this.getTrEl(elTargetCell);
var nTargetTrIndex = this.getTrIndex(elTargetRow);
var oTargetColumn = this.getColumn(elTargetCell);
var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
var oTargetRecord = this.getRecord(elTargetRow);
var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
var oAnchor = this._getSelectionAnchor();
var allRows = this.getTbodyEl().rows;
var currentRow, i, j;
// Both SHIFT and CTRL
if(bSHIFT && bCTRL) {
// Validate anchor
if(oAnchor) {
// Anchor is selected
if(this.isSelected(oAnchor.cell)) {
// All cells are on the same row
if(oAnchor.recordIndex === nTargetRecordIndex) {
// Select all cells between anchor cell and target cell, including target cell
if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
this.selectCell(elTargetRow.cells[i]);
}
}
// Select all cells between target cell and anchor cell, including target cell
else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
this.selectCell(elTargetRow.cells[i]);
}
}
}
// Anchor row is above target row
else if(oAnchor.recordIndex < nTargetRecordIndex) {
// Select all cells on anchor row from anchor cell to the end of the row
for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
this.selectCell(elTargetRow.cells[i]);
}
// Select all cells on all rows between anchor row and target row
for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
for(j=0; j<allRows[i].cells.length; j++){
this.selectCell(allRows[i].cells[j]);
}
}
// Select all cells on target row from first cell to the target cell
for(i=0; i<=nTargetColKeyIndex; i++) {
this.selectCell(elTargetRow.cells[i]);
}
}
// Anchor row is below target row
else {
// Select all cells on target row from target cell to the end of the row
for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
this.selectCell(elTargetRow.cells[i]);
}
// Select all cells on all rows between target row and anchor row
for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
for(j=0; j<allRows[i].cells.length; j++){
this.selectCell(allRows[i].cells[j]);
}
}
// Select all cells on anchor row from first cell to the anchor cell
for(i=0; i<oAnchor.colKeyIndex; i++) {
this.selectCell(elTargetRow.cells[i]);
}
}
}
// Anchor cell is unselected
else {
// All cells are on the same row
if(oAnchor.recordIndex === nTargetRecordIndex) {
// Unselect all cells between anchor cell and target cell
if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
this.unselectCell(elTargetRow.cells[i]);
}
}
// Select all cells between target cell and anchor cell
else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
this.unselectCell(elTargetRow.cells[i]);
}
}
}
// Anchor row is above target row
if(oAnchor.recordIndex < nTargetRecordIndex) {
// Unselect all cells from anchor cell to target cell
for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
currentRow = allRows[i];
for(j=0; j<currentRow.cells.length; j++) {
// This is the anchor row, only unselect cells after the anchor cell
if(currentRow.sectionRowIndex === oAnchor.trIndex) {
if(j>oAnchor.colKeyIndex) {
this.unselectCell(currentRow.cells[j]);
}
}
// This is the target row, only unelect cells before the target cell
else if(currentRow.sectionRowIndex === nTargetTrIndex) {
if(j<nTargetColKeyIndex) {
this.unselectCell(currentRow.cells[j]);
}
}
// Unselect all cells on this row
else {
this.unselectCell(currentRow.cells[j]);
}
}
}
}
// Anchor row is below target row
else {
// Unselect all cells from target cell to anchor cell
for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
currentRow = allRows[i];
for(j=0; j<currentRow.cells.length; j++) {
// This is the target row, only unselect cells after the target cell
if(currentRow.sectionRowIndex == nTargetTrIndex) {
if(j>nTargetColKeyIndex) {
this.unselectCell(currentRow.cells[j]);
}
}
// This is the anchor row, only unselect cells before the anchor cell
else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
if(j<oAnchor.colKeyIndex) {
this.unselectCell(currentRow.cells[j]);
}
}
// Unselect all cells on this row
else {
this.unselectCell(currentRow.cells[j]);
}
}
}
}
// Select the target cell
this.selectCell(elTargetCell);
}
}
// Invalid anchor
else {
// Set anchor
this._oAnchorCell = oTargetCell;
// Toggle selection of target
if(this.isSelected(oTargetCell)) {
this.unselectCell(oTargetCell);
}
else {
this.selectCell(oTargetCell);
}
}
}
// Only SHIFT
else if(bSHIFT) {
this.unselectAllCells();
// Validate anchor
if(oAnchor) {
// All cells are on the same row
if(oAnchor.recordIndex === nTargetRecordIndex) {
// Select all cells between anchor cell and target cell,
// including the anchor cell and target cell
if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
this.selectCell(elTargetRow.cells[i]);
}
}
// Select all cells between target cell and anchor cell
// including the target cell and anchor cell
else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
this.selectCell(elTargetRow.cells[i]);
}
}
}
// Anchor row is above target row
else if(oAnchor.recordIndex < nTargetRecordIndex) {
// Select all cells from anchor cell to target cell
// including the anchor cell and target cell
for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++