MDL-15505
[moodle-linuxchix.git] / lib / yui / datatable / datatable-beta-debug.js
blobba6106a65d9483f86582a925311ffd8730690059
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         YAHOO.log("Could not instantiate DataTable due to an invalid container element", "error", this.toString());
44         return;
45     }
47     // Initialize configs
48     this._initConfigs(oConfigs);
50     // Initialize ColumnSet
51     this._initColumnSet(aColumnDefs);
52     if(!this._oColumnSet) {
53         YAHOO.log("Could not instantiate DataTable due to an invalid ColumnSet", "error", this.toString());
54         return;
55     }
56     
57     // Initialize RecordSet
58     this._initRecordSet();
59     if(!this._oRecordSet) {
60         YAHOO.log("Could not instantiate DataTable due to an invalid RecordSet", "error", this.toString());
61         return;
62     }
64     // Initialize DataSource
65     this._initDataSource(oDataSource);
66     if(!this._oDataSource) {
67         YAHOO.log("Could not instantiate DataTable due to an invalid DataSource", "error", this.toString());
68         return;
69     }
71     // Progressive enhancement special case
72     if(this._oDataSource.dataType == YAHOO.util.DataSource.TYPE_HTMLTABLE) {
73         this._oDataSource.sendRequest(this.get("initialRequest"), this._onDataReturnEnhanceTable, this);
74     }
75     else {
76         // Initialize DOM elements
77         this._initTableEl();
78         if(!this._elTable || !this._elThead || !this._elTbody) {
79             YAHOO.log("Could not instantiate DataTable due to an invalid DOM elements", "error", this.toString());
80             return;
81         }
83         // Call Element's constructor after DOM elements are created
84         // but *before* table is populated with data
85         YAHOO.widget.DataTable.superclass.constructor.call(this, this._elContainer, this._oConfigs);
86         
87         //HACK: Set the Paginator values here via updatePaginator
88         if(this._oConfigs && this._oConfigs.paginator) {
89             this.updatePaginator(this._oConfigs.paginator);
90         }
92         // Send out for data in an asynchronous request
93         this._oDataSource.sendRequest(this.get("initialRequest"), this.onDataReturnInitializeTable, this);
94     }
96     // Initialize inline Cell editing
97     this._initCellEditorEl();
99     // Initialize Column sort
100     this._initColumnSort();
102     // Initialize DOM event listeners
103     this._initDomEvents();
105     YAHOO.widget.DataTable._nCount++;
106     YAHOO.log("DataTable initialized", "info", this.toString());
109 if(YAHOO.util.Element) {
110     YAHOO.lang.extend(YAHOO.widget.DataTable, YAHOO.util.Element);
112 else {
113     YAHOO.log("Missing dependency: YAHOO.util.Element","error",this.toString());
116 /////////////////////////////////////////////////////////////////////////////
118 // Superclass methods
120 /////////////////////////////////////////////////////////////////////////////
123  * Implementation of Element's abstract method. Sets up config values.
125  * @method initAttributes
126  * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
127  * @private
128  */
130 YAHOO.widget.DataTable.prototype.initAttributes = function(oConfigs) {
131     oConfigs = oConfigs || {};
132     YAHOO.widget.DataTable.superclass.initAttributes.call(this, oConfigs);
134     /**
135     * @config summary
136     * @description Value for the SUMMARY attribute.
137     * @type String
138     */
139     this.setAttributeConfig("summary", {
140         value: null,
141         validator: YAHOO.lang.isString,
142         method: function(sSummary) {
143             this._elTable.summary = sSummary;
144         }
145     });
147     /**
148     * @config selectionMode
149     * @description Specifies row or cell selection mode. Accepts the following strings:
150     *    <dl>
151     *      <dt>"standard"</dt>
152     *      <dd>Standard row selection with support for modifier keys to enable
153     *      multiple selections.</dd>
154     *
155     *      <dt>"single"</dt>
156     *      <dd>Row selection with modifier keys disabled to not allow
157     *      multiple selections.</dd>
158     *
159     *      <dt>"singlecell"</dt>
160     *      <dd>Cell selection with modifier keys disabled to not allow
161     *      multiple selections.</dd>
162     *
163     *      <dt>"cellblock"</dt>
164     *      <dd>Cell selection with support for modifier keys to enable multiple
165     *      selections in a block-fashion, like a spreadsheet.</dd>
166     *
167     *      <dt>"cellrange"</dt>
168     *      <dd>Cell selection with support for modifier keys to enable multiple
169     *      selections in a range-fashion, like a calendar.</dd>
170     *    </dl>
171     *
172     * @default "standard"
173     * @type String
174     */
175     this.setAttributeConfig("selectionMode", {
176         value: "standard",
177         validator: YAHOO.lang.isString
178     });
180     /**
181     * @config initialRequest
182     * @description Defines the initial request that gets sent to the DataSource.
183     * @type String
184     */
185     this.setAttributeConfig("initialRequest", {
186         value: "",
187         validator: YAHOO.lang.isString
188     });
190     /**
191     * @config sortedBy
192     * @description Object literal provides metadata for initial sort values if
193     * data will arrive pre-sorted:
194     * <dl>
195     *     <dt>sortedBy.key</dt>
196     *     <dd>Key of sorted Column</dd>
197     *     <dt>sortedBy.dir</dt>
198     *     <dd>Initial sort direction, either "asc" or "desc"</dd>
199     * </dl>
200     * @type Object
201     */
202     this.setAttributeConfig("sortedBy", {
203         value: null,
204         // TODO: accepted array for nested sorts
205         validator: function(oNewSortedBy) {
206             return (oNewSortedBy && (oNewSortedBy.constructor == Object) && oNewSortedBy.key);
207         },
208         method: function(oNewSortedBy) {
209             // Remove ASC/DESC from TH
210             var oOldSortedBy = this.get("sortedBy");
211             if(oOldSortedBy && (oOldSortedBy.constructor == Object) && oOldSortedBy.key) {
212                 var oldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
213                 var oldThEl = this.getThEl(oldColumn);
214                 YAHOO.util.Dom.removeClass(oldThEl, YAHOO.widget.DataTable.CLASS_ASC);
215                 YAHOO.util.Dom.removeClass(oldThEl, YAHOO.widget.DataTable.CLASS_DESC);
216             }
217             
218             // Set ASC/DESC on TH
219             var column = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
220             if(column) {
221                 var newClass = (oNewSortedBy.dir && (oNewSortedBy.dir != "asc")) ?
222                         YAHOO.widget.DataTable.CLASS_DESC :
223                         YAHOO.widget.DataTable.CLASS_ASC;
224                 YAHOO.util.Dom.addClass(this.id + "-col" + column.getId(), newClass);
225             }
226         }
227     });
229     /**
230     * @config paginator
231     * @description Object literal of pagination values.
232     * @default <br>
233     *   { containers:[], // UI container elements <br>
234     *   rowsPerPage:500, // 500 rows <br>
235     *   currentPage:1,  // page one <br>
236     *   pageLinks:0,    // show all links <br>
237     *   pageLinksStart:1, // first link is page 1 <br>
238     *   dropdownOptions:null, // no dropdown <br>
239     *   links: [], // links elements <br>
240     *   dropdowns: [] } //dropdown elements
241     * 
242     * @type Object
243     */
244     this.setAttributeConfig("paginator", {
245         value: {
246             rowsPerPage:500, // 500 rows per page
247             currentPage:1,  // show page one
248             startRecordIndex:0, // start with first Record
249             totalRecords:0, // how many Records total
250             totalPages:0, // how many pages total
251             rowsThisPage:0, // how many rows this page
252             pageLinks:0,    // show all links
253             pageLinksStart:1, // first link is page 1
254             dropdownOptions: null, //no dropdown
255             containers:[], // Paginator container element references
256             dropdowns: [], //dropdown element references,
257             links: [] // links elements
258         },
259         validator: function(oNewPaginator) {
260             if(oNewPaginator && (oNewPaginator.constructor == Object)) {
261                 // Check for incomplete set of values
262                 if((oNewPaginator.rowsPerPage !== undefined) &&
263                         (oNewPaginator.currentPage !== undefined) &&
264                         (oNewPaginator.startRecordIndex !== undefined) &&
265                         (oNewPaginator.totalRecords !== undefined) &&
266                         (oNewPaginator.totalPages !== undefined) &&
267                         (oNewPaginator.rowsThisPage !== undefined) &&
268                         (oNewPaginator.pageLinks !== undefined) &&
269                         (oNewPaginator.pageLinksStart !== undefined) &&
270                         (oNewPaginator.dropdownOptions !== undefined) &&
271                         (oNewPaginator.containers !== undefined) &&
272                         (oNewPaginator.dropdowns !== undefined) &&
273                         (oNewPaginator.links !== undefined)) {
275                     // Validate each value
276                     if(YAHOO.lang.isNumber(oNewPaginator.rowsPerPage) &&
277                             YAHOO.lang.isNumber(oNewPaginator.currentPage) &&
278                             YAHOO.lang.isNumber(oNewPaginator.startRecordIndex) &&
279                             YAHOO.lang.isNumber(oNewPaginator.totalRecords) &&
280                             YAHOO.lang.isNumber(oNewPaginator.totalPages) &&
281                             YAHOO.lang.isNumber(oNewPaginator.rowsThisPage) &&
282                             YAHOO.lang.isNumber(oNewPaginator.pageLinks) &&
283                             YAHOO.lang.isNumber(oNewPaginator.pageLinksStart) &&
284                             YAHOO.lang.isArray(oNewPaginator.dropdownOptions) &&
285                             YAHOO.lang.isArray(oNewPaginator.containers) &&
286                             YAHOO.lang.isArray(oNewPaginator.dropdowns) &&
287                             YAHOO.lang.isArray(oNewPaginator.links)) {
288                         return true;
289                     }
290                 }
291             }
292             return false;
293         }
294     });
296     /**
297     * @config paginated
298     * @description True if built-in client-side pagination is enabled
299     * @default false
300     * @type Boolean
301     */
302     this.setAttributeConfig("paginated", {
303         value: false,
304         validator: YAHOO.lang.isBoolean,
305         method: function(oParam) {
306             var oPaginator = this.get("paginator");
307             var aContainerEls = oPaginator.containers;
308             
309             // Paginator is enabled
310             if(oParam) {
311                 // No containers found, create two from scratch
312                 if(aContainerEls.length === 0) {
313                     // One before TABLE
314                     var pag0 = document.createElement("span");
315                     pag0.id = this.id + "-paginator0";
316                     YAHOO.util.Dom.addClass(pag0, YAHOO.widget.DataTable.CLASS_PAGINATOR);
317                     pag0 = this._elContainer.insertBefore(pag0, this._elTable);
318                     aContainerEls.push(pag0);
320                     // One after TABLE
321                     var pag1 = document.createElement("span");
322                     pag1.id = this.id + "-paginator1";
323                     YAHOO.util.Dom.addClass(pag1, YAHOO.widget.DataTable.CLASS_PAGINATOR);
324                     pag1 = this._elContainer.insertBefore(pag1, this._elTable.nextSibling);
325                     aContainerEls.push(pag1);
327                     // Add containers directly to tracker
328                     this._configs.paginator.value.containers = [pag0, pag1];
330                 }
331                 else {
332                     // Show each container
333                     for(var i=0; i<aContainerEls.length; i++) {
334                         aContainerEls[i].style.display = "";
335                     }
336                 }
338                 // Links are enabled
339                 if(oPaginator.pageLinks > -1) {
340                     var aLinkEls = oPaginator.links;
341                     // No links containers found, create from scratch
342                     if(aLinkEls.length === 0) {
343                         for(i=0; i<aContainerEls.length; i++) {
344                             // Create one links container per Paginator container
345                             var linkEl = document.createElement("span");
346                             linkEl.id = "yui-dt-pagselect"+i;
347                             linkEl = aContainerEls[i].appendChild(linkEl);
349                             // Add event listener
350                             //TODO: anon fnc
351                             YAHOO.util.Event.addListener(linkEl,"click",this._onPaginatorLinkClick,this);
353                              // Add directly to tracker
354                             this._configs.paginator.value.links.push(linkEl);
355                        }
356                    }
357                 }
359                 // Show these options in the dropdown
360                 var dropdownOptions = oPaginator.dropdownOptions || [];
362                 for(i=0; i<aContainerEls.length; i++) {
363                     // Create one SELECT element per Paginator container
364                     var selectEl = document.createElement("select");
365                     YAHOO.util.Dom.addClass(selectEl, YAHOO.widget.DataTable.CLASS_DROPDOWN);
366                     selectEl = aContainerEls[i].appendChild(selectEl);
367                     selectEl.id = "yui-dt-pagselect"+i;
369                     // Add event listener
370                     //TODO: anon fnc
371                     YAHOO.util.Event.addListener(selectEl,"change",this._onPaginatorDropdownChange,this);
373                     // Add DOM reference directly to tracker
374                    this._configs.paginator.value.dropdowns.push(selectEl);
376                     // Hide dropdown
377                     if(!oPaginator.dropdownOptions) {
378                         selectEl.style.display = "none";
379                     }
380                 }
382                 //TODO: fire paginatorDisabledEvent & add to api doc
383                 YAHOO.log("Paginator enabled", "info", this.toString());
384             }
385             // Pagination is disabled
386             else {
387                 // Containers found
388                 if(aContainerEls.length > 0) {
389                     // Destroy or just hide?
390                     
391                     // Hide each container
392                     for(i=0; i<aContainerEls.length; i++) {
393                         aContainerEls[i].style.display = "none";
394                     }
396                     /*TODO?
397                     // Destroy each container
398                     for(i=0; i<aContainerEls.length; i++) {
399                         YAHOO.util.Event.purgeElement(aContainerEls[i], true);
400                         aContainerEls.innerHTML = null;
401                         //TODO: remove container?
402                         // aContainerEls[i].parentNode.removeChild(aContainerEls[i]);
403                     }
404                     */
405                 }
406                 //TODO: fire paginatorDisabledEvent & add to api doc
407                 YAHOO.log("Paginator disabled", "info", this.toString());
408             }
409         }
410     });
412     /**
413     * @config caption
414     * @description Value for the CAPTION element.
415     * @type String
416     */
417     this.setAttributeConfig("caption", {
418         value: null,
419         validator: YAHOO.lang.isString,
420         method: function(sCaption) {
421             // Create CAPTION element
422             if(!this._elCaption) {
423                 if(!this._elTable.firstChild) {
424                     this._elCaption = this._elTable.appendChild(document.createElement("caption"));
425                 }
426                 else {
427                     this._elCaption = this._elTable.insertBefore(document.createElement("caption"), this._elTable.firstChild);
428                 }
429             }
430             // Set CAPTION value
431             this._elCaption.innerHTML = sCaption;
432         }
433     });
435     /**
436     * @config scrollable
437     * @description True if primary TBODY should scroll while THEAD remains fixed.
438     * When enabling this feature, captions cannot be used, and the following
439     * features are not recommended: inline editing, resizeable columns.
440     * @default false
441     * @type Boolean
442     */
443     this.setAttributeConfig("scrollable", {
444         value: false,
445         validator: function(oParam) {
446             //TODO: validate agnst resizeable
447             return (YAHOO.lang.isBoolean(oParam) &&
448                     // Not compatible with caption
449                     !YAHOO.lang.isString(this.get("caption")));
450         },
451         method: function(oParam) {
452             if(oParam) {
453                 //TODO: conf height
454                 YAHOO.util.Dom.addClass(this._elContainer,YAHOO.widget.DataTable.CLASS_SCROLLABLE);
455                 YAHOO.util.Dom.addClass(this._elTbody,YAHOO.widget.DataTable.CLASS_SCROLLBODY);
456             }
457             else {
458                 YAHOO.util.Dom.removeClass(this._elContainer,YAHOO.widget.DataTable.CLASS_SCROLLABLE);
459                 YAHOO.util.Dom.removeClass(this._elTbody,YAHOO.widget.DataTable.CLASS_SCROLLBODY);
461             }
462         }
463     });
466 /////////////////////////////////////////////////////////////////////////////
468 // Public constants
470 /////////////////////////////////////////////////////////////////////////////
473  * Class name assigned to TABLE element.
475  * @property DataTable.CLASS_TABLE
476  * @type String
477  * @static
478  * @final
479  * @default "yui-dt-table"
480  */
481 YAHOO.widget.DataTable.CLASS_TABLE = "yui-dt-table";
484  * Class name assigned to header container elements within each TH element.
486  * @property DataTable.CLASS_HEADER
487  * @type String
488  * @static
489  * @final
490  * @default "yui-dt-header"
491  */
492 YAHOO.widget.DataTable.CLASS_HEADER = "yui-dt-header";
495  * Class name assigned to the primary TBODY element.
497  * @property DataTable.CLASS_BODY
498  * @type String
499  * @static
500  * @final
501  * @default "yui-dt-body"
502  */
503 YAHOO.widget.DataTable.CLASS_BODY = "yui-dt-body";
506  * Class name assigned to the scrolling TBODY element of a fixed scrolling DataTable.
508  * @property DataTable.CLASS_SCROLLBODY
509  * @type String
510  * @static
511  * @final
512  * @default "yui-dt-scrollbody"
513  */
514 YAHOO.widget.DataTable.CLASS_SCROLLBODY = "yui-dt-scrollbody";
517  * Class name assigned to display label elements.
519  * @property DataTable.CLASS_LABEL
520  * @type String
521  * @static
522  * @final
523  * @default "yui-dt-label"
524  */
525 YAHOO.widget.DataTable.CLASS_LABEL = "yui-dt-label";
528  * Class name assigned to resizer handle elements.
530  * @property DataTable.CLASS_RESIZER
531  * @type String
532  * @static
533  * @final
534  * @default "yui-dt-resizer"
535  */
536 YAHOO.widget.DataTable.CLASS_RESIZER = "yui-dt-resizer";
539  * Class name assigned to Editor container elements.
541  * @property DataTable.CLASS_EDITOR
542  * @type String
543  * @static
544  * @final
545  * @default "yui-dt-editor"
546  */
547 YAHOO.widget.DataTable.CLASS_EDITOR = "yui-dt-editor";
550  * Class name assigned to paginator container elements.
552  * @property DataTable.CLASS_PAGINATOR
553  * @type String
554  * @static
555  * @final
556  * @default "yui-dt-paginator"
557  */
558 YAHOO.widget.DataTable.CLASS_PAGINATOR = "yui-dt-paginator";
561  * Class name assigned to page number indicators.
563  * @property DataTable.CLASS_PAGE
564  * @type String
565  * @static
566  * @final
567  * @default "yui-dt-page"
568  */
569 YAHOO.widget.DataTable.CLASS_PAGE = "yui-dt-page";
572  * Class name assigned to default indicators.
574  * @property DataTable.CLASS_DEFAULT
575  * @type String
576  * @static
577  * @final
578  * @default "yui-dt-default"
579  */
580 YAHOO.widget.DataTable.CLASS_DEFAULT = "yui-dt-default";
583  * Class name assigned to previous indicators.
585  * @property DataTable.CLASS_PREVIOUS
586  * @type String
587  * @static
588  * @final
589  * @default "yui-dt-previous"
590  */
591 YAHOO.widget.DataTable.CLASS_PREVIOUS = "yui-dt-previous";
594  * Class name assigned next indicators.
596  * @property DataTable.CLASS_NEXT
597  * @type String
598  * @static
599  * @final
600  * @default "yui-dt-next"
601  */
602 YAHOO.widget.DataTable.CLASS_NEXT = "yui-dt-next";
605  * Class name assigned to first elements.
607  * @property DataTable.CLASS_FIRST
608  * @type String
609  * @static
610  * @final
611  * @default "yui-dt-first"
612  */
613 YAHOO.widget.DataTable.CLASS_FIRST = "yui-dt-first";
616  * Class name assigned to last elements.
618  * @property DataTable.CLASS_LAST
619  * @type String
620  * @static
621  * @final
622  * @default "yui-dt-last"
623  */
624 YAHOO.widget.DataTable.CLASS_LAST = "yui-dt-last";
627  * Class name assigned to even elements.
629  * @property DataTable.CLASS_EVEN
630  * @type String
631  * @static
632  * @final
633  * @default "yui-dt-even"
634  */
635 YAHOO.widget.DataTable.CLASS_EVEN = "yui-dt-even";
638  * Class name assigned to odd elements.
640  * @property DataTable.CLASS_ODD
641  * @type String
642  * @static
643  * @final
644  * @default "yui-dt-odd"
645  */
646 YAHOO.widget.DataTable.CLASS_ODD = "yui-dt-odd";
649  * Class name assigned to selected elements.
651  * @property DataTable.CLASS_SELECTED
652  * @type String
653  * @static
654  * @final
655  * @default "yui-dt-selected"
656  */
657 YAHOO.widget.DataTable.CLASS_SELECTED = "yui-dt-selected";
660  * Class name assigned to highlighted elements.
662  * @property DataTable.CLASS_HIGHLIGHTED
663  * @type String
664  * @static
665  * @final
666  * @default "yui-dt-highlighted"
667  */
668 YAHOO.widget.DataTable.CLASS_HIGHLIGHTED = "yui-dt-highlighted";
671  * Class name assigned to disabled elements.
673  * @property DataTable.CLASS_DISABLED
674  * @type String
675  * @static
676  * @final
677  * @default "yui-dt-disabled"
678  */
679 YAHOO.widget.DataTable.CLASS_DISABLED = "yui-dt-disabled";
682  * Class name assigned to empty indicators.
684  * @property DataTable.CLASS_EMPTY
685  * @type String
686  * @static
687  * @final
688  * @default "yui-dt-empty"
689  */
690 YAHOO.widget.DataTable.CLASS_EMPTY = "yui-dt-empty";
693  * Class name assigned to loading indicatorx.
695  * @property DataTable.CLASS_LOADING
696  * @type String
697  * @static
698  * @final
699  * @default "yui-dt-loading"
700  */
701 YAHOO.widget.DataTable.CLASS_LOADING = "yui-dt-loading";
704  * Class name assigned to error indicators.
706  * @property DataTable.CLASS_ERROR
707  * @type String
708  * @static
709  * @final
710  * @default "yui-dt-error"
711  */
712 YAHOO.widget.DataTable.CLASS_ERROR = "yui-dt-error";
715  * Class name assigned to editable elements.
717  * @property DataTable.CLASS_EDITABLE
718  * @type String
719  * @static
720  * @final
721  * @default "yui-dt-editable"
722  */
723 YAHOO.widget.DataTable.CLASS_EDITABLE = "yui-dt-editable";
726  * Class name assigned to scrollable elements.
728  * @property DataTable.CLASS_SCROLLABLE
729  * @type String
730  * @static
731  * @final
732  * @default "yui-dt-scrollable"
733  */
734 YAHOO.widget.DataTable.CLASS_SCROLLABLE = "yui-dt-scrollable";
737  * Class name assigned to sortable elements.
739  * @property DataTable.CLASS_SORTABLE
740  * @type String
741  * @static
742  * @final
743  * @default "yui-dt-sortable"
744  */
745 YAHOO.widget.DataTable.CLASS_SORTABLE = "yui-dt-sortable";
748  * Class name assigned to ascending elements.
750  * @property DataTable.CLASS_ASC
751  * @type String
752  * @static
753  * @final
754  * @default "yui-dt-asc"
755  */
756 YAHOO.widget.DataTable.CLASS_ASC = "yui-dt-asc";
759  * Class name assigned to descending elements.
761  * @property DataTable.CLASS_DESC
762  * @type String
763  * @static
764  * @final
765  * @default "yui-dt-desc"
766  */
767 YAHOO.widget.DataTable.CLASS_DESC = "yui-dt-desc";
770  * Class name assigned to BUTTON container elements.
772  * @property DataTable.CLASS_BUTTON
773  * @type String
774  * @static
775  * @final
776  * @default "yui-dt-button"
777  */
778 YAHOO.widget.DataTable.CLASS_BUTTON = "yui-dt-button";
781  * Class name assigned to SELECT container elements.
783  * @property DataTable.CLASS_DROPDOWN
784  * @type String
785  * @static
786  * @final
787  * @default "yui-dt-dropdown"
788  */
789 YAHOO.widget.DataTable.CLASS_DROPDOWN = "yui-dt-dropdown";
792  * Class name assigned to INPUT TYPE=CHECKBOX container elements.
794  * @property DataTable.CLASS_CHECKBOX
795  * @type String
796  * @static
797  * @final
798  * @default "yui-dt-checkbox"
799  */
800 YAHOO.widget.DataTable.CLASS_CHECKBOX = "yui-dt-checkbox";
803  * Message to display if DataTable has no data.
805  * @property DataTable.MSG_EMPTY
806  * @type String
807  * @static
808  * @final
809  * @default "No records found."
810  */
811 YAHOO.widget.DataTable.MSG_EMPTY = "No records found.";
814  * Message to display while DataTable is loading data.
816  * @property DataTable.MSG_LOADING
817  * @type String
818  * @static
819  * @final
820  * @default "Loading data..."
821  */
822 YAHOO.widget.DataTable.MSG_LOADING = "Loading data...";
825  * Message to display while DataTable has data error.
827  * @property DataTable.MSG_ERROR
828  * @type String
829  * @static
830  * @final
831  * @default "Data error."
832  */
833 YAHOO.widget.DataTable.MSG_ERROR = "Data error.";
835 /////////////////////////////////////////////////////////////////////////////
837 // Private member variables
839 /////////////////////////////////////////////////////////////////////////////
842  * Internal class variable for indexing multiple DataTable instances.
844  * @property DataTable._nCount
845  * @type Number
846  * @private
847  * @static
848  */
849 YAHOO.widget.DataTable._nCount = 0;
852  * Index assigned to instance.
854  * @property _nIndex
855  * @type Number
856  * @private
857  */
858 YAHOO.widget.DataTable.prototype._nIndex = null;
861  * Counter for IDs assigned to TR elements.
863  * @property _nTrCount
864  * @type Number
865  * @private
866  */
867 YAHOO.widget.DataTable.prototype._nTrCount = 0;
870  * Unique name assigned to instance.
872  * @property _sName
873  * @type String
874  * @private
875  */
876 YAHOO.widget.DataTable.prototype._sName = null;
879  * DOM reference to the container element for the DataTable instance into which
880  * the TABLE element gets created.
882  * @property _elContainer
883  * @type HTMLElement
884  * @private
885  */
886 YAHOO.widget.DataTable.prototype._elContainer = null;
889  * DOM reference to the CAPTION element for the DataTable instance.
891  * @property _elCaption
892  * @type HTMLElement
893  * @private
894  */
895 YAHOO.widget.DataTable.prototype._elCaption = null;
898  * DOM reference to the TABLE element for the DataTable instance.
900  * @property _elTable
901  * @type HTMLElement
902  * @private
903  */
904 YAHOO.widget.DataTable.prototype._elTable = null;
907  * DOM reference to the THEAD element for the DataTable instance.
909  * @property _elThead
910  * @type HTMLElement
911  * @private
912  */
913 YAHOO.widget.DataTable.prototype._elThead = null;
916  * DOM reference to the primary TBODY element for the DataTable instance.
918  * @property _elTbody
919  * @type HTMLElement
920  * @private
921  */
922 YAHOO.widget.DataTable.prototype._elTbody = null;
925  * DOM reference to the secondary TBODY element used to display DataTable messages.
927  * @property _elMsgTbody
928  * @type HTMLElement
929  * @private
930  */
931 YAHOO.widget.DataTable.prototype._elMsgTbody = null;
934  * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
936  * @property _elMsgTbodyRow
937  * @type HTMLElement
938  * @private
939  */
940 YAHOO.widget.DataTable.prototype._elMsgTbodyRow = null;
943  * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
945  * @property _elMsgTbodyCell
946  * @type HTMLElement
947  * @private
948  */
949 YAHOO.widget.DataTable.prototype._elMsgTbodyCell = null;
952  * DataSource instance for the DataTable instance.
954  * @property _oDataSource
955  * @type YAHOO.util.DataSource
956  * @private
957  */
958 YAHOO.widget.DataTable.prototype._oDataSource = null;
961  * ColumnSet instance for the DataTable instance.
963  * @property _oColumnSet
964  * @type YAHOO.widget.ColumnSet
965  * @private
966  */
967 YAHOO.widget.DataTable.prototype._oColumnSet = null;
970  * RecordSet instance for the DataTable instance.
972  * @property _oRecordSet
973  * @type YAHOO.widget.RecordSet
974  * @private
975  */
976 YAHOO.widget.DataTable.prototype._oRecordSet = null;
979  * ID string of first label link element of the current DataTable page, if any.
980  * Used for focusing sortable Columns with TAB.
982  * @property _sFirstLabelLinkId
983  * @type String
984  * @private
985  */
986 YAHOO.widget.DataTable.prototype._sFirstLabelLinkId = null;
989  * ID string of first TR element of the current DataTable page.
991  * @property _sFirstTrId
992  * @type String
993  * @private
994  */
995 YAHOO.widget.DataTable.prototype._sFirstTrId = null;
998  * ID string of the last TR element of the current DataTable page.
1000  * @property _sLastTrId
1001  * @type String
1002  * @private
1003  */
1004 YAHOO.widget.DataTable.prototype._sLastTrId = null;
1037 /////////////////////////////////////////////////////////////////////////////
1039 // Private methods
1041 /////////////////////////////////////////////////////////////////////////////
1044  * Sets focus on the given element.
1046  * @method _focusEl
1047  * @param el {HTMLElement} Element.
1048  * @private
1049  */
1050 YAHOO.widget.DataTable.prototype._focusEl = function(el) {
1051     el = el || this._elTable;
1052     // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
1053     // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
1054     // strange unexpected things as the user clicks on buttons and other controls.
1055     setTimeout(function() { el.focus(); },0);
1062 // INIT FUNCTIONS
1065  * Initializes container element.
1067  * @method _initContainerEl
1068  * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
1069  * @private
1070  */
1071 YAHOO.widget.DataTable.prototype._initContainerEl = function(elContainer) {
1072     this._elContainer = null;
1073     elContainer = YAHOO.util.Dom.get(elContainer);
1074     if(elContainer && elContainer.tagName && (elContainer.tagName.toLowerCase() == "div")) {
1075         this._elContainer = elContainer;
1076     }
1080  * Initializes object literal of config values.
1082  * @method _initConfigs
1083  * @param oConfig {Object} Object literal of config values.
1084  * @private
1085  */
1086 YAHOO.widget.DataTable.prototype._initConfigs = function(oConfigs) {
1087     if(oConfigs) {
1088         if(oConfigs.constructor != Object) {
1089             oConfigs = null;
1090             YAHOO.log("Invalid configs", "warn", this.toString());
1091         }
1092         // Backward compatibility
1093         else if(YAHOO.lang.isBoolean(oConfigs.paginator)) {
1094             YAHOO.log("DataTable's paginator model has been revised" +
1095             " -- please refer to the documentation for implementation" +
1096             " details", "warn", this.toString());
1097         }
1098         this._oConfigs = oConfigs;
1099     }
1103  * Initializes ColumnSet.
1105  * @method _initColumnSet
1106  * @param aColumnDefs {Object[]} Array of object literal Column definitions.
1107  * @private
1108  */
1109 YAHOO.widget.DataTable.prototype._initColumnSet = function(aColumnDefs) {
1110     this._oColumnSet = null;
1111     if(YAHOO.lang.isArray(aColumnDefs)) {
1112         this._oColumnSet =  new YAHOO.widget.ColumnSet(aColumnDefs);
1113     }
1114     // Backward compatibility
1115     else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
1116         this._oColumnSet =  aColumnDefs;
1117         YAHOO.log("DataTable's constructor now requires an array" +
1118         " of object literal Column definitions instead of a ColumnSet instance",
1119         "warn", this.toString());
1120     }
1124  * Initializes DataSource.
1126  * @method _initDataSource
1127  * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
1128  * @private
1129  */
1130 YAHOO.widget.DataTable.prototype._initDataSource = function(oDataSource) {
1131     this._oDataSource = null;
1132     if(oDataSource && (oDataSource instanceof YAHOO.util.DataSource)) {
1133         this._oDataSource = oDataSource;
1134     }
1135     // Backward compatibility
1136     else {
1137         var tmpTable = null;
1138         var tmpContainer = this._elContainer;
1139         // Peek in container child nodes to see if TABLE already exists
1140         if(tmpContainer.hasChildNodes()) {
1141             var tmpChildren = tmpContainer.childNodes;
1142             for(i=0; i<tmpChildren.length; i++) {
1143                 if(tmpChildren[i].tagName && tmpChildren[i].tagName.toLowerCase() == "table") {
1144                     tmpTable = tmpChildren[i];
1145                     break;
1146                 }
1147             }
1148             if(tmpTable) {
1149                 var tmpFieldsArray = [];
1150                 for(i=0; i<this._oColumnSet.keys.length; i++) {
1151                     tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
1152                 }
1154                 this._oDataSource = new YAHOO.util.DataSource(tmpTable);
1155                 this._oDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
1156                 this._oDataSource.responseSchema = {fields: tmpFieldsArray};
1157                 YAHOO.log("Null DataSource for progressive enhancement from" +
1158                 " markup has been deprecated", "warn", this.toString());
1159             }
1160         }
1161     }
1165  * Initializes RecordSet.
1167  * @method _initRecordSet
1168  * @private
1169  */
1170 YAHOO.widget.DataTable.prototype._initRecordSet = function() {
1171     if(this._oRecordSet) {
1172         this._oRecordSet.reset();
1173     }
1174     else {
1175         this._oRecordSet = new YAHOO.widget.RecordSet();
1176     }
1180  * Creates HTML markup for TABLE, THEAD and TBODY elements.
1182  * @method _initTableEl
1183  * @private
1184  */
1185 YAHOO.widget.DataTable.prototype._initTableEl = function() {
1186     // Clear the container
1187     YAHOO.util.Event.purgeElement(this._elContainer, true);
1188     this._elContainer.innerHTML = "";
1190     // Create TABLE
1191     this._elTable = this._elContainer.appendChild(document.createElement("table"));
1192     var elTable = this._elTable;
1193     elTable.tabIndex = 0;
1194     elTable.id = this.id + "-table";
1195     YAHOO.util.Dom.addClass(elTable, YAHOO.widget.DataTable.CLASS_TABLE);
1197     // Create THEAD
1198     this._initTheadEl(elTable, this._oColumnSet);
1201     // Create TBODY for messages
1202     var elMsgTbody = document.createElement("tbody");
1203     var elMsgRow = elMsgTbody.appendChild(document.createElement("tr"));
1204     YAHOO.util.Dom.addClass(elMsgRow,YAHOO.widget.DataTable.CLASS_FIRST);
1205     YAHOO.util.Dom.addClass(elMsgRow,YAHOO.widget.DataTable.CLASS_LAST);
1206     this._elMsgRow = elMsgRow;
1207     var elMsgCell = elMsgRow.appendChild(document.createElement("td"));
1208     elMsgCell.colSpan = this._oColumnSet.keys.length;
1209     YAHOO.util.Dom.addClass(elMsgCell,YAHOO.widget.DataTable.CLASS_FIRST);
1210     YAHOO.util.Dom.addClass(elMsgCell,YAHOO.widget.DataTable.CLASS_LAST);
1211     this._elMsgTd = elMsgCell;
1212     this._elMsgTbody = elTable.appendChild(elMsgTbody);
1213     this.showTableMessage(YAHOO.widget.DataTable.MSG_LOADING, YAHOO.widget.DataTable.CLASS_LOADING);
1215     // Create TBODY for data
1216     this._elTbody = elTable.appendChild(document.createElement("tbody"));
1217     YAHOO.util.Dom.addClass(this._elTbody,YAHOO.widget.DataTable.CLASS_BODY);
1221  * Populates THEAD element with TH cells as defined by ColumnSet.
1223  * @method _initTheadEl
1224  * @private
1225  */
1226 YAHOO.widget.DataTable.prototype._initTheadEl = function() {
1227     var i,oColumn, colId;
1228     var oColumnSet = this._oColumnSet;
1229     this._sFirstLabelLinkId = null;
1230     
1231     // Create THEAD
1232     var elThead = document.createElement("thead");
1234     // Iterate through each row of Column headers...
1235     var colTree = oColumnSet.tree;
1236     for(i=0; i<colTree.length; i++) {
1237         var elTheadRow = elThead.appendChild(document.createElement("tr"));
1238         elTheadRow.id = this.id+"-hdrow"+i;
1240         var elTheadCell;
1241         // ...and create THEAD cells
1242         for(var j=0; j<colTree[i].length; j++) {
1243             oColumn = colTree[i][j];
1244             colId = oColumn.getId();
1245             elTheadCell = elTheadRow.appendChild(document.createElement("th"));
1246             elTheadCell.id = this.id + "-col" + colId;
1247             this._initThEl(elTheadCell,oColumn,i,j);
1248         }
1250         // Set FIRST/LAST on THEAD rows
1251         if(i === 0) {
1252             YAHOO.util.Dom.addClass(elTheadRow, YAHOO.widget.DataTable.CLASS_FIRST);
1253         }
1254         if(i === (colTree.length-1)) {
1255             YAHOO.util.Dom.addClass(elTheadRow, YAHOO.widget.DataTable.CLASS_LAST);
1256         }
1257     }
1259     this._elThead = this._elTable.appendChild(elThead);
1261     // Set FIRST/LAST on THEAD cells using the values in ColumnSet headers array
1262     var aFirstHeaders = oColumnSet.headers[0].split(" ");
1263     var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1].split(" ");
1264     for(i=0; i<aFirstHeaders.length; i++) {
1265         YAHOO.util.Dom.addClass(YAHOO.util.Dom.get(this.id+"-col"+aFirstHeaders[i]), YAHOO.widget.DataTable.CLASS_FIRST);
1266     }
1267     for(i=0; i<aLastHeaders.length; i++) {
1268         YAHOO.util.Dom.addClass(YAHOO.util.Dom.get(this.id+"-col"+aLastHeaders[i]), YAHOO.widget.DataTable.CLASS_LAST);
1269     }
1270     
1271     // Add Resizer only after DOM has been updated
1272     var foundDD = (YAHOO.util.DD) ? true : false;
1273     var needDD = false;
1274     for(i=0; i<this._oColumnSet.keys.length; i++) {
1275         oColumn = this._oColumnSet.keys[i];
1276         colId = oColumn.getId();
1277         var elTheadCellId = YAHOO.util.Dom.get(this.id + "-col" + colId);
1278         if(oColumn.resizeable) {
1279             if(foundDD) {
1280                 //TODO: fix fixed width tables
1281                 // Skip the last column for fixed-width tables
1282                 if(!this.fixedWidth || (this.fixedWidth &&
1283                         (oColumn.getKeyIndex() != this._oColumnSet.keys.length-1))) {
1284                     // TODO: better way to get elTheadContainer
1285                     var elThContainer = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_HEADER,"div",elTheadCellId)[0];
1286                     var elThResizer = elThContainer.appendChild(document.createElement("span"));
1287                     elThResizer.id = this.id + "-resizer" + colId;
1288                     YAHOO.util.Dom.addClass(elThResizer,YAHOO.widget.DataTable.CLASS_RESIZER);
1289                     oColumn.ddResizer = new YAHOO.util.ColumnResizer(
1290                             this, oColumn, elTheadCellId, elThResizer.id, elThResizer.id);
1291                     var cancelClick = function(e) {
1292                         YAHOO.util.Event.stopPropagation(e);
1293                     };
1294                     YAHOO.util.Event.addListener(elThResizer,"click",cancelClick);
1295                 }
1296                 if(this.fixedWidth) {
1297                     //TODO: fix fixedWidth
1298                     //elThContainer.style.overflow = "hidden";
1299                     //TODO: better way to get elTheadText
1300                     var elThLabel = (YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_LABEL,"span",elTheadCellId))[0];
1301                     elThLabel.style.overflow = "hidden";
1302                 }
1303             }
1304             else {
1305                 needDD = true;
1306             }
1307         }
1308     }
1309     if(needDD) {
1310         YAHOO.log("Could not find DragDrop dependancy for resizeable Columns", "warn", this.toString());
1311     }
1313     YAHOO.log("Column headers for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
1317  * Populates TH cell as defined by Column.
1319  * @method _initThEl
1320  * @param elTheadCell {HTMLElement} TH cell element reference.
1321  * @param oColumn {YAHOO.widget.Column} Column object.
1322  * @param row {number} Row index.
1323  * @param col {number} Column index.
1324  * @private
1325  */
1326 YAHOO.widget.DataTable.prototype._initThEl = function(elTheadCell,oColumn,row,col) {
1327     // Clear out the cell of prior content
1328     // TODO: purgeListeners and other validation-related things
1329     var index = this._nIndex;
1330     var colId = oColumn.getId();
1331     elTheadCell.yuiColumnId = colId;
1332     if(oColumn.abbr) {
1333         elTheadCell.abbr = oColumn.abbr;
1334     }
1335     if(oColumn.width) {
1336         elTheadCell.style.width = oColumn.width;
1337     }
1339     var aCustomClasses;
1340     if(YAHOO.lang.isString(oColumn.className)) {
1341         aCustomClasses = [oColumn.className];
1342     }
1343     else if(YAHOO.lang.isArray(oColumn.className)) {
1344         aCustomClasses = oColumn.className;
1345     }
1346     if(aCustomClasses) {
1347         for(var i=0; i<aCustomClasses.length; i++) {
1348             YAHOO.util.Dom.addClass(elTheadCell,aCustomClasses[i]);
1349         }
1350     }
1351     
1352     YAHOO.util.Dom.addClass(elTheadCell, "yui-dt-col-"+oColumn.key);
1353     
1354     elTheadCell.innerHTML = "";
1355     elTheadCell.rowSpan = oColumn.getRowspan();
1356     elTheadCell.colSpan = oColumn.getColspan();
1358     var elTheadContainer = elTheadCell.appendChild(document.createElement("div"));
1359     elTheadContainer.id = this.id + "-container" + colId;
1360     YAHOO.util.Dom.addClass(elTheadContainer,YAHOO.widget.DataTable.CLASS_HEADER);
1361     var elTheadLabel = elTheadContainer.appendChild(document.createElement("span"));
1362     elTheadLabel.id = this.id + "-label" + colId;
1363     YAHOO.util.Dom.addClass(elTheadLabel,YAHOO.widget.DataTable.CLASS_LABEL);
1365     var sLabel = YAHOO.lang.isValue(oColumn.label) ? oColumn.label : oColumn.key;
1366     if(oColumn.sortable) {
1367         YAHOO.util.Dom.addClass(elTheadCell,YAHOO.widget.DataTable.CLASS_SORTABLE);
1368         //TODO: Make sortLink customizeable
1369         //TODO: Make title configurable
1370         //TODO: Separate label from an accessibility link that says
1371         // "Click to sort ascending" and push it offscreen
1372         var sLabelLinkId = this.id + "-labellink" + colId;
1373         var sortLink = "?key=" + oColumn.key;
1374         elTheadLabel.innerHTML = "<a id=\"" + sLabelLinkId + "\" href=\"" + sortLink + "\" title=\"Click to sort\" class=\"" + YAHOO.widget.DataTable.CLASS_SORTABLE + "\">" + sLabel + "</a>";
1375         if(!this._sFirstLabelLinkId) {
1376             this._sFirstLabelLinkId = sLabelLinkId;
1377         }
1378     }
1379     else {
1380         elTheadLabel.innerHTML = sLabel;
1381     }
1385  * Creates HTML markup for Cell Editor.
1387  * @method _initCellEditorEl
1388  * @private
1389  */
1390 YAHOO.widget.DataTable.prototype._initCellEditorEl = function() {
1391     // Attach Cell Editor container element to body
1392     var elCellEditor = document.createElement("div");
1393     elCellEditor.id = this.id + "-celleditor";
1394     elCellEditor.style.display = "none";
1395     YAHOO.util.Dom.addClass(elCellEditor, YAHOO.widget.DataTable.CLASS_EDITOR);
1396     elCellEditor = document.body.appendChild(elCellEditor);
1398     // Internal tracker of Cell Editor values
1399     var oCellEditor = {};
1400     oCellEditor.container = elCellEditor;
1401     oCellEditor.value = null;
1402     oCellEditor.isActive = false;
1403     this._oCellEditor = oCellEditor;
1405     // Handle ESC key
1406     this.subscribe("editorKeydownEvent", function(oArgs) {
1407         var e = oArgs.event;
1408         var elTarget = YAHOO.util.Event.getTarget(e);
1410         // ESC hides Cell Editor
1411         if((e.keyCode == 27)) {
1412             this.cancelCellEditor();
1413         }
1414     });
1418  * Initializes Column sorting.
1420  * @method _initColumnSort
1421  * @private
1422  */
1423 YAHOO.widget.DataTable.prototype._initColumnSort = function() {
1424     this.subscribe("headerCellClickEvent", this.onEventSortColumn);
1428  * Initializes DOM event listeners.
1430  * @method _initDomEvents
1431  * @private
1432  */
1433 YAHOO.widget.DataTable.prototype._initDomEvents = function() {
1434     var elTable = this._elTable;
1435     var elThead = this._elThead;
1436     var elTbody = this._elTbody;
1437     var elContainer = this._elContainer;
1439     YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
1440     YAHOO.util.Event.addListener(document, "keydown", this._onDocumentKeydown, this);
1442     YAHOO.util.Event.addListener(elTable, "focus", this._onTableFocus, this);
1443     YAHOO.util.Event.addListener(elTable, "mouseover", this._onTableMouseover, this);
1444     YAHOO.util.Event.addListener(elTable, "mouseout", this._onTableMouseout, this);
1445     YAHOO.util.Event.addListener(elTable, "mousedown", this._onTableMousedown, this);
1446     YAHOO.util.Event.addListener(elTable, "keydown", this._onTableKeydown, this);
1447     YAHOO.util.Event.addListener(elTable, "keypress", this._onTableKeypress, this);
1449     // Since we can't listen for click and dblclick on the same element...
1450     YAHOO.util.Event.addListener(elTable, "dblclick", this._onTableDblclick, this);
1451     YAHOO.util.Event.addListener(elThead, "click", this._onTheadClick, this);
1452     YAHOO.util.Event.addListener(elTbody, "click", this._onTbodyClick, this);
1454     YAHOO.util.Event.addListener(elContainer, "scroll", this._onScroll, this); // for IE
1455     YAHOO.util.Event.addListener(elTbody, "scroll", this._onScroll, this); // for everyone else
1494 // DOM MUTATION FUNCTIONS
1500  * Adds a TR element to the primary TBODY at the page row index if given, otherwise
1501  * at the end of the page. Formats TD elements within the TR element using data
1502  * from the given Record.
1504  * @method _addTrEl
1505  * @param oRecord {YAHOO.widget.Record} Record instance.
1506  * @param index {Number} (optional) The page row index at which to add the TR
1507  * element.
1508  * @return {String} ID of the added TR element, or null.
1509  * @private
1510  */
1511 YAHOO.widget.DataTable.prototype._addTrEl = function(oRecord, index) {
1512     this.hideTableMessage();
1514     // It's an append if no index provided, or index is negative or too big
1515     var append = (!YAHOO.lang.isNumber(index) || (index < 0) ||
1516             (index >= (this._elTbody.rows.length))) ? true : false;
1517             
1518     var oColumnSet = this._oColumnSet;
1519     var oRecordSet = this._oRecordSet;
1520     var isSortedBy = this.get("sortedBy");
1521     var sortedColKeyIndex  = null;
1522     var sortedDir, newClass;
1523     if(isSortedBy) {
1524         sortedColKeyIndex = (isSortedBy.column) ?
1525                 isSortedBy.column.getKeyIndex() :
1526                 this._oColumnSet.getColumn(isSortedBy.key).getKeyIndex();
1527         sortedDir = isSortedBy.dir;
1528         newClass = (sortedDir === "desc") ? YAHOO.widget.DataTable.CLASS_DESC :
1529                 YAHOO.widget.DataTable.CLASS_ASC;
1531     }
1534     var elRow = (append) ? this._elTbody.appendChild(document.createElement("tr")) :
1535         this._elTbody.insertBefore(document.createElement("tr"),this._elTbody.rows[index]);
1537     elRow.id = this.id+"-bdrow"+this._nTrCount;
1538     this._nTrCount++;
1539     elRow.yuiRecordId = oRecord.getId();
1541     // Create TD cells
1542     for(var j=0; j<oColumnSet.keys.length; j++) {
1543         var oColumn = oColumnSet.keys[j];
1544         var elCell = elRow.appendChild(document.createElement("td"));
1545         elCell.id = elRow.id+"-cell"+j;
1546         elCell.yuiColumnId = oColumn.getId();
1547         elCell.headers = oColumnSet.headers[j];
1548         // For SF2 cellIndex bug: http://www.webreference.com/programming/javascript/ppk2/3.html
1549         elCell.yuiCellIndex = j;
1551         // Update UI
1552         this.formatCell(elCell, oRecord, oColumn);
1554         // Set FIRST/LAST on TD
1555         if (j === 0) {
1556             YAHOO.util.Dom.addClass(elCell, YAHOO.widget.DataTable.CLASS_FIRST);
1557         }
1558         else if (j === this._oColumnSet.keys.length-1) {
1559             YAHOO.util.Dom.addClass(elCell, YAHOO.widget.DataTable.CLASS_LAST);
1560         }
1561         
1562         // Remove ASC/DESC
1563         YAHOO.util.Dom.removeClass(elCell, YAHOO.widget.DataTable.CLASS_ASC);
1564         YAHOO.util.Dom.removeClass(elCell, YAHOO.widget.DataTable.CLASS_DESC);
1565         
1566         // Set ASC/DESC on TD
1567         if(j === sortedColKeyIndex) {
1568             newClass = (sortedDir === "desc") ?
1569                     YAHOO.widget.DataTable.CLASS_DESC :
1570                     YAHOO.widget.DataTable.CLASS_ASC;
1571             YAHOO.util.Dom.addClass(elCell, newClass);
1572         }
1575         /*p.abx {word-wrap:break-word;}
1576 ought to solve the problem for Safari (the long words will wrap in your
1577 tds, instead of overflowing to the next td.
1578 (this is supported by IE win as well, so hide it if needed).
1580 One thing, though: it doesn't work in combination with
1581 'white-space:nowrap'.*/
1583 // need a div wrapper for safari?
1584         //TODO: fix fixedWidth
1585         if(this.fixedWidth) {
1586             elCell.style.overflow = "hidden";
1587             //elCell.style.width = "20px";
1588         }
1589     }
1591     return elRow.id;
1595  * Formats all TD elements of given TR element with data from the given Record.
1597  * @method _updateTrEl
1598  * @param elRow {HTMLElement} The TR element to update.
1599  * @param oRecord {YAHOO.widget.Record} The associated Record instance.
1600  * @return {String} ID of the updated TR element, or null.
1601  * @private
1602  */
1603 YAHOO.widget.DataTable.prototype._updateTrEl = function(elRow, oRecord) {
1604     this.hideTableMessage();
1606     var isSortedBy = this.get("sortedBy");
1607     var sortedColKeyIndex  = null;
1608     var sortedDir, newClass;
1609     if(isSortedBy) {
1610         sortedColKeyIndex = (isSortedBy.column) ?
1611                 isSortedBy.column.getKeyIndex() :
1612                 this._oColumnSet.getColumn(isSortedBy.key).getKeyIndex();
1613         sortedDir = isSortedBy.dir;
1614         newClass = (sortedDir === "desc") ? YAHOO.widget.DataTable.CLASS_DESC :
1615                 YAHOO.widget.DataTable.CLASS_ASC;
1616     }
1618     // Update TD elements with new data
1619     for(var j=0; j<elRow.cells.length; j++) {
1620         var oColumn = this._oColumnSet.keys[j];
1621         var elCell = elRow.cells[j];
1622         this.formatCell(elCell, oRecord, oColumn);
1624         // Remove ASC/DESC
1625         YAHOO.util.Dom.removeClass(elCell, YAHOO.widget.DataTable.CLASS_ASC);
1626         YAHOO.util.Dom.removeClass(elCell, YAHOO.widget.DataTable.CLASS_DESC);
1628         // Set ASC/DESC on TD
1629         if(j === sortedColKeyIndex) {
1630             YAHOO.util.Dom.addClass(elCell, newClass);
1631         }
1632     }
1634     // Update Record ID
1635     elRow.yuiRecordId = oRecord.getId();
1636     
1637     return elRow.id;
1642  * Deletes TR element by DOM reference or by DataTable page row index.
1644  * @method _deleteTrEl
1645  * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
1646  * @return {Boolean} Returns true if successful, else returns false.
1647  * @private
1648  */
1649 YAHOO.widget.DataTable.prototype._deleteTrEl = function(row) {
1650     var rowIndex;
1651     
1652     // Get page row index for the element
1653     if(!YAHOO.lang.isNumber(row)) {
1654         rowIndex = YAHOO.util.Dom.get(row).sectionRowIndex;
1655     }
1656     else {
1657         rowIndex = row;
1658     }
1659     if(YAHOO.lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
1660         this._elTbody.deleteRow(rowIndex);
1661         return true;
1662     }
1663     else {
1664         return false;
1665     }
1694 // CSS/STATE FUNCTIONS
1700  * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
1701  * of the DataTable page and updates internal tracker.
1703  * @method _setFirstRow
1704  * @private
1705  */
1706 YAHOO.widget.DataTable.prototype._setFirstRow = function() {
1707     var rowEl = this.getFirstTrEl();
1708     if(rowEl) {
1709         // Remove FIRST
1710         if(this._sFirstTrId) {
1711             YAHOO.util.Dom.removeClass(this._sFirstTrId, YAHOO.widget.DataTable.CLASS_FIRST);
1712         }
1713         // Set FIRST
1714         YAHOO.util.Dom.addClass(rowEl, YAHOO.widget.DataTable.CLASS_FIRST);
1715         this._sFirstTrId = rowEl.id;
1716     }
1717     else {
1718         this._sFirstTrId = null;
1719     }
1723  * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
1724  * of the DataTable page and updates internal tracker.
1726  * @method _setLastRow
1727  * @private
1728  */
1729 YAHOO.widget.DataTable.prototype._setLastRow = function() {
1730     var rowEl = this.getLastTrEl();
1731     if(rowEl) {
1732         // Unassign previous class
1733         if(this._sLastTrId) {
1734             YAHOO.util.Dom.removeClass(this._sLastTrId, YAHOO.widget.DataTable.CLASS_LAST);
1735         }
1736         // Assign class
1737         YAHOO.util.Dom.addClass(rowEl, YAHOO.widget.DataTable.CLASS_LAST);
1738         this._sLastTrId = rowEl.id;
1739     }
1740     else {
1741         this._sLastTrId = null;
1742     }
1746  * Assigns the classes YAHOO.widget.DataTable.CLASS_EVEN and
1747  * YAHOO.widget.DataTable.CLASS_ODD to alternating TR elements of the DataTable
1748  * page. For performance, a subset of rows may be specified.
1750  * @method _setRowStripes
1751  * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
1752  * or string ID, or page row index of where to start striping.
1753  * @param range {Number} (optional) If given, how many rows to stripe, otherwise
1754  * stripe all the rows until the end.
1755  * @private
1756  */
1757 YAHOO.widget.DataTable.prototype._setRowStripes = function(row, range) {
1758     // Default values stripe all rows
1759     var allRows = this._elTbody.rows;
1760     var nStartIndex = 0;
1761     var nEndIndex = allRows.length;
1762     
1763     // Stripe a subset
1764     if((row !== null) && (row !== undefined)) {
1765         // Validate given start row
1766         var elStartRow = this.getTrEl(row);
1767         if(elStartRow) {
1768             nStartIndex = elStartRow.sectionRowIndex;
1769             
1770             // Validate given range
1771             if(YAHOO.lang.isNumber(range) && (range > 1)) {
1772                 nEndIndex = nStartIndex + range;
1773             }
1774         }
1775     }
1777     for(var i=nStartIndex; i<nEndIndex; i++) {
1778         if(i%2) {
1779             YAHOO.util.Dom.removeClass(allRows[i], YAHOO.widget.DataTable.CLASS_EVEN);
1780             YAHOO.util.Dom.addClass(allRows[i], YAHOO.widget.DataTable.CLASS_ODD);
1781         }
1782         else {
1783             YAHOO.util.Dom.removeClass(allRows[i], YAHOO.widget.DataTable.CLASS_ODD);
1784             YAHOO.util.Dom.addClass(allRows[i], YAHOO.widget.DataTable.CLASS_EVEN);
1785         }
1786     }
1833 /////////////////////////////////////////////////////////////////////////////
1835 // Private DOM Event Handlers
1837 /////////////////////////////////////////////////////////////////////////////
1840  * Handles scroll events on the CONTAINER (for IE) and TBODY elements (for everyone else).
1842  * @method _onScroll
1843  * @param e {HTMLEvent} The scroll event.
1844  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
1845  * @private
1846  */
1847 YAHOO.widget.DataTable.prototype._onScroll = function(e, oSelf) {
1848     var elTarget = YAHOO.util.Event.getTarget(e);
1849     var elTag = elTarget.tagName.toLowerCase();
1850     
1851     if(oSelf._oCellEditor.isActive) {
1852         oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
1853         oSelf.cancelCellEditor();
1854     }
1856     oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
1860  * Handles click events on the DOCUMENT.
1862  * @method _onDocumentClick
1863  * @param e {HTMLEvent} The click event.
1864  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
1865  * @private
1866  */
1867 YAHOO.widget.DataTable.prototype._onDocumentClick = function(e, oSelf) {
1868     var elTarget = YAHOO.util.Event.getTarget(e);
1869     var elTag = elTarget.tagName.toLowerCase();
1871     if(!YAHOO.util.Dom.isAncestor(oSelf._elTable, elTarget)) {
1872         oSelf.fireEvent("tableBlurEvent");
1874         // Fires editorBlurEvent when click is not within the TABLE.
1875         // For cases when click is within the TABLE, due to timing issues,
1876         // the editorBlurEvent needs to get fired by the lower-level DOM click
1877         // handlers below rather than by the TABLE click handler directly.
1878         if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
1879             // Only if the click was not within the Cell Editor container
1880             if(!YAHOO.util.Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
1881                     (oSelf._oCellEditor.container.id !== elTarget.id)) {
1882                 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
1883             }
1884         }
1885     }
1889  * Handles keydown events on the DOCUMENT.
1891  * @method _onDocumentKeydown
1892  * @param e {HTMLEvent} The keydown event.
1893  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
1894  * @private
1895  */
1896 YAHOO.widget.DataTable.prototype._onDocumentKeydown = function(e, oSelf) {
1897     var elTarget = YAHOO.util.Event.getTarget(e);
1898     var elTag = elTarget.tagName.toLowerCase();
1900     if(oSelf._oCellEditor && oSelf._oCellEditor.isActive &&
1901             YAHOO.util.Dom.isAncestor(oSelf._oCellEditor.container, elTarget)) {
1902         oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
1903     }
1907  * Handles focus events on the TABLE element.
1909  * @method _onTableFocus
1910  * @param e {HTMLEvent} The focus event.
1911  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
1912  * @private
1913  */
1914 YAHOO.widget.DataTable.prototype._onTableMouseover = function(e, oSelf) {
1915     oSelf.fireEvent("tableFocusEvent");
1919  * Handles mouseover events on the TABLE element.
1921  * @method _onTableMouseover
1922  * @param e {HTMLEvent} The mouseover event.
1923  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
1924  * @private
1925  */
1926 YAHOO.widget.DataTable.prototype._onTableMouseover = function(e, oSelf) {
1927     var elTarget = YAHOO.util.Event.getTarget(e);
1928     var elTag = elTarget.tagName.toLowerCase();
1930     while(elTarget && (elTag != "table")) {
1931         switch(elTag) {
1932             case "body":
1933                  break;
1934             case "a":
1935                 break;
1936             case "td":
1937                 oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
1938                 break;
1939             case "span":
1940                 if(YAHOO.util.Dom.hasClass(elTarget, YAHOO.widget.DataTable.CLASS_LABEL)) {
1941                     oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
1942                 }
1943                 break;
1944             case "th":
1945                 oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
1946                 break;
1947             case "tr":
1948                 if(elTarget.parentNode.tagName.toLowerCase() == "thead") {
1949                     oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
1950                 }
1951                 else {
1952                     oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
1953                 }
1954                 break;
1955             default:
1956                 break;
1957         }
1958         elTarget = elTarget.parentNode;
1959         if(elTarget) {
1960             elTag = elTarget.tagName.toLowerCase();
1961         }
1962     }
1963     oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elTable),event:e});
1967  * Handles mouseout events on the TABLE element.
1969  * @method _onTableMouseout
1970  * @param e {HTMLEvent} The mouseout event.
1971  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
1972  * @private
1973  */
1974 YAHOO.widget.DataTable.prototype._onTableMouseout = function(e, oSelf) {
1975     var elTarget = YAHOO.util.Event.getTarget(e);
1976     var elTag = elTarget.tagName.toLowerCase();
1978     while(elTarget && (elTag != "table")) {
1979         switch(elTag) {
1980             case "body":
1981                 break;
1982             case "a":
1983                 break;
1984             case "td":
1985                 oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
1986                 break;
1987             case "span":
1988                 if(YAHOO.util.Dom.hasClass(elTarget, YAHOO.widget.DataTable.CLASS_LABEL)) {
1989                     oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
1990                 }
1991                 break;
1992             case "th":
1993                 oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
1994                 break;
1995             case "tr":
1996                 if(elTarget.parentNode.tagName.toLowerCase() == "thead") {
1997                     oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
1998                 }
1999                 else {
2000                     oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
2001                 }
2002                 break;
2003             default:
2004                 break;
2005         }
2006         elTarget = elTarget.parentNode;
2007         if(elTarget) {
2008             elTag = elTarget.tagName.toLowerCase();
2009         }
2010     }
2011     oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elTable),event:e});
2015  * Handles mousedown events on the TABLE element.
2017  * @method _onTableMousedown
2018  * @param e {HTMLEvent} The mousedown event.
2019  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2020  * @private
2021  */
2022 YAHOO.widget.DataTable.prototype._onTableMousedown = function(e, oSelf) {
2023     var elTarget = YAHOO.util.Event.getTarget(e);
2024     var elTag = elTarget.tagName.toLowerCase();
2026     while(elTarget && (elTag != "table")) {
2027         switch(elTag) {
2028             case "body":
2029                 break;
2030             case "a":
2031                 break;
2032             case "td":
2033                 oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
2034                 break;
2035             case "span":
2036                 if(YAHOO.util.Dom.hasClass(elTarget, YAHOO.widget.DataTable.CLASS_LABEL)) {
2037                     oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
2038                 }
2039                 break;
2040             case "th":
2041                 oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
2042                 break;
2043             case "tr":
2044                 if(elTarget.parentNode.tagName.toLowerCase() == "thead") {
2045                     oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
2046                 }
2047                 else {
2048                     oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
2049                 }
2050                 break;
2051             default:
2052                 break;
2053         }
2054         elTarget = elTarget.parentNode;
2055         if(elTarget) {
2056             elTag = elTarget.tagName.toLowerCase();
2057         }
2058     }
2059     oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elTable),event:e});
2063  * Handles dblclick events on the TABLE element.
2065  * @method _onTableDblclick
2066  * @param e {HTMLEvent} The dblclick event.
2067  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2068  * @private
2069  */
2070 YAHOO.widget.DataTable.prototype._onTableDblclick = function(e, oSelf) {
2071     var elTarget = YAHOO.util.Event.getTarget(e);
2072     var elTag = elTarget.tagName.toLowerCase();
2074     while(elTarget && (elTag != "table")) {
2075         switch(elTag) {
2076             case "body":
2077                 break;
2078             case "td":
2079                 oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
2080                 break;
2081             case "span":
2082                 if(YAHOO.util.Dom.hasClass(elTarget, YAHOO.widget.DataTable.CLASS_LABEL)) {
2083                     oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
2084                 }
2085                 break;
2086             case "th":
2087                 oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
2088                 break;
2089             case "tr":
2090                 if(elTarget.parentNode.tagName.toLowerCase() == "thead") {
2091                     oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
2092                 }
2093                 else {
2094                     oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
2095                 }
2096                 break;
2097             default:
2098                 break;
2099         }
2100         elTarget = elTarget.parentNode;
2101         if(elTarget) {
2102             elTag = elTarget.tagName.toLowerCase();
2103         }
2104     }
2105     oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elTable),event:e});
2109  * Handles keydown events on the TABLE element. Handles arrow selection.
2111  * @method _onTableKeydown
2112  * @param e {HTMLEvent} The key event.
2113  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2114  * @private
2115  */
2116 YAHOO.widget.DataTable.prototype._onTableKeydown = function(e, oSelf) {
2117     var bSHIFT = e.shiftKey;
2118     var elTarget = YAHOO.util.Event.getTarget(e);
2119     
2120     // Ignore actions in the THEAD
2121     if(YAHOO.util.Dom.isAncestor(oSelf._elThead, elTarget)) {
2122         return;
2123     }
2124     
2125     var nKey = YAHOO.util.Event.getCharCode(e);
2126     
2127     // TAB to first label link if any
2128     if(nKey === 9 && !bSHIFT && (elTarget.id === oSelf._elTable.id)) {
2129         if(oSelf._sFirstLabelLinkId) {
2130             YAHOO.util.Event.stopEvent(e);
2131             oSelf._focusEl(YAHOO.util.Dom.get(oSelf._sFirstLabelLinkId));
2132         }
2133         return;
2134     }
2136     // Something is currently selected
2137     var lastSelectedId = oSelf._sLastSelectedId;
2138     var lastSelectedEl = YAHOO.util.Dom.get(lastSelectedId);
2139     if(lastSelectedEl && oSelf.isSelected(lastSelectedEl)) {
2140         //TODO: handle tab, backspace, delete
2141         
2142         // Handle arrow selection
2143         if((nKey > 36) && (nKey < 41)) {
2144             YAHOO.util.Event.stopEvent(e);
2145         }
2146         else {
2147             return;
2148         }
2150         var sMode = oSelf.get("selectionMode");
2151         var allRows = oSelf._elTbody.rows;
2152         var anchorId = oSelf._sSelectionAnchorId;
2153         var anchorEl = YAHOO.util.Dom.get(anchorId);
2154         var newSelectedEl, trIndex, tdIndex, startIndex, endIndex, i, anchorPos;
2156         ////////////////////////////////////////////////////////////////////////
2157         //
2158         // SHIFT cell block selection
2159         //
2160         ////////////////////////////////////////////////////////////////////////
2161         if(bSHIFT && (sMode == "cellblock")) {
2162             trIndex = lastSelectedEl.parentNode.sectionRowIndex;
2163             tdIndex = lastSelectedEl.yuiCellIndex;
2165             // Arrow DOWN
2166             if(nKey == 40) {
2167                 // Is the anchor cell above, below, or same row
2168                 if(anchorEl.parentNode.sectionRowIndex > trIndex) {
2169                     anchorPos = 1;
2170                 }
2171                 else if(anchorEl.parentNode.sectionRowIndex < trIndex) {
2172                     anchorPos = -1;
2173                 }
2174                 else {
2175                     anchorPos = 0;
2176                 }
2178                 // Is the anchor cell left or right
2179                 startIndex = Math.min(anchorEl.yuiCellIndex, tdIndex);
2180                 endIndex = Math.max(anchorEl.yuiCellIndex, tdIndex);
2182                 // Selecting away from anchor cell
2183                 if(anchorPos <= 0) {
2184                     // Select the horiz block on the next row
2185                     if(trIndex < allRows.length-1) {
2186                         for(i=startIndex; i<=endIndex; i++) {
2187                             newSelectedEl = allRows[trIndex+1].cells[i];
2188                             oSelf.selectCell(newSelectedEl);
2189                         }
2190                         oSelf._sLastSelectedId = allRows[trIndex+1].cells[tdIndex].id;
2191                     }
2192                 }
2193                 // Unselecting towards anchor cell
2194                 else {
2195                     // Unselect the horiz block on this row towards the next row
2196                     for(i=startIndex; i<=endIndex; i++) {
2197                         oSelf.unselectCell(allRows[trIndex].cells[i]);
2198                     }
2199                     oSelf._sLastSelectedId = allRows[trIndex+1].cells[tdIndex].id;
2200                 }
2201             }
2202             // Arrow up
2203             else if(nKey == 38) {
2204                 // Is the anchor cell above, below, or same row
2205                 if(anchorEl.parentNode.sectionRowIndex > trIndex) {
2206                     anchorPos = 1;
2207                 }
2208                 else if(anchorEl.parentNode.sectionRowIndex < trIndex) {
2209                     anchorPos = -1;
2210                 }
2211                 else {
2212                     anchorPos = 0;
2213                 }
2215                 // Is the anchor cell left or right?
2216                 startIndex = Math.min(anchorEl.yuiCellIndex, tdIndex);
2217                 endIndex = Math.max(anchorEl.yuiCellIndex, tdIndex);
2219                 // Selecting away from anchor cell
2220                 if(anchorPos >= 0) {
2221                     // Select the horiz block on the previous row
2222                     if(trIndex > 0) {
2223                         for(i=startIndex; i<=endIndex; i++) {
2224                             newSelectedEl = allRows[trIndex-1].cells[i];
2225                             oSelf.selectCell(newSelectedEl);
2226                         }
2227                         oSelf._sLastSelectedId = allRows[trIndex-1].cells[tdIndex].id;
2228                     }
2229                 }
2230                 // Unselecting towards anchor cell
2231                 else {
2232                     // Unselect the horiz block on this row towards the previous row
2233                     for(i=startIndex; i<=endIndex; i++) {
2234                         oSelf.unselectCell(allRows[trIndex].cells[i]);
2235                     }
2236                     oSelf._sLastSelectedId = allRows[trIndex-1].cells[tdIndex].id;
2237                 }
2238             }
2239             // Arrow right
2240             else if(nKey == 39) {
2241                 // Is the anchor cell left, right, or same column
2242                 if(anchorEl.yuiCellIndex > tdIndex) {
2243                     anchorPos = 1;
2244                 }
2245                 else if(anchorEl.yuiCellIndex < tdIndex) {
2246                     anchorPos = -1;
2247                 }
2248                 else {
2249                     anchorPos = 0;
2250                 }
2252                 // Selecting away from anchor cell
2253                 if(anchorPos <= 0) {
2254                     //Select the next vert block to the right
2255                     if(tdIndex < allRows[trIndex].cells.length-1) {
2256                         startIndex = Math.min(anchorEl.parentNode.sectionRowIndex, trIndex);
2257                         endIndex = Math.max(anchorEl.parentNode.sectionRowIndex, trIndex);
2258                         for(i=startIndex; i<=endIndex; i++) {
2259                             newSelectedEl = allRows[i].cells[tdIndex+1];
2260                             oSelf.selectCell(newSelectedEl);
2261                         }
2262                         oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex+1].id;
2263                     }
2264                 }
2265                 // Unselecting towards anchor cell
2266                 else {
2267                     // Unselect the vert block on this column towards the right
2268                     startIndex = Math.min(anchorEl.parentNode.sectionRowIndex, trIndex);
2269                     endIndex = Math.max(anchorEl.parentNode.sectionRowIndex, trIndex);
2270                     for(i=startIndex; i<=endIndex; i++) {
2271                         oSelf.unselectCell(allRows[i].cells[tdIndex]);
2272                     }
2273                     oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex+1].id;
2274                 }
2275             }
2276             // Arrow left
2277             else if(nKey == 37) {
2278                 // Is the anchor cell left, right, or same column
2279                 if(anchorEl.yuiCellIndex > tdIndex) {
2280                     anchorPos = 1;
2281                 }
2282                 else if(anchorEl.yuiCellIndex < tdIndex) {
2283                     anchorPos = -1;
2284                 }
2285                 else {
2286                     anchorPos = 0;
2287                 }
2289                 // Selecting away from anchor cell
2290                 if(anchorPos >= 0) {
2291                     //Select the previous vert block to the left
2292                     if(tdIndex > 0) {
2293                         startIndex = Math.min(anchorEl.parentNode.sectionRowIndex, trIndex);
2294                         endIndex = Math.max(anchorEl.parentNode.sectionRowIndex, trIndex);
2295                         for(i=startIndex; i<=endIndex; i++) {
2296                             newSelectedEl = allRows[i].cells[tdIndex-1];
2297                             oSelf.selectCell(newSelectedEl);
2298                         }
2299                         oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex-1].id;
2300                     }
2301                 }
2302                 // Unselecting towards anchor cell
2303                 else {
2304                     // Unselect the vert block on this column towards the left
2305                     startIndex = Math.min(anchorEl.parentNode.sectionRowIndex, trIndex);
2306                     endIndex = Math.max(anchorEl.parentNode.sectionRowIndex, trIndex);
2307                     for(i=startIndex; i<=endIndex; i++) {
2308                         oSelf.unselectCell(allRows[i].cells[tdIndex]);
2309                     }
2310                     oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex-1].id;
2311                 }
2312             }
2313         }
2314         ////////////////////////////////////////////////////////////////////////
2315         //
2316         // SHIFT cell range selection
2317         //
2318         ////////////////////////////////////////////////////////////////////////
2319         else if(bSHIFT && (sMode == "cellrange")) {
2320             trIndex = lastSelectedEl.parentNode.sectionRowIndex;
2321             tdIndex = lastSelectedEl.yuiCellIndex;
2323             // Is the anchor cell above, below, or same row
2324             if(anchorEl.parentNode.sectionRowIndex > trIndex) {
2325                 anchorPos = 1;
2326             }
2327             else if(anchorEl.parentNode.sectionRowIndex < trIndex) {
2328                 anchorPos = -1;
2329             }
2330             else {
2331                 anchorPos = 0;
2332             }
2334             // Arrow down
2335             if(nKey == 40) {
2336                 // Selecting away from anchor cell
2337                 if(anchorPos <= 0) {
2338                     // Select all cells to the end of this row
2339                     for(i=tdIndex+1; i<allRows[trIndex].cells.length; i++){
2340                         newSelectedEl = allRows[trIndex].cells[i];
2341                         oSelf.selectCell(newSelectedEl);
2342                     }
2344                     // Select some of the cells on the next row down
2345                     if(trIndex < allRows.length-1) {
2346                         for(i=0; i<=tdIndex; i++){
2347                             newSelectedEl = allRows[trIndex+1].cells[i];
2348                             oSelf.selectCell(newSelectedEl);
2349                         }
2350                     }
2351                 }
2352                 // Unselecting towards anchor cell
2353                 else {
2354                     // Unselect all cells to the end of this row
2355                     for(i=tdIndex; i<allRows[trIndex].cells.length; i++){
2356                         oSelf.unselectCell(allRows[trIndex].cells[i]);
2357                     }
2359                     // Unselect some of the cells on the next row down
2360                     for(i=0; i<tdIndex; i++){
2361                         oSelf.unselectCell(allRows[trIndex+1].cells[i]);
2362                     }
2363                     oSelf._sLastSelectedId = allRows[trIndex+1].cells[tdIndex].id;
2364                 }
2365             }
2366             // Arrow up
2367             else if(nKey == 38) {
2368                 // Selecting away from anchor cell
2369                 if(anchorPos >= 0) {
2370                     // Select all the cells to the beginning of this row
2371                     for(i=tdIndex-1; i>-1; i--){
2372                         newSelectedEl = allRows[trIndex].cells[i];
2373                         oSelf.selectCell(newSelectedEl);
2374                     }
2376                     // Select some of the cells from the end of the previous row
2377                     if(trIndex > 0) {
2378                         for(i=allRows[trIndex].cells.length-1; i>=tdIndex; i--){
2379                             newSelectedEl = allRows[trIndex-1].cells[i];
2380                             oSelf.selectCell(newSelectedEl);
2381                         }
2382                     }
2383                 }
2384                 // Unselecting towards anchor cell
2385                 else {
2386                     // Unselect all the cells to the beginning of this row
2387                     for(i=tdIndex; i>-1; i--){
2388                         oSelf.unselectCell(allRows[trIndex].cells[i]);
2389                     }
2391                     // Unselect some of the cells from the end of the previous row
2392                     for(i=allRows[trIndex].cells.length-1; i>tdIndex; i--){
2393                         oSelf.unselectCell(allRows[trIndex-1].cells[i]);
2394                     }
2395                     oSelf._sLastSelectedId = allRows[trIndex-1].cells[tdIndex].id;
2396                 }
2397             }
2398             // Arrow right
2399             else if(nKey == 39) {
2400                 // Selecting away from anchor cell
2401                 if(anchorPos < 0) {
2402                     // Select the next cell to the right
2403                     if(tdIndex < allRows[trIndex].cells.length-1) {
2404                         newSelectedEl = allRows[trIndex].cells[tdIndex+1];
2405                         oSelf.selectCell(newSelectedEl);
2406                     }
2407                     // Select the first cell of the next row
2408                     else if(trIndex < allRows.length-1) {
2409                         newSelectedEl = allRows[trIndex+1].cells[0];
2410                         oSelf.selectCell(newSelectedEl);
2411                     }
2412                 }
2413                 // Unselecting towards anchor cell
2414                 else if(anchorPos > 0) {
2415                     oSelf.unselectCell(allRows[trIndex].cells[tdIndex]);
2417                     // Unselect this cell towards the right
2418                     if(tdIndex < allRows[trIndex].cells.length-1) {
2419                         oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex+1].id;
2420                     }
2421                     // Unselect this cells towards the first cell of the next row
2422                     else {
2423                         oSelf._sLastSelectedId = allRows[trIndex+1].cells[0].id;
2424                     }
2425                 }
2426                 // Anchor is on this row
2427                 else {
2428                     // Selecting away from anchor
2429                     if(anchorEl.yuiCellIndex <= tdIndex) {
2430                         // Select the next cell to the right
2431                         if(tdIndex < allRows[trIndex].cells.length-1) {
2432                             newSelectedEl = allRows[trIndex].cells[tdIndex+1];
2433                             oSelf.selectCell(newSelectedEl);
2434                         }
2435                         // Select the first cell on the next row
2436                         else if(trIndex < allRows.length-1){
2437                             newSelectedEl = allRows[trIndex+1].cells[0];
2438                             oSelf.selectCell(newSelectedEl);
2439                         }
2440                     }
2441                     // Unselecting towards anchor
2442                     else {
2443                         // Unselect this cell towards the right
2444                         oSelf.unselectCell(allRows[trIndex].cells[tdIndex]);
2445                         oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex+1].id;
2446                     }
2447                 }
2448             }
2449             // Arrow left
2450             else if(nKey == 37) {
2451                 // Unselecting towards the anchor
2452                 if(anchorPos < 0) {
2453                     oSelf.unselectCell(allRows[trIndex].cells[tdIndex]);
2455                     // Unselect this cell towards the left
2456                     if(tdIndex > 0) {
2457                         oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex-1].id;
2458                     }
2459                     // Unselect this cell towards the last cell of the previous row
2460                     else {
2461                         oSelf._sLastSelectedId = allRows[trIndex-1].cells[allRows[trIndex-1].cells.length-1].id;
2462                     }
2463                 }
2464                 // Selecting towards the anchor
2465                 else if(anchorPos > 0) {
2466                     // Select the next cell to the left
2467                     if(tdIndex > 0) {
2468                         newSelectedEl = allRows[trIndex].cells[tdIndex-1];
2469                         oSelf.selectCell(newSelectedEl);
2470                     }
2471                     // Select the last cell of the previous row
2472                     else if(trIndex > 0){
2473                         newSelectedEl = allRows[trIndex-1].cells[allRows[trIndex-1].cells.length-1];
2474                         oSelf.selectCell(newSelectedEl);
2475                     }
2476                 }
2477                 // Anchor is on this row
2478                 else {
2479                     // Selecting away from anchor cell
2480                     if(anchorEl.yuiCellIndex >= tdIndex) {
2481                         // Select the next cell to the left
2482                         if(tdIndex > 0) {
2483                             newSelectedEl = allRows[trIndex].cells[tdIndex-1];
2484                             oSelf.selectCell(newSelectedEl);
2485                         }
2486                         // Select the last cell of the previous row
2487                         else if(trIndex > 0){
2488                             newSelectedEl = allRows[trIndex-1].cells[allRows[trIndex-1].cells.length-1];
2489                             oSelf.selectCell(newSelectedEl);
2490                         }
2491                     }
2492                     // Unselecting towards anchor cell
2493                     else {
2494                         oSelf.unselectCell(allRows[trIndex].cells[tdIndex]);
2496                         // Unselect this cell towards the left
2497                         if(tdIndex > 0) {
2498                             oSelf._sLastSelectedId = allRows[trIndex].cells[tdIndex-1].id;
2499                         }
2500                         // Unselect this cell towards the last cell of the previous row
2501                         else {
2502                             oSelf._sLastSelectedId = allRows[trIndex-1].cells[allRows[trIndex-1].cells.length-1].id;
2503                         }
2504                     }
2505                 }
2506             }
2507         }
2508         ////////////////////////////////////////////////////////////////////////
2509         //
2510         // Simple single cell selection
2511         //
2512         ////////////////////////////////////////////////////////////////////////
2513         else if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
2514             trIndex = lastSelectedEl.parentNode.sectionRowIndex;
2515             tdIndex = lastSelectedEl.yuiCellIndex;
2517             // Arrow down
2518             if(nKey == 40) {
2519                 oSelf.unselectAllCells();
2521                 // Select the next cell down
2522                 if(trIndex < allRows.length-1) {
2523                     newSelectedEl = allRows[trIndex+1].cells[tdIndex];
2524                     oSelf.selectCell(newSelectedEl);
2525                 }
2526                 // Select only the bottom cell
2527                 else {
2528                     newSelectedEl = lastSelectedEl;
2529                     oSelf.selectCell(newSelectedEl);
2530                 }
2532                 oSelf._sSelectionAnchorId = newSelectedEl.id;
2533             }
2534             // Arrow up
2535             else if(nKey == 38) {
2536                 oSelf.unselectAllCells();
2538                 // Select the next cell up
2539                 if(trIndex > 0) {
2540                     newSelectedEl = allRows[trIndex-1].cells[tdIndex];
2541                     oSelf.selectCell(newSelectedEl);
2542                 }
2543                 // Select only the top cell
2544                 else {
2545                     newSelectedEl = lastSelectedEl;
2546                     oSelf.selectCell(newSelectedEl);
2547                 }
2549                 oSelf._sSelectionAnchorId = newSelectedEl.id;
2550             }
2551             // Arrow right
2552             else if(nKey == 39) {
2553                 oSelf.unselectAllCells();
2555                 // Select the next cell to the right
2556                 if(tdIndex < lastSelectedEl.parentNode.cells.length-1) {
2557                     newSelectedEl = lastSelectedEl.parentNode.cells[tdIndex+1];
2558                     oSelf.selectCell(newSelectedEl);
2559                 }
2560                 // Select only the right cell
2561                 else {
2562                     newSelectedEl = lastSelectedEl;
2563                     oSelf.selectCell(newSelectedEl);
2564                 }
2566                 oSelf._sSelectionAnchorId = newSelectedEl.id;
2567             }
2568             // Arrow left
2569             else if(nKey == 37) {
2570                 oSelf.unselectAllCells();
2572                 // Select the next cell to the left
2573                 if(tdIndex > 0) {
2574                     newSelectedEl = lastSelectedEl.parentNode.cells[tdIndex-1];
2575                     oSelf.selectCell(newSelectedEl);
2576                 }
2577                 // Select only the left cell
2578                 else {
2579                     newSelectedEl = lastSelectedEl;
2580                     oSelf.selectCell(newSelectedEl);
2581                 }
2583                 oSelf._sSelectionAnchorId = newSelectedEl.id;
2584             }
2585         }
2586         ////////////////////////////////////////////////////////////////////////
2587         //
2588         // SHIFT row selection
2589         //
2590         ////////////////////////////////////////////////////////////////////////
2591         else if(bSHIFT && (sMode != "single")) {
2592             trIndex = lastSelectedEl.sectionRowIndex;
2594             if(anchorEl.sectionRowIndex > trIndex) {
2595                 anchorPos = 1;
2596             }
2597             else if(anchorEl.sectionRowIndex < trIndex) {
2598                 anchorPos = -1;
2599             }
2600             else {
2601                 anchorPos = 0;
2602             }
2604             // Arrow down
2605             if(nKey == 40) {
2606                 // Selecting away from anchor row
2607                 if(anchorPos <= 0) {
2608                     // Select the next row down
2609                     if(trIndex < allRows.length-1) {
2610                         oSelf.selectRow(trIndex+1);
2611                     }
2612                 }
2613                 // Unselecting toward anchor row
2614                 else {
2615                     // Unselect this row towards the anchor row down
2616                     oSelf.unselectRow(lastSelectedEl);
2617                     oSelf._sLastSelectedId = allRows[trIndex+1].id;
2618                 }
2620             }
2621             // Arrow up
2622             else if(nKey == 38) {
2623                 // Selecting away from anchor row
2624                 if(anchorPos >= 0) {
2625                     // Select the next row up
2626                     if(trIndex > 0) {
2627                         oSelf.selectRow(trIndex-1);
2628                     }
2629                 }
2630                 // Unselect this row towards the anchor row up
2631                 else {
2632                     oSelf.unselectRow(lastSelectedEl);
2633                     oSelf._sLastSelectedId = allRows[trIndex-1].id;
2634                 }
2635             }
2636             // Arrow right
2637             else if(nKey == 39) {
2638                 // Do nothing
2639             }
2640             // Arrow left
2641             else if(nKey == 37) {
2642                 // Do nothing
2643             }
2644         }
2645         ////////////////////////////////////////////////////////////////////////
2646         //
2647         // Simple single row selection
2648         //
2649         ////////////////////////////////////////////////////////////////////////
2650         else {
2651             trIndex = lastSelectedEl.sectionRowIndex;
2653             // Arrow down
2654             if(nKey == 40) {
2655                 oSelf.unselectAllRows();
2657                 // Select the next row
2658                 if(trIndex < allRows.length-1) {
2659                     newSelectedEl = allRows[trIndex+1];
2660                     oSelf.selectRow(newSelectedEl);
2661                 }
2662                 // Select only the last row
2663                 else {
2664                     newSelectedEl = lastSelectedEl;
2665                     oSelf.selectRow(lastSelectedEl);
2666                 }
2668                 oSelf._sSelectionAnchorId = newSelectedEl.id;
2669             }
2670             // Arrow up
2671             else if(nKey == 38) {
2672                 oSelf.unselectAllRows();
2674                 // Select the previous row
2675                 if(trIndex > 0) {
2676                     newSelectedEl = allRows[trIndex-1];
2677                     oSelf.selectRow(newSelectedEl);
2678                 }
2679                 // Select only the first row
2680                 else {
2681                     newSelectedEl = lastSelectedEl;
2682                     oSelf.selectRow(newSelectedEl);
2683                 }
2685                 oSelf._sSelectionAnchorId = newSelectedEl.id;
2686             }
2687             // Arrow right
2688             else if(nKey == 39) {
2689                 // Do nothing
2690             }
2691             // Arrow left
2692             else if(nKey == 37) {
2693                 // Do nothing
2694             }
2695         }
2696     }
2700  * Handles keypress events on the TABLE. Mainly to support stopEvent on Mac.
2702  * @method _onTableKeypress
2703  * @param e {HTMLEvent} The key event.
2704  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2705  * @private
2706  */
2707 YAHOO.widget.DataTable.prototype._onTableKeypress = function(e, oSelf) {
2708     var isMac = (navigator.userAgent.toLowerCase().indexOf("mac") != -1);
2709     if(isMac) {
2710         var nKey = YAHOO.util.Event.getCharCode(e);
2711         // arrow down
2712         if(nKey == 40) {
2713             YAHOO.util.Event.stopEvent(e);
2714         }
2715         // arrow up
2716         else if(nKey == 38) {
2717             YAHOO.util.Event.stopEvent(e);
2718         }
2719     }
2723  * Handles click events on the THEAD element.
2725  * @method _onTheadClick
2726  * @param e {HTMLEvent} The click event.
2727  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2728  * @private
2729  */
2730 YAHOO.widget.DataTable.prototype._onTheadClick = function(e, oSelf) {
2731     var elTarget = YAHOO.util.Event.getTarget(e);
2732     var elTag = elTarget.tagName.toLowerCase();
2734     if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
2735         oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
2736     }
2738     while(elTarget && (elTag != "thead")) {
2739             switch(elTag) {
2740                 case "body":
2741                     break;
2742                 case "span":
2743                     if(YAHOO.util.Dom.hasClass(elTarget, YAHOO.widget.DataTable.CLASS_LABEL)) {
2744                         oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
2745                     }
2746                     break;
2747                 case "th":
2748                     oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
2749                     break;
2750                 case "tr":
2751                     oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
2752                     break;
2753                 default:
2754                     break;
2755             }
2756             elTarget = elTarget.parentNode;
2757             if(elTarget) {
2758                 elTag = elTarget.tagName.toLowerCase();
2759             }
2760     }
2761     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elTable),event:e});
2765  * Handles click events on the primary TBODY element.
2767  * @method _onTbodyClick
2768  * @param e {HTMLEvent} The click event.
2769  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2770  * @private
2771  */
2772 YAHOO.widget.DataTable.prototype._onTbodyClick = function(e, oSelf) {
2773     var elTarget = YAHOO.util.Event.getTarget(e);
2774     var elTag = elTarget.tagName.toLowerCase();
2776     if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
2777         oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
2778     }
2780     while(elTarget && (elTag != "table")) {
2781         switch(elTag) {
2782             case "body":
2783                 break;
2784             case "input":
2785                 if(elTarget.type.toLowerCase() == "checkbox") {
2786                     oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
2787                 }
2788                 else if(elTarget.type.toLowerCase() == "radio") {
2789                     oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
2790                 }
2791                 break;
2792             case "a":
2793                 oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
2794                 break;
2795             case "button":
2796                 oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
2797                 break;
2798             case "td":
2799                 oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
2800                 break;
2801             case "tr":
2802                 oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
2803                 break;
2804             default:
2805                 break;
2806         }
2807         elTarget = elTarget.parentNode;
2808         if(elTarget) {
2809             elTag = elTarget.tagName.toLowerCase();
2810         }
2811     }
2812     oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elTable),event:e});
2815 /*TODO: delete
2816  * Handles keyup events on the TBODY. Executes deletion.
2818  * @method _onTbodyKeyup
2819  * @param e {HTMLEvent} The key event.
2820  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2821  * @private
2822  */
2823 /*YAHOO.widget.DataTable.prototype._onTbodyKeyup = function(e, oSelf) {
2824    var nKey = YAHOO.util.Event.getCharCode(e);
2825     // delete
2826     if(nKey == 46) {//TODO: if something is selected
2827         //TODO: delete row
2828     }
2829 };*/
2832  * Handles click events on paginator links.
2834  * @method _onPaginatorLinkClick
2835  * @param e {HTMLEvent} The click event.
2836  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2837  * @private
2838  */
2839 YAHOO.widget.DataTable.prototype._onPaginatorLinkClick = function(e, oSelf) {
2840     var elTarget = YAHOO.util.Event.getTarget(e);
2841     var elTag = elTarget.tagName.toLowerCase();
2843     if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
2844         oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
2845     }
2847     while(elTarget && (elTag != "table")) {
2848         switch(elTag) {
2849             case "body":
2850                 return;
2851             case "a":
2852                 YAHOO.util.Event.stopEvent(e);
2853                 //TODO: after the showPage call, figure out which link
2854                 //TODO: was clicked and reset focus to the new version of it
2855                 switch(elTarget.className) {
2856                     case YAHOO.widget.DataTable.CLASS_PAGE:
2857                         oSelf.showPage(parseInt(elTarget.innerHTML,10));
2858                         return;
2859                     case YAHOO.widget.DataTable.CLASS_FIRST:
2860                         oSelf.showPage(1);
2861                         return;
2862                     case YAHOO.widget.DataTable.CLASS_LAST:
2863                         oSelf.showPage(oSelf.get("paginator").totalPages);
2864                         return;
2865                     case YAHOO.widget.DataTable.CLASS_PREVIOUS:
2866                         oSelf.showPage(oSelf.get("paginator").currentPage - 1);
2867                         return;
2868                     case YAHOO.widget.DataTable.CLASS_NEXT:
2869                         oSelf.showPage(oSelf.get("paginator").currentPage + 1);
2870                         return;
2871                 }
2872                 break;
2873             default:
2874                 return;
2875         }
2876         elTarget = elTarget.parentNode;
2877         if(elTarget) {
2878             elTag = elTarget.tagName.toLowerCase();
2879         }
2880         else {
2881             return;
2882         }
2883     }
2887  * Handles change events on paginator SELECT element.
2889  * @method _onPaginatorDropdownChange
2890  * @param e {HTMLEvent} The change event.
2891  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2892  * @private
2893  */
2894 YAHOO.widget.DataTable.prototype._onPaginatorDropdownChange = function(e, oSelf) {
2895     var elTarget = YAHOO.util.Event.getTarget(e);
2896     var newValue = elTarget[elTarget.selectedIndex].value;
2898     var newRowsPerPage = YAHOO.lang.isValue(parseInt(newValue,10)) ? parseInt(newValue,10) : null;
2899     if(newRowsPerPage !== null) {
2900         var newStartRecordIndex = (oSelf.get("paginator").currentPage-1) * newRowsPerPage;
2901         oSelf.updatePaginator({rowsPerPage:newRowsPerPage, startRecordIndex:newStartRecordIndex});
2902         oSelf.refreshView();
2903     }
2904     else {
2905         YAHOO.log("Could not paginate with " + newValue + " rows per page", "error", oSelf.toString());
2906     }
2910  * Handles change events on SELECT elements within DataTable.
2912  * @method _onDropdownChange
2913  * @param e {HTMLEvent} The change event.
2914  * @param oSelf {YAHOO.widget.DataTable} DataTable instance.
2915  * @private
2916  */
2917 YAHOO.widget.DataTable.prototype._onDropdownChange = function(e, oSelf) {
2918     var elTarget = YAHOO.util.Event.getTarget(e);
2919     //TODO: pass what args?
2920     //var value = elTarget[elTarget.selectedIndex].value;
2921     oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
2962 /////////////////////////////////////////////////////////////////////////////
2964 // Public member variables
2966 /////////////////////////////////////////////////////////////////////////////
2969 /////////////////////////////////////////////////////////////////////////////
2971 // Public methods
2973 /////////////////////////////////////////////////////////////////////////////
2975 // OBJECT ACCESSORS
2978  * Public accessor to the unique name of the DataSource instance.
2980  * @method toString
2981  * @return {String} Unique name of the DataSource instance.
2982  */
2984 YAHOO.widget.DataTable.prototype.toString = function() {
2985     return "DataTable " + this._sName;
2989  * Returns the DataTable instance's DataSource instance.
2991  * @method getDataSource
2992  * @return {YAHOO.util.DataSource} DataSource instance.
2993  */
2994 YAHOO.widget.DataTable.prototype.getDataSource = function() {
2995     return this._oDataSource;
2999  * Returns the DataTable instance's ColumnSet instance.
3001  * @method getColumnSet
3002  * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
3003  */
3004 YAHOO.widget.DataTable.prototype.getColumnSet = function() {
3005     return this._oColumnSet;
3009  * Returns the DataTable instance's RecordSet instance.
3011  * @method getRecordSet
3012  * @return {YAHOO.widget.RecordSet} RecordSet instance.
3013  */
3014 YAHOO.widget.DataTable.prototype.getRecordSet = function() {
3015     return this._oRecordSet;
3019  * Returns the DataTable instance's Cell Editor as an object literal with the
3020  * following properties:
3021  * <dl>
3022  * <dt>cell</dt>
3023  * <dd>Cell element being edited</dd>
3025  * <dt>column</dt>
3026  * <dd>Associated Column instance</dd>
3028  * <dt>container</dt>
3029  * <dd>Reference to editor's container DIV element</dd>
3031  * <dt>isActive</dt>
3032  * <dd>True if cell is currently being edited</dd>
3034  * <dt>record</dt>
3035  * <dd>Associated Record instance</dd>
3037  * <dt>validator</dt>
3038  * <dd>Associated validator function</dd>
3040  * <dt>value</dt>
3041  * <dd>Current input value</dd>
3042  * </dl>
3049  * @method getCellEditor
3050  * @return {Object} Cell Editor object literal values.
3051  */
3052 YAHOO.widget.DataTable.prototype.getCellEditor = function() {
3053     return this._oCellEditor;
3098 // DOM ACCESSORS
3101  * Returns DOM reference to the DataTable's TABLE element.
3103  * @method getTableEl
3104  * @return {HTMLElement} Reference to TABLE element.
3105  */
3106 YAHOO.widget.DataTable.prototype.getTableEl = function() {
3107     return this._elTable;
3111  * Returns DOM reference to the DataTable's THEAD element.
3113  * @method getTheadEl
3114  * @return {HTMLElement} Reference to THEAD element.
3115  */
3116 YAHOO.widget.DataTable.prototype.getTheadEl = function() {
3117     return this._elThead;
3121  * Returns DOM reference to the DataTable's primary TBODY element.
3123  * @method getTbodyEl
3124  * @return {HTMLElement} Reference to TBODY element.
3125  */
3126 YAHOO.widget.DataTable.prototype.getTbodyEl = function() {
3127     return this._elTbody;
3129 // Backward compatibility
3130 YAHOO.widget.DataTable.prototype.getBody = function() {
3131     YAHOO.log("The method getBody() has been deprecated" +
3132             " in favor of getTbodyEl()", "warn", this.toString());
3133     return this.getTbodyEl();
3137  * Returns DOM reference to the DataTable's secondary TBODY element that is
3138  * used to display messages.
3140  * @method getMsgTbodyEl
3141  * @return {HTMLElement} Reference to TBODY element.
3142  */
3143 YAHOO.widget.DataTable.prototype.getMsgTbodyEl = function() {
3144     return this._elMsgTbody;
3148  * Returns DOM reference to the TD element within the secondary TBODY that is
3149  * used to display messages.
3151  * @method getMsgTdEl
3152  * @return {HTMLElement} Reference to TD element.
3153  */
3154 YAHOO.widget.DataTable.prototype.getMsgTdEl = function() {
3155     return this._elMsgTd;
3159  * Returns the corresponding TR reference for a given DOM element, ID string or
3160  * directly page row index. If the given identifier is a child of a TR element,
3161  * then DOM tree is traversed until a parent TR element is returned, otherwise
3162  * null.
3164  * @method getTrEl
3165  * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
3166  * get: by element reference, ID string, page row index, or Record.
3167  * @return {HTMLElement} Reference to TR element, or null.
3168  */
3169 YAHOO.widget.DataTable.prototype.getTrEl = function(row) {
3170     var allRows = this._elTbody.rows;
3171     
3172     // By Record
3173     if(row instanceof YAHOO.widget.Record) {
3174         var nTrIndex = this.getTrIndex(row);
3175         return allRows[nTrIndex];
3176     }
3177     // By page row index
3178     else if(YAHOO.lang.isNumber(row) && (row > -1) && (row < allRows.length)) {
3179         return allRows[row];
3180     }
3181     // By ID string or element reference
3182     else {
3183         var elRow;
3184         var el = YAHOO.util.Dom.get(row);
3185         
3186         // Validate HTML element
3187         if(el && (el.ownerDocument == document)) {
3188             // Validate TR element
3189             if(el.tagName.toLowerCase() != "tr") {
3190                 // Traverse up the DOM to find the corresponding TR element
3191                 elRow = YAHOO.util.Dom.getAncestorByTagName(el,"tr");
3192             }
3193             else {
3194                 elRow = el;
3195             }
3197             // Make sure the TR is in this TBODY
3198             if(elRow && (elRow.parentNode == this._elTbody)) {
3199                 // Now we can return the TR element
3200                 return elRow;
3201             }
3202         }
3203     }
3204     
3205     YAHOO.log("Could not get TR element for row " + row, "warn", this.toString());
3206     return null;
3208 // Backward compatibility
3209 YAHOO.widget.DataTable.prototype.getRow = function(index) {
3210     YAHOO.log("The method getRow() has been deprecated" +
3211             " in favor of getTrEl()", "warn", this.toString());
3212     return this.getTrEl(index);
3216  * Returns DOM reference to the first TR element in the DataTable page, or null.
3218  * @method getFirstTrEl
3219  * @return {HTMLElement} Reference to TR element.
3220  */
3221 YAHOO.widget.DataTable.prototype.getFirstTrEl = function() {
3222     return this._elTbody.rows[0] || null;
3226  * Returns DOM reference to the last TR element in the DataTable page, or null.
3228  * @method getLastTrEl
3229  * @return {HTMLElement} Reference to last TR element.
3230  */
3231 YAHOO.widget.DataTable.prototype.getLastTrEl = function() {
3232     var allRows = this._elTbody.rows;
3233         if(allRows.length > 0) {
3234             return allRows[allRows.length-1] || null;
3235         }
3239  * Returns DOM reference to the given TD element.
3241  * @method getTdEl
3242  * @param cell {HTMLElement | String} DOM element reference or string ID.
3243  * @return {HTMLElement} Reference to TD element.
3244  */
3245 YAHOO.widget.DataTable.prototype.getTdEl = function(cell) {
3246     var elCell;
3247     var el = YAHOO.util.Dom.get(cell);
3249     // Validate HTML element
3250     if(el && (el.ownerDocument == document)) {
3251         // Validate TD element
3252         if(el.tagName.toLowerCase() != "td") {
3253             // Traverse up the DOM to find the corresponding TR element
3254             elCell = YAHOO.util.Dom.getAncestorByTagName(el, "td");
3255         }
3256         else {
3257             elCell = el;
3258         }
3260         // Make sure the TD is in this TBODY
3261         if(elCell && (elCell.parentNode.parentNode == this._elTbody)) {
3262             // Now we can return the TD element
3263             return elCell;
3264         }
3265     }
3266     
3267     YAHOO.log("Could not get TD element for cell " + cell, "warn", this.toString());
3268     return null;
3272  * Returns DOM reference to the TH element at given DataTable page coordinates, or null.
3274  * @method getThEl
3275  * @param header {HTMLElement | String | YAHOO.widget.Column} DOM element
3276  * reference or string ID, or Column instance.
3277  * @return {HTMLElement} Reference to TH element.
3278  */
3279 YAHOO.widget.DataTable.prototype.getThEl = function(header) {
3280     var elHeader;
3281         
3282     // Validate Column instance
3283     if(header instanceof YAHOO.widget.Column) {
3284         var oColumn = header;
3285         elHeader = YAHOO.util.Dom.get(this.id + "-col" + oColumn.getId());
3286         if(elHeader) {
3287             return elHeader;
3288         }
3289     }
3290     // Validate HTML element
3291     else {
3292         var el = YAHOO.util.Dom.get(header);
3294         if(el && (el.ownerDocument == document)) {
3295             // Validate TH element
3296             if(el.tagName.toLowerCase() != "th") {
3297                 // Traverse up the DOM to find the corresponding TR element
3298                 elHeader = YAHOO.util.Dom.getAncestorByTagName(el,"th");
3299             }
3300             else {
3301                 elHeader = el;
3302             }
3304             // Make sure the TH is in this THEAD
3305             if(elHeader && (elHeader.parentNode.parentNode == this._elThead)) {
3306                 // Now we can return the TD element
3307                 return elHeader;
3308             }
3309         }
3310     }
3312     YAHOO.log("Could not get TH element for header " + header, "warn", this.toString());
3313     return null;
3317  * Returns the page row index of given row. Returns null if the row is not in
3318  * view on the current DataTable page.
3320  * @method getTrIndex
3321  * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
3322  * string reference to an element within the DataTable page, a Record instance,
3323  * or a Record's RecordSet index.
3324  * @return {Number} Page row index, or null if row does not exist or is not in view.
3325  */
3326 YAHOO.widget.DataTable.prototype.getTrIndex = function(row) {
3327     var nRecordIndex;
3328     
3329     // By Record
3330     if(row instanceof YAHOO.widget.Record) {
3331         nRecordIndex = this._oRecordSet.getRecordIndex(row);
3332     }
3333     // Calculate page row index from Record index
3334     else if(YAHOO.lang.isNumber(row)) {
3335         nRecordIndex = row;
3336     }
3337     if(YAHOO.lang.isNumber(nRecordIndex)) {
3338         // DataTable is paginated
3339         if(this.get("paginated")) {
3340             // Get the first and last Record on this page
3341             var startRecordIndex = this.get("paginator").startRecordIndex;
3342             var endRecordIndex = startRecordIndex + this.get("paginator").rowsPerPage - 1;
3343             // This Record is in view
3344             if((nRecordIndex >= startRecordIndex) && (nRecordIndex <= endRecordIndex)) {
3345                 return nRecordIndex - startRecordIndex;
3346             }
3347             // This Record is not in view
3348             else {
3349                 return null;
3350             }
3351         }
3352         // Not paginated, just return the Record index
3353         else {
3354             return nRecordIndex;
3355         }
3357     }
3358     // By element reference or ID string
3359     else {
3360         // Validate TR element
3361         elRow = this.getTrEl(row);
3362         if(elRow && (elRow.ownerDocument == document) &&
3363                 (elRow.parentNode == this._elTbody)) {
3364             return elRow.sectionRowIndex;
3365         }
3366     }
3367     
3368     YAHOO.log("Could not get page row index for row " + row, "warn", this.toString());
3369     return null;
3417 // TABLE FUNCTIONS
3420  * Resets a RecordSet with the given data and populates the page view
3421  * with the new data. Any previous data and selection states are cleared.
3422  * However, sort states are not cleared, so if the given data is in a particular
3423  * sort order, implementers should take care to reset the sortedBy property. If
3424  * pagination is enabled, the currentPage is shown and Paginator UI updated,
3425  * otherwise all rows are displayed as a single page. For performance, existing
3426  * DOM elements are reused when possible.
3428  * @method initializeTable
3429  * @param oData {Object | Object[]} An object literal of data or an array of
3430  * object literals containing data.
3431  */
3432 YAHOO.widget.DataTable.prototype.initializeTable = function(oData) {
3433     // Clear the RecordSet
3434     this._oRecordSet.reset();
3436     // Add data to RecordSet
3437     var records = this._oRecordSet.addRecords(oData);
3439     // Clear selections
3440     this._unselectAllTrEls();
3441     this._unselectAllTdEls();
3442     this._aSelections = null;
3443     this._sLastSelectedId = null;
3444     this._sSelectionAnchorId = null;
3446     // Refresh the view
3447     this.refreshView();
3448     this.fireEvent("initEvent");
3452  * Refreshes the view with existing Records from the RecordSet while
3453  * maintaining sort, pagination, and selection states. For performance, reuses
3454  * existing DOM elements when possible while deleting extraneous elements.
3456  * @method refreshView
3457  */
3458 YAHOO.widget.DataTable.prototype.refreshView = function() {
3459     var i, j, k, l, aRecords;
3460     var oPaginator = this.updatePaginator();
3462     // Paginator is enabled, show a subset of Records and update Paginator UI
3463     if(this.get("paginated")) {
3464         var rowsPerPage = oPaginator.rowsPerPage;
3465         var startRecordIndex = (oPaginator.currentPage - 1) * rowsPerPage;
3466         aRecords = this._oRecordSet.getRecords(startRecordIndex, rowsPerPage);
3467         this.formatPaginators();
3468     }
3469     // Show all records
3470     else {
3471         aRecords = this._oRecordSet.getRecords();
3472     }
3474     var elTbody = this._elTbody;
3475     var elRows = elTbody.rows;
3477     // Has rows
3478     if(YAHOO.lang.isArray(aRecords) && (aRecords.length > 0)) {
3479         this.hideTableMessage();
3481         // Keep track of selected rows
3482         var aSelectedRows = this.getSelectedRows();
3483         // Keep track of selected cells
3484         var aSelectedCells = this.getSelectedCells();
3485         // Anything to reinstate?
3486         var bReselect = (aSelectedRows.length>0) || (aSelectedCells.length > 0);
3488         // Remove extra rows from the bottom so as to preserve ID order
3489         while(elTbody.hasChildNodes() && (elRows.length > aRecords.length)) {
3490             elTbody.deleteRow(-1);
3491         }
3493         // Unselect all TR and TD elements in the UI
3494         if(bReselect) {
3495             this._unselectAllTrEls();
3496             this._unselectAllTdEls();
3497         }
3499         // From the top, update in-place existing rows
3500         for(i=0; i<elRows.length; i++) {
3501             this._updateTrEl(elRows[i], aRecords[i]);
3502         }
3504         // Add TR elements as necessary
3505         for(i=elRows.length; i<aRecords.length; i++) {
3506             this._addTrEl(aRecords[i]);
3507         }
3509         // Reinstate selected and sorted classes
3510         var allRows = elTbody.rows;
3511         if(bReselect) {
3512             // Loop over each row
3513             for(j=0; j<allRows.length; j++) {
3514                 var thisRow = allRows[j];
3515                 var sMode = this.get("selectionMode");
3516                 if ((sMode == "standard") || (sMode == "single")) {
3517                     // Set SELECTED
3518                     for(k=0; k<aSelectedRows.length; k++) {
3519                         if(aSelectedRows[k] === thisRow.yuiRecordId) {
3520                             YAHOO.util.Dom.addClass(thisRow, YAHOO.widget.DataTable.CLASS_SELECTED);
3521                             if(j === allRows.length-1) {
3522                                 this._sLastSelectedId = thisRow.id;
3523                                 this._sSelectionAnchorId = thisRow.id;
3524                             }
3525                         }
3526                     }
3527                 }
3528                 else {
3529                     // Loop over each cell
3530                     for(k=0; k<thisRow.cells.length; k++) {
3531                         var thisCell = thisRow.cells[k];
3532                         // Set SELECTED
3533                         for(l=0; l<aSelectedCells.length; l++) {
3534                             if((aSelectedCells[l].recordId === thisRow.yuiRecordId) &&
3535                                     (aSelectedCells[l].columnId === thisCell.yuiColumnId)) {
3536                                 YAHOO.util.Dom.addClass(thisCell, YAHOO.widget.DataTable.CLASS_SELECTED);
3537                                 if(k === thisRow.cells.length-1) {
3538                                     this._sLastSelectedId = thisCell.id;
3539                                     this._sSelectionAnchorId = thisCell.id;
3540                                 }
3541                             }
3542                         }
3543                     }
3544                 }
3545             }
3546         }
3547         
3548         // Set FIRST/LAST, EVEN/ODD
3549         this._setFirstRow();
3550         this._setLastRow();
3551         this._setRowStripes();
3553         this.fireEvent("refreshEvent");
3554         YAHOO.log("DataTable showing " + aRecords.length + " of " + this._oRecordSet.getLength() + " rows", "info", this.toString());
3555     }
3556     // Empty
3557     else {
3558         // Remove all rows
3559         while(elTbody.hasChildNodes()) {
3560             elTbody.deleteRow(-1);
3561         }
3563         this.showTableMessage(YAHOO.widget.DataTable.MSG_EMPTY, YAHOO.widget.DataTable.CLASS_EMPTY);
3564     }
3568  * Nulls out the entire DataTable instance and related objects, removes attached
3569  * event listeners, and clears out DOM elements inside the container. After
3570  * calling this method, the instance reference should be expliclitly nulled by
3571  * implementer, as in myDataTable = null. Use with caution!
3573  * @method destroy
3574  */
3575 YAHOO.widget.DataTable.prototype.destroy = function() {
3576     // Destroy Cell Editor
3577     YAHOO.util.Event.purgeElement(this._oCellEditor.container, true);
3578     document.body.removeChild(this._oCellEditor.container);
3579     
3580     var instanceName = this.toString();
3581     var elContainer = this._elContainer;
3583     // Unhook custom events
3584     this._oRecordSet.unsubscribeAll();
3585     this.unsubscribeAll();
3587     // Unhook DOM events
3588     YAHOO.util.Event.purgeElement(elContainer, true);
3590     // Remove DOM elements
3591     elContainer.innerHTML = "";
3593     // Null out objects
3594     for(var param in this) {
3595         if(this.hasOwnProperty(param)) {
3596             this[param] = null;
3597         }
3598     }
3600     YAHOO.log("DataTable instance destroyed: " + instanceName);
3604  * Displays message within secondary TBODY.
3606  * @method showTableMessage
3607  * @param sHTML {String} (optional) Value for innerHTML.
3608  * @param sClassName {String} (optional) Classname.
3609  */
3610 YAHOO.widget.DataTable.prototype.showTableMessage = function(sHTML, sClassName) {
3611     var elCell = this._elMsgTd;
3612     if(YAHOO.lang.isString(sHTML)) {
3613         elCell.innerHTML = sHTML;
3614     }
3615     if(YAHOO.lang.isString(sClassName)) {
3616         YAHOO.util.Dom.addClass(elCell, sClassName);
3617     }
3618     this._elMsgTbody.style.display = "";
3619     this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
3620     YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
3624  * Hides secondary TBODY.
3626  * @method hideTableMessage
3627  */
3628 YAHOO.widget.DataTable.prototype.hideTableMessage = function() {
3629     if(this._elMsgTbody.style.display != "none") {
3630         this._elMsgTbody.style.display = "none";
3631         this.fireEvent("tableMsgHideEvent");
3632         YAHOO.log("DataTable message hidden", "info", this.toString());
3633     }
3637  * Brings focus to DataTable instance.
3639  * @method focus
3640  */
3641 YAHOO.widget.DataTable.prototype.focus = function() {
3642     this._focusEl(this._elTable);
3710 // RECORDSET FUNCTIONS
3713  * Returns Record index for given TR element or page row index.
3715  * @method getRecordIndex
3716  * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
3717  * element reference or page row index.
3718  * @return {Number} Record's RecordSet index, or null.
3719  */
3720 YAHOO.widget.DataTable.prototype.getRecordIndex = function(row) {
3721     var nTrIndex;
3723     if(!YAHOO.lang.isNumber(row)) {
3724         // By Record
3725         if(row instanceof YAHOO.widget.Record) {
3726             return this._oRecordSet.getRecordIndex(row);
3727         }
3728         // By element reference
3729         else {
3730             // Find the TR element
3731             var el = this.getTrEl(row);
3732             if(el) {
3733                 nTrIndex = el.sectionRowIndex;
3734             }
3735         }
3736     }
3737     // By page row index
3738     else {
3739         nTrIndex = row;
3740     }
3742     if(YAHOO.lang.isNumber(nTrIndex)) {
3743         if(this.get("paginated")) {
3744             return this.get("paginator").startRecordIndex + nTrIndex;
3745         }
3746         else {
3747             return nTrIndex;
3748         }
3749     }
3751     YAHOO.log("Could not get Record index for row " + row, "warn", this.toString());
3752     return null;
3756  * For the given identifier, returns the associated Record instance.
3758  * @method getRecord
3759  * @param row {HTMLElement | String | Number} RecordSet position index, DOM
3760  * reference or ID string to an element within the DataTable page.
3761  * @return {YAHOO.widget.Record} Record instance.
3762  */
3763 YAHOO.widget.DataTable.prototype.getRecord = function(row) {
3764     var nRecordIndex = row;
3765     
3766     // By element reference or ID string
3767     if(!YAHOO.lang.isNumber(nRecordIndex)) {
3768         // Validate TR element
3769         var elRow = this.getTrEl(row);
3770         if(elRow) {
3771             nRecordIndex = this.getRecordIndex(row);
3772         }
3773     }
3774     // By Record index
3775     if(YAHOO.lang.isNumber(nRecordIndex)) {
3776         return this._oRecordSet.getRecord(nRecordIndex);
3777     }
3778     
3779     YAHOO.log("Could not get Record for row at " + row, "warn", this.toString());
3780     return null;
3828 // COLUMN FUNCTIONS
3831  * For the given identifier, returns the associated Column instance.
3833  * @method getColumn
3834  * @param column {HTMLElement | String | Number} ColumnSet.keys position index, DOM
3835  * reference or ID string to an element within the DataTable page.
3836  * @return {YAHOO.widget.Column} Column instance.
3837  */
3838  YAHOO.widget.DataTable.prototype.getColumn = function(column) {
3839     var nColumnIndex = column;
3841     // By element reference or ID string
3842     if(!YAHOO.lang.isNumber(nColumnIndex)) {
3843         // Validate TD element
3844         var elCell = this.getTdEl(column);
3845         if(elCell) {
3846             nColumnIndex = elCell.yuiColumnId;
3847         }
3848         // Validate TH element
3849         else {
3850             elCell = this.getThEl(column);
3851             if(elCell) {
3852                 nColumnIndex = elCell.yuiColumnId;
3853             }
3854         }
3855     }
3856     
3857     // By Column index
3858     if(YAHOO.lang.isNumber(nColumnIndex)) {
3859         return this._oColumnSet.getColumn(nColumnIndex);
3860     }
3862     YAHOO.log("Could not get Column for column at " + column, "warn", this.toString());
3863     return null;
3864  };
3867  * Sorts given Column.
3869  * @method sortColumn
3870  * @param oColumn {YAHOO.widget.Column} Column instance.
3871  */
3872 YAHOO.widget.DataTable.prototype.sortColumn = function(oColumn) {
3873     if(!oColumn) {
3874         return;
3875     }
3876     if(!oColumn instanceof YAHOO.widget.Column) {
3877         //TODO: accept the TH or TH.key
3878         //TODO: Get the column based on TH.yuiColumnId
3879         return;
3880     }
3881     if(oColumn.sortable) {
3882         // What is the default sort direction?
3883         var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultOrder) ? oColumn.sortOptions.defaultOrder : "asc";
3885         // Already sorted?
3886         var oSortedBy = this.get("sortedBy");
3887         if(oSortedBy && (oSortedBy.key === oColumn.key)) {
3888             if(oSortedBy.dir) {
3889                 sortDir = (oSortedBy.dir == "asc") ? "desc" : "asc";
3890             }
3891             else {
3892                 sortDir = (sortDir == "asc") ? "desc" : "asc";
3893             }
3894         }
3896         // Is there a custom sort handler function defined?
3897         var sortFnc = (oColumn.sortOptions && YAHOO.lang.isFunction(oColumn.sortOptions.sortFunction)) ?
3898                 oColumn.sortOptions.sortFunction : function(a, b, desc) {
3899                     var sorted = YAHOO.util.Sort.compare(a.getData(oColumn.key),b.getData(oColumn.key), desc);
3900                     if(sorted === 0) {
3901                         return YAHOO.util.Sort.compare(a.getId(),b.getId(), desc);
3902                     }
3903                     else {
3904                         return sorted;
3905                     }
3906         };
3908         // Do the actual sort
3909         var desc = (sortDir == "desc") ? true : false;
3910         this._oRecordSet.sortRecords(sortFnc, desc);
3912         // Update sortedBy tracker
3913         this.set("sortedBy", {key:oColumn.key, dir:sortDir, column:oColumn});
3914         
3915         // Reset to first page
3916         //TODO: Keep selection in view
3917         this.updatePaginator({currentPage:1});
3919         // Update the UI
3920         this.refreshView();
3922         this.fireEvent("columnSortEvent",{column:oColumn,dir:sortDir});
3923         YAHOO.log("Column \"" + oColumn.key + "\" sorted \"" + sortDir + "\"", "info", this.toString());
3924     }
3925     else {
3926         //TODO
3927         YAHOO.log("Column is not sortable", "info", this.toString());
3928     }
3975 // ROW FUNCTIONS
3979  * Adds one new Record of data into the RecordSet at the index if given,
3980  * otherwise at the end. If the new Record is in page view, the
3981  * corresponding DOM elements are also updated.
3983  * @method addRow
3984  * @param oData {Object} Object literal of data for the row.
3985  * @param index {Number} (optional) RecordSet position index at which to add data.
3986  */
3987 YAHOO.widget.DataTable.prototype.addRow = function(oData, index) {
3988     if(oData && (oData.constructor == Object)) {
3989         var oRecord = this._oRecordSet.addRecord(oData, index);
3990         if(oRecord) {
3991             var nTrIndex = this.getTrIndex(oRecord);
3993             // Row is in view
3994             if(YAHOO.lang.isNumber(nTrIndex)) {
3995                 // Paginated so just refresh the view to keep pagination state
3996                 if(this.get("paginated")) {
3997                     this.refreshView();
3998                 }
3999                 // Add the TR element
4000                 else {
4001                     var newTrId = this._addTrEl(oRecord, nTrIndex);
4002                     if(newTrId) {
4003                         // Is this an insert or an append?
4004                         var append = (YAHOO.lang.isNumber(nTrIndex) &&
4005                                 (nTrIndex == this._elTbody.rows.length-1)) ? true : false;
4007                         // Stripe the one new row
4008                         if(append) {
4009                             if((this._elTbody.rows.length-1)%2) {
4010                                 YAHOO.util.Dom.addClass(newTrId, YAHOO.widget.DataTable.CLASS_ODD);
4011                             }
4012                             else {
4013                                 YAHOO.util.Dom.addClass(newTrId, YAHOO.widget.DataTable.CLASS_EVEN);
4014                             }
4015                         }
4016                         // Restripe all the rows after the new one
4017                         else {
4018                             this._setRowStripes(nTrIndex);
4019                         }
4021                         // If new row is at the bottom
4022                         if(append) {
4023                             this._setLastRow();
4024                         }
4025                         // If new row is at the top
4026                         else if(YAHOO.lang.isNumber(index) && (nTrIndex === 0)) {
4027                             this._setFirstRow();
4028                         }
4029                     }
4030                 }
4031             }
4032             // Record is not in view so just update pagination UI
4033             else {
4034                 this.updatePaginator();
4035             }
4037             // TODO: what args to pass?
4038             this.fireEvent("rowAddEvent", {record:oRecord});
4040             // For log message
4041             nTrIndex = (YAHOO.lang.isValue(nTrIndex))? nTrIndex : "n/a";
4043             YAHOO.log("Added row: Record ID = " + oRecord.getId() +
4044                     ", Record index = " + this.getRecordIndex(oRecord) +
4045                     ", page row index = " + nTrIndex, "info", this.toString());
4046             return;
4047         }
4048     }
4049     YAHOO.log("Could not add row with " + YAHOO.lang.dump(oData), "error", this.toString());
4053  * Convenience method to add multiple rows.
4055  * @method addRows
4056  * @param aData {Object[]} Array of object literal data for the rows.
4057  * @param index {Number} (optional) RecordSet position index at which to add data.
4058  */
4059 YAHOO.widget.DataTable.prototype.addRows = function(aData, index) {
4060     if(YAHOO.lang.isArray(aData)) {
4061         var i;
4062         if(YAHOO.lang.isNumber(index)) {
4063             for(i=aData.length-1; i>-1; i--) {
4064                 this.addRow(aData[i], index);
4065             }
4066         }
4067         else {
4068             for(i=0; i<aData.length; i++) {
4069                 this.addRow(aData[i]);
4070             }
4071         }
4072     }
4073     else {
4074         YAHOO.log("Could not add rows " + YAHOO.lang.dump(aData));
4075     }
4079  * For the given row, updates the associated Record with the given data. If the
4080  * row is in view, the corresponding DOM elements are also updated.
4082  * @method updateRow
4083  * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
4084  * Which row to update: By Record instance, by Record's RecordSet
4085  * position index, by HTMLElement reference to the TR element, or by ID string
4086  * of the TR element.
4087  * @param oData {Object} Object literal of data for the row.
4088  */
4089 YAHOO.widget.DataTable.prototype.updateRow = function(row, oData) {
4090     var oldRecord, updatedRecord, elRow;
4092     // Get the Record directly
4093     if((row instanceof YAHOO.widget.Record) || (YAHOO.lang.isNumber(row))) {
4094             // Get the Record directly
4095             oldRecord = this._oRecordSet.getRecord(row);
4096             
4097             // Is this row in view?
4098             elRow = this.getTrEl(oldRecord);
4099     }
4100     // Get the Record by TR element
4101     else {
4102         elRow = this.getTrEl(row);
4103         if(elRow) {
4104             oldRecord = this._oRecordSet.getRecord(this.getRecordIndex(elRow));
4105         }
4106     }
4108     // Update the Record
4109     if(oldRecord) {
4110         // Copy data from the Record for the event that gets fired later
4111         var oRecordData = oldRecord.getData();
4112         var oldData = {};
4113         for(var param in oRecordData) {
4114             oldData[param] = oRecordData[param];
4115         }
4117         updatedRecord = this._oRecordSet.updateRecord(oldRecord, oData);
4118     }
4119     else {
4120         YAHOO.log("Could not update row " + row + " with the data : " +
4121                 YAHOO.lang.dump(oData), "error", this.toString());
4122         return;
4124     }
4125     
4126     // Update the TR only if row is in view
4127     if(elRow) {
4128         this._updateTrEl(elRow, updatedRecord);
4129     }
4131     this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
4132     YAHOO.log("DataTable row updated: Record ID = " + updatedRecord.getId() +
4133             ", Record index = " + this.getRecordIndex(updatedRecord) +
4134             ", page row index = " + this.getTrIndex(updatedRecord), "info", this.toString());
4138  * Deletes the given row's Record from the RecordSet. If the row is in view, the
4139  * corresponding DOM elements are also deleted.
4141  * @method deleteRow
4142  * @param row {HTMLElement | String | Number} DOM element reference or ID string
4143  * to DataTable page element or RecordSet index.
4144  */
4145 YAHOO.widget.DataTable.prototype.deleteRow = function(row) {
4146     // Get the Record index...
4147     var nRecordIndex = null;
4148     // ...by Record index
4149     if(YAHOO.lang.isNumber(row)) {
4150         nRecordIndex = row;
4151     }
4152     // ...by element reference
4153     else {
4154         var elRow = YAHOO.util.Dom.get(row);
4155         elRow = this.getTrEl(elRow);
4156         if(elRow) {
4157             nRecordIndex = this.getRecordIndex(elRow);
4158         }
4159     }
4160     if(nRecordIndex !== null) {
4161         var oRecord = this._oRecordSet.getRecord(nRecordIndex);
4162         if(oRecord) {
4163             var nRecordId = oRecord.getId();
4164             
4165             // Remove from selection tracker if there
4166             var tracker = this._aSelections || [];
4167             for(var j=0; j<tracker.length; j++) {
4168                 if((YAHOO.lang.isNumber(tracker[j]) && (tracker[j] === nRecordId)) ||
4169                         ((tracker[j].constructor == Object) && (tracker[j].recordId === nRecordId))) {
4170                     tracker.splice(j,1);
4171                 }
4172             }
4174             // Copy data from the Record for the event that gets fired later
4175             var oRecordData = oRecord.getData();
4176             var oData = {};
4177             for(var param in oRecordData) {
4178                 oData[param] = oRecordData[param];
4179             }
4181             // Delete Record from RecordSet
4182             this._oRecordSet.deleteRecord(nRecordIndex);
4184             // If row is in view, delete the TR element
4185             var nTrIndex = this.getTrIndex(nRecordIndex);
4186             if(YAHOO.lang.isNumber(nTrIndex)) {
4187                 var isLast = (nTrIndex == this.getLastTrEl().sectionRowIndex) ?
4188                         true : false;
4189                 this._deleteTrEl(nTrIndex);
4191                 // Empty body
4192                 if(this._elTbody.rows.length === 0) {
4193                     this.showTableMessage(YAHOO.widget.DataTable.MSG_EMPTY, YAHOO.widget.DataTable.CLASS_EMPTY);
4194                 }
4195                 // Update UI
4196                 else {
4197                     // Set FIRST/LAST
4198                     if(nTrIndex === 0) {
4199                         this._setFirstRow();
4200                     }
4201                     if(isLast) {
4202                         this._setLastRow();
4203                     }
4204                     // Set EVEN/ODD
4205                     if(nTrIndex != this._elTbody.rows.length) {
4206                         this._setRowStripes(nTrIndex);
4207                     }
4208                 }
4209             }
4211             this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,
4212                     oldData:oData, trElIndex:nTrIndex});
4213             YAHOO.log("DataTable row deleted: Record ID = " + nRecordId +
4214                     ", Record index = " + nRecordIndex +
4215                     ", page row index = " + nTrIndex, "info", this.toString());
4216         }
4217     }
4218     else {
4219         YAHOO.log("Could not delete row: " + row, "warn", this.toString());
4220     }
4224  * Convenience method to delete multiple rows.
4226  * @method deleteRows
4227  * @param row {HTMLElement | String | Number} DOM element reference or ID string
4228  * to DataTable page element or RecordSet index.
4229  * @param count {Number} (optional) How many rows to delete. A negative value
4230  * will delete towards the beginning.
4231  */
4232 YAHOO.widget.DataTable.prototype.deleteRows = function(row, count) {
4233     // Get the Record index...
4234     var nRecordIndex = null;
4235     // ...by Record index
4236     if(YAHOO.lang.isNumber(row)) {
4237         nRecordIndex = row;
4238     }
4239     // ...by element reference
4240     else {
4241         var elRow = YAHOO.util.Dom.get(row);
4242         elRow = this.getTrEl(elRow);
4243         if(elRow) {
4244             nRecordIndex = this.getRecordIndex(elRow);
4245         }
4246     }
4247     if(nRecordIndex !== null) {
4248         if(count && YAHOO.lang.isNumber(count)) {
4249             // Start with highest index and work down
4250             var startIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
4251             var endIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
4252             for(var i=startIndex; i>endIndex-1; i--) {
4253                 this.deleteRow(i);
4254             }
4255         }
4256         else {
4257             this.deleteRow(nRecordIndex);
4258         }
4259     }
4260     else {
4261         YAHOO.log("Could not delete row " + row, "info", this.toString());
4262     }
4310 // CELL FUNCTIONS
4313  * Outputs markup into the given TD based on given Record.
4315  * @method formatCell
4316  * @param elCell {HTMLElement} TD Element.
4317  * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
4318  * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
4319  * @return {HTML} Markup.
4320  */
4321 YAHOO.widget.DataTable.prototype.formatCell = function(elCell, oRecord, oColumn) {
4322     if(!(oRecord instanceof YAHOO.widget.Record)) {
4323         oRecord = this.getRecord(elCell);
4324     }
4325     if(!(oColumn instanceof YAHOO.widget.Column)) {
4326         oColumn = this._oColumnSet.getColumn(elCell.yuiColumnId);
4327     }
4328     
4329     if(oRecord && oColumn) {
4330         var oData = oRecord.getData(oColumn.key);
4332         var fnFormatter;
4333         if(YAHOO.lang.isString(oColumn.formatter)) {
4334             switch(oColumn.formatter) {
4335                 case "button":
4336                     fnFormatter = YAHOO.widget.DataTable.formatButton;
4337                     break;
4338                 case "checkbox":
4339                     fnFormatter = YAHOO.widget.DataTable.formatCheckbox;
4340                     break;
4341                 case "currency":
4342                     fnFormatter = YAHOO.widget.DataTable.formatCurrency;
4343                     break;
4344                 case "date":
4345                     fnFormatter = YAHOO.widget.DataTable.formatDate;
4346                     break;
4347                 case "dropdown":
4348                     fnFormatter = YAHOO.widget.DataTable.formatDropdown;
4349                     break;
4350                 case "email":
4351                     fnFormatter = YAHOO.widget.DataTable.formatEmail;
4352                     break;
4353                 case "link":
4354                     fnFormatter = YAHOO.widget.DataTable.formatLink;
4355                     break;
4356                 case "number":
4357                     fnFormatter = YAHOO.widget.DataTable.formatNumber;
4358                     break;
4359                 case "radio":
4360                     fnFormatter = YAHOO.widget.DataTable.formatRadio;
4361                     break;
4362                 case "text":
4363                     fnFormatter = YAHOO.widget.DataTable.formatText;
4364                     break;
4365                 case "textarea":
4366                     fnFormatter = YAHOO.widget.DataTable.formatTextarea;
4367                     break;
4368                 case "textbox":
4369                     fnFormatter = YAHOO.widget.DataTable.formatTextbox;
4370                     break;
4371                 case "html":
4372                     // This is the default
4373                     break;
4374                 default:
4375                     YAHOO.log("Could not find formatter function \"" +
4376                             oColumn.formatter + "\"", "warn", this.toString());
4377                     fnFormatter = null;
4378             }
4379         }
4380         else if(YAHOO.lang.isFunction(oColumn.formatter)) {
4381             fnFormatter = oColumn.formatter;
4382         }
4384         // Apply special formatter
4385         if(fnFormatter) {
4386             fnFormatter.call(this, elCell, oRecord, oColumn, oData);
4387         }
4388         else {
4389             elCell.innerHTML = (YAHOO.lang.isValue(oData)) ? oData.toString() : "";
4390         }
4392         // Add custom classNames
4393         var aCustomClasses = null;
4394         if(YAHOO.lang.isString(oColumn.className)) {
4395             aCustomClasses = [oColumn.className];
4396         }
4397         else if(YAHOO.lang.isArray(oColumn.className)) {
4398             aCustomClasses = oColumn.className;
4399         }
4400         if(aCustomClasses) {
4401             for(var i=0; i<aCustomClasses.length; i++) {
4402                 YAHOO.util.Dom.addClass(elCell, aCustomClasses[i]);
4403             }
4404         }
4405         
4406         YAHOO.util.Dom.addClass(elCell, "yui-dt-col-"+oColumn.key);
4408         // Is editable?
4409         if(oColumn.editor) {
4410             YAHOO.util.Dom.addClass(elCell,YAHOO.widget.DataTable.CLASS_EDITABLE);
4411         }
4412         
4413         this.fireEvent("cellFormatEvent", {record:oRecord, key:oColumn.key, el:elCell});
4414     }
4415     else {
4416         YAHOO.log("Could not format cell " + elCell, "error", this.toString());
4417     }
4422  * Formats a BUTTON element.
4424  * @method DataTable.formatButton
4425  * @param el {HTMLElement} The element to format with markup.
4426  * @param oRecord {YAHOO.widget.Record} Record instance.
4427  * @param oColumn {YAHOO.widget.Column} Column instance.
4428  * @param oData {Object | Boolean} Data value for the cell. By default, the value
4429  * is what gets written to the BUTTON.
4430  * @static
4431  */
4432 YAHOO.widget.DataTable.formatButton= function(el, oRecord, oColumn, oData) {
4433     var sValue = YAHOO.lang.isValue(oData) ? oData : "Click";
4434     //TODO: support YAHOO.widget.Button
4435     //if(YAHOO.widget.Button) {
4436     
4437     //}
4438     //else {
4439         el.innerHTML = "<button type=\"button\" class=\""+
4440                 YAHOO.widget.DataTable.CLASS_BUTTON + "\">" + sValue + "</button>";
4441     //}
4445  * Formats a CHECKBOX element.
4447  * @method DataTable.formatCheckbox
4448  * @param el {HTMLElement} The element to format with markup.
4449  * @param oRecord {YAHOO.widget.Record} Record instance.
4450  * @param oColumn {YAHOO.widget.Column} Column instance.
4451  * @param oData {Object | Boolean} Data value for the cell. Can be a simple
4452  * Boolean to indicate whether checkbox is checked or not. Can be object literal
4453  * {checked:bBoolean, label:sLabel}. Other forms of oData require a custom
4454  * formatter.
4455  * @static
4456  */
4457 YAHOO.widget.DataTable.formatCheckbox = function(el, oRecord, oColumn, oData) {
4458     var bChecked = oData;
4459     bChecked = (bChecked) ? " checked" : "";
4460     el.innerHTML = "<input type=\"checkbox\"" + bChecked +
4461             " class=\"" + YAHOO.widget.DataTable.CLASS_CHECKBOX + "\">";
4465  * Formats currency. Default unit is USD.
4467  * @method DataTable.formatCurrency
4468  * @param el {HTMLElement} The element to format with markup.
4469  * @param oRecord {YAHOO.widget.Record} Record instance.
4470  * @param oColumn {YAHOO.widget.Column} Column instance.
4471  * @param oData {Number} Data value for the cell.
4472  * @static
4473  */
4474 YAHOO.widget.DataTable.formatCurrency = function(el, oRecord, oColumn, oData) {
4475     if(YAHOO.lang.isNumber(oData)) {
4476         var nAmount = oData;
4477         var markup;
4479         // Round to the penny
4480         nAmount = Math.round(nAmount*100)/100;
4482         // Default currency is USD
4483         markup = "$"+nAmount;
4485         // Normalize digits
4486         var dotIndex = markup.indexOf(".");
4487         if(dotIndex < 0) {
4488             markup += ".00";
4489         }
4490         else {
4491             while(dotIndex > markup.length-3) {
4492                 markup += "0";
4493             }
4494         }
4495         el.innerHTML = markup;
4496     }
4497     else {
4498         el.innerHTML = YAHOO.lang.isValue(oData) ? oData : "";
4499     }
4503  * Formats JavaScript Dates.
4505  * @method DataTable.formatDate
4506  * @param el {HTMLElement} The element to format with markup.
4507  * @param oRecord {YAHOO.widget.Record} Record instance.
4508  * @param oColumn {YAHOO.widget.Column} Column instance.
4509  * @param oData {Object} Data value for the cell, or null.
4510  * @static
4511  */
4512 YAHOO.widget.DataTable.formatDate = function(el, oRecord, oColumn, oData) {
4513     var oDate = oData;
4514     if(oDate instanceof Date) {
4515         el.innerHTML = (oDate.getMonth()+1) + "/" + oDate.getDate()  + "/" + oDate.getFullYear();
4516     }
4517     else {
4518         el.innerHTML = YAHOO.lang.isValue(oData) ? oData : "";
4519     }
4523  * Formats SELECT elements.
4525  * @method DataTable.formatDropdown
4526  * @param el {HTMLElement} The element to format with markup.
4527  * @param oRecord {YAHOO.widget.Record} Record instance.
4528  * @param oColumn {YAHOO.widget.Column} Column instance.
4529  * @param oData {Object} Data value for the cell, or null.
4530  * @static
4531  */
4532 YAHOO.widget.DataTable.formatDropdown = function(el, oRecord, oColumn, oData) {
4533     var selectedValue = (YAHOO.lang.isValue(oData)) ? oData : oRecord.getData(oColumn.key);
4534     var options = (YAHOO.lang.isArray(oColumn.dropdownOptions)) ?
4535             oColumn.dropdownOptions : null;
4537     var selectEl;
4538     var collection = el.getElementsByTagName("select");
4539     
4540     // Create the form element only once, so we can attach the onChange listener
4541     if(collection.length === 0) {
4542         // Create SELECT element
4543         selectEl = document.createElement("select");
4544         YAHOO.util.Dom.addClass(selectEl, YAHOO.widget.DataTable.CLASS_DROPDOWN);
4545         selectEl = el.appendChild(selectEl);
4547         // Add event listener
4548         //TODO: static method doesn't have access to the datatable instance...
4549         YAHOO.util.Event.addListener(selectEl,"change",oDataTable._onDropdownChange,oDataTable);
4550     }
4552     selectEl = collection[0];
4554     // Update the form element
4555     if(selectEl) {
4556         // Clear out previous options
4557         selectEl.innerHTML = "";
4558         
4559         // We have options to populate
4560         if(options) {
4561             // Create OPTION elements
4562             for(var i=0; i<options.length; i++) {
4563                 var option = options[i];
4564                 var optionEl = document.createElement("option");
4565                 optionEl.value = (YAHOO.lang.isValue(option.value)) ?
4566                         option.value : option;
4567                 optionEl.innerHTML = (YAHOO.lang.isValue(option.text)) ?
4568                         option.text : option;
4569                 optionEl = selectEl.appendChild(optionEl);
4570             }
4571         }
4572         // Selected value is our only option
4573         else {
4574             selectEl.innerHTML = "<option value=\"" + selectedValue + "\">" + selectedValue + "</option>";
4575         }
4576     }
4577     else {
4578         el.innerHTML = YAHOO.lang.isValue(oData) ? oData : "";
4579     }
4583  * Formats emails.
4585  * @method DataTable.formatEmail
4586  * @param el {HTMLElement} The element to format with markup.
4587  * @param oRecord {YAHOO.widget.Record} Record instance.
4588  * @param oColumn {YAHOO.widget.Column} Column instance.
4589  * @param oData {Object} Data value for the cell, or null.
4590  * @static
4591  */
4592 YAHOO.widget.DataTable.formatEmail = function(el, oRecord, oColumn, oData) {
4593     if(YAHOO.lang.isString(oData)) {
4594         el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
4595     }
4596     else {
4597         el.innerHTML = YAHOO.lang.isValue(oData) ? oData : "";
4598     }
4602  * Formats links.
4604  * @method DataTable.formatLink
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} Data value for the cell, or null.
4609  * @static
4610  */
4611 YAHOO.widget.DataTable.formatLink = function(el, oRecord, oColumn, oData) {
4612     if(YAHOO.lang.isString(oData)) {
4613         el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
4614     }
4615     else {
4616         el.innerHTML = YAHOO.lang.isValue(oData) ? oData : "";
4617     }
4621  * Formats numbers.
4623  * @method DataTable.formatNumber
4624  * @param el {HTMLElement} The element to format with markup.
4625  * @param oRecord {YAHOO.widget.Record} Record instance.
4626  * @param oColumn {YAHOO.widget.Column} Column instance.
4627  * @param oData {Object} Data value for the cell, or null.
4628  * @static
4629  */
4630 YAHOO.widget.DataTable.formatNumber = function(el, oRecord, oColumn, oData) {
4631     if(YAHOO.lang.isNumber(oData)) {
4632         el.innerHTML = oData;
4633     }
4634     else {
4635         el.innerHTML = YAHOO.lang.isValue(oData) ? oData : "";
4636     }
4640  * Formats INPUT TYPE=RADIO elements.
4642  * @method DataTable.formatRadio
4643  * @param el {HTMLElement} The element to format with markup.
4644  * @param oRecord {YAHOO.widget.Record} Record instance.
4645  * @param oColumn {YAHOO.widget.Column} Column instance.
4646  * @param oData {Object} (Optional) Data value for the cell.
4647  * @static
4648  */
4649 YAHOO.widget.DataTable.formatRadio = function(el, oRecord, oColumn, oData) {
4650     var bChecked = oData;
4651     bChecked = (bChecked) ? " checked" : "";
4652     el.innerHTML = "<input type=\"radio\"" + bChecked +
4653             " name=\"" + oColumn.getId() + "-radio\"" +
4654             " class=\"" + YAHOO.widget.DataTable.CLASS_CHECKBOX + "\">";
4658  * Formats text strings.
4660  * @method DataTable.formatText
4661  * @param el {HTMLElement} The element to format with markup.
4662  * @param oRecord {YAHOO.widget.Record} Record instance.
4663  * @param oColumn {YAHOO.widget.Column} Column instance.
4664  * @param oData {Object} (Optional) Data value for the cell.
4665  * @static
4666  */
4667 YAHOO.widget.DataTable.formatText = function(el, oRecord, oColumn, oData) {
4668     var value = (YAHOO.lang.isValue(oRecord.getData(oColumn.key))) ?
4669             oRecord.getData(oColumn.key) : "";
4670     //TODO: move to util function
4671     el.innerHTML = value.toString().replace(/&/g, "&#38;").replace(/</g, "&#60;").replace(/>/g, "&#62;");
4675  * Formats TEXTAREA elements.
4677  * @method DataTable.formatTextarea
4678  * @param el {HTMLElement} The element to format with markup.
4679  * @param oRecord {YAHOO.widget.Record} Record instance.
4680  * @param oColumn {YAHOO.widget.Column} Column instance.
4681  * @param oData {Object} (Optional) Data value for the cell.
4682  * @static
4683  */
4684 YAHOO.widget.DataTable.formatTextarea = function(el, oRecord, oColumn, oData) {
4685     var value = (YAHOO.lang.isValue(oRecord.getData(oColumn.key))) ?
4686             oRecord.getData(oColumn.key) : "";
4687     var markup = "<textarea>" + value + "</textarea>";
4688     el.innerHTML = markup;
4692  * Formats INPUT TYPE=TEXT elements.
4694  * @method DataTable.formatTextbox
4695  * @param el {HTMLElement} The element to format with markup.
4696  * @param oRecord {YAHOO.widget.Record} Record instance.
4697  * @param oColumn {YAHOO.widget.Column} Column instance.
4698  * @param oData {Object} (Optional) Data value for the cell.
4699  * @static
4700  */
4701 YAHOO.widget.DataTable.formatTextbox = function(el, oRecord, oColumn, oData) {
4702     var value = (YAHOO.lang.isValue(oRecord.getData(oColumn.key))) ?
4703             oRecord.getData(oColumn.key) : "";
4704     var markup = "<input type=\"text\" value=\"" + value + "\">";
4705     el.innerHTML = markup;
4755 // PAGINATION
4758  * Updates Paginator values in response to RecordSet changes and/or DOM events.
4759  * Pass in all, a subset, or no values.
4761  * @method updatePaginator
4762  * @param oNewValues {Object} (Optional) Object literal of Paginator values, or
4763  * a subset of Paginator values.
4764  * @param {Object} Object literal of all Paginator values.
4765  */
4767 YAHOO.widget.DataTable.prototype.updatePaginator = function(oNewValues) {
4768     // Complete the set
4769     var oValidPaginator = this.get("paginator");
4770     for(var param in oNewValues) {
4771         if(oValidPaginator.hasOwnProperty(param)) {
4772             oValidPaginator[param] = oNewValues[param];
4773         }
4774     }
4775     
4776     oValidPaginator.totalRecords = this._oRecordSet.getLength();
4777     oValidPaginator.rowsThisPage = Math.min(oValidPaginator.rowsPerPage, oValidPaginator.totalRecords);
4778     oValidPaginator.totalPages = Math.ceil(oValidPaginator.totalRecords / oValidPaginator.rowsThisPage);
4779     if(isNaN(oValidPaginator.totalPages)) {
4780         oValidPaginator.totalPages = 0;
4781     }
4783     this.set("paginator", oValidPaginator);
4784     return this.get("paginator");
4788  * Displays given page of a paginated DataTable.
4790  * @method showPage
4791  * @param nPage {Number} Which page.
4792  */
4793 YAHOO.widget.DataTable.prototype.showPage = function(nPage) {
4794     // Validate input
4795     if(!YAHOO.lang.isNumber(nPage) || (nPage < 1) || (nPage > this.get("paginator").totalPages)) {
4796         nPage = 1;
4797     }
4798     this.updatePaginator({currentPage:nPage});
4799     this.refreshView();
4803  * Updates Paginator containers with markup. Override this method to customize pagination UI.
4805  * @method formatPaginators
4806  */
4807  YAHOO.widget.DataTable.prototype.formatPaginators = function() {
4808     var pag = this.get("paginator");
4810     // For Opera workaround
4811     var dropdownEnabled = false;
4813     // Links are enabled
4814     if(pag.pageLinks > -1) {
4815         for(var i=0; i<pag.links.length; i++) {
4816             this.formatPaginatorLinks(pag.links[i], pag.currentPage, pag.pageLinksStart, pag.pageLinks, pag.totalPages);
4817         }
4818     }
4820     // Dropdown is enabled
4821     for(i=0; i<pag.dropdowns.length; i++) {
4822          if(pag.dropdownOptions) {
4823             dropdownEnabled = true;
4824             this.formatPaginatorDropdown(pag.dropdowns[i], pag.dropdownOptions);
4825         }
4826         else {
4827             pag.dropdowns[i].style.display = "none";
4828         }
4829     }
4831     // For Opera artifacting in dropdowns
4832     if(dropdownEnabled && navigator.userAgent.toLowerCase().indexOf("opera") != -1) {
4833         document.body.style += '';
4834     }
4835     YAHOO.log("Paginators formatted", "info", this.toString());
4839  * Updates Paginator dropdown. If dropdown doesn't exist, the markup is created.
4840  * Sets dropdown elements's "selected" value.
4842  * @method formatPaginatorDropdown
4843  * @param elDropdown {HTMLElement} The SELECT element.
4844  * @param dropdownOptions {Object[]} OPTION values for display in the SELECT element.
4845  */
4846 YAHOO.widget.DataTable.prototype.formatPaginatorDropdown = function(elDropdown, dropdownOptions) {
4847     if(elDropdown && (elDropdown.ownerDocument == document)) {
4848         // Clear OPTION elements
4849         while (elDropdown.firstChild) {
4850             elDropdown.removeChild(elDropdown.firstChild);
4851         }
4853         // Create OPTION elements
4854         for(var j=0; j<dropdownOptions.length; j++) {
4855             var dropdownOption = dropdownOptions[j];
4856             var optionEl = document.createElement("option");
4857             optionEl.value = (YAHOO.lang.isValue(dropdownOption.value)) ?
4858                     dropdownOption.value : dropdownOption;
4859             optionEl.innerHTML = (YAHOO.lang.isValue(dropdownOption.text)) ?
4860                     dropdownOption.text : dropdownOption;
4861             optionEl = elDropdown.appendChild(optionEl);
4862         }
4864         var options = elDropdown.options;
4865         // Update dropdown's "selected" value
4866         if(options.length) {
4867             for(var i=options.length-1; i>-1; i--) {
4868                 if((this.get("paginator").rowsPerPage + "") === options[i].value) {
4869                     options[i].selected = true;
4870                 }
4871             }
4872         }
4874         // Show the dropdown
4875         elDropdown.style.display = "";
4876         return;
4877     }
4878     YAHOO.log("Could not update Paginator dropdown " + elDropdown, "error", this.toString());
4882  * Updates Paginator links container with markup.
4884  * @method formatPaginatorLinks
4885  * @param elContainer {HTMLElement} The link container element.
4886  * @param nCurrentPage {Number} Current page.
4887  * @param nPageLinksStart {Number} First page link to display.
4888  * @param nPageLinksLength {Number} How many page links to display.
4889  * @param nTotalPages {Number} Total number of pages.
4890  */
4891 YAHOO.widget.DataTable.prototype.formatPaginatorLinks = function(elContainer, nCurrentPage, nPageLinksStart, nPageLinksLength, nTotalPages) {
4892     if(elContainer && (elContainer.ownerDocument == document) &&
4893             YAHOO.lang.isNumber(nCurrentPage) && YAHOO.lang.isNumber(nPageLinksStart) &&
4894             YAHOO.lang.isNumber(nTotalPages)) {
4895         // Set up markup for first/last/previous/next
4896         var bIsFirstPage = (nCurrentPage == 1) ? true : false;
4897         var bIsLastPage = (nCurrentPage == nTotalPages) ? true : false;
4898         var sFirstLinkMarkup = (bIsFirstPage) ?
4899                 " <span class=\"" + YAHOO.widget.DataTable.CLASS_DISABLED +
4900                 " " + YAHOO.widget.DataTable.CLASS_FIRST + "\">&lt;&lt;</span> " :
4901                 " <a href=\"#\" class=\"" + YAHOO.widget.DataTable.CLASS_FIRST + "\">&lt;&lt;</a> ";
4902         var sPrevLinkMarkup = (bIsFirstPage) ?
4903                 " <span class=\"" + YAHOO.widget.DataTable.CLASS_DISABLED +
4904                 " " + YAHOO.widget.DataTable.CLASS_PREVIOUS + "\">&lt;</span> " :
4905                 " <a href=\"#\" class=\"" + YAHOO.widget.DataTable.CLASS_PREVIOUS + "\">&lt;</a> " ;
4906         var sNextLinkMarkup = (bIsLastPage) ?
4907                 " <span class=\"" + YAHOO.widget.DataTable.CLASS_DISABLED +
4908                 " " + YAHOO.widget.DataTable.CLASS_NEXT + "\">&gt;</span> " :
4909                 " <a href=\"#\" class=\"" + YAHOO.widget.DataTable.CLASS_NEXT + "\">&gt;</a> " ;
4910         var sLastLinkMarkup = (bIsLastPage) ?
4911                 " <span class=\"" + YAHOO.widget.DataTable.CLASS_DISABLED +
4912                 " " + YAHOO.widget.DataTable.CLASS_LAST +  "\">&gt;&gt;</span> " :
4913                 " <a href=\"#\" class=\"" + YAHOO.widget.DataTable.CLASS_LAST + "\">&gt;&gt;</a> ";
4915         // Start with first and previous
4916         var sMarkup = sFirstLinkMarkup + sPrevLinkMarkup;
4917         
4918         // Ok to show all links
4919         var nMaxLinks = nTotalPages;
4920         var nFirstLink = 1;
4921         var nLastLink = nTotalPages;
4923         if(nPageLinksLength > 0) {
4924         // Calculate how many links to show
4925             nMaxLinks = (nPageLinksStart+nPageLinksLength < nTotalPages) ?
4926                     nPageLinksStart+nPageLinksLength-1 : nTotalPages;
4928             // Try to keep the current page in the middle
4929             nFirstLink = (nCurrentPage - Math.floor(nMaxLinks/2) > 0) ? nCurrentPage - Math.floor(nMaxLinks/2) : 1;
4930             nLastLink = (nCurrentPage + Math.floor(nMaxLinks/2) <= nTotalPages) ? nCurrentPage + Math.floor(nMaxLinks/2) : nTotalPages;
4932             // Keep the last link in range
4933             if(nFirstLink === 1) {
4934                 nLastLink = nMaxLinks;
4935             }
4936             // Keep the first link in range
4937             else if(nLastLink === nTotalPages) {
4938                 nFirstLink = nTotalPages - nMaxLinks + 1;
4939             }
4941             // An even number of links can get funky
4942             if(nLastLink - nFirstLink === nMaxLinks) {
4943                 nLastLink--;
4944             }
4945       }
4946         
4947         // Generate markup for each page
4948         for(var i=nFirstLink; i<=nLastLink; i++) {
4949             if(i != nCurrentPage) {
4950                 sMarkup += " <a href=\"#\" class=\"" + YAHOO.widget.DataTable.CLASS_PAGE + "\">" + i + "</a> ";
4951             }
4952             else {
4953                 sMarkup += " <span class=\"" + YAHOO.widget.DataTable.CLASS_SELECTED + "\">" + i + "</span>";
4954             }
4955         }
4956         sMarkup += sNextLinkMarkup + sLastLinkMarkup;
4957         elContainer.innerHTML = sMarkup;
4958         return;
4959     }
4960     YAHOO.log("Could not format Paginator links", "error", this.toString());
5011 // SELECTION/HIGHLIGHTING
5014  * ID string of last highlighted cell element
5016  * @property _sLastHighlightedCellId
5017  * @type String
5018  * @private
5019  */
5020 YAHOO.widget.DataTable.prototype._sLastHighlightedCellId = null;
5023  * ID string of last highlighted row element
5025  * @property _sLastHighlightedRowId
5026  * @type String
5027  * @private
5028  */
5029 YAHOO.widget.DataTable.prototype._sLastHighlightedRowId = null;
5032  * Array of selections: {recordId:nRecordId, cellIndex:nCellIndex}
5034  * @property _aSelections
5035  * @type Object[]
5036  * @private
5037  */
5038 YAHOO.widget.DataTable.prototype._aSelections = null;
5041  * ID string of last selected element
5043  * @property _sLastSelectedId
5044  * @type String
5045  * @private
5046  */
5047 YAHOO.widget.DataTable.prototype._sLastSelectedId = null;
5050  * ID string of the selection anchor element.
5052  * @property _sSelectionAnchorId
5053  * @type String
5054  * @private
5055  */
5056 YAHOO.widget.DataTable.prototype._sSelectionAnchorId = null;
5059  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
5060  * from all TR elements on the page.
5062  * @method _unselectAllTrEls
5063  * @private
5064  */
5065 YAHOO.widget.DataTable.prototype._unselectAllTrEls = function() {
5066     var selectedRows = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_SELECTED,"tr",this._elTbody);
5067     YAHOO.util.Dom.removeClass(selectedRows, YAHOO.widget.DataTable.CLASS_SELECTED);
5071  * Returns array of selected TR elements on the page.
5073  * @method getSelectedTrEls
5074  * @return {HTMLElement[]} Array of selected TR elements.
5075  */
5076 YAHOO.widget.DataTable.prototype.getSelectedTrEls = function() {
5077     return YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_SELECTED,"tr",this._elTbody);
5081  * Sets given row to the selected state.
5083  * @method selectRow
5084  * @param row {HTMLElement | String} HTML element reference or ID.
5085  */
5086 YAHOO.widget.DataTable.prototype.selectRow = function(row) {
5087     // Validate the row
5088     var elRow = this.getTrEl(row);
5089     if(elRow) {
5090         var oRecord = this.getRecord(elRow);
5091         if(oRecord) {
5092             // Get Record ID
5093             var tracker = this._aSelections || [];
5094             var nRecordId = oRecord.getId();
5095             // Remove if already there
5097             // Use Array.indexOf if available...
5098             if(tracker.indexOf && (tracker.indexOf(nRecordId) >  -1)) {
5099                 tracker.splice(tracker.indexOf(nRecordId),1);
5100             }
5101             // ...or do it the old-fashioned way
5102             else {
5103                 for(var j=0; j<tracker.length; j++) {
5104                    if(tracker[j] === nRecordId){
5105                         tracker.splice(j,1);
5106                     }
5107                 }
5108             }
5109             // Add to the end
5110             tracker.push(nRecordId);
5112             // Update trackers
5113             this._sLastSelectedId = elRow.id;
5114             if(!this._sSelectionAnchorId) {
5115                 this._sSelectionAnchorId = elRow.id;
5116             }
5117             this._aSelections = tracker;
5118         
5119             // Update UI
5120             YAHOO.util.Dom.addClass(elRow, YAHOO.widget.DataTable.CLASS_SELECTED);
5122             this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
5123             YAHOO.log("Selected " + elRow, "info", this.toString());
5125             return;
5126         }
5127     }
5128     YAHOO.log("Could not select " + row, "warn", this.toString());
5130 // Backward compatibility
5131 YAHOO.widget.DataTable.prototype.select = function(els) {
5132     YAHOO.log("The method select() has been deprecated" +
5133             " in favor of selectRow()", "warn", this.toString());
5134     if(!YAHOO.lang.isArray(els)) {
5135         els = [els];
5136     }
5137     for(var i=0; i<els.length; i++) {
5138         this.selectRow(els[i]);
5139     }
5143  * Sets given row to the unselected state.
5145  * @method unselectRow
5146  * @param row {HTMLElement | String} HTML TR element reference or ID.
5147  */
5148 YAHOO.widget.DataTable.prototype.unselectRow = function(row) {
5149     // Validate the row
5150     var elRow = this.getTrEl(row);
5151     if(elRow) {
5152         var oRecord = this.getRecord(elRow);
5153         if(oRecord) {
5154             // Get Record ID
5155             var tracker = this._aSelections || [];
5156             var nRecordId = oRecord.getId();
5158             // Remove if there
5159             var bFound = false;
5160             
5161             // Use Array.indexOf if available...
5162             if(tracker.indexOf && (tracker.indexOf(nRecordId) >  -1)) {
5163                 tracker.splice(tracker.indexOf(nRecordId),1);
5164                 bFound = true;
5165             }
5166             // ...or do it the old-fashioned way
5167             else {
5168                 for(var j=0; j<tracker.length; j++) {
5169                    if(tracker[j] === nRecordId){
5170                         tracker.splice(j,1);
5171                         bFound = true;
5172                     }
5173                 }
5174             }
5176             if(bFound) {
5177                 // Update tracker
5178                 this._aSelections = tracker;
5180                 // Update the UI
5181                 YAHOO.util.Dom.removeClass(elRow, YAHOO.widget.DataTable.CLASS_SELECTED);
5183                 this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
5184                 YAHOO.log("Unselected " + elRow, "info", this.toString());
5186                 return;
5187             }
5188         }
5189     }
5190     YAHOO.log("Could not unselect row " + row, "warn", this.toString());
5194  * Clears out all row selections.
5196  * @method unselectAllRows
5197  */
5198 YAHOO.widget.DataTable.prototype.unselectAllRows = function() {
5199     // Remove from tracker
5200     var tracker = this._aSelections || [];
5201     for(var j=0; j<tracker.length; j++) {
5202        if(YAHOO.lang.isNumber(tracker[j])){
5203             tracker.splice(j,1);
5204         }
5205     }
5207     // Update tracker
5208     this._aSelections = tracker;
5210     // Update UI
5211     this._unselectAllTrEls();
5213     //TODO: send an array of [{el:el,record:record}]
5214     //TODO: or convert this to an unselectRows method
5215     //TODO: that takes an array of rows or unselects all if none given
5216     this.fireEvent("unselectAllRowsEvent");
5217     YAHOO.log("Unselected all rows", "info", this.toString());
5221  * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
5222  * from all TD elements in the internal tracker.
5224  * @method _unselectAllTdEls
5225  * @private
5226  */
5227 YAHOO.widget.DataTable.prototype._unselectAllTdEls = function() {
5228     var selectedCells = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_SELECTED,"td",this._elTbody);
5229     YAHOO.util.Dom.removeClass(selectedCells, YAHOO.widget.DataTable.CLASS_SELECTED);
5233  * Returns array of selected TD elements on the page.
5235  * @method getSelectedTdEls
5236  * @return {HTMLElement[]} Array of selected TD elements.
5237  */
5238 YAHOO.widget.DataTable.prototype.getSelectedTdEls = function() {
5239     return YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_SELECTED,"td",this._elTbody);
5243  * Sets given cell to the selected state.
5245  * @method selectCell
5246  * @param cell {HTMLElement | String} DOM element reference or ID string
5247  * to DataTable page element or RecordSet index.
5248  */
5249 YAHOO.widget.DataTable.prototype.selectCell = function(cell) {
5250     var elCell = this.getTdEl(cell);
5251     
5252     if(elCell) {
5253         var oRecord = this.getRecord(elCell);
5254         var nColumnId = elCell.yuiColumnId;
5256         if(oRecord && YAHOO.lang.isNumber(nColumnId)) {
5257             // Get Record ID
5258             var tracker = this._aSelections || [];
5259             var nRecordId = oRecord.getId();
5261             // Remove if there
5262             for(var j=0; j<tracker.length; j++) {
5263                if((tracker[j].recordId === nRecordId) && (tracker[j].columnId === nColumnId)){
5264                     tracker.splice(j,1);
5265                 }
5266             }
5268             // Add to the end
5269             tracker.push({recordId:nRecordId, columnId:nColumnId});
5271             // Update trackers
5272             this._aSelections = tracker;
5273             this._sLastSelectedId = elCell.id;
5274             if(!this._sSelectionAnchorId) {
5275                 this._sSelectionAnchorId = elCell.id;
5276             }
5278             // Update the UI
5279             YAHOO.util.Dom.addClass(elCell, YAHOO.widget.DataTable.CLASS_SELECTED);
5281             this.fireEvent("cellSelectEvent", {record:oRecord,
5282                     key: this._oColumnSet.getColumn(nColumnId).key, el:elCell});
5283             YAHOO.log("Selected " + elCell, "info", this.toString());
5284             return;
5285         }
5286     }
5287     YAHOO.log("Could not select " + cell, "warn", this.toString());
5291  * Sets given cell to the unselected state.
5293  * @method unselectCell
5294  * @param cell {HTMLElement | String} DOM element reference or ID string
5295  * to DataTable page element or RecordSet index.
5296  */
5297 YAHOO.widget.DataTable.prototype.unselectCell = function(cell) {
5298     var elCell = this.getTdEl(cell);
5300     if(elCell) {
5301         var oRecord = this.getRecord(elCell);
5302         var nColumnId = elCell.yuiColumnId;
5304         if(oRecord && YAHOO.lang.isNumber(nColumnId)) {
5305             // Get Record ID
5306             var tracker = this._aSelections || [];
5307             var id = oRecord.getId();
5309             // Is it selected?
5310             for(var j=0; j<tracker.length; j++) {
5311                 if((tracker[j].recordId === id) && (tracker[j].columnId === nColumnId)){
5312                     // Remove from tracker
5313                     tracker.splice(j,1);
5314                     
5315                     // Update tracker
5316                     this._aSelections = tracker;
5318                     // Update the UI
5319                     YAHOO.util.Dom.removeClass(elCell, YAHOO.widget.DataTable.CLASS_SELECTED);
5321                     this.fireEvent("cellUnselectEvent", {record:oRecord,
5322                             key:this._oColumnSet.getColumn(nColumnId).key, el:elCell});
5323                     YAHOO.log("Unselected " + elCell, "info", this.toString());
5325                     return;
5326                 }
5327             }
5328         }
5329     }
5330     YAHOO.log("Could not unselect " + cell, "warn", this.toString());
5334  * Clears out all cell selections.
5336  * @method unselectAllCells
5337  */
5338 YAHOO.widget.DataTable.prototype.unselectAllCells= function() {
5339     // Remove from tracker
5340     var tracker = this._aSelections || [];
5341     for(var j=0; j<tracker.length; j++) {
5342        if(tracker[j].constructor == Object){
5343             tracker.splice(j,1);
5344         }
5345     }
5347     // Update tracker
5348     this._aSelections = tracker;
5350     // Update UI
5351     this._unselectAllTdEls();
5352     
5353     //TODO: send data
5354     //TODO: or fire individual cellUnselectEvent
5355     this.fireEvent("unselectAllCellsEvent");
5356     YAHOO.log("Unselected all cells", "info", this.toString());
5360  * Returns true if given TR or TD element is select, false otherwise.
5362  * @method isSelected
5363  * @param el {HTMLElement} HTML element reference or ID of a TR or TD.
5364  * @return {Boolean} True if element is selected.
5365  */
5366 YAHOO.widget.DataTable.prototype.isSelected = function(el) {
5367     return YAHOO.util.Dom.hasClass(el,YAHOO.widget.DataTable.CLASS_SELECTED);
5371  * Returns selected rows as an array of Record IDs.
5373  * @method getSelectedRows
5374  * @return {HTMLElement[]} Array of selected rows by Record ID.
5375  */
5376 YAHOO.widget.DataTable.prototype.getSelectedRows = function() {
5377     var aSelectedRows = [];
5378     var tracker = this._aSelections || [];
5379     for(var j=0; j<tracker.length; j++) {
5380        if(YAHOO.lang.isNumber(tracker[j])){
5381             aSelectedRows.push(tracker[j]);
5382         }
5383     }
5384     return aSelectedRows;
5388  * Returns selected cells as an array of object literals:
5389  *     {recordId:nRecordID, columnId:nColumnId}.
5391  * @method getSelectedCells
5392  * @return {HTMLElement[]} Array of selected cells by Record and Column IDs.
5393  */
5394 YAHOO.widget.DataTable.prototype.getSelectedCells = function() {
5395     var aSelectedCells = [];
5396     var tracker = this._aSelections || [];
5397     for(var j=0; j<tracker.length; j++) {
5398        if(tracker[j] && (tracker[j].constructor == Object)){
5399             aSelectedCells.push({recordId:tracker[j].recordId, columnId:tracker[j].columnId});
5400         }
5401     }
5402     return aSelectedCells;
5406  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
5408  * @method highlightRow
5409  * @param row {HTMLElement | String} DOM element reference or ID string.
5410  */
5411 YAHOO.widget.DataTable.prototype.highlightRow = function(row) {
5412     var elRow = this.getTrEl(row);
5414     if(elRow) {
5415         // Make sure previous row is unhighlighted
5416         if(this._sLastHighlightedRowId) {
5417             YAHOO.util.Dom.removeClass(this._sLastHighlightedRowId,YAHOO.widget.DataTable.CLASS_HIGHLIGHTED);
5418         }
5419         var oRecord = this.getRecord(elRow);
5420         YAHOO.util.Dom.addClass(elRow,YAHOO.widget.DataTable.CLASS_HIGHLIGHTED);
5421         this._sLastHighlightedRowId = elRow.id;
5422         this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
5423         YAHOO.log("Highlighted " + elRow, "info", this.toString());
5424         return;
5425     }
5426     YAHOO.log("Could not highlight " + row, "warn", this.toString());
5430  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
5432  * @method unhighlightRow
5433  * @param row {HTMLElement | String} DOM element reference or ID string.
5434  */
5435 YAHOO.widget.DataTable.prototype.unhighlightRow = function(row) {
5436     var elRow = this.getTrEl(row);
5438     if(elRow) {
5439         var oRecord = this.getRecord(elRow);
5440         YAHOO.util.Dom.removeClass(elRow,YAHOO.widget.DataTable.CLASS_HIGHLIGHTED);
5441         this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
5442         YAHOO.log("Unhighlighted " + elRow, "info", this.toString());
5443         return;
5444     }
5445     YAHOO.log("Could not unhighlight " + row, "warn", this.toString());
5449  * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
5451  * @method highlightCell
5452  * @param cell {HTMLElement | String} DOM element reference or ID string.
5453  */
5454 YAHOO.widget.DataTable.prototype.highlightCell = function(cell) {
5455     var elCell = this.getTdEl(cell);
5457     if(elCell) {
5458         // Make sure previous cell is unhighlighted
5459         if(this._sLastHighlightedCellId) {
5460             YAHOO.util.Dom.removeClass(this._sLastHighlightedCellId,YAHOO.widget.DataTable.CLASS_HIGHLIGHTED);
5461         }
5462         
5463         var oRecord = this.getRecord(elCell);
5464         YAHOO.util.Dom.addClass(elCell,YAHOO.widget.DataTable.CLASS_HIGHLIGHTED);
5465         this._sLastHighlightedCellId = elCell.id;
5466         this.fireEvent("cellHighlightEvent", {record:oRecord,
5467                     key:this._oColumnSet.getColumn(elCell.yuiColumnId).key, el:elCell});
5468         YAHOO.log("Highlighted " + elCell, "info", this.toString());
5469         return;
5470     }
5471     YAHOO.log("Could not highlight " + cell, "warn", this.toString());
5475  * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
5477  * @method unhighlightCell
5478  * @param cell {HTMLElement | String} DOM element reference or ID string.
5479  */
5480 YAHOO.widget.DataTable.prototype.unhighlightCell = function(cell) {
5481     var elCell = this.getTdEl(cell);
5483     if(elCell) {
5484         var oRecord = this.getRecord(elCell);
5485         YAHOO.util.Dom.removeClass(elCell,YAHOO.widget.DataTable.CLASS_HIGHLIGHTED);
5486         this.fireEvent("cellUnhighlightEvent", {record:oRecord,
5487                     key:this._oColumnSet.getColumn(elCell.yuiColumnId).key, el:elCell});
5488         YAHOO.log("Unhighlighted " + elCell, "info", this.toString());
5489         return;
5490     }
5491     YAHOO.log("Could not unhighlight " + cell, "warn", this.toString());
5538 // INLINE EDITING
5540 /*TODO: for TAB handling
5541  * Shows Cell Editor for next cell.
5543  * @method editNextCell
5544  * @param elCell {HTMLElement} Cell element from which to edit next cell.
5545  */
5546 //YAHOO.widget.DataTable.prototype.editNextCell = function(elCell) {
5547 //};
5550  * Shows Cell Editor for given cell.
5552  * @method showCellEditor
5553  * @param elCell {HTMLElement | String} Cell element to edit.
5554  * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
5555  * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
5556  */
5557 YAHOO.widget.DataTable.prototype.showCellEditor = function(elCell, oRecord, oColumn) {
5558     elCell = YAHOO.util.Dom.get(elCell);
5559     
5560     if(elCell && (elCell.ownerDocument === document)) {
5561         if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
5562             oRecord = this.getRecord(elCell);
5563         }
5564         if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
5565             oColumn = this.getColumn(elCell);
5566         }
5567         if(oRecord && oColumn) {
5568             var oCellEditor = this._oCellEditor;
5569             
5570             // Clear previous Editor
5571             if(oCellEditor.isActive) {
5572                 this.cancelCellEditor();
5573             }
5575             // Editor not defined
5576             if(!oColumn.editor) {
5577                 return;
5578             }
5579             
5580             // Update Editor values
5581             oCellEditor.cell = elCell;
5582             oCellEditor.record = oRecord;
5583             oCellEditor.column = oColumn;
5584             oCellEditor.validator = (oColumn.editorOptions &&
5585                     YAHOO.lang.isFunction(oColumn.editorOptions.validator)) ?
5586                     oColumn.editorOptions.validator : null;
5587             oCellEditor.value = oRecord.getData(oColumn.key);
5589             // Move Editor
5590             var elContainer = oCellEditor.container;
5591             var x = YAHOO.util.Dom.getX(elCell);
5592             var y = YAHOO.util.Dom.getY(elCell);
5594             // SF doesn't get xy for cells in scrolling table
5595             // when tbody display is set to block
5596             if(isNaN(x) || isNaN(y)) {
5597                 x = elCell.offsetLeft + // cell pos relative to table
5598                         YAHOO.util.Dom.getX(this._elTable) - // plus table pos relative to document
5599                         this._elTbody.scrollLeft; // minus tbody scroll
5600                 y = elCell.offsetTop + // cell pos relative to table
5601                         YAHOO.util.Dom.getY(this._elTable) - // plus table pos relative to document
5602                         this._elTbody.scrollTop + // minus tbody scroll
5603                         this._elThead.offsetHeight; // account for fixed headers
5604             }
5605             
5606             elContainer.style.left = x + "px";
5607             elContainer.style.top = y + "px";
5609             // Show Editor
5610             elContainer.style.display = "";
5611             
5612             // Render Editor markup
5613             var fnEditor;
5614             if(YAHOO.lang.isString(oColumn.editor)) {
5615                 switch(oColumn.editor) {
5616                     case "checkbox":
5617                         fnEditor = YAHOO.widget.DataTable.editCheckbox;
5618                         break;
5619                     case "date":
5620                         fnEditor = YAHOO.widget.DataTable.editDate;
5621                         break;
5622                     case "dropdown":
5623                         fnEditor = YAHOO.widget.DataTable.editDropdown;
5624                         break;
5625                     case "radio":
5626                         fnEditor = YAHOO.widget.DataTable.editRadio;
5627                         break;
5628                     case "textarea":
5629                         fnEditor = YAHOO.widget.DataTable.editTextarea;
5630                         break;
5631                     case "textbox":
5632                         fnEditor = YAHOO.widget.DataTable.editTextbox;
5633                         break;
5634                     default:
5635                         fnEditor = null;
5636                 }
5637             }
5638             else if(YAHOO.lang.isFunction(oColumn.editor)) {
5639                 fnEditor = oColumn.editor;
5640             }
5642             if(fnEditor) {
5643                 // Create DOM input elements
5644                 fnEditor(this._oCellEditor, this);
5645                 
5646                 // Show Save/Cancel buttons
5647                 if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
5648                     this.showCellEditorBtns(elContainer);
5649                 }
5651                 // Hook to customize the UI
5652                 this.doBeforeShowCellEditor(this._oCellEditor);
5654                 oCellEditor.isActive = true;
5655                 
5656                 //TODO: verify which args to pass
5657                 this.fireEvent("editorShowEvent", {editor:oCellEditor});
5658                 YAHOO.log("Cell Editor shown for " + elCell, "info", this.toString());
5659                 return;
5660             }
5661         }
5662     }
5663     YAHOO.log("Could not show Cell Editor for " + elCell, "warn", this.toString());
5667  * Overridable abstract method to customize Cell Editor UI.
5669  * @method doBeforeShowCellEditor
5670  * @param oCellEditor {Object} Cell Editor object literal.
5671  */
5672 YAHOO.widget.DataTable.prototype.doBeforeShowCellEditor = function(oCellEditor) {
5676  * Adds Save/Cancel buttons to Cell Editor.
5678  * @method showCellEditorBtns
5679  * @param elContainer {HTMLElement} Cell Editor container.
5680  */
5681 YAHOO.widget.DataTable.prototype.showCellEditorBtns = function(elContainer) {
5682     // Buttons
5683     var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
5684     YAHOO.util.Dom.addClass(elBtnsDiv, YAHOO.widget.DataTable.CLASS_BUTTON);
5686     // Save button
5687     var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
5688     YAHOO.util.Dom.addClass(elSaveBtn, YAHOO.widget.DataTable.CLASS_DEFAULT);
5689     elSaveBtn.innerHTML = "OK";
5690     YAHOO.util.Event.addListener(elSaveBtn, "click", this.saveCellEditor, this, true);
5692     // Cancel button
5693     var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
5694     elCancelBtn.innerHTML = "Cancel";
5695     YAHOO.util.Event.addListener(elCancelBtn, "click", this.cancelCellEditor, this, true);
5699  * Clears Cell Editor of all state and UI.
5701  * @method resetCellEditor
5702  */
5704 YAHOO.widget.DataTable.prototype.resetCellEditor = function() {
5705     var elContainer = this._oCellEditor.container;
5706     elContainer.style.display = "none";
5707     YAHOO.util.Event.purgeElement(elContainer, true);
5708     elContainer.innerHTML = "";
5709     this._oCellEditor.value = null;
5710     this._oCellEditor.isActive = false;
5714  * Saves Cell Editor input to Record.
5716  * @method saveCellEditor
5717  */
5718 YAHOO.widget.DataTable.prototype.saveCellEditor = function() {
5719     //TODO: Copy the editor's values to pass to the event
5720     if(this._oCellEditor.isActive) {
5721         var newData = this._oCellEditor.value;
5722         var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
5724         // Validate input data
5725         if(this._oCellEditor.validator) {
5726             this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData);
5727             if(this._oCellEditor.value === null ) {
5728                 this.resetCellEditor();
5729                 this.fireEvent("editorRevertEvent",
5730                         {editor:this._oCellEditor, oldData:oldData, newData:newData});
5731                 YAHOO.log("Could not save Cell Editor input due to invalid data " +
5732                         YAHOO.lang.dump(newData), "warn", this.toString());
5733                 return;
5734             }
5735         }
5737         // Update the Record
5738         this._oRecordSet.updateKey(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
5740         // Update the UI
5741         this.formatCell(this._oCellEditor.cell);
5743         // Clear out the Cell Editor
5744         this.resetCellEditor();
5746         this.fireEvent("editorSaveEvent",
5747                 {editor:this._oCellEditor, oldData:oldData, newData:newData});
5748         YAHOO.log("Cell Editor input saved", "info", this.toString());
5749     }
5750     else {
5751         YAHOO.log("Cell Editor not active to save input", "warn", this.toString());
5752     }
5756  * Cancels Cell Editor.
5758  * @method cancelCellEditor
5759  */
5760 YAHOO.widget.DataTable.prototype.cancelCellEditor = function() {
5761     if(this._oCellEditor.isActive) {
5762         this.resetCellEditor();
5763         //TODO: preserve values for the event?
5764         this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
5765         YAHOO.log("Cell Editor input canceled", "info", this.toString());
5766     }
5767     else {
5768         YAHOO.log("Cell Editor not active to cancel input", "warn", this.toString());
5769     }
5773  * Enables CHECKBOX Editor.
5775  * @method editCheckbox
5776  */
5777 //YAHOO.widget.DataTable.editCheckbox = function(elContainer, oRecord, oColumn, oEditor, oSelf) {
5778 YAHOO.widget.DataTable.editCheckbox = function(oEditor, oSelf) {
5779     var elCell = oEditor.cell;
5780     var oRecord = oEditor.record;
5781     var oColumn = oEditor.column;
5782     var elContainer = oEditor.container;
5783     var aCheckedValues = oRecord.getData(oColumn.key);
5784     if(!YAHOO.lang.isArray(aCheckedValues)) {
5785         aCheckedValues = [aCheckedValues];
5786     }
5788     // Checkboxes
5789     if(oColumn.editorOptions && YAHOO.lang.isArray(oColumn.editorOptions.checkboxOptions)) {
5790         var checkboxOptions = oColumn.editorOptions.checkboxOptions;
5791         var checkboxValue, checkboxId, elLabel, j, k;
5792         // First create the checkbox buttons in an IE-friendly way
5793         for(j=0; j<checkboxOptions.length; j++) {
5794             checkboxValue = YAHOO.lang.isValue(checkboxOptions[j].label) ?
5795                     checkboxOptions[j].label : checkboxOptions[j];
5796             checkboxId =  oSelf.id + "-editor-checkbox" + j;
5797             elContainer.innerHTML += "<input type=\"checkbox\"" +
5798                     " name=\"" + oSelf.id + "-editor-checkbox\"" +
5799                     " value=\"" + checkboxValue + "\"" +
5800                     " id=\"" +  checkboxId + "\">";
5801             // Then create the labels in an IE-friendly way
5802             elLabel = elContainer.appendChild(document.createElement("label"));
5803             elLabel.htmlFor = checkboxId;
5804             elLabel.innerHTML = checkboxValue;
5805         }
5806         var aCheckboxEls = [];
5807         var checkboxEl;
5808         // Loop through checkboxes to check them
5809         for(j=0; j<checkboxOptions.length; j++) {
5810             checkboxEl = YAHOO.util.Dom.get(oSelf.id + "-editor-checkbox" + j);
5811             aCheckboxEls.push(checkboxEl);
5812             for(k=0; k<aCheckedValues.length; k++) {
5813                 if(checkboxEl.value === aCheckedValues[k]) {
5814                     checkboxEl.checked = true;
5815                 }
5816             }
5817             // Focus the first checkbox
5818             if(j===0) {
5819                 oSelf._focusEl(checkboxEl);
5820             }
5821         }
5822         // Loop through checkboxes to assign click handlers
5823         for(j=0; j<checkboxOptions.length; j++) {
5824             checkboxEl = YAHOO.util.Dom.get(oSelf.id + "-editor-checkbox" + j);
5825             YAHOO.util.Event.addListener(checkboxEl, "click", function(){
5826                 var aNewValues = [];
5827                 for(var m=0; m<aCheckboxEls.length; m++) {
5828                     if(aCheckboxEls[m].checked) {
5829                         aNewValues.push(aCheckboxEls[m].value);
5830                     }
5831                 }
5832                 oSelf._oCellEditor.value = aNewValues;
5833                 oSelf.fireEvent("editorUpdateEvent",{editor:oSelf._oCellEditor});
5834             });
5835         }
5836     }
5840  * Enables Date Editor.
5842  * @method editDate
5843  */
5844 YAHOO.widget.DataTable.editDate = function(oEditor, oSelf) {
5845     var elCell = oEditor.cell;
5846     var oRecord = oEditor.record;
5847     var oColumn = oEditor.column;
5848     var elContainer = oEditor.container;
5849     var value = oRecord.getData(oColumn.key);
5851     // Calendar widget
5852     if(YAHOO.widget.Calendar) {
5853         var selectedValue = (value.getMonth()+1)+"/"+value.getDate()+"/"+value.getFullYear();
5854         var calContainer = elContainer.appendChild(document.createElement("div"));
5855         calContainer.id = "yui-dt-" + oSelf._nIndex + "-col" + oColumn.getKeyIndex() + "-dateContainer";
5856         var calendar =
5857                 new YAHOO.widget.Calendar("yui-dt-" + oSelf._nIndex + "-col" + oColumn.getKeyIndex() + "-date",
5858                 calContainer.id,
5859                 {selected:selectedValue, pagedate:value});
5860         calendar.render();
5861         calContainer.style.cssFloat = "none";
5863         //var calFloatClearer = elContainer.appendChild(document.createElement("br"));
5864         //calFloatClearer.style.clear = "both";
5865         
5866         calendar.selectEvent.subscribe(function(type, args, obj) {
5867             oSelf._oCellEditor.value = new Date(args[0][0][0], args[0][0][1]-1, args[0][0][2]);
5868         });
5869         oSelf.fireEvent("editorUpdateEvent",{editor:oSelf._oCellEditor});
5870     }
5871     else {
5872         //TODO;
5873     }
5877  * Enables SELECT Editor.
5879  * @method editDropdown
5880  */
5881 YAHOO.widget.DataTable.editDropdown = function(oEditor, oSelf) {
5882     var elCell = oEditor.cell;
5883     var oRecord = oEditor.record;
5884     var oColumn = oEditor.column;
5885     var elContainer = oEditor.container;
5886     var value = oRecord.getData(oColumn.key);
5888     // Textbox
5889     var elDropdown = elContainer.appendChild(document.createElement("select"));
5890     var dropdownOptions = (oColumn.editorOptions && YAHOO.lang.isArray(oColumn.editorOptions.dropdownOptions)) ?
5891             oColumn.editorOptions.dropdownOptions : [];
5892     for(var j=0; j<dropdownOptions.length; j++) {
5893         var dropdownOption = dropdownOptions[j];
5894         var elOption = document.createElement("option");
5895         elOption.value = (YAHOO.lang.isValue(dropdownOption.value)) ?
5896                 dropdownOption.value : dropdownOption;
5897         elOption.innerHTML = (YAHOO.lang.isValue(dropdownOption.text)) ?
5898                 dropdownOption.text : dropdownOption;
5899         elOption = elDropdown.appendChild(elOption);
5900         if(value === elDropdown.options[j].value) {
5901             elDropdown.options[j].selected = true;
5902         }
5903     }
5904     
5905     // Set up a listener on each check box to track the input value
5906     YAHOO.util.Event.addListener(elDropdown, "change",
5907         function(){
5908             oSelf._oCellEditor.value = elDropdown[elDropdown.selectedIndex].value;
5909             oSelf.fireEvent("editorUpdateEvent",{editor:oSelf._oCellEditor});
5910     });
5911             
5912     // Focus the dropdown
5913     oSelf._focusEl(elDropdown);
5917  * Enables INPUT TYPE=RADIO Editor.
5919  * @method editRadio
5920  */
5921 YAHOO.widget.DataTable.editRadio = function(oEditor, oSelf) {
5922     var elCell = oEditor.cell;
5923     var oRecord = oEditor.record;
5924     var oColumn = oEditor.column;
5925     var elContainer = oEditor.container;
5926     var value = oRecord.getData(oColumn.key);
5928     // Radios
5929     if(oColumn.editorOptions && YAHOO.lang.isArray(oColumn.editorOptions.radioOptions)) {
5930         var radioOptions = oColumn.editorOptions.radioOptions;
5931         var radioValue, radioId, elLabel, j;
5932         // First create the radio buttons in an IE-friendly way
5933         for(j=0; j<radioOptions.length; j++) {
5934             radioValue = YAHOO.lang.isValue(radioOptions[j].label) ?
5935                     radioOptions[j].label : radioOptions[j];
5936             radioId =  oSelf.id + "-editor-radio" + j;
5937             elContainer.innerHTML += "<input type=\"radio\"" +
5938                     " name=\"" + oSelf.id + "-editor-radio\"" +
5939                     " value=\"" + radioValue + "\"" +
5940                     " id=\"" +  radioId + "\">";
5941             // Then create the labels in an IE-friendly way
5942             elLabel = elContainer.appendChild(document.createElement("label"));
5943             elLabel.htmlFor = radioId;
5944             elLabel.innerHTML = radioValue;
5945         }
5946         // Then check one, and assign click handlers
5947         for(j=0; j<radioOptions.length; j++) {
5948             var radioEl = YAHOO.util.Dom.get(oSelf.id + "-editor-radio" + j);
5949             if(value === radioEl.value) {
5950                 radioEl.checked = true;
5951                 oSelf._focusEl(radioEl);
5952             }
5953             YAHOO.util.Event.addListener(radioEl, "click",
5954                 function(){
5955                     oSelf._oCellEditor.value = this.value;
5956                     oSelf.fireEvent("editorUpdateEvent",{editor:oSelf._oCellEditor});
5957             });
5958         }
5959     }
5963  * Enables TEXTAREA Editor.
5965  * @method editTextarea
5966  */
5967 YAHOO.widget.DataTable.editTextarea = function(oEditor, oSelf) {
5968    var elCell = oEditor.cell;
5969    var oRecord = oEditor.record;
5970    var oColumn = oEditor.column;
5971    var elContainer = oEditor.container;
5972    var value = oRecord.getData(oColumn.key);
5974     // Textarea
5975     var elTextarea = elContainer.appendChild(document.createElement("textarea"));
5976     elTextarea.style.width = elCell.offsetWidth + "px"; //(parseInt(elCell.offsetWidth,10)) + "px";
5977     elTextarea.style.height = "3em"; //(parseInt(elCell.offsetHeight,10)) + "px";
5978     elTextarea.value = YAHOO.lang.isValue(value) ? value : "";
5979     
5980     // Set up a listener on each check box to track the input value
5981     YAHOO.util.Event.addListener(elTextarea, "keyup", function(){
5982         //TODO: set on a timeout
5983         oSelf._oCellEditor.value = elTextarea.value;
5984         oSelf.fireEvent("editorUpdateEvent",{editor:oSelf._oCellEditor});
5985     });
5986     
5987     // Select the text
5988     elTextarea.focus();
5989     elTextarea.select();
5993  * Enables INPUT TYPE=TEXT Editor.
5995  * @method editTextbox
5996  */
5997 YAHOO.widget.DataTable.editTextbox = function(oEditor, oSelf) {
5998    var elCell = oEditor.cell;
5999    var oRecord = oEditor.record;
6000    var oColumn = oEditor.column;
6001    var elContainer = oEditor.container;
6002    var value = YAHOO.lang.isValue(oRecord.getData(oColumn.key)) ? oRecord.getData(oColumn.key) : "";
6004     // Textbox
6005     var elTextbox = elContainer.appendChild(document.createElement("input"));
6006     elTextbox.type = "text";
6007     elTextbox.style.width = elCell.offsetWidth + "px"; //(parseInt(elCell.offsetWidth,10)) + "px";
6008     //elTextbox.style.height = "1em"; //(parseInt(elCell.offsetHeight,10)) + "px";
6009     elTextbox.value = value;
6011     // Set up a listener on each textbox to track the input value
6012     YAHOO.util.Event.addListener(elTextbox, "keyup", function(){
6013         //TODO: set on a timeout
6014         oSelf._oCellEditor.value = elTextbox.value;
6015         oSelf.fireEvent("editorUpdateEvent",{editor:oSelf._oCellEditor});
6016     });
6018     // Select the text
6019     elTextbox.focus();
6020     elTextbox.select();
6024  * Validates Editor input value to type Number, doing type conversion as
6025  * necessary. A valid Number value is return, else the previous value is returned
6026  * if input value does not validate.
6027  * 
6029  * @method validateNumber
6030  * @static
6032 YAHOO.widget.DataTable.validateNumber = function(oData) {
6033     //Convert to number
6034     var number = oData * 1;
6036     // Validate
6037     if(YAHOO.lang.isNumber(number)) {
6038         return number;
6039     }
6040     else {
6041         YAHOO.log("Could not validate data " + YAHOO.lang.dump(oData) + " to type Number", "warn", this.toString());
6042         return null;
6043     }
6083 // ABSTRACT METHODS
6086  * Overridable method gives implementers a hook to access data before
6087  * it gets added to RecordSet and rendered to the TBODY.
6089  * @method doBeforeLoadData
6090  * @param sRequest {String} Original request.
6091  * @param oResponse {Object} Response object.
6092  * @return {Boolean} Return true to continue loading data into RecordSet and
6093  * updating DataTable with new Records, false to cancel.
6094  */
6095 YAHOO.widget.DataTable.prototype.doBeforeLoadData = function(sRequest, oResponse) {
6096     return true;
6161 /////////////////////////////////////////////////////////////////////////////
6163 // Public Custom Event Handlers
6165 /////////////////////////////////////////////////////////////////////////////
6168  * Overridable custom event handler to sort Column.
6170  * @method onEventSortColumn
6171  * @param oArgs.event {HTMLEvent} Event object.
6172  * @param oArgs.target {HTMLElement} Target element.
6173  */
6174 YAHOO.widget.DataTable.prototype.onEventSortColumn = function(oArgs) {
6175 //TODO: support nested header column sorting
6176     var evt = oArgs.event;
6177     var target = oArgs.target;
6178     YAHOO.util.Event.stopEvent(evt);
6179     
6180     var el = this.getThEl(target) || this.getTdEl(target);
6181     if(el && YAHOO.lang.isNumber(el.yuiColumnId)) {
6182         this.sortColumn(this._oColumnSet.getColumn(el.yuiColumnId));
6183     }
6184     else {
6185         YAHOO.log("Could not sort column " + target, "warn", this.toString());
6186     }
6190  * Overridable custom event handler to manage selection according to desktop paradigm.
6192  * @method onEventSelectRow
6193  * @param oArgs.event {HTMLEvent} Event object.
6194  * @param oArgs.target {HTMLElement} Target element.
6195  */
6196 YAHOO.widget.DataTable.prototype.onEventSelectRow = function(oArgs) {
6197     var sMode = this.get("selectionMode");
6198     if ((sMode == "singlecell") || (sMode == "cellblock") || (sMode == "cellrange")) {
6199         return;
6200     }
6202     var evt = oArgs.event;
6203     var elTarget = oArgs.target;
6205     var bSHIFT = evt.shiftKey;
6206     var bCTRL = evt.ctrlKey;
6207     var i, nAnchorTrIndex;
6209     // Validate target row
6210     var elTargetRow = this.getTrEl(elTarget);
6211     if(elTargetRow) {
6212         var allRows = this._elTbody.rows;
6213         var nTargetTrIndex = elTargetRow.sectionRowIndex;
6214         var elAnchorRow = YAHOO.util.Dom.get(this._sSelectionAnchorId);
6215         
6216         // Both SHIFT and CTRL
6217         if((sMode != "single") && bSHIFT && bCTRL) {
6218             // Validate anchor row
6219             if(elAnchorRow && YAHOO.lang.isNumber(elAnchorRow.sectionRowIndex)) {
6220                 nAnchorTrIndex = elAnchorRow.sectionRowIndex;
6221                 if(this.isSelected(elAnchorRow)) {
6222                     // Select all rows between anchor row and target row, including target row
6223                     if(nAnchorTrIndex < nTargetTrIndex) {
6224                         for(i=nAnchorTrIndex+1; i<=nTargetTrIndex; i++) {
6225                             if(!this.isSelected(allRows[i])) {
6226                                 this.selectRow(allRows[i]);
6227                             }
6228                         }
6229                     }
6230                     // Select all rows between target row and anchor row, including target row
6231                     else {
6232                         for(i=nAnchorTrIndex-1; i>=nTargetTrIndex; i--) {
6233                             if(!this.isSelected(allRows[i])) {
6234                                 this.selectRow(allRows[i]);
6235                             }
6236                         }
6237                     }
6238                 }
6239                 else {
6240                     // Unselect all rows between anchor row and target row
6241                     if(nAnchorTrIndex < nTargetTrIndex) {
6242                         for(i=nAnchorTrIndex+1; i<=nTargetTrIndex-1; i++) {
6243                             if(this.isSelected(allRows[i])) {
6244                                 this.unselectRow(allRows[i]);
6245                             }
6246                         }
6247                     }
6248                     // Unselect all rows between target row and anchor row
6249                     else {
6250                         for(i=nTargetTrIndex+1; i<=nAnchorTrIndex-1; i++) {
6251                             if(this.isSelected(allRows[i])) {
6252                                 this.unselectRow(allRows[i]);
6253                             }
6254                         }
6255                     }
6256                     // Select the target row
6257                     this.selectRow(elTargetRow);
6258                 }
6259             }
6260             // Invalid anchor
6261             else {
6262                 // Set anchor
6263                 this._sSelectionAnchorId = elTargetRow.id;
6265                 // Toggle selection of target
6266                 if(this.isSelected(elTargetRow)) {
6267                     this.unselectRow(elTargetRow);
6268                 }
6269                 else {
6270                     this.selectRow(elTargetRow);
6271                 }
6272             }
6273         }
6274         // Only SHIFT
6275         else if((sMode != "single") && bSHIFT) {
6276             this.unselectAllRows();
6278             // Validate anchor
6279             if(elAnchorRow && YAHOO.lang.isNumber(elAnchorRow.sectionRowIndex)) {
6280                 nAnchorTrIndex = elAnchorRow.sectionRowIndex;
6282                 // Select all rows between anchor row and target row,
6283                 // including the anchor row and target row
6284                 if(nAnchorTrIndex < nTargetTrIndex) {
6285                     for(i=nAnchorTrIndex; i<=nTargetTrIndex; i++) {
6286                         this.selectRow(allRows[i]);
6287                     }
6288                 }
6289                 // Select all rows between target row and anchor row,
6290                 // including the target row and anchor row
6291                 else {
6292                     for(i=nAnchorTrIndex; i>=nTargetTrIndex; i--) {
6293                         this.selectRow(allRows[i]);
6294                     }
6295                 }
6296             }
6297             // Invalid anchor
6298             else {
6299                 // Set anchor
6300                 this._sSelectionAnchorId = elTargetRow.id;
6302                 // Select target row only
6303                 this.selectRow(elTargetRow);
6304             }
6305         }
6306         // Only CTRL
6307         else if((sMode != "single") && bCTRL) {
6308             // Set anchor
6309             this._sSelectionAnchorId = elTargetRow.id;
6311             // Toggle selection of target
6312             if(this.isSelected(elTargetRow)) {
6313                 this.unselectRow(elTargetRow);
6314             }
6315             else {
6316                 this.selectRow(elTargetRow);
6317             }
6318         }
6319         // Neither SHIFT nor CTRL
6320         else if(sMode == "single") {
6321             this.unselectAllRows();
6322             this.selectRow(elTargetRow);
6323         }
6324         // Neither SHIFT nor CTRL
6325         else {
6326             // Set anchor
6327             this._sSelectionAnchorId = elTargetRow.id;
6329             // Select only target
6330             this.unselectAllRows();
6331             this.selectRow(elTargetRow);
6332         }
6333         YAHOO.util.Event.stopEvent(evt);
6335         // Clear any selections that are a byproduct of the click or dblclick
6336         var sel;
6337         if(window.getSelection) {
6338                 sel = window.getSelection();
6339         }
6340         else if(document.getSelection) {
6341                 sel = document.getSelection();
6342         }
6343         else if(document.selection) {
6344                 sel = document.selection;
6345         }
6346         if(sel) {
6347             if(sel.empty) {
6348                 sel.empty();
6349             }
6350             else if (sel.removeAllRanges) {
6351                 sel.removeAllRanges();
6352             }
6353             else if(sel.collapse) {
6354                 sel.collapse();
6355             }
6356         }
6357     }
6358     else {
6359         YAHOO.log("Could not select row " + elTarget, "warn", this.toString());
6360     }
6364  * Overridable custom event handler to select cell.
6366  * @method onEventSelectCell
6367  * @param oArgs.event {HTMLEvent} Event object.
6368  * @param oArgs.target {HTMLElement} Target element.
6369  */
6370 YAHOO.widget.DataTable.prototype.onEventSelectCell = function(oArgs) {
6371     var sMode = this.get("selectionMode");
6372     if ((sMode == "standard") || (sMode == "single")) {
6373         return;
6374     }
6376     var evt = oArgs.event;
6377     var elTarget = oArgs.target;
6379     var bSHIFT = evt.shiftKey;
6380     var bCTRL = evt.ctrlKey;
6381     var i, j, nAnchorTrIndex, nAnchorTdIndex, currentRow, startIndex, endIndex;
6382     
6383     var elTargetCell = this.getTdEl(elTarget);
6384     if(elTargetCell) {
6385         var elTargetRow = this.getTrEl(elTargetCell);
6386         var allRows = this._elTbody.rows;
6387         var nTargetTrIndex = elTargetRow.sectionRowIndex;
6388         var nTargetTdIndex = elTarget.yuiCellIndex;
6389         var elAnchorCell = YAHOO.util.Dom.get(this._sSelectionAnchorId);
6391         // Both SHIFT and CTRL
6392         if((sMode != "singlecell") && bSHIFT && bCTRL) {
6393             // Validate anchor
6394             if(elAnchorCell && YAHOO.lang.isNumber(elAnchorCell.yuiCellIndex)) {
6395                 nAnchorTrIndex = elAnchorCell.parentNode.sectionRowIndex;
6396                 nAnchorTdIndex = elAnchorCell.yuiCellIndex;
6397                 
6398                 // Anchor is selected
6399                 if(this.isSelected(elAnchorCell)) {
6400                     // All cells are on the same row
6401                     if(nAnchorTrIndex == nTargetTrIndex) {
6402                         // Select all cells between anchor cell and target cell, including target cell
6403                         if(nAnchorTdIndex < nTargetTdIndex) {
6404                             for(i=nAnchorTdIndex+1; i<=nTargetTdIndex; i++) {
6405                                 this.selectCell(allRows[nTargetTrIndex].cells[i]);
6406                             }
6407                         }
6408                         // Select all cells between target cell and anchor cell, including target cell
6409                         else if(nTargetTdIndex < nAnchorTdIndex) {
6410                             for(i=nTargetTdIndex; i<nAnchorTdIndex; i++) {
6411                                 this.selectCell(allRows[nTargetTrIndex].cells[i]);
6412                             }
6413                         }
6414                     }
6415                     // Anchor row is above target row
6416                     else if(nAnchorTrIndex < nTargetTrIndex) {
6417                         if(sMode == "cellrange") {
6418                             // Select all cells on anchor row from anchor cell to the end of the row
6419                             for(i=nAnchorTdIndex+1; i<allRows[nAnchorTrIndex].cells.length; i++) {
6420                                 this.selectCell(allRows[nAnchorTrIndex].cells[i]);
6421                             }
6422                             
6423                             // Select all cells on all rows between anchor row and target row
6424                             for(i=nAnchorTrIndex+1; i<nTargetTrIndex; i++) {
6425                                 for(j=0; j<allRows[i].cells.length; j++){
6426                                     this.selectCell(allRows[i].cells[j]);
6427                                 }
6428                             }
6430                             // Select all cells on target row from first cell to the target cell
6431                             for(i=0; i<=nTargetTdIndex; i++) {
6432                                 this.selectCell(allRows[nTargetTrIndex].cells[i]);
6433                             }
6434                         }
6435                         else if(sMode == "cellblock") {
6436                             startIndex = Math.min(nAnchorTdIndex, nTargetTdIndex);
6437                             endIndex = Math.max(nAnchorTdIndex, nTargetTdIndex);
6438                             
6439                             // Select all cells from startIndex to endIndex on rows between anchor row and target row
6440                             for(i=nAnchorTrIndex; i<=nTargetTrIndex; i++) {
6441                                 for(j=startIndex; j<=endIndex; j++) {
6442                                     this.selectCell(allRows[i].cells[j]);
6443                                 }
6444                             }
6445                         }
6446                     }
6447                     // Anchor row is below target row
6448                     else {
6449                         if(sMode == "cellrange") {
6450                             // Select all cells on target row from target cell to the end of the row
6451                             for(i=nTargetTdIndex; i<allRows[nTargetTrIndex].cells.length; i++) {
6452                                 this.selectCell(allRows[nTargetTrIndex].cells[i]);
6453                             }
6455                             // Select all cells on all rows between target row and anchor row
6456                             for(i=nTargetTrIndex+1; i<nAnchorTrIndex; i++) {
6457                                 for(j=0; j<allRows[i].cells.length; j++){
6458                                     this.selectCell(allRows[i].cells[j]);
6459                                 }
6460                             }
6462                             // Select all cells on anchor row from first cell to the anchor cell
6463                             for(i=0; i<nAnchorTdIndex; i++) {
6464                                 this.selectCell(allRows[nAnchorTrIndex].cells[i]);
6465                             }
6466                         }
6467                         else if(sMode == "cellblock") {
6468                             startIndex = Math.min(nAnchorTdIndex, nTargetTdIndex);
6469                             endIndex = Math.max(nAnchorTdIndex, nTargetTdIndex);
6471                             // Select all cells from startIndex to endIndex on rows between target row and anchor row
6472                             for(i=nAnchorTrIndex; i>=nTargetTrIndex; i--) {
6473                                 for(j=endIndex; j>=startIndex; j--) {
6474                                     this.selectCell(allRows[i].cells[j]);
6475                                 }
6476                             }
6477                         }
6478                     }
6479                 }
6480                 // Anchor cell is unselected
6481                 else {
6482                     // All cells are on the same row
6483                     if(nAnchorTrIndex == nTargetTrIndex) {
6484                         // Unselect all cells between anchor cell and target cell
6485                         if(nAnchorTdIndex < nTargetTdIndex) {
6486                             for(i=nAnchorTdIndex+1; i<nTargetTdIndex; i++) {
6487                                 this.unselectCell(allRows[nTargetTrIndex].cells[i]);
6488                             }
6489                         }
6490                         // Select all cells between target cell and anchor cell
6491                         else if(nTargetTdIndex < nAnchorTdIndex) {
6492                             for(i=nTargetTdIndex+1; i<nAnchorTdIndex; i++) {
6493                                 this.unselectCell(allRows[nTargetTrIndex].cells[i]);
6494                             }
6495                         }
6496                     }
6497                     // Anchor row is above target row
6498                     if(nAnchorTrIndex < nTargetTrIndex) {
6499                         // Unselect all cells from anchor cell to target cell
6500                         for(i=nAnchorTrIndex; i<=nTargetTrIndex; i++) {
6501                             currentRow = allRows[i];
6502                             for(j=0; j<currentRow.cells.length; j++) {
6503                                 // This is the anchor row, only unselect cells after the anchor cell
6504                                 if(currentRow.sectionRowIndex == nAnchorTrIndex) {
6505                                     if(j>nAnchorTdIndex) {
6506                                         this.unselectCell(currentRow.cells[j]);
6507                                     }
6508                                 }
6509                                 // This is the target row, only unelect cells before the target cell
6510                                 else if(currentRow.sectionRowIndex == nTargetTrIndex) {
6511                                     if(j<nTargetTdIndex) {
6512                                         this.unselectCell(currentRow.cells[j]);
6513                                     }
6514                                 }
6515                                 // Unselect all cells on this row
6516                                 else {
6517                                     this.unselectCell(currentRow.cells[j]);
6518                                 }
6519                             }
6520                         }
6521                     }
6522                     // Anchor row is below target row
6523                     else {
6524                         // Unselect all cells from target cell to anchor cell
6525                         for(i=nTargetTrIndex; i<=nAnchorTrIndex; i++) {
6526                             currentRow = allRows[i];
6527                             for(j=0; j<currentRow.cells.length; j++) {
6528                                 // This is the target row, only unselect cells after the target cell
6529                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
6530                                     if(j>nTargetTdIndex) {
6531                                         this.unselectCell(currentRow.cells[j]);
6532                                     }
6533                                 }
6534                                 // This is the anchor row, only unselect cells before the anchor cell
6535                                 else if(currentRow.sectionRowIndex == nAnchorTrIndex) {
6536                                     if(j<nAnchorTdIndex) {
6537                                         this.unselectCell(currentRow.cells[j]);
6538                                     }
6539                                 }
6540                                 // Unselect all cells on this row
6541                                 else {
6542                                     this.unselectCell(currentRow.cells[j]);
6543                                 }
6544                             }
6545                         }
6546                     }
6548                     // Select the target cell
6549                     this.selectCell(elTargetCell);
6550                 }
6551             }
6552             // Invalid anchor
6553             else {
6554                 // Set anchor
6555                 this._sSelectionAnchorId = elTargetCell.id;
6557                 // Toggle selection of target
6558                 if(this.isSelected(elTargetCell)) {
6559                     this.unselectCell(elTargetCell);
6560                 }
6561                 else {
6562                     this.selectCell(elTargetCell);
6563                 }
6564             }
6565         }
6566         // Only SHIFT
6567         else if((sMode != "singlecell") && bSHIFT) {
6568             this.unselectAllCells();
6570             // Validate anchor
6571             if(elAnchorCell && YAHOO.lang.isNumber(elAnchorCell.yuiCellIndex)) {
6572                 nAnchorTrIndex = elAnchorCell.parentNode.sectionRowIndex;
6573                 nAnchorTdIndex = elAnchorCell.yuiCellIndex;
6574                 
6575                 // All cells are on the same row
6576                 if(nAnchorTrIndex == nTargetTrIndex) {
6577                     // Select all cells between anchor cell and target cell,
6578                     // including the anchor cell and target cell
6579                     if(nAnchorTdIndex < nTargetTdIndex) {
6580                         for(i=nAnchorTdIndex; i<=nTargetTdIndex; i++) {
6581                             this.selectCell(allRows[nTargetTrIndex].cells[i]);
6582                         }
6583                     }
6584                     // Select all cells between target cell and anchor cell
6585                     // including the target cell and anchor cell
6586                     else if(nTargetTdIndex < nAnchorTdIndex) {
6587                         for(i=nTargetTdIndex; i<=nAnchorTdIndex; i++) {
6588                             this.selectCell(allRows[nTargetTrIndex].cells[i]);
6589                         }
6590                     }
6591                 }
6592                 // Anchor row is above target row
6593                 else if(nAnchorTrIndex < nTargetTrIndex) {
6594                     if(sMode == "cellrange") {
6595                         // Select all cells from anchor cell to target cell
6596                         // including the anchor cell and target cell
6597                         for(i=nAnchorTrIndex; i<=nTargetTrIndex; i++) {
6598                             currentRow = allRows[i];
6599                             for(j=0; j<currentRow.cells.length; j++) {
6600                                 // This is the anchor row, only select the anchor cell and after
6601                                 if(currentRow.sectionRowIndex == nAnchorTrIndex) {
6602                                     if(j>=nAnchorTdIndex) {
6603                                         this.selectCell(currentRow.cells[j]);
6604                                     }
6605                                 }
6606                                 // This is the target row, only select the target cell and before
6607                                 else if(currentRow.sectionRowIndex == nTargetTrIndex) {
6608                                     if(j<=nTargetTdIndex) {
6609                                         this.selectCell(currentRow.cells[j]);
6610                                     }
6611                                 }
6612                                 // Select all cells on this row
6613                                 else {
6614                                     this.selectCell(currentRow.cells[j]);
6615                                 }
6616                             }
6617                         }
6618                     }
6619                     else if(sMode == "cellblock") {
6620                         // Select the cellblock from anchor cell to target cell
6621                         // including the anchor cell and the target cell
6622                         startIndex = Math.min(nAnchorTdIndex, nTargetTdIndex);
6623                         endIndex = Math.max(nAnchorTdIndex, nTargetTdIndex);
6625                         for(i=nAnchorTrIndex; i<=nTargetTrIndex; i++) {
6626                             for(j=startIndex; j<=endIndex; j++) {
6627                                 this.selectCell(allRows[i].cells[j]);
6628                             }
6629                         }
6630                         
6631                         this._sLastSelectedId = allRows[nTargetTrIndex].cells[nTargetTdIndex].id;
6632                     }
6633                 }
6634                 // Anchor row is below target row
6635                 else {
6636                     if(sMode == "cellrange") {
6637                         // Select all cells from target cell to anchor cell,
6638                         // including the target cell and anchor cell
6639                         for(i=nTargetTrIndex; i<=nAnchorTrIndex; i++) {
6640                             currentRow = allRows[i];
6641                             for(j=0; j<currentRow.cells.length; j++) {
6642                                 // This is the target row, only select the target cell and after
6643                                 if(currentRow.sectionRowIndex == nTargetTrIndex) {
6644                                     if(j>=nTargetTdIndex) {
6645                                         this.selectCell(currentRow.cells[j]);
6646                                     }
6647                                 }
6648                                 // This is the anchor row, only select the anchor cell and before
6649                                 else if(currentRow.sectionRowIndex == nAnchorTrIndex) {
6650                                     if(j<=nAnchorTdIndex) {
6651                                         this.selectCell(currentRow.cells[j]);
6652                                     }
6653                                 }
6654                                 // Select all cells on this row
6655                                 else {
6656                                     this.selectCell(currentRow.cells[j]);
6657                                 }
6658                             }
6659                         }
6660                     }
6661                     else if(sMode == "cellblock") {
6662                         // Select the cellblock from target cell to anchor cell
6663                         // including the target cell and the anchor cell
6664                         startIndex = Math.min(nAnchorTdIndex, nTargetTdIndex);
6665                         endIndex = Math.max(nAnchorTdIndex, nTargetTdIndex);
6667                         for(i=nTargetTrIndex; i<=nAnchorTrIndex; i++) {
6668                             for(j=startIndex; j<=endIndex; j++) {
6669                                 this.selectCell(allRows[i].cells[j]);
6670                             }
6671                         }
6672                         
6673                         this._sLastSelectedId = allRows[nTargetTrIndex].cells[nTargetTdIndex].id;
6674                     }
6675                 }
6676             }
6677             // Invalid anchor
6678             else {
6679                 // Set anchor
6680                 this._sSelectionAnchorId = elTargetCell.id;
6682                 // Select target only
6683                 this.selectCell(elTargetCell);
6684             }
6685         }
6686         // Only CTRL
6687         else if((sMode != "singlecell") && bCTRL) {
6688             // Set anchor
6689             this._sSelectionAnchorId = elTargetCell.id;
6691             // Toggle selection of target
6692             if(this.isSelected(elTargetCell)) {
6693                 this.unselectCell(elTargetCell);
6694             }
6695             else {
6696                 this.selectCell(elTargetCell);
6697             }
6698         }
6699         // Neither SHIFT nor CTRL, or multi-selection has been disabled
6700         else {
6701             // Set anchor
6702             this._sSelectionAnchorId = elTargetCell.id;
6704             // Select only target
6705             this.unselectAllCells();
6706             this.selectCell(elTargetCell);
6707         }
6709         YAHOO.util.Event.stopEvent(evt);
6711         // Clear any selections that are a byproduct of the click or dblclick
6712         var sel;
6713         if(window.getSelection) {
6714                 sel = window.getSelection();
6715         }
6716         else if(document.getSelection) {
6717                 sel = document.getSelection();
6718         }
6719         else if(document.selection) {
6720                 sel = document.selection;
6721         }
6722         if(sel) {
6723             if(sel.empty) {
6724                 sel.empty();
6725             }
6726             else if (sel.removeAllRanges) {
6727                 sel.removeAllRanges();
6728             }
6729             else if(sel.collapse) {
6730                 sel.collapse();
6731             }
6732         }
6733     }
6734     else {
6735         YAHOO.log("Could not select cell " + elTarget, "warn", this.toString());
6736     }
6750  * Overridable custom event handler to highlight row.
6752  * @method onEventHighlightRow
6753  * @param oArgs.event {HTMLEvent} Event object.
6754  * @param oArgs.target {HTMLElement} Target element.
6755  */
6756 YAHOO.widget.DataTable.prototype.onEventHighlightRow = function(oArgs) {
6757     var evt = oArgs.event;
6758     var elTarget = oArgs.target;
6759     this.highlightRow(elTarget);
6763  * Overridable custom event handler to unhighlight row.
6765  * @method onEventUnhighlightRow
6766  * @param oArgs.event {HTMLEvent} Event object.
6767  * @param oArgs.target {HTMLElement} Target element.
6768  */
6769 YAHOO.widget.DataTable.prototype.onEventUnhighlightRow = function(oArgs) {
6770     var evt = oArgs.event;
6771     var elTarget = oArgs.target;
6772     this.unhighlightRow(elTarget);
6776  * Overridable custom event handler to highlight cell.
6778  * @method onEventHighlightCell
6779  * @param oArgs.event {HTMLEvent} Event object.
6780  * @param oArgs.target {HTMLElement} Target element.
6781  */
6782 YAHOO.widget.DataTable.prototype.onEventHighlightCell = function(oArgs) {
6783     var evt = oArgs.event;
6784     var elTarget = oArgs.target;
6785     this.highlightCell(elTarget);
6789  * Overridable custom event handler to unhighlight cell.
6791  * @method onEventUnhighlightCell
6792  * @param oArgs.event {HTMLEvent} Event object.
6793  * @param oArgs.target {HTMLElement} Target element.
6794  */
6795 YAHOO.widget.DataTable.prototype.onEventUnhighlightCell = function(oArgs) {
6796     var evt = oArgs.event;
6797     var elTarget = oArgs.target;
6798     this.unhighlightCell(elTarget);
6802  * Overridable custom event handler to format cell.
6804  * @method onEventFormatCell
6805  * @param oArgs.event {HTMLEvent} Event object.
6806  * @param oArgs.target {HTMLElement} Target element.
6807  */
6808 YAHOO.widget.DataTable.prototype.onEventFormatCell = function(oArgs) {
6809     var evt = oArgs.event;
6810     var target = oArgs.target;
6811     var elTag = target.tagName.toLowerCase();
6813     var elCell = this.getTdEl(target);
6814     if(elCell && YAHOO.lang.isNumber(elCell.yuiColumnId)) {
6815         var oColumn = this._oColumnSet.getColumn(elCell.yuiColumnId);
6816         this.formatCell(elCell, this.getRecord(elCell), oColumn);
6817     }
6818     else {
6819         YAHOO.log("Could not format cell " + target, "warn", this.toString());
6820     }
6824  * Overridable custom event handler to edit cell.
6826  * @method onEventShowCellEditor
6827  * @param oArgs.event {HTMLEvent} Event object.
6828  * @param oArgs.target {HTMLElement} Target element.
6829  */
6830 YAHOO.widget.DataTable.prototype.onEventShowCellEditor = function(oArgs) {
6831     var evt = oArgs.event;
6832     var target = oArgs.target;
6833     var elTag = target.tagName.toLowerCase();
6835     var elCell = this.getTdEl(target);
6836     if(elCell) {
6837         this.showCellEditor(elCell);
6838     }
6839     else {
6840         YAHOO.log("Could not edit cell " + target, "warn", this.toString());
6841     }
6843 // Backward compatibility
6844 YAHOO.widget.DataTable.prototype.onEventEditCell = function(oArgs) {
6845     YAHOO.log("The method onEventEditCell() has been deprecated" +
6846         " in favor of onEventShowCellEditor()", "warn", this.toString());
6847     this.onEventShowCellEditor(oArgs);
6851  * Overridable custom event handler to save Cell Editor input.
6853  * @method onEventSaveCellEditor
6854  * @param oArgs.editor {Object} Cell Editor object literal.
6855  */
6856 YAHOO.widget.DataTable.prototype.onEventSaveCellEditor = function(oArgs) {
6857     this.saveCellEditor();
6861  * Callback function for creating a progressively enhanced DataTable first
6862  * receives data from DataSource and populates the RecordSet, then initializes
6863  * DOM elements.
6865  * @method _onDataReturnEnhanceTable
6866  * @param sRequest {String} Original request.
6867  * @param oResponse {Object} Response object.
6868  * @param bError {Boolean} (optional) True if there was a data error.
6869  * @private
6870  */
6871 YAHOO.widget.DataTable.prototype._onDataReturnEnhanceTable = function(sRequest, oResponse) {
6872     // Pass data through abstract method for any transformations
6873     var ok = this.doBeforeLoadData(sRequest, oResponse);
6875     // Data ok to populate
6876     if(ok && oResponse && !oResponse.error && YAHOO.lang.isArray(oResponse.results)) {
6877         // Update RecordSet
6878         this._oRecordSet.addRecords(oResponse.results);
6880         // Initialize DOM elements
6881         this._initTableEl();
6882         if(!this._elTable || !this._elThead || !this._elTbody) {
6883             YAHOO.log("Could not instantiate DataTable due to an invalid DOM elements", "error", this.toString());
6884             return;
6885         }
6887         // Call Element's constructor after DOM elements are created
6888         // but *before* UI is updated with data
6889         YAHOO.widget.DataTable.superclass.constructor.call(this, this._elContainer, this._oConfigs);
6891         //HACK: Set the Paginator values
6892         if(this._oConfigs.paginator) {
6893             this.updatePaginator(this._oConfigs.paginator);
6894         }
6896         // Update the UI
6897         this.refreshView();
6898     }
6899     // Error
6900     else if(ok && oResponse.error) {
6901         this.showTableMessage(YAHOO.widget.DataTable.MSG_ERROR, YAHOO.widget.DataTable.CLASS_ERROR);
6902     }
6903     // Empty
6904     else if(ok){
6905         this.showTableMessage(YAHOO.widget.DataTable.MSG_EMPTY, YAHOO.widget.DataTable.CLASS_EMPTY);
6906     }
6908     
6910  * Callback function receives data from DataSource and populates an entire
6911  * DataTable with Records and TR elements, clearing previous Records, if any.
6913  * @method onDataReturnInitializeTable
6914  * @param sRequest {String} Original request.
6915  * @param oResponse {Object} Response object.
6916  * @param bError {Boolean} (optional) True if there was a data error.
6917  */
6918 YAHOO.widget.DataTable.prototype.onDataReturnInitializeTable = function(sRequest, oResponse) {
6919     this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse});
6921     // Pass data through abstract method for any transformations
6922     var ok = this.doBeforeLoadData(sRequest, oResponse);
6924     // Data ok to populate
6925     if(ok && oResponse && !oResponse.error && YAHOO.lang.isArray(oResponse.results)) {
6926         this.initializeTable(oResponse.results);
6927     }
6928     // Error
6929     else if(ok && oResponse.error) {
6930         this.showTableMessage(YAHOO.widget.DataTable.MSG_ERROR, YAHOO.widget.DataTable.CLASS_ERROR);
6931     }
6932     // Empty
6933     else if(ok){
6934         this.showTableMessage(YAHOO.widget.DataTable.MSG_EMPTY, YAHOO.widget.DataTable.CLASS_EMPTY);
6935     }
6937 // Backward compatibility
6938 YAHOO.widget.DataTable.prototype.onDataReturnReplaceRows = function(sRequest, oResponse) {
6939     YAHOO.log("The method onDataReturnReplaceRows() has been deprecated" +
6940             " in favor of onDataReturnInitializeTable()", "warn", this.toString());
6941     this.onDataReturnInitializeTable(sRequest, oResponse);
6945  * Callback function receives data from DataSource and appends to an existing
6946  * DataTable new Records and, if applicable, creates or updates
6947  * corresponding TR elements.
6949  * @method onDataReturnAppendRows
6950  * @param sRequest {String} Original request.
6951  * @param oResponse {Object} Response object.
6952  * @param bError {Boolean} (optional) True if there was a data error.
6953  */
6954 YAHOO.widget.DataTable.prototype.onDataReturnAppendRows = function(sRequest, oResponse) {
6955     this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse});
6956     
6957     // Pass data through abstract method for any transformations
6958     var ok = this.doBeforeLoadData(sRequest, oResponse);
6959     
6960     // Data ok to append
6961     if(ok && oResponse && !oResponse.error && YAHOO.lang.isArray(oResponse.results)) {
6962         this.addRows(oResponse.results);
6963     }
6964     // Error
6965     else if(ok && oResponse.error) {
6966         this.showTableMessage(YAHOO.widget.DataTable.MSG_ERROR, YAHOO.widget.DataTable.CLASS_ERROR);
6967     }
6971  * Callback function receives data from DataSource and inserts into top of an
6972  * existing DataTable new Records and, if applicable, creates or updates
6973  * corresponding TR elements.
6975  * @method onDataReturnInsertRows
6976  * @param sRequest {String} Original request.
6977  * @param oResponse {Object} Response object.
6978  * @param bError {Boolean} (optional) True if there was a data error.
6979  */
6980 YAHOO.widget.DataTable.prototype.onDataReturnInsertRows = function(sRequest, oResponse) {
6981     this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse});
6982     
6983     // Pass data through abstract method for any transformations
6984     var ok = this.doBeforeLoadData(sRequest, oResponse);
6985     
6986     // Data ok to append
6987     if(ok && oResponse && !oResponse.error && YAHOO.lang.isArray(oResponse.results)) {
6988         this.addRows(oResponse.results, 0);
6989     }
6990     // Error
6991     else if(ok && oResponse.error) {
6992         this.showTableMessage(YAHOO.widget.DataTable.MSG_ERROR, YAHOO.widget.DataTable.CLASS_ERROR);
6993     }
7030     /////////////////////////////////////////////////////////////////////////////
7031     //
7032     // Custom Events
7033     //
7034     /////////////////////////////////////////////////////////////////////////////
7036     /**
7037      * Fired when the DataTable instance's initialization is complete.
7038      *
7039      * @event initEvent
7040      */
7042     /**
7043      * Fired when the DataTable's view is refreshed.
7044      *
7045      * @event refreshEvent
7046      */
7048     /**
7049      * Fired when data is returned from DataSource.
7050      *
7051      * @event dataReturnEvent
7052      * @param oArgs.request {String} Original request.
7053      * @param oArgs.response {Object} Response object.
7054      */
7056     /**
7057      * Fired when the DataTable has a focus.
7058      *
7059      * @event tableFocusEvent
7060      */
7062     /**
7063      * Fired when the DataTable has a blur.
7064      *
7065      * @event tableBlurEvent
7066      */
7068     /**
7069      * Fired when the DataTable has a mouseover.
7070      *
7071      * @event tableMouseoverEvent
7072      * @param oArgs.event {HTMLEvent} The event object.
7073      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
7074      *
7075      */
7077     /**
7078      * Fired when the DataTable has a mouseout.
7079      *
7080      * @event tableMouseoutEvent
7081      * @param oArgs.event {HTMLEvent} The event object.
7082      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
7083      *
7084      */
7086     /**
7087      * Fired when the DataTable has a mousedown.
7088      *
7089      * @event tableMousedownEvent
7090      * @param oArgs.event {HTMLEvent} The event object.
7091      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
7092      *
7093      */
7095     /**
7096      * Fired when the DataTable has a click.
7097      *
7098      * @event tableClickEvent
7099      * @param oArgs.event {HTMLEvent} The event object.
7100      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
7101      *
7102      */
7104     /**
7105      * Fired when the DataTable has a dblclick.
7106      *
7107      * @event tableDblclickEvent
7108      * @param oArgs.event {HTMLEvent} The event object.
7109      * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
7110      *
7111      */
7113     /**
7114      * Fired when a fixed scrolling DataTable has a scroll.
7115      *
7116      * @event tableScrollEvent
7117      * @param oArgs.event {HTMLEvent} The event object.
7118      * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
7119      * or the DataTable's TBODY element (everyone else).
7120      *
7121      */
7123     /**
7124      * Fired when a message is shown in the DataTable's message element.
7125      *
7126      * @event tableMsgShowEvent
7127      * @param oArgs.html {String} The HTML displayed.
7128      * @param oArgs.className {String} The className assigned.
7129      *
7130      */
7132     /**
7133      * Fired when the DataTable's message element is hidden.
7134      *
7135      * @event tableMsgHideEvent
7136      */
7138     /**
7139      * Fired when a header row has a mouseover.
7140      *
7141      * @event headerRowMouseoverEvent
7142      * @param oArgs.event {HTMLEvent} The event object.
7143      * @param oArgs.target {HTMLElement} The TR element.
7144      */
7146     /**
7147      * Fired when a header row has a mouseout.
7148      *
7149      * @event headerRowMouseoutEvent
7150      * @param oArgs.event {HTMLEvent} The event object.
7151      * @param oArgs.target {HTMLElement} The TR element.
7152      */
7154     /**
7155      * Fired when a header row has a mousedown.
7156      *
7157      * @event headerRowMousedownEvent
7158      * @param oArgs.event {HTMLEvent} The event object.
7159      * @param oArgs.target {HTMLElement} The TR element.
7160      */
7162     /**
7163      * Fired when a header row has a click.
7164      *
7165      * @event headerRowClickEvent
7166      * @param oArgs.event {HTMLEvent} The event object.
7167      * @param oArgs.target {HTMLElement} The TR element.
7168      */
7170     /**
7171      * Fired when a header row has a dblclick.
7172      *
7173      * @event headerRowDblclickEvent
7174      * @param oArgs.event {HTMLEvent} The event object.
7175      * @param oArgs.target {HTMLElement} The TR element.
7176      */
7178     /**
7179      * Fired when a header cell has a mouseover.
7180      *
7181      * @event headerCellMouseoverEvent
7182      * @param oArgs.event {HTMLEvent} The event object.
7183      * @param oArgs.target {HTMLElement} The TH element.
7184      *
7185      */
7187     /**
7188      * Fired when a header cell has a mouseout.
7189      *
7190      * @event headerCellMouseoutEvent
7191      * @param oArgs.event {HTMLEvent} The event object.
7192      * @param oArgs.target {HTMLElement} The TH element.
7193      *
7194      */
7196     /**
7197      * Fired when a header cell has a mousedown.
7198      *
7199      * @event headerCellMousedownEvent
7200      * @param oArgs.event {HTMLEvent} The event object.
7201      * @param oArgs.target {HTMLElement} The TH element.
7202      */
7204     /**
7205      * Fired when a header cell has a click.
7206      *
7207      * @event headerCellClickEvent
7208      * @param oArgs.event {HTMLEvent} The event object.
7209      * @param oArgs.target {HTMLElement} The TH element.
7210      */
7212     /**
7213      * Fired when a header cell has a dblclick.
7214      *
7215      * @event headerCellDblclickEvent
7216      * @param oArgs.event {HTMLEvent} The event object.
7217      * @param oArgs.target {HTMLElement} The TH element.
7218      */
7220     /**
7221      * Fired when a header label has a mouseover.
7222      *
7223      * @event headerLabelMouseoverEvent
7224      * @param oArgs.event {HTMLEvent} The event object.
7225      * @param oArgs.target {HTMLElement} The SPAN element.
7226      *
7227      */
7229     /**
7230      * Fired when a header label has a mouseout.
7231      *
7232      * @event headerLabelMouseoutEvent
7233      * @param oArgs.event {HTMLEvent} The event object.
7234      * @param oArgs.target {HTMLElement} The SPAN element.
7235      *
7236      */
7238     /**
7239      * Fired when a header label has a mousedown.
7240      *
7241      * @event headerLabelMousedownEvent
7242      * @param oArgs.event {HTMLEvent} The event object.
7243      * @param oArgs.target {HTMLElement} The SPAN element.
7244      */
7246     /**
7247      * Fired when a header label has a click.
7248      *
7249      * @event headerLabelClickEvent
7250      * @param oArgs.event {HTMLEvent} The event object.
7251      * @param oArgs.target {HTMLElement} The SPAN element.
7252      */
7254     /**
7255      * Fired when a header label has a dblclick.
7256      *
7257      * @event headerLabelDblclickEvent
7258      * @param oArgs.event {HTMLEvent} The event object.
7259      * @param oArgs.target {HTMLElement} The SPAN element.
7260      */
7262     /**
7263      * Fired when a column is sorted.
7264      *
7265      * @event columnSortEvent
7266      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
7267      * @param oArgs.dir {String} Sort direction "asc" or "desc".
7268      */
7270     /**
7271      * Fired when a column is resized.
7272      *
7273      * @event columnResizeEvent
7274      * @param oArgs.column {YAHOO.widget.Column} The Column instance.
7275      * @param oArgs.target {HTMLElement} The TH element.
7276      */
7278     /**
7279      * Fired when a row has a mouseover.
7280      *
7281      * @event rowMouseoverEvent
7282      * @param oArgs.event {HTMLEvent} The event object.
7283      * @param oArgs.target {HTMLElement} The TR element.
7284      */
7286     /**
7287      * Fired when a row has a mouseout.
7288      *
7289      * @event rowMouseoutEvent
7290      * @param oArgs.event {HTMLEvent} The event object.
7291      * @param oArgs.target {HTMLElement} The TR element.
7292      */
7294     /**
7295      * Fired when a row has a mousedown.
7296      *
7297      * @event rowMousedownEvent
7298      * @param oArgs.event {HTMLEvent} The event object.
7299      * @param oArgs.target {HTMLElement} The TR element.
7300      */
7302     /**
7303      * Fired when a row has a click.
7304      *
7305      * @event rowClickEvent
7306      * @param oArgs.event {HTMLEvent} The event object.
7307      * @param oArgs.target {HTMLElement} The TR element.
7308      */
7310     /**
7311      * Fired when a row has a dblclick.
7312      *
7313      * @event rowDblclickEvent
7314      * @param oArgs.event {HTMLEvent} The event object.
7315      * @param oArgs.target {HTMLElement} The TR element.
7316      */
7318     /**
7319      * Fired when a row is added.
7320      *
7321      * @event rowAddEvent
7322      * @param oArgs.record {YAHOO.widget.Record} The added Record.
7323      */
7325     /**
7326      * Fired when a row is updated.
7327      *
7328      * @event rowUpdateEvent
7329      * @param oArgs.record {YAHOO.widget.Record} The updated Record.
7330      * @param oArgs.oldData {Object} Object literal of the old data.
7331      */
7333     /**
7334      * Fired when a row is deleted.
7335      *
7336      * @event rowDeleteEvent
7337      * @param oArgs.oldData {Object} Object literal of the deleted data.
7338      * @param oArgs.recordIndex {Number} Index of the deleted Record.
7339      * @param oArgs.trElIndex {Number} Index of the deleted TR element, if in view.
7340      */
7342     /**
7343      * Fired when a row is selected.
7344      *
7345      * @event rowSelectEvent
7346      * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
7347      * @param oArgs.record {YAHOO.widget.Record} The selected Record.
7348      */
7350     /**
7351      * Fired when a row is unselected.
7352      *
7353      * @event rowUnselectEvent
7354      * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
7355      * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
7356      */
7358     /*TODO: delete and use rowUnselectEvent?
7359      * Fired when all row selections are cleared.
7360      *
7361      * @event unselectAllRowsEvent
7362      */
7364     /*
7365      * Fired when a row is highlighted.
7366      *
7367      * @event rowHighlightEvent
7368      * @param oArgs.el {HTMLElement} The highlighted TR element.
7369      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
7370      */
7372     /*
7373      * Fired when a row is unhighlighted.
7374      *
7375      * @event rowUnhighlightEvent
7376      * @param oArgs.el {HTMLElement} The highlighted TR element.
7377      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
7378      */
7380     /**
7381      * Fired when a cell has a mouseover.
7382      *
7383      * @event cellMouseoverEvent
7384      * @param oArgs.event {HTMLEvent} The event object.
7385      * @param oArgs.target {HTMLElement} The TD element.
7386      */
7388     /**
7389      * Fired when a cell has a mouseout.
7390      *
7391      * @event cellMouseoutEvent
7392      * @param oArgs.event {HTMLEvent} The event object.
7393      * @param oArgs.target {HTMLElement} The TD element.
7394      */
7396     /**
7397      * Fired when a cell has a mousedown.
7398      *
7399      * @event cellMousedownEvent
7400      * @param oArgs.event {HTMLEvent} The event object.
7401      * @param oArgs.target {HTMLElement} The TD element.
7402      */
7404     /**
7405      * Fired when a cell has a click.
7406      *
7407      * @event cellClickEvent
7408      * @param oArgs.event {HTMLEvent} The event object.
7409      * @param oArgs.target {HTMLElement} The TD element.
7410      */
7412     /**
7413      * Fired when a cell has a dblclick.
7414      *
7415      * @event cellDblclickEvent
7416      * @param oArgs.event {HTMLEvent} The event object.
7417      * @param oArgs.target {HTMLElement} The TD element.
7418      */
7420     /**
7421      * Fired when a cell is formatted.
7422      *
7423      * @event cellFormatEvent
7424      * @param oArgs.el {HTMLElement} The formatted TD element.
7425      * @param oArgs.record {YAHOO.widget.Record} The formatted Record.
7426      * @param oArgs.key {String} The key of the formatted cell.
7427      */
7429     /**
7430      * Fired when a cell is selected.
7431      *
7432      * @event cellSelectEvent
7433      * @param oArgs.el {HTMLElement} The selected TD element.
7434      * @param oArgs.record {YAHOO.widget.Record} The selected Record.
7435      * @param oArgs.key {String} The key of the selected cell.
7436      */
7438     /**
7439      * Fired when a cell is unselected.
7440      *
7441      * @event cellUnselectEvent
7442      * @param oArgs.el {HTMLElement} The unselected TD element.
7443      * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
7444      * @param oArgs.key {String} The key of the unselected cell.
7445      */
7447     /**
7448      * Fired when a cell is highlighted.
7449      *
7450      * @event cellHighlightEvent
7451      * @param oArgs.el {HTMLElement} The highlighted TD element.
7452      * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
7453      * @param oArgs.key {String} The key of the highlighted cell.
7454      */
7456     /**
7457      * Fired when a cell is unhighlighted.
7458      *
7459      * @event cellUnhighlightEvent
7460      * @param oArgs.el {HTMLElement} The unhighlighted TD element.
7461      * @param oArgs.record {YAHOO.widget.Record} The unhighlighted Record.
7462      * @param oArgs.key {String} The key of the unhighlighted cell.
7463      */
7465     /*TODO: hide from doc and use cellUnselectEvent
7466      * Fired when all cell selections are cleared.
7467      *
7468      * @event unselectAllCellsEvent
7469      */
7471     /*TODO: implement
7472      * Fired when DataTable paginator is updated.
7473      *
7474      * @event paginatorUpdateEvent
7475      * @param paginator {Object} Object literal of Paginator values.
7476      */
7478     /**
7479      * Fired when an Editor is activated.
7480      *
7481      * @event editorShowEvent
7482      * @param oArgs.editor {Object} The Editor object literal.
7483      */
7485     /**
7486      * Fired when an active Editor has a keydown.
7487      *
7488      * @event editorKeydownEvent
7489      * @param oArgs.editor {Object} The Editor object literal.
7490      * @param oArgs.event {HTMLEvent} The event object.
7491      */
7493     /**
7494      * Fired when Editor input is reverted.
7495      *
7496      * @event editorRevertEvent
7497      * @param oArgs.editor {Object} The Editor object literal.
7498      * @param oArgs.newData {Object} New data value.
7499      * @param oArgs.oldData {Object} Old data value.
7500      */
7502     /**
7503      * Fired when Editor input is saved.
7504      *
7505      * @event editorSaveEvent
7506      * @param oArgs.editor {Object} The Editor object literal.
7507      * @param oArgs.newData {Object} New data value.
7508      * @param oArgs.oldData {Object} Old data value.
7509      */
7511     /**
7512      * Fired when Editor input is canceled.
7513      *
7514      * @event editorCancelEvent
7515      * @param oArgs.editor {Object} The Editor object literal.
7516      */
7518     /**
7519      * Fired when an active Editor has a blur.
7520      *
7521      * @event editorBlurEvent
7522      * @param oArgs.editor {Object} The Editor object literal.
7523      */
7531     /**
7532      * Fired when a link is clicked.
7533      *
7534      * @event linkClickEvent
7535      * @param oArgs.event {HTMLEvent} The event object.
7536      * @param oArgs.target {HTMLElement} The A element.
7537      */
7539     /**
7540      * Fired when a BUTTON element is clicked.
7541      *
7542      * @event buttonClickEvent
7543      * @param oArgs.event {HTMLEvent} The event object.
7544      * @param oArgs.target {HTMLElement} The BUTTON element.
7545      */
7547     /**
7548      * Fired when a CHECKBOX element is clicked.
7549      *
7550      * @event checkboxClickEvent
7551      * @param oArgs.event {HTMLEvent} The event object.
7552      * @param oArgs.target {HTMLElement} The CHECKBOX element.
7553      */
7555     /*TODO
7556      * Fired when a SELECT element is changed.
7557      *
7558      * @event dropdownChangeEvent
7559      * @param oArgs.event {HTMLEvent} The event object.
7560      * @param oArgs.target {HTMLElement} The SELECT element.
7561      */
7563     /**
7564      * Fired when a RADIO element is clicked.
7565      *
7566      * @event radioClickEvent
7567      * @param oArgs.event {HTMLEvent} The event object.
7568      * @param oArgs.target {HTMLElement} The RADIO element.
7569      */
7572 /****************************************************************************/
7573 /****************************************************************************/
7574 /****************************************************************************/
7577  * The ColumnSet class defines and manages a DataTable's Columns,
7578  * including nested hierarchies and access to individual Column instances.
7580  * @namespace YAHOO.widget
7581  * @class ColumnSet
7582  * @uses YAHOO.util.EventProvider
7583  * @constructor
7584  * @param aHeaders {Object[]} Array of object literals that define header cells.
7585  */
7586 YAHOO.widget.ColumnSet = function(aHeaders) {
7587     this._sName = "instance" + YAHOO.widget.ColumnSet._nCount;
7589     // DOM tree representation of all Columns
7590     var tree = [];
7591     // Flat representation of all Columns
7592     var flat = [];
7593     // Flat representation of only Columns that are meant to display data
7594     var keys = [];
7595     // Array of HEADERS attribute values for all keys in the "keys" array
7596     var headers = [];
7598     // Tracks current node list depth being tracked
7599     var nodeDepth = -1;
7601     // Internal recursive function to defined Column instances
7602     var parseColumns = function(nodeList, parent) {
7603         // One level down
7604         nodeDepth++;
7606         // Create corresponding tree node if not already there for this depth
7607         if(!tree[nodeDepth]) {
7608             tree[nodeDepth] = [];
7609         }
7612         // Parse each node at this depth for attributes and any children
7613         for(var j=0; j<nodeList.length; j++) {
7614             var currentNode = nodeList[j];
7616             // Instantiate a new Column for each node
7617             var oColumn = new YAHOO.widget.Column(currentNode);
7619             // Add the new Column to the flat list
7620             flat.push(oColumn);
7622             // Assign its parent as an attribute, if applicable
7623             if(parent) {
7624                 oColumn.parent = parent;
7625             }
7627             // The Column has descendants
7628             if(YAHOO.lang.isArray(currentNode.children)) {
7629                 oColumn.children = currentNode.children;
7631                 // Determine COLSPAN value for this Column
7632                 var terminalChildNodes = 0;
7633                 var countTerminalChildNodes = function(ancestor) {
7634                     var descendants = ancestor.children;
7635                     // Drill down each branch and count terminal nodes
7636                     for(var k=0; k<descendants.length; k++) {
7637                         // Keep drilling down
7638                         if(YAHOO.lang.isArray(descendants[k].children)) {
7639                             countTerminalChildNodes(descendants[k]);
7640                         }
7641                         // Reached branch terminus
7642                         else {
7643                             terminalChildNodes++;
7644                         }
7645                     }
7646                 };
7647                 countTerminalChildNodes(currentNode);
7648                 oColumn._colspan = terminalChildNodes;
7650                 // Cascade certain properties to children if not defined on their own
7651                 var currentChildren = currentNode.children;
7652                 for(var k=0; k<currentChildren.length; k++) {
7653                     var child = currentChildren[k];
7654                     if(oColumn.className && (child.className === undefined)) {
7655                         child.className = oColumn.className;
7656                     }
7657                     if(oColumn.editor && (child.editor === undefined)) {
7658                         child.editor = oColumn.editor;
7659                     }
7660                     if(oColumn.editorOptions && (child.editorOptions === undefined)) {
7661                         child.editorOptions = oColumn.editorOptions;
7662                     }
7663                     if(oColumn.formatter && (child.formatter === undefined)) {
7664                         child.formatter = oColumn.formatter;
7665                     }
7666                     if(oColumn.resizeable && (child.resizeable === undefined)) {
7667                         child.resizeable = oColumn.resizeable;
7668                     }
7669                     if(oColumn.sortable && (child.sortable === undefined)) {
7670                         child.sortable = oColumn.sortable;
7671                     }
7672                     if(oColumn.width && (child.width === undefined)) {
7673                         child.width = oColumn.width;
7674                     }
7675                     // Backward compatibility
7676                     if(oColumn.type && (child.type === undefined)) {
7677                         child.type = oColumn.type;
7678                     }
7679                     if(oColumn.type && !oColumn.formatter) {
7680                         YAHOO.log("The property type has been" +
7681                         " deprecated in favor of formatter", "warn", oColumn.toString());
7682                         oColumn.formatter = oColumn.type;
7683                     }
7684                     if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
7685                         YAHOO.log("The property text has been" +
7686                         " deprecated in favor of label", "warn", oColumn.toString());
7687                         oColumn.label = oColumn.text;
7688                     }
7689                     if(oColumn.parser) {
7690                         YAHOO.log("The property parser is no longer supported",
7691                         "warn", this.toString());
7692                     }
7693                     if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
7694                             (oColumn.sortOptions.descFunction))) {
7695                         YAHOO.log("The properties sortOptions.ascFunction and " +
7696                         " sortOptions.descFunction have been deprecated in favor " +
7697                         " of sortOptions.sortFunction", "warn", this.toString());
7698                     }
7699                 }
7701                 // The children themselves must also be parsed for Column instances
7702                 if(!tree[nodeDepth+1]) {
7703                     tree[nodeDepth+1] = [];
7704                 }
7705                 parseColumns(currentChildren, oColumn);
7706             }
7707             // This Column does not have any children
7708             else {
7709                 oColumn._nKeyIndex = keys.length;
7710                 oColumn._colspan = 1;
7711                 keys.push(oColumn);
7712             }
7714             // Add the Column to the top-down tree
7715             tree[nodeDepth].push(oColumn);
7716         }
7717         nodeDepth--;
7718     };
7720     // Parse out Column instances from the array of object literals
7721     if(YAHOO.lang.isArray(aHeaders)) {
7722         parseColumns(aHeaders);
7723     }
7725     // Determine ROWSPAN value for each Column in the tree
7726     var parseTreeForRowspan = function(tree) {
7727         var maxRowDepth = 1;
7728         var currentRow;
7729         var currentColumn;
7731         // Calculate the max depth of descendants for this row
7732         var countMaxRowDepth = function(row, tmpRowDepth) {
7733             tmpRowDepth = tmpRowDepth || 1;
7735             for(var n=0; n<row.length; n++) {
7736                 var col = row[n];
7737                 // Column has children, so keep counting
7738                 if(YAHOO.lang.isArray(col.children)) {
7739                     tmpRowDepth++;
7740                     countMaxRowDepth(col.children, tmpRowDepth);
7741                     tmpRowDepth--;
7742                 }
7743                 // No children, is it the max depth?
7744                 else {
7745                     if(tmpRowDepth > maxRowDepth) {
7746                         maxRowDepth = tmpRowDepth;
7747                     }
7748                 }
7750             }
7751         };
7753         // Count max row depth for each row
7754         for(var m=0; m<tree.length; m++) {
7755             currentRow = tree[m];
7756             countMaxRowDepth(currentRow);
7758             // Assign the right ROWSPAN values to each Column in the row
7759             for(var p=0; p<currentRow.length; p++) {
7760                 currentColumn = currentRow[p];
7761                 if(!YAHOO.lang.isArray(currentColumn.children)) {
7762                     currentColumn._rowspan = maxRowDepth;
7763                 }
7764                 else {
7765                     currentColumn._rowspan = 1;
7766                 }
7767             }
7769             // Reset counter for next row
7770             maxRowDepth = 1;
7771         }
7772     };
7773     parseTreeForRowspan(tree);
7779     // Store header relationships in an array for HEADERS attribute
7780     var recurseAncestorsForHeaders = function(i, oColumn) {
7781         headers[i].push(oColumn._nId);
7782         if(oColumn.parent) {
7783             recurseAncestorsForHeaders(i, oColumn.parent);
7784         }
7785     };
7786     for(var i=0; i<keys.length; i++) {
7787         headers[i] = [];
7788         recurseAncestorsForHeaders(i, keys[i]);
7789         headers[i] = headers[i].reverse();
7790         headers[i] = headers[i].join(" ");
7791     }
7793     // Save to the ColumnSet instance
7794     this.tree = tree;
7795     this.flat = flat;
7796     this.keys = keys;
7797     this.headers = headers;
7799     YAHOO.widget.ColumnSet._nCount++;
7800     YAHOO.log("ColumnSet initialized", "info", this.toString());
7803 /////////////////////////////////////////////////////////////////////////////
7805 // Public member variables
7807 /////////////////////////////////////////////////////////////////////////////
7810  * Internal class variable to index multiple data table instances.
7812  * @property ColumnSet._nCount
7813  * @type number
7814  * @private
7815  * @static
7816  */
7817 YAHOO.widget.ColumnSet._nCount = 0;
7820  * Unique instance name.
7822  * @property _sName
7823  * @type String
7824  * @private
7825  */
7826 YAHOO.widget.ColumnSet.prototype._sName = null;
7828 /////////////////////////////////////////////////////////////////////////////
7830 // Public member variables
7832 /////////////////////////////////////////////////////////////////////////////
7835  * Top-down tree representation of Column hierarchy.
7837  * @property tree
7838  * @type YAHOO.widget.Column[]
7839  */
7840 YAHOO.widget.ColumnSet.prototype.tree = null;
7843  * Flattened representation of all Columns.
7845  * @property flat
7846  * @type YAHOO.widget.Column[]
7847  * @default []
7848  */
7849 YAHOO.widget.ColumnSet.prototype.flat = null;
7852  * Array of Columns that map one-to-one to a table column.
7854  * @property keys
7855  * @type YAHOO.widget.Column[]
7856  * @default []
7857  */
7858 YAHOO.widget.ColumnSet.prototype.keys = null;
7861  * ID index of nested parent hierarchies for HEADERS accessibility attribute.
7863  * @property headers
7864  * @type String[]
7865  * @default []
7866  */
7867 YAHOO.widget.ColumnSet.prototype.headers = null;
7869 /////////////////////////////////////////////////////////////////////////////
7871 // Public methods
7873 /////////////////////////////////////////////////////////////////////////////
7876  * Public accessor to the unique name of the ColumnSet instance.
7878  * @method toString
7879  * @return {String} Unique name of the ColumnSet instance.
7880  */
7882 YAHOO.widget.ColumnSet.prototype.toString = function() {
7883     return "ColumnSet " + this._sName;
7887  * Returns Column instance with given ID number or key.
7889  * @method getColumn
7890  * @param column {Number | String} ID number or unique key.
7891  * @return {YAHOO.widget.Column} Column instance.
7892  */
7894 YAHOO.widget.ColumnSet.prototype.getColumn = function(column) {
7895     var allColumns = this.flat;
7896     if(YAHOO.lang.isNumber(column)) {
7897         for(var i=0; i<allColumns.length; i++) {
7898             if(allColumns[i]._nId === column) {
7899                 return allColumns[i];
7900             }
7901         }
7902     }
7903     else if(YAHOO.lang.isString(column)) {
7904         for(i=0; i<allColumns.length; i++) {
7905             if(allColumns[i].key === column) {
7906                 return allColumns[i];
7907             }
7908         }
7909     }
7910     return null;
7913 /****************************************************************************/
7914 /****************************************************************************/
7915 /****************************************************************************/
7918  * The Column class defines and manages attributes of DataTable Columns
7920  * @namespace YAHOO.widget
7921  * @class Column
7922  * @constructor
7923  * @param oConfigs {Object} Object literal of configuration values.
7924  */
7925 YAHOO.widget.Column = function(oConfigs) {
7926     // Internal variables
7927     this._nId = YAHOO.widget.Column._nCount;
7928     this._sName = "Column instance" + this._nId;
7930     // Object literal defines Column attributes
7931     if(oConfigs && (oConfigs.constructor == Object)) {
7932         for(var sConfig in oConfigs) {
7933             if(sConfig) {
7934                 this[sConfig] = oConfigs[sConfig];
7935             }
7936         }
7937     }
7939     if(!YAHOO.lang.isValue(this.key)) {
7940         this.key = "yui-dt-column"+this._nId;
7941     }
7942     YAHOO.widget.Column._nCount++;
7945 /////////////////////////////////////////////////////////////////////////////
7947 // Private member variables
7949 /////////////////////////////////////////////////////////////////////////////
7952  * Internal instance counter.
7954  * @property Column._nCount
7955  * @type Number
7956  * @private
7957  * @static
7958  * @default 0
7959  */
7960 YAHOO.widget.Column._nCount = 0;
7963  * Unique instance name.
7965  * @property _sName
7966  * @type String
7967  * @private
7968  */
7969 YAHOO.widget.Column.prototype._sName = null;
7973  * Unique number assigned at instantiation, indicates original order within
7974  * ColumnSet.
7976  * @property _nId
7977  * @type Number
7978  * @private
7979  */
7980 YAHOO.widget.Column.prototype._nId = null;
7983  * Reference to Column's index within its ColumnSet's keys array, or null if not applicable.
7985  * @property _nKeyIndex
7986  * @type Number
7987  * @private
7988  */
7989 YAHOO.widget.Column.prototype._nKeyIndex = null;
7992  * Number of table cells the Column spans.
7994  * @property _colspan
7995  * @type Number
7996  * @private
7997  */
7998 YAHOO.widget.Column.prototype._colspan = 1;
8001  * Number of table rows the Column spans.
8003  * @property _rowspan
8004  * @type Number
8005  * @private
8006  */
8007 YAHOO.widget.Column.prototype._rowspan = 1;
8010  * Column's parent Column instance, or null.
8012  * @property _parent
8013  * @type YAHOO.widget.Column
8014  * @private
8015  */
8016 YAHOO.widget.Column.prototype._parent = null;
8019  * Current offsetWidth of the Column (in pixels).
8021  * @property _width
8022  * @type Number
8023  * @private
8024  */
8025 YAHOO.widget.Column.prototype._width = null;
8028  * Minimum width the Column can support (in pixels). Value is populated only if table
8029  * is fixedWidth, null otherwise.
8031  * @property _minWidth
8032  * @type Number
8033  * @private
8034  */
8035 YAHOO.widget.Column.prototype._minWidth = null;
8037 /////////////////////////////////////////////////////////////////////////////
8039 // Public member variables
8041 /////////////////////////////////////////////////////////////////////////////
8044  * Associated database field, or null.
8046  * @property key
8047  * @type String
8048  */
8049 YAHOO.widget.Column.prototype.key = null;
8052  * Text or HTML for display as Column's label in the TH element.
8054  * @property label
8055  * @type String
8056  */
8057 YAHOO.widget.Column.prototype.label = null;
8060  * Column head cell ABBR for accessibility.
8062  * @property abbr
8063  * @type String
8064  */
8065 YAHOO.widget.Column.prototype.abbr = null;
8068  * Array of object literals that define children (nested headers) of a Column.
8070  * @property children
8071  * @type Object[]
8072  */
8073 YAHOO.widget.Column.prototype.children = null;
8076  * Column width.
8078  * @property width
8079  * @type String
8080  */
8081 YAHOO.widget.Column.prototype.width = null;
8084  * Custom CSS class or array of classes to be applied to every cell in the Column.
8086  * @property className
8087  * @type String || String[]
8088  */
8089 YAHOO.widget.Column.prototype.className = null;
8092  * Defines a format function.
8094  * @property formatter
8095  * @type String || HTMLFunction
8096  */
8097 YAHOO.widget.Column.prototype.formatter = null;
8100  * Defines an editor function, otherwise Column is not editable.
8102  * @property editor
8103  * @type String || HTMLFunction
8104  */
8105 YAHOO.widget.Column.prototype.editor = null;
8108  * Defines editor options for Column in an object literal of param:value pairs.
8110  * @property editorOptions
8111  * @type Object
8112  */
8113 YAHOO.widget.Column.prototype.editorOptions = null;
8116  * True if Column is resizeable, false otherwise.
8118  * @property resizeable
8119  * @type Boolean
8120  * @default false
8121  */
8122 YAHOO.widget.Column.prototype.resizeable = false;
8125  * True if Column is sortable, false otherwise.
8127  * @property sortable
8128  * @type Boolean
8129  * @default false
8130  */
8131 YAHOO.widget.Column.prototype.sortable = false;
8134  * Default sort order for Column: "asc" or "desc".
8136  * @property sortOptions.defaultOrder
8137  * @type String
8138  * @default null
8139  */
8141  * Custom sort handler.
8143  * @property sortOptions.sortFunction
8144  * @type Function
8145  * @default null
8146  */
8147 YAHOO.widget.Column.prototype.sortOptions = null;
8163 /////////////////////////////////////////////////////////////////////////////
8165 // Public methods
8167 /////////////////////////////////////////////////////////////////////////////
8170  * Public accessor to the unique name of the Column instance.
8172  * @method toString
8173  * @return {String} Column's unique name.
8174  */
8175 YAHOO.widget.Column.prototype.toString = function() {
8176     return this._sName;
8180  * Returns unique number assigned at instantiation, indicates original order
8181  * within ColumnSet.
8183  * @method getId
8184  * @return {Number} Column's unique ID number.
8185  */
8186 YAHOO.widget.Column.prototype.getId = function() {
8187     return this._nId;
8191  * Public accessor returns Column's key index within its ColumnSet's keys array, or
8192  * null if not applicable.
8194  * @method getKeyIndex
8195  * @return {Number} Column's key index within its ColumnSet keys array, if applicable.
8196  */
8197 YAHOO.widget.Column.prototype.getKeyIndex = function() {
8198     return this._nKeyIndex;
8202  * Public accessor returns Column's parent instance if any, or null otherwise.
8204  * @method getParent
8205  * @return {YAHOO.widget.Column} Column's parent instance.
8206  */
8207 YAHOO.widget.Column.prototype.getParent = function() {
8208     return this._parent;
8212  * Public accessor returns Column's calculated COLSPAN value.
8214  * @method getColspan
8215  * @return {Number} Column's COLSPAN value.
8216  */
8217 YAHOO.widget.Column.prototype.getColspan = function() {
8218     return this._colspan;
8220 // Backward compatibility
8221 YAHOO.widget.Column.prototype.getColSpan = function() {
8222     YAHOO.log("The method getColSpan() has been" +
8223     " deprecated in favor of getColspan()", "warn", this.toString());
8224     return this.getColspan();
8228  * Public accessor returns Column's calculated ROWSPAN value.
8230  * @method getRowspan
8231  * @return {Number} Column's ROWSPAN value.
8232  */
8233 YAHOO.widget.Column.prototype.getRowspan = function() {
8234     return this._rowspan;
8237 // Backward compatibility
8238 YAHOO.widget.Column.prototype.getIndex = function() {
8239     YAHOO.log("The method getIndex() has been" +
8240     " deprecated in favor of getKeyIndex()", "warn",
8241     this.toString());
8242     return this.getKeyIndex();
8244 YAHOO.widget.Column.prototype.format = function() {
8245     YAHOO.log("The method format() has been deprecated in favor of the " +
8246     "DataTable method formatCell()", "error", this.toString());
8248 YAHOO.widget.Column.formatCheckbox = function(elCell, oRecord, oColumn, oData) {
8249     YAHOO.log("The method YAHOO.widget.Column.formatCheckbox() has been" +
8250     " deprecated in favor of YAHOO.widget.DataTable.formatCheckbox()", "warn",
8251     "YAHOO.widget.Column.formatCheckbox");
8252     YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
8254 YAHOO.widget.Column.formatCurrency = function(elCell, oRecord, oColumn, oData) {
8255     YAHOO.log("The method YAHOO.widget.Column.formatCurrency() has been" +
8256     " deprecated in favor of YAHOO.widget.DataTable.formatCurrency()", "warn",
8257     "YAHOO.widget.Column.formatCurrency");
8258     YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
8260 YAHOO.widget.Column.formatDate = function(elCell, oRecord, oColumn, oData) {
8261     YAHOO.log("The method YAHOO.widget.Column.formatDate() has been" +
8262     " deprecated in favor of YAHOO.widget.DataTable.formatDate()", "warn",
8263     "YAHOO.widget.Column.formatDate");
8264     YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
8266 YAHOO.widget.Column.formatEmail = function(elCell, oRecord, oColumn, oData) {
8267     YAHOO.log("The method YAHOO.widget.Column.formatEmail() has been" +
8268     " deprecated in favor of YAHOO.widget.DataTable.formatEmail()", "warn",
8269     "YAHOO.widget.Column.formatEmail");
8270     YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
8272 YAHOO.widget.Column.formatLink = function(elCell, oRecord, oColumn, oData) {
8273     YAHOO.log("The method YAHOO.widget.Column.formatLink() has been" +
8274     " deprecated in favor of YAHOO.widget.DataTable.formatLink()", "warn",
8275     "YAHOO.widget.Column.formatLink");
8276     YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
8278 YAHOO.widget.Column.formatNumber = function(elCell, oRecord, oColumn, oData) {
8279     YAHOO.log("The method YAHOO.widget.Column.formatNumber() has been" +
8280     " deprecated in favor of YAHOO.widget.DataTable.formatNumber()", "warn",
8281     "YAHOO.widget.Column.formatNumber");
8282     YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
8284 YAHOO.widget.Column.formatSelect = function(elCell, oRecord, oColumn, oData) {
8285     YAHOO.log("The method YAHOO.widget.Column.formatSelect() has been" +
8286     " deprecated in favor of YAHOO.widget.DataTable.formatDropdown()", "warn",
8287     "YAHOO.widget.Column.formatSelect");
8288     YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
8291 /****************************************************************************/
8292 /****************************************************************************/
8293 /****************************************************************************/
8296  * Sort static utility to support Column sorting.
8298  * @namespace YAHOO.util
8299  * @class Sort
8300  * @static
8301  */
8302 YAHOO.util.Sort = {
8303     /////////////////////////////////////////////////////////////////////////////
8304     //
8305     // Public methods
8306     //
8307     /////////////////////////////////////////////////////////////////////////////
8309     /**
8310      * Comparator function for simple case-insensitive string sorting.
8311      *
8312      * @method compare
8313      * @param a {Object} First sort argument.
8314      * @param b {Object} Second sort argument.
8315      * @param desc {Boolean} True if sort direction is descending, false if
8316      * sort direction is ascending.
8317      */
8318     compare: function(a, b, desc) {
8319         if((a === null) || (typeof a == "undefined")) {
8320             if((b === null) || (typeof b == "undefined")) {
8321                 return 0;
8322             }
8323             else {
8324                 return 1;
8325             }
8326         }
8327         else if((b === null) || (typeof b == "undefined")) {
8328             return -1;
8329         }
8331         if(a.constructor == String) {
8332             a = a.toLowerCase();
8333         }
8334         if(b.constructor == String) {
8335             b = b.toLowerCase();
8336         }
8337         if(a < b) {
8338             return (desc) ? 1 : -1;
8339         }
8340         else if (a > b) {
8341             return (desc) ? -1 : 1;
8342         }
8343         else {
8344             return 0;
8345         }
8346     }
8349 /****************************************************************************/
8350 /****************************************************************************/
8351 /****************************************************************************/
8354  * ColumnResizer subclasses DragDrop to support resizeable Columns.
8356  * @namespace YAHOO.util
8357  * @class ColumnResizer
8358  * @extends YAHOO.util.DragDrop
8359  * @constructor
8360  * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
8361  * @param oColumn {YAHOO.widget.Column} Column instance.
8362  * @param elThead {HTMLElement} TH element reference.
8363  * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
8364  * @param sGroup {String} Group name of related DragDrop items.
8365  * @param oConfig {Object} (Optional) Object literal of config values.
8366  */
8367 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elThead, sHandleId, sGroup, oConfig) {
8368     if(oDataTable && oColumn && elThead && sHandleId) {
8369         this.datatable = oDataTable;
8370         this.column = oColumn;
8371         this.cell = elThead;
8372         this.init(sHandleId, sGroup, oConfig);
8373         //this.initFrame();
8374         this.setYConstraint(0,0);
8375     }
8376     else {
8377         YAHOO.log("Column resizer could not be created due to invalid colElId","warn");
8378     }
8381 if(YAHOO.util.DD) {
8382     YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DD);
8385 /////////////////////////////////////////////////////////////////////////////
8387 // Public DOM event handlers
8389 /////////////////////////////////////////////////////////////////////////////
8392  * Handles mousedown events on the Column resizer.
8394  * @method onMouseDown
8395  * @param e {string} The mousedown event
8396  */
8397 YAHOO.util.ColumnResizer.prototype.onMouseDown = function(e) {
8398     this.startWidth = this.cell.offsetWidth;
8399     this.startPos = YAHOO.util.Dom.getX(this.getDragEl());
8401     if(this.datatable.fixedWidth) {
8402         var cellLabel = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_LABEL,"span",this.cell)[0];
8403         this.minWidth = cellLabel.offsetWidth + 6;
8404         var sib = this.cell.nextSibling;
8405         var sibCellLabel = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.DataTable.CLASS_LABEL,"span",sib)[0];
8406         this.sibMinWidth = sibCellLabel.offsetWidth + 6;
8407 //!!
8408         var left = ((this.startWidth - this.minWidth) < 0) ? 0 : (this.startWidth - this.minWidth);
8409         var right = ((sib.offsetWidth - this.sibMinWidth) < 0) ? 0 : (sib.offsetWidth - this.sibMinWidth);
8410         this.setXConstraint(left, right);
8411         YAHOO.log("cellstartwidth:" + this.startWidth,"time");
8412         YAHOO.log("cellminwidth:" + this.minWidth,"time");
8413         YAHOO.log("sibstartwidth:" + sib.offsetWidth,"time");
8414         YAHOO.log("sibminwidth:" + this.sibMinWidth,"time");
8415         YAHOO.log("l:" + left + " AND r:" + right,"time");
8416     }
8421  * Handles mouseup events on the Column resizer.
8423  * @method onMouseUp
8424  * @param e {string} The mouseup event
8425  */
8426 YAHOO.util.ColumnResizer.prototype.onMouseUp = function(e) {
8427     //TODO: replace the resizer where it belongs:
8428     var resizeStyle = YAHOO.util.Dom.get(this.handleElId).style;
8429     resizeStyle.left = "auto";
8430     resizeStyle.right = 0;
8431     resizeStyle.marginRight = "-6px";
8432     resizeStyle.width = "6px";
8433     //.yui-dt-headresizer {position:absolute;margin-right:-6px;right:0;bottom:0;width:6px;height:100%;cursor:w-resize;cursor:col-resize;}
8436     //var cells = this.datatable._elTable.tHead.rows[this.datatable._elTable.tHead.rows.length-1].cells;
8437     //for(var i=0; i<cells.length; i++) {
8438         //cells[i].style.width = "5px";
8439     //}
8441     //TODO: set new ColumnSet width values
8442     this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.cell});
8446  * Handles drag events on the Column resizer.
8448  * @method onDrag
8449  * @param e {string} The drag event
8450  */
8451 YAHOO.util.ColumnResizer.prototype.onDrag = function(e) {
8452     var newPos = YAHOO.util.Dom.getX(this.getDragEl());
8453     //YAHOO.log("newpos:"+newPos,"warn");//YAHOO.util.Event.getPageX(e);
8454     var offsetX = newPos - this.startPos;
8455     //YAHOO.log("offset:"+offsetX,"warn");
8456     //YAHOO.log("startwidth:"+this.startWidth + " and offset:"+offsetX,"warn");
8457     var newWidth = this.startWidth + offsetX;
8458     //YAHOO.log("newwidth:"+newWidth,"warn");
8460     if(newWidth < this.minWidth) {
8461         newWidth = this.minWidth;
8462     }
8464     // Resize the Column
8465     var oDataTable = this.datatable;
8466     var elCell = this.cell;
8468     //YAHOO.log("newwidth" + newWidth,"warn");
8469     //YAHOO.log(newWidth + " AND "+ elColumn.offsetWidth + " AND " + elColumn.id,"warn");
8471     // Resize the other Columns
8472     if(oDataTable.fixedWidth) {
8473         // Moving right or left?
8474         var sib = elCell.nextSibling;
8475         //var sibIndex = elCell.index + 1;
8476         var sibnewwidth = sib.offsetWidth - offsetX;
8477         if(sibnewwidth < this.sibMinWidth) {
8478             sibnewwidth = this.sibMinWidth;
8479         }
8481         //TODO: how else to cycle through all the Columns without having to use an index property?
8482         for(var i=0; i<oDataTable._oColumnSet.length; i++) {
8483             //if((i != elCell.index) &&  (i!=sibIndex)) {
8484             //    YAHOO.util.Dom.get(oDataTable._oColumnSet.keys[i].id).style.width = oDataTable._oColumnSet.keys[i].width + "px";
8485             //}
8486         }
8487         sib.style.width = sibnewwidth;
8488         elCell.style.width = newWidth + "px";
8489         //oDataTable._oColumnSet.flat[sibIndex].width = sibnewwidth;
8490         //oDataTable._oColumnSet.flat[elCell.index].width = newWidth;
8492     }
8493     else {
8494         elCell.style.width = newWidth + "px";
8495     }
8501 /****************************************************************************/
8502 /****************************************************************************/
8503 /****************************************************************************/
8506  * A RecordSet defines and manages a set of Records.
8508  * @namespace YAHOO.widget
8509  * @class RecordSet
8510  * @param data {Object || Object[]} An object literal or an array of data.
8511  * @constructor
8512  */
8513 YAHOO.widget.RecordSet = function(data) {
8514     // Internal variables
8515     this._sName = "RecordSet instance" + YAHOO.widget.RecordSet._nCount;
8516     YAHOO.widget.RecordSet._nCount++;
8517     this._records = [];
8518     this._length = 0;
8519     
8520     if(data) {
8521         if(YAHOO.lang.isArray(data)) {
8522             this.addRecords(data);
8523         }
8524         else if(data.constructor == Object) {
8525             this.addRecord(data);
8526         }
8527     }
8529     /**
8530      * Fired when a new Record is added to the RecordSet.
8531      *
8532      * @event recordAddEvent
8533      * @param oArgs.record {YAHOO.widget.Record} The Record instance.
8534      * @param oArgs.data {Object} Data added.
8535      */
8536     this.createEvent("recordAddEvent");
8538     /**
8539      * Fired when multiple Records are added to the RecordSet at once.
8540      *
8541      * @event recordsAddEvent
8542      * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
8543      * @param oArgs.data {Object[]} Data added.
8544      */
8545     this.createEvent("recordsAddEvent");
8547     /**
8548      * Fired when a Record is updated with new data.
8549      *
8550      * @event recordUpdateEvent
8551      * @param oArgs.record {YAHOO.widget.Record} The Record instance.
8552      * @param oArgs.newData {Object} New data.
8553      * @param oArgs.oldData {Object} Old data.
8554      */
8555     this.createEvent("recordUpdateEvent");
8556     
8557     /**
8558      * Fired when a Record is deleted from the RecordSet.
8559      *
8560      * @event recordDeleteEvent
8561      * @param oArgs.data {Object} A copy of the data held by the Record,
8562      * or an array of data object literals if multiple Records were deleted at once.
8563      * @param oArgs.index {Object} Index of the deleted Record.
8564      */
8565     this.createEvent("recordDeleteEvent");
8567     /**
8568      * Fired when multiple Records are deleted from the RecordSet at once.
8569      *
8570      * @event recordsDeleteEvent
8571      * @param oArgs.data {Object[]} An array of data object literals copied
8572      * from the Records.
8573      * @param oArgs.index {Object} Index of the first deleted Record.
8574      */
8575     this.createEvent("recordsDeleteEvent");
8576     
8577     /**
8578      * Fired when all Records are deleted from the RecordSet at once.
8579      *
8580      * @event resetEvent
8581      */
8582     this.createEvent("resetEvent");
8584     /**
8585      * Fired when a Record Key is updated with new data.
8586      *
8587      * @event keyUpdateEvent
8588      * @param oArgs.record {YAHOO.widget.Record} The Record instance.
8589      * @param oArgs.key {String} The updated key.
8590      * @param oArgs.newData {Object} New data.
8591      * @param oArgs.oldData {Object} Old data.
8592      *
8593      */
8594     this.createEvent("keyUpdateEvent");
8596     YAHOO.log("RecordSet initialized", "info", this.toString());
8599 if(YAHOO.util.EventProvider) {
8600     YAHOO.augment(YAHOO.widget.RecordSet, YAHOO.util.EventProvider);
8602 else {
8603     YAHOO.log("Missing dependency: YAHOO.util.EventProvider","error",this.toString());
8606 /////////////////////////////////////////////////////////////////////////////
8608 // Private member variables
8610 /////////////////////////////////////////////////////////////////////////////
8612  * Internal class variable to name multiple Recordset instances.
8614  * @property RecordSet._nCount
8615  * @type Number
8616  * @private
8617  * @static
8618  */
8619 YAHOO.widget.RecordSet._nCount = 0;
8622  * Unique instance name.
8624  * @property _sName
8625  * @type String
8626  * @private
8627  */
8628 YAHOO.widget.RecordSet.prototype._sName = null;
8631  * Internal variable to give unique indexes to Record instances.
8633  * @property _nCount
8634  * @type Number
8635  * @private
8636  */
8637 YAHOO.widget.RecordSet.prototype._nRecordCount = 0;
8640  * Internal counter of how many Records are in the RecordSet.
8642  * @property _length
8643  * @type Number
8644  * @private
8645  */
8646 YAHOO.widget.RecordSet.prototype._length = null;
8648 /////////////////////////////////////////////////////////////////////////////
8650 // Private methods
8652 /////////////////////////////////////////////////////////////////////////////
8655  * Adds one Record to the RecordSet at the given index. If index is null,
8656  * then adds the Record to the end of the RecordSet.
8658  * @method _addRecord
8659  * @param oData {Object} An object literal of data.
8660  * @param index {Number} (optional) Position index.
8661  * @return {YAHOO.widget.Record} A Record instance.
8662  * @private
8663  */
8664 YAHOO.widget.RecordSet.prototype._addRecord = function(oData, index) {
8665     var oRecord = new YAHOO.widget.Record(oData);
8666     oRecord._nId = this._nRecordCount;
8667     this._nRecordCount++;
8668     
8669     if(YAHOO.lang.isNumber(index) && (index > -1)) {
8670         this._records.splice(index,0,oRecord);
8671     }
8672     else {
8673         index = this.getLength();
8674         this._records.push(oRecord);
8675     }
8676     this._length++;
8677     return oRecord;
8681  * Deletes Records from the RecordSet at the given index. If range is null,
8682  * then only one Record is deleted.
8684  * @method _deleteRecord
8685  * @param index {Number} Position index.
8686  * @param range {Number} (optional) How many Records to delete
8687  * @private
8688  */
8689 YAHOO.widget.RecordSet.prototype._deleteRecord = function(index, range) {
8690     if(!YAHOO.lang.isNumber(range) || (range < 0)) {
8691         range = 1;
8692     }
8693     this._records.splice(index, range);
8694     this._length = this._length - range;
8697 /////////////////////////////////////////////////////////////////////////////
8699 // Public methods
8701 /////////////////////////////////////////////////////////////////////////////
8704  * Public accessor to the unique name of the RecordSet instance.
8706  * @method toString
8707  * @return {String} Unique name of the RecordSet instance.
8708  */
8709 YAHOO.widget.RecordSet.prototype.toString = function() {
8710     return this._sName;
8714  * Returns the number of Records held in the RecordSet.
8716  * @method getLength
8717  * @return {Number} Number of records in the RecordSet.
8718  */
8719 YAHOO.widget.RecordSet.prototype.getLength = function() {
8720         return this._length;
8724  * Returns Record at given position index.
8726  * @method getRecord
8727  * @param index {Number} Record's Recordset position index.
8728  * @return {YAHOO.widget.Record} Record object.
8729  */
8730 YAHOO.widget.RecordSet.prototype.getRecord = function(index) {
8731     if(YAHOO.lang.isNumber(index)) {
8732         return this._records[index];
8733     }
8734     /*else if(YAHOO.lang.isString(identifier)) {
8735         for(var i=0; i<this._records.length; i++) {
8736             if(this._records[i].yuiRecordId == identifier) {
8737                 return this._records[i];
8738             }
8739         }
8740     }*/
8741     return null;
8746  * Returns an array of Records from the RecordSet.
8748  * @method getRecords
8749  * @param index {Number} (optional) Recordset position index of which Record to
8750  * start at.
8751  * @param range {Number} (optional) Number of Records to get.
8752  * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
8753  * length equal to given range. If index is not given, all Records are returned.
8754  */
8755 YAHOO.widget.RecordSet.prototype.getRecords = function(index, range) {
8756     if(!YAHOO.lang.isNumber(index)) {
8757         return this._records;
8758     }
8759     if(!YAHOO.lang.isNumber(range)) {
8760         return this._records.slice(index);
8761     }
8762     return this._records.slice(index, index+range);
8766  * Returns position index for the given Record.
8768  * @method getRecordIndex
8769  * @param oRecord {YAHOO.widget.Record} Record instance.
8770  * @return {Number} Record's RecordSet position index.
8771  */
8773 YAHOO.widget.RecordSet.prototype.getRecordIndex = function(oRecord) {
8774     for(var i=this._records.length-1; i>-1; i--) {
8775         if(oRecord.getId() === this._records[i].getId()) {
8776             return i;
8777         }
8778     }
8779     return null;
8784  * Adds one Record to the RecordSet at the given index. If index is null,
8785  * then adds the Record to the end of the RecordSet.
8787  * @method addRecord
8788  * @param oData {Object} An object literal of data.
8789  * @param index {Number} (optional) Position index.
8790  * @return {YAHOO.widget.Record} A Record instance.
8791  */
8792 YAHOO.widget.RecordSet.prototype.addRecord = function(oData, index) {
8793     if(oData && (oData.constructor == Object)) {
8794         var oRecord = this._addRecord(oData, index);
8795         this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
8796         YAHOO.log("Added Record at index " + index +
8797                 " with data " + YAHOO.lang.dump(oData), "info", this.toString());
8798         return oRecord;
8799     }
8800     else {
8801         YAHOO.log("Could not add Record with data" +
8802                 YAHOO.lang.dump(oData), "info", this.toString());
8803         return null;
8804     }
8808  * Adds multiple Records at once to the RecordSet at the given index with the
8809  * given data. If index is null, then the new Records are added to the end of
8810  * the RecordSet.
8812  * @method addRecords
8813  * @param aData {Object[]} An array of object literal data.
8814  * @param index {Number} (optional) Position index.
8815  * @return {YAHOO.widget.Record[]} An array of Record instances.
8816  */
8817 YAHOO.widget.RecordSet.prototype.addRecords = function(aData, index) {
8818     if(YAHOO.lang.isArray(aData)) {
8819         var newRecords = [];
8820         // Can't go backwards bc we need to preserve order
8821         for(var i=0; i<aData.length; i++) {
8822             if(aData[i] && (aData[i].constructor == Object)) {
8823                 var record = this._addRecord(aData[i], index);
8824                 newRecords.push(record);
8825             }
8826        }
8827         this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
8828         YAHOO.log("Added " + newRecords.length + " Record(s) at index " + index +
8829                 " with data " + YAHOO.lang.dump(aData), "info", this.toString());
8830        return newRecords;
8831     }
8832     else if(aData && (aData.constructor == Object)) {
8833         var oRecord = this._addRecord(aData);
8834         this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
8835         YAHOO.log("Added 1 Record at index " + index +
8836                 " with data " + YAHOO.lang.dump(aData), "info", this.toString());
8837         return oRecord;
8838     }
8839     else {
8840         YAHOO.log("Could not add Records with data " +
8841                 YAHOO.lang.dump(aData), "info", this.toString());
8842     }
8846  * Updates given Record with given data.
8848  * @method updateRecord
8849  * @param record {YAHOO.widget.Record | Number} A Record instance, or Record's
8850  * RecordSet position index.
8851  * @param oData {Object) Object literal of new data.
8852  * @return {YAHOO.widget.Record} Updated Record, or null.
8853  */
8854 YAHOO.widget.RecordSet.prototype.updateRecord = function(record, oData) {
8855     var oRecord = null;
8856     if(YAHOO.lang.isNumber(record)) {
8857         oRecord = this._records[record];
8858     }
8859     else if(record instanceof YAHOO.widget.Record) {
8860         oRecord = record;
8861     }
8862     if(oRecord && oData && (oData.constructor == Object)) {
8863         // Copy data from the Record for the event that gets fired later
8864         var oldData = {};
8865         for(var key in oRecord._oData) {
8866             oldData[key] = oRecord._oData[key];
8867         }
8868         oRecord._oData = oData;
8869         this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
8870         YAHOO.log("Record at index " + this.getRecordIndex(oRecord) +
8871                 " updated with data " + YAHOO.lang.dump(oData), "info", this.toString());
8872         return oRecord;
8873     }
8874     else {
8875         YAHOO.log("Could not update Record " + record, "error", this.toString());
8876         return null;
8877     }
8881  * Updates given Record at given key with given data.
8883  * @method updateKey
8884  * @param record {YAHOO.widget.Record | Number} A Record instance, or Record's
8885  * RecordSet position index.
8886  * @param sKey {String} Key name.
8887  * @param oData {Object) New data.
8888  */
8889 YAHOO.widget.RecordSet.prototype.updateKey = function(record, sKey, oData) {
8890     var oRecord;
8891     
8892     if(YAHOO.lang.isNumber(record)) {
8893         oRecord = this._records[record];
8894     }
8895     if(record instanceof YAHOO.widget.Record) {
8896         oRecord = record;
8898         var oldData = null;
8899         var keyValue = oRecord._oData[sKey];
8900         // Copy data from the Record for the event that gets fired later
8901         if(keyValue && keyValue.constructor == Object) {
8902             oldData = {};
8903             for(var key in keyValue) {
8904                 oldData[key] = keyValue[key];
8905             }
8906         }
8907         // Copy by value
8908         else {
8909             oldData = keyValue;
8910         }
8912         oRecord._oData[sKey] = oData;
8913         this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
8914         YAHOO.log("Key \"" + sKey +
8915                 "\" for Record at index " + this.getRecordIndex(oRecord) +
8916                 " updated to \"" + YAHOO.lang.dump(oData) + "\"", "info", this.toString());
8917     }
8918     else {
8919         YAHOO.log("Could not update key " + sKey + " for Record " + record, "error", this.toString());
8920     }
8924  * Replaces all Records in RecordSet with new data.
8926  * @method replaceRecords
8927  * @param data {Object || Object[]} An object literal of data or an array of
8928  * object literal data.
8929  * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
8930  * an array of Records.
8931  */
8932 YAHOO.widget.RecordSet.prototype.replaceRecords = function(data) {
8933     this.reset();
8934     return this.addRecords(data);
8938  * Sorts all Records by given function.
8940  * @method sortRecords
8941  * @param fnSort {Function} Reference to a sort function.
8942  * @param desc {Boolean} True if sort direction is descending, false if sort
8943  * direction is ascending.
8944  * @return {YAHOO.widget.Record[]} Sorted array of Records.
8945  */
8946 YAHOO.widget.RecordSet.prototype.sortRecords = function(fnSort, desc) {
8947     return this._records.sort(function(a, b) {return fnSort(a, b, desc);});
8952  * Removes the Record at the given position index from the RecordSet. If a range
8953  * is also provided, removes that many Records, starting from the index. Length
8954  * of RecordSet is correspondingly shortened.
8956  * @method deleteRecord
8957  * @param index {Number} Record's RecordSet position index.
8958  * @param range {Number} (optional) How many Records to delete.
8959  * @return {Object} A copy of the data held by the deleted Record.
8960  */
8961 YAHOO.widget.RecordSet.prototype.deleteRecord = function(index) {
8962     if(YAHOO.lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
8963         // Copy data from the Record for the event that gets fired later
8964         var oRecordData = this.getRecord(index).getData();
8965         var oData = {};
8966         for(var key in oRecordData) {
8967             oData[key] = oRecordData[key];
8968         }
8969         
8970         this._deleteRecord(index);
8971         this.fireEvent("recordDeleteEvent",{data:oData,index:index});
8972         YAHOO.log("Record deleted at index " + index +
8973                 " and containing data " + YAHOO.lang.dump(oData), "info", this.toString());
8974         return oData;
8975     }
8976     else {
8977         YAHOO.log("Could not delete Record at index " + index, "error", this.toString());
8978         return null;
8979     }
8983  * Removes the Record at the given position index from the RecordSet. If a range
8984  * is also provided, removes that many Records, starting from the index. Length
8985  * of RecordSet is correspondingly shortened.
8987  * @method deleteRecords
8988  * @param index {Number} Record's RecordSet position index.
8989  * @param range {Number} (optional) How many Records to delete.
8990  */
8991 YAHOO.widget.RecordSet.prototype.deleteRecords = function(index, range) {
8992     if(!YAHOO.lang.isNumber(range)) {
8993         range = 1;
8994     }
8995     if(YAHOO.lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
8996         var recordsToDelete = this.getRecords(index, range);
8997         // Copy data from each Record for the event that gets fired later
8998         var deletedData = [];
8999         for(var i=0; i<recordsToDelete.length; i++) {
9000             var oData = {};
9001             for(var key in recordsToDelete[i]) {
9002                 oData[key] = recordsToDelete[i][key];
9003             }
9004             deletedData.push(oData);
9005         }
9006         this._deleteRecord(index, range);
9008         this.fireEvent("recordsDeleteEvent",{data:deletedData,index:index});
9009         YAHOO.log(range + "Record(s) deleted at index " + index +
9010                 " and containing data " + YAHOO.lang.dump(deletedData), "info", this.toString());
9012     }
9013     else {
9014         YAHOO.log("Could not delete Records at index " + index, "error", this.toString());
9015     }
9019  * Deletes all Records from the RecordSet.
9021  * @method reset
9022  */
9023 YAHOO.widget.RecordSet.prototype.reset = function() {
9024     this._records = [];
9025     this._length = 0;
9026     this.fireEvent("resetEvent");
9027     YAHOO.log("All Records deleted from RecordSet", "info", this.toString());
9031 /****************************************************************************/
9032 /****************************************************************************/
9033 /****************************************************************************/
9036  * The Record class defines a DataTable record.
9038  * @namespace YAHOO.widget
9039  * @class Record
9040  * @constructor
9041  * @param oConfigs {Object} (optional) Object literal of key/value pairs.
9042  */
9043 YAHOO.widget.Record = function(oLiteral) {
9044     this._oData = {};
9045     if(oLiteral && (oLiteral.constructor == Object)) {
9046         for(var sKey in oLiteral) {
9047             this._oData[sKey] = oLiteral[sKey];
9048         }
9049     }
9052 /////////////////////////////////////////////////////////////////////////////
9054 // Private member variables
9056 /////////////////////////////////////////////////////////////////////////////
9058  * Unique number assigned at instantiation, indicates original order within
9059  * RecordSet.
9061  * @property _nId
9062  * @type Number
9063  * @private
9064  */
9065 YAHOO.widget.Record.prototype._nId = null;
9068  * Holds data for the Record in an object literal.
9070  * @property _oData
9071  * @type Object
9072  * @private
9073  */
9074 YAHOO.widget.Record.prototype._oData = null;
9076 /////////////////////////////////////////////////////////////////////////////
9078 // Public member variables
9080 /////////////////////////////////////////////////////////////////////////////
9082 /////////////////////////////////////////////////////////////////////////////
9084 // Public methods
9086 /////////////////////////////////////////////////////////////////////////////
9089  * Returns unique number assigned at instantiation, indicates original order
9090  * within RecordSet.
9092  * @method getId
9093  * @return Number
9094  */
9095 YAHOO.widget.Record.prototype.getId = function() {
9096     return this._nId;
9100  * Returns data for the Record for a key if given, or the entire object
9101  * literal otherwise.
9103  * @method getData
9104  * @param sKey {String} (Optional) The key to retrieve a single data value.
9105  * @return Object
9106  */
9107 YAHOO.widget.Record.prototype.getData = function(sKey) {
9108     if(YAHOO.lang.isString(sKey)) {
9109         return this._oData[sKey];
9110     }
9111     else {
9112         return this._oData;
9113     }
9117 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.3.0", build: "442"});