Added LinuxChix theme
[moodle-linuxchix.git] / lib / yui / datatable / datatable-beta.js
blobb56afe45a1c21d8462748e609c8cef7057d1d0ad
1 /*
2 Copyright (c) 2007, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.3.0
6 */
7 /**
8  * The DataTable widget provides a progressively enhanced DHTML control for
9  * displaying tabular data across A-grade browsers.
10  *
11  * @module datatable
12  * @requires yahoo, dom, event, datasource
13  * @optional dragdrop
14  * @title DataTable Widget
15  * @beta
16  */
18 /****************************************************************************/
19 /****************************************************************************/
20 /****************************************************************************/
22 /**
23  * DataTable class for the YUI DataTable widget.
24  *
25  * @namespace YAHOO.widget
26  * @class DataTable
27  * @uses YAHOO.util.EventProvider
28  * @constructor
29  * @param elContainer {HTMLElement} Container element for the TABLE.
30  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
31  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
32  * @param oConfigs {object} (optional) Object literal of configuration values.
33  */
34 YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
35     // Internal vars
36     this._nIndex = YAHOO.widget.DataTable._nCount;
37     this._sName = "instance" + this._nIndex;
38     this.id = "yui-dt"+this._nIndex;
40     // Initialize container element
41     this._initContainerEl(elContainer);
42     if(!this._elContainer) {
43         return;
44     }
46     // Initialize configs
47     this._initConfigs(oConfigs);
49     // Initialize ColumnSet
50     this._initColumnSet(aColumnDefs);
51     if(!this._oColumnSet) {
52         return;
53     }
54     
55     // Initialize RecordSet
56     this._initRecordSet();
57     if(!this._oRecordSet) {
58         return;
59     }
61     // Initialize DataSource
62     this._initDataSource(oDataSource);
63     if(!this._oDataSource) {
64         return;
65     }
67     // Progressive enhancement special case
68     if(this._oDataSource.dataType == YAHOO.util.DataSource.TYPE_HTMLTABLE) {
69         this._oDataSource.sendRequest(this.get("initialRequest"), this._onDataReturnEnhanceTable, this);
70     }
71     else {
72         // Initialize DOM elements
73         this._initTableEl();
74         if(!this._elTable || !this._elThead || !this._elTbody) {
75             return;
76         }
78         // Call Element's constructor after DOM elements are created
79         // but *before* table is populated with data
80         YAHOO.widget.DataTable.superclass.constructor.call(this, this._elContainer, this._oConfigs);
81         
82         //HACK: Set the Paginator values here via updatePaginator
83         if(this._oConfigs && this._oConfigs.paginator) {
84             this.updatePaginator(this._oConfigs.paginator);
85         }
87         // Send out for data in an asynchronous request
88         this._oDataSource.sendRequest(this.get("initialRequest"), this.onDataReturnInitializeTable, this);
89     }
91     // Initialize inline Cell editing
92     this._initCellEditorEl();
94     // Initialize Column sort
95     this._initColumnSort();
97     // Initialize DOM event listeners
98     this._initDomEvents();
100     YAHOO.widget.DataTable._nCount++;
103 if(YAHOO.util.Element) {
104     YAHOO.lang.extend(YAHOO.widget.DataTable, YAHOO.util.Element);
106 else {
109 /////////////////////////////////////////////////////////////////////////////
111 // Superclass methods
113 /////////////////////////////////////////////////////////////////////////////
116  * Implementation of Element's abstract method. Sets up config values.
118  * @method initAttributes
119  * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
120  * @private
121  */
123 YAHOO.widget.DataTable.prototype.initAttributes = function(oConfigs) {
124     oConfigs = oConfigs || {};
125     YAHOO.widget.DataTable.superclass.initAttributes.call(this, oConfigs);
127     /**
128     * @config summary
129     * @description Value for the SUMMARY attribute.
130     * @type String
131     */
132     this.setAttributeConfig("summary", {
133         value: null,
134         validator: YAHOO.lang.isString,
135         method: function(sSummary) {
136             this._elTable.summary = sSummary;
137         }
138     });
140     /**
141     * @config selectionMode
142     * @description Specifies row or cell selection mode. Accepts the following strings:
143     *    <dl>
144     *      <dt>"standard"</dt>
145     *      <dd>Standard row selection with support for modifier keys to enable
146     *      multiple selections.</dd>
147     *
148     *      <dt>"single"</dt>
149     *      <dd>Row selection with modifier keys disabled to not allow
150     *      multiple selections.</dd>
151     *
152     *      <dt>"singlecell"</dt>
153     *      <dd>Cell selection with modifier keys disabled to not allow
154     *      multiple selections.</dd>
155     *
156     *      <dt>"cellblock"</dt>
157     *      <dd>Cell selection with support for modifier keys to enable multiple
158     *      selections in a block-fashion, like a spreadsheet.</dd>
159     *
160     *      <dt>"cellrange"</dt>
161     *      <dd>Cell selection with support for modifier keys to enable multiple
162     *      selections in a range-fashion, like a calendar.</dd>
163     *    </dl>
164     *
165     * @default "standard"
166     * @type String
167     */
168     this.setAttributeConfig("selectionMode", {
169         value: "standard",
170         validator: YAHOO.lang.isString
171     });
173     /**
174     * @config initialRequest
175     * @description Defines the initial request that gets sent to the DataSource.
176     * @type String
177     */
178     this.setAttributeConfig("initialRequest", {
179         value: "",
180         validator: YAHOO.lang.isString
181     });
183     /**
184     * @config sortedBy
185     * @description Object literal provides metadata for initial sort values if
186     * data will arrive pre-sorted:
187     * <dl>
188     *     <dt>sortedBy.key</dt>
189     *     <dd>Key of sorted Column</dd>
190     *     <dt>sortedBy.dir</dt>
191     *     <dd>Initial sort direction, either "asc" or "desc"</dd>
192     * </dl>
193     * @type Object
194     */
195     this.setAttributeConfig("sortedBy", {
196         value: null,
197         // TODO: accepted array for nested sorts
198         validator: function(oNewSortedBy) {
199             return (oNewSortedBy && (oNewSortedBy.constructor == Object) && oNewSortedBy.key);
200         },
201         method: function(oNewSortedBy) {
202             // Remove ASC/DESC from TH
203             var oOldSortedBy = this.get("sortedBy");
204             if(oOldSortedBy && (oOldSortedBy.constructor == Object) && oOldSortedBy.key) {
205                 var oldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
206                 var oldThEl = this.getThEl(oldColumn);
207                 YAHOO.util.Dom.removeClass(oldThEl, YAHOO.widget.DataTable.CLASS_ASC);
208                 YAHOO.util.Dom.removeClass(oldThEl, YAHOO.widget.DataTable.CLASS_DESC);
209             }
210             
211             // Set ASC/DESC on TH
212             var column = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
213             if(column) {
214                 var newClass = (oNewSortedBy.dir && (oNewSortedBy.dir != "asc")) ?
215                         YAHOO.widget.DataTable.CLASS_DESC :
216                         YAHOO.widget.DataTable.CLASS_ASC;
217                 YAHOO.util.Dom.addClass(this.id + "-col" + column.getId(), newClass);
218             }
219         }
220     });
222     /**
223     * @config paginator
224     * @description Object literal of pagination values.
225     * @default <br>
226     *   { containers:[], // UI container elements <br>
227     *   rowsPerPage:500, // 500 rows <br>
228     *   currentPage:1,  // page one <br>
229     *   pageLinks:0,    // show all links <br>
230     *   pageLinksStart:1, // first link is page 1 <br>
231     *   dropdownOptions:null, // no dropdown <br>
232     *   links: [], // links elements <br>
233     *   dropdowns: [] } //dropdown elements
234     * 
235     * @type Object
236     */
237     this.setAttributeConfig("paginator", {
238         value: {
239             rowsPerPage:500, // 500 rows per page
240             currentPage:1,  // show page one
241             startRecordIndex:0, // start with first Record
242             totalRecords:0, // how many Records total
243             totalPages:0, // how many pages total
244             rowsThisPage:0, // how many rows this page
245             pageLinks:0,    // show all links
246             pageLinksStart:1, // first link is page 1
247             dropdownOptions: null, //no dropdown
248             containers:[], // Paginator container element references
249             dropdowns: [], //dropdown element references,
250             links: [] // links elements
251         },
252         validator: function(oNewPaginator) {
253             if(oNewPaginator && (oNewPaginator.constructor == Object)) {
254                 // Check for incomplete set of values
255                 if((oNewPaginator.rowsPerPage !== undefined) &&
256                         (oNewPaginator.currentPage !== undefined) &&
257                         (oNewPaginator.startRecordIndex !== undefined) &&
258                         (oNewPaginator.totalRecords !== undefined) &&
259                         (oNewPaginator.totalPages !== undefined) &&
260                         (oNewPaginator.rowsThisPage !== undefined) &&
261                         (oNewPaginator.pageLinks !== undefined) &&
262                         (oNewPaginator.pageLinksStart !== undefined) &&
263                         (oNewPaginator.dropdownOptions !== undefined) &&
264                         (oNewPaginator.containers !== undefined) &&
265                         (oNewPaginator.dropdowns !== undefined) &&
266                         (oNewPaginator.links !== undefined)) {
268                     // Validate each value
269                     if(YAHOO.lang.isNumber(oNewPaginator.rowsPerPage) &&
270                             YAHOO.lang.isNumber(oNewPaginator.currentPage) &&
271                             YAHOO.lang.isNumber(oNewPaginator.startRecordIndex) &&
272                             YAHOO.lang.isNumber(oNewPaginator.totalRecords) &&
273                             YAHOO.lang.isNumber(oNewPaginator.totalPages) &&
274                             YAHOO.lang.isNumber(oNewPaginator.rowsThisPage) &&
275                             YAHOO.lang.isNumber(oNewPaginator.pageLinks) &&
276                             YAHOO.lang.isNumber(oNewPaginator.pageLinksStart) &&
277                             YAHOO.lang.isArray(oNewPaginator.dropdownOptions) &&
278                             YAHOO.lang.isArray(oNewPaginator.containers) &&
279                             YAHOO.lang.isArray(oNewPaginator.dropdowns) &&
280                             YAHOO.lang.isArray(oNewPaginator.links)) {
281                         return true;
282                     }
283                 }
284             }
285             return false;
286         }
287     });
289     /**
290     * @config paginated
291     * @description True if built-in client-side pagination is enabled
292     * @default false
293     * @type Boolean
294     */
295     this.setAttributeConfig("paginated", {
296         value: false,
297         validator: YAHOO.lang.isBoolean,
298         method: function(oParam) {
299             var oPaginator = this.get("paginator");
300             var aContainerEls = oPaginator.containers;
301             
302             // Paginator is enabled
303             if(oParam) {
304                 // No containers found, create two from scratch
305                 if(aContainerEls.length === 0) {
306                     // One before TABLE
307                     var pag0 = document.createElement("span");
308                     pag0.id = this.id + "-paginator0";
309                     YAHOO.util.Dom.addClass(pag0, YAHOO.widget.DataTable.CLASS_PAGINATOR);
310                     pag0 = this._elContainer.insertBefore(pag0, this._elTable);
311                     aContainerEls.push(pag0);
313                     // One after TABLE
314                     var pag1 = document.createElement("span");
315                     pag1.id = this.id + "-paginator1";
316                     YAHOO.util.Dom.addClass(pag1, YAHOO.widget.DataTable.CLASS_PAGINATOR);
317                     pag1 = this._elContainer.insertBefore(pag1, this._elTable.nextSibling);
318                     aContainerEls.push(pag1);
320                     // Add containers directly to tracker
321                     this._configs.paginator.value.containers = [pag0, pag1];
323                 }
324                 else {
325                     // Show each container
326                     for(var i=0; i<aContainerEls.length; i++) {
327                         aContainerEls[i].style.display = "";
328                     }
329                 }
331                 // Links are enabled
332                 if(oPaginator.pageLinks > -1) {
333                     var aLinkEls = oPaginator.links;
334                     // No links containers found, create from scratch
335                     if(aLinkEls.length === 0) {
336                         for(i=0; i<aContainerEls.length; i++) {
337                             // Create one links container per Paginator container
338                             var linkEl = document.createElement("span");
339                             linkEl.id = "yui-dt-pagselect"+i;
340                             linkEl = aContainerEls[i].appendChild(linkEl);
342                             // Add event listener
343                             //TODO: anon fnc
344                             YAHOO.util.Event.addListener(linkEl,"click",this._onPaginatorLinkClick,this);
346                              // Add directly to tracker
347                             this._configs.paginator.value.links.push(linkEl);
348                        }
349                    }
350                 }
352                 // Show these options in the dropdown
353                 var dropdownOptions = oPaginator.dropdownOptions || [];
355                 for(i=0; i<aContainerEls.length; i++) {
356                     // Create one SELECT element per Paginator container
357                     var selectEl = document.createElement("select");
358                     YAHOO.util.Dom.addClass(selectEl, YAHOO.widget.DataTable.CLASS_DROPDOWN);
359                     selectEl = aContainerEls[i].appendChild(selectEl);
360                     selectEl.id = "yui-dt-pagselect"+i;
362                     // Add event listener
363                     //TODO: anon fnc
364                     YAHOO.util.Event.addListener(selectEl,"change",this._onPaginatorDropdownChange,this);
366                     // Add DOM reference directly to tracker
367                    this._configs.paginator.value.dropdowns.push(selectEl);
369                     // Hide dropdown
370                     if(!oPaginator.dropdownOptions) {
371                         selectEl.style.display = "none";
372                     }
373                 }
375                 //TODO: fire paginatorDisabledEvent & add to api doc
376             }
377             // Pagination is disabled
378             else {
379                 // Containers found
380                 if(aContainerEls.length > 0) {
381                     // Destroy or just hide?
382                     
383                     // Hide each container
384                     for(i=0; i<aContainerEls.length; i++) {
385                         aContainerEls[i].style.display = "none";
386                     }
388                     /*TODO?
389                     // Destroy each container
390                     for(i=0; i<aContainerEls.length; i++) {
391                         YAHOO.util.Event.purgeElement(aContainerEls[i], true);
392                         aContainerEls.innerHTML = null;
393                         //TODO: remove container?
394                         // aContainerEls[i].parentNode.removeChild(aContainerEls[i]);
395                     }
396                     */
397                 }
398                 //TODO: fire paginatorDisabledEvent & add to api doc
399             }
400         }
401     });
403     /**
404     * @config caption
405     * @description Value for the CAPTION element.
406     * @type String
407     */
408     this.setAttributeConfig("caption", {
409         value: null,
410         validator: YAHOO.lang.isString,
411         method: function(sCaption) {
412             // Create CAPTION element
413             if(!this._elCaption) {
414                 if(!this._elTable.firstChild) {
415                     this._elCaption = this._elTable.appendChild(document.createElement("caption"));
416                 }
417                 else {
418                     this._elCaption = this._elTable.insertBefore(document.createElement("caption"), this._elTable.firstChild);
419                 }
420             }
421             // Set CAPTION value
422             this._elCaption.innerHTML = sCaption;
423         }
424     });
426     /**
427     * @config scrollable
428     * @description True if primary TBODY should scroll while THEAD remains fixed.
429     * When enabling this feature, captions cannot be used, and the following
430     * features are not recommended: inline editing, resizeable columns.
431     * @default false
432     * @type Boolean
433     */
434     this.setAttributeConfig("scrollable", {
435         value: false,
436         validator: function(oParam) {
437             //TODO: validate agnst resizeable
438             return (YAHOO.lang.isBoolean(oParam) &&
439                     // Not compatible with caption
440                     !YAHOO.lang.isString(this.get("caption")));
441         },
442         method: function(oParam) {
443             if(oParam) {
444                 //TODO: conf height
445                 YAHOO.util.Dom.addClass(this._elContainer,YAHOO.widget.DataTable.CLASS_SCROLLABLE);
446                 YAHOO.util.Dom.addClass(this._elTbody,YAHOO.widget.DataTable.CLASS_SCROLLBODY);
447             }
448             else {
449                 YAHOO.util.Dom.removeClass(this._elContainer,YAHOO.widget.DataTable.CLASS_SCROLLABLE);
450                 YAHOO.util.Dom.removeClass(this._elTbody,YAHOO.widget.DataTable.CLASS_SCROLLBODY);
452             }
453         }
454     });
457 /////////////////////////////////////////////////////////////////////////////
459 // Public constants
461 /////////////////////////////////////////////////////////////////////////////
464  * Class name assigned to TABLE element.
466  * @property DataTable.CLASS_TABLE
467  * @type String
468  * @static
469  * @final
470  * @default "yui-dt-table"
471  */
472 YAHOO.widget.DataTable.CLASS_TABLE = "yui-dt-table";
475  * Class name assigned to header container elements within each TH element.
477  * @property DataTable.CLASS_HEADER
478  * @type String
479  * @static
480  * @final
481  * @default "yui-dt-header"
482  */
483 YAHOO.widget.DataTable.CLASS_HEADER = "yui-dt-header";
486  * Class name assigned to the primary TBODY element.
488  * @property DataTable.CLASS_BODY
489  * @type String
490  * @static
491  * @final
492  * @default "yui-dt-body"
493  */
494 YAHOO.widget.DataTable.CLASS_BODY = "yui-dt-body";
497  * Class name assigned to the scrolling TBODY element of a fixed scrolling DataTable.
499  * @property DataTable.CLASS_SCROLLBODY
500  * @type String
501  * @static
502  * @final
503  * @default "yui-dt-scrollbody"
504  */
505 YAHOO.widget.DataTable.CLASS_SCROLLBODY = "yui-dt-scrollbody";
508  * Class name assigned to display label elements.
510  * @property DataTable.CLASS_LABEL
511  * @type String
512  * @static
513  * @final
514  * @default "yui-dt-label"
515  */
516 YAHOO.widget.DataTable.CLASS_LABEL = "yui-dt-label";
519  * Class name assigned to resizer handle elements.
521  * @property DataTable.CLASS_RESIZER
522  * @type String
523  * @static
524  * @final
525  * @default "yui-dt-resizer"
526  */
527 YAHOO.widget.DataTable.CLASS_RESIZER = "yui-dt-resizer";
530  * Class name assigned to Editor container elements.
532  * @property DataTable.CLASS_EDITOR
533  * @type String
534  * @static
535  * @final
536  * @default "yui-dt-editor"
537  */
538 YAHOO.widget.DataTable.CLASS_EDITOR = "yui-dt-editor";
541  * Class name assigned to paginator container elements.
543  * @property DataTable.CLASS_PAGINATOR
544  * @type String
545  * @static
546  * @final
547  * @default "yui-dt-paginator"
548  */
549 YAHOO.widget.DataTable.CLASS_PAGINATOR = "yui-dt-paginator";
552  * Class name assigned to page number indicators.
554  * @property DataTable.CLASS_PAGE
555  * @type String
556  * @static
557  * @final
558  * @default "yui-dt-page"
559  */
560 YAHOO.widget.DataTable.CLASS_PAGE = "yui-dt-page";
563  * Class name assigned to default indicators.
565  * @property DataTable.CLASS_DEFAULT
566  * @type String
567  * @static
568  * @final
569  * @default "yui-dt-default"
570  */
571 YAHOO.widget.DataTable.CLASS_DEFAULT = "yui-dt-default";
574  * Class name assigned to previous indicators.
576  * @property DataTable.CLASS_PREVIOUS
577  * @type String
578  * @static
579  * @final
580  * @default "yui-dt-previous"
581  */
582 YAHOO.widget.DataTable.CLASS_PREVIOUS = "yui-dt-previous";
585  * Class name assigned next indicators.
587  * @property DataTable.CLASS_NEXT
588  * @type String
589  * @static
590  * @final
591  * @default "yui-dt-next"
592  */
593 YAHOO.widget.DataTable.CLASS_NEXT = "yui-dt-next";
596  * Class name assigned to first elements.
598  * @property DataTable.CLASS_FIRST
599  * @type String
600  * @static
601  * @final
602  * @default "yui-dt-first"
603  */
604 YAHOO.widget.DataTable.CLASS_FIRST = "yui-dt-first";
607  * Class name assigned to last elements.
609  * @property DataTable.CLASS_LAST
610  * @type String
611  * @static
612  * @final
613  * @default "yui-dt-last"
614  */
615 YAHOO.widget.DataTable.CLASS_LAST = "yui-dt-last";
618  * Class name assigned to even elements.
620  * @property DataTable.CLASS_EVEN
621  * @type String
622  * @static
623  * @final
624  * @default "yui-dt-even"
625  */
626 YAHOO.widget.DataTable.CLASS_EVEN = "yui-dt-even";
629  * Class name assigned to odd elements.
631  * @property DataTable.CLASS_ODD
632  * @type String
633  * @static
634  * @final
635  * @default "yui-dt-odd"
636  */
637 YAHOO.widget.DataTable.CLASS_ODD = "yui-dt-odd";
640  * Class name assigned to selected elements.
642  * @property DataTable.CLASS_SELECTED
643  * @type String
644  * @static
645  * @final
646  * @default "yui-dt-selected"
647  */
648 YAHOO.widget.DataTable.CLASS_SELECTED = "yui-dt-selected";
651  * Class name assigned to highlighted elements.
653  * @property DataTable.CLASS_HIGHLIGHTED
654  * @type String
655  * @static
656  * @final
657  * @default "yui-dt-highlighted"
658  */
659 YAHOO.widget.DataTable.CLASS_HIGHLIGHTED = "yui-dt-highlighted";
662  * Class name assigned to disabled elements.
664  * @property DataTable.CLASS_DISABLED
665  * @type String
666  * @static
667  * @final
668  * @default "yui-dt-disabled"
669  */
670 YAHOO.widget.DataTable.CLASS_DISABLED = "yui-dt-disabled";
673  * Class name assigned to empty indicators.
675  * @property DataTable.CLASS_EMPTY
676  * @type String
677  * @static
678  * @final
679  * @default "yui-dt-empty"
680  */
681 YAHOO.widget.DataTable.CLASS_EMPTY = "yui-dt-empty";
684  * Class name assigned to loading indicatorx.
686  * @property DataTable.CLASS_LOADING
687  * @type String
688  * @static
689  * @final
690  * @default "yui-dt-loading"
691  */
692 YAHOO.widget.DataTable.CLASS_LOADING = "yui-dt-loading";
695  * Class name assigned to error indicators.
697  * @property DataTable.CLASS_ERROR
698  * @type String
699  * @static
700  * @final
701  * @default "yui-dt-error"
702  */
703 YAHOO.widget.DataTable.CLASS_ERROR = "yui-dt-error";
706  * Class name assigned to editable elements.
708  * @property DataTable.CLASS_EDITABLE
709  * @type String
710  * @static
711  * @final
712  * @default "yui-dt-editable"
713  */
714 YAHOO.widget.DataTable.CLASS_EDITABLE = "yui-dt-editable";
717  * Class name assigned to scrollable elements.
719  * @property DataTable.CLASS_SCROLLABLE
720  * @type String
721  * @static
722  * @final
723  * @default "yui-dt-scrollable"
724  */
725 YAHOO.widget.DataTable.CLASS_SCROLLABLE = "yui-dt-scrollable";
728  * Class name assigned to sortable elements.
730  * @property DataTable.CLASS_SORTABLE
731  * @type String
732  * @static
733  * @final
734  * @default "yui-dt-sortable"
735  */
736 YAHOO.widget.DataTable.CLASS_SORTABLE = "yui-dt-sortable";
739  * Class name assigned to ascending elements.
741  * @property DataTable.CLASS_ASC
742  * @type String
743  * @static
744  * @final
745  * @default "yui-dt-asc"
746  */
747 YAHOO.widget.DataTable.CLASS_ASC = "yui-dt-asc";
750  * Class name assigned to descending elements.
752  * @property DataTable.CLASS_DESC
753  * @type String
754  * @static
755  * @final
756  * @default "yui-dt-desc"
757  */
758 YAHOO.widget.DataTable.CLASS_DESC = "yui-dt-desc";
761  * Class name assigned to BUTTON container elements.
763  * @property DataTable.CLASS_BUTTON
764  * @type String
765  * @static
766  * @final
767  * @default "yui-dt-button"
768  */
769 YAHOO.widget.DataTable.CLASS_BUTTON = "yui-dt-button";
772  * Class name assigned to SELECT container elements.
774  * @property DataTable.CLASS_DROPDOWN
775  * @type String
776  * @static
777  * @final
778  * @default "yui-dt-dropdown"
779  */
780 YAHOO.widget.DataTable.CLASS_DROPDOWN = "yui-dt-dropdown";
783  * Class name assigned to INPUT TYPE=CHECKBOX container elements.
785  * @property DataTable.CLASS_CHECKBOX
786  * @type String
787  * @static
788  * @final
789  * @default "yui-dt-checkbox"
790  */
791 YAHOO.widget.DataTable.CLASS_CHECKBOX = "yui-dt-checkbox";
794  * Message to display if DataTable has no data.
796  * @property DataTable.MSG_EMPTY
797  * @type String
798  * @static
799  * @final
800  * @default "No records found."
801  */
802 YAHOO.widget.DataTable.MSG_EMPTY = "No records found.";
805  * Message to display while DataTable is loading data.
807  * @property DataTable.MSG_LOADING
808  * @type String
809  * @static
810  * @final
811  * @default "Loading data..."
812  */
813 YAHOO.widget.DataTable.MSG_LOADING = "Loading data...";
816  * Message to display while DataTable has data error.
818  * @property DataTable.MSG_ERROR
819  * @type String
820  * @static
821  * @final
822  * @default "Data error."
823  */
824 YAHOO.widget.DataTable.MSG_ERROR = "Data error.";
826 /////////////////////////////////////////////////////////////////////////////
828 // Private member variables
830 /////////////////////////////////////////////////////////////////////////////
833  * Internal class variable for indexing multiple DataTable instances.
835  * @property DataTable._nCount
836  * @type Number
837  * @private
838  * @static
839  */
840 YAHOO.widget.DataTable._nCount = 0;
843  * Index assigned to instance.
845  * @property _nIndex
846  * @type Number
847  * @private
848  */
849 YAHOO.widget.DataTable.prototype._nIndex = null;
852  * Counter for IDs assigned to TR elements.
854  * @property _nTrCount
855  * @type Number
856  * @private
857  */
858 YAHOO.widget.DataTable.prototype._nTrCount = 0;
861  * Unique name assigned to instance.
863  * @property _sName
864  * @type String
865  * @private
866  */
867 YAHOO.widget.DataTable.prototype._sName = null;
870  * DOM reference to the container element for the DataTable instance into which
871  * the TABLE element gets created.
873  * @property _elContainer
874  * @type HTMLElement
875  * @private
876  */
877 YAHOO.widget.DataTable.prototype._elContainer = null;
880  * DOM reference to the CAPTION element for the DataTable instance.
882  * @property _elCaption
883  * @type HTMLElement
884  * @private
885  */
886 YAHOO.widget.DataTable.prototype._elCaption = null;
889  * DOM reference to the TABLE element for the DataTable instance.
891  * @property _elTable
892  * @type HTMLElement
893  * @private
894  */
895 YAHOO.widget.DataTable.prototype._elTable = null;
898  * DOM reference to the THEAD element for the DataTable instance.
900  * @property _elThead
901  * @type HTMLElement
902  * @private
903  */
904 YAHOO.widget.DataTable.prototype._elThead = null;
907  * DOM reference to the primary TBODY element for the DataTable instance.
909  * @property _elTbody
910  * @type HTMLElement
911  * @private
912  */
913 YAHOO.widget.DataTable.prototype._elTbody = null;
916  * DOM reference to the secondary TBODY element used to display DataTable messages.
918  * @property _elMsgTbody
919  * @type HTMLElement
920  * @private
921  */
922 YAHOO.widget.DataTable.prototype._elMsgTbody = null;
925  * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
927  * @property _elMsgTbodyRow
928  * @type HTMLElement
929  * @private
930  */
931 YAHOO.widget.DataTable.prototype._elMsgTbodyRow = null;
934  * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
936  * @property _elMsgTbodyCell
937  * @type HTMLElement
938  * @private
939  */
940 YAHOO.widget.DataTable.prototype._elMsgTbodyCell = null;
943  * DataSource instance for the DataTable instance.
945  * @property _oDataSource
946  * @type YAHOO.util.DataSource
947  * @private
948  */
949 YAHOO.widget.DataTable.prototype._oDataSource = null;
952  * ColumnSet instance for the DataTable instance.
954  * @property _oColumnSet
955  * @type YAHOO.widget.ColumnSet
956  * @private
957  */
958 YAHOO.widget.DataTable.prototype._oColumnSet = null;
961  * RecordSet instance for the DataTable instance.
963  * @property _oRecordSet
964  * @type YAHOO.widget.RecordSet
965  * @private
966  */
967 YAHOO.widget.DataTable.prototype._oRecordSet = null;
970  * ID string of first label link element of the current DataTable page, if any.
971  * Used for focusing sortable Columns with TAB.
973  * @property _sFirstLabelLinkId
974  * @type String
975  * @private
976  */
977 YAHOO.widget.DataTable.prototype._sFirstLabelLinkId = null;
980  * ID string of first TR element of the current DataTable page.
982  * @property _sFirstTrId
983  * @type String
984  * @private
985  */
986 YAHOO.widget.DataTable.prototype._sFirstTrId = null;
989  * ID string of the last TR element of the current DataTable page.
991  * @property _sLastTrId
992  * @type String
993  * @private
994  */
995 YAHOO.widget.DataTable.prototype._sLastTrId = null;
1028 /////////////////////////////////////////////////////////////////////////////
1030 // Private methods
1032 /////////////////////////////////////////////////////////////////////////////
1035  * Sets focus on the given element.
1037  * @method _focusEl
1038  * @param el {HTMLElement} Element.
1039  * @private
1040  */
1041 YAHOO.widget.DataTable.prototype._focusEl = function(el) {
1042     el = el || this._elTable;
1043     // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
1044     // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
1045     // strange unexpected things as the user clicks on buttons and other controls.
1046     setTimeout(function() { el.focus(); },0);
1053 // INIT FUNCTIONS
1056  * Initializes container element.
1058  * @method _initContainerEl
1059  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
1060  * @private
1061  */
1062 YAHOO.widget.DataTable.prototype._initContainerEl = function(elContainer) {
1063     this._elContainer = null;
1064     elContainer = YAHOO.util.Dom.get(elContainer);
1065     if(elContainer && elContainer.tagName && (elContainer.tagName.toLowerCase() == "div")) {
1066         this._elContainer = elContainer;
1067     }
1071  * Initializes object literal of config values.
1073  * @method _initConfigs
1074  * @param oConfig {Object} Object literal of config values.
1075  * @private
1076  */
1077 YAHOO.widget.DataTable.prototype._initConfigs = function(oConfigs) {
1078     if(oConfigs) {
1079         if(oConfigs.constructor != Object) {
1080             oConfigs = null;
1081         }
1082         // Backward compatibility
1083         else if(YAHOO.lang.isBoolean(oConfigs.paginator)) {
1084         }
1085         this._oConfigs = oConfigs;
1086     }
1090  * Initializes ColumnSet.
1092  * @method _initColumnSet
1093  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
1094  * @private
1095  */
1096 YAHOO.widget.DataTable.prototype._initColumnSet = function(aColumnDefs) {
1097     this._oColumnSet = null;
1098     if(YAHOO.lang.isArray(aColumnDefs)) {
1099         this._oColumnSet =  new YAHOO.widget.ColumnSet(aColumnDefs);
1100     }
1101     // Backward compatibility
1102     else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
1103         this._oColumnSet =  aColumnDefs;
1104     }
1108  * Initializes DataSource.
1110  * @method _initDataSource
1111  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
1112  * @private
1113  */
1114 YAHOO.widget.DataTable.prototype._initDataSource = function(oDataSource) {
1115     this._oDataSource = null;
1116     if(oDataSource && (oDataSource instanceof YAHOO.util.DataSource)) {
1117         this._oDataSource = oDataSource;
1118     }
1119     // Backward compatibility
1120     else {
1121         var tmpTable = null;
1122         var tmpContainer = this._elContainer;
1123         // Peek in container child nodes to see if TABLE already exists
1124         if(tmpContainer.hasChildNodes()) {
1125             var tmpChildren = tmpContainer.childNodes;
1126             for(i=0; i<tmpChildren.length; i++) {
1127                 if(tmpChildren[i].tagName && tmpChildren[i].tagName.toLowerCase() == "table") {
1128                     tmpTable = tmpChildren[i];
1129                     break;
1130                 }
1131             }
1132             if(tmpTable) {
1133                 var tmpFieldsArray = [];
1134                 for(i=0; i<this._oColumnSet.keys.length; i++) {
1135                     tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
1136                 }
1138                 this._oDataSource = new YAHOO.util.DataSource(tmpTable);
1139                 this._oDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
1140                 this._oDataSource.responseSchema = {fields: tmpFieldsArray};
1141             }
1142         }
1143     }
1147  * Initializes RecordSet.
1149  * @method _initRecordSet
1150  * @private
1151  */
1152 YAHOO.widget.DataTable.prototype._initRecordSet = function() {
1153     if(this._oRecordSet) {
1154         this._oRecordSet.reset();
1155     }
1156     else {
1157         this._oRecordSet = new YAHOO.widget.RecordSet();
1158     }
1162  * Creates HTML markup for TABLE, THEAD and TBODY elements.
1164  * @method _initTableEl
1165  * @private
1166  */
1167 YAHOO.widget.DataTable.prototype._initTableEl = function() {
1168     // Clear the container
1169     YAHOO.util.Event.purgeElement(this._elContainer, true);
1170     this._elContainer.innerHTML = "";
1172     // Create TABLE
1173     this._elTable = this._elContainer.appendChild(document.createElement("table"));
1174     var elTable = this._elTable;
1175     elTable.tabIndex = 0;
1176     elTable.id = this.id + "-table";
1177     YAHOO.util.Dom.addClass(elTable, YAHOO.widget.DataTable.CLASS_TABLE);
1179     // Create THEAD
1180     this._initTheadEl(elTable, this._oColumnSet);
1183     // Create TBODY for messages
1184     var elMsgTbody = document.createElement("tbody");
1185     var elMsgRow = elMsgTbody.appendChild(document.createElement("tr"));
1186     YAHOO.util.Dom.addClass(elMsgRow,YAHOO.widget.DataTable.CLASS_FIRST);
1187     YAHOO.util.Dom.addClass(elMsgRow,YAHOO.widget.DataTable.CLASS_LAST);
1188     this._elMsgRow = elMsgRow;
1189     var elMsgCell = elMsgRow.appendChild(document.createElement("td"));
1190     elMsgCell.colSpan = this._oColumnSet.keys.length;
1191     YAHOO.util.Dom.addClass(elMsgCell,YAHOO.widget.DataTable.CLASS_FIRST);
1192     YAHOO.util.Dom.addClass(elMsgCell,YAHOO.widget.DataTable.CLASS_LAST);
1193     this._elMsgTd = elMsgCell;
1194     this._elMsgTbody = elTable.appendChild(elMsgTbody);
1195     this.showTableMessage(YAHOO.widget.DataTable.MSG_LOADING, YAHOO.widget.DataTable.CLASS_LOADING);
1197     // Create TBODY for data
1198     this._elTbody = elTable.appendChild(document.createElement("tbody"));
1199     YAHOO.util.Dom.addClass(this._elTbody,YAHOO.widget.DataTable.CLASS_BODY);
1203  * Populates THEAD element with TH cells as defined by ColumnSet.
1205  * @method _initTheadEl
1206  * @private
1207  */
1208 YAHOO.widget.DataTable.prototype._initTheadEl = function() {
1209     var i,oColumn, colId;
1210     var oColumnSet = this._oColumnSet;
1211     this._sFirstLabelLinkId = null;
1212     
1213     // Create THEAD
1214     var elThead = document.createElement("thead");
1216     // Iterate through each row of Column headers...
1217     var colTree = oColumnSet.tree;
1218     for(i=0; i<colTree.length; i++) {
1219         var elTheadRow = elThead.appendChild(document.createElement("tr"));
1220         elTheadRow.id = this.id+"-hdrow"+i;
1222         var elTheadCell;
1223         // ...and create THEAD cells
1224         for(var j=0; j<colTree[i].length; j++) {
1225             oColumn = colTree[i][j];
1226             colId = oColumn.getId();
1227             elTheadCell = elTheadRow.appendChild(document.createElement("th"));
1228             elTheadCell.id = this.id + "-col" + colId;
1229             this._initThEl(elTheadCell,oColumn,i,j);
1230         }
1232         // Set FIRST/LAST on THEAD rows
1233         if(i === 0) {
1234             YAHOO.util.Dom.addClass(elTheadRow, YAHOO.widget.DataTable.CLASS_FIRST);
1235         }
1236         if(i === (colTree.length-1)) {
1237             YAHOO.util.Dom.addClass(elTheadRow, YAHOO.widget.DataTable.CLASS_LAST);
1238         }
1239     }
1241     this._elThead = this._elTable.appendChild(elThead);
1243     // Set FIRST/LAST on THEAD cells using the values in ColumnSet headers array
1244     var aFirstHeaders = oColumnSet.headers[0].split(" ");
1245     var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1].split(" ");
1246     for(i=0; i<aFirstHeaders.length; i++) {
1247         YAHOO.util.Dom.addClass(YAHOO.util.Dom.get(this.id+"-col"+aFirstHeaders[i]), YAHOO.widget.DataTable.CLASS_FIRST);
1248     }
1249     for(i=0; i<aLastHeaders.length; i++) {
1250         YAHOO.util.Dom.addClass(YAHOO.util.Dom.get(this.id+"-col"+aLastHeaders[i]), YAHOO.widget.DataTable.CLASS_LAST);
1251     }
1252     
1253     // Add Resizer only after DOM has been updated
1254     var foundDD = (YAHOO.util.DD) ? true : false;
1255     var needDD = false;
1256     for(i=0; i<this._oColumnSet.keys.length; i++) {
1257         oColumn = this._oColumnSet.keys[i];
1258         colId = oColumn.getId();
1259         var elTheadCellId = YAHOO.util.Dom.get(this.id + "-col" + colId);
1260         if(oColumn.resizeable) {
1261             if(foundDD) {
1262                 //TODO: fix fixed width tables
1263                 // Skip the last column for fixed-width tables
1264                 if(!this.fixedWidth || (this.fixedWidth &&
1265                         (oColumn.getKeyIndex() != this._oColumnSet.keys.length-1))) {
1266                     // TODO: better way to get elTheadContainer
1267                     var elThContainer = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_HEADER,"div",elTheadCellId)[0];
1268                     var elThResizer = elThContainer.appendChild(document.createElement("span"));
1269                     elThResizer.id = this.id + "-resizer" + colId;
1270                     YAHOO.util.Dom.addClass(elThResizer,YAHOO.widget.DataTable.CLASS_RESIZER);
1271                     oColumn.ddResizer = new YAHOO.util.ColumnResizer(
1272                             this, oColumn, elTheadCellId, elThResizer.id, elThResizer.id);
1273                     var cancelClick = function(e) {
1274                         YAHOO.util.Event.stopPropagation(e);
1275                     };
1276                     YAHOO.util.Event.addListener(elThResizer,"click",cancelClick);
1277                 }
1278                 if(this.fixedWidth) {
1279                     //TODO: fix fixedWidth
1280                     //elThContainer.style.overflow = "hidden";
1281                     //TODO: better way to get elTheadText
1282                     var elThLabel = (YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_LABEL,"span",elTheadCellId))[0];
1283                     elThLabel.style.overflow = "hidden";
1284                 }
1285             }
1286             else {
1287                 needDD = true;
1288             }
1289         }
1290     }
1291     if(needDD) {
1292     }
1297  * Populates TH cell as defined by Column.
1299  * @method _initThEl
1300  * @param elTheadCell {HTMLElement} TH cell element reference.
1301  * @param oColumn {YAHOO.widget.Column} Column object.
1302  * @param row {number} Row index.
1303  * @param col {number} Column index.
1304  * @private
1305  */
1306 YAHOO.widget.DataTable.prototype._initThEl = function(elTheadCell,oColumn,row,col) {
1307     // Clear out the cell of prior content
1308     // TODO: purgeListeners and other validation-related things
1309     var index = this._nIndex;
1310     var colId = oColumn.getId();
1311     elTheadCell.yuiColumnId = colId;
1312     if(oColumn.abbr) {
1313         elTheadCell.abbr = oColumn.abbr;
1314     }
1315     if(oColumn.width) {
1316         elTheadCell.style.width = oColumn.width;
1317     }
1319     var aCustomClasses;
1320     if(YAHOO.lang.isString(oColumn.className)) {
1321         aCustomClasses = [oColumn.className];
1322     }
1323     else if(YAHOO.lang.isArray(oColumn.className)) {
1324         aCustomClasses = oColumn.className;
1325     }
1326     if(aCustomClasses) {
1327         for(var i=0; i<aCustomClasses.length; i++) {
1328             YAHOO.util.Dom.addClass(elTheadCell,aCustomClasses[i]);
1329         }
1330     }
1331     
1332     YAHOO.util.Dom.addClass(elTheadCell, "yui-dt-col-"+oColumn.key);
1333     
1334     elTheadCell.innerHTML = "";
1335     elTheadCell.rowSpan = oColumn.getRowspan();
1336     elTheadCell.colSpan = oColumn.getColspan();
1338     var elTheadContainer = elTheadCell.appendChild(document.createElement("div"));
1339     elTheadContainer.id = this.id + "-container" + colId;
1340     YAHOO.util.Dom.addClass(elTheadContainer,YAHOO.widget.DataTable.CLASS_HEADER);
1341     var elTheadLabel = elTheadContainer.appendChild(document.createElement("span"));
1342     elTheadLabel.id = this.id + "-label" + colId;
1343     YAHOO.util.Dom.addClass(elTheadLabel,YAHOO.widget.DataTable.CLASS_LABEL);
1345     var sLabel = YAHOO.lang.isValue(oColumn.label) ? oColumn.label : oColumn.key;
1346     if(oColumn.sortable) {
1347         YAHOO.util.Dom.addClass(elTheadCell,YAHOO.widget.DataTable.CLASS_SORTABLE);
1348         //TODO: Make sortLink customizeable
1349         //TODO: Make title configurable
1350         //TODO: Separate label from an accessibility link that says
1351         // "Click to sort ascending" and push it offscreen
1352         var sLabelLinkId = this.id + "-labellink" + colId;
1353         var sortLink = "?key=" + oColumn.key;
1354         elTheadLabel.innerHTML = "<a id=\"" + sLabelLinkId + "\" href=\"" + sortLink + "\" title=\"Click to sort\" class=\"" + YAHOO.widget.DataTable.CLASS_SORTABLE + "\">" + sLabel + "</a>";
1355         if(!this._sFirstLabelLinkId) {
1356             this._sFirstLabelLinkId = sLabelLinkId;
1357         }
1358     }
1359     else {
1360         elTheadLabel.innerHTML = sLabel;
1361     }
1365  * Creates HTML markup for Cell Editor.
1367  * @method _initCellEditorEl
1368  * @private
1369  */
1370 YAHOO.widget.DataTable.prototype._initCellEditorEl = function() {
1371     // Attach Cell Editor container element to body
1372     var elCellEditor = document.createElement("div");
1373     elCellEditor.id = this.id + "-celleditor";
1374     elCellEditor.style.display = "none";
1375     YAHOO.util.Dom.addClass(elCellEditor, YAHOO.widget.DataTable.CLASS_EDITOR);
1376     elCellEditor = document.body.appendChild(elCellEditor);
1378     // Internal tracker of Cell Editor values
1379     var oCellEditor = {};
1380     oCellEditor.container = elCellEditor;
1381     oCellEditor.value = null;
1382     oCellEditor.isActive = false;
1383     this._oCellEditor = oCellEditor;
1385     // Handle ESC key
1386     this.subscribe("editorKeydownEvent", function(oArgs) {
1387         var e = oArgs.event;
1388         var elTarget = YAHOO.util.Event.getTarget(e);
1390         // ESC hides Cell Editor
1391         if((e.keyCode == 27)) {
1392             this.cancelCellEditor();
1393         }
1394     });
1398  * Initializes Column sorting.
1400  * @method _initColumnSort
1401  * @private
1402  */
1403 YAHOO.widget.DataTable.prototype._initColumnSort = function() {
1404     this.subscribe("headerCellClickEvent", this.onEventSortColumn);
1408  * Initializes DOM event listeners.
1410  * @method _initDomEvents
1411  * @private
1412  */
1413 YAHOO.widget.DataTable.prototype._initDomEvents = function() {
1414     var elTable = this._elTable;
1415     var elThead = this._elThead;
1416     var elTbody = this._elTbody;
1417     var elContainer = this._elContainer;
1419     YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
1420     YAHOO.util.Event.addListener(document, "keydown", this._onDocumentKeydown, this);
1422     YAHOO.util.Event.addListener(elTable, "focus", this._onTableFocus, this);
1423     YAHOO.util.Event.addListener(elTable, "mouseover", this._onTableMouseover, this);
1424     YAHOO.util.Event.addListener(elTable, "mouseout", this._onTableMouseout, this);
1425     YAHOO.util.Event.addListener(elTable, "mousedown", this._onTableMousedown, this);
1426     YAHOO.util.Event.addListener(elTable, "keydown", this._onTableKeydown, this);
1427     YAHOO.util.Event.addListener(elTable, "keypress", this._onTableKeypress, this);
1429     // Since we can't listen for click and dblclick on the same element...
1430     YAHOO.util.Event.addListener(elTable, "dblclick", this._onTableDblclick, this);
1431     YAHOO.util.Event.addListener(elThead, "click", this._onTheadClick, this);
1432     YAHOO.util.Event.addListener(elTbody, "click", this._onTbodyClick, this);
1434     YAHOO.util.Event.addListener(elContainer, "scroll", this._onScroll, this); // for IE
1435     YAHOO.util.Event.addListener(elTbody, "scroll", this._onScroll, this); // for everyone else
1474 // DOM MUTATION FUNCTIONS
1480  * Adds a TR element to the primary TBODY at the page row index if given, otherwise
1481  * at the end of the page. Formats TD elements within the TR element using data
1482  * from the given Record.
1484  * @method _addTrEl
1485  * @param oRecord {YAHOO.widget.Record} Record instance.
1486  * @param index {Number} (optional) The page row index at which to add the TR
1487  * element.
1488  * @return {String} ID of the added TR element, or null.
1489  * @private
1490  */
1491 YAHOO.widget.DataTable.prototype._addTrEl = function(oRecord, index) {
1492     this.hideTableMessage();
1494     // It's an append if no index provided, or index is negative or too big
1495     var append = (!YAHOO.lang.isNumber(index) || (index < 0) ||
1496             (index >= (this._elTbody.rows.length))) ? true : false;
1497             
1498     var oColumnSet = this._oColumnSet;
1499     var oRecordSet = this._oRecordSet;
1500     var isSortedBy = this.get("sortedBy");
1501     var sortedColKeyIndex  = null;
1502     var sortedDir, newClass;
1503     if(isSortedBy) {
1504         sortedColKeyIndex = (isSortedBy.column) ?
1505                 isSortedBy.column.getKeyIndex() :
1506                 this._oColumnSet.getColumn(isSortedBy.key).getKeyIndex();
1507         sortedDir = isSortedBy.dir;
1508         newClass = (sortedDir === "desc") ? YAHOO.widget.DataTable.CLASS_DESC :
1509                 YAHOO.widget.DataTable.CLASS_ASC;
1511     }
1514     var elRow = (append) ? this._elTbody.appendChild(document.createElement("tr")) :
1515         this._elTbody.insertBefore(document.createElement("tr"),this._elTbody.rows[index]);
1517     elRow.id = this.id+"-bdrow"+this._nTrCount;
1518     this._nTrCount++;
1519     elRow.yuiRecordId = oRecord.getId();
1521     // Create TD cells
1522     for(var j=0; j<oColumnSet.keys.length; j++) {
1523         var oColumn = oColumnSet.keys[j];
1524         var elCell = elRow.appendChild(document.createElement("td"));
1525         elCell.id = elRow.id+"-cell"+j;
1526         elCell.yuiColumnId = oColumn.getId();
1527         elCell.headers = oColumnSet.headers[j];
1528         // For SF2 cellIndex bug: http://www.webreference.com/programming/javascript/ppk2/3.html
1529         elCell.yuiCellIndex = j;
1531         // Update UI
1532         this.formatCell(elCell, oRecord, oColumn);
1534         // Set FIRST/LAST on TD
1535         if (j === 0) {
1536             YAHOO.util.Dom.addClass(elCell, YAHOO.widget.DataTable.CLASS_FIRST);
1537         }
1538         else if (j === this._oColumnSet.keys.length-1) {
1539             YAHOO.util.Dom.addClass(elCell, YAHOO.widget.DataTable.CLASS_LAST);
1540         }
1541         
1542         // Remove ASC/DESC
1543         YAHOO.util.Dom.removeClass(elCell, YAHOO.widget.DataTable.CLASS_ASC);
1544         YAHOO.util.Dom.removeClass(elCell, YAHOO.widget.DataTable.CLASS_DESC);
1545         
1546         // Set ASC/DESC on TD
1547         if(j === sortedColKeyIndex) {
1548             newClass = (sortedDir === "desc") ?
1549                     YAHOO.widget.DataTable.CLASS_DESC :
1550                     YAHOO.widget.DataTable.CLASS_ASC;
1551             YAHOO.util.Dom.addClass(elCell, newClass);
1552         }
1555         /*p.abx {word-wrap:break-word;}
1556 ought to solve the problem for Safari (the long words will wrap in your
1557 tds, instead of overflowing to the next td.
1558 (this is supported by IE win as well, so hide it if needed).
1560 One thing, though: it doesn't work in combination with
1561 'white-space:nowrap'.*/
1563 // need a div wrapper for safari?
1564         //TODO: fix fixedWidth
1565         if(this.fixedWidth) {
1566             elCell.style.overflow = "hidden";
1567             //elCell.style.width = "20px";
1568         }
1569     }
1571     return elRow.id;
1575  * Formats all TD elements of given TR element with data from the given Record.
1577  * @method _updateTrEl
1578  * @param elRow {HTMLElement} The TR element to update.
1579  * @param oRecord {YAHOO.widget.Record} The associated Record instance.
1580  * @return {String} ID of the updated TR element, or null.
1581  * @private
1582  */
1583 YAHOO.widget.DataTable.prototype._updateTrEl = function(elRow, oRecord) {
1584     this.hideTableMessage();
1586     var isSortedBy = this.get("sortedBy");
1587     var sortedColKeyIndex  = null;
1588     var sortedDir, newClass;
1589     if(isSortedBy) {
1590         sortedColKeyIndex = (isSortedBy.column) ?
1591                 isSortedBy.column.getKeyIndex() :
1592                 this._oColumnSet.getColumn(isSortedBy.key).getKeyIndex();
1593         sortedDir = isSortedBy.dir;
1594         newClass = (sortedDir === "desc") ? YAHOO.widget.DataTable.CLASS_DESC :
1595                 YAHOO.widget.DataTable.CLASS_ASC;
1596     }
1598     // Update TD elements with new data
1599     for(var j=0; j<elRow.cells.length; j++) {
1600         var oColumn = this._oColumnSet.keys[j];
1601         var elCell = elRow.cells[j];
1602         this.formatCell(elCell, oRecord, oColumn);
1604         // Remove ASC/DESC
1605         YAHOO.util.Dom.removeClass(elCell, YAHOO.widget.DataTable.CLASS_ASC);
1606         YAHOO.util.Dom.removeClass(elCell, YAHOO.widget.DataTable.CLASS_DESC);
1608         // Set ASC/DESC on TD
1609         if(j === sortedColKeyIndex) {
1610             YAHOO.util.Dom.addClass(elCell, newClass);
1611         }
1612     }
1614     // Update Record ID
1615     elRow.yuiRecordId = oRecord.getId();
1616     
1617     return elRow.id;
1622  * Deletes TR element by DOM reference or by DataTable page row index.
1624  * @method _deleteTrEl
1625  * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
1626  * @return {Boolean} Returns true if successful, else returns false.
1627  * @private
1628  */
1629 YAHOO.widget.DataTable.prototype._deleteTrEl = function(row) {
1630     var rowIndex;
1631     
1632     // Get page row index for the element
1633     if(!YAHOO.lang.isNumber(row)) {
1634         rowIndex = YAHOO.util.Dom.get(row).sectionRowIndex;
1635     }
1636     else {
1637         rowIndex = row;
1638     }
1639     if(YAHOO.lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
1640         this._elTbody.deleteRow(rowIndex);
1641         return true;
1642     }
1643     else {
1644         return false;
1645     }
1674 // CSS/STATE FUNCTIONS
1680  * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
1681  * of the DataTable page and updates internal tracker.
1683  * @method _setFirstRow
1684  * @private
1685  */
1686 YAHOO.widget.DataTable.prototype._setFirstRow = function() {
1687     var rowEl = this.getFirstTrEl();
1688     if(rowEl) {
1689         // Remove FIRST
1690         if(this._sFirstTrId) {
1691             YAHOO.util.Dom.removeClass(this._sFirstTrId, YAHOO.widget.DataTable.CLASS_FIRST);
1692         }
1693         // Set FIRST
1694         YAHOO.util.Dom.addClass(rowEl, YAHOO.widget.DataTable.CLASS_FIRST);
1695         this._sFirstTrId = rowEl.id;
1696     }
1697     else {
1698         this._sFirstTrId = null;
1699     }
1703  * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
1704  * of the DataTable page and updates internal tracker.
1706  * @method _setLastRow
1707  * @private
1708  */
1709 YAHOO.widget.DataTable.prototype._setLastRow = function() {
1710     var rowEl = this.getLastTrEl();
1711     if(rowEl) {
1712         // Unassign previous class
1713         if(this._sLastTrId) {
1714             YAHOO.util.Dom.removeClass(this._sLastTrId, YAHOO.widget.DataTable.CLASS_LAST);
1715         }
1716         // Assign class
1717         YAHOO.util.Dom.addClass(rowEl, YAHOO.widget.DataTable.CLASS_LAST);
1718         this._sLastTrId = rowEl.id;
1719     }
1720     else {
1721         this._sLastTrId = null;
1722     }
1726  * Assigns the classes YAHOO.widget.DataTable.CLASS_EVEN and
1727  * YAHOO.widget.DataTable.CLASS_ODD to alternating TR elements of the DataTable
1728  * page. For performance, a subset of rows may be specified.
1730  * @method _setRowStripes
1731  * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
1732  * or string ID, or page row index of where to start striping.
1733  * @param range {Number} (optional) If given, how many rows to stripe, otherwise
1734  * stripe all the rows until the end.
1735  * @private
1736  */
1737 YAHOO.widget.DataTable.prototype._setRowStripes = function(row, range) {
1738     // Default values stripe all rows
1739     var allRows = this._elTbody.rows;
1740     var nStartIndex = 0;
1741     var nEndIndex = allRows.length;
1742     
1743     // Stripe a subset
1744     if((row !== null) && (row !== undefined)) {
1745         // Validate given start row
1746         var elStartRow = this.getTrEl(row);
1747         if(elStartRow) {
1748             nStartIndex = elStartRow.sectionRowIndex;
1749             
1750             // Validate given range
1751             if(YAHOO.lang.isNumber(range) && (range > 1)) {
1752                 nEndIndex = nStartIndex + range;
1753             }
1754         }
1755     }
1757     for(var i=nStartIndex; i<nEndIndex; i++) {
1758         if(i%2) {
1759             YAHOO.util.Dom.removeClass(allRows[i], YAHOO.widget.DataTable.CLASS_EVEN);
1760             YAHOO.util.Dom.addClass(allRows[i], YAHOO.widget.DataTable.CLASS_ODD);
1761         }
1762         else {
1763             YAHOO.util.Dom.removeClass(allRows[i], YAHOO.widget.DataTable.CLASS_ODD);
1764             YAHOO.util.Dom.addClass(allRows[i], YAHOO.widget.DataTable.CLASS_EVEN);
1765         }
1766     }
1813 /////////////////////////////////////////////////////////////////////////////
1815 // Private DOM Event Handlers
1817 /////////////////////////////////////////////////////////////////////////////
1820  * Handles scroll events on the CONTAINER (for IE) and TBODY elements (for everyone else).
1822  * @method _onScroll
1823  * @param e {HTMLEvent} The scroll event.
1824  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
1825  * @private
1826  */
1827 YAHOO.widget.DataTable.prototype._onScroll = function(e, oSelf) {
1828     var elTarget = YAHOO.util.Event.getTarget(e);
1829     var elTag = elTarget.tagName.toLowerCase();
1830     
1831     if(oSelf._oCellEditor.isActive) {
1832         oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
1833         oSelf.cancelCellEditor();
1834     }
1836     oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
1840  * Handles click events on the DOCUMENT.
1842  * @method _onDocumentClick
1843  * @param e {HTMLEvent} The click event.
1844  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
1845  * @private
1846  */
1847 YAHOO.widget.DataTable.prototype._onDocumentClick = function(e, oSelf) {
1848     var elTarget = YAHOO.util.Event.getTarget(e);
1849     var elTag = elTarget.tagName.toLowerCase();
1851     if(!YAHOO.util.Dom.isAncestor(oSelf._elTable, elTarget)) {
1852         oSelf.fireEvent("tableBlurEvent");
1854         // Fires editorBlurEvent when click is not within the TABLE.
1855         // For cases when click is within the TABLE, due to timing issues,
1856         // the editorBlurEvent needs to get fired by the lower-level DOM click
1857         // handlers below rather than by the TABLE click handler directly.
1858         if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
1859             // Only if the click was not within the Cell Editor container
1860             if(!YAHOO.util.Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
1861                     (oSelf._oCellEditor.container.id !== elTarget.id)) {
1862                 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
1863             }
1864         }
1865     }
1869  * Handles keydown events on the DOCUMENT.
1871  * @method _onDocumentKeydown
1872  * @param e {HTMLEvent} The keydown event.
1873  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
1874  * @private
1875  */
1876 YAHOO.widget.DataTable.prototype._onDocumentKeydown = function(e, oSelf) {
1877     var elTarget = YAHOO.util.Event.getTarget(e);
1878     var elTag = elTarget.tagName.toLowerCase();
1880     if(oSelf._oCellEditor && oSelf._oCellEditor.isActive &&
1881             YAHOO.util.Dom.isAncestor(oSelf._oCellEditor.container, elTarget)) {
1882         oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
1883     }
1887  * Handles focus events on the TABLE element.
1889  * @method _onTableFocus
1890  * @param e {HTMLEvent} The focus event.
1891  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
1892  * @private
1893  */
1894 YAHOO.widget.DataTable.prototype._onTableMouseover = function(e, oSelf) {
1895     oSelf.fireEvent("tableFocusEvent");
1899  * Handles mouseover events on the TABLE element.
1901  * @method _onTableMouseover
1902  * @param e {HTMLEvent} The mouseover event.
1903  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
1904  * @private
1905  */
1906 YAHOO.widget.DataTable.prototype._onTableMouseover = function(e, oSelf) {
1907     var elTarget = YAHOO.util.Event.getTarget(e);
1908     var elTag = elTarget.tagName.toLowerCase();
1910     while(elTarget && (elTag != "table")) {
1911         switch(elTag) {
1912             case "body":
1913                  break;
1914             case "a":
1915                 break;
1916             case "td":
1917                 oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
1918                 break;
1919             case "span":
1920                 if(YAHOO.util.Dom.hasClass(elTarget, YAHOO.widget.DataTable.CLASS_LABEL)) {
1921                     oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
1922                 }
1923                 break;
1924             case "th":
1925                 oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
1926                 break;
1927             case "tr":
1928                 if(elTarget.parentNode.tagName.toLowerCase() == "thead") {
1929                     oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
1930                 }
1931                 else {
1932                     oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
1933                 }
1934                 break;
1935             default:
1936                 break;
1937         }
1938         elTarget = elTarget.parentNode;
1939         if(elTarget) {
1940             elTag = elTarget.tagName.toLowerCase();
1941         }
1942     }
1943     oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elTable),event:e});
1947  * Handles mouseout events on the TABLE element.
1949  * @method _onTableMouseout
1950  * @param e {HTMLEvent} The mouseout event.
1951  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
1952  * @private
1953  */
1954 YAHOO.widget.DataTable.prototype._onTableMouseout = function(e, oSelf) {
1955     var elTarget = YAHOO.util.Event.getTarget(e);
1956     var elTag = elTarget.tagName.toLowerCase();
1958     while(elTarget && (elTag != "table")) {
1959         switch(elTag) {
1960             case "body":
1961                 break;
1962             case "a":
1963                 break;
1964             case "td":
1965                 oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
1966                 break;
1967             case "span":
1968                 if(YAHOO.util.Dom.hasClass(elTarget, YAHOO.widget.DataTable.CLASS_LABEL)) {
1969                     oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
1970                 }
1971                 break;
1972             case "th":
1973                 oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
1974                 break;
1975             case "tr":
1976                 if(elTarget.parentNode.tagName.toLowerCase() == "thead") {
1977                     oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
1978                 }
1979                 else {
1980                     oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
1981                 }
1982                 break;
1983             default:
1984                 break;
1985         }
1986         elTarget = elTarget.parentNode;
1987         if(elTarget) {
1988             elTag = elTarget.tagName.toLowerCase();
1989         }
1990     }
1991     oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elTable),event:e});
1995  * Handles mousedown events on the TABLE element.
1997  * @method _onTableMousedown
1998  * @param e {HTMLEvent} The mousedown event.
1999  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2000  * @private
2001  */
2002 YAHOO.widget.DataTable.prototype._onTableMousedown = function(e, oSelf) {
2003     var elTarget = YAHOO.util.Event.getTarget(e);
2004     var elTag = elTarget.tagName.toLowerCase();
2006     while(elTarget && (elTag != "table")) {
2007         switch(elTag) {
2008             case "body":
2009                 break;
2010             case "a":
2011                 break;
2012             case "td":
2013                 oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
2014                 break;
2015             case "span":
2016                 if(YAHOO.util.Dom.hasClass(elTarget, YAHOO.widget.DataTable.CLASS_LABEL)) {
2017                     oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
2018                 }
2019                 break;
2020             case "th":
2021                 oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
2022                 break;
2023             case "tr":
2024                 if(elTarget.parentNode.tagName.toLowerCase() == "thead") {
2025                     oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
2026                 }
2027                 else {
2028                     oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
2029                 }
2030                 break;
2031             default:
2032                 break;
2033         }
2034         elTarget = elTarget.parentNode;
2035         if(elTarget) {
2036             elTag = elTarget.tagName.toLowerCase();
2037         }
2038     }
2039     oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elTable),event:e});
2043  * Handles dblclick events on the TABLE element.
2045  * @method _onTableDblclick
2046  * @param e {HTMLEvent} The dblclick event.
2047  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2048  * @private
2049  */
2050 YAHOO.widget.DataTable.prototype._onTableDblclick = function(e, oSelf) {
2051     var elTarget = YAHOO.util.Event.getTarget(e);
2052     var elTag = elTarget.tagName.toLowerCase();
2054     while(elTarget && (elTag != "table")) {
2055         switch(elTag) {
2056             case "body":
2057                 break;
2058             case "td":
2059                 oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
2060                 break;
2061             case "span":
2062                 if(YAHOO.util.Dom.hasClass(elTarget, YAHOO.widget.DataTable.CLASS_LABEL)) {
2063                     oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
2064                 }
2065                 break;
2066             case "th":
2067                 oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
2068                 break;
2069             case "tr":
2070                 if(elTarget.parentNode.tagName.toLowerCase() == "thead") {
2071                     oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
2072                 }
2073                 else {
2074                     oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
2075                 }
2076                 break;
2077             default:
2078                 break;
2079         }
2080         elTarget = elTarget.parentNode;
2081         if(elTarget) {
2082             elTag = elTarget.tagName.toLowerCase();
2083         }
2084     }
2085     oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elTable),event:e});
2089  * Handles keydown events on the TABLE element. Handles arrow selection.
2091  * @method _onTableKeydown
2092  * @param e {HTMLEvent} The key event.
2093  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2094  * @private
2095  */
2096 YAHOO.widget.DataTable.prototype._onTableKeydown = function(e, oSelf) {
2097     var bSHIFT = e.shiftKey;
2098     var elTarget = YAHOO.util.Event.getTarget(e);
2099     
2100     // Ignore actions in the THEAD
2101     if(YAHOO.util.Dom.isAncestor(oSelf._elThead, elTarget)) {
2102         return;
2103     }
2104     
2105     var nKey = YAHOO.util.Event.getCharCode(e);
2106     
2107     // TAB to first label link if any
2108     if(nKey === 9 && !bSHIFT && (elTarget.id === oSelf._elTable.id)) {
2109         if(oSelf._sFirstLabelLinkId) {
2110             YAHOO.util.Event.stopEvent(e);
2111             oSelf._focusEl(YAHOO.util.Dom.get(oSelf._sFirstLabelLinkId));
2112         }
2113         return;
2114     }
2116     // Something is currently selected
2117     var lastSelectedId = oSelf._sLastSelectedId;
2118     var lastSelectedEl = YAHOO.util.Dom.get(lastSelectedId);
2119     if(lastSelectedEl && oSelf.isSelected(lastSelectedEl)) {
2120         //TODO: handle tab, backspace, delete
2121         
2122         // Handle arrow selection
2123         if((nKey > 36) && (nKey < 41)) {
2124             YAHOO.util.Event.stopEvent(e);
2125         }
2126         else {
2127             return;
2128         }
2130         var sMode = oSelf.get("selectionMode");
2131         var allRows = oSelf._elTbody.rows;
2132         var anchorId = oSelf._sSelectionAnchorId;
2133         var anchorEl = YAHOO.util.Dom.get(anchorId);
2134         var newSelectedEl, trIndex, tdIndex, startIndex, endIndex, i, anchorPos;
2136         ////////////////////////////////////////////////////////////////////////
2137         //
2138         // SHIFT cell block selection
2139         //
2140         ////////////////////////////////////////////////////////////////////////
2141         if(bSHIFT && (sMode == "cellblock")) {
2142             trIndex = lastSelectedEl.parentNode.sectionRowIndex;
2143             tdIndex = lastSelectedEl.yuiCellIndex;
2145             // Arrow DOWN
2146             if(nKey == 40) {
2147                 // Is the anchor cell above, below, or same row
2148                 if(anchorEl.parentNode.sectionRowIndex > trIndex) {
2149                     anchorPos = 1;
2150                 }
2151                 else if(anchorEl.parentNode.sectionRowIndex < trIndex) {
2152                     anchorPos = -1;
2153                 }
2154                 else {
2155                     anchorPos = 0;
2156                 }
2158                 // Is the anchor cell left or right
2159                 startIndex = Math.min(anchorEl.yuiCellIndex, tdIndex);
2160                 endIndex = Math.max(anchorEl.yuiCellIndex, tdIndex);
2162                 // Selecting away from anchor cell
2163                 if(anchorPos <= 0) {
2164                     // Select the horiz block on the next row
2165                     if(trIndex < allRows.length-1) {
2166                         for(i=startIndex; i<=endIndex; i++) {
2167                             newSelectedEl = allRows[trIndex+1].cells[i];
2168                             oSelf.selectCell(newSelectedEl);
2169                         }
2170                         oSelf._sLastSelectedId = allRows[trIndex+1].cells[tdIndex].id;
2171                     }
2172                 }
2173                 // Unselecting towards anchor cell
2174                 else {
2175                     // Unselect the horiz block on this row towards the next row
2176                     for(i=startIndex; i<=endIndex; i++) {
2177                         oSelf.unselectCell(allRows[trIndex].cells[i]);
2178                     }
2179                     oSelf._sLastSelectedId = allRows[trIndex+1].cells[tdIndex].id;
2180                 }
2181             }
2182             // Arrow up
2183             else if(nKey == 38) {
2184                 // Is the anchor cell above, below, or same row
2185                 if(anchorEl.parentNode.sectionRowIndex > trIndex) {
2186                     anchorPos = 1;
2187                 }
2188                 else if(anchorEl.parentNode.sectionRowIndex < trIndex) {
2189                     anchorPos = -1;
2190                 }
2191                 else {
2192                     anchorPos = 0;
2193                 }
2195                 // Is the anchor cell left or right?
2196                 startIndex = Math.min(anchorEl.yuiCellIndex, tdIndex);
2197                 endIndex = Math.max(anchorEl.yuiCellIndex, tdIndex);
2199                 // Selecting away from anchor cell
2200                 if(anchorPos >= 0) {
2201                     // Select the horiz block on the previous row
2202                     if(trIndex > 0) {
2203                         for(i=startIndex; i<=endIndex; i++) {
2204                             newSelectedEl = allRows[trIndex-1].cells[i];
2205                             oSelf.selectCell(newSelectedEl);
2206                         }
2207                         oSelf._sLastSelectedId = allRows[trIndex-1].cells[tdIndex].id;
2208                     }
2209                 }
2210                 // Unselecting towards anchor cell
2211                 else {
2212                     // Unselect the horiz block on this row towards the previous row
2213                     for(i=startIndex; i<=endIndex; i++) {
2214                         oSelf.unselectCell(allRows[trIndex].cells[i]);
2215                     }
2216                     oSelf._sLastSelectedId = allRows[trIndex-1].cells[tdIndex].id;
2217                 }
2218             }
2219             // Arrow right
2220             else if(nKey == 39) {
2221                 // Is the anchor cell left, right, or same column
2222                 if(anchorEl.yuiCellIndex > tdIndex) {
2223                     anchorPos = 1;
2224                 }
2225                 else if(anchorEl.yuiCellIndex < tdIndex) {
2226                     anchorPos = -1;
2227                 }
2228                 else {
2229                     anchorPos = 0;
2230                 }
2232                 // Selecting away from anchor cell
2233                 if(anchorPos <= 0) {
2234                     //Select the next vert block to the right
2235                     if(tdIndex < allRows[trIndex].cells.length-1) {
2236                         startIndex = Math.min(anchorEl.parentNode.sectionRowIndex, trIndex);
2237                         endIndex = Math.max(anchorEl.parentNode.sectionRowIndex, trIndex);
2238                         for(i=startIndex; i<=endIndex; i++) {
2239                             newSelectedEl = allRows[i].cells[tdIndex+1];
2240                             oSelf.selectCell(newSelectedEl);
2241                         }
2242                         oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex+1].id;
2243                     }
2244                 }
2245                 // Unselecting towards anchor cell
2246                 else {
2247                     // Unselect the vert block on this column towards the right
2248                     startIndex = Math.min(anchorEl.parentNode.sectionRowIndex, trIndex);
2249                     endIndex = Math.max(anchorEl.parentNode.sectionRowIndex, trIndex);
2250                     for(i=startIndex; i<=endIndex; i++) {
2251                         oSelf.unselectCell(allRows[i].cells[tdIndex]);
2252                     }
2253                     oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex+1].id;
2254                 }
2255             }
2256             // Arrow left
2257             else if(nKey == 37) {
2258                 // Is the anchor cell left, right, or same column
2259                 if(anchorEl.yuiCellIndex > tdIndex) {
2260                     anchorPos = 1;
2261                 }
2262                 else if(anchorEl.yuiCellIndex < tdIndex) {
2263                     anchorPos = -1;
2264                 }
2265                 else {
2266                     anchorPos = 0;
2267                 }
2269                 // Selecting away from anchor cell
2270                 if(anchorPos >= 0) {
2271                     //Select the previous vert block to the left
2272                     if(tdIndex > 0) {
2273                         startIndex = Math.min(anchorEl.parentNode.sectionRowIndex, trIndex);
2274                         endIndex = Math.max(anchorEl.parentNode.sectionRowIndex, trIndex);
2275                         for(i=startIndex; i<=endIndex; i++) {
2276                             newSelectedEl = allRows[i].cells[tdIndex-1];
2277                             oSelf.selectCell(newSelectedEl);
2278                         }
2279                         oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex-1].id;
2280                     }
2281                 }
2282                 // Unselecting towards anchor cell
2283                 else {
2284                     // Unselect the vert block on this column towards the left
2285                     startIndex = Math.min(anchorEl.parentNode.sectionRowIndex, trIndex);
2286                     endIndex = Math.max(anchorEl.parentNode.sectionRowIndex, trIndex);
2287                     for(i=startIndex; i<=endIndex; i++) {
2288                         oSelf.unselectCell(allRows[i].cells[tdIndex]);
2289                     }
2290                     oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex-1].id;
2291                 }
2292             }
2293         }
2294         ////////////////////////////////////////////////////////////////////////
2295         //
2296         // SHIFT cell range selection
2297         //
2298         ////////////////////////////////////////////////////////////////////////
2299         else if(bSHIFT && (sMode == "cellrange")) {
2300             trIndex = lastSelectedEl.parentNode.sectionRowIndex;
2301             tdIndex = lastSelectedEl.yuiCellIndex;
2303             // Is the anchor cell above, below, or same row
2304             if(anchorEl.parentNode.sectionRowIndex > trIndex) {
2305                 anchorPos = 1;
2306             }
2307             else if(anchorEl.parentNode.sectionRowIndex < trIndex) {
2308                 anchorPos = -1;
2309             }
2310             else {
2311                 anchorPos = 0;
2312             }
2314             // Arrow down
2315             if(nKey == 40) {
2316                 // Selecting away from anchor cell
2317                 if(anchorPos <= 0) {
2318                     // Select all cells to the end of this row
2319                     for(i=tdIndex+1; i<allRows[trIndex].cells.length; i++){
2320                         newSelectedEl = allRows[trIndex].cells[i];
2321                         oSelf.selectCell(newSelectedEl);
2322                     }
2324                     // Select some of the cells on the next row down
2325                     if(trIndex < allRows.length-1) {
2326                         for(i=0; i<=tdIndex; i++){
2327                             newSelectedEl = allRows[trIndex+1].cells[i];
2328                             oSelf.selectCell(newSelectedEl);
2329                         }
2330                     }
2331                 }
2332                 // Unselecting towards anchor cell
2333                 else {
2334                     // Unselect all cells to the end of this row
2335                     for(i=tdIndex; i<allRows[trIndex].cells.length; i++){
2336                         oSelf.unselectCell(allRows[trIndex].cells[i]);
2337                     }
2339                     // Unselect some of the cells on the next row down
2340                     for(i=0; i<tdIndex; i++){
2341                         oSelf.unselectCell(allRows[trIndex+1].cells[i]);
2342                     }
2343                     oSelf._sLastSelectedId = allRows[trIndex+1].cells[tdIndex].id;
2344                 }
2345             }
2346             // Arrow up
2347             else if(nKey == 38) {
2348                 // Selecting away from anchor cell
2349                 if(anchorPos >= 0) {
2350                     // Select all the cells to the beginning of this row
2351                     for(i=tdIndex-1; i>-1; i--){
2352                         newSelectedEl = allRows[trIndex].cells[i];
2353                         oSelf.selectCell(newSelectedEl);
2354                     }
2356                     // Select some of the cells from the end of the previous row
2357                     if(trIndex > 0) {
2358                         for(i=allRows[trIndex].cells.length-1; i>=tdIndex; i--){
2359                             newSelectedEl = allRows[trIndex-1].cells[i];
2360                             oSelf.selectCell(newSelectedEl);
2361                         }
2362                     }
2363                 }
2364                 // Unselecting towards anchor cell
2365                 else {
2366                     // Unselect all the cells to the beginning of this row
2367                     for(i=tdIndex; i>-1; i--){
2368                         oSelf.unselectCell(allRows[trIndex].cells[i]);
2369                     }
2371                     // Unselect some of the cells from the end of the previous row
2372                     for(i=allRows[trIndex].cells.length-1; i>tdIndex; i--){
2373                         oSelf.unselectCell(allRows[trIndex-1].cells[i]);
2374                     }
2375                     oSelf._sLastSelectedId = allRows[trIndex-1].cells[tdIndex].id;
2376                 }
2377             }
2378             // Arrow right
2379             else if(nKey == 39) {
2380                 // Selecting away from anchor cell
2381                 if(anchorPos < 0) {
2382                     // Select the next cell to the right
2383                     if(tdIndex < allRows[trIndex].cells.length-1) {
2384                         newSelectedEl = allRows[trIndex].cells[tdIndex+1];
2385                         oSelf.selectCell(newSelectedEl);
2386                     }
2387                     // Select the first cell of the next row
2388                     else if(trIndex < allRows.length-1) {
2389                         newSelectedEl = allRows[trIndex+1].cells[0];
2390                         oSelf.selectCell(newSelectedEl);
2391                     }
2392                 }
2393                 // Unselecting towards anchor cell
2394                 else if(anchorPos > 0) {
2395                     oSelf.unselectCell(allRows[trIndex].cells[tdIndex]);
2397                     // Unselect this cell towards the right
2398                     if(tdIndex < allRows[trIndex].cells.length-1) {
2399                         oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex+1].id;
2400                     }
2401                     // Unselect this cells towards the first cell of the next row
2402                     else {
2403                         oSelf._sLastSelectedId = allRows[trIndex+1].cells[0].id;
2404                     }
2405                 }
2406                 // Anchor is on this row
2407                 else {
2408                     // Selecting away from anchor
2409                     if(anchorEl.yuiCellIndex <= tdIndex) {
2410                         // Select the next cell to the right
2411                         if(tdIndex < allRows[trIndex].cells.length-1) {
2412                             newSelectedEl = allRows[trIndex].cells[tdIndex+1];
2413                             oSelf.selectCell(newSelectedEl);
2414                         }
2415                         // Select the first cell on the next row
2416                         else if(trIndex < allRows.length-1){
2417                             newSelectedEl = allRows[trIndex+1].cells[0];
2418                             oSelf.selectCell(newSelectedEl);
2419                         }
2420                     }
2421                     // Unselecting towards anchor
2422                     else {
2423                         // Unselect this cell towards the right
2424                         oSelf.unselectCell(allRows[trIndex].cells[tdIndex]);
2425                         oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex+1].id;
2426                     }
2427                 }
2428             }
2429             // Arrow left
2430             else if(nKey == 37) {
2431                 // Unselecting towards the anchor
2432                 if(anchorPos < 0) {
2433                     oSelf.unselectCell(allRows[trIndex].cells[tdIndex]);
2435                     // Unselect this cell towards the left
2436                     if(tdIndex > 0) {
2437                         oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex-1].id;
2438                     }
2439                     // Unselect this cell towards the last cell of the previous row
2440                     else {
2441                         oSelf._sLastSelectedId = allRows[trIndex-1].cells[allRows[trIndex-1].cells.length-1].id;
2442                     }
2443                 }
2444                 // Selecting towards the anchor
2445                 else if(anchorPos > 0) {
2446                     // Select the next cell to the left
2447                     if(tdIndex > 0) {
2448                         newSelectedEl = allRows[trIndex].cells[tdIndex-1];
2449                         oSelf.selectCell(newSelectedEl);
2450                     }
2451                     // Select the last cell of the previous row
2452                     else if(trIndex > 0){
2453                         newSelectedEl = allRows[trIndex-1].cells[allRows[trIndex-1].cells.length-1];
2454                         oSelf.selectCell(newSelectedEl);
2455                     }
2456                 }
2457                 // Anchor is on this row
2458                 else {
2459                     // Selecting away from anchor cell
2460                     if(anchorEl.yuiCellIndex >= tdIndex) {
2461                         // Select the next cell to the left
2462                         if(tdIndex > 0) {
2463                             newSelectedEl = allRows[trIndex].cells[tdIndex-1];
2464                             oSelf.selectCell(newSelectedEl);
2465                         }
2466                         // Select the last cell of the previous row
2467                         else if(trIndex > 0){
2468                             newSelectedEl = allRows[trIndex-1].cells[allRows[trIndex-1].cells.length-1];
2469                             oSelf.selectCell(newSelectedEl);
2470                         }
2471                     }
2472                     // Unselecting towards anchor cell
2473                     else {
2474                         oSelf.unselectCell(allRows[trIndex].cells[tdIndex]);
2476                         // Unselect this cell towards the left
2477                         if(tdIndex > 0) {
2478                             oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex-1].id;
2479                         }
2480                         // Unselect this cell towards the last cell of the previous row
2481                         else {
2482                             oSelf._sLastSelectedId = allRows[trIndex-1].cells[allRows[trIndex-1].cells.length-1].id;
2483                         }
2484                     }
2485                 }
2486             }
2487         }
2488         ////////////////////////////////////////////////////////////////////////
2489         //
2490         // Simple single cell selection
2491         //
2492         ////////////////////////////////////////////////////////////////////////
2493         else if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
2494             trIndex = lastSelectedEl.parentNode.sectionRowIndex;
2495             tdIndex = lastSelectedEl.yuiCellIndex;
2497             // Arrow down
2498             if(nKey == 40) {
2499                 oSelf.unselectAllCells();
2501                 // Select the next cell down
2502                 if(trIndex < allRows.length-1) {
2503                     newSelectedEl = allRows[trIndex+1].cells[tdIndex];
2504                     oSelf.selectCell(newSelectedEl);
2505                 }
2506                 // Select only the bottom cell
2507                 else {
2508                     newSelectedEl = lastSelectedEl;
2509                     oSelf.selectCell(newSelectedEl);
2510                 }
2512                 oSelf._sSelectionAnchorId = newSelectedEl.id;
2513             }
2514             // Arrow up
2515             else if(nKey == 38) {
2516                 oSelf.unselectAllCells();
2518                 // Select the next cell up
2519                 if(trIndex > 0) {
2520                     newSelectedEl = allRows[trIndex-1].cells[tdIndex];
2521                     oSelf.selectCell(newSelectedEl);
2522                 }
2523                 // Select only the top cell
2524                 else {
2525                     newSelectedEl = lastSelectedEl;
2526                     oSelf.selectCell(newSelectedEl);
2527                 }
2529                 oSelf._sSelectionAnchorId = newSelectedEl.id;
2530             }
2531             // Arrow right
2532             else if(nKey == 39) {
2533                 oSelf.unselectAllCells();
2535                 // Select the next cell to the right
2536                 if(tdIndex < lastSelectedEl.parentNode.cells.length-1) {
2537                     newSelectedEl = lastSelectedEl.parentNode.cells[tdIndex+1];
2538                     oSelf.selectCell(newSelectedEl);
2539                 }
2540                 // Select only the right cell
2541                 else {
2542                     newSelectedEl = lastSelectedEl;
2543                     oSelf.selectCell(newSelectedEl);
2544                 }
2546                 oSelf._sSelectionAnchorId = newSelectedEl.id;
2547             }
2548             // Arrow left
2549             else if(nKey == 37) {
2550                 oSelf.unselectAllCells();
2552                 // Select the next cell to the left
2553                 if(tdIndex > 0) {
2554                     newSelectedEl = lastSelectedEl.parentNode.cells[tdIndex-1];
2555                     oSelf.selectCell(newSelectedEl);
2556                 }
2557                 // Select only the left cell
2558                 else {
2559                     newSelectedEl = lastSelectedEl;
2560                     oSelf.selectCell(newSelectedEl);
2561                 }
2563                 oSelf._sSelectionAnchorId = newSelectedEl.id;
2564             }
2565         }
2566         ////////////////////////////////////////////////////////////////////////
2567         //
2568         // SHIFT row selection
2569         //
2570         ////////////////////////////////////////////////////////////////////////
2571         else if(bSHIFT && (sMode != "single")) {
2572             trIndex = lastSelectedEl.sectionRowIndex;
2574             if(anchorEl.sectionRowIndex > trIndex) {
2575                 anchorPos = 1;
2576             }
2577             else if(anchorEl.sectionRowIndex < trIndex) {
2578                 anchorPos = -1;
2579             }
2580             else {
2581                 anchorPos = 0;
2582             }
2584             // Arrow down
2585             if(nKey == 40) {
2586                 // Selecting away from anchor row
2587                 if(anchorPos <= 0) {
2588                     // Select the next row down
2589                     if(trIndex < allRows.length-1) {
2590                         oSelf.selectRow(trIndex+1);
2591                     }
2592                 }
2593                 // Unselecting toward anchor row
2594                 else {
2595                     // Unselect this row towards the anchor row down
2596                     oSelf.unselectRow(lastSelectedEl);
2597                     oSelf._sLastSelectedId = allRows[trIndex+1].id;
2598                 }
2600             }
2601             // Arrow up
2602             else if(nKey == 38) {
2603                 // Selecting away from anchor row
2604                 if(anchorPos >= 0) {
2605                     // Select the next row up
2606                     if(trIndex > 0) {
2607                         oSelf.selectRow(trIndex-1);
2608                     }
2609                 }
2610                 // Unselect this row towards the anchor row up
2611                 else {
2612                     oSelf.unselectRow(lastSelectedEl);
2613                     oSelf._sLastSelectedId = allRows[trIndex-1].id;
2614                 }
2615             }
2616             // Arrow right
2617             else if(nKey == 39) {
2618                 // Do nothing
2619             }
2620             // Arrow left
2621             else if(nKey == 37) {
2622                 // Do nothing
2623             }
2624         }
2625         ////////////////////////////////////////////////////////////////////////
2626         //
2627         // Simple single row selection
2628         //
2629         ////////////////////////////////////////////////////////////////////////
2630         else {
2631             trIndex = lastSelectedEl.sectionRowIndex;
2633             // Arrow down
2634             if(nKey == 40) {
2635                 oSelf.unselectAllRows();
2637                 // Select the next row
2638                 if(trIndex < allRows.length-1) {
2639                     newSelectedEl = allRows[trIndex+1];
2640                     oSelf.selectRow(newSelectedEl);
2641                 }
2642                 // Select only the last row
2643                 else {
2644                     newSelectedEl = lastSelectedEl;
2645                     oSelf.selectRow(lastSelectedEl);
2646                 }
2648                 oSelf._sSelectionAnchorId = newSelectedEl.id;
2649             }
2650             // Arrow up
2651             else if(nKey == 38) {
2652                 oSelf.unselectAllRows();
2654                 // Select the previous row
2655                 if(trIndex > 0) {
2656                     newSelectedEl = allRows[trIndex-1];
2657                     oSelf.selectRow(newSelectedEl);
2658                 }
2659                 // Select only the first row
2660                 else {
2661                     newSelectedEl = lastSelectedEl;
2662                     oSelf.selectRow(newSelectedEl);
2663                 }
2665                 oSelf._sSelectionAnchorId = newSelectedEl.id;
2666             }
2667             // Arrow right
2668             else if(nKey == 39) {
2669                 // Do nothing
2670             }
2671             // Arrow left
2672             else if(nKey == 37) {
2673                 // Do nothing
2674             }
2675         }
2676     }
2680  * Handles keypress events on the TABLE. Mainly to support stopEvent on Mac.
2682  * @method _onTableKeypress
2683  * @param e {HTMLEvent} The key event.
2684  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2685  * @private
2686  */
2687 YAHOO.widget.DataTable.prototype._onTableKeypress = function(e, oSelf) {
2688     var isMac = (navigator.userAgent.toLowerCase().indexOf("mac") != -1);
2689     if(isMac) {
2690         var nKey = YAHOO.util.Event.getCharCode(e);
2691         // arrow down
2692         if(nKey == 40) {
2693             YAHOO.util.Event.stopEvent(e);
2694         }
2695         // arrow up
2696         else if(nKey == 38) {
2697             YAHOO.util.Event.stopEvent(e);
2698         }
2699     }
2703  * Handles click events on the THEAD element.
2705  * @method _onTheadClick
2706  * @param e {HTMLEvent} The click event.
2707  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2708  * @private
2709  */
2710 YAHOO.widget.DataTable.prototype._onTheadClick = function(e, oSelf) {
2711     var elTarget = YAHOO.util.Event.getTarget(e);
2712     var elTag = elTarget.tagName.toLowerCase();
2714     if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
2715         oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
2716     }
2718     while(elTarget && (elTag != "thead")) {
2719             switch(elTag) {
2720                 case "body":
2721                     break;
2722                 case "span":
2723                     if(YAHOO.util.Dom.hasClass(elTarget, YAHOO.widget.DataTable.CLASS_LABEL)) {
2724                         oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
2725                     }
2726                     break;
2727                 case "th":
2728                     oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
2729                     break;
2730                 case "tr":
2731                     oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
2732                     break;
2733                 default:
2734                     break;
2735             }
2736             elTarget = elTarget.parentNode;
2737             if(elTarget) {
2738                 elTag = elTarget.tagName.toLowerCase();
2739             }
2740     }
2741     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elTable),event:e});
2745  * Handles click events on the primary TBODY element.
2747  * @method _onTbodyClick
2748  * @param e {HTMLEvent} The click event.
2749  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2750  * @private
2751  */
2752 YAHOO.widget.DataTable.prototype._onTbodyClick = function(e, oSelf) {
2753     var elTarget = YAHOO.util.Event.getTarget(e);
2754     var elTag = elTarget.tagName.toLowerCase();
2756     if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
2757         oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
2758     }
2760     while(elTarget && (elTag != "table")) {
2761         switch(elTag) {
2762             case "body":
2763                 break;
2764             case "input":
2765                 if(elTarget.type.toLowerCase() == "checkbox") {
2766                     oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
2767                 }
2768                 else if(elTarget.type.toLowerCase() == "radio") {
2769                     oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
2770                 }
2771                 break;
2772             case "a":
2773                 oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
2774                 break;
2775             case "button":
2776                 oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
2777                 break;
2778             case "td":
2779                 oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
2780                 break;
2781             case "tr":
2782                 oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
2783                 break;
2784             default:
2785                 break;
2786         }
2787         elTarget = elTarget.parentNode;
2788         if(elTarget) {
2789             elTag = elTarget.tagName.toLowerCase();
2790         }
2791     }
2792     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elTable),event:e});
2795 /*TODO: delete
2796  * Handles keyup events on the TBODY. Executes deletion.
2798  * @method _onTbodyKeyup
2799  * @param e {HTMLEvent} The key event.
2800  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2801  * @private
2802  */
2803 /*YAHOO.widget.DataTable.prototype._onTbodyKeyup = function(e, oSelf) {
2804    var nKey = YAHOO.util.Event.getCharCode(e);
2805     // delete
2806     if(nKey == 46) {//TODO: if something is selected
2807         //TODO: delete row
2808     }
2809 };*/
2812  * Handles click events on paginator links.
2814  * @method _onPaginatorLinkClick
2815  * @param e {HTMLEvent} The click event.
2816  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2817  * @private
2818  */
2819 YAHOO.widget.DataTable.prototype._onPaginatorLinkClick = function(e, oSelf) {
2820     var elTarget = YAHOO.util.Event.getTarget(e);
2821     var elTag = elTarget.tagName.toLowerCase();
2823     if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
2824         oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
2825     }
2827     while(elTarget && (elTag != "table")) {
2828         switch(elTag) {
2829             case "body":
2830                 return;
2831             case "a":
2832                 YAHOO.util.Event.stopEvent(e);
2833                 //TODO: after the showPage call, figure out which link
2834                 //TODO: was clicked and reset focus to the new version of it
2835                 switch(elTarget.className) {
2836                     case YAHOO.widget.DataTable.CLASS_PAGE:
2837                         oSelf.showPage(parseInt(elTarget.innerHTML,10));
2838                         return;
2839                     case YAHOO.widget.DataTable.CLASS_FIRST:
2840                         oSelf.showPage(1);
2841                         return;
2842                     case YAHOO.widget.DataTable.CLASS_LAST:
2843                         oSelf.showPage(oSelf.get("paginator").totalPages);
2844                         return;
2845                     case YAHOO.widget.DataTable.CLASS_PREVIOUS:
2846                         oSelf.showPage(oSelf.get("paginator").currentPage - 1);
2847                         return;
2848                     case YAHOO.widget.DataTable.CLASS_NEXT:
2849                         oSelf.showPage(oSelf.get("paginator").currentPage + 1);
2850                         return;
2851                 }
2852                 break;
2853             default:
2854                 return;
2855         }
2856         elTarget = elTarget.parentNode;
2857         if(elTarget) {
2858             elTag = elTarget.tagName.toLowerCase();
2859         }
2860         else {
2861             return;
2862         }
2863     }
2867  * Handles change events on paginator SELECT element.
2869  * @method _onPaginatorDropdownChange
2870  * @param e {HTMLEvent} The change event.
2871  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2872  * @private
2873  */
2874 YAHOO.widget.DataTable.prototype._onPaginatorDropdownChange = function(e, oSelf) {
2875     var elTarget = YAHOO.util.Event.getTarget(e);
2876     var newValue = elTarget[elTarget.selectedIndex].value;
2878     var newRowsPerPage = YAHOO.lang.isValue(parseInt(newValue,10)) ? parseInt(newValue,10) : null;
2879     if(newRowsPerPage !== null) {
2880         var newStartRecordIndex = (oSelf.get("paginator").currentPage-1) * newRowsPerPage;
2881         oSelf.updatePaginator({rowsPerPage:newRowsPerPage, startRecordIndex:newStartRecordIndex});
2882         oSelf.refreshView();
2883     }
2884     else {
2885     }
2889  * Handles change events on SELECT elements within DataTable.
2891  * @method _onDropdownChange
2892  * @param e {HTMLEvent} The change event.
2893  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2894  * @private
2895  */
2896 YAHOO.widget.DataTable.prototype._onDropdownChange = function(e, oSelf) {
2897     var elTarget = YAHOO.util.Event.getTarget(e);
2898     //TODO: pass what args?
2899     //var value = elTarget[elTarget.selectedIndex].value;
2900     oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
2941 /////////////////////////////////////////////////////////////////////////////
2943 // Public member variables
2945 /////////////////////////////////////////////////////////////////////////////
2948 /////////////////////////////////////////////////////////////////////////////
2950 // Public methods
2952 /////////////////////////////////////////////////////////////////////////////
2954 // OBJECT ACCESSORS
2957  * Public accessor to the unique name of the DataSource instance.
2959  * @method toString
2960  * @return {String} Unique name of the DataSource instance.
2961  */
2963 YAHOO.widget.DataTable.prototype.toString = function() {
2964     return "DataTable " + this._sName;
2968  * Returns the DataTable instance's DataSource instance.
2970  * @method getDataSource
2971  * @return {YAHOO.util.DataSource} DataSource instance.
2972  */
2973 YAHOO.widget.DataTable.prototype.getDataSource = function() {
2974     return this._oDataSource;
2978  * Returns the DataTable instance's ColumnSet instance.
2980  * @method getColumnSet
2981  * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
2982  */
2983 YAHOO.widget.DataTable.prototype.getColumnSet = function() {
2984     return this._oColumnSet;
2988  * Returns the DataTable instance's RecordSet instance.
2990  * @method getRecordSet
2991  * @return {YAHOO.widget.RecordSet} RecordSet instance.
2992  */
2993 YAHOO.widget.DataTable.prototype.getRecordSet = function() {
2994     return this._oRecordSet;
2998  * Returns the DataTable instance's Cell Editor as an object literal with the
2999  * following properties:
3000  * <dl>
3001  * <dt>cell</dt>
3002  * <dd>Cell element being edited</dd>
3004  * <dt>column</dt>
3005  * <dd>Associated Column instance</dd>
3007  * <dt>container</dt>
3008  * <dd>Reference to editor's container DIV element</dd>
3010  * <dt>isActive</dt>
3011  * <dd>True if cell is currently being edited</dd>
3013  * <dt>record</dt>
3014  * <dd>Associated Record instance</dd>
3016  * <dt>validator</dt>
3017  * <dd>Associated validator function</dd>
3019  * <dt>value</dt>
3020  * <dd>Current input value</dd>
3021  * </dl>
3028  * @method getCellEditor
3029  * @return {Object} Cell Editor object literal values.
3030  */
3031 YAHOO.widget.DataTable.prototype.getCellEditor = function() {
3032     return this._oCellEditor;
3077 // DOM ACCESSORS
3080  * Returns DOM reference to the DataTable's TABLE element.
3082  * @method getTableEl
3083  * @return {HTMLElement} Reference to TABLE element.
3084  */
3085 YAHOO.widget.DataTable.prototype.getTableEl = function() {
3086     return this._elTable;
3090  * Returns DOM reference to the DataTable's THEAD element.
3092  * @method getTheadEl
3093  * @return {HTMLElement} Reference to THEAD element.
3094  */
3095 YAHOO.widget.DataTable.prototype.getTheadEl = function() {
3096     return this._elThead;
3100  * Returns DOM reference to the DataTable's primary TBODY element.
3102  * @method getTbodyEl
3103  * @return {HTMLElement} Reference to TBODY element.
3104  */
3105 YAHOO.widget.DataTable.prototype.getTbodyEl = function() {
3106     return this._elTbody;
3108 // Backward compatibility
3109 YAHOO.widget.DataTable.prototype.getBody = function() {
3110     return this.getTbodyEl();
3114  * Returns DOM reference to the DataTable's secondary TBODY element that is
3115  * used to display messages.
3117  * @method getMsgTbodyEl
3118  * @return {HTMLElement} Reference to TBODY element.
3119  */
3120 YAHOO.widget.DataTable.prototype.getMsgTbodyEl = function() {
3121     return this._elMsgTbody;
3125  * Returns DOM reference to the TD element within the secondary TBODY that is
3126  * used to display messages.
3128  * @method getMsgTdEl
3129  * @return {HTMLElement} Reference to TD element.
3130  */
3131 YAHOO.widget.DataTable.prototype.getMsgTdEl = function() {
3132     return this._elMsgTd;
3136  * Returns the corresponding TR reference for a given DOM element, ID string or
3137  * directly page row index. If the given identifier is a child of a TR element,
3138  * then DOM tree is traversed until a parent TR element is returned, otherwise
3139  * null.
3141  * @method getTrEl
3142  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
3143  * get: by element reference, ID string, page row index, or Record.
3144  * @return {HTMLElement} Reference to TR element, or null.
3145  */
3146 YAHOO.widget.DataTable.prototype.getTrEl = function(row) {
3147     var allRows = this._elTbody.rows;
3148     
3149     // By Record
3150     if(row instanceof YAHOO.widget.Record) {
3151         var nTrIndex = this.getTrIndex(row);
3152         return allRows[nTrIndex];
3153     }
3154     // By page row index
3155     else if(YAHOO.lang.isNumber(row) && (row > -1) && (row < allRows.length)) {
3156         return allRows[row];
3157     }
3158     // By ID string or element reference
3159     else {
3160         var elRow;
3161         var el = YAHOO.util.Dom.get(row);
3162         
3163         // Validate HTML element
3164         if(el && (el.ownerDocument == document)) {
3165             // Validate TR element
3166             if(el.tagName.toLowerCase() != "tr") {
3167                 // Traverse up the DOM to find the corresponding TR element
3168                 elRow = YAHOO.util.Dom.getAncestorByTagName(el,"tr");
3169             }
3170             else {
3171                 elRow = el;
3172             }
3174             // Make sure the TR is in this TBODY
3175             if(elRow && (elRow.parentNode == this._elTbody)) {
3176                 // Now we can return the TR element
3177                 return elRow;
3178             }
3179         }
3180     }
3181     
3182     return null;
3184 // Backward compatibility
3185 YAHOO.widget.DataTable.prototype.getRow = function(index) {
3186     return this.getTrEl(index);
3190  * Returns DOM reference to the first TR element in the DataTable page, or null.
3192  * @method getFirstTrEl
3193  * @return {HTMLElement} Reference to TR element.
3194  */
3195 YAHOO.widget.DataTable.prototype.getFirstTrEl = function() {
3196     return this._elTbody.rows[0] || null;
3200  * Returns DOM reference to the last TR element in the DataTable page, or null.
3202  * @method getLastTrEl
3203  * @return {HTMLElement} Reference to last TR element.
3204  */
3205 YAHOO.widget.DataTable.prototype.getLastTrEl = function() {
3206     var allRows = this._elTbody.rows;
3207         if(allRows.length > 0) {
3208             return allRows[allRows.length-1] || null;
3209         }
3213  * Returns DOM reference to the given TD element.
3215  * @method getTdEl
3216  * @param cell {HTMLElement | String} DOM element reference or string ID.
3217  * @return {HTMLElement} Reference to TD element.
3218  */
3219 YAHOO.widget.DataTable.prototype.getTdEl = function(cell) {
3220     var elCell;
3221     var el = YAHOO.util.Dom.get(cell);
3223     // Validate HTML element
3224     if(el && (el.ownerDocument == document)) {
3225         // Validate TD element
3226         if(el.tagName.toLowerCase() != "td") {
3227             // Traverse up the DOM to find the corresponding TR element
3228             elCell = YAHOO.util.Dom.getAncestorByTagName(el, "td");
3229         }
3230         else {
3231             elCell = el;
3232         }
3234         // Make sure the TD is in this TBODY
3235         if(elCell && (elCell.parentNode.parentNode == this._elTbody)) {
3236             // Now we can return the TD element
3237             return elCell;
3238         }
3239     }
3240     
3241     return null;
3245  * Returns DOM reference to the TH element at given DataTable page coordinates, or null.
3247  * @method getThEl
3248  * @param header {HTMLElement | String | YAHOO.widget.Column} DOM element
3249  * reference or string ID, or Column instance.
3250  * @return {HTMLElement} Reference to TH element.
3251  */
3252 YAHOO.widget.DataTable.prototype.getThEl = function(header) {
3253     var elHeader;
3254         
3255     // Validate Column instance
3256     if(header instanceof YAHOO.widget.Column) {
3257         var oColumn = header;
3258         elHeader = YAHOO.util.Dom.get(this.id + "-col" + oColumn.getId());
3259         if(elHeader) {
3260             return elHeader;
3261         }
3262     }
3263     // Validate HTML element
3264     else {
3265         var el = YAHOO.util.Dom.get(header);
3267         if(el && (el.ownerDocument == document)) {
3268             // Validate TH element
3269             if(el.tagName.toLowerCase() != "th") {
3270                 // Traverse up the DOM to find the corresponding TR element
3271                 elHeader = YAHOO.util.Dom.getAncestorByTagName(el,"th");
3272             }
3273             else {
3274                 elHeader = el;
3275             }
3277             // Make sure the TH is in this THEAD
3278             if(elHeader && (elHeader.parentNode.parentNode == this._elThead)) {
3279                 // Now we can return the TD element
3280                 return elHeader;
3281             }
3282         }
3283     }
3285     return null;
3289  * Returns the page row index of given row. Returns null if the row is not in
3290  * view on the current DataTable page.
3292  * @method getTrIndex
3293  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
3294  * string reference to an element within the DataTable page, a Record instance,
3295  * or a Record's RecordSet index.
3296  * @return {Number} Page row index, or null if row does not exist or is not in view.
3297  */
3298 YAHOO.widget.DataTable.prototype.getTrIndex = function(row) {
3299     var nRecordIndex;
3300     
3301     // By Record
3302     if(row instanceof YAHOO.widget.Record) {
3303         nRecordIndex = this._oRecordSet.getRecordIndex(row);
3304     }
3305     // Calculate page row index from Record index
3306     else if(YAHOO.lang.isNumber(row)) {
3307         nRecordIndex = row;
3308     }
3309     if(YAHOO.lang.isNumber(nRecordIndex)) {
3310         // DataTable is paginated
3311         if(this.get("paginated")) {
3312             // Get the first and last Record on this page
3313             var startRecordIndex = this.get("paginator").startRecordIndex;
3314             var endRecordIndex = startRecordIndex + this.get("paginator").rowsPerPage - 1;
3315             // This Record is in view
3316             if((nRecordIndex >= startRecordIndex) && (nRecordIndex <= endRecordIndex)) {
3317                 return nRecordIndex - startRecordIndex;
3318             }
3319             // This Record is not in view
3320             else {
3321                 return null;
3322             }
3323         }
3324         // Not paginated, just return the Record index
3325         else {
3326             return nRecordIndex;
3327         }
3329     }
3330     // By element reference or ID string
3331     else {
3332         // Validate TR element
3333         elRow = this.getTrEl(row);
3334         if(elRow && (elRow.ownerDocument == document) &&
3335                 (elRow.parentNode == this._elTbody)) {
3336             return elRow.sectionRowIndex;
3337         }
3338     }
3339     
3340     return null;
3388 // TABLE FUNCTIONS
3391  * Resets a RecordSet with the given data and populates the page view
3392  * with the new data. Any previous data and selection states are cleared.
3393  * However, sort states are not cleared, so if the given data is in a particular
3394  * sort order, implementers should take care to reset the sortedBy property. If
3395  * pagination is enabled, the currentPage is shown and Paginator UI updated,
3396  * otherwise all rows are displayed as a single page. For performance, existing
3397  * DOM elements are reused when possible.
3399  * @method initializeTable
3400  * @param oData {Object | Object[]} An object literal of data or an array of
3401  * object literals containing data.
3402  */
3403 YAHOO.widget.DataTable.prototype.initializeTable = function(oData) {
3404     // Clear the RecordSet
3405     this._oRecordSet.reset();
3407     // Add data to RecordSet
3408     var records = this._oRecordSet.addRecords(oData);
3410     // Clear selections
3411     this._unselectAllTrEls();
3412     this._unselectAllTdEls();
3413     this._aSelections = null;
3414     this._sLastSelectedId = null;
3415     this._sSelectionAnchorId = null;
3417     // Refresh the view
3418     this.refreshView();
3419     this.fireEvent("initEvent");
3423  * Refreshes the view with existing Records from the RecordSet while
3424  * maintaining sort, pagination, and selection states. For performance, reuses
3425  * existing DOM elements when possible while deleting extraneous elements.
3427  * @method refreshView
3428  */
3429 YAHOO.widget.DataTable.prototype.refreshView = function() {
3430     var i, j, k, l, aRecords;
3431     var oPaginator = this.updatePaginator();
3433     // Paginator is enabled, show a subset of Records and update Paginator UI
3434     if(this.get("paginated")) {
3435         var rowsPerPage = oPaginator.rowsPerPage;
3436         var startRecordIndex = (oPaginator.currentPage - 1) * rowsPerPage;
3437         aRecords = this._oRecordSet.getRecords(startRecordIndex, rowsPerPage);
3438         this.formatPaginators();
3439     }
3440     // Show all records
3441     else {
3442         aRecords = this._oRecordSet.getRecords();
3443     }
3445     var elTbody = this._elTbody;
3446     var elRows = elTbody.rows;
3448     // Has rows
3449     if(YAHOO.lang.isArray(aRecords) && (aRecords.length > 0)) {
3450         this.hideTableMessage();
3452         // Keep track of selected rows
3453         var aSelectedRows = this.getSelectedRows();
3454         // Keep track of selected cells
3455         var aSelectedCells = this.getSelectedCells();
3456         // Anything to reinstate?
3457         var bReselect = (aSelectedRows.length>0) || (aSelectedCells.length > 0);
3459         // Remove extra rows from the bottom so as to preserve ID order
3460         while(elTbody.hasChildNodes() && (elRows.length > aRecords.length)) {
3461             elTbody.deleteRow(-1);
3462         }
3464         // Unselect all TR and TD elements in the UI
3465         if(bReselect) {
3466             this._unselectAllTrEls();
3467             this._unselectAllTdEls();
3468         }
3470         // From the top, update in-place existing rows
3471         for(i=0; i<elRows.length; i++) {
3472             this._updateTrEl(elRows[i], aRecords[i]);
3473         }
3475         // Add TR elements as necessary
3476         for(i=elRows.length; i<aRecords.length; i++) {
3477             this._addTrEl(aRecords[i]);
3478         }
3480         // Reinstate selected and sorted classes
3481         var allRows = elTbody.rows;
3482         if(bReselect) {
3483             // Loop over each row
3484             for(j=0; j<allRows.length; j++) {
3485                 var thisRow = allRows[j];
3486                 var sMode = this.get("selectionMode");
3487                 if ((sMode == "standard") || (sMode == "single")) {
3488                     // Set SELECTED
3489                     for(k=0; k<aSelectedRows.length; k++) {
3490                         if(aSelectedRows[k] === thisRow.yuiRecordId) {
3491                             YAHOO.util.Dom.addClass(thisRow, YAHOO.widget.DataTable.CLASS_SELECTED);
3492                             if(j === allRows.length-1) {
3493                                 this._sLastSelectedId = thisRow.id;
3494                                 this._sSelectionAnchorId = thisRow.id;
3495                             }
3496                         }
3497                     }
3498                 }
3499                 else {
3500                     // Loop over each cell
3501                     for(k=0; k<thisRow.cells.length; k++) {
3502                         var thisCell = thisRow.cells[k];
3503                         // Set SELECTED
3504                         for(l=0; l<aSelectedCells.length; l++) {
3505                             if((aSelectedCells[l].recordId === thisRow.yuiRecordId) &&
3506                                     (aSelectedCells[l].columnId === thisCell.yuiColumnId)) {
3507                                 YAHOO.util.Dom.addClass(thisCell, YAHOO.widget.DataTable.CLASS_SELECTED);
3508                                 if(k === thisRow.cells.length-1) {
3509                                     this._sLastSelectedId = thisCell.id;
3510                                     this._sSelectionAnchorId = thisCell.id;
3511                                 }
3512                             }
3513                         }
3514                     }
3515                 }
3516             }
3517         }
3518         
3519         // Set FIRST/LAST, EVEN/ODD
3520         this._setFirstRow();
3521         this._setLastRow();
3522         this._setRowStripes();
3524         this.fireEvent("refreshEvent");
3525     }
3526     // Empty
3527     else {
3528         // Remove all rows
3529         while(elTbody.hasChildNodes()) {
3530             elTbody.deleteRow(-1);
3531         }
3533         this.showTableMessage(YAHOO.widget.DataTable.MSG_EMPTY, YAHOO.widget.DataTable.CLASS_EMPTY);
3534     }
3538  * Nulls out the entire DataTable instance and related objects, removes attached
3539  * event listeners, and clears out DOM elements inside the container. After
3540  * calling this method, the instance reference should be expliclitly nulled by
3541  * implementer, as in myDataTable = null. Use with caution!
3543  * @method destroy
3544  */
3545 YAHOO.widget.DataTable.prototype.destroy = function() {
3546     // Destroy Cell Editor
3547     YAHOO.util.Event.purgeElement(this._oCellEditor.container, true);
3548     document.body.removeChild(this._oCellEditor.container);
3549     
3550     var instanceName = this.toString();
3551     var elContainer = this._elContainer;
3553     // Unhook custom events
3554     this._oRecordSet.unsubscribeAll();
3555     this.unsubscribeAll();
3557     // Unhook DOM events
3558     YAHOO.util.Event.purgeElement(elContainer, true);
3560     // Remove DOM elements
3561     elContainer.innerHTML = "";
3563     // Null out objects
3564     for(var param in this) {
3565         if(this.hasOwnProperty(param)) {
3566             this[param] = null;
3567         }
3568     }
3573  * Displays message within secondary TBODY.
3575  * @method showTableMessage
3576  * @param sHTML {String} (optional) Value for innerHTML.
3577  * @param sClassName {String} (optional) Classname.
3578  */
3579 YAHOO.widget.DataTable.prototype.showTableMessage = function(sHTML, sClassName) {
3580     var elCell = this._elMsgTd;
3581     if(YAHOO.lang.isString(sHTML)) {
3582         elCell.innerHTML = sHTML;
3583     }
3584     if(YAHOO.lang.isString(sClassName)) {
3585         YAHOO.util.Dom.addClass(elCell, sClassName);
3586     }
3587     this._elMsgTbody.style.display = "";
3588     this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
3592  * Hides secondary TBODY.
3594  * @method hideTableMessage
3595  */
3596 YAHOO.widget.DataTable.prototype.hideTableMessage = function() {
3597     if(this._elMsgTbody.style.display != "none") {
3598         this._elMsgTbody.style.display = "none";
3599         this.fireEvent("tableMsgHideEvent");
3600     }
3604  * Brings focus to DataTable instance.
3606  * @method focus
3607  */
3608 YAHOO.widget.DataTable.prototype.focus = function() {
3609     this._focusEl(this._elTable);
3677 // RECORDSET FUNCTIONS
3680  * Returns Record index for given TR element or page row index.
3682  * @method getRecordIndex
3683  * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
3684  * element reference or page row index.
3685  * @return {Number} Record's RecordSet index, or null.
3686  */
3687 YAHOO.widget.DataTable.prototype.getRecordIndex = function(row) {
3688     var nTrIndex;
3690     if(!YAHOO.lang.isNumber(row)) {
3691         // By Record
3692         if(row instanceof YAHOO.widget.Record) {
3693             return this._oRecordSet.getRecordIndex(row);
3694         }
3695         // By element reference
3696         else {
3697             // Find the TR element
3698             var el = this.getTrEl(row);
3699             if(el) {
3700                 nTrIndex = el.sectionRowIndex;
3701             }
3702         }
3703     }
3704     // By page row index
3705     else {
3706         nTrIndex = row;
3707     }
3709     if(YAHOO.lang.isNumber(nTrIndex)) {
3710         if(this.get("paginated")) {
3711             return this.get("paginator").startRecordIndex + nTrIndex;
3712         }
3713         else {
3714             return nTrIndex;
3715         }
3716     }
3718     return null;
3722  * For the given identifier, returns the associated Record instance.
3724  * @method getRecord
3725  * @param row {HTMLElement | String | Number} RecordSet position index, DOM
3726  * reference or ID string to an element within the DataTable page.
3727  * @return {YAHOO.widget.Record} Record instance.
3728  */
3729 YAHOO.widget.DataTable.prototype.getRecord = function(row) {
3730     var nRecordIndex = row;
3731     
3732     // By element reference or ID string
3733     if(!YAHOO.lang.isNumber(nRecordIndex)) {
3734         // Validate TR element
3735         var elRow = this.getTrEl(row);
3736         if(elRow) {
3737             nRecordIndex = this.getRecordIndex(row);
3738         }
3739     }
3740     // By Record index
3741     if(YAHOO.lang.isNumber(nRecordIndex)) {
3742         return this._oRecordSet.getRecord(nRecordIndex);
3743     }
3744     
3745     return null;
3793 // COLUMN FUNCTIONS
3796  * For the given identifier, returns the associated Column instance.
3798  * @method getColumn
3799  * @param column {HTMLElement | String | Number} ColumnSet.keys position index, DOM
3800  * reference or ID string to an element within the DataTable page.
3801  * @return {YAHOO.widget.Column} Column instance.
3802  */
3803  YAHOO.widget.DataTable.prototype.getColumn = function(column) {
3804     var nColumnIndex = column;
3806     // By element reference or ID string
3807     if(!YAHOO.lang.isNumber(nColumnIndex)) {
3808         // Validate TD element
3809         var elCell = this.getTdEl(column);
3810         if(elCell) {
3811             nColumnIndex = elCell.yuiColumnId;
3812         }
3813         // Validate TH element
3814         else {
3815             elCell = this.getThEl(column);
3816             if(elCell) {
3817                 nColumnIndex = elCell.yuiColumnId;
3818             }
3819         }
3820     }
3821     
3822     // By Column index
3823     if(YAHOO.lang.isNumber(nColumnIndex)) {
3824         return this._oColumnSet.getColumn(nColumnIndex);
3825     }
3827     return null;
3828  };
3831  * Sorts given Column.
3833  * @method sortColumn
3834  * @param oColumn {YAHOO.widget.Column} Column instance.
3835  */
3836 YAHOO.widget.DataTable.prototype.sortColumn = function(oColumn) {
3837     if(!oColumn) {
3838         return;
3839     }
3840     if(!oColumn instanceof YAHOO.widget.Column) {
3841         //TODO: accept the TH or TH.key
3842         //TODO: Get the column based on TH.yuiColumnId
3843         return;
3844     }
3845     if(oColumn.sortable) {
3846         // What is the default sort direction?
3847         var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultOrder) ? oColumn.sortOptions.defaultOrder : "asc";
3849         // Already sorted?
3850         var oSortedBy = this.get("sortedBy");
3851         if(oSortedBy && (oSortedBy.key === oColumn.key)) {
3852             if(oSortedBy.dir) {
3853                 sortDir = (oSortedBy.dir == "asc") ? "desc" : "asc";
3854             }
3855             else {
3856                 sortDir = (sortDir == "asc") ? "desc" : "asc";
3857             }
3858         }
3860         // Is there a custom sort handler function defined?
3861         var sortFnc = (oColumn.sortOptions && YAHOO.lang.isFunction(oColumn.sortOptions.sortFunction)) ?
3862                 oColumn.sortOptions.sortFunction : function(a, b, desc) {
3863                     var sorted = YAHOO.util.Sort.compare(a.getData(oColumn.key),b.getData(oColumn.key), desc);
3864                     if(sorted === 0) {
3865                         return YAHOO.util.Sort.compare(a.getId(),b.getId(), desc);
3866                     }
3867                     else {
3868                         return sorted;
3869                     }
3870         };
3872         // Do the actual sort
3873         var desc = (sortDir == "desc") ? true : false;
3874         this._oRecordSet.sortRecords(sortFnc, desc);
3876         // Update sortedBy tracker
3877         this.set("sortedBy", {key:oColumn.key, dir:sortDir, column:oColumn});
3878         
3879         // Reset to first page
3880         //TODO: Keep selection in view
3881         this.updatePaginator({currentPage:1});
3883         // Update the UI
3884         this.refreshView();
3886         this.fireEvent("columnSortEvent",{column:oColumn,dir:sortDir});
3887     }
3888     else {
3889         //TODO
3890     }
3937 // ROW FUNCTIONS
3941  * Adds one new Record of data into the RecordSet at the index if given,
3942  * otherwise at the end. If the new Record is in page view, the
3943  * corresponding DOM elements are also updated.
3945  * @method addRow
3946  * @param oData {Object} Object literal of data for the row.
3947  * @param index {Number} (optional) RecordSet position index at which to add data.
3948  */
3949 YAHOO.widget.DataTable.prototype.addRow = function(oData, index) {
3950     if(oData && (oData.constructor == Object)) {
3951         var oRecord = this._oRecordSet.addRecord(oData, index);
3952         if(oRecord) {
3953             var nTrIndex = this.getTrIndex(oRecord);
3955             // Row is in view
3956             if(YAHOO.lang.isNumber(nTrIndex)) {
3957                 // Paginated so just refresh the view to keep pagination state
3958                 if(this.get("paginated")) {
3959                     this.refreshView();
3960                 }
3961                 // Add the TR element
3962                 else {
3963                     var newTrId = this._addTrEl(oRecord, nTrIndex);
3964                     if(newTrId) {
3965                         // Is this an insert or an append?
3966                         var append = (YAHOO.lang.isNumber(nTrIndex) &&
3967                                 (nTrIndex == this._elTbody.rows.length-1)) ? true : false;
3969                         // Stripe the one new row
3970                         if(append) {
3971                             if((this._elTbody.rows.length-1)%2) {
3972                                 YAHOO.util.Dom.addClass(newTrId, YAHOO.widget.DataTable.CLASS_ODD);
3973                             }
3974                             else {
3975                                 YAHOO.util.Dom.addClass(newTrId, YAHOO.widget.DataTable.CLASS_EVEN);
3976                             }
3977                         }
3978                         // Restripe all the rows after the new one
3979                         else {
3980                             this._setRowStripes(nTrIndex);
3981                         }
3983                         // If new row is at the bottom
3984                         if(append) {
3985                             this._setLastRow();
3986                         }
3987                         // If new row is at the top
3988                         else if(YAHOO.lang.isNumber(index) && (nTrIndex === 0)) {
3989                             this._setFirstRow();
3990                         }
3991                     }
3992                 }
3993             }
3994             // Record is not in view so just update pagination UI
3995             else {
3996                 this.updatePaginator();
3997             }
3999             // TODO: what args to pass?
4000             this.fireEvent("rowAddEvent", {record:oRecord});
4002             // For log message
4003             nTrIndex = (YAHOO.lang.isValue(nTrIndex))? nTrIndex : "n/a";
4005             return;
4006         }
4007     }
4011  * Convenience method to add multiple rows.
4013  * @method addRows
4014  * @param aData {Object[]} Array of object literal data for the rows.
4015  * @param index {Number} (optional) RecordSet position index at which to add data.
4016  */
4017 YAHOO.widget.DataTable.prototype.addRows = function(aData, index) {
4018     if(YAHOO.lang.isArray(aData)) {
4019         var i;
4020         if(YAHOO.lang.isNumber(index)) {
4021             for(i=aData.length-1; i>-1; i--) {
4022                 this.addRow(aData[i], index);
4023             }
4024         }
4025         else {
4026             for(i=0; i<aData.length; i++) {
4027                 this.addRow(aData[i]);
4028             }
4029         }
4030     }
4031     else {
4032     }
4036  * For the given row, updates the associated Record with the given data. If the
4037  * row is in view, the corresponding DOM elements are also updated.
4039  * @method updateRow
4040  * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
4041  * Which row to update: By Record instance, by Record's RecordSet
4042  * position index, by HTMLElement reference to the TR element, or by ID string
4043  * of the TR element.
4044  * @param oData {Object} Object literal of data for the row.
4045  */
4046 YAHOO.widget.DataTable.prototype.updateRow = function(row, oData) {
4047     var oldRecord, updatedRecord, elRow;
4049     // Get the Record directly
4050     if((row instanceof YAHOO.widget.Record) || (YAHOO.lang.isNumber(row))) {
4051             // Get the Record directly
4052             oldRecord = this._oRecordSet.getRecord(row);
4053             
4054             // Is this row in view?
4055             elRow = this.getTrEl(oldRecord);
4056     }
4057     // Get the Record by TR element
4058     else {
4059         elRow = this.getTrEl(row);
4060         if(elRow) {
4061             oldRecord = this._oRecordSet.getRecord(this.getRecordIndex(elRow));
4062         }
4063     }
4065     // Update the Record
4066     if(oldRecord) {
4067         // Copy data from the Record for the event that gets fired later
4068         var oRecordData = oldRecord.getData();
4069         var oldData = {};
4070         for(var param in oRecordData) {
4071             oldData[param] = oRecordData[param];
4072         }
4074         updatedRecord = this._oRecordSet.updateRecord(oldRecord, oData);
4075     }
4076     else {
4077         return;
4079     }
4080     
4081     // Update the TR only if row is in view
4082     if(elRow) {
4083         this._updateTrEl(elRow, updatedRecord);
4084     }
4086     this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
4090  * Deletes the given row's Record from the RecordSet. If the row is in view, the
4091  * corresponding DOM elements are also deleted.
4093  * @method deleteRow
4094  * @param row {HTMLElement | String | Number} DOM element reference or ID string
4095  * to DataTable page element or RecordSet index.
4096  */
4097 YAHOO.widget.DataTable.prototype.deleteRow = function(row) {
4098     // Get the Record index...
4099     var nRecordIndex = null;
4100     // ...by Record index
4101     if(YAHOO.lang.isNumber(row)) {
4102         nRecordIndex = row;
4103     }
4104     // ...by element reference
4105     else {
4106         var elRow = YAHOO.util.Dom.get(row);
4107         elRow = this.getTrEl(elRow);
4108         if(elRow) {
4109             nRecordIndex = this.getRecordIndex(elRow);
4110         }
4111     }
4112     if(nRecordIndex !== null) {
4113         var oRecord = this._oRecordSet.getRecord(nRecordIndex);
4114         if(oRecord) {
4115             var nRecordId = oRecord.getId();
4116             
4117             // Remove from selection tracker if there
4118             var tracker = this._aSelections || [];
4119             for(var j=0; j<tracker.length; j++) {
4120                 if((YAHOO.lang.isNumber(tracker[j]) && (tracker[j] === nRecordId)) ||
4121                         ((tracker[j].constructor == Object) && (tracker[j].recordId === nRecordId))) {
4122                     tracker.splice(j,1);
4123                 }
4124             }
4126             // Copy data from the Record for the event that gets fired later
4127             var oRecordData = oRecord.getData();
4128             var oData = {};
4129             for(var param in oRecordData) {
4130                 oData[param] = oRecordData[param];
4131             }
4133             // Delete Record from RecordSet
4134             this._oRecordSet.deleteRecord(nRecordIndex);
4136             // If row is in view, delete the TR element
4137             var nTrIndex = this.getTrIndex(nRecordIndex);
4138             if(YAHOO.lang.isNumber(nTrIndex)) {
4139                 var isLast = (nTrIndex == this.getLastTrEl().sectionRowIndex) ?
4140                         true : false;
4141                 this._deleteTrEl(nTrIndex);
4143                 // Empty body
4144                 if(this._elTbody.rows.length === 0) {
4145                     this.showTableMessage(YAHOO.widget.DataTable.MSG_EMPTY, YAHOO.widget.DataTable.CLASS_EMPTY);
4146                 }
4147                 // Update UI
4148                 else {
4149                     // Set FIRST/LAST
4150                     if(nTrIndex === 0) {
4151                         this._setFirstRow();
4152                     }
4153                     if(isLast) {
4154                         this._setLastRow();
4155                     }
4156                     // Set EVEN/ODD
4157                     if(nTrIndex != this._elTbody.rows.length) {
4158                         this._setRowStripes(nTrIndex);
4159                     }
4160                 }
4161             }
4163             this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,
4164                     oldData:oData, trElIndex:nTrIndex});
4165         }
4166     }
4167     else {
4168     }
4172  * Convenience method to delete multiple rows.
4174  * @method deleteRows
4175  * @param row {HTMLElement | String | Number} DOM element reference or ID string
4176  * to DataTable page element or RecordSet index.
4177  * @param count {Number} (optional) How many rows to delete. A negative value
4178  * will delete towards the beginning.
4179  */
4180 YAHOO.widget.DataTable.prototype.deleteRows = function(row, count) {
4181     // Get the Record index...
4182     var nRecordIndex = null;
4183     // ...by Record index
4184     if(YAHOO.lang.isNumber(row)) {
4185         nRecordIndex = row;
4186     }
4187     // ...by element reference
4188     else {
4189         var elRow = YAHOO.util.Dom.get(row);
4190         elRow = this.getTrEl(elRow);
4191         if(elRow) {
4192             nRecordIndex = this.getRecordIndex(elRow);
4193         }
4194     }
4195     if(nRecordIndex !== null) {
4196         if(count && YAHOO.lang.isNumber(count)) {
4197             // Start with highest index and work down
4198             var startIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
4199             var endIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
4200             for(var i=startIndex; i>endIndex-1; i--) {
4201                 this.deleteRow(i);
4202             }
4203         }
4204         else {
4205             this.deleteRow(nRecordIndex);
4206         }
4207     }
4208     else {
4209     }
4257 // CELL FUNCTIONS
4260  * Outputs markup into the given TD based on given Record.
4262  * @method formatCell
4263  * @param elCell {HTMLElement} TD Element.
4264  * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
4265  * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
4266  * @return {HTML} Markup.
4267  */
4268 YAHOO.widget.DataTable.prototype.formatCell = function(elCell, oRecord, oColumn) {
4269     if(!(oRecord instanceof YAHOO.widget.Record)) {
4270         oRecord = this.getRecord(elCell);
4271     }
4272     if(!(oColumn instanceof YAHOO.widget.Column)) {
4273         oColumn = this._oColumnSet.getColumn(elCell.yuiColumnId);
4274     }
4275     
4276     if(oRecord && oColumn) {
4277         var oData = oRecord.getData(oColumn.key);
4279         var fnFormatter;
4280         if(YAHOO.lang.isString(oColumn.formatter)) {
4281             switch(oColumn.formatter) {
4282                 case "button":
4283                     fnFormatter = YAHOO.widget.DataTable.formatButton;
4284                     break;
4285                 case "checkbox":
4286                     fnFormatter = YAHOO.widget.DataTable.formatCheckbox;
4287                     break;
4288                 case "currency":
4289                     fnFormatter = YAHOO.widget.DataTable.formatCurrency;
4290                     break;
4291                 case "date":
4292                     fnFormatter = YAHOO.widget.DataTable.formatDate;
4293                     break;
4294                 case "dropdown":
4295                     fnFormatter = YAHOO.widget.DataTable.formatDropdown;
4296                     break;
4297                 case "email":
4298                     fnFormatter = YAHOO.widget.DataTable.formatEmail;
4299                     break;
4300                 case "link":
4301                     fnFormatter = YAHOO.widget.DataTable.formatLink;
4302                     break;
4303                 case "number":
4304                     fnFormatter = YAHOO.widget.DataTable.formatNumber;
4305                     break;
4306                 case "radio":
4307                     fnFormatter = YAHOO.widget.DataTable.formatRadio;
4308                     break;
4309                 case "text":
4310                     fnFormatter = YAHOO.widget.DataTable.formatText;
4311                     break;
4312                 case "textarea":
4313                     fnFormatter = YAHOO.widget.DataTable.formatTextarea;
4314                     break;
4315                 case "textbox":
4316                     fnFormatter = YAHOO.widget.DataTable.formatTextbox;
4317                     break;
4318                 case "html":
4319                     // This is the default
4320                     break;
4321                 default:
4322                     fnFormatter = null;
4323             }
4324         }
4325         else if(YAHOO.lang.isFunction(oColumn.formatter)) {
4326             fnFormatter = oColumn.formatter;
4327         }
4329         // Apply special formatter
4330         if(fnFormatter) {
4331             fnFormatter.call(this, elCell, oRecord, oColumn, oData);
4332         }
4333         else {
4334             elCell.innerHTML = (YAHOO.lang.isValue(oData)) ? oData.toString() : "";
4335         }
4337         // Add custom classNames
4338         var aCustomClasses = null;
4339         if(YAHOO.lang.isString(oColumn.className)) {
4340             aCustomClasses = [oColumn.className];
4341         }
4342         else if(YAHOO.lang.isArray(oColumn.className)) {
4343             aCustomClasses = oColumn.className;
4344         }
4345         if(aCustomClasses) {
4346             for(var i=0; i<aCustomClasses.length; i++) {
4347                 YAHOO.util.Dom.addClass(elCell, aCustomClasses[i]);
4348             }
4349         }
4350         
4351         YAHOO.util.Dom.addClass(elCell, "yui-dt-col-"+oColumn.key);
4353         // Is editable?
4354         if(oColumn.editor) {
4355             YAHOO.util.Dom.addClass(elCell,YAHOO.widget.DataTable.CLASS_EDITABLE);
4356         }
4357         
4358         this.fireEvent("cellFormatEvent", {record:oRecord, key:oColumn.key, el:elCell});
4359     }
4360     else {
4361     }
4366  * Formats a BUTTON element.
4368  * @method DataTable.formatButton
4369  * @param el {HTMLElement} The element to format with markup.
4370  * @param oRecord {YAHOO.widget.Record} Record instance.
4371  * @param oColumn {YAHOO.widget.Column} Column instance.
4372  * @param oData {Object | Boolean} Data value for the cell. By default, the value
4373  * is what gets written to the BUTTON.
4374  * @static
4375  */
4376 YAHOO.widget.DataTable.formatButton= function(el, oRecord, oColumn, oData) {
4377     var sValue = YAHOO.lang.isValue(oData) ? oData : "Click";
4378     //TODO: support YAHOO.widget.Button
4379     //if(YAHOO.widget.Button) {
4380     
4381     //}
4382     //else {
4383         el.innerHTML = "<button type=\"button\" class=\""+
4384                 YAHOO.widget.DataTable.CLASS_BUTTON + "\">" + sValue + "</button>";
4385     //}
4389  * Formats a CHECKBOX element.
4391  * @method DataTable.formatCheckbox
4392  * @param el {HTMLElement} The element to format with markup.
4393  * @param oRecord {YAHOO.widget.Record} Record instance.
4394  * @param oColumn {YAHOO.widget.Column} Column instance.
4395  * @param oData {Object | Boolean} Data value for the cell. Can be a simple
4396  * Boolean to indicate whether checkbox is checked or not. Can be object literal
4397  * {checked:bBoolean, label:sLabel}. Other forms of oData require a custom
4398  * formatter.
4399  * @static
4400  */
4401 YAHOO.widget.DataTable.formatCheckbox = function(el, oRecord, oColumn, oData) {
4402     var bChecked = oData;
4403     bChecked = (bChecked) ? " checked" : "";
4404     el.innerHTML = "<input type=\"checkbox\"" + bChecked +
4405             " class=\"" + YAHOO.widget.DataTable.CLASS_CHECKBOX + "\">";
4409  * Formats currency. Default unit is USD.
4411  * @method DataTable.formatCurrency
4412  * @param el {HTMLElement} The element to format with markup.
4413  * @param oRecord {YAHOO.widget.Record} Record instance.
4414  * @param oColumn {YAHOO.widget.Column} Column instance.
4415  * @param oData {Number} Data value for the cell.
4416  * @static
4417  */
4418 YAHOO.widget.DataTable.formatCurrency = function(el, oRecord, oColumn, oData) {
4419     if(YAHOO.lang.isNumber(oData)) {
4420         var nAmount = oData;
4421         var markup;
4423         // Round to the penny
4424         nAmount = Math.round(nAmount*100)/100;
4426         // Default currency is USD
4427         markup = "$"+nAmount;
4429         // Normalize digits
4430         var dotIndex = markup.indexOf(".");
4431         if(dotIndex < 0) {
4432             markup += ".00";
4433         }
4434         else {
4435             while(dotIndex > markup.length-3) {
4436                 markup += "0";
4437             }
4438         }
4439         el.innerHTML = markup;
4440     }
4441     else {
4442         el.innerHTML = YAHOO.lang.isValue(oData) ? oData : "";
4443     }
4447  * Formats JavaScript Dates.
4449  * @method DataTable.formatDate
4450  * @param el {HTMLElement} The element to format with markup.
4451  * @param oRecord {YAHOO.widget.Record} Record instance.
4452  * @param oColumn {YAHOO.widget.Column} Column instance.
4453  * @param oData {Object} Data value for the cell, or null.
4454  * @static
4455  */
4456 YAHOO.widget.DataTable.formatDate = function(el, oRecord, oColumn, oData) {
4457     var oDate = oData;
4458     if(oDate instanceof Date) {
4459         el.innerHTML = (oDate.getMonth()+1) + "/" + oDate.getDate()  + "/" + oDate.getFullYear();
4460     }
4461     else {
4462         el.innerHTML = YAHOO.lang.isValue(oData) ? oData : "";
4463     }
4467  * Formats SELECT elements.
4469  * @method DataTable.formatDropdown
4470  * @param el {HTMLElement} The element to format with markup.
4471  * @param oRecord {YAHOO.widget.Record} Record instance.
4472  * @param oColumn {YAHOO.widget.Column} Column instance.
4473  * @param oData {Object} Data value for the cell, or null.
4474  * @static
4475  */
4476 YAHOO.widget.DataTable.formatDropdown = function(el, oRecord, oColumn, oData) {
4477     var selectedValue = (YAHOO.lang.isValue(oData)) ? oData : oRecord.getData(oColumn.key);
4478     var options = (YAHOO.lang.isArray(oColumn.dropdownOptions)) ?
4479             oColumn.dropdownOptions : null;
4481     var selectEl;
4482     var collection = el.getElementsByTagName("select");
4483     
4484     // Create the form element only once, so we can attach the onChange listener
4485     if(collection.length === 0) {
4486         // Create SELECT element
4487         selectEl = document.createElement("select");
4488         YAHOO.util.Dom.addClass(selectEl, YAHOO.widget.DataTable.CLASS_DROPDOWN);
4489         selectEl = el.appendChild(selectEl);
4491         // Add event listener
4492         //TODO: static method doesn't have access to the datatable instance...
4493         YAHOO.util.Event.addListener(selectEl,"change",oDataTable._onDropdownChange,oDataTable);
4494     }
4496     selectEl = collection[0];
4498     // Update the form element
4499     if(selectEl) {
4500         // Clear out previous options
4501         selectEl.innerHTML = "";
4502         
4503         // We have options to populate
4504         if(options) {
4505             // Create OPTION elements
4506             for(var i=0; i<options.length; i++) {
4507                 var option = options[i];
4508                 var optionEl = document.createElement("option");
4509                 optionEl.value = (YAHOO.lang.isValue(option.value)) ?
4510                         option.value : option;
4511                 optionEl.innerHTML = (YAHOO.lang.isValue(option.text)) ?
4512                         option.text : option;
4513                 optionEl = selectEl.appendChild(optionEl);
4514             }
4515         }
4516         // Selected value is our only option
4517         else {
4518             selectEl.innerHTML = "<option value=\"" + selectedValue + "\">" + selectedValue + "</option>";
4519         }
4520     }
4521     else {
4522         el.innerHTML = YAHOO.lang.isValue(oData) ? oData : "";
4523     }
4527  * Formats emails.
4529  * @method DataTable.formatEmail
4530  * @param el {HTMLElement} The element to format with markup.
4531  * @param oRecord {YAHOO.widget.Record} Record instance.
4532  * @param oColumn {YAHOO.widget.Column} Column instance.
4533  * @param oData {Object} Data value for the cell, or null.
4534  * @static
4535  */
4536 YAHOO.widget.DataTable.formatEmail = function(el, oRecord, oColumn, oData) {
4537     if(YAHOO.lang.isString(oData)) {
4538         el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
4539     }
4540     else {
4541         el.innerHTML = YAHOO.lang.isValue(oData) ? oData : "";
4542     }
4546  * Formats links.
4548  * @method DataTable.formatLink
4549  * @param el {HTMLElement} The element to format with markup.
4550  * @param oRecord {YAHOO.widget.Record} Record instance.
4551  * @param oColumn {YAHOO.widget.Column} Column instance.
4552  * @param oData {Object} Data value for the cell, or null.
4553  * @static
4554  */
4555 YAHOO.widget.DataTable.formatLink = function(el, oRecord, oColumn, oData) {
4556     if(YAHOO.lang.isString(oData)) {
4557         el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
4558     }
4559     else {
4560         el.innerHTML = YAHOO.lang.isValue(oData) ? oData : "";
4561     }
4565  * Formats numbers.
4567  * @method DataTable.formatNumber
4568  * @param el {HTMLElement} The element to format with markup.
4569  * @param oRecord {YAHOO.widget.Record} Record instance.
4570  * @param oColumn {YAHOO.widget.Column} Column instance.
4571  * @param oData {Object} Data value for the cell, or null.
4572  * @static
4573  */
4574 YAHOO.widget.DataTable.formatNumber = function(el, oRecord, oColumn, oData) {
4575     if(YAHOO.lang.isNumber(oData)) {
4576         el.innerHTML = oData;
4577     }
4578     else {
4579         el.innerHTML = YAHOO.lang.isValue(oData) ? oData : "";
4580     }
4584  * Formats INPUT TYPE=RADIO elements.
4586  * @method DataTable.formatRadio
4587  * @param el {HTMLElement} The element to format with markup.
4588  * @param oRecord {YAHOO.widget.Record} Record instance.
4589  * @param oColumn {YAHOO.widget.Column} Column instance.
4590  * @param oData {Object} (Optional) Data value for the cell.
4591  * @static
4592  */
4593 YAHOO.widget.DataTable.formatRadio = function(el, oRecord, oColumn, oData) {
4594     var bChecked = oData;
4595     bChecked = (bChecked) ? " checked" : "";
4596     el.innerHTML = "<input type=\"radio\"" + bChecked +
4597             " name=\"" + oColumn.getId() + "-radio\"" +
4598             " class=\"" + YAHOO.widget.DataTable.CLASS_CHECKBOX + "\">";
4602  * Formats text strings.
4604  * @method DataTable.formatText
4605  * @param el {HTMLElement} The element to format with markup.
4606  * @param oRecord {YAHOO.widget.Record} Record instance.
4607  * @param oColumn {YAHOO.widget.Column} Column instance.
4608  * @param oData {Object} (Optional) Data value for the cell.
4609  * @static
4610  */
4611 YAHOO.widget.DataTable.formatText = function(el, oRecord, oColumn, oData) {
4612     var value = (YAHOO.lang.isValue(oRecord.getData(oColumn.key))) ?
4613             oRecord.getData(oColumn.key) : "";
4614     //TODO: move to util function
4615     el.innerHTML = value.toString().replace(/&/g, "&#38;").replace(/</g, "&#60;").replace(/>/g, "&#62;");
4619  * Formats TEXTAREA elements.
4621  * @method DataTable.formatTextarea
4622  * @param el {HTMLElement} The element to format with markup.
4623  * @param oRecord {YAHOO.widget.Record} Record instance.
4624  * @param oColumn {YAHOO.widget.Column} Column instance.
4625  * @param oData {Object} (Optional) Data value for the cell.
4626  * @static
4627  */
4628 YAHOO.widget.DataTable.formatTextarea = function(el, oRecord, oColumn, oData) {
4629     var value = (YAHOO.lang.isValue(oRecord.getData(oColumn.key))) ?
4630             oRecord.getData(oColumn.key) : "";
4631     var markup = "<textarea>" + value + "</textarea>";
4632     el.innerHTML = markup;
4636  * Formats INPUT TYPE=TEXT elements.
4638  * @method DataTable.formatTextbox
4639  * @param el {HTMLElement} The element to format with markup.
4640  * @param oRecord {YAHOO.widget.Record} Record instance.
4641  * @param oColumn {YAHOO.widget.Column} Column instance.
4642  * @param oData {Object} (Optional) Data value for the cell.
4643  * @static
4644  */
4645 YAHOO.widget.DataTable.formatTextbox = function(el, oRecord, oColumn, oData) {
4646     var value = (YAHOO.lang.isValue(oRecord.getData(oColumn.key))) ?
4647             oRecord.getData(oColumn.key) : "";
4648     var markup = "<input type=\"text\" value=\"" + value + "\">";
4649     el.innerHTML = markup;
4699 // PAGINATION
4702  * Updates Paginator values in response to RecordSet changes and/or DOM events.
4703  * Pass in all, a subset, or no values.
4705  * @method updatePaginator
4706  * @param oNewValues {Object} (Optional) Object literal of Paginator values, or
4707  * a subset of Paginator values.
4708  * @param {Object} Object literal of all Paginator values.
4709  */
4711 YAHOO.widget.DataTable.prototype.updatePaginator = function(oNewValues) {
4712     // Complete the set
4713     var oValidPaginator = this.get("paginator");
4714     for(var param in oNewValues) {
4715         if(oValidPaginator.hasOwnProperty(param)) {
4716             oValidPaginator[param] = oNewValues[param];
4717         }
4718     }
4719     
4720     oValidPaginator.totalRecords = this._oRecordSet.getLength();
4721     oValidPaginator.rowsThisPage = Math.min(oValidPaginator.rowsPerPage, oValidPaginator.totalRecords);
4722     oValidPaginator.totalPages = Math.ceil(oValidPaginator.totalRecords / oValidPaginator.rowsThisPage);
4723     if(isNaN(oValidPaginator.totalPages)) {
4724         oValidPaginator.totalPages = 0;
4725     }
4727     this.set("paginator", oValidPaginator);
4728     return this.get("paginator");
4732  * Displays given page of a paginated DataTable.
4734  * @method showPage
4735  * @param nPage {Number} Which page.
4736  */
4737 YAHOO.widget.DataTable.prototype.showPage = function(nPage) {
4738     // Validate input
4739     if(!YAHOO.lang.isNumber(nPage) || (nPage < 1) || (nPage > this.get("paginator").totalPages)) {
4740         nPage = 1;
4741     }
4742     this.updatePaginator({currentPage:nPage});
4743     this.refreshView();
4747  * Updates Paginator containers with markup. Override this method to customize pagination UI.
4749  * @method formatPaginators
4750  */
4751  YAHOO.widget.DataTable.prototype.formatPaginators = function() {
4752     var pag = this.get("paginator");
4754     // For Opera workaround
4755     var dropdownEnabled = false;
4757     // Links are enabled
4758     if(pag.pageLinks > -1) {
4759         for(var i=0; i<pag.links.length; i++) {
4760             this.formatPaginatorLinks(pag.links[i], pag.currentPage, pag.pageLinksStart, pag.pageLinks, pag.totalPages);
4761         }
4762     }
4764     // Dropdown is enabled
4765     for(i=0; i<pag.dropdowns.length; i++) {
4766          if(pag.dropdownOptions) {
4767             dropdownEnabled = true;
4768             this.formatPaginatorDropdown(pag.dropdowns[i], pag.dropdownOptions);
4769         }
4770         else {
4771             pag.dropdowns[i].style.display = "none";
4772         }
4773     }
4775     // For Opera artifacting in dropdowns
4776     if(dropdownEnabled && navigator.userAgent.toLowerCase().indexOf("opera") != -1) {
4777         document.body.style += '';
4778     }
4782  * Updates Paginator dropdown. If dropdown doesn't exist, the markup is created.
4783  * Sets dropdown elements's "selected" value.
4785  * @method formatPaginatorDropdown
4786  * @param elDropdown {HTMLElement} The SELECT element.
4787  * @param dropdownOptions {Object[]} OPTION values for display in the SELECT element.
4788  */
4789 YAHOO.widget.DataTable.prototype.formatPaginatorDropdown = function(elDropdown, dropdownOptions) {
4790     if(elDropdown && (elDropdown.ownerDocument == document)) {
4791         // Clear OPTION elements
4792         while (elDropdown.firstChild) {
4793             elDropdown.removeChild(elDropdown.firstChild);
4794         }
4796         // Create OPTION elements
4797         for(var j=0; j<dropdownOptions.length; j++) {
4798             var dropdownOption = dropdownOptions[j];
4799             var optionEl = document.createElement("option");
4800             optionEl.value = (YAHOO.lang.isValue(dropdownOption.value)) ?
4801                     dropdownOption.value : dropdownOption;
4802             optionEl.innerHTML = (YAHOO.lang.isValue(dropdownOption.text)) ?
4803                     dropdownOption.text : dropdownOption;
4804             optionEl = elDropdown.appendChild(optionEl);
4805         }
4807         var options = elDropdown.options;
4808         // Update dropdown's "selected" value
4809         if(options.length) {
4810             for(var i=options.length-1; i>-1; i--) {
4811                 if((this.get("paginator").rowsPerPage + "") === options[i].value) {
4812                     options[i].selected = true;
4813                 }
4814             }
4815         }
4817         // Show the dropdown
4818         elDropdown.style.display = "";
4819         return;
4820     }
4824  * Updates Paginator links container with markup.
4826  * @method formatPaginatorLinks
4827  * @param elContainer {HTMLElement} The link container element.
4828  * @param nCurrentPage {Number} Current page.
4829  * @param nPageLinksStart {Number} First page link to display.
4830  * @param nPageLinksLength {Number} How many page links to display.
4831  * @param nTotalPages {Number} Total number of pages.
4832  */
4833 YAHOO.widget.DataTable.prototype.formatPaginatorLinks = function(elContainer, nCurrentPage, nPageLinksStart, nPageLinksLength, nTotalPages) {
4834     if(elContainer && (elContainer.ownerDocument == document) &&
4835             YAHOO.lang.isNumber(nCurrentPage) && YAHOO.lang.isNumber(nPageLinksStart) &&
4836             YAHOO.lang.isNumber(nTotalPages)) {
4837         // Set up markup for first/last/previous/next
4838         var bIsFirstPage = (nCurrentPage == 1) ? true : false;
4839         var bIsLastPage = (nCurrentPage == nTotalPages) ? true : false;
4840         var sFirstLinkMarkup = (bIsFirstPage) ?
4841                 " <span class=\"" + YAHOO.widget.DataTable.CLASS_DISABLED +
4842                 " " + YAHOO.widget.DataTable.CLASS_FIRST + "\">&lt;&lt;</span> " :
4843                 " <a href=\"#\" class=\"" + YAHOO.widget.DataTable.CLASS_FIRST + "\">&lt;&lt;</a> ";
4844         var sPrevLinkMarkup = (bIsFirstPage) ?
4845                 " <span class=\"" + YAHOO.widget.DataTable.CLASS_DISABLED +
4846                 " " + YAHOO.widget.DataTable.CLASS_PREVIOUS + "\">&lt;</span> " :
4847                 " <a href=\"#\" class=\"" + YAHOO.widget.DataTable.CLASS_PREVIOUS + "\">&lt;</a> " ;
4848         var sNextLinkMarkup = (bIsLastPage) ?
4849                 " <span class=\"" + YAHOO.widget.DataTable.CLASS_DISABLED +
4850                 " " + YAHOO.widget.DataTable.CLASS_NEXT + "\">&gt;</span> " :
4851                 " <a href=\"#\" class=\"" + YAHOO.widget.DataTable.CLASS_NEXT + "\">&gt;</a> " ;
4852         var sLastLinkMarkup = (bIsLastPage) ?
4853                 " <span class=\"" + YAHOO.widget.DataTable.CLASS_DISABLED +
4854                 " " + YAHOO.widget.DataTable.CLASS_LAST +  "\">&gt;&gt;</span> " :
4855                 " <a href=\"#\" class=\"" + YAHOO.widget.DataTable.CLASS_LAST + "\">&gt;&gt;</a> ";
4857         // Start with first and previous
4858         var sMarkup = sFirstLinkMarkup + sPrevLinkMarkup;
4859         
4860         // Ok to show all links
4861         var nMaxLinks = nTotalPages;
4862         var nFirstLink = 1;
4863         var nLastLink = nTotalPages;
4865         if(nPageLinksLength > 0) {
4866         // Calculate how many links to show
4867             nMaxLinks = (nPageLinksStart+nPageLinksLength < nTotalPages) ?
4868                     nPageLinksStart+nPageLinksLength-1 : nTotalPages;
4870             // Try to keep the current page in the middle
4871             nFirstLink = (nCurrentPage - Math.floor(nMaxLinks/2) > 0) ? nCurrentPage - Math.floor(nMaxLinks/2) : 1;
4872             nLastLink = (nCurrentPage + Math.floor(nMaxLinks/2) <= nTotalPages) ? nCurrentPage + Math.floor(nMaxLinks/2) : nTotalPages;
4874             // Keep the last link in range
4875             if(nFirstLink === 1) {
4876                 nLastLink = nMaxLinks;
4877             }
4878             // Keep the first link in range
4879             else if(nLastLink === nTotalPages) {
4880                 nFirstLink = nTotalPages - nMaxLinks + 1;
4881             }
4883             // An even number of links can get funky
4884             if(nLastLink - nFirstLink === nMaxLinks) {
4885                 nLastLink--;
4886             }
4887       }
4888         
4889         // Generate markup for each page
4890         for(var i=nFirstLink; i<=nLastLink; i++) {
4891             if(i != nCurrentPage) {
4892                 sMarkup += " <a href=\"#\" class=\"" + YAHOO.widget.DataTable.CLASS_PAGE + "\">" + i + "</a> ";
4893             }
4894             else {
4895                 sMarkup += " <span class=\"" + YAHOO.widget.DataTable.CLASS_SELECTED + "\">" + i + "</span>";
4896             }
4897         }
4898         sMarkup += sNextLinkMarkup + sLastLinkMarkup;
4899         elContainer.innerHTML = sMarkup;
4900         return;
4901     }
4952 // SELECTION/HIGHLIGHTING
4955  * ID string of last highlighted cell element
4957  * @property _sLastHighlightedCellId
4958  * @type String
4959  * @private
4960  */
4961 YAHOO.widget.DataTable.prototype._sLastHighlightedCellId = null;
4964  * ID string of last highlighted row element
4966  * @property _sLastHighlightedRowId
4967  * @type String
4968  * @private
4969  */
4970 YAHOO.widget.DataTable.prototype._sLastHighlightedRowId = null;
4973  * Array of selections: {recordId:nRecordId, cellIndex:nCellIndex}
4975  * @property _aSelections
4976  * @type Object[]
4977  * @private
4978  */
4979 YAHOO.widget.DataTable.prototype._aSelections = null;
4982  * ID string of last selected element
4984  * @property _sLastSelectedId
4985  * @type String
4986  * @private
4987  */
4988 YAHOO.widget.DataTable.prototype._sLastSelectedId = null;
4991  * ID string of the selection anchor element.
4993  * @property _sSelectionAnchorId
4994  * @type String
4995  * @private
4996  */
4997 YAHOO.widget.DataTable.prototype._sSelectionAnchorId = null;
5000  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
5001  * from all TR elements on the page.
5003  * @method _unselectAllTrEls
5004  * @private
5005  */
5006 YAHOO.widget.DataTable.prototype._unselectAllTrEls = function() {
5007     var selectedRows = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_SELECTED,"tr",this._elTbody);
5008     YAHOO.util.Dom.removeClass(selectedRows, YAHOO.widget.DataTable.CLASS_SELECTED);
5012  * Returns array of selected TR elements on the page.
5014  * @method getSelectedTrEls
5015  * @return {HTMLElement[]} Array of selected TR elements.
5016  */
5017 YAHOO.widget.DataTable.prototype.getSelectedTrEls = function() {
5018     return YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_SELECTED,"tr",this._elTbody);
5022  * Sets given row to the selected state.
5024  * @method selectRow
5025  * @param row {HTMLElement | String} HTML element reference or ID.
5026  */
5027 YAHOO.widget.DataTable.prototype.selectRow = function(row) {
5028     // Validate the row
5029     var elRow = this.getTrEl(row);
5030     if(elRow) {
5031         var oRecord = this.getRecord(elRow);
5032         if(oRecord) {
5033             // Get Record ID
5034             var tracker = this._aSelections || [];
5035             var nRecordId = oRecord.getId();
5036             // Remove if already there
5038             // Use Array.indexOf if available...
5039             if(tracker.indexOf && (tracker.indexOf(nRecordId) >  -1)) {
5040                 tracker.splice(tracker.indexOf(nRecordId),1);
5041             }
5042             // ...or do it the old-fashioned way
5043             else {
5044                 for(var j=0; j<tracker.length; j++) {
5045                    if(tracker[j] === nRecordId){
5046                         tracker.splice(j,1);
5047                     }
5048                 }
5049             }
5050             // Add to the end
5051             tracker.push(nRecordId);
5053             // Update trackers
5054             this._sLastSelectedId = elRow.id;
5055             if(!this._sSelectionAnchorId) {
5056                 this._sSelectionAnchorId = elRow.id;
5057             }
5058             this._aSelections = tracker;
5059         
5060             // Update UI
5061             YAHOO.util.Dom.addClass(elRow, YAHOO.widget.DataTable.CLASS_SELECTED);
5063             this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
5065             return;
5066         }
5067     }
5069 // Backward compatibility
5070 YAHOO.widget.DataTable.prototype.select = function(els) {
5071     if(!YAHOO.lang.isArray(els)) {
5072         els = [els];
5073     }
5074     for(var i=0; i<els.length; i++) {
5075         this.selectRow(els[i]);
5076     }
5080  * Sets given row to the unselected state.
5082  * @method unselectRow
5083  * @param row {HTMLElement | String} HTML TR element reference or ID.
5084  */
5085 YAHOO.widget.DataTable.prototype.unselectRow = function(row) {
5086     // Validate the row
5087     var elRow = this.getTrEl(row);
5088     if(elRow) {
5089         var oRecord = this.getRecord(elRow);
5090         if(oRecord) {
5091             // Get Record ID
5092             var tracker = this._aSelections || [];
5093             var nRecordId = oRecord.getId();
5095             // Remove if there
5096             var bFound = false;
5097             
5098             // Use Array.indexOf if available...
5099             if(tracker.indexOf && (tracker.indexOf(nRecordId) >  -1)) {
5100                 tracker.splice(tracker.indexOf(nRecordId),1);
5101                 bFound = true;
5102             }
5103             // ...or do it the old-fashioned way
5104             else {
5105                 for(var j=0; j<tracker.length; j++) {
5106                    if(tracker[j] === nRecordId){
5107                         tracker.splice(j,1);
5108                         bFound = true;
5109                     }
5110                 }
5111             }
5113             if(bFound) {
5114                 // Update tracker
5115                 this._aSelections = tracker;
5117                 // Update the UI
5118                 YAHOO.util.Dom.removeClass(elRow, YAHOO.widget.DataTable.CLASS_SELECTED);
5120                 this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
5122                 return;
5123             }
5124         }
5125     }
5129  * Clears out all row selections.
5131  * @method unselectAllRows
5132  */
5133 YAHOO.widget.DataTable.prototype.unselectAllRows = function() {
5134     // Remove from tracker
5135     var tracker = this._aSelections || [];
5136     for(var j=0; j<tracker.length; j++) {
5137        if(YAHOO.lang.isNumber(tracker[j])){
5138             tracker.splice(j,1);
5139         }
5140     }
5142     // Update tracker
5143     this._aSelections = tracker;
5145     // Update UI
5146     this._unselectAllTrEls();
5148     //TODO: send an array of [{el:el,record:record}]
5149     //TODO: or convert this to an unselectRows method
5150     //TODO: that takes an array of rows or unselects all if none given
5151     this.fireEvent("unselectAllRowsEvent");
5155  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
5156  * from all TD elements in the internal tracker.
5158  * @method _unselectAllTdEls
5159  * @private
5160  */
5161 YAHOO.widget.DataTable.prototype._unselectAllTdEls = function() {
5162     var selectedCells = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_SELECTED,"td",this._elTbody);
5163     YAHOO.util.Dom.removeClass(selectedCells, YAHOO.widget.DataTable.CLASS_SELECTED);
5167  * Returns array of selected TD elements on the page.
5169  * @method getSelectedTdEls
5170  * @return {HTMLElement[]} Array of selected TD elements.
5171  */
5172 YAHOO.widget.DataTable.prototype.getSelectedTdEls = function() {
5173     return YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_SELECTED,"td",this._elTbody);
5177  * Sets given cell to the selected state.
5179  * @method selectCell
5180  * @param cell {HTMLElement | String} DOM element reference or ID string
5181  * to DataTable page element or RecordSet index.
5182  */
5183 YAHOO.widget.DataTable.prototype.selectCell = function(cell) {
5184     var elCell = this.getTdEl(cell);
5185     
5186     if(elCell) {
5187         var oRecord = this.getRecord(elCell);
5188         var nColumnId = elCell.yuiColumnId;
5190         if(oRecord && YAHOO.lang.isNumber(nColumnId)) {
5191             // Get Record ID
5192             var tracker = this._aSelections || [];
5193             var nRecordId = oRecord.getId();
5195             // Remove if there
5196             for(var j=0; j<tracker.length; j++) {
5197                if((tracker[j].recordId === nRecordId) && (tracker[j].columnId === nColumnId)){
5198                     tracker.splice(j,1);
5199                 }
5200             }
5202             // Add to the end
5203             tracker.push({recordId:nRecordId, columnId:nColumnId});
5205             // Update trackers
5206             this._aSelections = tracker;
5207             this._sLastSelectedId = elCell.id;
5208             if(!this._sSelectionAnchorId) {
5209                 this._sSelectionAnchorId = elCell.id;
5210             }
5212             // Update the UI
5213             YAHOO.util.Dom.addClass(elCell, YAHOO.widget.DataTable.CLASS_SELECTED);
5215             this.fireEvent("cellSelectEvent", {record:oRecord,
5216                     key: this._oColumnSet.getColumn(nColumnId).key, el:elCell});
5217             return;
5218         }
5219     }
5223  * Sets given cell to the unselected state.
5225  * @method unselectCell
5226  * @param cell {HTMLElement | String} DOM element reference or ID string
5227  * to DataTable page element or RecordSet index.
5228  */
5229 YAHOO.widget.DataTable.prototype.unselectCell = function(cell) {
5230     var elCell = this.getTdEl(cell);
5232     if(elCell) {
5233         var oRecord = this.getRecord(elCell);
5234         var nColumnId = elCell.yuiColumnId;
5236         if(oRecord && YAHOO.lang.isNumber(nColumnId)) {
5237             // Get Record ID
5238             var tracker = this._aSelections || [];
5239             var id = oRecord.getId();
5241             // Is it selected?
5242             for(var j=0; j<tracker.length; j++) {
5243                 if((tracker[j].recordId === id) && (tracker[j].columnId === nColumnId)){
5244                     // Remove from tracker
5245                     tracker.splice(j,1);
5246                     
5247                     // Update tracker
5248                     this._aSelections = tracker;
5250                     // Update the UI
5251                     YAHOO.util.Dom.removeClass(elCell, YAHOO.widget.DataTable.CLASS_SELECTED);
5253                     this.fireEvent("cellUnselectEvent", {record:oRecord,
5254                             key:this._oColumnSet.getColumn(nColumnId).key, el:elCell});
5256                     return;
5257                 }
5258             }
5259         }
5260     }
5264  * Clears out all cell selections.
5266  * @method unselectAllCells
5267  */
5268 YAHOO.widget.DataTable.prototype.unselectAllCells= function() {
5269     // Remove from tracker
5270     var tracker = this._aSelections || [];
5271     for(var j=0; j<tracker.length; j++) {
5272        if(tracker[j].constructor == Object){
5273             tracker.splice(j,1);
5274         }
5275     }
5277     // Update tracker
5278     this._aSelections = tracker;
5280     // Update UI
5281     this._unselectAllTdEls();
5282     
5283     //TODO: send data
5284     //TODO: or fire individual cellUnselectEvent
5285     this.fireEvent("unselectAllCellsEvent");
5289  * Returns true if given TR or TD element is select, false otherwise.
5291  * @method isSelected
5292  * @param el {HTMLElement} HTML element reference or ID of a TR or TD.
5293  * @return {Boolean} True if element is selected.
5294  */
5295 YAHOO.widget.DataTable.prototype.isSelected = function(el) {
5296     return YAHOO.util.Dom.hasClass(el,YAHOO.widget.DataTable.CLASS_SELECTED);
5300  * Returns selected rows as an array of Record IDs.
5302  * @method getSelectedRows
5303  * @return {HTMLElement[]} Array of selected rows by Record ID.
5304  */
5305 YAHOO.widget.DataTable.prototype.getSelectedRows = function() {
5306     var aSelectedRows = [];
5307     var tracker = this._aSelections || [];
5308     for(var j=0; j<tracker.length; j++) {
5309        if(YAHOO.lang.isNumber(tracker[j])){
5310             aSelectedRows.push(tracker[j]);
5311         }
5312     }
5313     return aSelectedRows;
5317  * Returns selected cells as an array of object literals:
5318  *     {recordId:nRecordID, columnId:nColumnId}.
5320  * @method getSelectedCells
5321  * @return {HTMLElement[]} Array of selected cells by Record and Column IDs.
5322  */
5323 YAHOO.widget.DataTable.prototype.getSelectedCells = function() {
5324     var aSelectedCells = [];
5325     var tracker = this._aSelections || [];
5326     for(var j=0; j<tracker.length; j++) {
5327        if(tracker[j] && (tracker[j].constructor == Object)){
5328             aSelectedCells.push({recordId:tracker[j].recordId, columnId:tracker[j].columnId});
5329         }
5330     }
5331     return aSelectedCells;
5335  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
5337  * @method highlightRow
5338  * @param row {HTMLElement | String} DOM element reference or ID string.
5339  */
5340 YAHOO.widget.DataTable.prototype.highlightRow = function(row) {
5341     var elRow = this.getTrEl(row);
5343     if(elRow) {
5344         // Make sure previous row is unhighlighted
5345         if(this._sLastHighlightedRowId) {
5346             YAHOO.util.Dom.removeClass(this._sLastHighlightedRowId,YAHOO.widget.DataTable.CLASS_HIGHLIGHTED);
5347         }
5348         var oRecord = this.getRecord(elRow);
5349         YAHOO.util.Dom.addClass(elRow,YAHOO.widget.DataTable.CLASS_HIGHLIGHTED);
5350         this._sLastHighlightedRowId = elRow.id;
5351         this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
5352         return;
5353     }
5357  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
5359  * @method unhighlightRow
5360  * @param row {HTMLElement | String} DOM element reference or ID string.
5361  */
5362 YAHOO.widget.DataTable.prototype.unhighlightRow = function(row) {
5363     var elRow = this.getTrEl(row);
5365     if(elRow) {
5366         var oRecord = this.getRecord(elRow);
5367         YAHOO.util.Dom.removeClass(elRow,YAHOO.widget.DataTable.CLASS_HIGHLIGHTED);
5368         this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
5369         return;
5370     }
5374  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
5376  * @method highlightCell
5377  * @param cell {HTMLElement | String} DOM element reference or ID string.
5378  */
5379 YAHOO.widget.DataTable.prototype.highlightCell = function(cell) {
5380     var elCell = this.getTdEl(cell);
5382     if(elCell) {
5383         // Make sure previous cell is unhighlighted
5384         if(this._sLastHighlightedCellId) {
5385             YAHOO.util.Dom.removeClass(this._sLastHighlightedCellId,YAHOO.widget.DataTable.CLASS_HIGHLIGHTED);
5386         }
5387         
5388         var oRecord = this.getRecord(elCell);
5389         YAHOO.util.Dom.addClass(elCell,YAHOO.widget.DataTable.CLASS_HIGHLIGHTED);
5390         this._sLastHighlightedCellId = elCell.id;
5391         this.fireEvent("cellHighlightEvent", {record:oRecord,
5392                     key:this._oColumnSet.getColumn(elCell.yuiColumnId).key, el:elCell});
5393         return;
5394     }
5398  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
5400  * @method unhighlightCell
5401  * @param cell {HTMLElement | String} DOM element reference or ID string.
5402  */
5403 YAHOO.widget.DataTable.prototype.unhighlightCell = function(cell) {
5404     var elCell = this.getTdEl(cell);
5406     if(elCell) {
5407         var oRecord = this.getRecord(elCell);
5408         YAHOO.util.Dom.removeClass(elCell,YAHOO.widget.DataTable.CLASS_HIGHLIGHTED);
5409         this.fireEvent("cellUnhighlightEvent", {record:oRecord,
5410                     key:this._oColumnSet.getColumn(elCell.yuiColumnId).key, el:elCell});
5411         return;
5412     }
5459 // INLINE EDITING
5461 /*TODO: for TAB handling
5462  * Shows Cell Editor for next cell.
5464  * @method editNextCell
5465  * @param elCell {HTMLElement} Cell element from which to edit next cell.
5466  */
5467 //YAHOO.widget.DataTable.prototype.editNextCell = function(elCell) {
5468 //};
5471  * Shows Cell Editor for given cell.
5473  * @method showCellEditor
5474  * @param elCell {HTMLElement | String} Cell element to edit.
5475  * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
5476  * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
5477  */
5478 YAHOO.widget.DataTable.prototype.showCellEditor = function(elCell, oRecord, oColumn) {
5479     elCell = YAHOO.util.Dom.get(elCell);
5480     
5481     if(elCell && (elCell.ownerDocument === document)) {
5482         if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
5483             oRecord = this.getRecord(elCell);
5484         }
5485         if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
5486             oColumn = this.getColumn(elCell);
5487         }
5488         if(oRecord && oColumn) {
5489             var oCellEditor = this._oCellEditor;
5490             
5491             // Clear previous Editor
5492             if(oCellEditor.isActive) {
5493                 this.cancelCellEditor();
5494             }
5496             // Editor not defined
5497             if(!oColumn.editor) {
5498                 return;
5499             }
5500             
5501             // Update Editor values
5502             oCellEditor.cell = elCell;
5503             oCellEditor.record = oRecord;
5504             oCellEditor.column = oColumn;
5505             oCellEditor.validator = (oColumn.editorOptions &&
5506                     YAHOO.lang.isFunction(oColumn.editorOptions.validator)) ?
5507                     oColumn.editorOptions.validator : null;
5508             oCellEditor.value = oRecord.getData(oColumn.key);
5510             // Move Editor
5511             var elContainer = oCellEditor.container;
5512             var x = YAHOO.util.Dom.getX(elCell);
5513             var y = YAHOO.util.Dom.getY(elCell);
5515             // SF doesn't get xy for cells in scrolling table
5516             // when tbody display is set to block
5517             if(isNaN(x) || isNaN(y)) {
5518                 x = elCell.offsetLeft + // cell pos relative to table
5519                         YAHOO.util.Dom.getX(this._elTable) - // plus table pos relative to document
5520                         this._elTbody.scrollLeft; // minus tbody scroll
5521                 y = elCell.offsetTop + // cell pos relative to table
5522                         YAHOO.util.Dom.getY(this._elTable) - // plus table pos relative to document
5523                         this._elTbody.scrollTop + // minus tbody scroll
5524                         this._elThead.offsetHeight; // account for fixed headers
5525             }
5526             
5527             elContainer.style.left = x + "px";
5528             elContainer.style.top = y + "px";
5530             // Show Editor
5531             elContainer.style.display = "";
5532             
5533             // Render Editor markup
5534             var fnEditor;
5535             if(YAHOO.lang.isString(oColumn.editor)) {
5536                 switch(oColumn.editor) {
5537                     case "checkbox":
5538                         fnEditor = YAHOO.widget.DataTable.editCheckbox;
5539                         break;
5540                     case "date":
5541                         fnEditor = YAHOO.widget.DataTable.editDate;
5542                         break;
5543                     case "dropdown":
5544                         fnEditor = YAHOO.widget.DataTable.editDropdown;
5545                         break;
5546                     case "radio":
5547                         fnEditor = YAHOO.widget.DataTable.editRadio;
5548                         break;
5549                     case "textarea":
5550                         fnEditor = YAHOO.widget.DataTable.editTextarea;
5551                         break;
5552                     case "textbox":
5553                         fnEditor = YAHOO.widget.DataTable.editTextbox;
5554                         break;
5555                     default:
5556                         fnEditor = null;
5557                 }
5558             }
5559             else if(YAHOO.lang.isFunction(oColumn.editor)) {
5560                 fnEditor = oColumn.editor;
5561             }
5563             if(fnEditor) {
5564                 // Create DOM input elements
5565                 fnEditor(this._oCellEditor, this);
5566                 
5567                 // Show Save/Cancel buttons
5568                 if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
5569                     this.showCellEditorBtns(elContainer);
5570                 }
5572                 // Hook to customize the UI
5573                 this.doBeforeShowCellEditor(this._oCellEditor);
5575                 oCellEditor.isActive = true;
5576                 
5577                 //TODO: verify which args to pass
5578                 this.fireEvent("editorShowEvent", {editor:oCellEditor});
5579                 return;
5580             }
5581         }
5582     }
5586  * Overridable abstract method to customize Cell Editor UI.
5588  * @method doBeforeShowCellEditor
5589  * @param oCellEditor {Object} Cell Editor object literal.
5590  */
5591 YAHOO.widget.DataTable.prototype.doBeforeShowCellEditor = function(oCellEditor) {
5595  * Adds Save/Cancel buttons to Cell Editor.
5597  * @method showCellEditorBtns
5598  * @param elContainer {HTMLElement} Cell Editor container.
5599  */
5600 YAHOO.widget.DataTable.prototype.showCellEditorBtns = function(elContainer) {
5601     // Buttons
5602     var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
5603     YAHOO.util.Dom.addClass(elBtnsDiv, YAHOO.widget.DataTable.CLASS_BUTTON);
5605     // Save button
5606     var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
5607     YAHOO.util.Dom.addClass(elSaveBtn, YAHOO.widget.DataTable.CLASS_DEFAULT);
5608     elSaveBtn.innerHTML = "OK";
5609     YAHOO.util.Event.addListener(elSaveBtn, "click", this.saveCellEditor, this, true);
5611     // Cancel button
5612     var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
5613     elCancelBtn.innerHTML = "Cancel";
5614     YAHOO.util.Event.addListener(elCancelBtn, "click", this.cancelCellEditor, this, true);
5618  * Clears Cell Editor of all state and UI.
5620  * @method resetCellEditor
5621  */
5623 YAHOO.widget.DataTable.prototype.resetCellEditor = function() {
5624     var elContainer = this._oCellEditor.container;
5625     elContainer.style.display = "none";
5626     YAHOO.util.Event.purgeElement(elContainer, true);
5627     elContainer.innerHTML = "";
5628     this._oCellEditor.value = null;
5629     this._oCellEditor.isActive = false;
5633  * Saves Cell Editor input to Record.
5635  * @method saveCellEditor
5636  */
5637 YAHOO.widget.DataTable.prototype.saveCellEditor = function() {
5638     //TODO: Copy the editor's values to pass to the event
5639     if(this._oCellEditor.isActive) {
5640         var newData = this._oCellEditor.value;
5641         var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
5643         // Validate input data
5644         if(this._oCellEditor.validator) {
5645             this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData);
5646             if(this._oCellEditor.value === null ) {
5647                 this.resetCellEditor();
5648                 this.fireEvent("editorRevertEvent",
5649                         {editor:this._oCellEditor, oldData:oldData, newData:newData});
5650                 return;
5651             }
5652         }
5654         // Update the Record
5655         this._oRecordSet.updateKey(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
5657         // Update the UI
5658         this.formatCell(this._oCellEditor.cell);
5660         // Clear out the Cell Editor
5661         this.resetCellEditor();
5663         this.fireEvent("editorSaveEvent",
5664                 {editor:this._oCellEditor, oldData:oldData, newData:newData});
5665     }
5666     else {
5667     }
5671  * Cancels Cell Editor.
5673  * @method cancelCellEditor
5674  */
5675 YAHOO.widget.DataTable.prototype.cancelCellEditor = function() {
5676     if(this._oCellEditor.isActive) {
5677         this.resetCellEditor();
5678         //TODO: preserve values for the event?
5679         this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
5680     }
5681     else {
5682     }
5686  * Enables CHECKBOX Editor.
5688  * @method editCheckbox
5689  */
5690 //YAHOO.widget.DataTable.editCheckbox = function(elContainer, oRecord, oColumn, oEditor, oSelf) {
5691 YAHOO.widget.DataTable.editCheckbox = function(oEditor, oSelf) {
5692     var elCell = oEditor.cell;
5693     var oRecord = oEditor.record;
5694     var oColumn = oEditor.column;
5695     var elContainer = oEditor.container;
5696     var aCheckedValues = oRecord.getData(oColumn.key);
5697     if(!YAHOO.lang.isArray(aCheckedValues)) {
5698         aCheckedValues = [aCheckedValues];
5699     }
5701     // Checkboxes
5702     if(oColumn.editorOptions && YAHOO.lang.isArray(oColumn.editorOptions.checkboxOptions)) {
5703         var checkboxOptions = oColumn.editorOptions.checkboxOptions;
5704         var checkboxValue, checkboxId, elLabel, j, k;
5705         // First create the checkbox buttons in an IE-friendly way
5706         for(j=0; j<checkboxOptions.length; j++) {
5707             checkboxValue = YAHOO.lang.isValue(checkboxOptions[j].label) ?
5708                     checkboxOptions[j].label : checkboxOptions[j];
5709             checkboxId =  oSelf.id + "-editor-checkbox" + j;
5710             elContainer.innerHTML += "<input type=\"checkbox\"" +
5711                     " name=\"" + oSelf.id + "-editor-checkbox\"" +
5712                     " value=\"" + checkboxValue + "\"" +
5713                     " id=\"" +  checkboxId + "\">";
5714             // Then create the labels in an IE-friendly way
5715             elLabel = elContainer.appendChild(document.createElement("label"));
5716             elLabel.htmlFor = checkboxId;
5717             elLabel.innerHTML = checkboxValue;
5718         }
5719         var aCheckboxEls = [];
5720         var checkboxEl;
5721         // Loop through checkboxes to check them
5722         for(j=0; j<checkboxOptions.length; j++) {
5723             checkboxEl = YAHOO.util.Dom.get(oSelf.id + "-editor-checkbox" + j);
5724             aCheckboxEls.push(checkboxEl);
5725             for(k=0; k<aCheckedValues.length; k++) {
5726                 if(checkboxEl.value === aCheckedValues[k]) {
5727                     checkboxEl.checked = true;
5728                 }
5729             }
5730             // Focus the first checkbox
5731             if(j===0) {
5732                 oSelf._focusEl(checkboxEl);
5733             }
5734         }
5735         // Loop through checkboxes to assign click handlers
5736         for(j=0; j<checkboxOptions.length; j++) {
5737             checkboxEl = YAHOO.util.Dom.get(oSelf.id + "-editor-checkbox" + j);
5738             YAHOO.util.Event.addListener(checkboxEl, "click", function(){
5739                 var aNewValues = [];
5740                 for(var m=0; m<aCheckboxEls.length; m++) {
5741                     if(aCheckboxEls[m].checked) {
5742                         aNewValues.push(aCheckboxEls[m].value);
5743                     }
5744                 }
5745                 oSelf._oCellEditor.value = aNewValues;
5746                 oSelf.fireEvent("editorUpdateEvent",{editor:oSelf._oCellEditor});
5747             });
5748         }
5749     }
5753  * Enables Date Editor.
5755  * @method editDate
5756  */
5757 YAHOO.widget.DataTable.editDate = function(oEditor, oSelf) {
5758     var elCell = oEditor.cell;
5759     var oRecord = oEditor.record;
5760     var oColumn = oEditor.column;
5761     var elContainer = oEditor.container;
5762     var value = oRecord.getData(oColumn.key);
5764     // Calendar widget
5765     if(YAHOO.widget.Calendar) {
5766         var selectedValue = (value.getMonth()+1)+"/"+value.getDate()+"/"+value.getFullYear();
5767         var calContainer = elContainer.appendChild(document.createElement("div"));
5768         calContainer.id = "yui-dt-" + oSelf._nIndex + "-col" + oColumn.getKeyIndex() + "-dateContainer";
5769         var calendar =
5770                 new YAHOO.widget.Calendar("yui-dt-" + oSelf._nIndex + "-col" + oColumn.getKeyIndex() + "-date",
5771                 calContainer.id,
5772                 {selected:selectedValue, pagedate:value});
5773         calendar.render();
5774         calContainer.style.cssFloat = "none";
5776         //var calFloatClearer = elContainer.appendChild(document.createElement("br"));
5777         //calFloatClearer.style.clear = "both";
5778         
5779         calendar.selectEvent.subscribe(function(type, args, obj) {
5780             oSelf._oCellEditor.value = new Date(args[0][0][0], args[0][0][1]-1, args[0][0][2]);
5781         });
5782         oSelf.fireEvent("editorUpdateEvent",{editor:oSelf._oCellEditor});
5783     }
5784     else {
5785         //TODO;
5786     }
5790  * Enables SELECT Editor.
5792  * @method editDropdown
5793  */
5794 YAHOO.widget.DataTable.editDropdown = function(oEditor, oSelf) {
5795     var elCell = oEditor.cell;
5796     var oRecord = oEditor.record;
5797     var oColumn = oEditor.column;
5798     var elContainer = oEditor.container;
5799     var value = oRecord.getData(oColumn.key);
5801     // Textbox
5802     var elDropdown = elContainer.appendChild(document.createElement("select"));
5803     var dropdownOptions = (oColumn.editorOptions && YAHOO.lang.isArray(oColumn.editorOptions.dropdownOptions)) ?
5804             oColumn.editorOptions.dropdownOptions : [];
5805     for(var j=0; j<dropdownOptions.length; j++) {
5806         var dropdownOption = dropdownOptions[j];
5807         var elOption = document.createElement("option");
5808         elOption.value = (YAHOO.lang.isValue(dropdownOption.value)) ?
5809                 dropdownOption.value : dropdownOption;
5810         elOption.innerHTML = (YAHOO.lang.isValue(dropdownOption.text)) ?
5811                 dropdownOption.text : dropdownOption;
5812         elOption = elDropdown.appendChild(elOption);
5813         if(value === elDropdown.options[j].value) {
5814             elDropdown.options[j].selected = true;
5815         }
5816     }
5817     
5818     // Set up a listener on each check box to track the input value
5819     YAHOO.util.Event.addListener(elDropdown, "change",
5820         function(){
5821             oSelf._oCellEditor.value = elDropdown[elDropdown.selectedIndex].value;
5822             oSelf.fireEvent("editorUpdateEvent",{editor:oSelf._oCellEditor});
5823     });
5824             
5825     // Focus the dropdown
5826     oSelf._focusEl(elDropdown);
5830  * Enables INPUT TYPE=RADIO Editor.
5832  * @method editRadio
5833  */
5834 YAHOO.widget.DataTable.editRadio = function(oEditor, oSelf) {
5835     var elCell = oEditor.cell;
5836     var oRecord = oEditor.record;
5837     var oColumn = oEditor.column;
5838     var elContainer = oEditor.container;
5839     var value = oRecord.getData(oColumn.key);
5841     // Radios
5842     if(oColumn.editorOptions && YAHOO.lang.isArray(oColumn.editorOptions.radioOptions)) {
5843         var radioOptions = oColumn.editorOptions.radioOptions;
5844         var radioValue, radioId, elLabel, j;
5845         // First create the radio buttons in an IE-friendly way
5846         for(j=0; j<radioOptions.length; j++) {
5847             radioValue = YAHOO.lang.isValue(radioOptions[j].label) ?
5848                     radioOptions[j].label : radioOptions[j];
5849             radioId =  oSelf.id + "-editor-radio" + j;
5850             elContainer.innerHTML += "<input type=\"radio\"" +
5851                     " name=\"" + oSelf.id + "-editor-radio\"" +
5852                     " value=\"" + radioValue + "\"" +
5853                     " id=\"" +  radioId + "\">";
5854             // Then create the labels in an IE-friendly way
5855             elLabel = elContainer.appendChild(document.createElement("label"));
5856             elLabel.htmlFor = radioId;
5857             elLabel.innerHTML = radioValue;
5858         }
5859         // Then check one, and assign click handlers
5860         for(j=0; j<radioOptions.length; j++) {
5861             var radioEl = YAHOO.util.Dom.get(oSelf.id + "-editor-radio" + j);
5862             if(value === radioEl.value) {
5863                 radioEl.checked = true;
5864                 oSelf._focusEl(radioEl);
5865             }
5866             YAHOO.util.Event.addListener(radioEl, "click",
5867                 function(){
5868                     oSelf._oCellEditor.value = this.value;
5869                     oSelf.fireEvent("editorUpdateEvent",{editor:oSelf._oCellEditor});
5870             });
5871         }
5872     }
5876  * Enables TEXTAREA Editor.
5878  * @method editTextarea
5879  */
5880 YAHOO.widget.DataTable.editTextarea = function(oEditor, oSelf) {
5881    var elCell = oEditor.cell;
5882    var oRecord = oEditor.record;
5883    var oColumn = oEditor.column;
5884    var elContainer = oEditor.container;
5885    var value = oRecord.getData(oColumn.key);
5887     // Textarea
5888     var elTextarea = elContainer.appendChild(document.createElement("textarea"));
5889     elTextarea.style.width = elCell.offsetWidth + "px"; //(parseInt(elCell.offsetWidth,10)) + "px";
5890     elTextarea.style.height = "3em"; //(parseInt(elCell.offsetHeight,10)) + "px";
5891     elTextarea.value = YAHOO.lang.isValue(value) ? value : "";
5892     
5893     // Set up a listener on each check box to track the input value
5894     YAHOO.util.Event.addListener(elTextarea, "keyup", function(){
5895         //TODO: set on a timeout
5896         oSelf._oCellEditor.value = elTextarea.value;
5897         oSelf.fireEvent("editorUpdateEvent",{editor:oSelf._oCellEditor});
5898     });
5899     
5900     // Select the text
5901     elTextarea.focus();
5902     elTextarea.select();
5906  * Enables INPUT TYPE=TEXT Editor.
5908  * @method editTextbox
5909  */
5910 YAHOO.widget.DataTable.editTextbox = function(oEditor, oSelf) {
5911    var elCell = oEditor.cell;
5912    var oRecord = oEditor.record;
5913    var oColumn = oEditor.column;
5914    var elContainer = oEditor.container;
5915    var value = YAHOO.lang.isValue(oRecord.getData(oColumn.key)) ? oRecord.getData(oColumn.key) : "";
5917     // Textbox
5918     var elTextbox = elContainer.appendChild(document.createElement("input"));
5919     elTextbox.type = "text";
5920     elTextbox.style.width = elCell.offsetWidth + "px"; //(parseInt(elCell.offsetWidth,10)) + "px";
5921     //elTextbox.style.height = "1em"; //(parseInt(elCell.offsetHeight,10)) + "px";
5922     elTextbox.value = value;
5924     // Set up a listener on each textbox to track the input value
5925     YAHOO.util.Event.addListener(elTextbox, "keyup", function(){
5926         //TODO: set on a timeout
5927         oSelf._oCellEditor.value = elTextbox.value;
5928         oSelf.fireEvent("editorUpdateEvent",{editor:oSelf._oCellEditor});
5929     });
5931     // Select the text
5932     elTextbox.focus();
5933     elTextbox.select();
5937  * Validates Editor input value to type Number, doing type conversion as
5938  * necessary. A valid Number value is return, else the previous value is returned
5939  * if input value does not validate.
5940  * 
5942  * @method validateNumber
5943  * @static
5945 YAHOO.widget.DataTable.validateNumber = function(oData) {
5946     //Convert to number
5947     var number = oData * 1;
5949     // Validate
5950     if(YAHOO.lang.isNumber(number)) {
5951         return number;
5952     }
5953     else {
5954         return null;
5955     }
5995 // ABSTRACT METHODS
5998  * Overridable method gives implementers a hook to access data before
5999  * it gets added to RecordSet and rendered to the TBODY.
6001  * @method doBeforeLoadData
6002  * @param sRequest {String} Original request.
6003  * @param oResponse {Object} Response object.
6004  * @return {Boolean} Return true to continue loading data into RecordSet and
6005  * updating DataTable with new Records, false to cancel.
6006  */
6007 YAHOO.widget.DataTable.prototype.doBeforeLoadData = function(sRequest, oResponse) {
6008     return true;
6073 /////////////////////////////////////////////////////////////////////////////
6075 // Public Custom Event Handlers
6077 /////////////////////////////////////////////////////////////////////////////
6080  * Overridable custom event handler to sort Column.
6082  * @method onEventSortColumn
6083  * @param oArgs.event {HTMLEvent} Event object.
6084  * @param oArgs.target {HTMLElement} Target element.
6085  */
6086 YAHOO.widget.DataTable.prototype.onEventSortColumn = function(oArgs) {
6087 //TODO: support nested header column sorting
6088     var evt = oArgs.event;
6089     var target = oArgs.target;
6090     YAHOO.util.Event.stopEvent(evt);
6091     
6092     var el = this.getThEl(target) || this.getTdEl(target);
6093     if(el && YAHOO.lang.isNumber(el.yuiColumnId)) {
6094         this.sortColumn(this._oColumnSet.getColumn(el.yuiColumnId));
6095     }
6096     else {
6097     }
6101  * Overridable custom event handler to manage selection according to desktop paradigm.
6103  * @method onEventSelectRow
6104  * @param oArgs.event {HTMLEvent} Event object.
6105  * @param oArgs.target {HTMLElement} Target element.
6106  */
6107 YAHOO.widget.DataTable.prototype.onEventSelectRow = function(oArgs) {
6108     var sMode = this.get("selectionMode");
6109     if ((sMode == "singlecell") || (sMode == "cellblock") || (sMode == "cellrange")) {
6110         return;
6111     }
6113     var evt = oArgs.event;
6114     var elTarget = oArgs.target;
6116     var bSHIFT = evt.shiftKey;
6117     var bCTRL = evt.ctrlKey;
6118     var i, nAnchorTrIndex;
6120     // Validate target row
6121     var elTargetRow = this.getTrEl(elTarget);
6122     if(elTargetRow) {
6123         var allRows = this._elTbody.rows;
6124         var nTargetTrIndex = elTargetRow.sectionRowIndex;
6125         var elAnchorRow = YAHOO.util.Dom.get(this._sSelectionAnchorId);
6126         
6127         // Both SHIFT and CTRL
6128         if((sMode != "single") && bSHIFT && bCTRL) {
6129             // Validate anchor row
6130             if(elAnchorRow && YAHOO.lang.isNumber(elAnchorRow.sectionRowIndex)) {
6131                 nAnchorTrIndex = elAnchorRow.sectionRowIndex;
6132                 if(this.isSelected(elAnchorRow)) {
6133                     // Select all rows between anchor row and target row, including target row
6134                     if(nAnchorTrIndex < nTargetTrIndex) {
6135                         for(i=nAnchorTrIndex+1; i<=nTargetTrIndex; i++) {
6136                             if(!this.isSelected(allRows[i])) {
6137                                 this.selectRow(allRows[i]);
6138                             }
6139                         }
6140                     }
6141                     // Select all rows between target row and anchor row, including target row
6142                     else {
6143                         for(i=nAnchorTrIndex-1; i>=nTargetTrIndex; i--) {
6144                             if(!this.isSelected(allRows[i])) {
6145                                 this.selectRow(allRows[i]);
6146                             }
6147                         }
6148                     }
6149                 }
6150                 else {
6151                     // Unselect all rows between anchor row and target row
6152                     if(nAnchorTrIndex < nTargetTrIndex) {
6153                         for(i=nAnchorTrIndex+1; i<=nTargetTrIndex-1; i++) {
6154                             if(this.isSelected(allRows[i])) {
6155                                 this.unselectRow(allRows[i]);
6156                             }
6157                         }
6158                     }
6159                     // Unselect all rows between target row and anchor row
6160                     else {
6161                         for(i=nTargetTrIndex+1; i<=nAnchorTrIndex-1; i++) {
6162                             if(this.isSelected(allRows[i])) {
6163                                 this.unselectRow(allRows[i]);
6164                             }
6165                         }
6166                     }
6167                     // Select the target row
6168                     this.selectRow(elTargetRow);
6169                 }
6170             }
6171             // Invalid anchor
6172             else {
6173                 // Set anchor
6174                 this._sSelectionAnchorId = elTargetRow.id;
6176                 // Toggle selection of target
6177                 if(this.isSelected(elTargetRow)) {
6178                     this.unselectRow(elTargetRow);
6179                 }
6180                 else {
6181                     this.selectRow(elTargetRow);
6182                 }
6183             }
6184         }
6185         // Only SHIFT
6186         else if((sMode != "single") && bSHIFT) {
6187             this.unselectAllRows();
6189             // Validate anchor
6190             if(elAnchorRow && YAHOO.lang.isNumber(elAnchorRow.sectionRowIndex)) {
6191                 nAnchorTrIndex = elAnchorRow.sectionRowIndex;
6193                 // Select all rows between anchor row and target row,
6194                 // including the anchor row and target row
6195                 if(nAnchorTrIndex < nTargetTrIndex) {
6196                     for(i=nAnchorTrIndex; i<=nTargetTrIndex; i++) {
6197                         this.selectRow(allRows[i]);
6198                     }
6199                 }
6200                 // Select all rows between target row and anchor row,
6201                 // including the target row and anchor row
6202                 else {
6203                     for(i=nAnchorTrIndex; i>=nTargetTrIndex; i--) {
6204                         this.selectRow(allRows[i]);
6205                     }
6206                 }
6207             }
6208             // Invalid anchor
6209             else {
6210                 // Set anchor
6211                 this._sSelectionAnchorId = elTargetRow.id;
6213                 // Select target row only
6214                 this.selectRow(elTargetRow);
6215             }
6216         }
6217         // Only CTRL
6218         else if((sMode != "single") && bCTRL) {
6219             // Set anchor
6220             this._sSelectionAnchorId = elTargetRow.id;
6222             // Toggle selection of target
6223             if(this.isSelected(elTargetRow)) {
6224                 this.unselectRow(elTargetRow);
6225             }
6226             else {
6227                 this.selectRow(elTargetRow);
6228             }
6229         }
6230         // Neither SHIFT nor CTRL
6231         else if(sMode == "single") {
6232             this.unselectAllRows();
6233             this.selectRow(elTargetRow);
6234         }
6235         // Neither SHIFT nor CTRL
6236         else {
6237             // Set anchor
6238             this._sSelectionAnchorId = elTargetRow.id;
6240             // Select only target
6241             this.unselectAllRows();
6242             this.selectRow(elTargetRow);
6243         }
6244         YAHOO.util.Event.stopEvent(evt);
6246         // Clear any selections that are a byproduct of the click or dblclick
6247         var sel;
6248         if(window.getSelection) {
6249                 sel = window.getSelection();
6250         }
6251         else if(document.getSelection) {
6252                 sel = document.getSelection();
6253         }
6254         else if(document.selection) {
6255                 sel = document.selection;
6256         }
6257         if(sel) {
6258             if(sel.empty) {
6259                 sel.empty();
6260             }
6261             else if (sel.removeAllRanges) {
6262                 sel.removeAllRanges();
6263             }
6264             else if(sel.collapse) {
6265                 sel.collapse();
6266             }
6267         }
6268     }
6269     else {
6270     }
6274  * Overridable custom event handler to select cell.
6276  * @method onEventSelectCell
6277  * @param oArgs.event {HTMLEvent} Event object.
6278  * @param oArgs.target {HTMLElement} Target element.
6279  */
6280 YAHOO.widget.DataTable.prototype.onEventSelectCell = function(oArgs) {
6281     var sMode = this.get("selectionMode");
6282     if ((sMode == "standard") || (sMode == "single")) {
6283         return;
6284     }
6286     var evt = oArgs.event;
6287     var elTarget = oArgs.target;
6289     var bSHIFT = evt.shiftKey;
6290     var bCTRL = evt.ctrlKey;
6291     var i, j, nAnchorTrIndex, nAnchorTdIndex, currentRow, startIndex, endIndex;
6292     
6293     var elTargetCell = this.getTdEl(elTarget);
6294     if(elTargetCell) {
6295         var elTargetRow = this.getTrEl(elTargetCell);
6296         var allRows = this._elTbody.rows;
6297         var nTargetTrIndex = elTargetRow.sectionRowIndex;
6298         var nTargetTdIndex = elTarget.yuiCellIndex;
6299         var elAnchorCell = YAHOO.util.Dom.get(this._sSelectionAnchorId);
6301         // Both SHIFT and CTRL
6302         if((sMode != "singlecell") && bSHIFT && bCTRL) {
6303             // Validate anchor
6304             if(elAnchorCell && YAHOO.lang.isNumber(elAnchorCell.yuiCellIndex)) {
6305                 nAnchorTrIndex = elAnchorCell.parentNode.sectionRowIndex;
6306                 nAnchorTdIndex = elAnchorCell.yuiCellIndex;
6307                 
6308                 // Anchor is selected
6309                 if(this.isSelected(elAnchorCell)) {
6310                     // All cells are on the same row
6311                     if(nAnchorTrIndex == nTargetTrIndex) {
6312                         // Select all cells between anchor cell and target cell, including target cell
6313                         if(nAnchorTdIndex < nTargetTdIndex) {
6314                             for(i=nAnchorTdIndex+1; i<=nTargetTdIndex; i++) {
6315                                 this.selectCell(allRows[nTargetTrIndex].cells[i]);
6316                             }
6317                         }
6318                         // Select all cells between target cell and anchor cell, including target cell
6319                         else if(nTargetTdIndex < nAnchorTdIndex) {
6320                             for(i=nTargetTdIndex; i<nAnchorTdIndex; i++) {
6321                                 this.selectCell(allRows[nTargetTrIndex].cells[i]);
6322                             }
6323                         }
6324                     }
6325                     // Anchor row is above target row
6326                     else if(nAnchorTrIndex < nTargetTrIndex) {
6327                         if(sMode == "cellrange") {
6328                             // Select all cells on anchor row from anchor cell to the end of the row
6329                             for(i=nAnchorTdIndex+1; i<allRows[nAnchorTrIndex].cells.length; i++) {
6330                                 this.selectCell(allRows[nAnchorTrIndex].cells[i]);
6331                             }
6332                             
6333                             // Select all cells on all rows between anchor row and target row
6334                             for(i=nAnchorTrIndex+1; i<nTargetTrIndex; i++) {
6335                                 for(j=0; j<allRows[i].cells.length; j++){
6336                                     this.selectCell(allRows[i].cells[j]);
6337                                 }
6338                             }
6340                             // Select all cells on target row from first cell to the target cell
6341                             for(i=0; i<=nTargetTdIndex; i++) {
6342                                 this.selectCell(allRows[nTargetTrIndex].cells[i]);
6343                             }
6344                         }
6345                         else if(sMode == "cellblock") {
6346                             startIndex = Math.min(nAnchorTdIndex, nTargetTdIndex);
6347                             endIndex = Math.max(nAnchorTdIndex, nTargetTdIndex);
6348                             
6349                             // Select all cells from startIndex to endIndex on rows between anchor row and target row
6350                             for(i=nAnchorTrIndex; i<=nTargetTrIndex; i++) {
6351                                 for(j=startIndex; j<=endIndex; j++) {
6352                                     this.selectCell(allRows[i].cells[j]);
6353                                 }
6354                             }
6355                         }
6356                     }
6357                     // Anchor row is below target row
6358                     else {
6359                         if(sMode == "cellrange") {
6360                             // Select all cells on target row from target cell to the end of the row
6361                             for(i=nTargetTdIndex; i<allRows[nTargetTrIndex].cells.length; i++) {
6362                                 this.selectCell(allRows[nTargetTrIndex].cells[i]);
6363                             }
6365                             // Select all cells on all rows between target row and anchor row
6366                             for(i=nTargetTrIndex+1; i<nAnchorTrIndex; i++) {
6367                                 for(j=0; j<allRows[i].cells.length; j++){
6368                                     this.selectCell(allRows[i].cells[j]);
6369                                 }
6370                             }
6372                             // Select all cells on anchor row from first cell to the anchor cell
6373                             for(i=0; i<nAnchorTdIndex; i++) {
6374                                 this.selectCell(allRows[nAnchorTrIndex].cells[i]);
6375                             }
6376                         }
6377                         else if(sMode == "cellblock") {
6378                             startIndex = Math.min(nAnchorTdIndex, nTargetTdIndex);
6379                             endIndex = Math.max(nAnchorTdIndex, nTargetTdIndex);
6381                             // Select all cells from startIndex to endIndex on rows between target row and anchor row
6382                             for(i=nAnchorTrIndex; i>=nTargetTrIndex; i--) {
6383                                 for(j=endIndex; j>=startIndex; j--) {
6384                                     this.selectCell(allRows[i].cells[j]);
6385                                 }
6386                             }
6387                         }
6388                     }
6389                 }
6390                 // Anchor cell is unselected
6391                 else {
6392                     // All cells are on the same row
6393                     if(nAnchorTrIndex == nTargetTrIndex) {
6394                         // Unselect all cells between anchor cell and target cell
6395                         if(nAnchorTdIndex < nTargetTdIndex) {
6396                             for(i=nAnchorTdIndex+1; i<nTargetTdIndex; i++) {
6397                                 this.unselectCell(allRows[nTargetTrIndex].cells[i]);
6398                             }
6399                         }
6400                         // Select all cells between target cell and anchor cell
6401                         else if(nTargetTdIndex < nAnchorTdIndex) {
6402                             for(i=nTargetTdIndex+1; i<nAnchorTdIndex; i++) {
6403                                 this.unselectCell(allRows[nTargetTrIndex].cells[i]);
6404                             }
6405                         }
6406                     }
6407                     // Anchor row is above target row
6408                     if(nAnchorTrIndex < nTargetTrIndex) {
6409                         // Unselect all cells from anchor cell to target cell
6410                         for(i=nAnchorTrIndex; i<=nTargetTrIndex; i++) {
6411                             currentRow = allRows[i];
6412                             for(j=0; j<currentRow.cells.length; j++) {
6413                                 // This is the anchor row, only unselect cells after the anchor cell
6414                                 if(currentRow.sectionRowIndex == nAnchorTrIndex) {
6415                                     if(j>nAnchorTdIndex) {
6416                                         this.unselectCell(currentRow.cells[j]);
6417                                     }
6418                                 }
6419                                 // This is the target row, only unelect cells before the target cell
6420                                 else if(currentRow.sectionRowIndex == nTargetTrIndex) {
6421                                     if(j<nTargetTdIndex) {
6422                                         this.unselectCell(currentRow.cells[j]);
6423                                     }
6424                                 }
6425                                 // Unselect all cells on this row
6426                                 else {
6427                                     this.unselectCell(currentRow.cells[j]);
6428                                 }
6429                             }
6430                         }
6431                     }
6432                     // Anchor row is below target row
6433                     else {
6434                         // Unselect all cells from target cell to anchor cell
6435                         for(i=nTargetTrIndex; i<=nAnchorTrIndex; i++) {
6436                             currentRow = allRows[i];
6437                             for(j=0; j<currentRow.cells.length; j++) {
6438                                 // This is the target row, only unselect cells after the target cell
6439                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
6440                                     if(j>nTargetTdIndex) {
6441                                         this.unselectCell(currentRow.cells[j]);
6442                                     }
6443                                 }
6444                                 // This is the anchor row, only unselect cells before the anchor cell
6445                                 else if(currentRow.sectionRowIndex == nAnchorTrIndex) {
6446                                     if(j<nAnchorTdIndex) {
6447                                         this.unselectCell(currentRow.cells[j]);
6448                                     }
6449                                 }
6450                                 // Unselect all cells on this row
6451                                 else {
6452                                     this.unselectCell(currentRow.cells[j]);
6453                                 }
6454                             }
6455                         }
6456                     }
6458                     // Select the target cell
6459                     this.selectCell(elTargetCell);
6460                 }
6461             }
6462             // Invalid anchor
6463             else {
6464                 // Set anchor
6465                 this._sSelectionAnchorId = elTargetCell.id;
6467                 // Toggle selection of target
6468                 if(this.isSelected(elTargetCell)) {
6469                     this.unselectCell(elTargetCell);
6470                 }
6471                 else {
6472                     this.selectCell(elTargetCell);
6473                 }
6474             }
6475         }
6476         // Only SHIFT
6477         else if((sMode != "singlecell") && bSHIFT) {
6478             this.unselectAllCells();
6480             // Validate anchor
6481             if(elAnchorCell && YAHOO.lang.isNumber(elAnchorCell.yuiCellIndex)) {
6482                 nAnchorTrIndex = elAnchorCell.parentNode.sectionRowIndex;
6483                 nAnchorTdIndex = elAnchorCell.yuiCellIndex;
6484                 
6485                 // All cells are on the same row
6486                 if(nAnchorTrIndex == nTargetTrIndex) {
6487                     // Select all cells between anchor cell and target cell,
6488                     // including the anchor cell and target cell
6489                     if(nAnchorTdIndex < nTargetTdIndex) {
6490                         for(i=nAnchorTdIndex; i<=nTargetTdIndex; i++) {
6491                             this.selectCell(allRows[nTargetTrIndex].cells[i]);
6492                         }
6493                     }
6494                     // Select all cells between target cell and anchor cell
6495                     // including the target cell and anchor cell
6496                     else if(nTargetTdIndex < nAnchorTdIndex) {
6497                         for(i=nTargetTdIndex; i<=nAnchorTdIndex; i++) {
6498                             this.selectCell(allRows[nTargetTrIndex].cells[i]);
6499                         }
6500                     }
6501                 }
6502                 // Anchor row is above target row
6503                 else if(nAnchorTrIndex < nTargetTrIndex) {
6504                     if(sMode == "cellrange") {
6505                         // Select all cells from anchor cell to target cell
6506                         // including the anchor cell and target cell
6507                         for(i=nAnchorTrIndex; i<=nTargetTrIndex; i++) {
6508                             currentRow = allRows[i];
6509                             for(j=0; j<currentRow.cells.length; j++) {
6510                                 // This is the anchor row, only select the anchor cell and after
6511                                 if(currentRow.sectionRowIndex == nAnchorTrIndex) {
6512                                     if(j>=nAnchorTdIndex) {
6513                                         this.selectCell(currentRow.cells[j]);
6514                                     }
6515                                 }
6516                                 // This is the target row, only select the target cell and before
6517                                 else if(currentRow.sectionRowIndex == nTargetTrIndex) {
6518                                     if(j<=nTargetTdIndex) {
6519                                         this.selectCell(currentRow.cells[j]);
6520                                     }
6521                                 }
6522                                 // Select all cells on this row
6523                                 else {
6524                                     this.selectCell(currentRow.cells[j]);
6525                                 }
6526                             }
6527                         }
6528                     }
6529                     else if(sMode == "cellblock") {
6530                         // Select the cellblock from anchor cell to target cell
6531                         // including the anchor cell and the target cell
6532                         startIndex = Math.min(nAnchorTdIndex, nTargetTdIndex);
6533                         endIndex = Math.max(nAnchorTdIndex, nTargetTdIndex);
6535                         for(i=nAnchorTrIndex; i<=nTargetTrIndex; i++) {
6536                             for(j=startIndex; j<=endIndex; j++) {
6537                                 this.selectCell(allRows[i].cells[j]);
6538                             }
6539                         }
6540                         
6541                         this._sLastSelectedId = allRows[nTargetTrIndex].cells[nTargetTdIndex].id;
6542                     }
6543                 }
6544                 // Anchor row is below target row
6545                 else {
6546                     if(sMode == "cellrange") {
6547                         // Select all cells from target cell to anchor cell,
6548                         // including the target cell and anchor cell
6549                         for(i=nTargetTrIndex; i<=nAnchorTrIndex; i++) {
6550                             currentRow = allRows[i];
6551                             for(j=0; j<currentRow.cells.length; j++) {
6552                                 // This is the target row, only select the target cell and after
6553                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
6554                                     if(j>=nTargetTdIndex) {
6555                                         this.selectCell(currentRow.cells[j]);
6556                                     }
6557                                 }
6558                                 // This is the anchor row, only select the anchor cell and before
6559                                 else if(currentRow.sectionRowIndex == nAnchorTrIndex) {
6560                                     if(j<=nAnchorTdIndex) {
6561                                         this.selectCell(currentRow.cells[j]);
6562                                     }
6563                                 }
6564                                 // Select all cells on this row
6565                                 else {
6566                                     this.selectCell(currentRow.cells[j]);
6567                                 }
6568                             }
6569                         }
6570                     }
6571                     else if(sMode == "cellblock") {
6572                         // Select the cellblock from target cell to anchor cell
6573                         // including the target cell and the anchor cell
6574                         startIndex = Math.min(nAnchorTdIndex, nTargetTdIndex);
6575                         endIndex = Math.max(nAnchorTdIndex, nTargetTdIndex);
6577                         for(i=nTargetTrIndex; i<=nAnchorTrIndex; i++) {
6578                             for(j=startIndex; j<=endIndex; j++) {
6579                                 this.selectCell(allRows[i].cells[j]);
6580                             }
6581                         }
6582                         
6583                         this._sLastSelectedId = allRows[nTargetTrIndex].cells[nTargetTdIndex].id;
6584                     }
6585                 }
6586             }
6587             // Invalid anchor
6588             else {
6589                 // Set anchor
6590                 this._sSelectionAnchorId = elTargetCell.id;
6592                 // Select target only
6593                 this.selectCell(elTargetCell);
6594             }
6595         }
6596         // Only CTRL
6597         else if((sMode != "singlecell") && bCTRL) {
6598             // Set anchor
6599             this._sSelectionAnchorId = elTargetCell.id;
6601             // Toggle selection of target
6602             if(this.isSelected(elTargetCell)) {
6603                 this.unselectCell(elTargetCell);
6604             }
6605             else {
6606                 this.selectCell(elTargetCell);
6607             }
6608         }
6609         // Neither SHIFT nor CTRL, or multi-selection has been disabled
6610         else {
6611             // Set anchor
6612             this._sSelectionAnchorId = elTargetCell.id;
6614             // Select only target
6615             this.unselectAllCells();
6616             this.selectCell(elTargetCell);
6617         }
6619         YAHOO.util.Event.stopEvent(evt);
6621         // Clear any selections that are a byproduct of the click or dblclick
6622         var sel;
6623         if(window.getSelection) {
6624                 sel = window.getSelection();
6625         }
6626         else if(document.getSelection) {
6627                 sel = document.getSelection();
6628         }
6629         else if(document.selection) {
6630                 sel = document.selection;
6631         }
6632         if(sel) {
6633             if(sel.empty) {
6634                 sel.empty();
6635             }
6636             else if (sel.removeAllRanges) {
6637                 sel.removeAllRanges();
6638             }
6639             else if(sel.collapse) {
6640                 sel.collapse();
6641             }
6642         }
6643     }
6644     else {
6645     }
6659  * Overridable custom event handler to highlight row.
6661  * @method onEventHighlightRow
6662  * @param oArgs.event {HTMLEvent} Event object.
6663  * @param oArgs.target {HTMLElement} Target element.
6664  */
6665 YAHOO.widget.DataTable.prototype.onEventHighlightRow = function(oArgs) {
6666     var evt = oArgs.event;
6667     var elTarget = oArgs.target;
6668     this.highlightRow(elTarget);
6672  * Overridable custom event handler to unhighlight row.
6674  * @method onEventUnhighlightRow
6675  * @param oArgs.event {HTMLEvent} Event object.
6676  * @param oArgs.target {HTMLElement} Target element.
6677  */
6678 YAHOO.widget.DataTable.prototype.onEventUnhighlightRow = function(oArgs) {
6679     var evt = oArgs.event;
6680     var elTarget = oArgs.target;
6681     this.unhighlightRow(elTarget);
6685  * Overridable custom event handler to highlight cell.
6687  * @method onEventHighlightCell
6688  * @param oArgs.event {HTMLEvent} Event object.
6689  * @param oArgs.target {HTMLElement} Target element.
6690  */
6691 YAHOO.widget.DataTable.prototype.onEventHighlightCell = function(oArgs) {
6692     var evt = oArgs.event;
6693     var elTarget = oArgs.target;
6694     this.highlightCell(elTarget);
6698  * Overridable custom event handler to unhighlight cell.
6700  * @method onEventUnhighlightCell
6701  * @param oArgs.event {HTMLEvent} Event object.
6702  * @param oArgs.target {HTMLElement} Target element.
6703  */
6704 YAHOO.widget.DataTable.prototype.onEventUnhighlightCell = function(oArgs) {
6705     var evt = oArgs.event;
6706     var elTarget = oArgs.target;
6707     this.unhighlightCell(elTarget);
6711  * Overridable custom event handler to format cell.
6713  * @method onEventFormatCell
6714  * @param oArgs.event {HTMLEvent} Event object.
6715  * @param oArgs.target {HTMLElement} Target element.
6716  */
6717 YAHOO.widget.DataTable.prototype.onEventFormatCell = function(oArgs) {
6718     var evt = oArgs.event;
6719     var target = oArgs.target;
6720     var elTag = target.tagName.toLowerCase();
6722     var elCell = this.getTdEl(target);
6723     if(elCell && YAHOO.lang.isNumber(elCell.yuiColumnId)) {
6724         var oColumn = this._oColumnSet.getColumn(elCell.yuiColumnId);
6725         this.formatCell(elCell, this.getRecord(elCell), oColumn);
6726     }
6727     else {
6728     }
6732  * Overridable custom event handler to edit cell.
6734  * @method onEventShowCellEditor
6735  * @param oArgs.event {HTMLEvent} Event object.
6736  * @param oArgs.target {HTMLElement} Target element.
6737  */
6738 YAHOO.widget.DataTable.prototype.onEventShowCellEditor = function(oArgs) {
6739     var evt = oArgs.event;
6740     var target = oArgs.target;
6741     var elTag = target.tagName.toLowerCase();
6743     var elCell = this.getTdEl(target);
6744     if(elCell) {
6745         this.showCellEditor(elCell);
6746     }
6747     else {
6748     }
6750 // Backward compatibility
6751 YAHOO.widget.DataTable.prototype.onEventEditCell = function(oArgs) {
6752     this.onEventShowCellEditor(oArgs);
6756  * Overridable custom event handler to save Cell Editor input.
6758  * @method onEventSaveCellEditor
6759  * @param oArgs.editor {Object} Cell Editor object literal.
6760  */
6761 YAHOO.widget.DataTable.prototype.onEventSaveCellEditor = function(oArgs) {
6762     this.saveCellEditor();
6766  * Callback function for creating a progressively enhanced DataTable first
6767  * receives data from DataSource and populates the RecordSet, then initializes
6768  * DOM elements.
6770  * @method _onDataReturnEnhanceTable
6771  * @param sRequest {String} Original request.
6772  * @param oResponse {Object} Response object.
6773  * @param bError {Boolean} (optional) True if there was a data error.
6774  * @private
6775  */
6776 YAHOO.widget.DataTable.prototype._onDataReturnEnhanceTable = function(sRequest, oResponse) {
6777     // Pass data through abstract method for any transformations
6778     var ok = this.doBeforeLoadData(sRequest, oResponse);
6780     // Data ok to populate
6781     if(ok && oResponse && !oResponse.error && YAHOO.lang.isArray(oResponse.results)) {
6782         // Update RecordSet
6783         this._oRecordSet.addRecords(oResponse.results);
6785         // Initialize DOM elements
6786         this._initTableEl();
6787         if(!this._elTable || !this._elThead || !this._elTbody) {
6788             return;
6789         }
6791         // Call Element's constructor after DOM elements are created
6792         // but *before* UI is updated with data
6793         YAHOO.widget.DataTable.superclass.constructor.call(this, this._elContainer, this._oConfigs);
6795         //HACK: Set the Paginator values
6796         if(this._oConfigs.paginator) {
6797             this.updatePaginator(this._oConfigs.paginator);
6798         }
6800         // Update the UI
6801         this.refreshView();
6802     }
6803     // Error
6804     else if(ok && oResponse.error) {
6805         this.showTableMessage(YAHOO.widget.DataTable.MSG_ERROR, YAHOO.widget.DataTable.CLASS_ERROR);
6806     }
6807     // Empty
6808     else if(ok){
6809         this.showTableMessage(YAHOO.widget.DataTable.MSG_EMPTY, YAHOO.widget.DataTable.CLASS_EMPTY);
6810     }
6812     
6814  * Callback function receives data from DataSource and populates an entire
6815  * DataTable with Records and TR elements, clearing previous Records, if any.
6817  * @method onDataReturnInitializeTable
6818  * @param sRequest {String} Original request.
6819  * @param oResponse {Object} Response object.
6820  * @param bError {Boolean} (optional) True if there was a data error.
6821  */
6822 YAHOO.widget.DataTable.prototype.onDataReturnInitializeTable = function(sRequest, oResponse) {
6823     this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse});
6825     // Pass data through abstract method for any transformations
6826     var ok = this.doBeforeLoadData(sRequest, oResponse);
6828     // Data ok to populate
6829     if(ok && oResponse && !oResponse.error && YAHOO.lang.isArray(oResponse.results)) {
6830         this.initializeTable(oResponse.results);
6831     }
6832     // Error
6833     else if(ok && oResponse.error) {
6834         this.showTableMessage(YAHOO.widget.DataTable.MSG_ERROR, YAHOO.widget.DataTable.CLASS_ERROR);
6835     }
6836     // Empty
6837     else if(ok){
6838         this.showTableMessage(YAHOO.widget.DataTable.MSG_EMPTY, YAHOO.widget.DataTable.CLASS_EMPTY);
6839     }
6841 // Backward compatibility
6842 YAHOO.widget.DataTable.prototype.onDataReturnReplaceRows = function(sRequest, oResponse) {
6843     this.onDataReturnInitializeTable(sRequest, oResponse);
6847  * Callback function receives data from DataSource and appends to an existing
6848  * DataTable new Records and, if applicable, creates or updates
6849  * corresponding TR elements.
6851  * @method onDataReturnAppendRows
6852  * @param sRequest {String} Original request.
6853  * @param oResponse {Object} Response object.
6854  * @param bError {Boolean} (optional) True if there was a data error.
6855  */
6856 YAHOO.widget.DataTable.prototype.onDataReturnAppendRows = function(sRequest, oResponse) {
6857     this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse});
6858     
6859     // Pass data through abstract method for any transformations
6860     var ok = this.doBeforeLoadData(sRequest, oResponse);
6861     
6862     // Data ok to append
6863     if(ok && oResponse && !oResponse.error && YAHOO.lang.isArray(oResponse.results)) {
6864         this.addRows(oResponse.results);
6865     }
6866     // Error
6867     else if(ok && oResponse.error) {
6868         this.showTableMessage(YAHOO.widget.DataTable.MSG_ERROR, YAHOO.widget.DataTable.CLASS_ERROR);
6869     }
6873  * Callback function receives data from DataSource and inserts into top of an
6874  * existing DataTable new Records and, if applicable, creates or updates
6875  * corresponding TR elements.
6877  * @method onDataReturnInsertRows
6878  * @param sRequest {String} Original request.
6879  * @param oResponse {Object} Response object.
6880  * @param bError {Boolean} (optional) True if there was a data error.
6881  */
6882 YAHOO.widget.DataTable.prototype.onDataReturnInsertRows = function(sRequest, oResponse) {
6883     this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse});
6884     
6885     // Pass data through abstract method for any transformations
6886     var ok = this.doBeforeLoadData(sRequest, oResponse);
6887     
6888     // Data ok to append
6889     if(ok && oResponse && !oResponse.error && YAHOO.lang.isArray(oResponse.results)) {
6890         this.addRows(oResponse.results, 0);
6891     }
6892     // Error
6893     else if(ok && oResponse.error) {
6894         this.showTableMessage(YAHOO.widget.DataTable.MSG_ERROR, YAHOO.widget.DataTable.CLASS_ERROR);
6895     }
6932     /////////////////////////////////////////////////////////////////////////////
6933     //
6934     // Custom Events
6935     //
6936     /////////////////////////////////////////////////////////////////////////////
6938     /**
6939      * Fired when the DataTable instance's initialization is complete.
6940      *
6941      * @event initEvent
6942      */
6944     /**
6945      * Fired when the DataTable's view is refreshed.
6946      *
6947      * @event refreshEvent
6948      */
6950     /**
6951      * Fired when data is returned from DataSource.
6952      *
6953      * @event dataReturnEvent
6954      * @param oArgs.request {String} Original request.
6955      * @param oArgs.response {Object} Response object.
6956      */
6958     /**
6959      * Fired when the DataTable has a focus.
6960      *
6961      * @event tableFocusEvent
6962      */
6964     /**
6965      * Fired when the DataTable has a blur.
6966      *
6967      * @event tableBlurEvent
6968      */
6970     /**
6971      * Fired when the DataTable has a mouseover.
6972      *
6973      * @event tableMouseoverEvent
6974      * @param oArgs.event {HTMLEvent} The event object.
6975      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
6976      *
6977      */
6979     /**
6980      * Fired when the DataTable has a mouseout.
6981      *
6982      * @event tableMouseoutEvent
6983      * @param oArgs.event {HTMLEvent} The event object.
6984      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
6985      *
6986      */
6988     /**
6989      * Fired when the DataTable has a mousedown.
6990      *
6991      * @event tableMousedownEvent
6992      * @param oArgs.event {HTMLEvent} The event object.
6993      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
6994      *
6995      */
6997     /**
6998      * Fired when the DataTable has a click.
6999      *
7000      * @event tableClickEvent
7001      * @param oArgs.event {HTMLEvent} The event object.
7002      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
7003      *
7004      */
7006     /**
7007      * Fired when the DataTable has a dblclick.
7008      *
7009      * @event tableDblclickEvent
7010      * @param oArgs.event {HTMLEvent} The event object.
7011      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
7012      *
7013      */
7015     /**
7016      * Fired when a fixed scrolling DataTable has a scroll.
7017      *
7018      * @event tableScrollEvent
7019      * @param oArgs.event {HTMLEvent} The event object.
7020      * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
7021      * or the DataTable's TBODY element (everyone else).
7022      *
7023      */
7025     /**
7026      * Fired when a message is shown in the DataTable's message element.
7027      *
7028      * @event tableMsgShowEvent
7029      * @param oArgs.html {String} The HTML displayed.
7030      * @param oArgs.className {String} The className assigned.
7031      *
7032      */
7034     /**
7035      * Fired when the DataTable's message element is hidden.
7036      *
7037      * @event tableMsgHideEvent
7038      */
7040     /**
7041      * Fired when a header row has a mouseover.
7042      *
7043      * @event headerRowMouseoverEvent
7044      * @param oArgs.event {HTMLEvent} The event object.
7045      * @param oArgs.target {HTMLElement} The TR element.
7046      */
7048     /**
7049      * Fired when a header row has a mouseout.
7050      *
7051      * @event headerRowMouseoutEvent
7052      * @param oArgs.event {HTMLEvent} The event object.
7053      * @param oArgs.target {HTMLElement} The TR element.
7054      */
7056     /**
7057      * Fired when a header row has a mousedown.
7058      *
7059      * @event headerRowMousedownEvent
7060      * @param oArgs.event {HTMLEvent} The event object.
7061      * @param oArgs.target {HTMLElement} The TR element.
7062      */
7064     /**
7065      * Fired when a header row has a click.
7066      *
7067      * @event headerRowClickEvent
7068      * @param oArgs.event {HTMLEvent} The event object.
7069      * @param oArgs.target {HTMLElement} The TR element.
7070      */
7072     /**
7073      * Fired when a header row has a dblclick.
7074      *
7075      * @event headerRowDblclickEvent
7076      * @param oArgs.event {HTMLEvent} The event object.
7077      * @param oArgs.target {HTMLElement} The TR element.
7078      */
7080     /**
7081      * Fired when a header cell has a mouseover.
7082      *
7083      * @event headerCellMouseoverEvent
7084      * @param oArgs.event {HTMLEvent} The event object.
7085      * @param oArgs.target {HTMLElement} The TH element.
7086      *
7087      */
7089     /**
7090      * Fired when a header cell has a mouseout.
7091      *
7092      * @event headerCellMouseoutEvent
7093      * @param oArgs.event {HTMLEvent} The event object.
7094      * @param oArgs.target {HTMLElement} The TH element.
7095      *
7096      */
7098     /**
7099      * Fired when a header cell has a mousedown.
7100      *
7101      * @event headerCellMousedownEvent
7102      * @param oArgs.event {HTMLEvent} The event object.
7103      * @param oArgs.target {HTMLElement} The TH element.
7104      */
7106     /**
7107      * Fired when a header cell has a click.
7108      *
7109      * @event headerCellClickEvent
7110      * @param oArgs.event {HTMLEvent} The event object.
7111      * @param oArgs.target {HTMLElement} The TH element.
7112      */
7114     /**
7115      * Fired when a header cell has a dblclick.
7116      *
7117      * @event headerCellDblclickEvent
7118      * @param oArgs.event {HTMLEvent} The event object.
7119      * @param oArgs.target {HTMLElement} The TH element.
7120      */
7122     /**
7123      * Fired when a header label has a mouseover.
7124      *
7125      * @event headerLabelMouseoverEvent
7126      * @param oArgs.event {HTMLEvent} The event object.
7127      * @param oArgs.target {HTMLElement} The SPAN element.
7128      *
7129      */
7131     /**
7132      * Fired when a header label has a mouseout.
7133      *
7134      * @event headerLabelMouseoutEvent
7135      * @param oArgs.event {HTMLEvent} The event object.
7136      * @param oArgs.target {HTMLElement} The SPAN element.
7137      *
7138      */
7140     /**
7141      * Fired when a header label has a mousedown.
7142      *
7143      * @event headerLabelMousedownEvent
7144      * @param oArgs.event {HTMLEvent} The event object.
7145      * @param oArgs.target {HTMLElement} The SPAN element.
7146      */
7148     /**
7149      * Fired when a header label has a click.
7150      *
7151      * @event headerLabelClickEvent
7152      * @param oArgs.event {HTMLEvent} The event object.
7153      * @param oArgs.target {HTMLElement} The SPAN element.
7154      */
7156     /**
7157      * Fired when a header label has a dblclick.
7158      *
7159      * @event headerLabelDblclickEvent
7160      * @param oArgs.event {HTMLEvent} The event object.
7161      * @param oArgs.target {HTMLElement} The SPAN element.
7162      */
7164     /**
7165      * Fired when a column is sorted.
7166      *
7167      * @event columnSortEvent
7168      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
7169      * @param oArgs.dir {String} Sort direction "asc" or "desc".
7170      */
7172     /**
7173      * Fired when a column is resized.
7174      *
7175      * @event columnResizeEvent
7176      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
7177      * @param oArgs.target {HTMLElement} The TH element.
7178      */
7180     /**
7181      * Fired when a row has a mouseover.
7182      *
7183      * @event rowMouseoverEvent
7184      * @param oArgs.event {HTMLEvent} The event object.
7185      * @param oArgs.target {HTMLElement} The TR element.
7186      */
7188     /**
7189      * Fired when a row has a mouseout.
7190      *
7191      * @event rowMouseoutEvent
7192      * @param oArgs.event {HTMLEvent} The event object.
7193      * @param oArgs.target {HTMLElement} The TR element.
7194      */
7196     /**
7197      * Fired when a row has a mousedown.
7198      *
7199      * @event rowMousedownEvent
7200      * @param oArgs.event {HTMLEvent} The event object.
7201      * @param oArgs.target {HTMLElement} The TR element.
7202      */
7204     /**
7205      * Fired when a row has a click.
7206      *
7207      * @event rowClickEvent
7208      * @param oArgs.event {HTMLEvent} The event object.
7209      * @param oArgs.target {HTMLElement} The TR element.
7210      */
7212     /**
7213      * Fired when a row has a dblclick.
7214      *
7215      * @event rowDblclickEvent
7216      * @param oArgs.event {HTMLEvent} The event object.
7217      * @param oArgs.target {HTMLElement} The TR element.
7218      */
7220     /**
7221      * Fired when a row is added.
7222      *
7223      * @event rowAddEvent
7224      * @param oArgs.record {YAHOO.widget.Record} The added Record.
7225      */
7227     /**
7228      * Fired when a row is updated.
7229      *
7230      * @event rowUpdateEvent
7231      * @param oArgs.record {YAHOO.widget.Record} The updated Record.
7232      * @param oArgs.oldData {Object} Object literal of the old data.
7233      */
7235     /**
7236      * Fired when a row is deleted.
7237      *
7238      * @event rowDeleteEvent
7239      * @param oArgs.oldData {Object} Object literal of the deleted data.
7240      * @param oArgs.recordIndex {Number} Index of the deleted Record.
7241      * @param oArgs.trElIndex {Number} Index of the deleted TR element, if in view.
7242      */
7244     /**
7245      * Fired when a row is selected.
7246      *
7247      * @event rowSelectEvent
7248      * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
7249      * @param oArgs.record {YAHOO.widget.Record} The selected Record.
7250      */
7252     /**
7253      * Fired when a row is unselected.
7254      *
7255      * @event rowUnselectEvent
7256      * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
7257      * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
7258      */
7260     /*TODO: delete and use rowUnselectEvent?
7261      * Fired when all row selections are cleared.
7262      *
7263      * @event unselectAllRowsEvent
7264      */
7266     /*
7267      * Fired when a row is highlighted.
7268      *
7269      * @event rowHighlightEvent
7270      * @param oArgs.el {HTMLElement} The highlighted TR element.
7271      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
7272      */
7274     /*
7275      * Fired when a row is unhighlighted.
7276      *
7277      * @event rowUnhighlightEvent
7278      * @param oArgs.el {HTMLElement} The highlighted TR element.
7279      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
7280      */
7282     /**
7283      * Fired when a cell has a mouseover.
7284      *
7285      * @event cellMouseoverEvent
7286      * @param oArgs.event {HTMLEvent} The event object.
7287      * @param oArgs.target {HTMLElement} The TD element.
7288      */
7290     /**
7291      * Fired when a cell has a mouseout.
7292      *
7293      * @event cellMouseoutEvent
7294      * @param oArgs.event {HTMLEvent} The event object.
7295      * @param oArgs.target {HTMLElement} The TD element.
7296      */
7298     /**
7299      * Fired when a cell has a mousedown.
7300      *
7301      * @event cellMousedownEvent
7302      * @param oArgs.event {HTMLEvent} The event object.
7303      * @param oArgs.target {HTMLElement} The TD element.
7304      */
7306     /**
7307      * Fired when a cell has a click.
7308      *
7309      * @event cellClickEvent
7310      * @param oArgs.event {HTMLEvent} The event object.
7311      * @param oArgs.target {HTMLElement} The TD element.
7312      */
7314     /**
7315      * Fired when a cell has a dblclick.
7316      *
7317      * @event cellDblclickEvent
7318      * @param oArgs.event {HTMLEvent} The event object.
7319      * @param oArgs.target {HTMLElement} The TD element.
7320      */
7322     /**
7323      * Fired when a cell is formatted.
7324      *
7325      * @event cellFormatEvent
7326      * @param oArgs.el {HTMLElement} The formatted TD element.
7327      * @param oArgs.record {YAHOO.widget.Record} The formatted Record.
7328      * @param oArgs.key {String} The key of the formatted cell.
7329      */
7331     /**
7332      * Fired when a cell is selected.
7333      *
7334      * @event cellSelectEvent
7335      * @param oArgs.el {HTMLElement} The selected TD element.
7336      * @param oArgs.record {YAHOO.widget.Record} The selected Record.
7337      * @param oArgs.key {String} The key of the selected cell.
7338      */
7340     /**
7341      * Fired when a cell is unselected.
7342      *
7343      * @event cellUnselectEvent
7344      * @param oArgs.el {HTMLElement} The unselected TD element.
7345      * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
7346      * @param oArgs.key {String} The key of the unselected cell.
7347      */
7349     /**
7350      * Fired when a cell is highlighted.
7351      *
7352      * @event cellHighlightEvent
7353      * @param oArgs.el {HTMLElement} The highlighted TD element.
7354      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
7355      * @param oArgs.key {String} The key of the highlighted cell.
7356      */
7358     /**
7359      * Fired when a cell is unhighlighted.
7360      *
7361      * @event cellUnhighlightEvent
7362      * @param oArgs.el {HTMLElement} The unhighlighted TD element.
7363      * @param oArgs.record {YAHOO.widget.Record} The unhighlighted Record.
7364      * @param oArgs.key {String} The key of the unhighlighted cell.
7365      */
7367     /*TODO: hide from doc and use cellUnselectEvent
7368      * Fired when all cell selections are cleared.
7369      *
7370      * @event unselectAllCellsEvent
7371      */
7373     /*TODO: implement
7374      * Fired when DataTable paginator is updated.
7375      *
7376      * @event paginatorUpdateEvent
7377      * @param paginator {Object} Object literal of Paginator values.
7378      */
7380     /**
7381      * Fired when an Editor is activated.
7382      *
7383      * @event editorShowEvent
7384      * @param oArgs.editor {Object} The Editor object literal.
7385      */
7387     /**
7388      * Fired when an active Editor has a keydown.
7389      *
7390      * @event editorKeydownEvent
7391      * @param oArgs.editor {Object} The Editor object literal.
7392      * @param oArgs.event {HTMLEvent} The event object.
7393      */
7395     /**
7396      * Fired when Editor input is reverted.
7397      *
7398      * @event editorRevertEvent
7399      * @param oArgs.editor {Object} The Editor object literal.
7400      * @param oArgs.newData {Object} New data value.
7401      * @param oArgs.oldData {Object} Old data value.
7402      */
7404     /**
7405      * Fired when Editor input is saved.
7406      *
7407      * @event editorSaveEvent
7408      * @param oArgs.editor {Object} The Editor object literal.
7409      * @param oArgs.newData {Object} New data value.
7410      * @param oArgs.oldData {Object} Old data value.
7411      */
7413     /**
7414      * Fired when Editor input is canceled.
7415      *
7416      * @event editorCancelEvent
7417      * @param oArgs.editor {Object} The Editor object literal.
7418      */
7420     /**
7421      * Fired when an active Editor has a blur.
7422      *
7423      * @event editorBlurEvent
7424      * @param oArgs.editor {Object} The Editor object literal.
7425      */
7433     /**
7434      * Fired when a link is clicked.
7435      *
7436      * @event linkClickEvent
7437      * @param oArgs.event {HTMLEvent} The event object.
7438      * @param oArgs.target {HTMLElement} The A element.
7439      */
7441     /**
7442      * Fired when a BUTTON element is clicked.
7443      *
7444      * @event buttonClickEvent
7445      * @param oArgs.event {HTMLEvent} The event object.
7446      * @param oArgs.target {HTMLElement} The BUTTON element.
7447      */
7449     /**
7450      * Fired when a CHECKBOX element is clicked.
7451      *
7452      * @event checkboxClickEvent
7453      * @param oArgs.event {HTMLEvent} The event object.
7454      * @param oArgs.target {HTMLElement} The CHECKBOX element.
7455      */
7457     /*TODO
7458      * Fired when a SELECT element is changed.
7459      *
7460      * @event dropdownChangeEvent
7461      * @param oArgs.event {HTMLEvent} The event object.
7462      * @param oArgs.target {HTMLElement} The SELECT element.
7463      */
7465     /**
7466      * Fired when a RADIO element is clicked.
7467      *
7468      * @event radioClickEvent
7469      * @param oArgs.event {HTMLEvent} The event object.
7470      * @param oArgs.target {HTMLElement} The RADIO element.
7471      */
7474 /****************************************************************************/
7475 /****************************************************************************/
7476 /****************************************************************************/
7479  * The ColumnSet class defines and manages a DataTable's Columns,
7480  * including nested hierarchies and access to individual Column instances.
7482  * @namespace YAHOO.widget
7483  * @class ColumnSet
7484  * @uses YAHOO.util.EventProvider
7485  * @constructor
7486  * @param aHeaders {Object[]} Array of object literals that define header cells.
7487  */
7488 YAHOO.widget.ColumnSet = function(aHeaders) {
7489     this._sName = "instance" + YAHOO.widget.ColumnSet._nCount;
7491     // DOM tree representation of all Columns
7492     var tree = [];
7493     // Flat representation of all Columns
7494     var flat = [];
7495     // Flat representation of only Columns that are meant to display data
7496     var keys = [];
7497     // Array of HEADERS attribute values for all keys in the "keys" array
7498     var headers = [];
7500     // Tracks current node list depth being tracked
7501     var nodeDepth = -1;
7503     // Internal recursive function to defined Column instances
7504     var parseColumns = function(nodeList, parent) {
7505         // One level down
7506         nodeDepth++;
7508         // Create corresponding tree node if not already there for this depth
7509         if(!tree[nodeDepth]) {
7510             tree[nodeDepth] = [];
7511         }
7514         // Parse each node at this depth for attributes and any children
7515         for(var j=0; j<nodeList.length; j++) {
7516             var currentNode = nodeList[j];
7518             // Instantiate a new Column for each node
7519             var oColumn = new YAHOO.widget.Column(currentNode);
7521             // Add the new Column to the flat list
7522             flat.push(oColumn);
7524             // Assign its parent as an attribute, if applicable
7525             if(parent) {
7526                 oColumn.parent = parent;
7527             }
7529             // The Column has descendants
7530             if(YAHOO.lang.isArray(currentNode.children)) {
7531                 oColumn.children = currentNode.children;
7533                 // Determine COLSPAN value for this Column
7534                 var terminalChildNodes = 0;
7535                 var countTerminalChildNodes = function(ancestor) {
7536                     var descendants = ancestor.children;
7537                     // Drill down each branch and count terminal nodes
7538                     for(var k=0; k<descendants.length; k++) {
7539                         // Keep drilling down
7540                         if(YAHOO.lang.isArray(descendants[k].children)) {
7541                             countTerminalChildNodes(descendants[k]);
7542                         }
7543                         // Reached branch terminus
7544                         else {
7545                             terminalChildNodes++;
7546                         }
7547                     }
7548                 };
7549                 countTerminalChildNodes(currentNode);
7550                 oColumn._colspan = terminalChildNodes;
7552                 // Cascade certain properties to children if not defined on their own
7553                 var currentChildren = currentNode.children;
7554                 for(var k=0; k<currentChildren.length; k++) {
7555                     var child = currentChildren[k];
7556                     if(oColumn.className && (child.className === undefined)) {
7557                         child.className = oColumn.className;
7558                     }
7559                     if(oColumn.editor && (child.editor === undefined)) {
7560                         child.editor = oColumn.editor;
7561                     }
7562                     if(oColumn.editorOptions && (child.editorOptions === undefined)) {
7563                         child.editorOptions = oColumn.editorOptions;
7564                     }
7565                     if(oColumn.formatter && (child.formatter === undefined)) {
7566                         child.formatter = oColumn.formatter;
7567                     }
7568                     if(oColumn.resizeable && (child.resizeable === undefined)) {
7569                         child.resizeable = oColumn.resizeable;
7570                     }
7571                     if(oColumn.sortable && (child.sortable === undefined)) {
7572                         child.sortable = oColumn.sortable;
7573                     }
7574                     if(oColumn.width && (child.width === undefined)) {
7575                         child.width = oColumn.width;
7576                     }
7577                     // Backward compatibility
7578                     if(oColumn.type && (child.type === undefined)) {
7579                         child.type = oColumn.type;
7580                     }
7581                     if(oColumn.type && !oColumn.formatter) {
7582                         oColumn.formatter = oColumn.type;
7583                     }
7584                     if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
7585                         oColumn.label = oColumn.text;
7586                     }
7587                     if(oColumn.parser) {
7588                     }
7589                     if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
7590                             (oColumn.sortOptions.descFunction))) {
7591                     }
7592                 }
7594                 // The children themselves must also be parsed for Column instances
7595                 if(!tree[nodeDepth+1]) {
7596                     tree[nodeDepth+1] = [];
7597                 }
7598                 parseColumns(currentChildren, oColumn);
7599             }
7600             // This Column does not have any children
7601             else {
7602                 oColumn._nKeyIndex = keys.length;
7603                 oColumn._colspan = 1;
7604                 keys.push(oColumn);
7605             }
7607             // Add the Column to the top-down tree
7608             tree[nodeDepth].push(oColumn);
7609         }
7610         nodeDepth--;
7611     };
7613     // Parse out Column instances from the array of object literals
7614     if(YAHOO.lang.isArray(aHeaders)) {
7615         parseColumns(aHeaders);
7616     }
7618     // Determine ROWSPAN value for each Column in the tree
7619     var parseTreeForRowspan = function(tree) {
7620         var maxRowDepth = 1;
7621         var currentRow;
7622         var currentColumn;
7624         // Calculate the max depth of descendants for this row
7625         var countMaxRowDepth = function(row, tmpRowDepth) {
7626             tmpRowDepth = tmpRowDepth || 1;
7628             for(var n=0; n<row.length; n++) {
7629                 var col = row[n];
7630                 // Column has children, so keep counting
7631                 if(YAHOO.lang.isArray(col.children)) {
7632                     tmpRowDepth++;
7633                     countMaxRowDepth(col.children, tmpRowDepth);
7634                     tmpRowDepth--;
7635                 }
7636                 // No children, is it the max depth?
7637                 else {
7638                     if(tmpRowDepth > maxRowDepth) {
7639                         maxRowDepth = tmpRowDepth;
7640                     }
7641                 }
7643             }
7644         };
7646         // Count max row depth for each row
7647         for(var m=0; m<tree.length; m++) {
7648             currentRow = tree[m];
7649             countMaxRowDepth(currentRow);
7651             // Assign the right ROWSPAN values to each Column in the row
7652             for(var p=0; p<currentRow.length; p++) {
7653                 currentColumn = currentRow[p];
7654                 if(!YAHOO.lang.isArray(currentColumn.children)) {
7655                     currentColumn._rowspan = maxRowDepth;
7656                 }
7657                 else {
7658                     currentColumn._rowspan = 1;
7659                 }
7660             }
7662             // Reset counter for next row
7663             maxRowDepth = 1;
7664         }
7665     };
7666     parseTreeForRowspan(tree);
7672     // Store header relationships in an array for HEADERS attribute
7673     var recurseAncestorsForHeaders = function(i, oColumn) {
7674         headers[i].push(oColumn._nId);
7675         if(oColumn.parent) {
7676             recurseAncestorsForHeaders(i, oColumn.parent);
7677         }
7678     };
7679     for(var i=0; i<keys.length; i++) {
7680         headers[i] = [];
7681         recurseAncestorsForHeaders(i, keys[i]);
7682         headers[i] = headers[i].reverse();
7683         headers[i] = headers[i].join(" ");
7684     }
7686     // Save to the ColumnSet instance
7687     this.tree = tree;
7688     this.flat = flat;
7689     this.keys = keys;
7690     this.headers = headers;
7692     YAHOO.widget.ColumnSet._nCount++;
7695 /////////////////////////////////////////////////////////////////////////////
7697 // Public member variables
7699 /////////////////////////////////////////////////////////////////////////////
7702  * Internal class variable to index multiple data table instances.
7704  * @property ColumnSet._nCount
7705  * @type number
7706  * @private
7707  * @static
7708  */
7709 YAHOO.widget.ColumnSet._nCount = 0;
7712  * Unique instance name.
7714  * @property _sName
7715  * @type String
7716  * @private
7717  */
7718 YAHOO.widget.ColumnSet.prototype._sName = null;
7720 /////////////////////////////////////////////////////////////////////////////
7722 // Public member variables
7724 /////////////////////////////////////////////////////////////////////////////
7727  * Top-down tree representation of Column hierarchy.
7729  * @property tree
7730  * @type YAHOO.widget.Column[]
7731  */
7732 YAHOO.widget.ColumnSet.prototype.tree = null;
7735  * Flattened representation of all Columns.
7737  * @property flat
7738  * @type YAHOO.widget.Column[]
7739  * @default []
7740  */
7741 YAHOO.widget.ColumnSet.prototype.flat = null;
7744  * Array of Columns that map one-to-one to a table column.
7746  * @property keys
7747  * @type YAHOO.widget.Column[]
7748  * @default []
7749  */
7750 YAHOO.widget.ColumnSet.prototype.keys = null;
7753  * ID index of nested parent hierarchies for HEADERS accessibility attribute.
7755  * @property headers
7756  * @type String[]
7757  * @default []
7758  */
7759 YAHOO.widget.ColumnSet.prototype.headers = null;
7761 /////////////////////////////////////////////////////////////////////////////
7763 // Public methods
7765 /////////////////////////////////////////////////////////////////////////////
7768  * Public accessor to the unique name of the ColumnSet instance.
7770  * @method toString
7771  * @return {String} Unique name of the ColumnSet instance.
7772  */
7774 YAHOO.widget.ColumnSet.prototype.toString = function() {
7775     return "ColumnSet " + this._sName;
7779  * Returns Column instance with given ID number or key.
7781  * @method getColumn
7782  * @param column {Number | String} ID number or unique key.
7783  * @return {YAHOO.widget.Column} Column instance.
7784  */
7786 YAHOO.widget.ColumnSet.prototype.getColumn = function(column) {
7787     var allColumns = this.flat;
7788     if(YAHOO.lang.isNumber(column)) {
7789         for(var i=0; i<allColumns.length; i++) {
7790             if(allColumns[i]._nId === column) {
7791                 return allColumns[i];
7792             }
7793         }
7794     }
7795     else if(YAHOO.lang.isString(column)) {
7796         for(i=0; i<allColumns.length; i++) {
7797             if(allColumns[i].key === column) {
7798                 return allColumns[i];
7799             }
7800         }
7801     }
7802     return null;
7805 /****************************************************************************/
7806 /****************************************************************************/
7807 /****************************************************************************/
7810  * The Column class defines and manages attributes of DataTable Columns
7812  * @namespace YAHOO.widget
7813  * @class Column
7814  * @constructor
7815  * @param oConfigs {Object} Object literal of configuration values.
7816  */
7817 YAHOO.widget.Column = function(oConfigs) {
7818     // Internal variables
7819     this._nId = YAHOO.widget.Column._nCount;
7820     this._sName = "Column instance" + this._nId;
7822     // Object literal defines Column attributes
7823     if(oConfigs && (oConfigs.constructor == Object)) {
7824         for(var sConfig in oConfigs) {
7825             if(sConfig) {
7826                 this[sConfig] = oConfigs[sConfig];
7827             }
7828         }
7829     }
7831     if(!YAHOO.lang.isValue(this.key)) {
7832         this.key = "yui-dt-column"+this._nId;
7833     }
7834     YAHOO.widget.Column._nCount++;
7837 /////////////////////////////////////////////////////////////////////////////
7839 // Private member variables
7841 /////////////////////////////////////////////////////////////////////////////
7844  * Internal instance counter.
7846  * @property Column._nCount
7847  * @type Number
7848  * @private
7849  * @static
7850  * @default 0
7851  */
7852 YAHOO.widget.Column._nCount = 0;
7855  * Unique instance name.
7857  * @property _sName
7858  * @type String
7859  * @private
7860  */
7861 YAHOO.widget.Column.prototype._sName = null;
7865  * Unique number assigned at instantiation, indicates original order within
7866  * ColumnSet.
7868  * @property _nId
7869  * @type Number
7870  * @private
7871  */
7872 YAHOO.widget.Column.prototype._nId = null;
7875  * Reference to Column's index within its ColumnSet's keys array, or null if not applicable.
7877  * @property _nKeyIndex
7878  * @type Number
7879  * @private
7880  */
7881 YAHOO.widget.Column.prototype._nKeyIndex = null;
7884  * Number of table cells the Column spans.
7886  * @property _colspan
7887  * @type Number
7888  * @private
7889  */
7890 YAHOO.widget.Column.prototype._colspan = 1;
7893  * Number of table rows the Column spans.
7895  * @property _rowspan
7896  * @type Number
7897  * @private
7898  */
7899 YAHOO.widget.Column.prototype._rowspan = 1;
7902  * Column's parent Column instance, or null.
7904  * @property _parent
7905  * @type YAHOO.widget.Column
7906  * @private
7907  */
7908 YAHOO.widget.Column.prototype._parent = null;
7911  * Current offsetWidth of the Column (in pixels).
7913  * @property _width
7914  * @type Number
7915  * @private
7916  */
7917 YAHOO.widget.Column.prototype._width = null;
7920  * Minimum width the Column can support (in pixels). Value is populated only if table
7921  * is fixedWidth, null otherwise.
7923  * @property _minWidth
7924  * @type Number
7925  * @private
7926  */
7927 YAHOO.widget.Column.prototype._minWidth = null;
7929 /////////////////////////////////////////////////////////////////////////////
7931 // Public member variables
7933 /////////////////////////////////////////////////////////////////////////////
7936  * Associated database field, or null.
7938  * @property key
7939  * @type String
7940  */
7941 YAHOO.widget.Column.prototype.key = null;
7944  * Text or HTML for display as Column's label in the TH element.
7946  * @property label
7947  * @type String
7948  */
7949 YAHOO.widget.Column.prototype.label = null;
7952  * Column head cell ABBR for accessibility.
7954  * @property abbr
7955  * @type String
7956  */
7957 YAHOO.widget.Column.prototype.abbr = null;
7960  * Array of object literals that define children (nested headers) of a Column.
7962  * @property children
7963  * @type Object[]
7964  */
7965 YAHOO.widget.Column.prototype.children = null;
7968  * Column width.
7970  * @property width
7971  * @type String
7972  */
7973 YAHOO.widget.Column.prototype.width = null;
7976  * Custom CSS class or array of classes to be applied to every cell in the Column.
7978  * @property className
7979  * @type String || String[]
7980  */
7981 YAHOO.widget.Column.prototype.className = null;
7984  * Defines a format function.
7986  * @property formatter
7987  * @type String || HTMLFunction
7988  */
7989 YAHOO.widget.Column.prototype.formatter = null;
7992  * Defines an editor function, otherwise Column is not editable.
7994  * @property editor
7995  * @type String || HTMLFunction
7996  */
7997 YAHOO.widget.Column.prototype.editor = null;
8000  * Defines editor options for Column in an object literal of param:value pairs.
8002  * @property editorOptions
8003  * @type Object
8004  */
8005 YAHOO.widget.Column.prototype.editorOptions = null;
8008  * True if Column is resizeable, false otherwise.
8010  * @property resizeable
8011  * @type Boolean
8012  * @default false
8013  */
8014 YAHOO.widget.Column.prototype.resizeable = false;
8017  * True if Column is sortable, false otherwise.
8019  * @property sortable
8020  * @type Boolean
8021  * @default false
8022  */
8023 YAHOO.widget.Column.prototype.sortable = false;
8026  * Default sort order for Column: "asc" or "desc".
8028  * @property sortOptions.defaultOrder
8029  * @type String
8030  * @default null
8031  */
8033  * Custom sort handler.
8035  * @property sortOptions.sortFunction
8036  * @type Function
8037  * @default null
8038  */
8039 YAHOO.widget.Column.prototype.sortOptions = null;
8055 /////////////////////////////////////////////////////////////////////////////
8057 // Public methods
8059 /////////////////////////////////////////////////////////////////////////////
8062  * Public accessor to the unique name of the Column instance.
8064  * @method toString
8065  * @return {String} Column's unique name.
8066  */
8067 YAHOO.widget.Column.prototype.toString = function() {
8068     return this._sName;
8072  * Returns unique number assigned at instantiation, indicates original order
8073  * within ColumnSet.
8075  * @method getId
8076  * @return {Number} Column's unique ID number.
8077  */
8078 YAHOO.widget.Column.prototype.getId = function() {
8079     return this._nId;
8083  * Public accessor returns Column's key index within its ColumnSet's keys array, or
8084  * null if not applicable.
8086  * @method getKeyIndex
8087  * @return {Number} Column's key index within its ColumnSet keys array, if applicable.
8088  */
8089 YAHOO.widget.Column.prototype.getKeyIndex = function() {
8090     return this._nKeyIndex;
8094  * Public accessor returns Column's parent instance if any, or null otherwise.
8096  * @method getParent
8097  * @return {YAHOO.widget.Column} Column's parent instance.
8098  */
8099 YAHOO.widget.Column.prototype.getParent = function() {
8100     return this._parent;
8104  * Public accessor returns Column's calculated COLSPAN value.
8106  * @method getColspan
8107  * @return {Number} Column's COLSPAN value.
8108  */
8109 YAHOO.widget.Column.prototype.getColspan = function() {
8110     return this._colspan;
8112 // Backward compatibility
8113 YAHOO.widget.Column.prototype.getColSpan = function() {
8114     return this.getColspan();
8118  * Public accessor returns Column's calculated ROWSPAN value.
8120  * @method getRowspan
8121  * @return {Number} Column's ROWSPAN value.
8122  */
8123 YAHOO.widget.Column.prototype.getRowspan = function() {
8124     return this._rowspan;
8127 // Backward compatibility
8128 YAHOO.widget.Column.prototype.getIndex = function() {
8129     return this.getKeyIndex();
8131 YAHOO.widget.Column.prototype.format = function() {
8133 YAHOO.widget.Column.formatCheckbox = function(elCell, oRecord, oColumn, oData) {
8134     YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
8136 YAHOO.widget.Column.formatCurrency = function(elCell, oRecord, oColumn, oData) {
8137     YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
8139 YAHOO.widget.Column.formatDate = function(elCell, oRecord, oColumn, oData) {
8140     YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
8142 YAHOO.widget.Column.formatEmail = function(elCell, oRecord, oColumn, oData) {
8143     YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
8145 YAHOO.widget.Column.formatLink = function(elCell, oRecord, oColumn, oData) {
8146     YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
8148 YAHOO.widget.Column.formatNumber = function(elCell, oRecord, oColumn, oData) {
8149     YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
8151 YAHOO.widget.Column.formatSelect = function(elCell, oRecord, oColumn, oData) {
8152     YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
8155 /****************************************************************************/
8156 /****************************************************************************/
8157 /****************************************************************************/
8160  * Sort static utility to support Column sorting.
8162  * @namespace YAHOO.util
8163  * @class Sort
8164  * @static
8165  */
8166 YAHOO.util.Sort = {
8167     /////////////////////////////////////////////////////////////////////////////
8168     //
8169     // Public methods
8170     //
8171     /////////////////////////////////////////////////////////////////////////////
8173     /**
8174      * Comparator function for simple case-insensitive string sorting.
8175      *
8176      * @method compare
8177      * @param a {Object} First sort argument.
8178      * @param b {Object} Second sort argument.
8179      * @param desc {Boolean} True if sort direction is descending, false if
8180      * sort direction is ascending.
8181      */
8182     compare: function(a, b, desc) {
8183         if((a === null) || (typeof a == "undefined")) {
8184             if((b === null) || (typeof b == "undefined")) {
8185                 return 0;
8186             }
8187             else {
8188                 return 1;
8189             }
8190         }
8191         else if((b === null) || (typeof b == "undefined")) {
8192             return -1;
8193         }
8195         if(a.constructor == String) {
8196             a = a.toLowerCase();
8197         }
8198         if(b.constructor == String) {
8199             b = b.toLowerCase();
8200         }
8201         if(a < b) {
8202             return (desc) ? 1 : -1;
8203         }
8204         else if (a > b) {
8205             return (desc) ? -1 : 1;
8206         }
8207         else {
8208             return 0;
8209         }
8210     }
8213 /****************************************************************************/
8214 /****************************************************************************/
8215 /****************************************************************************/
8218  * ColumnResizer subclasses DragDrop to support resizeable Columns.
8220  * @namespace YAHOO.util
8221  * @class ColumnResizer
8222  * @extends YAHOO.util.DragDrop
8223  * @constructor
8224  * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
8225  * @param oColumn {YAHOO.widget.Column} Column instance.
8226  * @param elThead {HTMLElement} TH element reference.
8227  * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
8228  * @param sGroup {String} Group name of related DragDrop items.
8229  * @param oConfig {Object} (Optional) Object literal of config values.
8230  */
8231 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elThead, sHandleId, sGroup, oConfig) {
8232     if(oDataTable && oColumn && elThead && sHandleId) {
8233         this.datatable = oDataTable;
8234         this.column = oColumn;
8235         this.cell = elThead;
8236         this.init(sHandleId, sGroup, oConfig);
8237         //this.initFrame();
8238         this.setYConstraint(0,0);
8239     }
8240     else {
8241     }
8244 if(YAHOO.util.DD) {
8245     YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DD);
8248 /////////////////////////////////////////////////////////////////////////////
8250 // Public DOM event handlers
8252 /////////////////////////////////////////////////////////////////////////////
8255  * Handles mousedown events on the Column resizer.
8257  * @method onMouseDown
8258  * @param e {string} The mousedown event
8259  */
8260 YAHOO.util.ColumnResizer.prototype.onMouseDown = function(e) {
8261     this.startWidth = this.cell.offsetWidth;
8262     this.startPos = YAHOO.util.Dom.getX(this.getDragEl());
8264     if(this.datatable.fixedWidth) {
8265         var cellLabel = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_LABEL,"span",this.cell)[0];
8266         this.minWidth = cellLabel.offsetWidth + 6;
8267         var sib = this.cell.nextSibling;
8268         var sibCellLabel = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_LABEL,"span",sib)[0];
8269         this.sibMinWidth = sibCellLabel.offsetWidth + 6;
8270 //!!
8271         var left = ((this.startWidth - this.minWidth) < 0) ? 0 : (this.startWidth - this.minWidth);
8272         var right = ((sib.offsetWidth - this.sibMinWidth) < 0) ? 0 : (sib.offsetWidth - this.sibMinWidth);
8273         this.setXConstraint(left, right);
8274     }
8279  * Handles mouseup events on the Column resizer.
8281  * @method onMouseUp
8282  * @param e {string} The mouseup event
8283  */
8284 YAHOO.util.ColumnResizer.prototype.onMouseUp = function(e) {
8285     //TODO: replace the resizer where it belongs:
8286     var resizeStyle = YAHOO.util.Dom.get(this.handleElId).style;
8287     resizeStyle.left = "auto";
8288     resizeStyle.right = 0;
8289     resizeStyle.marginRight = "-6px";
8290     resizeStyle.width = "6px";
8291     //.yui-dt-headresizer {position:absolute;margin-right:-6px;right:0;bottom:0;width:6px;height:100%;cursor:w-resize;cursor:col-resize;}
8294     //var cells = this.datatable._elTable.tHead.rows[this.datatable._elTable.tHead.rows.length-1].cells;
8295     //for(var i=0; i<cells.length; i++) {
8296         //cells[i].style.width = "5px";
8297     //}
8299     //TODO: set new ColumnSet width values
8300     this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.cell});
8304  * Handles drag events on the Column resizer.
8306  * @method onDrag
8307  * @param e {string} The drag event
8308  */
8309 YAHOO.util.ColumnResizer.prototype.onDrag = function(e) {
8310     var newPos = YAHOO.util.Dom.getX(this.getDragEl());
8311     var offsetX = newPos - this.startPos;
8312     var newWidth = this.startWidth + offsetX;
8314     if(newWidth < this.minWidth) {
8315         newWidth = this.minWidth;
8316     }
8318     // Resize the Column
8319     var oDataTable = this.datatable;
8320     var elCell = this.cell;
8323     // Resize the other Columns
8324     if(oDataTable.fixedWidth) {
8325         // Moving right or left?
8326         var sib = elCell.nextSibling;
8327         //var sibIndex = elCell.index + 1;
8328         var sibnewwidth = sib.offsetWidth - offsetX;
8329         if(sibnewwidth < this.sibMinWidth) {
8330             sibnewwidth = this.sibMinWidth;
8331         }
8333         //TODO: how else to cycle through all the Columns without having to use an index property?
8334         for(var i=0; i<oDataTable._oColumnSet.length; i++) {
8335             //if((i != elCell.index) &&  (i!=sibIndex)) {
8336             //    YAHOO.util.Dom.get(oDataTable._oColumnSet.keys[i].id).style.width = oDataTable._oColumnSet.keys[i].width + "px";
8337             //}
8338         }
8339         sib.style.width = sibnewwidth;
8340         elCell.style.width = newWidth + "px";
8341         //oDataTable._oColumnSet.flat[sibIndex].width = sibnewwidth;
8342         //oDataTable._oColumnSet.flat[elCell.index].width = newWidth;
8344     }
8345     else {
8346         elCell.style.width = newWidth + "px";
8347     }
8353 /****************************************************************************/
8354 /****************************************************************************/
8355 /****************************************************************************/
8358  * A RecordSet defines and manages a set of Records.
8360  * @namespace YAHOO.widget
8361  * @class RecordSet
8362  * @param data {Object || Object[]} An object literal or an array of data.
8363  * @constructor
8364  */
8365 YAHOO.widget.RecordSet = function(data) {
8366     // Internal variables
8367     this._sName = "RecordSet instance" + YAHOO.widget.RecordSet._nCount;
8368     YAHOO.widget.RecordSet._nCount++;
8369     this._records = [];
8370     this._length = 0;
8371     
8372     if(data) {
8373         if(YAHOO.lang.isArray(data)) {
8374             this.addRecords(data);
8375         }
8376         else if(data.constructor == Object) {
8377             this.addRecord(data);
8378         }
8379     }
8381     /**
8382      * Fired when a new Record is added to the RecordSet.
8383      *
8384      * @event recordAddEvent
8385      * @param oArgs.record {YAHOO.widget.Record} The Record instance.
8386      * @param oArgs.data {Object} Data added.
8387      */
8388     this.createEvent("recordAddEvent");
8390     /**
8391      * Fired when multiple Records are added to the RecordSet at once.
8392      *
8393      * @event recordsAddEvent
8394      * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
8395      * @param oArgs.data {Object[]} Data added.
8396      */
8397     this.createEvent("recordsAddEvent");
8399     /**
8400      * Fired when a Record is updated with new data.
8401      *
8402      * @event recordUpdateEvent
8403      * @param oArgs.record {YAHOO.widget.Record} The Record instance.
8404      * @param oArgs.newData {Object} New data.
8405      * @param oArgs.oldData {Object} Old data.
8406      */
8407     this.createEvent("recordUpdateEvent");
8408     
8409     /**
8410      * Fired when a Record is deleted from the RecordSet.
8411      *
8412      * @event recordDeleteEvent
8413      * @param oArgs.data {Object} A copy of the data held by the Record,
8414      * or an array of data object literals if multiple Records were deleted at once.
8415      * @param oArgs.index {Object} Index of the deleted Record.
8416      */
8417     this.createEvent("recordDeleteEvent");
8419     /**
8420      * Fired when multiple Records are deleted from the RecordSet at once.
8421      *
8422      * @event recordsDeleteEvent
8423      * @param oArgs.data {Object[]} An array of data object literals copied
8424      * from the Records.
8425      * @param oArgs.index {Object} Index of the first deleted Record.
8426      */
8427     this.createEvent("recordsDeleteEvent");
8428     
8429     /**
8430      * Fired when all Records are deleted from the RecordSet at once.
8431      *
8432      * @event resetEvent
8433      */
8434     this.createEvent("resetEvent");
8436     /**
8437      * Fired when a Record Key is updated with new data.
8438      *
8439      * @event keyUpdateEvent
8440      * @param oArgs.record {YAHOO.widget.Record} The Record instance.
8441      * @param oArgs.key {String} The updated key.
8442      * @param oArgs.newData {Object} New data.
8443      * @param oArgs.oldData {Object} Old data.
8444      *
8445      */
8446     this.createEvent("keyUpdateEvent");
8450 if(YAHOO.util.EventProvider) {
8451     YAHOO.augment(YAHOO.widget.RecordSet, YAHOO.util.EventProvider);
8453 else {
8456 /////////////////////////////////////////////////////////////////////////////
8458 // Private member variables
8460 /////////////////////////////////////////////////////////////////////////////
8462  * Internal class variable to name multiple Recordset instances.
8464  * @property RecordSet._nCount
8465  * @type Number
8466  * @private
8467  * @static
8468  */
8469 YAHOO.widget.RecordSet._nCount = 0;
8472  * Unique instance name.
8474  * @property _sName
8475  * @type String
8476  * @private
8477  */
8478 YAHOO.widget.RecordSet.prototype._sName = null;
8481  * Internal variable to give unique indexes to Record instances.
8483  * @property _nCount
8484  * @type Number
8485  * @private
8486  */
8487 YAHOO.widget.RecordSet.prototype._nRecordCount = 0;
8490  * Internal counter of how many Records are in the RecordSet.
8492  * @property _length
8493  * @type Number
8494  * @private
8495  */
8496 YAHOO.widget.RecordSet.prototype._length = null;
8498 /////////////////////////////////////////////////////////////////////////////
8500 // Private methods
8502 /////////////////////////////////////////////////////////////////////////////
8505  * Adds one Record to the RecordSet at the given index. If index is null,
8506  * then adds the Record to the end of the RecordSet.
8508  * @method _addRecord
8509  * @param oData {Object} An object literal of data.
8510  * @param index {Number} (optional) Position index.
8511  * @return {YAHOO.widget.Record} A Record instance.
8512  * @private
8513  */
8514 YAHOO.widget.RecordSet.prototype._addRecord = function(oData, index) {
8515     var oRecord = new YAHOO.widget.Record(oData);
8516     oRecord._nId = this._nRecordCount;
8517     this._nRecordCount++;
8518     
8519     if(YAHOO.lang.isNumber(index) && (index > -1)) {
8520         this._records.splice(index,0,oRecord);
8521     }
8522     else {
8523         index = this.getLength();
8524         this._records.push(oRecord);
8525     }
8526     this._length++;
8527     return oRecord;
8531  * Deletes Records from the RecordSet at the given index. If range is null,
8532  * then only one Record is deleted.
8534  * @method _deleteRecord
8535  * @param index {Number} Position index.
8536  * @param range {Number} (optional) How many Records to delete
8537  * @private
8538  */
8539 YAHOO.widget.RecordSet.prototype._deleteRecord = function(index, range) {
8540     if(!YAHOO.lang.isNumber(range) || (range < 0)) {
8541         range = 1;
8542     }
8543     this._records.splice(index, range);
8544     this._length = this._length - range;
8547 /////////////////////////////////////////////////////////////////////////////
8549 // Public methods
8551 /////////////////////////////////////////////////////////////////////////////
8554  * Public accessor to the unique name of the RecordSet instance.
8556  * @method toString
8557  * @return {String} Unique name of the RecordSet instance.
8558  */
8559 YAHOO.widget.RecordSet.prototype.toString = function() {
8560     return this._sName;
8564  * Returns the number of Records held in the RecordSet.
8566  * @method getLength
8567  * @return {Number} Number of records in the RecordSet.
8568  */
8569 YAHOO.widget.RecordSet.prototype.getLength = function() {
8570         return this._length;
8574  * Returns Record at given position index.
8576  * @method getRecord
8577  * @param index {Number} Record's Recordset position index.
8578  * @return {YAHOO.widget.Record} Record object.
8579  */
8580 YAHOO.widget.RecordSet.prototype.getRecord = function(index) {
8581     if(YAHOO.lang.isNumber(index)) {
8582         return this._records[index];
8583     }
8584     /*else if(YAHOO.lang.isString(identifier)) {
8585         for(var i=0; i<this._records.length; i++) {
8586             if(this._records[i].yuiRecordId == identifier) {
8587                 return this._records[i];
8588             }
8589         }
8590     }*/
8591     return null;
8596  * Returns an array of Records from the RecordSet.
8598  * @method getRecords
8599  * @param index {Number} (optional) Recordset position index of which Record to
8600  * start at.
8601  * @param range {Number} (optional) Number of Records to get.
8602  * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
8603  * length equal to given range. If index is not given, all Records are returned.
8604  */
8605 YAHOO.widget.RecordSet.prototype.getRecords = function(index, range) {
8606     if(!YAHOO.lang.isNumber(index)) {
8607         return this._records;
8608     }
8609     if(!YAHOO.lang.isNumber(range)) {
8610         return this._records.slice(index);
8611     }
8612     return this._records.slice(index, index+range);
8616  * Returns position index for the given Record.
8618  * @method getRecordIndex
8619  * @param oRecord {YAHOO.widget.Record} Record instance.
8620  * @return {Number} Record's RecordSet position index.
8621  */
8623 YAHOO.widget.RecordSet.prototype.getRecordIndex = function(oRecord) {
8624     for(var i=this._records.length-1; i>-1; i--) {
8625         if(oRecord.getId() === this._records[i].getId()) {
8626             return i;
8627         }
8628     }
8629     return null;
8634  * Adds one Record to the RecordSet at the given index. If index is null,
8635  * then adds the Record to the end of the RecordSet.
8637  * @method addRecord
8638  * @param oData {Object} An object literal of data.
8639  * @param index {Number} (optional) Position index.
8640  * @return {YAHOO.widget.Record} A Record instance.
8641  */
8642 YAHOO.widget.RecordSet.prototype.addRecord = function(oData, index) {
8643     if(oData && (oData.constructor == Object)) {
8644         var oRecord = this._addRecord(oData, index);
8645         this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
8646         return oRecord;
8647     }
8648     else {
8649         return null;
8650     }
8654  * Adds multiple Records at once to the RecordSet at the given index with the
8655  * given data. If index is null, then the new Records are added to the end of
8656  * the RecordSet.
8658  * @method addRecords
8659  * @param aData {Object[]} An array of object literal data.
8660  * @param index {Number} (optional) Position index.
8661  * @return {YAHOO.widget.Record[]} An array of Record instances.
8662  */
8663 YAHOO.widget.RecordSet.prototype.addRecords = function(aData, index) {
8664     if(YAHOO.lang.isArray(aData)) {
8665         var newRecords = [];
8666         // Can't go backwards bc we need to preserve order
8667         for(var i=0; i<aData.length; i++) {
8668             if(aData[i] && (aData[i].constructor == Object)) {
8669                 var record = this._addRecord(aData[i], index);
8670                 newRecords.push(record);
8671             }
8672        }
8673         this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
8674        return newRecords;
8675     }
8676     else if(aData && (aData.constructor == Object)) {
8677         var oRecord = this._addRecord(aData);
8678         this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
8679         return oRecord;
8680     }
8681     else {
8682     }
8686  * Updates given Record with given data.
8688  * @method updateRecord
8689  * @param record {YAHOO.widget.Record | Number} A Record instance, or Record's
8690  * RecordSet position index.
8691  * @param oData {Object) Object literal of new data.
8692  * @return {YAHOO.widget.Record} Updated Record, or null.
8693  */
8694 YAHOO.widget.RecordSet.prototype.updateRecord = function(record, oData) {
8695     var oRecord = null;
8696     if(YAHOO.lang.isNumber(record)) {
8697         oRecord = this._records[record];
8698     }
8699     else if(record instanceof YAHOO.widget.Record) {
8700         oRecord = record;
8701     }
8702     if(oRecord && oData && (oData.constructor == Object)) {
8703         // Copy data from the Record for the event that gets fired later
8704         var oldData = {};
8705         for(var key in oRecord._oData) {
8706             oldData[key] = oRecord._oData[key];
8707         }
8708         oRecord._oData = oData;
8709         this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
8710         return oRecord;
8711     }
8712     else {
8713         return null;
8714     }
8718  * Updates given Record at given key with given data.
8720  * @method updateKey
8721  * @param record {YAHOO.widget.Record | Number} A Record instance, or Record's
8722  * RecordSet position index.
8723  * @param sKey {String} Key name.
8724  * @param oData {Object) New data.
8725  */
8726 YAHOO.widget.RecordSet.prototype.updateKey = function(record, sKey, oData) {
8727     var oRecord;
8728     
8729     if(YAHOO.lang.isNumber(record)) {
8730         oRecord = this._records[record];
8731     }
8732     if(record instanceof YAHOO.widget.Record) {
8733         oRecord = record;
8735         var oldData = null;
8736         var keyValue = oRecord._oData[sKey];
8737         // Copy data from the Record for the event that gets fired later
8738         if(keyValue && keyValue.constructor == Object) {
8739             oldData = {};
8740             for(var key in keyValue) {
8741                 oldData[key] = keyValue[key];
8742             }
8743         }
8744         // Copy by value
8745         else {
8746             oldData = keyValue;
8747         }
8749         oRecord._oData[sKey] = oData;
8750         this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
8751     }
8752     else {
8753     }
8757  * Replaces all Records in RecordSet with new data.
8759  * @method replaceRecords
8760  * @param data {Object || Object[]} An object literal of data or an array of
8761  * object literal data.
8762  * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
8763  * an array of Records.
8764  */
8765 YAHOO.widget.RecordSet.prototype.replaceRecords = function(data) {
8766     this.reset();
8767     return this.addRecords(data);
8771  * Sorts all Records by given function.
8773  * @method sortRecords
8774  * @param fnSort {Function} Reference to a sort function.
8775  * @param desc {Boolean} True if sort direction is descending, false if sort
8776  * direction is ascending.
8777  * @return {YAHOO.widget.Record[]} Sorted array of Records.
8778  */
8779 YAHOO.widget.RecordSet.prototype.sortRecords = function(fnSort, desc) {
8780     return this._records.sort(function(a, b) {return fnSort(a, b, desc);});
8785  * Removes the Record at the given position index from the RecordSet. If a range
8786  * is also provided, removes that many Records, starting from the index. Length
8787  * of RecordSet is correspondingly shortened.
8789  * @method deleteRecord
8790  * @param index {Number} Record's RecordSet position index.
8791  * @param range {Number} (optional) How many Records to delete.
8792  * @return {Object} A copy of the data held by the deleted Record.
8793  */
8794 YAHOO.widget.RecordSet.prototype.deleteRecord = function(index) {
8795     if(YAHOO.lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
8796         // Copy data from the Record for the event that gets fired later
8797         var oRecordData = this.getRecord(index).getData();
8798         var oData = {};
8799         for(var key in oRecordData) {
8800             oData[key] = oRecordData[key];
8801         }
8802         
8803         this._deleteRecord(index);
8804         this.fireEvent("recordDeleteEvent",{data:oData,index:index});
8805         return oData;
8806     }
8807     else {
8808         return null;
8809     }
8813  * Removes the Record at the given position index from the RecordSet. If a range
8814  * is also provided, removes that many Records, starting from the index. Length
8815  * of RecordSet is correspondingly shortened.
8817  * @method deleteRecords
8818  * @param index {Number} Record's RecordSet position index.
8819  * @param range {Number} (optional) How many Records to delete.
8820  */
8821 YAHOO.widget.RecordSet.prototype.deleteRecords = function(index, range) {
8822     if(!YAHOO.lang.isNumber(range)) {
8823         range = 1;
8824     }
8825     if(YAHOO.lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
8826         var recordsToDelete = this.getRecords(index, range);
8827         // Copy data from each Record for the event that gets fired later
8828         var deletedData = [];
8829         for(var i=0; i<recordsToDelete.length; i++) {
8830             var oData = {};
8831             for(var key in recordsToDelete[i]) {
8832                 oData[key] = recordsToDelete[i][key];
8833             }
8834             deletedData.push(oData);
8835         }
8836         this._deleteRecord(index, range);
8838         this.fireEvent("recordsDeleteEvent",{data:deletedData,index:index});
8840     }
8841     else {
8842     }
8846  * Deletes all Records from the RecordSet.
8848  * @method reset
8849  */
8850 YAHOO.widget.RecordSet.prototype.reset = function() {
8851     this._records = [];
8852     this._length = 0;
8853     this.fireEvent("resetEvent");
8857 /****************************************************************************/
8858 /****************************************************************************/
8859 /****************************************************************************/
8862  * The Record class defines a DataTable record.
8864  * @namespace YAHOO.widget
8865  * @class Record
8866  * @constructor
8867  * @param oConfigs {Object} (optional) Object literal of key/value pairs.
8868  */
8869 YAHOO.widget.Record = function(oLiteral) {
8870     this._oData = {};
8871     if(oLiteral && (oLiteral.constructor == Object)) {
8872         for(var sKey in oLiteral) {
8873             this._oData[sKey] = oLiteral[sKey];
8874         }
8875     }
8878 /////////////////////////////////////////////////////////////////////////////
8880 // Private member variables
8882 /////////////////////////////////////////////////////////////////////////////
8884  * Unique number assigned at instantiation, indicates original order within
8885  * RecordSet.
8887  * @property _nId
8888  * @type Number
8889  * @private
8890  */
8891 YAHOO.widget.Record.prototype._nId = null;
8894  * Holds data for the Record in an object literal.
8896  * @property _oData
8897  * @type Object
8898  * @private
8899  */
8900 YAHOO.widget.Record.prototype._oData = null;
8902 /////////////////////////////////////////////////////////////////////////////
8904 // Public member variables
8906 /////////////////////////////////////////////////////////////////////////////
8908 /////////////////////////////////////////////////////////////////////////////
8910 // Public methods
8912 /////////////////////////////////////////////////////////////////////////////
8915  * Returns unique number assigned at instantiation, indicates original order
8916  * within RecordSet.
8918  * @method getId
8919  * @return Number
8920  */
8921 YAHOO.widget.Record.prototype.getId = function() {
8922     return this._nId;
8926  * Returns data for the Record for a key if given, or the entire object
8927  * literal otherwise.
8929  * @method getData
8930  * @param sKey {String} (Optional) The key to retrieve a single data value.
8931  * @return Object
8932  */
8933 YAHOO.widget.Record.prototype.getData = function(sKey) {
8934     if(YAHOO.lang.isString(sKey)) {
8935         return this._oData[sKey];
8936     }
8937     else {
8938         return this._oData;
8939     }
8943 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.3.0", build: "442"});