2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 * Mechanism to execute a series of callbacks in a non-blocking queue. Each callback is executed via setTimout unless configured with a negative timeout, in which case it is run in blocking mode in the same execution thread as the previous callback. Callbacks can be function references or object literals with the following keys:
10 * <li><code>method</code> - {Function} REQUIRED the callback function.</li>
11 * <li><code>scope</code> - {Object} the scope from which to execute the callback. Default is the global window scope.</li>
12 * <li><code>argument</code> - {Array} parameters to be passed to method as individual arguments.</li>
13 * <li><code>timeout</code> - {number} millisecond delay to wait after previous callback completion before executing this callback. Negative values cause immediate blocking execution. Default 0.</li>
14 * <li><code>until</code> - {Function} boolean function executed before each iteration. Return true to indicate completion and proceed to the next callback.</li>
15 * <li><code>iterations</code> - {Number} number of times to execute the callback before proceeding to the next callback in the chain. Incompatible with <code>until</code>.</li>
18 * @namespace YAHOO.util
21 * @param callback* {Function|Object} Any number of callbacks to initialize the queue
23 YAHOO.util.Chain = function () {
30 this.q = [].slice.call(arguments);
33 * Event fired when the callback queue is emptied via execution (not via
34 * a call to chain.stop().
37 this.createEvent('end');
40 YAHOO.util.Chain.prototype = {
42 * Timeout id used to pause or stop execution and indicate the execution state of the Chain. 0 indicates paused or stopped, -1 indicates blocking execution, and any positive number indicates non-blocking execution.
50 * Begin executing the chain, or resume execution from the last paused position.
52 * @return {Chain} the Chain instance
55 // Grab the first callback in the queue
59 // If there is no callback in the queue or the Chain is currently
60 // in an execution mode, return
62 this.fireEvent('end');
70 if (typeof fn === 'function') {
71 var o = c.scope || {},
72 args = c.argument || [],
76 if (!(args instanceof Array)) {
80 // Execute immediately if the callback timeout is negative.
85 // Execute the callback from scope, with argument
88 } else if (c.iterations) {
89 for (;c.iterations-- > 0;) {
99 // If the until condition is set, check if we're done
102 // Shift this callback from the queue and execute the next
107 // Otherwise if either iterations is not set or we're
108 // executing the last iteration, shift callback from the queue
109 } else if (!c.iterations || !--c.iterations) {
113 // Otherwise set to execute after the configured timeout
114 this.id = setTimeout(function () {
115 // Execute the callback from scope, with argument
117 // Check if the Chain was not paused from inside the callback
119 // Indicate ready to run state
121 // Start the fun all over again
132 * Add a callback to the end of the queue
134 * @param c {Function|Object} the callback function ref or object literal
135 * @return {Chain} the Chain instance
143 * Pause the execution of the Chain after the current execution of the
144 * current callback completes. If called interstitially, clears the
145 * timeout for the pending callback. Paused Chains can be restarted with
148 * @return {Chain} the Chain instance
151 clearTimeout(this.id);
157 * Stop and clear the Chain's queue after the current execution of the
158 * current callback completes.
160 * @return {Chain} the Chain instance
168 YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);
170 /****************************************************************************/
171 /****************************************************************************/
172 /****************************************************************************/
175 * The ColumnSet class defines and manages a DataTable's Columns,
176 * including nested hierarchies and access to individual Column instances.
178 * @namespace YAHOO.widget
180 * @uses YAHOO.util.EventProvider
182 * @param aDefinitions {Object[]} Array of object literals that define cells in
185 YAHOO.widget.ColumnSet = function(aDefinitions) {
186 this._sId = "yui-cs" + YAHOO.widget.ColumnSet._nCount;
188 // First clone the defs
189 aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions);
190 this._init(aDefinitions);
192 YAHOO.widget.ColumnSet._nCount++;
195 /////////////////////////////////////////////////////////////////////////////
197 // Private member variables
199 /////////////////////////////////////////////////////////////////////////////
202 * Internal class variable to index multiple ColumnSet instances.
204 * @property ColumnSet._nCount
209 YAHOO.widget.ColumnSet._nCount = 0;
211 YAHOO.widget.ColumnSet.prototype = {
213 * Unique instance name.
222 * Array of object literal Column definitions passed to the constructor.
224 * @property _aDefinitions
228 _aDefinitions : null,
230 /////////////////////////////////////////////////////////////////////////////
232 // Public member variables
234 /////////////////////////////////////////////////////////////////////////////
237 * Top-down tree representation of Column hierarchy.
240 * @type YAHOO.widget.Column[]
245 * Flattened representation of all Columns.
248 * @type YAHOO.widget.Column[]
254 * Array of Columns that map one-to-one to a table column.
257 * @type YAHOO.widget.Column[]
263 * ID index of nested parent hierarchies for HEADERS accessibility attribute.
271 /////////////////////////////////////////////////////////////////////////////
275 /////////////////////////////////////////////////////////////////////////////
278 * Initializes ColumnSet instance with data from Column definitions.
281 * @param aDefinitions {Object[]} Array of object literals that define cells in
286 _init : function(aDefinitions) {
287 // DOM tree representation of all Columns
289 // Flat representation of all Columns
291 // Flat representation of only Columns that are meant to display data
293 // Array of HEADERS attribute values for all keys in the "keys" array
296 // Tracks current node list depth being tracked
299 // Internal recursive function to define Column instances
300 var parseColumns = function(nodeList, parent) {
304 // Create corresponding tree node if not already there for this depth
305 if(!tree[nodeDepth]) {
306 tree[nodeDepth] = [];
310 // Parse each node at this depth for attributes and any children
311 for(var j=0; j<nodeList.length; j++) {
312 var currentNode = nodeList[j];
314 // Instantiate a new Column for each node
315 var oColumn = new YAHOO.widget.Column(currentNode);
317 // Cross-reference Column ID back to the original object literal definition
318 currentNode.yuiColumnId = oColumn._sId;
320 // Add the new Column to the flat list
323 // Assign its parent as an attribute, if applicable
325 oColumn._oParent = parent;
328 // The Column has descendants
329 if(YAHOO.lang.isArray(currentNode.children)) {
330 oColumn.children = currentNode.children;
332 // Determine COLSPAN value for this Column
333 var terminalChildNodes = 0;
334 var countTerminalChildNodes = function(ancestor) {
335 var descendants = ancestor.children;
336 // Drill down each branch and count terminal nodes
337 for(var k=0; k<descendants.length; k++) {
338 // Keep drilling down
339 if(YAHOO.lang.isArray(descendants[k].children)) {
340 countTerminalChildNodes(descendants[k]);
342 // Reached branch terminus
344 terminalChildNodes++;
348 countTerminalChildNodes(currentNode);
349 oColumn._nColspan = terminalChildNodes;
351 // Cascade certain properties to children if not defined on their own
352 var currentChildren = currentNode.children;
353 for(var k=0; k<currentChildren.length; k++) {
354 var child = currentChildren[k];
355 if(oColumn.className && (child.className === undefined)) {
356 child.className = oColumn.className;
358 if(oColumn.editor && (child.editor === undefined)) {
359 child.editor = oColumn.editor;
362 if(oColumn.editorOptions && (child.editorOptions === undefined)) {
363 child.editorOptions = oColumn.editorOptions;
365 if(oColumn.formatter && (child.formatter === undefined)) {
366 child.formatter = oColumn.formatter;
368 if(oColumn.resizeable && (child.resizeable === undefined)) {
369 child.resizeable = oColumn.resizeable;
371 if(oColumn.sortable && (child.sortable === undefined)) {
372 child.sortable = oColumn.sortable;
377 if(oColumn.width && (child.width === undefined)) {
378 child.width = oColumn.width;
380 if(oColumn.minWidth && (child.minWidth === undefined)) {
381 child.minWidth = oColumn.minWidth;
383 if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) {
384 child.maxAutoWidth = oColumn.maxAutoWidth;
386 // Backward compatibility
387 if(oColumn.type && (child.type === undefined)) {
388 child.type = oColumn.type;
390 if(oColumn.type && !oColumn.formatter) {
391 oColumn.formatter = oColumn.type;
393 if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
394 oColumn.label = oColumn.text;
398 if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
399 (oColumn.sortOptions.descFunction))) {
403 // The children themselves must also be parsed for Column instances
404 if(!tree[nodeDepth+1]) {
405 tree[nodeDepth+1] = [];
407 parseColumns(currentChildren, oColumn);
409 // This Column does not have any children
411 oColumn._nKeyIndex = keys.length;
412 oColumn._nColspan = 1;
416 // Add the Column to the top-down tree
417 tree[nodeDepth].push(oColumn);
422 // Parse out Column instances from the array of object literals
423 if(YAHOO.lang.isArray(aDefinitions)) {
424 parseColumns(aDefinitions);
427 this._aDefinitions = aDefinitions;
435 // Determine ROWSPAN value for each Column in the tree
436 var parseTreeForRowspan = function(tree) {
441 // Calculate the max depth of descendants for this row
442 var countMaxRowDepth = function(row, tmpRowDepth) {
443 tmpRowDepth = tmpRowDepth || 1;
445 for(var n=0; n<row.length; n++) {
447 // Column has children, so keep counting
448 if(YAHOO.lang.isArray(col.children)) {
450 countMaxRowDepth(col.children, tmpRowDepth);
453 // No children, is it the max depth?
455 if(tmpRowDepth > maxRowDepth) {
456 maxRowDepth = tmpRowDepth;
463 // Count max row depth for each row
464 for(var m=0; m<tree.length; m++) {
465 currentRow = tree[m];
466 countMaxRowDepth(currentRow);
468 // Assign the right ROWSPAN values to each Column in the row
469 for(var p=0; p<currentRow.length; p++) {
470 currentColumn = currentRow[p];
471 if(!YAHOO.lang.isArray(currentColumn.children)) {
472 currentColumn._nRowspan = maxRowDepth;
475 currentColumn._nRowspan = 1;
479 // Reset counter for next row
483 parseTreeForRowspan(tree);
485 // Store tree index values
486 for(i=0; i<tree[0].length; i++) {
487 tree[0][i]._nTreeIndex = i;
490 // Store header relationships in an array for HEADERS attribute
491 var recurseAncestorsForHeaders = function(i, oColumn) {
492 headers[i].push(oColumn.getSanitizedKey());
493 if(oColumn._oParent) {
494 recurseAncestorsForHeaders(i, oColumn._oParent);
497 for(i=0; i<keys.length; i++) {
499 recurseAncestorsForHeaders(i, keys[i]);
500 headers[i] = headers[i].reverse();
503 // Save to the ColumnSet instance
507 this.headers = headers;
510 /////////////////////////////////////////////////////////////////////////////
514 /////////////////////////////////////////////////////////////////////////////
517 * Returns unique name of the ColumnSet instance.
520 * @return {String} Unique name of the ColumnSet instance.
528 * ColumnSet instance name, for logging.
531 * @return {String} Unique name of the ColumnSet instance.
534 toString : function() {
535 return "ColumnSet instance " + this._sId;
539 * Public accessor to the definitions array.
541 * @method getDefinitions
542 * @return {Object[]} Array of object literal Column definitions.
545 getDefinitions : function() {
546 var aDefinitions = this._aDefinitions;
548 // Internal recursive function to define Column instances
549 var parseColumns = function(nodeList, oSelf) {
550 // Parse each node at this depth for attributes and any children
551 for(var j=0; j<nodeList.length; j++) {
552 var currentNode = nodeList[j];
554 // Get the Column for each node
555 var oColumn = oSelf.getColumnById(currentNode.yuiColumnId);
558 // Update the current values
559 var oDefinition = oColumn.getDefinition();
560 for(var name in oDefinition) {
561 if(YAHOO.lang.hasOwnProperty(oDefinition, name)) {
562 currentNode[name] = oDefinition[name];
567 // The Column has descendants
568 if(YAHOO.lang.isArray(currentNode.children)) {
569 // The children themselves must also be parsed for Column instances
570 parseColumns(currentNode.children, oSelf);
575 parseColumns(aDefinitions, this);
576 this._aDefinitions = aDefinitions;
581 * Returns Column instance with given ID.
583 * @method getColumnById
584 * @param column {String} Column ID.
585 * @return {YAHOO.widget.Column} Column instance.
588 getColumnById : function(column) {
589 if(YAHOO.lang.isString(column)) {
590 var allColumns = this.flat;
591 for(var i=allColumns.length-1; i>-1; i--) {
592 if(allColumns[i]._sId === column) {
593 return allColumns[i];
601 * Returns Column instance with given key or ColumnSet key index.
604 * @param column {String | Number} Column key or ColumnSet key index.
605 * @return {YAHOO.widget.Column} Column instance.
608 getColumn : function(column) {
609 if(YAHOO.lang.isNumber(column) && this.keys[column]) {
610 return this.keys[column];
612 else if(YAHOO.lang.isString(column)) {
613 var allColumns = this.flat;
615 for(var i=0; i<allColumns.length; i++) {
616 if(allColumns[i].key === column) {
617 aColumns.push(allColumns[i]);
620 if(aColumns.length === 1) {
623 else if(aColumns.length > 1) {
631 * Public accessor returns array of given Column's desendants (if any), including itself.
633 * @method getDescendants
634 * @parem {YAHOO.widget.Column} Column instance.
635 * @return {Array} Array including the Column itself and all descendants (if any).
637 getDescendants : function(oColumn) {
639 var allDescendants = [];
642 // Recursive function to loop thru all children
643 var parse = function(oParent) {
644 allDescendants.push(oParent);
645 // This Column has children
646 if(oParent.children) {
647 for(i=0; i<oParent.children.length; i++) {
648 parse(oSelf.getColumn(oParent.children[i].key));
654 return allDescendants;
658 /****************************************************************************/
659 /****************************************************************************/
660 /****************************************************************************/
663 * The Column class defines and manages attributes of DataTable Columns
665 * @namespace YAHOO.widget
668 * @param oConfigs {Object} Object literal of definitions.
670 YAHOO.widget.Column = function(oConfigs) {
671 this._sId = "yui-col" + YAHOO.widget.Column._nCount;
673 // Object literal defines Column attributes
674 if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
675 for(var sConfig in oConfigs) {
677 this[sConfig] = oConfigs[sConfig];
682 // Assign a key if not found
683 if(!YAHOO.lang.isValue(this.key)) {
684 this.key = "yui-dt-col" + YAHOO.widget.Column._nCount;
687 // Assign a field if not found, defaults to key
688 if(!YAHOO.lang.isValue(this.field)) {
689 this.field = this.key;
693 YAHOO.widget.Column._nCount++;
695 // Backward compatibility
696 if(this.width && !YAHOO.lang.isNumber(this.width)) {
699 if(this.editor && YAHOO.lang.isString(this.editor)) {
700 this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions);
704 /////////////////////////////////////////////////////////////////////////////
706 // Private member variables
708 /////////////////////////////////////////////////////////////////////////////
710 YAHOO.lang.augmentObject(YAHOO.widget.Column, {
712 * Internal class variable to index multiple Column instances.
714 * @property Column._nCount
721 formatCheckbox : function(elCell, oRecord, oColumn, oData) {
722 YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
725 formatCurrency : function(elCell, oRecord, oColumn, oData) {
726 YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
729 formatDate : function(elCell, oRecord, oColumn, oData) {
730 YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
733 formatEmail : function(elCell, oRecord, oColumn, oData) {
734 YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
737 formatLink : function(elCell, oRecord, oColumn, oData) {
738 YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
741 formatNumber : function(elCell, oRecord, oColumn, oData) {
742 YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
745 formatSelect : function(elCell, oRecord, oColumn, oData) {
746 YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
750 YAHOO.widget.Column.prototype = {
752 * Unique String identifier assigned at instantiation.
761 * Reference to Column's current position index within its ColumnSet's keys
762 * array, if applicable. This property only applies to non-nested and bottom-
763 * level child Columns.
765 * @property _nKeyIndex
772 * Reference to Column's current position index within its ColumnSet's tree
773 * array, if applicable. This property only applies to non-nested and top-
774 * level parent Columns.
776 * @property _nTreeIndex
783 * Number of table cells the Column spans.
785 * @property _nColspan
792 * Number of table rows the Column spans.
794 * @property _nRowspan
801 * Column's parent Column instance, or null.
804 * @type YAHOO.widget.Column
810 * The DOM reference to the associated TH element.
819 * The DOM reference to the associated TH element's liner DIV element.
821 * @property _elThLiner
828 * The DOM reference to the associated TH element's label SPAN element.
830 * @property _elThLabel
837 * The DOM reference to the associated resizerelement (if any).
839 * @property _elResizer
846 * Internal width tracker.
855 * For unreg() purposes, a reference to the Column's DragDrop instance.
858 * @type YAHOO.util.DragDrop
864 * For unreg() purposes, a reference to the Column resizer's DragDrop instance.
866 * @property _ddResizer
867 * @type YAHOO.util.DragDrop
872 /////////////////////////////////////////////////////////////////////////////
874 // Public member variables
876 /////////////////////////////////////////////////////////////////////////////
879 * Unique name, required.
887 * Associated database field, or null.
895 * Text or HTML for display as Column's label in the TH element.
903 * Column head cell ABBR for accessibility.
911 * Array of object literals that define children (nested headers) of a Column.
919 * Column width (in pixels).
927 * Minimum Column width (in pixels).
936 * When a width is not defined for a Column, maxAutoWidth defines an upper
937 * limit that the Column should be auto-sized to. If resizeable is enabled,
938 * users may still resize to a greater width. Most useful for Columns intended
939 * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very
940 * wide Columns from disrupting visual readability by inducing truncation.
942 * @property maxAutoWidth
949 * True if Column is in hidden state.
958 * True if Column is in selected state.
967 * Custom CSS class or array of classes to be applied to every cell in the Column.
969 * @property className
970 * @type String || String[]
975 * Defines a format function.
977 * @property formatter
978 * @type String || HTMLFunction
983 * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
985 * @property currencyOptions
989 currencyOptions : null,
992 * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
994 * @property dateOptions
1001 * A CellEditor instance, otherwise Column is not editable.
1004 * @type YAHOO.widget.CellEditor
1009 * True if Column is resizeable, false otherwise. The Drag & Drop Utility is
1010 * required to enable this feature. Only bottom-level and non-nested Columns are
1013 * @property resizeable
1020 * True if Column is sortable, false otherwise.
1022 * @property sortable
1029 * @property sortOptions.defaultOrder
1030 * @deprecated Use sortOptions.defaultDir.
1033 * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
1035 * @property sortOptions.defaultDir
1040 * Custom field to sort on.
1042 * @property sortOptions.field
1047 * Custom sort handler.
1049 * @property sortOptions.sortFunction
1069 /////////////////////////////////////////////////////////////////////////////
1073 /////////////////////////////////////////////////////////////////////////////
1076 * Returns unique ID string.
1079 * @return {String} Unique ID string.
1081 getId : function() {
1086 * Column instance name, for logging.
1089 * @return {String} Column's unique name.
1091 toString : function() {
1092 return "Column instance " + this._sId;
1096 * Returns object literal definition.
1098 * @method getDefinition
1099 * @return {Object} Object literal definition.
1101 getDefinition : function() {
1102 var oDefinition = {};
1104 // Update the definition
1105 oDefinition.abbr = this.abbr;
1106 oDefinition.className = this.className;
1107 oDefinition.editor = this.editor;
1108 oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
1109 oDefinition.field = this.field;
1110 oDefinition.formatter = this.formatter;
1111 oDefinition.hidden = this.hidden;
1112 oDefinition.key = this.key;
1113 oDefinition.label = this.label;
1114 oDefinition.minWidth = this.minWidth;
1115 oDefinition.maxAutoWidth = this.maxAutoWidth;
1116 oDefinition.resizeable = this.resizeable;
1117 oDefinition.selected = this.selected;
1118 oDefinition.sortable = this.sortable;
1119 oDefinition.sortOptions = this.sortOptions;
1120 oDefinition.width = this.width;
1126 * Returns unique Column key.
1129 * @return {String} Column key.
1131 getKey : function() {
1139 * @return {String} Column field.
1141 getField : function() {
1146 * Returns Column key which has been sanitized for DOM (class and ID) usage
1147 * starts with letter, contains only letters, numbers, hyphen, or period.
1149 * @method getSanitizedKey
1150 * @return {String} Sanitized Column key.
1152 getSanitizedKey : function() {
1153 return this.getKey().replace(/[^\w\-]/g,"");
1157 * Public accessor returns Column's current position index within its
1158 * ColumnSet's keys array, if applicable. Only non-nested and bottom-level
1159 * child Columns will return a value.
1161 * @method getKeyIndex
1162 * @return {Number} Position index, or null.
1164 getKeyIndex : function() {
1165 return this._nKeyIndex;
1169 * Public accessor returns Column's current position index within its
1170 * ColumnSet's tree array, if applicable. Only non-nested and top-level parent
1171 * Columns will return a value;
1173 * @method getTreeIndex
1174 * @return {Number} Position index, or null.
1176 getTreeIndex : function() {
1177 return this._nTreeIndex;
1181 * Public accessor returns Column's parent instance if any, or null otherwise.
1184 * @return {YAHOO.widget.Column} Column's parent instance.
1186 getParent : function() {
1187 return this._oParent;
1191 * Public accessor returns Column's calculated COLSPAN value.
1193 * @method getColspan
1194 * @return {Number} Column's COLSPAN value.
1196 getColspan : function() {
1197 return this._nColspan;
1199 // Backward compatibility
1200 getColSpan : function() {
1201 return this.getColspan();
1205 * Public accessor returns Column's calculated ROWSPAN value.
1207 * @method getRowspan
1208 * @return {Number} Column's ROWSPAN value.
1210 getRowspan : function() {
1211 return this._nRowspan;
1215 * Returns DOM reference to the key TH element.
1218 * @return {HTMLElement} TH element.
1220 getThEl : function() {
1225 * Returns DOM reference to the TH's liner DIV element. Introduced since
1226 * resizeable Columns may have an extra resizer liner, making the DIV liner
1227 * not reliably the TH element's first child.
1229 * @method getThLInerEl
1230 * @return {HTMLElement} TH element.
1232 getThLinerEl : function() {
1233 return this._elThLiner;
1237 * Returns DOM reference to the resizer element, or null.
1239 * @method getResizerEl
1240 * @return {HTMLElement} DIV element.
1242 getResizerEl : function() {
1243 return this._elResizer;
1246 // Backward compatibility
1249 * @deprecated Use getThEl
1251 getColEl : function() {
1252 return this.getThEl();
1254 getIndex : function() {
1255 return this.getKeyIndex();
1257 format : function() {
1261 /****************************************************************************/
1262 /****************************************************************************/
1263 /****************************************************************************/
1266 * Sort static utility to support Column sorting.
1268 * @namespace YAHOO.util
1273 /////////////////////////////////////////////////////////////////////////////
1277 /////////////////////////////////////////////////////////////////////////////
1280 * Comparator function for simple case-insensitive string sorting.
1283 * @param a {Object} First sort argument.
1284 * @param b {Object} Second sort argument.
1285 * @param desc {Boolean} True if sort direction is descending, false if
1286 * sort direction is ascending.
1288 compare: function(a, b, desc) {
1289 if((a === null) || (typeof a == "undefined")) {
1290 if((b === null) || (typeof b == "undefined")) {
1297 else if((b === null) || (typeof b == "undefined")) {
1301 if(a.constructor == String) {
1302 a = a.toLowerCase();
1304 if(b.constructor == String) {
1305 b = b.toLowerCase();
1308 return (desc) ? 1 : -1;
1311 return (desc) ? -1 : 1;
1319 /****************************************************************************/
1320 /****************************************************************************/
1321 /****************************************************************************/
1324 * ColumnDD subclasses DragDrop to support rearrangeable Columns.
1326 * @namespace YAHOO.util
1328 * @extends YAHOO.util.DDProxy
1330 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1331 * @param oColumn {YAHOO.widget.Column} Column instance.
1332 * @param elTh {HTMLElement} TH element reference.
1333 * @param elTarget {HTMLElement} Drag target element.
1335 YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
1336 if(oDataTable && oColumn && elTh && elTarget) {
1337 this.datatable = oDataTable;
1338 this.table = oDataTable.getTableEl();
1339 this.column = oColumn;
1340 this.headCell = elTh;
1341 this.pointer = elTarget;
1342 this.newIndex = null;
1344 this.initFrame(); // Needed for DDProxy
1345 this.invalidHandleTypes = {};
1347 // Set top/bottom padding to account for children of nested columns
1348 this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
1350 YAHOO.util.Event.on(window, 'resize', function() {
1351 this.initConstraints();
1358 if(YAHOO.util.DDProxy) {
1359 YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
1360 initConstraints: function() {
1361 //Get the top, right, bottom and left positions
1362 var region = YAHOO.util.Dom.getRegion(this.table),
1363 //Get the element we are working on
1365 //Get the xy position of it
1366 xy = YAHOO.util.Dom.getXY(el),
1367 //Get the width and height
1368 width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
1369 height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
1370 //Set left to x minus left
1371 left = ((xy[0] - region.left) + 15), //Buffer of 15px
1372 //Set right to right minus x minus width
1373 right = ((region.right - xy[0] - width) + 15);
1375 //Set the constraints based on the above calculations
1376 this.setXConstraint(left, right);
1377 this.setYConstraint(10, 10);
1379 _resizeProxy: function() {
1380 this.constructor.superclass._resizeProxy.apply(this, arguments);
1381 var dragEl = this.getDragEl(),
1384 YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
1385 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
1386 var xy = YAHOO.util.Dom.getXY(el);
1387 YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
1389 YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
1390 YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
1391 YAHOO.util.Dom.setXY(this.dragEl, xy);
1393 onMouseDown: function() {
1394 this.initConstraints();
1395 this.resetConstraints();
1397 clickValidator: function(e) {
1398 if(!this.column.hidden) {
1399 var target = YAHOO.util.Event.getTarget(e);
1400 return ( this.isValidHandleChild(target) &&
1401 (this.id == this.handleElId ||
1402 this.DDM.handleWasClicked(target, this.id)) );
1405 onDragOver: function(ev, id) {
1406 // Validate target as a Column
1407 var target = this.datatable.getColumn(id);
1409 // Validate target as a top-level parent
1410 var targetIndex = target.getTreeIndex();
1411 while((targetIndex === null) && target.getParent()) {
1412 target = target.getParent();
1413 targetIndex = target.getTreeIndex();
1415 if(targetIndex !== null) {
1416 // Are we placing to left or right of target?
1417 var elTarget = target.getThEl();
1418 var newIndex = targetIndex;
1419 var mouseX = YAHOO.util.Event.getPageX(ev),
1420 targetX = YAHOO.util.Dom.getX(elTarget),
1421 midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
1422 currentIndex = this.column.getTreeIndex();
1424 if (mouseX < midX) {
1425 YAHOO.util.Dom.setX(this.pointer, targetX);
1427 var targetWidth = parseInt(elTarget.offsetWidth, 10);
1428 YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
1431 if (targetIndex > currentIndex) {
1437 else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
1438 newIndex = this.datatable.getColumnSet().tree[0].length;
1440 this.newIndex = newIndex;
1444 onDragDrop: function() {
1445 this.datatable.reorderColumn(this.column, this.newIndex);
1447 endDrag: function() {
1448 this.newIndex = null;
1449 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
1454 /****************************************************************************/
1455 /****************************************************************************/
1456 /****************************************************************************/
1459 * ColumnResizer subclasses DragDrop to support resizeable Columns.
1461 * @namespace YAHOO.util
1462 * @class ColumnResizer
1463 * @extends YAHOO.util.DDProxy
1465 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1466 * @param oColumn {YAHOO.widget.Column} Column instance.
1467 * @param elTh {HTMLElement} TH element reference.
1468 * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
1469 * @param elProxy {HTMLElement} Resizer proxy element.
1471 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
1472 if(oDataTable && oColumn && elTh && sHandleId) {
1473 this.datatable = oDataTable;
1474 this.column = oColumn;
1475 this.headCell = elTh;
1476 this.headCellLiner = oColumn.getThLinerEl();
1477 this.resizerLiner = elTh.firstChild;
1478 this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
1479 this.initFrame(); // Needed for proxy
1480 this.resetResizerEl(); // Needed when rowspan > 0
1482 // Set right padding for bug 1858462
1483 this.setPadding(0, 1, 0, 0);
1490 YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
1491 /////////////////////////////////////////////////////////////////////////////
1495 /////////////////////////////////////////////////////////////////////////////
1497 * Resets resizer element.
1499 * @method resetResizerEl
1501 resetResizerEl : function() {
1502 var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
1503 resizerStyle.left = "auto";
1504 resizerStyle.right = 0;
1505 resizerStyle.top = "auto";
1506 resizerStyle.bottom = 0;
1507 resizerStyle.height = this.headCell.offsetHeight+"px";
1510 /////////////////////////////////////////////////////////////////////////////
1512 // Public DOM event handlers
1514 /////////////////////////////////////////////////////////////////////////////
1517 * Handles mouseup events on the Column resizer.
1520 * @param e {string} The mouseup event
1522 onMouseUp : function(e) {
1523 // Reset height of all resizer els in case TH's have changed height
1524 var allKeys = this.datatable.getColumnSet().keys,
1526 for(var i=0, len=allKeys.length; i<len; i++) {
1528 if(col._ddResizer) {
1529 col._ddResizer.resetResizerEl();
1532 this.resetResizerEl();
1534 var el = this.headCellLiner;
1535 var newWidth = el.offsetWidth -
1536 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) -
1537 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0);
1539 this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth});
1543 * Handles mousedown events on the Column resizer.
1545 * @method onMouseDown
1546 * @param e {string} The mousedown event
1548 onMouseDown : function(e) {
1549 this.startWidth = this.headCellLiner.offsetWidth;
1550 this.startX = YAHOO.util.Event.getXY(e)[0];
1551 this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) +
1552 (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
1556 * Custom clickValidator to ensure Column is not in hidden state.
1558 * @method clickValidator
1562 clickValidator : function(e) {
1563 if(!this.column.hidden) {
1564 var target = YAHOO.util.Event.getTarget(e);
1565 return ( this.isValidHandleChild(target) &&
1566 (this.id == this.handleElId ||
1567 this.DDM.handleWasClicked(target, this.id)) );
1572 * Handles start drag on the Column resizer.
1575 * @param e {string} The drag event
1577 startDrag : function() {
1578 // Shrinks height of all resizer els to not hold open TH els
1579 var allKeys = this.datatable.getColumnSet().keys,
1580 thisKey = this.column.getKeyIndex(),
1582 for(var i=0, len=allKeys.length; i<len; i++) {
1584 if(col._ddResizer) {
1585 YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em";
1591 * Handles drag events on the Column resizer.
1594 * @param e {string} The drag event
1596 onDrag : function(e) {
1597 var newX = YAHOO.util.Event.getXY(e)[0];
1598 if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) {
1599 var offsetX = newX - this.startX;
1600 var newWidth = this.startWidth + offsetX - this.nLinerPadding;
1602 this.datatable.setColumnWidth(this.column, newWidth);
1609 /////////////////////////////////////////////////////////////////////////////
1613 /////////////////////////////////////////////////////////////////////////////
1616 * @property editorOptions
1617 * @deprecated Pass configs directly to CellEditor constructor.
1623 var lang = YAHOO.lang,
1625 widget = YAHOO.widget,
1629 DT = widget.DataTable;
1631 /****************************************************************************/
1632 /****************************************************************************/
1633 /****************************************************************************/
1636 * A RecordSet defines and manages a set of Records.
1638 * @namespace YAHOO.widget
1640 * @param data {Object || Object[]} An object literal or an array of data.
1643 YAHOO.widget.RecordSet = function(data) {
1644 // Internal variables
1645 this._sId = "yui-rs" + widget.RecordSet._nCount;
1646 widget.RecordSet._nCount++;
1651 if(lang.isArray(data)) {
1652 this.addRecords(data);
1654 else if(lang.isObject(data)) {
1655 this.addRecord(data);
1661 var RS = widget.RecordSet;
1664 * Internal class variable to name multiple Recordset instances.
1666 * @property RecordSet._nCount
1675 /////////////////////////////////////////////////////////////////////////////
1677 // Private member variables
1679 /////////////////////////////////////////////////////////////////////////////
1681 * Unique String identifier assigned at instantiation.
1690 * Internal counter of how many Records are in the RecordSet.
1695 * @deprecated No longer used
1699 /////////////////////////////////////////////////////////////////////////////
1703 /////////////////////////////////////////////////////////////////////////////
1706 * Adds one Record to the RecordSet at the given index. If index is null,
1707 * then adds the Record to the end of the RecordSet.
1709 * @method _addRecord
1710 * @param oData {Object} An object literal of data.
1711 * @param index {Number} (optional) Position index.
1712 * @return {YAHOO.widget.Record} A Record instance.
1715 _addRecord : function(oData, index) {
1716 var oRecord = new YAHOO.widget.Record(oData);
1718 if(YAHOO.lang.isNumber(index) && (index > -1)) {
1719 this._records.splice(index,0,oRecord);
1722 //index = this.getLength();
1723 //this._records[index] = oRecord;
1724 this._records[this._records.length] = oRecord;
1731 * Sets/replaces one Record to the RecordSet at the given index. Existing
1732 * Records with higher indexes are not shifted. If no index specified, the
1733 * Record is added to the end of the RecordSet.
1735 * @method _setRecord
1736 * @param oData {Object} An object literal of data.
1737 * @param index {Number} (optional) Position index.
1738 * @return {YAHOO.widget.Record} A Record instance.
1741 _setRecord : function(oData, index) {
1742 if (!lang.isNumber(index) || index < 0) {
1743 index = this._records.length;
1745 return (this._records[index] = new widget.Record(oData));
1747 if(lang.isNumber(index) && (index > -1)) {
1748 this._records[index] = oRecord;
1749 if((index+1) > this.getLength()) {
1750 this._length = index+1;
1754 this._records[this.getLength()] = oRecord;
1762 * Deletes Records from the RecordSet at the given index. If range is null,
1763 * then only one Record is deleted.
1765 * @method _deleteRecord
1766 * @param index {Number} Position index.
1767 * @param range {Number} (optional) How many Records to delete
1770 _deleteRecord : function(index, range) {
1771 if(!lang.isNumber(range) || (range < 0)) {
1774 this._records.splice(index, range);
1775 //this._length = this._length - range;
1778 /////////////////////////////////////////////////////////////////////////////
1782 /////////////////////////////////////////////////////////////////////////////
1785 * Returns unique name of the RecordSet instance.
1788 * @return {String} Unique name of the RecordSet instance.
1790 getId : function() {
1795 * Public accessor to the unique name of the RecordSet instance.
1798 * @return {String} Unique name of the RecordSet instance.
1800 toString : function() {
1801 return "RecordSet instance " + this._sId;
1805 * Returns the number of Records held in the RecordSet.
1808 * @return {Number} Number of records in the RecordSet.
1810 getLength : function() {
1811 //return this._length;
1812 return this._records.length;
1816 * Returns Record by ID or RecordSet position index.
1819 * @param record {YAHOO.widget.Record | Number | String} Record instance,
1820 * RecordSet position index, or Record ID.
1821 * @return {YAHOO.widget.Record} Record object.
1823 getRecord : function(record) {
1825 if(record instanceof widget.Record) {
1826 for(i=0; i<this._records.length; i++) {
1827 if(this._records[i] && (this._records[i]._sId === record._sId)) {
1832 else if(lang.isNumber(record)) {
1833 if((record > -1) && (record < this.getLength())) {
1834 return this._records[record];
1837 else if(lang.isString(record)) {
1838 for(i=0; i<this._records.length; i++) {
1839 if(this._records[i] && (this._records[i]._sId === record)) {
1840 return this._records[i];
1844 // Not a valid Record for this RecordSet
1850 * Returns an array of Records from the RecordSet.
1852 * @method getRecords
1853 * @param index {Number} (optional) Recordset position index of which Record to
1855 * @param range {Number} (optional) Number of Records to get.
1856 * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
1857 * length equal to given range. If index is not given, all Records are returned.
1859 getRecords : function(index, range) {
1860 if(!lang.isNumber(index)) {
1861 return this._records;
1863 if(!lang.isNumber(range)) {
1864 return this._records.slice(index);
1866 return this._records.slice(index, index+range);
1870 * Returns a boolean indicating whether Records exist in the RecordSet at the
1871 * specified index range. Returns true if and only if a Record exists at each
1872 * index in the range.
1873 * @method hasRecords
1876 * @return {Boolean} true if all indices are populated in the RecordSet
1878 hasRecords : function (index, range) {
1879 var recs = this.getRecords(index,range);
1880 for (var i = 0; i < range; ++i) {
1881 if (typeof recs[i] === 'undefined') {
1889 * Returns current position index for the given Record.
1891 * @method getRecordIndex
1892 * @param oRecord {YAHOO.widget.Record} Record instance.
1893 * @return {Number} Record's RecordSet position index.
1896 getRecordIndex : function(oRecord) {
1898 for(var i=this._records.length-1; i>-1; i--) {
1899 if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
1909 * Adds one Record to the RecordSet at the given index. If index is null,
1910 * then adds the Record to the end of the RecordSet.
1913 * @param oData {Object} An object literal of data.
1914 * @param index {Number} (optional) Position index.
1915 * @return {YAHOO.widget.Record} A Record instance.
1917 addRecord : function(oData, index) {
1918 if(lang.isObject(oData)) {
1919 var oRecord = this._addRecord(oData, index);
1920 this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
1929 * Adds multiple Records at once to the RecordSet at the given index with the
1930 * given object literal data. If index is null, then the new Records are
1931 * added to the end of the RecordSet.
1933 * @method addRecords
1934 * @param aData {Object[]} An object literal data or an array of data object literals.
1935 * @param index {Number} (optional) Position index.
1936 * @return {YAHOO.widget.Record[]} An array of Record instances.
1938 addRecords : function(aData, index) {
1939 if(lang.isArray(aData)) {
1940 var newRecords = [],
1943 index = lang.isNumber(index) ? index : this._records.length;
1946 // Can't go backwards bc we need to preserve order
1947 for(i=0,len=aData.length; i<len; ++i) {
1948 if(lang.isObject(aData[i])) {
1949 var record = this._addRecord(aData[i], idx++);
1950 newRecords.push(record);
1953 this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
1956 else if(lang.isObject(aData)) {
1957 var oRecord = this._addRecord(aData);
1958 this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
1967 * Sets or replaces one Record to the RecordSet at the given index. Unlike
1968 * addRecord, an existing Record at that index is not shifted to preserve it.
1969 * If no index is specified, it adds the Record to the end of the RecordSet.
1972 * @param oData {Object} An object literal of data.
1973 * @param index {Number} (optional) Position index.
1974 * @return {YAHOO.widget.Record} A Record instance.
1976 setRecord : function(oData, index) {
1977 if(lang.isObject(oData)) {
1978 var oRecord = this._setRecord(oData, index);
1979 this.fireEvent("recordSetEvent",{record:oRecord,data:oData});
1988 * Sets or replaces multiple Records at once to the RecordSet with the given
1989 * data, starting at the given index. If index is not specified, then the new
1990 * Records are added to the end of the RecordSet.
1992 * @method setRecords
1993 * @param aData {Object[]} An array of object literal data.
1994 * @param index {Number} (optional) Position index.
1995 * @return {YAHOO.widget.Record[]} An array of Record instances.
1997 setRecords : function(aData, index) {
1998 var Rec = widget.Record,
1999 a = lang.isArray(aData) ? aData : [aData],
2001 i = 0, l = a.length, j = 0;
2003 index = parseInt(index,10)|0;
2006 if (typeof a[i] === 'object' && a[i]) {
2007 added[j++] = this._records[index + i] = new Rec(a[i]);
2011 this.fireEvent("recordsSetEvent",{records:added,data:aData});
2012 // Backward compatibility for bug 1918245
2013 this.fireEvent("recordsSet",{records:added,data:aData});
2015 if (a.length && !added.length) {
2018 return added.length > 1 ? added : added[0];
2022 * Updates given Record with given data.
2024 * @method updateRecord
2025 * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2026 * a RecordSet position index, or a Record ID.
2027 * @param oData {Object} Object literal of new data.
2028 * @return {YAHOO.widget.Record} Updated Record, or null.
2030 updateRecord : function(record, oData) {
2031 var oRecord = this.getRecord(record);
2032 if(oRecord && lang.isObject(oData)) {
2033 // Copy data from the Record for the event that gets fired later
2035 for(var key in oRecord._oData) {
2036 if(lang.hasOwnProperty(oRecord._oData, key)) {
2037 oldData[key] = oRecord._oData[key];
2040 oRecord._oData = oData;
2041 this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
2051 * @deprecated Use updateRecordValue
2053 updateKey : function(record, sKey, oData) {
2054 this.updateRecordValue(record, sKey, oData);
2057 * Sets given Record at given key to given data.
2059 * @method updateRecordValue
2060 * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2061 * a RecordSet position index, or a Record ID.
2062 * @param sKey {String} Key name.
2063 * @param oData {Object} New data.
2065 updateRecordValue : function(record, sKey, oData) {
2066 var oRecord = this.getRecord(record);
2069 var keyValue = oRecord._oData[sKey];
2070 // Copy data from the Record for the event that gets fired later
2071 if(keyValue && lang.isObject(keyValue)) {
2073 for(var key in keyValue) {
2074 if(lang.hasOwnProperty(keyValue, key)) {
2075 oldData[key] = keyValue[key];
2084 oRecord._oData[sKey] = oData;
2085 this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2086 this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2093 * Replaces all Records in RecordSet with new object literal data.
2095 * @method replaceRecords
2096 * @param data {Object || Object[]} An object literal of data or an array of
2097 * data object literals.
2098 * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
2099 * an array of Records.
2101 replaceRecords : function(data) {
2103 return this.addRecords(data);
2107 * Sorts all Records by given function. Records keep their unique IDs but will
2108 * have new RecordSet position indexes.
2110 * @method sortRecords
2111 * @param fnSort {Function} Reference to a sort function.
2112 * @param desc {Boolean} True if sort direction is descending, false if sort
2113 * direction is ascending.
2114 * @return {YAHOO.widget.Record[]} Sorted array of Records.
2116 sortRecords : function(fnSort, desc) {
2117 return this._records.sort(function(a, b) {return fnSort(a, b, desc);});
2121 * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
2123 * @method reverseRecords
2124 * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
2126 reverseRecords : function() {
2127 return this._records.reverse();
2131 * Removes the Record at the given position index from the RecordSet. If a range
2132 * is also provided, removes that many Records, starting from the index. Length
2133 * of RecordSet is correspondingly shortened.
2135 * @method deleteRecord
2136 * @param index {Number} Record's RecordSet position index.
2137 * @param range {Number} (optional) How many Records to delete.
2138 * @return {Object} A copy of the data held by the deleted Record.
2140 deleteRecord : function(index) {
2141 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2142 // Copy data from the Record for the event that gets fired later
2143 var oData = widget.DataTable._cloneObject(this.getRecord(index).getData());
2145 this._deleteRecord(index);
2146 this.fireEvent("recordDeleteEvent",{data:oData,index:index});
2155 * Removes the Record at the given position index from the RecordSet. If a range
2156 * is also provided, removes that many Records, starting from the index. Length
2157 * of RecordSet is correspondingly shortened.
2159 * @method deleteRecords
2160 * @param index {Number} Record's RecordSet position index.
2161 * @param range {Number} (optional) How many Records to delete.
2162 * @return {Object[]} An array of copies of the data held by the deleted Records.
2164 deleteRecords : function(index, range) {
2165 if(!lang.isNumber(range)) {
2168 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2169 var recordsToDelete = this.getRecords(index, range);
2170 // Copy data from each Record for the event that gets fired later
2171 var deletedData = [];
2173 for(var i=0; i<recordsToDelete.length; i++) {
2174 deletedData[deletedData.length] = widget.DataTable._cloneObject(recordsToDelete[i]);
2176 this._deleteRecord(index, range);
2178 this.fireEvent("recordsDeleteEvent",{data:deletedData,index:index});
2188 * Deletes all Records from the RecordSet.
2192 reset : function() {
2195 this.fireEvent("resetEvent");
2199 /////////////////////////////////////////////////////////////////////////////
2203 /////////////////////////////////////////////////////////////////////////////
2205 // RecordSet uses EventProvider
2206 lang.augmentProto(RS, util.EventProvider);
2209 * Fired when a new Record is added to the RecordSet.
2211 * @event recordAddEvent
2212 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2213 * @param oArgs.data {Object} Data added.
2217 * Fired when multiple Records are added to the RecordSet at once.
2219 * @event recordsAddEvent
2220 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2221 * @param oArgs.data {Object[]} Data added.
2225 * Fired when a Record is set in the RecordSet.
2227 * @event recordSetEvent
2228 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2229 * @param oArgs.data {Object} Data added.
2233 * Fired when multiple Records are set in the RecordSet at once.
2235 * @event recordsSetEvent
2236 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2237 * @param oArgs.data {Object[]} Data added.
2241 * Fired when a Record is updated with new data.
2243 * @event recordUpdateEvent
2244 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2245 * @param oArgs.newData {Object} New data.
2246 * @param oArgs.oldData {Object} Old data.
2250 * Fired when a Record is deleted from the RecordSet.
2252 * @event recordDeleteEvent
2253 * @param oArgs.data {Object} A copy of the data held by the Record,
2254 * or an array of data object literals if multiple Records were deleted at once.
2255 * @param oArgs.index {Object} Index of the deleted Record.
2259 * Fired when multiple Records are deleted from the RecordSet at once.
2261 * @event recordsDeleteEvent
2262 * @param oArgs.data {Object[]} An array of data object literals copied
2264 * @param oArgs.index {Object} Index of the first deleted Record.
2268 * Fired when all Records are deleted from the RecordSet at once.
2274 * @event keyUpdateEvent
2275 * @deprecated Use recordValueUpdateEvent
2279 * Fired when a Record value is updated with new data.
2281 * @event recordValueUpdateEvent
2282 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2283 * @param oArgs.key {String} The updated key.
2284 * @param oArgs.newData {Object} New data.
2285 * @param oArgs.oldData {Object} Old data.
2290 /****************************************************************************/
2291 /****************************************************************************/
2292 /****************************************************************************/
2295 * The Record class defines a DataTable record.
2297 * @namespace YAHOO.widget
2300 * @param oConfigs {Object} (optional) Object literal of key/value pairs.
2302 YAHOO.widget.Record = function(oLiteral) {
2303 this._nCount = widget.Record._nCount;
2304 this._sId = "yui-rec" + this._nCount;
2305 widget.Record._nCount++;
2307 if(lang.isObject(oLiteral)) {
2308 for(var sKey in oLiteral) {
2309 if(lang.hasOwnProperty(oLiteral, sKey)) {
2310 this._oData[sKey] = oLiteral[sKey];
2316 /////////////////////////////////////////////////////////////////////////////
2318 // Private member variables
2320 /////////////////////////////////////////////////////////////////////////////
2323 * Internal class variable to give unique IDs to Record instances.
2325 * @property Record._nCount
2329 YAHOO.widget.Record._nCount = 0;
2331 YAHOO.widget.Record.prototype = {
2333 * Immutable unique count assigned at instantiation. Remains constant while a
2334 * Record's position index can change from sorting.
2343 * Immutable unique ID assigned at instantiation. Remains constant while a
2344 * Record's position index can change from sorting.
2353 * Holds data for the Record in an object literal.
2361 /////////////////////////////////////////////////////////////////////////////
2363 // Public member variables
2365 /////////////////////////////////////////////////////////////////////////////
2367 /////////////////////////////////////////////////////////////////////////////
2371 /////////////////////////////////////////////////////////////////////////////
2374 * Returns unique count assigned at instantiation.
2379 getCount : function() {
2380 return this._nCount;
2384 * Returns unique ID assigned at instantiation.
2389 getId : function() {
2394 * Returns data for the Record for a field if given, or the entire object
2395 * literal otherwise.
2398 * @param sField {String} (Optional) The field from which to retrieve data value.
2401 getData : function(sField) {
2402 if(lang.isString(sField)) {
2403 return this._oData[sField];
2411 * Sets given data at the given key. Use the RecordSet method setValue to trigger
2415 * @param sKey {String} The key of the new value.
2416 * @param oData {MIXED} The new value.
2418 setData : function(sKey, oData) {
2419 this._oData[sKey] = oData;
2427 var lang = YAHOO.lang,
2429 widget = YAHOO.widget,
2434 DS = util.DataSourceBase;
2437 * The DataTable widget provides a progressively enhanced DHTML control for
2438 * displaying tabular data across A-grade browsers.
2441 * @requires yahoo, dom, event, element, datasource
2442 * @optional dragdrop, dragdrop
2443 * @title DataTable Widget
2446 /****************************************************************************/
2447 /****************************************************************************/
2448 /****************************************************************************/
2451 * DataTable class for the YUI DataTable widget.
2453 * @namespace YAHOO.widget
2455 * @extends YAHOO.util.Element
2457 * @param elContainer {HTMLElement} Container element for the TABLE.
2458 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
2459 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
2460 * @param oConfigs {object} (optional) Object literal of configuration values.
2462 YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
2463 var DT = widget.DataTable;
2465 ////////////////////////////////////////////////////////////////////////////
2466 // Backward compatibility for SDT, but prevent infinite loops
2468 if(oConfigs && oConfigs.scrollable) {
2469 return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
2472 ////////////////////////////////////////////////////////////////////////////
2476 this._nIndex = DT._nCount;
2477 this._sId = "yui-dt"+this._nIndex;
2478 this._oChainRender = new YAHOO.util.Chain();
2479 this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
2481 // Initialize configs
2482 this._initConfigs(oConfigs);
2484 // Initialize DataSource
2485 this._initDataSource(oDataSource);
2486 if(!this._oDataSource) {
2490 // Initialize ColumnSet
2491 this._initColumnSet(aColumnDefs);
2492 if(!this._oColumnSet) {
2496 // Initialize RecordSet
2497 this._initRecordSet();
2498 if(!this._oRecordSet) {
2501 // Initialize Attributes
2502 DT.superclass.constructor.call(this, elContainer, this.configs);
2504 // Initialize DOM elements
2505 var okDom = this._initDomElements(elContainer);
2510 // Show message as soon as config is available
2511 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
2513 ////////////////////////////////////////////////////////////////////////////
2514 // Once per instance
2518 DT._nCurrentCount++;
2520 ////////////////////////////////////////////////////////////////////////////
2523 // Send a simple initial request
2525 success : this.onDataReturnSetRows,
2526 failure : this.onDataReturnSetRows,
2528 argument: this.getState()
2531 var initialLoad = this.get("initialLoad");
2532 if(initialLoad === true) {
2533 this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
2535 // Do not send an initial request at all
2536 else if(initialLoad === false) {
2537 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
2539 // Send an initial request with a custom payload
2541 var oCustom = initialLoad || {};
2542 oCallback.argument = oCustom.argument || {};
2543 this._oDataSource.sendRequest(oCustom.request, oCallback);
2547 var DT = widget.DataTable;
2549 /////////////////////////////////////////////////////////////////////////////
2553 /////////////////////////////////////////////////////////////////////////////
2555 lang.augmentObject(DT, {
2558 * Class name assigned to outer DataTable container.
2560 * @property DataTable.CLASS_DATATABLE
2566 CLASS_DATATABLE : "yui-dt",
2569 * Class name assigned to liner DIV elements.
2571 * @property DataTable.CLASS_LINER
2575 * @default "yui-dt-liner"
2577 CLASS_LINER : "yui-dt-liner",
2580 * Class name assigned to display label elements.
2582 * @property DataTable.CLASS_LABEL
2586 * @default "yui-dt-label"
2588 CLASS_LABEL : "yui-dt-label",
2591 * Class name assigned to messaging elements.
2593 * @property DataTable.CLASS_MESSAGE
2597 * @default "yui-dt-message"
2599 CLASS_MESSAGE : "yui-dt-message",
2602 * Class name assigned to mask element when DataTable is disabled.
2604 * @property DataTable.CLASS_MASK
2608 * @default "yui-dt-mask"
2610 CLASS_MASK : "yui-dt-mask",
2613 * Class name assigned to data elements.
2615 * @property DataTable.CLASS_DATA
2619 * @default "yui-dt-data"
2621 CLASS_DATA : "yui-dt-data",
2624 * Class name assigned to Column drag target.
2626 * @property DataTable.CLASS_COLTARGET
2630 * @default "yui-dt-coltarget"
2632 CLASS_COLTARGET : "yui-dt-coltarget",
2635 * Class name assigned to resizer handle elements.
2637 * @property DataTable.CLASS_RESIZER
2641 * @default "yui-dt-resizer"
2643 CLASS_RESIZER : "yui-dt-resizer",
2646 * Class name assigned to resizer liner elements.
2648 * @property DataTable.CLASS_RESIZERLINER
2652 * @default "yui-dt-resizerliner"
2654 CLASS_RESIZERLINER : "yui-dt-resizerliner",
2657 * Class name assigned to resizer proxy elements.
2659 * @property DataTable.CLASS_RESIZERPROXY
2663 * @default "yui-dt-resizerproxy"
2665 CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
2668 * Class name assigned to CellEditor container elements.
2670 * @property DataTable.CLASS_EDITOR
2674 * @default "yui-dt-editor"
2676 CLASS_EDITOR : "yui-dt-editor",
2679 * Class name assigned to paginator container elements.
2681 * @property DataTable.CLASS_PAGINATOR
2685 * @default "yui-dt-paginator"
2687 CLASS_PAGINATOR : "yui-dt-paginator",
2690 * Class name assigned to page number indicators.
2692 * @property DataTable.CLASS_PAGE
2696 * @default "yui-dt-page"
2698 CLASS_PAGE : "yui-dt-page",
2701 * Class name assigned to default indicators.
2703 * @property DataTable.CLASS_DEFAULT
2707 * @default "yui-dt-default"
2709 CLASS_DEFAULT : "yui-dt-default",
2712 * Class name assigned to previous indicators.
2714 * @property DataTable.CLASS_PREVIOUS
2718 * @default "yui-dt-previous"
2720 CLASS_PREVIOUS : "yui-dt-previous",
2723 * Class name assigned next indicators.
2725 * @property DataTable.CLASS_NEXT
2729 * @default "yui-dt-next"
2731 CLASS_NEXT : "yui-dt-next",
2734 * Class name assigned to first elements.
2736 * @property DataTable.CLASS_FIRST
2740 * @default "yui-dt-first"
2742 CLASS_FIRST : "yui-dt-first",
2745 * Class name assigned to last elements.
2747 * @property DataTable.CLASS_LAST
2751 * @default "yui-dt-last"
2753 CLASS_LAST : "yui-dt-last",
2756 * Class name assigned to even elements.
2758 * @property DataTable.CLASS_EVEN
2762 * @default "yui-dt-even"
2764 CLASS_EVEN : "yui-dt-even",
2767 * Class name assigned to odd elements.
2769 * @property DataTable.CLASS_ODD
2773 * @default "yui-dt-odd"
2775 CLASS_ODD : "yui-dt-odd",
2778 * Class name assigned to selected elements.
2780 * @property DataTable.CLASS_SELECTED
2784 * @default "yui-dt-selected"
2786 CLASS_SELECTED : "yui-dt-selected",
2789 * Class name assigned to highlighted elements.
2791 * @property DataTable.CLASS_HIGHLIGHTED
2795 * @default "yui-dt-highlighted"
2797 CLASS_HIGHLIGHTED : "yui-dt-highlighted",
2800 * Class name assigned to hidden elements.
2802 * @property DataTable.CLASS_HIDDEN
2806 * @default "yui-dt-hidden"
2808 CLASS_HIDDEN : "yui-dt-hidden",
2811 * Class name assigned to disabled elements.
2813 * @property DataTable.CLASS_DISABLED
2817 * @default "yui-dt-disabled"
2819 CLASS_DISABLED : "yui-dt-disabled",
2822 * Class name assigned to empty indicators.
2824 * @property DataTable.CLASS_EMPTY
2828 * @default "yui-dt-empty"
2830 CLASS_EMPTY : "yui-dt-empty",
2833 * Class name assigned to loading indicatorx.
2835 * @property DataTable.CLASS_LOADING
2839 * @default "yui-dt-loading"
2841 CLASS_LOADING : "yui-dt-loading",
2844 * Class name assigned to error indicators.
2846 * @property DataTable.CLASS_ERROR
2850 * @default "yui-dt-error"
2852 CLASS_ERROR : "yui-dt-error",
2855 * Class name assigned to editable elements.
2857 * @property DataTable.CLASS_EDITABLE
2861 * @default "yui-dt-editable"
2863 CLASS_EDITABLE : "yui-dt-editable",
2866 * Class name assigned to draggable elements.
2868 * @property DataTable.CLASS_DRAGGABLE
2872 * @default "yui-dt-draggable"
2874 CLASS_DRAGGABLE : "yui-dt-draggable",
2877 * Class name assigned to resizeable elements.
2879 * @property DataTable.CLASS_RESIZEABLE
2883 * @default "yui-dt-resizeable"
2885 CLASS_RESIZEABLE : "yui-dt-resizeable",
2888 * Class name assigned to scrollable elements.
2890 * @property DataTable.CLASS_SCROLLABLE
2894 * @default "yui-dt-scrollable"
2896 CLASS_SCROLLABLE : "yui-dt-scrollable",
2899 * Class name assigned to sortable elements.
2901 * @property DataTable.CLASS_SORTABLE
2905 * @default "yui-dt-sortable"
2907 CLASS_SORTABLE : "yui-dt-sortable",
2910 * Class name assigned to ascending elements.
2912 * @property DataTable.CLASS_ASC
2916 * @default "yui-dt-asc"
2918 CLASS_ASC : "yui-dt-asc",
2921 * Class name assigned to descending elements.
2923 * @property DataTable.CLASS_DESC
2927 * @default "yui-dt-desc"
2929 CLASS_DESC : "yui-dt-desc",
2932 * Class name assigned to BUTTON elements and/or container elements.
2934 * @property DataTable.CLASS_BUTTON
2938 * @default "yui-dt-button"
2940 CLASS_BUTTON : "yui-dt-button",
2943 * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
2945 * @property DataTable.CLASS_CHECKBOX
2949 * @default "yui-dt-checkbox"
2951 CLASS_CHECKBOX : "yui-dt-checkbox",
2954 * Class name assigned to SELECT elements and/or container elements.
2956 * @property DataTable.CLASS_DROPDOWN
2960 * @default "yui-dt-dropdown"
2962 CLASS_DROPDOWN : "yui-dt-dropdown",
2965 * Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
2967 * @property DataTable.CLASS_RADIO
2971 * @default "yui-dt-radio"
2973 CLASS_RADIO : "yui-dt-radio",
2975 /////////////////////////////////////////////////////////////////////////
2977 // Private static properties
2979 /////////////////////////////////////////////////////////////////////////
2982 * Internal class variable for indexing multiple DataTable instances.
2984 * @property DataTable._nCount
2992 * Internal class variable tracking current number of DataTable instances,
2993 * so that certain class values can be reset when all instances are destroyed.
2995 * @property DataTable._nCurrentCount
3003 * Reference to the STYLE node that is dynamically created and updated
3004 * in order to manage Column widths.
3006 * @property DataTable._elDynStyleNode
3011 _elDynStyleNode : null,
3014 * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
3016 * @property DataTable._bDynStylesFallback
3021 _bDynStylesFallback : (ua.ie && (ua.ie<7)) ? true : false,
3024 * Object literal hash of Columns and their dynamically create style rules.
3026 * @property DataTable._oDynStyles
3034 * Element reference to shared Column drag target.
3036 * @property DataTable._elColumnDragTarget
3041 _elColumnDragTarget : null,
3044 * Element reference to shared Column resizer proxy.
3046 * @property DataTable._elColumnResizerProxy
3051 _elColumnResizerProxy : null,
3053 /////////////////////////////////////////////////////////////////////////
3055 // Private static methods
3057 /////////////////////////////////////////////////////////////////////////
3060 * Clones object literal or array of object literals.
3062 * @method DataTable._cloneObject
3063 * @param o {Object} Object.
3067 _cloneObject : function(o) {
3068 if(!lang.isValue(o)) {
3074 if(o instanceof YAHOO.widget.BaseCellEditor) {
3077 else if(lang.isFunction(o)) {
3080 else if(lang.isArray(o)) {
3082 for(var i=0,len=o.length;i<len;i++) {
3083 array[i] = DT._cloneObject(o[i]);
3087 else if(lang.isObject(o)) {
3089 if(lang.hasOwnProperty(o, x)) {
3090 if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
3091 copy[x] = DT._cloneObject(o[x]);
3107 * Destroys shared Column drag target.
3109 * @method DataTable._destroyColumnDragTargetEl
3113 _destroyColumnDragTargetEl : function() {
3114 if(DT._elColumnDragTarget) {
3115 var el = DT._elColumnDragTarget;
3116 YAHOO.util.Event.purgeElement(el);
3117 el.parentNode.removeChild(el);
3118 DT._elColumnDragTarget = null;
3124 * Creates HTML markup for shared Column drag target.
3126 * @method DataTable._initColumnDragTargetEl
3127 * @return {HTMLElement} Reference to Column drag target.
3131 _initColumnDragTargetEl : function() {
3132 if(!DT._elColumnDragTarget) {
3133 // Attach Column drag target element as first child of body
3134 var elColumnDragTarget = document.createElement('div');
3135 elColumnDragTarget.className = DT.CLASS_COLTARGET;
3136 elColumnDragTarget.style.display = "none";
3137 document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
3139 // Internal tracker of Column drag target
3140 DT._elColumnDragTarget = elColumnDragTarget;
3143 return DT._elColumnDragTarget;
3147 * Destroys shared Column resizer proxy.
3149 * @method DataTable._destroyColumnResizerProxyEl
3150 * @return {HTMLElement} Reference to Column resizer proxy.
3154 _destroyColumnResizerProxyEl : function() {
3155 if(DT._elColumnResizerProxy) {
3156 var el = DT._elColumnResizerProxy;
3157 YAHOO.util.Event.purgeElement(el);
3158 el.parentNode.removeChild(el);
3159 DT._elColumnResizerProxy = null;
3164 * Creates HTML markup for shared Column resizer proxy.
3166 * @method DataTable._initColumnResizerProxyEl
3167 * @return {HTMLElement} Reference to Column resizer proxy.
3171 _initColumnResizerProxyEl : function() {
3172 if(!DT._elColumnResizerProxy) {
3173 // Attach Column resizer element as first child of body
3174 var elColumnResizerProxy = document.createElement("div");
3175 elColumnResizerProxy.id = "yui-dt-colresizerproxy"; // Needed for ColumnResizer
3176 elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
3177 document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
3179 // Internal tracker of Column resizer proxy
3180 DT._elColumnResizerProxy = elColumnResizerProxy;
3182 return DT._elColumnResizerProxy;
3186 * Formats a BUTTON element.
3188 * @method DataTable.formatButton
3189 * @param el {HTMLElement} The element to format with markup.
3190 * @param oRecord {YAHOO.widget.Record} Record instance.
3191 * @param oColumn {YAHOO.widget.Column} Column instance.
3192 * @param oData {Object | Boolean} Data value for the cell. By default, the value
3193 * is what gets written to the BUTTON.
3196 formatButton : function(el, oRecord, oColumn, oData) {
3197 var sValue = lang.isValue(oData) ? oData : "Click";
3198 //TODO: support YAHOO.widget.Button
3199 //if(YAHOO.widget.Button) {
3203 el.innerHTML = "<button type=\"button\" class=\""+
3204 DT.CLASS_BUTTON + "\">" + sValue + "</button>";
3209 * Formats a CHECKBOX element.
3211 * @method DataTable.formatCheckbox
3212 * @param el {HTMLElement} The element to format with markup.
3213 * @param oRecord {YAHOO.widget.Record} Record instance.
3214 * @param oColumn {YAHOO.widget.Column} Column instance.
3215 * @param oData {Object | Boolean} Data value for the cell. Can be a simple
3216 * Boolean to indicate whether checkbox is checked or not. Can be object literal
3217 * {checked:bBoolean, label:sLabel}. Other forms of oData require a custom
3221 formatCheckbox : function(el, oRecord, oColumn, oData) {
3222 var bChecked = oData;
3223 bChecked = (bChecked) ? " checked=\"checked\"" : "";
3224 el.innerHTML = "<input type=\"checkbox\"" + bChecked +
3225 " class=\"" + DT.CLASS_CHECKBOX + "\" />";
3229 * Formats currency. Default unit is USD.
3231 * @method DataTable.formatCurrency
3232 * @param el {HTMLElement} The element to format with markup.
3233 * @param oRecord {YAHOO.widget.Record} Record instance.
3234 * @param oColumn {YAHOO.widget.Column} Column instance.
3235 * @param oData {Number} Data value for the cell.
3238 formatCurrency : function(el, oRecord, oColumn, oData) {
3239 el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || this.get("currencyOptions"));
3243 * Formats JavaScript Dates.
3245 * @method DataTable.formatDate
3246 * @param el {HTMLElement} The element to format with markup.
3247 * @param oRecord {YAHOO.widget.Record} Record instance.
3248 * @param oColumn {YAHOO.widget.Column} Column instance.
3249 * @param oData {Object} Data value for the cell, or null.
3252 formatDate : function(el, oRecord, oColumn, oData) {
3253 var oConfig = oColumn.dateOptions || this.get("dateOptions");
3254 el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
3258 * Formats SELECT elements.
3260 * @method DataTable.formatDropdown
3261 * @param el {HTMLElement} The element to format with markup.
3262 * @param oRecord {YAHOO.widget.Record} Record instance.
3263 * @param oColumn {YAHOO.widget.Column} Column instance.
3264 * @param oData {Object} Data value for the cell, or null.
3267 formatDropdown : function(el, oRecord, oColumn, oData) {
3268 var selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
3269 options = (lang.isArray(oColumn.dropdownOptions)) ?
3270 oColumn.dropdownOptions : null,
3273 collection = el.getElementsByTagName("select");
3275 // Create the form element only once, so we can attach the onChange listener
3276 if(collection.length === 0) {
3277 // Create SELECT element
3278 selectEl = document.createElement("select");
3279 selectEl.className = DT.CLASS_DROPDOWN;
3280 selectEl = el.appendChild(selectEl);
3282 // Add event listener
3283 Ev.addListener(selectEl,"change",this._onDropdownChange,this);
3286 selectEl = collection[0];
3288 // Update the form element
3290 // Clear out previous options
3291 selectEl.innerHTML = "";
3293 // We have options to populate
3295 // Create OPTION elements
3296 for(var i=0; i<options.length; i++) {
3297 var option = options[i];
3298 var optionEl = document.createElement("option");
3299 optionEl.value = (lang.isValue(option.value)) ?
3300 option.value : option;
3301 // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
3302 optionEl.innerHTML = (lang.isValue(option.text)) ?
3303 option.text : (lang.isValue(option.label)) ? option.label : option;
3304 optionEl = selectEl.appendChild(optionEl);
3305 if (optionEl.value == selectedValue) {
3306 optionEl.selected = true;
3310 // Selected value is our only option
3312 selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
3316 el.innerHTML = lang.isValue(oData) ? oData : "";
3323 * @method DataTable.formatEmail
3324 * @param el {HTMLElement} The element to format with markup.
3325 * @param oRecord {YAHOO.widget.Record} Record instance.
3326 * @param oColumn {YAHOO.widget.Column} Column instance.
3327 * @param oData {Object} Data value for the cell, or null.
3330 formatEmail : function(el, oRecord, oColumn, oData) {
3331 if(lang.isString(oData)) {
3332 el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
3335 el.innerHTML = lang.isValue(oData) ? oData : "";
3342 * @method DataTable.formatLink
3343 * @param el {HTMLElement} The element to format with markup.
3344 * @param oRecord {YAHOO.widget.Record} Record instance.
3345 * @param oColumn {YAHOO.widget.Column} Column instance.
3346 * @param oData {Object} Data value for the cell, or null.
3349 formatLink : function(el, oRecord, oColumn, oData) {
3350 if(lang.isString(oData)) {
3351 el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
3354 el.innerHTML = lang.isValue(oData) ? oData : "";
3361 * @method DataTable.formatNumber
3362 * @param el {HTMLElement} The element to format with markup.
3363 * @param oRecord {YAHOO.widget.Record} Record instance.
3364 * @param oColumn {YAHOO.widget.Column} Column instance.
3365 * @param oData {Object} Data value for the cell, or null.
3368 formatNumber : function(el, oRecord, oColumn, oData) {
3369 el.innerHTML = util.Number.format(oData, oColumn.numberOptions || this.get("numberOptions"));
3373 * Formats INPUT TYPE=RADIO elements.
3375 * @method DataTable.formatRadio
3376 * @param el {HTMLElement} The element to format with markup.
3377 * @param oRecord {YAHOO.widget.Record} Record instance.
3378 * @param oColumn {YAHOO.widget.Column} Column instance.
3379 * @param oData {Object} (Optional) Data value for the cell.
3382 formatRadio : function(el, oRecord, oColumn, oData) {
3383 var bChecked = oData;
3384 bChecked = (bChecked) ? " checked=\"checked\"" : "";
3385 el.innerHTML = "<input type=\"radio\"" + bChecked +
3386 " name=\""+this.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
3387 " class=\"" + DT.CLASS_RADIO+ "\" />";
3391 * Formats text strings.
3393 * @method DataTable.formatText
3394 * @param el {HTMLElement} The element to format with markup.
3395 * @param oRecord {YAHOO.widget.Record} Record instance.
3396 * @param oColumn {YAHOO.widget.Column} Column instance.
3397 * @param oData {Object} (Optional) Data value for the cell.
3400 formatText : function(el, oRecord, oColumn, oData) {
3401 var value = (lang.isValue(oData)) ? oData : "";
3402 //TODO: move to util function
3403 el.innerHTML = value.toString().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
3407 * Formats TEXTAREA elements.
3409 * @method DataTable.formatTextarea
3410 * @param el {HTMLElement} The element to format with markup.
3411 * @param oRecord {YAHOO.widget.Record} Record instance.
3412 * @param oColumn {YAHOO.widget.Column} Column instance.
3413 * @param oData {Object} (Optional) Data value for the cell.
3416 formatTextarea : function(el, oRecord, oColumn, oData) {
3417 var value = (lang.isValue(oData)) ? oData : "",
3418 markup = "<textarea>" + value + "</textarea>";
3419 el.innerHTML = markup;
3423 * Formats INPUT TYPE=TEXT elements.
3425 * @method DataTable.formatTextbox
3426 * @param el {HTMLElement} The element to format with markup.
3427 * @param oRecord {YAHOO.widget.Record} Record instance.
3428 * @param oColumn {YAHOO.widget.Column} Column instance.
3429 * @param oData {Object} (Optional) Data value for the cell.
3432 formatTextbox : function(el, oRecord, oColumn, oData) {
3433 var value = (lang.isValue(oData)) ? oData : "",
3434 markup = "<input type=\"text\" value=\"" + value + "\" />";
3435 el.innerHTML = markup;
3439 * Default cell formatter
3441 * @method DataTable.formatDefault
3442 * @param el {HTMLElement} The element to format with markup.
3443 * @param oRecord {YAHOO.widget.Record} Record instance.
3444 * @param oColumn {YAHOO.widget.Column} Column instance.
3445 * @param oData {Object} (Optional) Data value for the cell.
3448 formatDefault : function(el, oRecord, oColumn, oData) {
3449 el.innerHTML = oData === undefined ||
3451 (typeof oData === 'number' && isNaN(oData)) ?
3452 " " : oData.toString();
3456 * Validates data value to type Number, doing type conversion as
3457 * necessary. A valid Number value is return, else null is returned
3458 * if input value does not validate.
3461 * @method DataTable.validateNumber
3462 * @param oData {Object} Data to validate.
3465 validateNumber : function(oData) {
3467 var number = oData * 1;
3470 if(lang.isNumber(number)) {
3479 // Done in separate step so referenced functions are defined.
3481 * Cell formatting functions.
3482 * @property DataTable.Formatter
3487 button : DT.formatButton,
3488 checkbox : DT.formatCheckbox,
3489 currency : DT.formatCurrency,
3490 "date" : DT.formatDate,
3491 dropdown : DT.formatDropdown,
3492 email : DT.formatEmail,
3493 link : DT.formatLink,
3494 "number" : DT.formatNumber,
3495 radio : DT.formatRadio,
3496 text : DT.formatText,
3497 textarea : DT.formatTextarea,
3498 textbox : DT.formatTextbox,
3500 defaultFormatter : DT.formatDefault
3503 lang.extend(DT, util.Element, {
3505 /////////////////////////////////////////////////////////////////////////////
3507 // Superclass methods
3509 /////////////////////////////////////////////////////////////////////////////
3512 * Implementation of Element's abstract method. Sets up config values.
3514 * @method initAttributes
3515 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
3519 initAttributes : function(oConfigs) {
3520 oConfigs = oConfigs || {};
3521 DT.superclass.initAttributes.call(this, oConfigs);
3524 * @attribute summary
3525 * @description Value for the SUMMARY attribute.
3529 this.setAttributeConfig("summary", {
3531 validator: lang.isString,
3532 method: function(sSummary) {
3534 this._elTable.summary = sSummary;
3540 * @attribute selectionMode
3541 * @description Specifies row or cell selection mode. Accepts the following strings:
3543 * <dt>"standard"</dt>
3544 * <dd>Standard row selection with support for modifier keys to enable
3545 * multiple selections.</dd>
3548 * <dd>Row selection with modifier keys disabled to not allow
3549 * multiple selections.</dd>
3551 * <dt>"singlecell"</dt>
3552 * <dd>Cell selection with modifier keys disabled to not allow
3553 * multiple selections.</dd>
3555 * <dt>"cellblock"</dt>
3556 * <dd>Cell selection with support for modifier keys to enable multiple
3557 * selections in a block-fashion, like a spreadsheet.</dd>
3559 * <dt>"cellrange"</dt>
3560 * <dd>Cell selection with support for modifier keys to enable multiple
3561 * selections in a range-fashion, like a calendar.</dd>
3564 * @default "standard"
3567 this.setAttributeConfig("selectionMode", {
3569 validator: lang.isString
3573 * @attribute sortedBy
3574 * @description Object literal provides metadata for initial sort values if
3575 * data will arrive pre-sorted:
3577 * <dt>sortedBy.key</dt>
3578 * <dd>{String} Key of sorted Column</dd>
3579 * <dt>sortedBy.dir</dt>
3580 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3582 * @type Object | null
3584 this.setAttributeConfig("sortedBy", {
3586 // TODO: accepted array for nested sorts
3587 validator: function(oNewSortedBy) {
3589 return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
3592 return (oNewSortedBy === null);
3595 method: function(oNewSortedBy) {
3596 // Stash the previous value
3597 var oOldSortedBy = this.get("sortedBy");
3599 // Workaround for bug 1827195
3600 this._configs.sortedBy.value = oNewSortedBy;
3602 // Remove ASC/DESC from TH
3609 if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
3610 oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
3611 nOldColumnKeyIndex = oOldColumn.getKeyIndex();
3613 // Remove previous UI from THEAD
3614 var elOldTh = oOldColumn.getThEl();
3615 Dom.removeClass(elOldTh, oOldSortedBy.dir);
3616 this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
3619 oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
3620 nNewColumnKeyIndex = oNewColumn.getKeyIndex();
3622 // Update THEAD with new UI
3623 var elNewTh = oNewColumn.getThEl();
3624 // Backward compatibility
3625 if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") || (oNewSortedBy.dir == "desc"))) {
3626 var newClass = (oNewSortedBy.dir == "desc") ?
3629 Dom.addClass(elNewTh, newClass);
3632 var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
3633 Dom.addClass(elNewTh, sortClass);
3635 this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
3641 this._elTbody.style.display = "none";
3642 var allRows = this._elTbody.rows,
3644 for(var i=allRows.length-1; i>-1; i--) {
3645 allCells = allRows[i].childNodes;
3646 if(allCells[nOldColumnKeyIndex]) {
3647 Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
3649 if(allCells[nNewColumnKeyIndex]) {
3650 Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
3653 this._elTbody.style.display = "";
3656 this._clearTrTemplateEl();
3661 * @attribute paginator
3662 * @description An instance of YAHOO.widget.Paginator.
3664 * @type {Object|YAHOO.widget.Paginator}
3666 this.setAttributeConfig("paginator", {
3668 validator : function (val) {
3669 return val === null || val instanceof widget.Paginator;
3671 method : function () { this._updatePaginator.apply(this,arguments); }
3675 * @attribute caption
3676 * @description Value for the CAPTION element. NB: Not supported in
3677 * ScrollingDataTable.
3680 this.setAttributeConfig("caption", {
3682 validator: lang.isString,
3683 method: function(sCaption) {
3684 this._initCaptionEl(sCaption);
3689 * @attribute draggableColumns
3690 * @description True if Columns are draggable to reorder, false otherwise.
3691 * The Drag & Drop Utility is required to enable this feature. Only top-level
3692 * and non-nested Columns are draggable. Write once.
3696 this.setAttributeConfig("draggableColumns", {
3698 validator: lang.isBoolean,
3699 method: function(oParam) {
3702 this._initDraggableColumns();
3705 this._destroyDraggableColumns();
3712 * @attribute renderLoopSize
3713 * @description A value greater than 0 enables DOM rendering of rows to be
3714 * executed from a non-blocking timeout queue and sets how many rows to be
3715 * rendered per timeout. Recommended for very large data sets.
3719 this.setAttributeConfig("renderLoopSize", {
3721 validator: lang.isNumber
3725 * @attribute formatRow
3726 * @description A function that accepts a TR element and its associated Record
3727 * for custom formatting. The function must return TRUE in order to automatically
3728 * continue formatting of child TD elements, else TD elements will not be
3729 * automatically formatted.
3733 this.setAttributeConfig("formatRow", {
3735 validator: lang.isFunction
3739 * @attribute generateRequest
3740 * @description A function that converts an object literal of desired DataTable
3741 * states into a request value which is then passed to the DataSource's
3742 * sendRequest method in order to retrieve data for those states. This
3743 * function is passed an object literal of state data and a reference to the
3744 * DataTable instance:
3747 * <dt>pagination<dt>
3749 * <dt>offsetRecord</dt>
3750 * <dd>{Number} Index of the first Record of the desired page</dd>
3751 * <dt>rowsPerPage</dt>
3752 * <dd>{Number} Number of rows per page</dd>
3757 * <dd>{String} Key of sorted Column</dd>
3759 * <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3762 * <dd>The DataTable instance</dd>
3765 * and by default returns a String of syntax:
3766 * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
3768 * @default HTMLFunction
3770 this.setAttributeConfig("generateRequest", {
3771 value: function(oState, oSelf) {
3773 oState = oState || {pagination:null, sortedBy:null};
3774 var sort = (oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey();
3775 var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
3776 var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
3777 var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
3779 // Build the request
3780 return "sort=" + sort +
3782 "&startIndex=" + startIndex +
3783 ((results !== null) ? "&results=" + results : "");
3785 validator: lang.isFunction
3789 * @attribute initialRequest
3790 * @description Defines the initial request that gets sent to the DataSource
3791 * during initialization. Value is ignored if initialLoad is set to any value
3796 this.setAttributeConfig("initialRequest", {
3801 * @attribute initialLoad
3802 * @description Determines whether or not to load data at instantiation. By
3803 * default, will trigger a sendRequest() to the DataSource and pass in the
3804 * request defined by initialRequest. If set to false, data will not load
3805 * at instantiation. Alternatively, implementers who wish to work with a
3806 * custom payload may pass in an object literal with the following values:
3809 * <dt>request (MIXED)</dt>
3810 * <dd>Request value.</dd>
3812 * <dt>argument (MIXED)</dt>
3813 * <dd>Custom data that will be passed through to the callback function.</dd>
3817 * @type Boolean | Object
3820 this.setAttributeConfig("initialLoad", {
3825 * @attribute dynamicData
3826 * @description If true, sorting and pagination are relegated to the DataSource
3827 * for handling, using the request returned by the "generateRequest" function.
3828 * Each new DataSource response blows away all previous Records. False by default, so
3829 * sorting and pagination will be handled directly on the client side, without
3830 * causing any new requests for data from the DataSource.
3834 this.setAttributeConfig("dynamicData", {
3836 validator: lang.isBoolean
3840 * @attribute MSG_EMPTY
3841 * @description Message to display if DataTable has no data.
3843 * @default "No records found."
3845 this.setAttributeConfig("MSG_EMPTY", {
3846 value: "No records found.",
3847 validator: lang.isString
3851 * @attribute MSG_LOADING
3852 * @description Message to display while DataTable is loading data.
3854 * @default "Loading..."
3856 this.setAttributeConfig("MSG_LOADING", {
3857 value: "Loading...",
3858 validator: lang.isString
3862 * @attribute MSG_ERROR
3863 * @description Message to display while DataTable has data error.
3865 * @default "Data error."
3867 this.setAttributeConfig("MSG_ERROR", {
3868 value: "Data error.",
3869 validator: lang.isString
3873 * @attribute MSG_SORTASC
3874 * @description Message to display in tooltip to sort Column in ascending order.
3876 * @default "Click to sort ascending"
3878 this.setAttributeConfig("MSG_SORTASC", {
3879 value: "Click to sort ascending",
3880 validator: lang.isString,
3881 method: function(sParam) {
3883 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3884 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
3885 allKeys[i]._elThLabel.firstChild.title = sParam;
3893 * @attribute MSG_SORTDESC
3894 * @description Message to display in tooltip to sort Column in descending order.
3896 * @default "Click to sort descending"
3898 this.setAttributeConfig("MSG_SORTDESC", {
3899 value: "Click to sort descending",
3900 validator: lang.isString,
3901 method: function(sParam) {
3903 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3904 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
3905 allKeys[i]._elThLabel.firstChild.title = sParam;
3913 * @attribute currencySymbol
3916 this.setAttributeConfig("currencySymbol", {
3918 validator: lang.isString
3922 * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
3923 * @attribute currencyOptions
3925 * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
3927 this.setAttributeConfig("currencyOptions", {
3929 prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
3931 decimalSeparator:".",
3932 thousandsSeparator:","
3937 * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
3938 * @attribute dateOptions
3940 * @default {format:"%m/%d/%Y", locale:"en"}
3942 this.setAttributeConfig("dateOptions", {
3943 value: {format:"%m/%d/%Y", locale:"en"}
3947 * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
3948 * @attribute numberOptions
3950 * @default {decimalPlaces:0, thousandsSeparator:","}
3952 this.setAttributeConfig("numberOptions", {
3955 thousandsSeparator:","
3961 /////////////////////////////////////////////////////////////////////////////
3963 // Private member variables
3965 /////////////////////////////////////////////////////////////////////////////
3968 * True if instance is initialized, so as to fire the initEvent after render.
3978 * Index assigned to instance.
3987 * Counter for IDs assigned to TR elements.
3989 * @property _nTrCount
3996 * Counter for IDs assigned to TD elements.
3998 * @property _nTdCount
4005 * Unique id assigned to instance "yui-dtN", useful prefix for generating unique
4006 * DOM ID strings and log messages.
4017 * @property _oChainRender
4018 * @type YAHOO.util.Chain
4021 _oChainRender : null,
4024 * DOM reference to the container element for the DataTable instance into which
4025 * all other elements get created.
4027 * @property _elContainer
4031 _elContainer : null,
4034 * DOM reference to the mask element for the DataTable instance which disables it.
4043 * DOM reference to the TABLE element for the DataTable instance.
4045 * @property _elTable
4052 * DOM reference to the CAPTION element for the DataTable instance.
4054 * @property _elCaption
4061 * DOM reference to the COLGROUP element for the DataTable instance.
4063 * @property _elColgroup
4070 * DOM reference to the THEAD element for the DataTable instance.
4072 * @property _elThead
4079 * DOM reference to the primary TBODY element for the DataTable instance.
4081 * @property _elTbody
4088 * DOM reference to the secondary TBODY element used to display DataTable messages.
4090 * @property _elMsgTbody
4097 * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
4099 * @property _elMsgTr
4106 * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
4108 * @property _elMsgTd
4115 * DataSource instance for the DataTable instance.
4117 * @property _oDataSource
4118 * @type YAHOO.util.DataSource
4121 _oDataSource : null,
4124 * ColumnSet instance for the DataTable instance.
4126 * @property _oColumnSet
4127 * @type YAHOO.widget.ColumnSet
4133 * RecordSet instance for the DataTable instance.
4135 * @property _oRecordSet
4136 * @type YAHOO.widget.RecordSet
4142 * The active CellEditor instance for the DataTable instance.
4144 * @property _oCellEditor
4145 * @type YAHOO.widget.CellEditor
4148 _oCellEditor : null,
4151 * ID string of first TR element of the current DataTable page.
4153 * @property _sFirstTrId
4160 * ID string of the last TR element of the current DataTable page.
4162 * @property _sLastTrId
4169 * Template row to create all new rows from.
4170 * @property _elTrTemplate
4171 * @type {HTMLElement}
4174 _elTrTemplate : null,
4177 * Sparse array of custom functions to set column widths for browsers that don't
4178 * support dynamic CSS rules. Functions are added at the index representing
4179 * the number of rows they update.
4181 * @property _aDynFunctions
4185 _aDynFunctions : [],
4215 /////////////////////////////////////////////////////////////////////////////
4219 /////////////////////////////////////////////////////////////////////////////
4222 * Clears browser text selection. Useful to call on rowSelectEvent or
4223 * cellSelectEvent to prevent clicks or dblclicks from selecting text in the
4226 * @method clearTextSelection
4228 clearTextSelection : function() {
4230 if(window.getSelection) {
4231 sel = window.getSelection();
4233 else if(document.getSelection) {
4234 sel = document.getSelection();
4236 else if(document.selection) {
4237 sel = document.selection;
4243 else if (sel.removeAllRanges) {
4244 sel.removeAllRanges();
4246 else if(sel.collapse) {
4253 * Sets focus on the given element.
4256 * @param el {HTMLElement} Element.
4259 _focusEl : function(el) {
4260 el = el || this._elTbody;
4261 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
4262 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
4263 // strange unexpected things as the user clicks on buttons and other controls.
4264 setTimeout(function() {
4274 * Forces Gecko repaint.
4276 * @method _repaintGecko
4277 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4280 _repaintGecko : (ua.gecko) ?
4282 el = el || this._elContainer;
4283 var parent = el.parentNode;
4284 var nextSibling = el.nextSibling;
4285 parent.insertBefore(parent.removeChild(el), nextSibling);
4289 * Forces Opera repaint.
4291 * @method _repaintOpera
4294 _repaintOpera : (ua.opera) ?
4297 document.documentElement.className += " ";
4298 document.documentElement.className.trim();
4303 * Forces Webkit repaint.
4305 * @method _repaintWebkit
4306 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4309 _repaintWebkit : (ua.webkit) ?
4311 el = el || this._elContainer;
4312 var parent = el.parentNode;
4313 var nextSibling = el.nextSibling;
4314 parent.insertBefore(parent.removeChild(el), nextSibling);
4341 * Initializes object literal of config values.
4343 * @method _initConfigs
4344 * @param oConfig {Object} Object literal of config values.
4347 _initConfigs : function(oConfigs) {
4348 if(!oConfigs || !lang.isObject(oConfigs)) {
4351 this.configs = oConfigs;
4355 * Initializes ColumnSet.
4357 * @method _initColumnSet
4358 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
4361 _initColumnSet : function(aColumnDefs) {
4362 var oColumn, i, len;
4364 if(this._oColumnSet) {
4365 // First clear _oDynStyles for existing ColumnSet and
4366 // uregister CellEditor Custom Events
4367 for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
4368 oColumn = this._oColumnSet.keys[i];
4369 DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
4370 if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
4371 oColumn.editor.unsubscribeAll();
4375 this._oColumnSet = null;
4376 this._clearTrTemplateEl();
4379 if(lang.isArray(aColumnDefs)) {
4380 this._oColumnSet = new YAHOO.widget.ColumnSet(aColumnDefs);
4382 // Backward compatibility
4383 else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
4384 this._oColumnSet = aColumnDefs;
4387 // Register CellEditor Custom Events
4388 var allKeys = this._oColumnSet.keys;
4389 for(i=0, len=allKeys.length; i<len; i++) {
4390 oColumn = allKeys[i];
4391 if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
4392 oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
4393 oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
4394 oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
4395 oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
4396 oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
4397 oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
4398 oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
4399 oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
4405 * Initializes DataSource.
4407 * @method _initDataSource
4408 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
4411 _initDataSource : function(oDataSource) {
4412 this._oDataSource = null;
4413 if(oDataSource && (oDataSource instanceof DS)) {
4414 this._oDataSource = oDataSource;
4416 // Backward compatibility
4418 var tmpTable = null;
4419 var tmpContainer = this._elContainer;
4421 //TODO: this will break if re-initing DS at runtime for SDT
4422 // Peek in container child nodes to see if TABLE already exists
4423 if(tmpContainer.hasChildNodes()) {
4424 var tmpChildren = tmpContainer.childNodes;
4425 for(i=0; i<tmpChildren.length; i++) {
4426 if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
4427 tmpTable = tmpChildren[i];
4432 var tmpFieldsArray = [];
4433 for(; i<this._oColumnSet.keys.length; i++) {
4434 tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
4437 this._oDataSource = new DS(tmpTable);
4438 this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
4439 this._oDataSource.responseSchema = {fields: tmpFieldsArray};
4446 * Initializes RecordSet.
4448 * @method _initRecordSet
4451 _initRecordSet : function() {
4452 if(this._oRecordSet) {
4453 this._oRecordSet.reset();
4456 this._oRecordSet = new YAHOO.widget.RecordSet();
4461 * Initializes DOM elements.
4463 * @method _initDomElements
4464 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
4465 * return {Boolean} False in case of error, otherwise true
4468 _initDomElements : function(elContainer) {
4470 this._initContainerEl(elContainer);
4472 this._initTableEl(this._elContainer);
4474 this._initColgroupEl(this._elTable);
4476 this._initTheadEl(this._elTable);
4479 this._initMsgTbodyEl(this._elTable);
4482 this._initTbodyEl(this._elTable);
4484 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody) {
4493 * Destroy's the DataTable outer container element, if available.
4495 * @method _destroyContainerEl
4496 * @param elContainer {HTMLElement} Reference to the container element.
4499 _destroyContainerEl : function(elContainer) {
4500 Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
4501 Ev.purgeElement(elContainer, true);
4502 elContainer.innerHTML = "";
4504 this._elContainer = null;
4505 this._elColgroup = null;
4506 this._elThead = null;
4507 this._elTbody = null;
4511 * Initializes the DataTable outer container element, including a mask.
4513 * @method _initContainerEl
4514 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
4517 _initContainerEl : function(elContainer) {
4518 // Validate container
4519 elContainer = Dom.get(elContainer);
4521 if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
4523 this._destroyContainerEl(elContainer);
4525 Dom.addClass(elContainer, DT.CLASS_DATATABLE);
4526 Ev.addListener(elContainer, "focus", this._onTableFocus, this);
4527 Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
4528 this._elContainer = elContainer;
4530 var elMask = document.createElement("div");
4531 elMask.className = DT.CLASS_MASK;
4532 elMask.style.display = "none";
4533 this._elMask = elContainer.appendChild(elMask);
4538 * Destroy's the DataTable TABLE element, if available.
4540 * @method _destroyTableEl
4543 _destroyTableEl : function() {
4544 var elTable = this._elTable;
4546 Ev.purgeElement(elTable, true);
4547 elTable.parentNode.removeChild(elTable);
4548 this._elCaption = null;
4549 this._elColgroup = null;
4550 this._elThead = null;
4551 this._elTbody = null;
4556 * Creates HTML markup CAPTION element.
4558 * @method _initCaptionEl
4559 * @param sCaption {String} Text for caption.
4562 _initCaptionEl : function(sCaption) {
4563 if(this._elTable && sCaption) {
4564 // Create CAPTION element
4565 if(!this._elCaption) {
4566 this._elCaption = this._elTable.createCaption();
4568 // Set CAPTION value
4569 this._elCaption.innerHTML = sCaption;
4571 else if(this._elCaption) {
4572 this._elCaption.parentNode.removeChild(this._elCaption);
4577 * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
4578 * container element.
4580 * @method _initTableEl
4581 * @param elContainer {HTMLElement} Container element into which to create TABLE.
4584 _initTableEl : function(elContainer) {
4587 this._destroyTableEl();
4590 this._elTable = elContainer.appendChild(document.createElement("table"));
4592 // Set SUMMARY attribute
4593 this._elTable.summary = this.get("summary");
4595 // Create CAPTION element
4596 if(this.get("caption")) {
4597 this._initCaptionEl(this.get("caption"));
4603 * Destroy's the DataTable COLGROUP element, if available.
4605 * @method _destroyColgroupEl
4608 _destroyColgroupEl : function() {
4609 var elColgroup = this._elColgroup;
4611 var elTable = elColgroup.parentNode;
4612 Ev.purgeElement(elColgroup, true);
4613 elTable.removeChild(elColgroup);
4614 this._elColgroup = null;
4619 * Initializes COLGROUP and COL elements for managing minWidth.
4621 * @method _initColgroupEl
4622 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4625 _initColgroupEl : function(elTable) {
4628 this._destroyColgroupEl();
4630 // Add COLs to DOCUMENT FRAGMENT
4631 var allCols = this._aColIds || [],
4632 allKeys = this._oColumnSet.keys,
4633 i = 0, len = allCols.length,
4635 elFragment = document.createDocumentFragment(),
4636 elColTemplate = document.createElement("col");
4638 for(i=0,len=allKeys.length; i<len; i++) {
4639 oColumn = allKeys[i];
4640 elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
4644 var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
4645 elColgroup.appendChild(elFragment);
4646 this._elColgroup = elColgroup;
4651 * Adds a COL element to COLGROUP at given index.
4653 * @method _insertColgroupColEl
4654 * @param index {Number} Index of new COL element.
4657 _insertColgroupColEl : function(index) {
4658 if(lang.isNumber(index)&& this._elColgroup) {
4659 var nextSibling = this._elColgroup.childNodes[index] || null;
4660 this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
4665 * Removes a COL element to COLGROUP at given index.
4667 * @method _removeColgroupColEl
4668 * @param index {Number} Index of removed COL element.
4671 _removeColgroupColEl : function(index) {
4672 if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
4673 this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
4678 * Reorders a COL element from old index(es) to new index.
4680 * @method _reorderColgroupColEl
4681 * @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
4682 * @param newIndex {Number} New index.
4685 _reorderColgroupColEl : function(aKeyIndexes, newIndex) {
4686 if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) {
4690 for(i=aKeyIndexes.length-1; i>-1; i--) {
4691 tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
4694 var nextSibling = this._elColgroup.childNodes[newIndex] || null;
4695 for(i=tmpCols.length-1; i>-1; i--) {
4696 this._elColgroup.insertBefore(tmpCols[i], nextSibling);
4702 * Destroy's the DataTable THEAD element, if available.
4704 * @method _destroyTheadEl
4707 _destroyTheadEl : function() {
4708 var elThead = this._elThead;
4710 var elTable = elThead.parentNode;
4711 Ev.purgeElement(elThead, true);
4712 this._destroyColumnHelpers();
4713 elTable.removeChild(elThead);
4714 this._elThead = null;
4719 * Initializes THEAD element.
4721 * @method _initTheadEl
4722 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4723 * @param {HTMLElement} Initialized THEAD element.
4726 _initTheadEl : function(elTable) {
4727 elTable = elTable || this._elTable;
4731 this._destroyTheadEl();
4733 //TODO: append to DOM later for performance
4734 var elThead = (this._elColgroup) ?
4735 elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
4736 elTable.appendChild(document.createElement("thead"));
4738 // Set up DOM events for THEAD
4739 Ev.addListener(elThead, "focus", this._onTheadFocus, this);
4740 Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
4741 Ev.addListener(elThead, "mouseover", this._onTableMouseover, this);
4742 Ev.addListener(elThead, "mouseout", this._onTableMouseout, this);
4743 Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
4744 Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
4745 Ev.addListener(elThead, "click", this._onTheadClick, this);
4747 // Since we can't listen for click and dblclick on the same element...
4748 // Attach separately to THEAD and TBODY
4749 ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
4751 var oColumnSet = this._oColumnSet,
4754 // Add TRs to the THEAD
4755 var colTree = oColumnSet.tree;
4757 for(i=0; i<colTree.length; i++) {
4758 var elTheadTr = elThead.appendChild(document.createElement("tr"));
4760 // ...and create TH cells
4761 for(j=0; j<colTree[i].length; j++) {
4762 oColumn = colTree[i][j];
4763 elTh = elTheadTr.appendChild(document.createElement("th"));
4764 this._initThEl(elTh,oColumn);
4767 // Set FIRST/LAST on THEAD rows
4769 Dom.addClass(elTheadTr, DT.CLASS_FIRST);
4771 if(i === (colTree.length-1)) {
4772 Dom.addClass(elTheadTr, DT.CLASS_LAST);
4777 // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
4778 var aFirstHeaders = oColumnSet.headers[0] || [];
4779 for(i=0; i<aFirstHeaders.length; i++) {
4780 Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
4782 var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
4783 for(i=0; i<aLastHeaders.length; i++) {
4784 Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
4788 ///TODO: try _repaintGecko(this._elContainer) instead
4790 if(ua.webkit && ua.webkit < 420) {
4792 setTimeout(function() {
4793 elThead.style.display = "";
4795 elThead.style.display = 'none';
4798 this._elThead = elThead;
4800 // Column helpers needs _elThead to exist
4801 this._initColumnHelpers();
4806 * Populates TH element as defined by Column.
4809 * @param elTh {HTMLElement} TH element reference.
4810 * @param oColumn {YAHOO.widget.Column} Column object.
4813 _initThEl : function(elTh, oColumn) {
4814 elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
4815 elTh.innerHTML = "";
4816 elTh.rowSpan = oColumn.getRowspan();
4817 elTh.colSpan = oColumn.getColspan();
4818 oColumn._elTh = elTh;
4820 var elThLiner = elTh.appendChild(document.createElement("div"));
4821 elThLiner.id = elTh.id + "-liner"; // Needed for resizer
4822 elThLiner.className = DT.CLASS_LINER;
4823 oColumn._elThLiner = elThLiner;
4825 var elThLabel = elThLiner.appendChild(document.createElement("span"));
4826 elThLabel.className = DT.CLASS_LABEL;
4828 // Assign abbr attribute
4830 elTh.abbr = oColumn.abbr;
4832 // Clear minWidth on hidden Columns
4833 if(oColumn.hidden) {
4834 this._clearMinWidth(oColumn);
4837 elTh.className = this._getColumnClassNames(oColumn);
4839 // Set Column width...
4841 // Validate minWidth
4842 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
4843 oColumn.minWidth : oColumn.width;
4844 // ...for fallback cases
4845 if(DT._bDynStylesFallback) {
4846 elTh.firstChild.style.overflow = 'hidden';
4847 elTh.firstChild.style.width = nWidth + 'px';
4849 // ...for non fallback cases
4851 this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
4855 this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
4856 oColumn._elThLabel = elThLabel;
4860 * Outputs markup into the given TH based on given Column.
4862 * @method DataTable.formatTheadCell
4863 * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
4864 * not the liner DIV element.
4865 * @param oColumn {YAHOO.widget.Column} Column instance.
4866 * @param oSortedBy {Object} Sort state object literal.
4868 formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
4869 var sKey = oColumn.getKey();
4870 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
4872 // Add accessibility link for sortable Columns
4873 if(oColumn.sortable) {
4874 // Calculate the direction
4875 var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
4876 var bDesc = (sSortClass === DT.CLASS_DESC);
4878 // This is the sorted Column
4879 if(oSortedBy && (oColumn.key === oSortedBy.key)) {
4880 bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
4883 // Generate a unique HREF for visited status
4884 var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
4886 // Generate a dynamic TITLE for sort status
4887 var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
4889 // Format the element
4890 elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
4892 // Just display the label for non-sortable Columns
4894 elCellLabel.innerHTML = sLabel;
4899 * Disables DD from top-level Column TH elements.
4901 * @method _destroyDraggableColumns
4904 _destroyDraggableColumns : function() {
4906 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
4907 oColumn = this._oColumnSet.tree[0][i];
4909 oColumn._dd = oColumn._dd.unreg();
4910 Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);
4916 * Initializes top-level Column TH elements into DD instances.
4918 * @method _initDraggableColumns
4921 _initDraggableColumns : function() {
4922 this._destroyDraggableColumns();
4924 var oColumn, elTh, elDragTarget;
4925 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
4926 oColumn = this._oColumnSet.tree[0][i];
4927 elTh = oColumn.getThEl();
4928 Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
4929 elDragTarget = DT._initColumnDragTargetEl();
4930 oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
4938 * Disables resizeability on key Column TH elements.
4940 * @method _destroyResizeableColumns
4943 _destroyResizeableColumns : function() {
4944 var aKeys = this._oColumnSet.keys;
4945 for(var i=0, len=aKeys.length; i<len; i++) {
4946 if(aKeys[i]._ddResizer) {
4947 aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
4948 Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
4954 * Initializes resizeability on key Column TH elements.
4956 * @method _initResizeableColumns
4959 _initResizeableColumns : function() {
4960 this._destroyResizeableColumns();
4962 var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
4963 for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
4964 oColumn = this._oColumnSet.keys[i];
4965 if(oColumn.resizeable) {
4966 elTh = oColumn.getThEl();
4967 Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
4968 elThLiner = oColumn.getThLinerEl();
4970 // Bug 1915349: So resizer is as tall as TH when rowspan > 1
4971 // Create a separate resizer liner with position:relative
4972 elThResizerLiner = elTh.appendChild(document.createElement("div"));
4973 elThResizerLiner.className = DT.CLASS_RESIZERLINER;
4975 // Move TH contents into the new resizer liner
4976 elThResizerLiner.appendChild(elThLiner);
4978 // Create the resizer
4979 elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
4980 elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
4981 elThResizer.className = DT.CLASS_RESIZER;
4982 oColumn._elResizer = elThResizer;
4984 // Create the resizer proxy, once globally
4985 elResizerProxy = DT._initColumnResizerProxyEl();
4986 oColumn._ddResizer = new YAHOO.util.ColumnResizer(
4987 this, oColumn, elTh, elThResizer, elResizerProxy);
4988 cancelClick = function(e) {
4989 Ev.stopPropagation(e);
4991 Ev.addListener(elThResizer,"click",cancelClick);
5000 * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
5002 * @method _destroyColumnHelpers
5005 _destroyColumnHelpers : function() {
5006 this._destroyDraggableColumns();
5007 this._destroyResizeableColumns();
5011 * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
5013 * @method _initColumnHelpers
5016 _initColumnHelpers : function() {
5017 if(this.get("draggableColumns")) {
5018 this._initDraggableColumns();
5020 this._initResizeableColumns();
5024 * Destroy's the DataTable TBODY element, if available.
5026 * @method _destroyTbodyEl
5029 _destroyTbodyEl : function() {
5030 var elTbody = this._elTbody;
5032 var elTable = elTbody.parentNode;
5033 Ev.purgeElement(elTbody, true);
5034 elTable.removeChild(elTbody);
5035 this._elTbody = null;
5040 * Initializes TBODY element for data.
5042 * @method _initTbodyEl
5043 * @param elTable {HTMLElement} TABLE element into which to create TBODY .
5046 _initTbodyEl : function(elTable) {
5049 this._destroyTbodyEl();
5052 var elTbody = elTable.appendChild(document.createElement("tbody"));
5053 elTbody.tabIndex = 0;
5054 elTbody.className = DT.CLASS_DATA;
5056 // Set up DOM events for TBODY
5057 Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
5058 Ev.addListener(elTbody, "mouseover", this._onTableMouseover, this);
5059 Ev.addListener(elTbody, "mouseout", this._onTableMouseout, this);
5060 Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
5061 Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
5062 Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
5063 Ev.addListener(elTbody, "keypress", this._onTableKeypress, this);
5064 Ev.addListener(elTbody, "click", this._onTbodyClick, this);
5066 // Since we can't listen for click and dblclick on the same element...
5067 // Attach separately to THEAD and TBODY
5068 ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
5071 // IE puts focus outline in the wrong place
5073 elTbody.hideFocus=true;
5076 this._elTbody = elTbody;
5081 * Destroy's the DataTable message TBODY element, if available.
5083 * @method _destroyMsgTbodyEl
5086 _destroyMsgTbodyEl : function() {
5087 var elMsgTbody = this._elMsgTbody;
5089 var elTable = elMsgTbody.parentNode;
5090 Ev.purgeElement(elMsgTbody, true);
5091 elTable.removeChild(elMsgTbody);
5092 this._elTbody = null;
5097 * Initializes TBODY element for messaging.
5099 * @method _initMsgTbodyEl
5100 * @param elTable {HTMLElement} TABLE element into which to create TBODY
5103 _initMsgTbodyEl : function(elTable) {
5105 var elMsgTbody = document.createElement("tbody");
5106 elMsgTbody.className = DT.CLASS_MESSAGE;
5107 var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
5108 elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5109 this._elMsgTr = elMsgTr;
5110 var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
5111 elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
5112 elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5113 this._elMsgTd = elMsgTd;
5114 elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
5115 var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
5116 elMsgLiner.className = DT.CLASS_LINER;
5117 this._elMsgTbody = elMsgTbody;
5122 * Initialize internal event listeners
5124 * @method _initEvents
5127 _initEvents : function () {
5128 // Initialize Column sort
5129 this._initColumnSort();
5131 // Add the document level click listener
5132 YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
5134 // Paginator integration
5135 this.subscribe("paginatorChange",function () {
5136 this._handlePaginatorChange.apply(this,arguments);
5139 this.subscribe("initEvent",function () {
5140 this.renderPaginator();
5143 // Initialize CellEditor integration
5144 this._initCellEditing();
5148 * Initializes Column sorting.
5150 * @method _initColumnSort
5153 _initColumnSort : function() {
5154 this.subscribe("theadCellClickEvent", this.onEventSortColumn);
5156 // Backward compatibility
5157 var oSortedBy = this.get("sortedBy");
5159 if(oSortedBy.dir == "desc") {
5160 this._configs.sortedBy.value.dir = DT.CLASS_DESC;
5162 else if(oSortedBy.dir == "asc") {
5163 this._configs.sortedBy.value.dir = DT.CLASS_ASC;
5169 * Initializes CellEditor integration.
5171 * @method _initCellEditing
5174 _initCellEditing : function() {
5175 this.subscribe("editorBlurEvent",function () {
5176 this.onEditorBlurEvent.apply(this,arguments);
5178 this.subscribe("editorBlockEvent",function () {
5179 this.onEditorBlockEvent.apply(this,arguments);
5181 this.subscribe("editorUnblockEvent",function () {
5182 this.onEditorUnblockEvent.apply(this,arguments);
5218 // DOM MUTATION FUNCTIONS
5221 * Retruns classnames to represent current Column states.
5222 * @method _getColumnClassnames
5223 * @param oColumn {YAHOO.widget.Column} Column instance.
5224 * @param aAddClasses {String[]} An array of additional classnames to add to the
5226 * @return {String} A String of classnames to be assigned to TH or TD elements
5230 _getColumnClassNames : function (oColumn, aAddClasses) {
5234 if(lang.isString(oColumn.className)) {
5235 // Single custom class
5236 allClasses = [oColumn.className];
5238 else if(lang.isArray(oColumn.className)) {
5239 // Array of custom classes
5240 allClasses = oColumn.className;
5243 // no custom classes
5247 // Hook for setting width with via dynamic style uses key since ID is too disposable
5248 allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
5250 // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
5251 allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
5253 var isSortedBy = this.get("sortedBy") || {};
5255 if(oColumn.key === isSortedBy.key) {
5256 allClasses[allClasses.length] = isSortedBy.dir || '';
5259 if(oColumn.hidden) {
5260 allClasses[allClasses.length] = DT.CLASS_HIDDEN;
5263 if(oColumn.selected) {
5264 allClasses[allClasses.length] = DT.CLASS_SELECTED;
5267 if(oColumn.sortable) {
5268 allClasses[allClasses.length] = DT.CLASS_SORTABLE;
5271 if(oColumn.resizeable) {
5272 allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
5275 if(oColumn.editor) {
5276 allClasses[allClasses.length] = DT.CLASS_EDITABLE;
5279 // Addtnl classes, including First/Last
5281 allClasses = allClasses.concat(aAddClasses);
5284 return allClasses.join(' ');
5288 * Clears TR element template in response to any Column state change.
5289 * @method _clearTrTemplateEl
5292 _clearTrTemplateEl : function () {
5293 this._elTrTemplate = null;
5297 * Returns a new TR element template with TD elements classed with current
5299 * @method _getTrTemplateEl
5300 * @return {HTMLElement} A TR element to be cloned and added to the DOM.
5303 _getTrTemplateEl : function (oRecord, index) {
5304 // Template is already available
5305 if(this._elTrTemplate) {
5306 return this._elTrTemplate;
5308 // Template needs to be created
5311 tr = d.createElement('tr'),
5312 td = d.createElement('td'),
5313 div = d.createElement('div');
5315 // Append the liner element
5316 td.appendChild(div);
5318 // Create TD elements into DOCUMENT FRAGMENT
5319 var df = document.createDocumentFragment(),
5320 allKeys = this._oColumnSet.keys,
5323 // Set state for each TD;
5325 for(var i=0, keysLen=allKeys.length; i<keysLen; i++) {
5326 // Clone the TD template
5327 elTd = td.cloneNode(true);
5329 // Format the base TD
5330 elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
5332 df.appendChild(elTd);
5335 this._elTrTemplate = tr;
5341 * Formats a basic TD element.
5342 * @method _formatTdEl
5343 * @param oColumn {YAHOO.widget.Column} Associated Column instance.
5344 * @param elTd {HTMLElement} An unformatted TD element.
5345 * @param index {Number} Column key index.
5346 * @param isLast {Boolean} True if Column is last key of the ColumnSet.
5347 * @return {HTMLElement} A formatted TD element.
5350 _formatTdEl : function (oColumn, elTd, index, isLast) {
5351 var oColumnSet = this._oColumnSet;
5353 // Set the TD's accessibility headers
5354 var allHeaders = oColumnSet.headers,
5355 allColHeaders = allHeaders[index],
5358 for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
5359 sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
5360 sTdHeaders += sHeader;
5362 elTd.headers = sTdHeaders;
5364 // Class the TD element
5365 var aAddClasses = [];
5367 aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
5370 aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
5372 elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
5374 // Class the liner element
5375 elTd.firstChild.className = DT.CLASS_LINER;
5377 // Set Column width for fallback cases
5378 if(oColumn.width && DT._bDynStylesFallback) {
5379 // Validate minWidth
5380 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
5381 oColumn.minWidth : oColumn.width;
5382 elTd.firstChild.style.overflow = 'hidden';
5383 elTd.firstChild.style.width = nWidth + 'px';
5391 * Create a new TR element for a given Record and appends it with the correct
5392 * number of Column-state-classed TD elements. Striping is the responsibility of
5393 * the calling function, which may decide to stripe the single row, a subset of
5394 * rows, or all the rows.
5395 * @method _createTrEl
5396 * @param oRecord {YAHOO.widget.Record} Record instance
5397 * @return {HTMLElement} The new TR element. This must be added to the DOM.
5400 _addTrEl : function (oRecord) {
5401 var elTrTemplate = this._getTrTemplateEl();
5403 // Clone the TR template.
5404 var elTr = elTrTemplate.cloneNode(true);
5407 return this._updateTrEl(elTr,oRecord);
5411 * Formats the contents of the given TR's TD elements with data from the given
5412 * Record. Only innerHTML should change, nothing structural.
5414 * @method _updateTrEl
5415 * @param elTr {HTMLElement} The TR element to update.
5416 * @param oRecord {YAHOO.widget.Record} The associated Record instance.
5417 * @return {HTMLElement} DOM reference to the new TR element.
5420 _updateTrEl : function(elTr, oRecord) {
5421 var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
5423 // Hide the row to prevent constant reflows
5424 elTr.style.display = 'none';
5426 // Update TD elements with new data
5427 var allTds = elTr.childNodes,
5429 for(var i=0,len=allTds.length; i<len; ++i) {
5432 // Set the cell content
5433 this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
5436 // Redisplay the row for reflow
5437 elTr.style.display = '';
5440 elTr.id = oRecord.getId(); // Needed for Record association and tracking of FIRST/LAST
5446 * Deletes TR element by DOM reference or by DataTable page row index.
5448 * @method _deleteTrEl
5449 * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
5450 * @return {Boolean} Returns true if successful, else returns false.
5453 _deleteTrEl : function(row) {
5456 // Get page row index for the element
5457 if(!lang.isNumber(row)) {
5458 rowIndex = Dom.get(row).sectionRowIndex;
5463 if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
5464 // Cannot use tbody.deleteRow due to IE6 instability
5465 //return this._elTbody.deleteRow(rowIndex);
5466 return this._elTbody.removeChild(this.getTrEl(row));
5499 // CSS/STATE FUNCTIONS
5505 * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
5506 * of the DataTable page and updates internal tracker.
5508 * @method _unsetFirstRow
5511 _unsetFirstRow : function() {
5513 if(this._sFirstTrId) {
5514 Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
5515 this._sFirstTrId = null;
5520 * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
5521 * of the DataTable page and updates internal tracker.
5523 * @method _setFirstRow
5526 _setFirstRow : function() {
5527 this._unsetFirstRow();
5528 var elTr = this.getFirstTrEl();
5531 Dom.addClass(elTr, DT.CLASS_FIRST);
5532 this._sFirstTrId = elTr.id;
5537 * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
5538 * of the DataTable page and updates internal tracker.
5540 * @method _unsetLastRow
5543 _unsetLastRow : function() {
5544 // Unassign previous class
5545 if(this._sLastTrId) {
5546 Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
5547 this._sLastTrId = null;
5552 * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
5553 * of the DataTable page and updates internal tracker.
5555 * @method _setLastRow
5558 _setLastRow : function() {
5559 this._unsetLastRow();
5560 var elTr = this.getLastTrEl();
5563 Dom.addClass(elTr, DT.CLASS_LAST);
5564 this._sLastTrId = elTr.id;
5569 * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
5571 * @method _setRowStripes
5572 * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
5573 * or string ID, or page row index of where to start striping.
5574 * @param range {Number} (optional) If given, how many rows to stripe, otherwise
5575 * stripe all the rows until the end.
5578 _setRowStripes : function(row, range) {
5579 // Default values stripe all rows
5580 var allRows = this._elTbody.rows,
5582 nEndIndex = allRows.length,
5583 aOdds = [], nOddIdx = 0,
5584 aEvens = [], nEvenIdx = 0;
5587 if((row !== null) && (row !== undefined)) {
5588 // Validate given start row
5589 var elStartRow = this.getTrEl(row);
5591 nStartIndex = elStartRow.sectionRowIndex;
5593 // Validate given range
5594 if(lang.isNumber(range) && (range > 1)) {
5595 nEndIndex = nStartIndex + range;
5600 for(var i=nStartIndex; i<nEndIndex; i++) {
5602 aOdds[nOddIdx++] = allRows[i];
5604 aEvens[nEvenIdx++] = allRows[i];
5609 Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
5612 if (aEvens.length) {
5613 Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
5618 * Assigns the class DT.CLASS_SELECTED to TR and TD elements.
5620 * @method _setSelections
5623 _setSelections : function() {
5624 // Keep track of selected rows
5625 var allSelectedRows = this.getSelectedRows();
5626 // Keep track of selected cells
5627 var allSelectedCells = this.getSelectedCells();
5628 // Anything to select?
5629 if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
5630 var oColumnSet = this._oColumnSet,
5632 // Loop over each row
5633 for(var i=0; i<allSelectedRows.length; i++) {
5634 el = Dom.get(allSelectedRows[i]);
5636 Dom.addClass(el, DT.CLASS_SELECTED);
5639 // Loop over each cell
5640 for(i=0; i<allSelectedCells.length; i++) {
5641 el = Dom.get(allSelectedCells[i].recordId);
5643 Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
5691 /////////////////////////////////////////////////////////////////////////////
5693 // Private DOM Event Handlers
5695 /////////////////////////////////////////////////////////////////////////////
5698 * Validates minWidths whenever the render chain ends.
5700 * @method _onRenderChainEnd
5703 _onRenderChainEnd : function() {
5704 // Hide loading message
5705 this.hideTableMessage();
5707 // Show empty message
5708 if(this._elTbody.rows.length === 0) {
5709 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
5712 // Execute in timeout thread to give implementers a chance
5713 // to subscribe after the constructor
5715 setTimeout(function() {
5716 if((oSelf instanceof DT) && oSelf._sId) {
5719 oSelf._bInit = false;
5720 oSelf.fireEvent("initEvent");
5724 oSelf.fireEvent("renderEvent");
5725 // Backward compatibility
5726 oSelf.fireEvent("refreshEvent");
5728 // Post-render routine
5729 oSelf.validateColumnWidths();
5731 // Post-render event
5732 oSelf.fireEvent("postRenderEvent");
5734 /*if(YAHOO.example.Performance.trialStart) {
5735 YAHOO.example.Performance.trialStart = null;
5743 * Handles click events on the DOCUMENT.
5745 * @method _onDocumentClick
5746 * @param e {HTMLEvent} The click event.
5747 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5750 _onDocumentClick : function(e, oSelf) {
5751 var elTarget = Ev.getTarget(e);
5752 var elTag = elTarget.nodeName.toLowerCase();
5754 if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
5755 oSelf.fireEvent("tableBlurEvent");
5757 // Fires editorBlurEvent when click is not within the TABLE.
5758 // For cases when click is within the TABLE, due to timing issues,
5759 // the editorBlurEvent needs to get fired by the lower-level DOM click
5760 // handlers below rather than by the TABLE click handler directly.
5761 if(oSelf._oCellEditor) {
5762 if(oSelf._oCellEditor.getContainerEl) {
5763 var elContainer = oSelf._oCellEditor.getContainerEl();
5764 // Only if the click was not within the CellEditor container
5765 if(!Dom.isAncestor(elContainer, elTarget) &&
5766 (elContainer.id !== elTarget.id)) {
5767 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
5770 // Backward Compatibility
5771 else if(oSelf._oCellEditor.isActive) {
5772 // Only if the click was not within the Cell Editor container
5773 if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
5774 (oSelf._oCellEditor.container.id !== elTarget.id)) {
5775 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
5783 * Handles focus events on the DataTable instance.
5785 * @method _onTableFocus
5786 * @param e {HTMLEvent} The focus event.
5787 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5790 _onTableFocus : function(e, oSelf) {
5791 oSelf.fireEvent("tableFocusEvent");
5795 * Handles focus events on the THEAD element.
5797 * @method _onTheadFocus
5798 * @param e {HTMLEvent} The focus event.
5799 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5802 _onTheadFocus : function(e, oSelf) {
5803 oSelf.fireEvent("theadFocusEvent");
5804 oSelf.fireEvent("tableFocusEvent");
5808 * Handles focus events on the TBODY element.
5810 * @method _onTbodyFocus
5811 * @param e {HTMLEvent} The focus event.
5812 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5815 _onTbodyFocus : function(e, oSelf) {
5816 oSelf.fireEvent("tbodyFocusEvent");
5817 oSelf.fireEvent("tableFocusEvent");
5821 * Handles mouseover events on the DataTable instance.
5823 * @method _onTableMouseover
5824 * @param e {HTMLEvent} The mouseover event.
5825 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5828 _onTableMouseover : function(e, oSelf) {
5829 var elTarget = Ev.getTarget(e);
5830 var elTag = elTarget.nodeName.toLowerCase();
5831 var bKeepBubbling = true;
5832 while(elTarget && (elTag != "table")) {
5839 bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
5842 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5843 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
5844 // Backward compatibility
5845 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
5849 bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
5850 // Backward compatibility
5851 bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
5854 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5855 bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
5856 // Backward compatibility
5857 bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
5860 bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
5866 if(bKeepBubbling === false) {
5870 elTarget = elTarget.parentNode;
5872 elTag = elTarget.nodeName.toLowerCase();
5876 oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
5880 * Handles mouseout events on the DataTable instance.
5882 * @method _onTableMouseout
5883 * @param e {HTMLEvent} The mouseout event.
5884 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5887 _onTableMouseout : function(e, oSelf) {
5888 var elTarget = Ev.getTarget(e);
5889 var elTag = elTarget.nodeName.toLowerCase();
5890 var bKeepBubbling = true;
5891 while(elTarget && (elTag != "table")) {
5898 bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
5901 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5902 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
5903 // Backward compatibility
5904 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
5908 bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
5909 // Backward compatibility
5910 bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
5913 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5914 bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
5915 // Backward compatibility
5916 bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
5919 bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
5925 if(bKeepBubbling === false) {
5929 elTarget = elTarget.parentNode;
5931 elTag = elTarget.nodeName.toLowerCase();
5935 oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
5939 * Handles mousedown events on the DataTable instance.
5941 * @method _onTableMousedown
5942 * @param e {HTMLEvent} The mousedown event.
5943 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5946 _onTableMousedown : function(e, oSelf) {
5947 var elTarget = Ev.getTarget(e);
5948 var elTag = elTarget.nodeName.toLowerCase();
5949 var bKeepBubbling = true;
5950 while(elTarget && (elTag != "table")) {
5957 bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
5960 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5961 bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
5962 // Backward compatibility
5963 bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
5967 bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
5968 // Backward compatibility
5969 bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
5972 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5973 bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
5974 // Backward compatibility
5975 bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
5978 bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
5984 if(bKeepBubbling === false) {
5988 elTarget = elTarget.parentNode;
5990 elTag = elTarget.nodeName.toLowerCase();
5994 oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
5998 * Handles mouseup events on the DataTable instance.
6000 * @method _onTableMouseup
6001 * @param e {HTMLEvent} The mouseup event.
6002 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6005 _onTableMouseup : function(e, oSelf) {
6006 var elTarget = Ev.getTarget(e);
6007 var elTag = elTarget.nodeName.toLowerCase();
6008 var bKeepBubbling = true;
6009 while(elTarget && (elTag != "table")) {
6016 bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
6019 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6020 bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
6021 // Backward compatibility
6022 bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
6026 bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
6027 // Backward compatibility
6028 bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
6031 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6032 bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
6033 // Backward compatibility
6034 bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
6037 bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
6043 if(bKeepBubbling === false) {
6047 elTarget = elTarget.parentNode;
6049 elTag = elTarget.nodeName.toLowerCase();
6053 oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
6057 * Handles dblclick events on the DataTable instance.
6059 * @method _onTableDblclick
6060 * @param e {HTMLEvent} The dblclick event.
6061 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6064 _onTableDblclick : function(e, oSelf) {
6065 var elTarget = Ev.getTarget(e);
6066 var elTag = elTarget.nodeName.toLowerCase();
6067 var bKeepBubbling = true;
6068 while(elTarget && (elTag != "table")) {
6073 bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
6076 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6077 bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
6078 // Backward compatibility
6079 bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
6083 bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
6084 // Backward compatibility
6085 bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
6088 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6089 bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
6090 // Backward compatibility
6091 bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
6094 bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
6100 if(bKeepBubbling === false) {
6104 elTarget = elTarget.parentNode;
6106 elTag = elTarget.nodeName.toLowerCase();
6110 oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6113 * Handles keydown events on the THEAD element.
6115 * @method _onTheadKeydown
6116 * @param e {HTMLEvent} The key event.
6117 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6120 _onTheadKeydown : function(e, oSelf) {
6121 var elTarget = Ev.getTarget(e);
6122 var elTag = elTarget.nodeName.toLowerCase();
6123 var bKeepBubbling = true;
6124 while(elTarget && (elTag != "table")) {
6130 // TODO: implement textareaKeyEvent
6133 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
6138 if(bKeepBubbling === false) {
6142 elTarget = elTarget.parentNode;
6144 elTag = elTarget.nodeName.toLowerCase();
6148 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6152 * Handles keydown events on the TBODY element. Handles selection behavior,
6153 * provides hooks for ENTER to edit functionality.
6155 * @method _onTbodyKeydown
6156 * @param e {HTMLEvent} The key event.
6157 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6160 _onTbodyKeydown : function(e, oSelf) {
6161 var sMode = oSelf.get("selectionMode");
6163 if(sMode == "standard") {
6164 oSelf._handleStandardSelectionByKey(e);
6166 else if(sMode == "single") {
6167 oSelf._handleSingleSelectionByKey(e);
6169 else if(sMode == "cellblock") {
6170 oSelf._handleCellBlockSelectionByKey(e);
6172 else if(sMode == "cellrange") {
6173 oSelf._handleCellRangeSelectionByKey(e);
6175 else if(sMode == "singlecell") {
6176 oSelf._handleSingleCellSelectionByKey(e);
6179 if(oSelf._oCellEditor) {
6180 if(oSelf._oCellEditor.fireEvent) {
6181 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6183 else if(oSelf._oCellEditor.isActive) {
6184 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6188 var elTarget = Ev.getTarget(e);
6189 var elTag = elTarget.nodeName.toLowerCase();
6190 var bKeepBubbling = true;
6191 while(elTarget && (elTag != "table")) {
6196 bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
6201 if(bKeepBubbling === false) {
6205 elTarget = elTarget.parentNode;
6207 elTag = elTarget.nodeName.toLowerCase();
6211 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6215 * Handles keypress events on the TABLE. Mainly to support stopEvent on Mac.
6217 * @method _onTableKeypress
6218 * @param e {HTMLEvent} The key event.
6219 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6222 _onTableKeypress : function(e, oSelf) {
6223 if(ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") !== -1) && (ua.webkit < 420)) {
6224 var nKey = Ev.getCharCode(e);
6230 else if(nKey == 38) {
6237 * Handles click events on the THEAD element.
6239 * @method _onTheadClick
6240 * @param e {HTMLEvent} The click event.
6241 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6244 _onTheadClick : function(e, oSelf) {
6245 // This blurs the CellEditor
6246 if(oSelf._oCellEditor) {
6247 if(oSelf._oCellEditor.fireEvent) {
6248 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6250 // Backward compatibility
6251 else if(oSelf._oCellEditor.isActive) {
6252 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6256 var elTarget = Ev.getTarget(e),
6257 elTag = elTarget.nodeName.toLowerCase(),
6258 bKeepBubbling = true;
6259 while(elTarget && (elTag != "table")) {
6264 var sType = elTarget.type.toLowerCase();
6265 if(sType == "checkbox") {
6266 bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
6268 else if(sType == "radio") {
6269 bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
6271 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6272 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6276 bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
6279 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6282 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6283 bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
6284 // Backward compatibility
6285 bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
6289 bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
6290 // Backward compatibility
6291 bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
6294 bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
6295 // Backward compatibility
6296 bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
6301 if(bKeepBubbling === false) {
6305 elTarget = elTarget.parentNode;
6307 elTag = elTarget.nodeName.toLowerCase();
6311 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6315 * Handles click events on the primary TBODY element.
6317 * @method _onTbodyClick
6318 * @param e {HTMLEvent} The click event.
6319 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6322 _onTbodyClick : function(e, oSelf) {
6323 // This blurs the CellEditor
6324 if(oSelf._oCellEditor) {
6325 if(oSelf._oCellEditor.fireEvent) {
6326 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6328 else if(oSelf._oCellEditor.isActive) {
6329 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6333 // Fire Custom Events
6334 var elTarget = Ev.getTarget(e),
6335 elTag = elTarget.nodeName.toLowerCase(),
6336 bKeepBubbling = true;
6337 while(elTarget && (elTag != "table")) {
6342 var sType = elTarget.type.toLowerCase();
6343 if(sType == "checkbox") {
6344 bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
6346 else if(sType == "radio") {
6347 bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
6349 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6350 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6354 bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
6357 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6360 bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
6363 bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
6368 if(bKeepBubbling === false) {
6372 elTarget = elTarget.parentNode;
6374 elTag = elTarget.nodeName.toLowerCase();
6378 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6382 * Handles change events on SELECT elements within DataTable.
6384 * @method _onDropdownChange
6385 * @param e {HTMLEvent} The change event.
6386 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6389 _onDropdownChange : function(e, oSelf) {
6390 var elTarget = Ev.getTarget(e);
6391 oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
6425 /////////////////////////////////////////////////////////////////////////////
6427 // Public member variables
6429 /////////////////////////////////////////////////////////////////////////////
6431 * Returns object literal of initial configs.
6440 /////////////////////////////////////////////////////////////////////////////
6444 /////////////////////////////////////////////////////////////////////////////
6447 * Returns unique id assigned to instance, which is a useful prefix for
6448 * generating unique DOM ID strings.
6451 * @return {String} Unique ID of the DataSource instance.
6453 getId : function() {
6458 * DataSource instance name, for logging.
6461 * @return {String} Unique name of the DataSource instance.
6464 toString : function() {
6465 return "DataTable instance " + this._sId;
6469 * Returns the DataTable instance's DataSource instance.
6471 * @method getDataSource
6472 * @return {YAHOO.util.DataSource} DataSource instance.
6474 getDataSource : function() {
6475 return this._oDataSource;
6479 * Returns the DataTable instance's ColumnSet instance.
6481 * @method getColumnSet
6482 * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
6484 getColumnSet : function() {
6485 return this._oColumnSet;
6489 * Returns the DataTable instance's RecordSet instance.
6491 * @method getRecordSet
6492 * @return {YAHOO.widget.RecordSet} RecordSet instance.
6494 getRecordSet : function() {
6495 return this._oRecordSet;
6499 * Returns on object literal representing the DataTable instance's current
6500 * state with the following properties:
6502 * <dt>pagination</dt>
6503 * <dd>Instance of YAHOO.widget.Paginator</dd>
6508 * <dt>sortedBy.key</dt>
6509 * <dd>{String} Key of sorted Column</dd>
6510 * <dt>sortedBy.dir</dt>
6511 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
6515 * <dt>selectedRows</dt>
6516 * <dd>Array of selected rows by Record ID.</dd>
6518 * <dt>selectedCells</dt>
6519 * <dd>Selected cells as an array of object literals:
6520 * {recordId:sRecordId, columnKey:sColumnKey}</dd>
6524 * @return {Object} DataTable instance state object literal values.
6526 getState : function() {
6528 totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
6529 pagination: this.get("paginator") ? this.get("paginator").getState() : null,
6530 sortedBy: this.get("sortedBy"),
6531 selectedRows: this.getSelectedRows(),
6532 selectedCells: this.getSelectedCells()
6581 * Returns DOM reference to the DataTable's container element.
6583 * @method getContainerEl
6584 * @return {HTMLElement} Reference to DIV element.
6586 getContainerEl : function() {
6587 return this._elContainer;
6591 * Returns DOM reference to the DataTable's TABLE element.
6593 * @method getTableEl
6594 * @return {HTMLElement} Reference to TABLE element.
6596 getTableEl : function() {
6597 return this._elTable;
6601 * Returns DOM reference to the DataTable's THEAD element.
6603 * @method getTheadEl
6604 * @return {HTMLElement} Reference to THEAD element.
6606 getTheadEl : function() {
6607 return this._elThead;
6611 * Returns DOM reference to the DataTable's primary TBODY element.
6613 * @method getTbodyEl
6614 * @return {HTMLElement} Reference to TBODY element.
6616 getTbodyEl : function() {
6617 return this._elTbody;
6621 * Returns DOM reference to the DataTable's secondary TBODY element that is
6622 * used to display messages.
6624 * @method getMsgTbodyEl
6625 * @return {HTMLElement} Reference to TBODY element.
6627 getMsgTbodyEl : function() {
6628 return this._elMsgTbody;
6632 * Returns DOM reference to the TD element within the secondary TBODY that is
6633 * used to display messages.
6635 * @method getMsgTdEl
6636 * @return {HTMLElement} Reference to TD element.
6638 getMsgTdEl : function() {
6639 return this._elMsgTd;
6643 * Returns the corresponding TR reference for a given DOM element, ID string or
6644 * directly page row index. If the given identifier is a child of a TR element,
6645 * then DOM tree is traversed until a parent TR element is returned, otherwise
6649 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
6650 * get: by element reference, ID string, page row index, or Record.
6651 * @return {HTMLElement} Reference to TR element, or null.
6653 getTrEl : function(row) {
6655 if(row instanceof YAHOO.widget.Record) {
6656 return document.getElementById(row.getId());
6658 // By page row index
6659 else if(lang.isNumber(row)) {
6660 var allRows = this._elTbody.rows;
6661 return ((row > -1) && (row < allRows.length)) ? allRows[row] : null;
6663 // By ID string or element reference
6665 var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
6667 // Validate HTML element
6668 if(elRow && (elRow.ownerDocument == document)) {
6669 // Validate TR element
6670 if(elRow.nodeName.toLowerCase() != "tr") {
6671 // Traverse up the DOM to find the corresponding TR element
6672 elRow = Dom.getAncestorByTagName(elRow,"tr");
6683 * Returns DOM reference to the first TR element in the DataTable page, or null.
6685 * @method getFirstTrEl
6686 * @return {HTMLElement} Reference to TR element.
6688 getFirstTrEl : function() {
6689 return this._elTbody.rows[0] || null;
6693 * Returns DOM reference to the last TR element in the DataTable page, or null.
6695 * @method getLastTrEl
6696 * @return {HTMLElement} Reference to last TR element.
6698 getLastTrEl : function() {
6699 var allRows = this._elTbody.rows;
6700 if(allRows.length > 0) {
6701 return allRows[allRows.length-1] || null;
6706 * Returns DOM reference to the next TR element from the given TR element, or null.
6708 * @method getNextTrEl
6709 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6710 * reference, ID string, page row index, or Record from which to get next TR element.
6711 * @return {HTMLElement} Reference to next TR element.
6713 getNextTrEl : function(row) {
6714 var nThisTrIndex = this.getTrIndex(row);
6715 if(nThisTrIndex !== null) {
6716 var allRows = this._elTbody.rows;
6717 if(nThisTrIndex < allRows.length-1) {
6718 return allRows[nThisTrIndex+1];
6726 * Returns DOM reference to the previous TR element from the given TR element, or null.
6728 * @method getPreviousTrEl
6729 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6730 * reference, ID string, page row index, or Record from which to get previous TR element.
6731 * @return {HTMLElement} Reference to previous TR element.
6733 getPreviousTrEl : function(row) {
6734 var nThisTrIndex = this.getTrIndex(row);
6735 if(nThisTrIndex !== null) {
6736 var allRows = this._elTbody.rows;
6737 if(nThisTrIndex > 0) {
6738 return allRows[nThisTrIndex-1];
6746 * Returns DOM reference to a TD liner element.
6748 * @method getTdLinerEl
6749 * @param cell {HTMLElement | Object} TD element or child of a TD element, or
6750 * object literal of syntax {record:oRecord, column:oColumn}.
6751 * @return {HTMLElement} Reference to TD liner element.
6753 getTdLinerEl : function(cell) {
6754 var elCell = this.getTdEl(cell);
6755 return elCell.firstChild || null;
6759 * Returns DOM reference to a TD element.
6762 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
6763 * object literal of syntax {record:oRecord, column:oColumn}.
6764 * @return {HTMLElement} Reference to TD element.
6766 getTdEl : function(cell) {
6768 var el = Dom.get(cell);
6770 // Validate HTML element
6771 if(el && (el.ownerDocument == document)) {
6772 // Validate TD element
6773 if(el.nodeName.toLowerCase() != "td") {
6774 // Traverse up the DOM to find the corresponding TR element
6775 elCell = Dom.getAncestorByTagName(el, "td");
6784 var oRecord, nColKeyIndex;
6786 if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
6787 oRecord = this.getRecord(cell.recordId);
6788 var oColumn = this.getColumn(cell.columnKey);
6790 nColKeyIndex = oColumn.getKeyIndex();
6794 if(cell.record && cell.column && cell.column.getKeyIndex) {
6795 oRecord = cell.record;
6796 nColKeyIndex = cell.column.getKeyIndex();
6798 var elRow = this.getTrEl(oRecord);
6799 if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
6800 return elRow.cells[nColKeyIndex] || null;
6808 * Returns DOM reference to the first TD element in the DataTable page (by default),
6809 * the first TD element of the optionally given row, or null.
6811 * @method getFirstTdEl
6812 * @param row {HTMLElement} (optional) row from which to get first TD
6813 * @return {HTMLElement} Reference to TD element.
6815 getFirstTdEl : function(row) {
6816 var elRow = this.getTrEl(row) || this.getFirstTrEl();
6817 if(elRow && (elRow.cells.length > 0)) {
6818 return elRow.cells[0];
6824 * Returns DOM reference to the last TD element in the DataTable page (by default),
6825 * the first TD element of the optionally given row, or null.
6827 * @method getLastTdEl
6828 * @return {HTMLElement} Reference to last TD element.
6830 getLastTdEl : function(row) {
6831 var elRow = this.getTrEl(row) || this.getLastTrEl();
6832 if(elRow && (elRow.cells.length > 0)) {
6833 return elRow.cells[elRow.cells.length-1];
6839 * Returns DOM reference to the next TD element from the given cell, or null.
6841 * @method getNextTdEl
6842 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6843 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6844 * @return {HTMLElement} Reference to next TD element, or null.
6846 getNextTdEl : function(cell) {
6847 var elCell = this.getTdEl(cell);
6849 var nThisTdIndex = elCell.cellIndex;
6850 var elRow = this.getTrEl(elCell);
6851 if(nThisTdIndex < elRow.cells.length-1) {
6852 return elRow.cells[nThisTdIndex+1];
6855 var elNextRow = this.getNextTrEl(elRow);
6857 return elNextRow.cells[0];
6865 * Returns DOM reference to the previous TD element from the given cell, or null.
6867 * @method getPreviousTdEl
6868 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6869 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
6870 * @return {HTMLElement} Reference to previous TD element, or null.
6872 getPreviousTdEl : function(cell) {
6873 var elCell = this.getTdEl(cell);
6875 var nThisTdIndex = elCell.cellIndex;
6876 var elRow = this.getTrEl(elCell);
6877 if(nThisTdIndex > 0) {
6878 return elRow.cells[nThisTdIndex-1];
6881 var elPreviousRow = this.getPreviousTrEl(elRow);
6883 return this.getLastTdEl(elPreviousRow);
6891 * Returns DOM reference to the above TD element from the given cell, or null.
6893 * @method getAboveTdEl
6894 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6895 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6896 * @return {HTMLElement} Reference to next TD element, or null.
6898 getAboveTdEl : function(cell) {
6899 var elCell = this.getTdEl(cell);
6901 var elPreviousRow = this.getPreviousTrEl(elCell);
6903 return elPreviousRow.cells[elCell.cellIndex];
6910 * Returns DOM reference to the below TD element from the given cell, or null.
6912 * @method getBelowTdEl
6913 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6914 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
6915 * @return {HTMLElement} Reference to previous TD element, or null.
6917 getBelowTdEl : function(cell) {
6918 var elCell = this.getTdEl(cell);
6920 var elNextRow = this.getNextTrEl(elCell);
6922 return elNextRow.cells[elCell.cellIndex];
6929 * Returns DOM reference to a TH liner element. Needed to normalize for resizeable
6930 * Columns, which have an additional resizer liner DIV element between the TH
6931 * element and the liner DIV element.
6933 * @method getThLinerEl
6934 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
6935 * DOM element reference, or string ID.
6936 * @return {HTMLElement} Reference to TH liner element.
6938 getThLinerEl : function(theadCell) {
6939 var oColumn = this.getColumn(theadCell);
6940 return (oColumn) ? oColumn.getThLinerEl() : null;
6944 * Returns DOM reference to a TH element.
6947 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
6948 * DOM element reference, or string ID.
6949 * @return {HTMLElement} Reference to TH element.
6951 getThEl : function(theadCell) {
6954 // Validate Column instance
6955 if(theadCell instanceof YAHOO.widget.Column) {
6956 var oColumn = theadCell;
6957 elTh = oColumn.getThEl();
6962 // Validate HTML element
6964 var el = Dom.get(theadCell);
6966 if(el && (el.ownerDocument == document)) {
6967 // Validate TH element
6968 if(el.nodeName.toLowerCase() != "th") {
6969 // Traverse up the DOM to find the corresponding TR element
6970 elTh = Dom.getAncestorByTagName(el,"th");
6984 * Returns the page row index of given row. Returns null if the row is not on the
6985 * current DataTable page.
6987 * @method getTrIndex
6988 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
6989 * string reference to an element within the DataTable page, a Record instance,
6990 * or a Record's RecordSet index.
6991 * @return {Number} Page row index, or null if row does not exist or is not on current page.
6993 getTrIndex : function(row) {
6997 if(row instanceof YAHOO.widget.Record) {
6998 nRecordIndex = this._oRecordSet.getRecordIndex(row);
6999 if(nRecordIndex === null) {
7000 // Not a valid Record
7004 // Calculate page row index from Record index
7005 else if(lang.isNumber(row)) {
7008 if(lang.isNumber(nRecordIndex)) {
7009 // Validate the number
7010 if((nRecordIndex > -1) && (nRecordIndex < this._oRecordSet.getLength())) {
7011 // DataTable is paginated
7012 var oPaginator = this.get('paginator');
7014 // Check the record index is within the indices of the
7016 var rng = oPaginator.getPageRecords();
7017 if (rng && nRecordIndex >= rng[0] && nRecordIndex <= rng[1]) {
7018 // This Record is on current page
7019 return nRecordIndex - rng[0];
7021 // This Record is not on current page
7026 // Not paginated, just return the Record index
7028 return nRecordIndex;
7031 // RecordSet index is out of range
7036 // By element reference or ID string
7038 // Validate TR element
7039 var elRow = this.getTrEl(row);
7040 if(elRow && (elRow.ownerDocument == document) &&
7041 (elRow.parentNode == this._elTbody)) {
7042 return elRow.sectionRowIndex;
7097 * Resets a RecordSet with the given data and populates the page view
7098 * with the new data. Any previous data, and selection and sort states are
7099 * cleared. New data should be added as a separate step.
7101 * @method initializeTable
7103 initializeTable : function() {
7107 // Clear the RecordSet
7108 this._oRecordSet.reset();
7110 // Clear the Paginator's totalRecords if paginating
7111 var pag = this.get('paginator');
7113 pag.set('totalRecords',0);
7117 this._unselectAllTrEls();
7118 this._unselectAllTdEls();
7119 this._aSelections = null;
7120 this._oAnchorRecord = null;
7121 this._oAnchorCell = null;
7124 this.set("sortedBy", null);
7128 * Internal wrapper calls run() on render Chain instance.
7130 * @method _runRenderChain
7133 _runRenderChain : function() {
7134 this._oChainRender.run();
7138 * Renders the view with existing Records from the RecordSet while
7139 * maintaining sort, pagination, and selection states. For performance, reuses
7140 * existing DOM elements when possible while deleting extraneous elements.
7144 render : function() {
7145 //YAHOO.example.Performance.trialStart = new Date();
7147 this._oChainRender.stop();
7149 var i, j, k, len, allRecords;
7151 var oPaginator = this.get('paginator');
7152 // Paginator is enabled, show a subset of Records and update Paginator UI
7154 allRecords = this._oRecordSet.getRecords(
7155 oPaginator.getStartIndex(),
7156 oPaginator.getRowsPerPage());
7158 // Not paginated, show all records
7160 allRecords = this._oRecordSet.getRecords();
7163 // From the top, update in-place existing rows, so as to reuse DOM elements
7164 var elTbody = this._elTbody,
7165 loopN = this.get("renderLoopSize"),
7166 nRecordsLength = allRecords.length;
7169 if(nRecordsLength > 0) {
7170 elTbody.style.display = "none";
7171 while(elTbody.lastChild) {
7172 elTbody.removeChild(elTbody.lastChild);
7174 elTbody.style.display = "";
7176 // Set up the loop Chain to render rows
7177 this._oChainRender.add({
7178 method: function(oArg) {
7179 if((this instanceof DT) && this._sId) {
7180 var i = oArg.nCurrentRecord,
7181 endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
7182 nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
7185 elTbody.style.display = "none";
7187 for(; i<endRecordIndex; i++) {
7188 elRow = Dom.get(allRecords[i].getId());
7189 elRow = elRow || this._addTrEl(allRecords[i]);
7190 nextSibling = elTbody.childNodes[i] || null;
7191 elTbody.insertBefore(elRow, nextSibling);
7193 elTbody.style.display = "";
7195 // Set up for the next loop
7196 oArg.nCurrentRecord = i;
7200 iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
7202 nCurrentRecord: 0,//nRecordsLength-1, // Start at first Record
7203 nLoopLength: (loopN > 0) ? loopN : nRecordsLength
7205 timeout: (loopN > 0) ? 0 : -1
7208 // Post-render tasks
7209 this._oChainRender.add({
7210 method: function(oArg) {
7211 if((this instanceof DT) && this._sId) {
7212 while(elTbody.rows.length > nRecordsLength) {
7213 elTbody.removeChild(elTbody.lastChild);
7215 this._setFirstRow();
7217 this._setRowStripes();
7218 this._setSelections();
7222 timeout: (loopN > 0) ? 0 : -1
7226 // Table has no rows
7228 // Set up the loop Chain to delete rows
7229 var nTotal = elTbody.rows.length;
7231 this._oChainRender.add({
7232 method: function(oArg) {
7233 if((this instanceof DT) && this._sId) {
7234 var i = oArg.nCurrent,
7235 loopN = oArg.nLoopLength,
7236 nIterEnd = (i - loopN < 0) ? -1 : i - loopN;
7238 elTbody.style.display = "none";
7240 for(; i>nIterEnd; i--) {
7241 elTbody.deleteRow(-1);
7243 elTbody.style.display = "";
7245 // Set up for the next loop
7250 iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
7253 nLoopLength: (loopN > 0) ? loopN : nTotal
7255 timeout: (loopN > 0) ? 0 : -1
7259 this._runRenderChain();
7263 * Disables DataTable UI.
7267 disable : function() {
7268 var elTable = this._elTable;
7269 var elMask = this._elMask;
7270 elMask.style.width = elTable.offsetWidth + "px";
7271 elMask.style.height = elTable.offsetHeight + "px";
7272 elMask.style.display = "";
7273 this.fireEvent("disableEvent");
7277 * Undisables DataTable UI.
7281 undisable : function() {
7282 this._elMask.style.display = "none";
7283 this.fireEvent("undisableEvent");
7287 * Nulls out the entire DataTable instance and related objects, removes attached
7288 * event listeners, and clears out DOM elements inside the container. After
7289 * calling this method, the instance reference should be expliclitly nulled by
7290 * implementer, as in myDataTable = null. Use with caution!
7294 destroy : function() {
7296 var instanceName = this.toString();
7298 this._oChainRender.stop();
7300 // Destroy static resizer proxy and column proxy
7301 DT._destroyColumnDragTargetEl();
7302 DT._destroyColumnResizerProxyEl();
7304 // Destroy ColumnDD and ColumnResizers
7305 this._destroyColumnHelpers();
7307 // Destroy all CellEditors
7309 for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
7310 oCellEditor = this._oColumnSet.flat[i].editor;
7311 if(oCellEditor && oCellEditor.destroy) {
7312 oCellEditor.destroy();
7313 this._oColumnSet.flat[i].editor = null;
7317 // Unhook custom events
7318 this._oRecordSet.unsubscribeAll();
7319 this.unsubscribeAll();
7321 // Unhook DOM events
7322 Ev.removeListener(document, "click", this._onDocumentClick);
7324 // Clear out the container
7325 this._destroyContainerEl(this._elContainer);
7328 for(var param in this) {
7329 if(lang.hasOwnProperty(this, param)) {
7334 // Clean up static values
7335 DT._nCurrentCount--;
7337 if(DT._nCurrentCount < 1) {
7338 if(DT._elDynStyleNode) {
7339 document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
7340 DT._elDynStyleNode = null;
7347 * Displays message within secondary TBODY.
7349 * @method showTableMessage
7350 * @param sHTML {String} (optional) Value for innerHTMlang.
7351 * @param sClassName {String} (optional) Classname.
7353 showTableMessage : function(sHTML, sClassName) {
7354 var elCell = this._elMsgTd;
7355 if(lang.isString(sHTML)) {
7356 elCell.firstChild.innerHTML = sHTML;
7358 if(lang.isString(sClassName)) {
7359 elCell.className = sClassName;
7362 this._elMsgTbody.style.display = "";
7364 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
7368 * Hides secondary TBODY.
7370 * @method hideTableMessage
7372 hideTableMessage : function() {
7373 if(this._elMsgTbody.style.display != "none") {
7374 this._elMsgTbody.style.display = "none";
7375 this._elMsgTbody.parentNode.style.width = "";
7376 this.fireEvent("tableMsgHideEvent");
7381 * Brings focus to the TBODY element. Alias to focusTbodyEl.
7385 focus : function() {
7386 this.focusTbodyEl();
7390 * Brings focus to the THEAD element.
7392 * @method focusTheadEl
7394 focusTheadEl : function() {
7395 this._focusEl(this._elThead);
7399 * Brings focus to the TBODY element.
7401 * @method focusTbodyEl
7403 focusTbodyEl : function() {
7404 this._focusEl(this._elTbody);
7408 * Setting display:none on DataTable or any parent may impact width validations.
7409 * After setting display back to "", implementers should call this method to
7410 * manually perform those validations.
7414 onShow : function() {
7415 this.validateColumnWidths();
7417 for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
7419 if(col._ddResizer) {
7420 col._ddResizer.resetResizerEl();
7491 // RECORDSET FUNCTIONS
7494 * Returns Record index for given TR element or page row index.
7496 * @method getRecordIndex
7497 * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
7498 * element reference or page row index.
7499 * @return {Number} Record's RecordSet index, or null.
7501 getRecordIndex : function(row) {
7504 if(!lang.isNumber(row)) {
7506 if(row instanceof YAHOO.widget.Record) {
7507 return this._oRecordSet.getRecordIndex(row);
7509 // By element reference
7511 // Find the TR element
7512 var el = this.getTrEl(row);
7514 nTrIndex = el.sectionRowIndex;
7518 // By page row index
7523 if(lang.isNumber(nTrIndex)) {
7524 var oPaginator = this.get("paginator");
7526 return oPaginator.get('recordOffset') + nTrIndex;
7537 * For the given identifier, returns the associated Record instance.
7540 * @param row {HTMLElement | Number | String} DOM reference to a TR element (or
7541 * child of a TR element), RecordSet position index, or Record ID.
7542 * @return {YAHOO.widget.Record} Record instance.
7544 getRecord : function(row) {
7545 var oRecord = this._oRecordSet.getRecord(row);
7548 // Validate TR element
7549 var elRow = this.getTrEl(row);
7551 oRecord = this._oRecordSet.getRecord(this.getRecordIndex(elRow.sectionRowIndex));
7555 if(oRecord instanceof YAHOO.widget.Record) {
7556 return this._oRecordSet.getRecord(oRecord);
7611 * For the given identifier, returns the associated Column instance. Note: For
7612 * getting Columns by Column ID string, please use the method getColumnById().
7615 * @param column {HTMLElement | String | Number} TH/TD element (or child of a
7616 * TH/TD element), a Column key, or a ColumnSet key index.
7617 * @return {YAHOO.widget.Column} Column instance.
7619 getColumn : function(column) {
7620 var oColumn = this._oColumnSet.getColumn(column);
7623 // Validate TD element
7624 var elCell = this.getTdEl(column);
7626 oColumn = this._oColumnSet.getColumn(elCell.cellIndex);
7628 // Validate TH element
7630 elCell = this.getThEl(column);
7633 var allColumns = this._oColumnSet.flat;
7634 for(var i=0, len=allColumns.length; i<len; i++) {
7635 if(allColumns[i].getThEl().id === elCell.id) {
7636 oColumn = allColumns[i];
7648 * For the given Column ID, returns the associated Column instance. Note: For
7649 * getting Columns by key, please use the method getColumn().
7651 * @method getColumnById
7652 * @param column {String} Column ID string.
7653 * @return {YAHOO.widget.Column} Column instance.
7655 getColumnById : function(column) {
7656 return this._oColumnSet.getColumnById(column);
7660 * For the given Column instance, returns next direction to sort.
7662 * @method getColumnSortDir
7663 * @param oColumn {YAHOO.widget.Column} Column instance.
7664 * @param oSortedBy {Object} (optional) Specify the state, or use current state.
7665 * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
7667 getColumnSortDir : function(oColumn, oSortedBy) {
7668 // Backward compatibility
7669 if(oColumn.sortOptions && oColumn.sortOptions.defaultOrder) {
7670 if(oColumn.sortOptions.defaultOrder == "asc") {
7671 oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
7673 else if (oColumn.sortOptions.defaultOrder == "desc") {
7674 oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
7678 // What is the Column's default sort direction?
7679 var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
7681 // Is the Column currently sorted?
7682 var bSorted = false;
7683 oSortedBy = oSortedBy || this.get("sortedBy");
7684 if(oSortedBy && (oSortedBy.key === oColumn.key)) {
7687 sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7690 sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7697 * Overridable method gives implementers a hook to show loading message before
7700 * @method doBeforeSortColumn
7701 * @param oColumn {YAHOO.widget.Column} Column instance.
7702 * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
7703 * YAHOO.widget.DataTable.CLASS_DESC.
7704 * @return {Boolean} Return true to continue sorting Column.
7706 doBeforeSortColumn : function(oColumn, sSortDir) {
7707 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
7712 * Sorts given Column. If "dynamicData" is true, current selections are purged before
7713 * a request is sent to the DataSource for data for the new state (using the
7714 * request returned by "generateRequest()").
7716 * @method sortColumn
7717 * @param oColumn {YAHOO.widget.Column} Column instance.
7718 * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
7719 * YAHOO.widget.DataTable.CLASS_DESC
7721 sortColumn : function(oColumn, sDir) {
7722 if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
7723 if(!oColumn.sortable) {
7724 Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
7727 // Validate given direction
7728 if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
7733 var sSortDir = sDir || this.getColumnSortDir(oColumn);
7735 // Is the Column currently sorted?
7736 var oSortedBy = this.get("sortedBy") || {};
7737 var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
7739 var ok = this.doBeforeSortColumn(oColumn, sSortDir);
7742 if(this.get("dynamicData")) {
7743 // Get current state
7744 var oState = this.getState();
7746 // Reset record offset, if paginated
7747 if(oState.pagination) {
7748 oState.pagination.recordOffset = 0;
7751 // Update sortedBy to new values
7757 // Get the request for the new state
7758 var request = this.get("generateRequest")(oState, this);
7761 this.unselectAllRows();
7762 this.unselectAllCells();
7764 // Send request for new data
7766 success : this.onDataReturnSetRows,
7767 failure : this.onDataReturnSetRows,
7768 argument : oState, // Pass along the new state to the callback
7771 this._oDataSource.sendRequest(request, callback);
7775 // Is there a custom sort handler function defined?
7776 var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
7777 // Custom sort function
7778 oColumn.sortOptions.sortFunction : null;
7781 if(!bSorted || sDir || sortFnc) {
7782 // Get the field to sort
7783 var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
7785 // Default sort function if necessary
7786 sortFnc = sortFnc ||
7787 function(a, b, desc) {
7788 var sorted = YAHOO.util.Sort.compare(a.getData(sField),b.getData(sField), desc);
7790 return YAHOO.util.Sort.compare(a.getCount(),b.getCount(), desc); // Bug 1932978
7797 this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false));
7799 // Just reverse the Records
7801 this._oRecordSet.reverseRecords();
7804 // Reset to first page if paginated
7805 var oPaginator = this.get('paginator');
7807 // Set page silently, so as not to fire change event.
7808 oPaginator.setPage(1,true);
7811 // Update UI via sortedBy
7813 this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn});
7816 this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
7823 * Sets given Column to given pixel width. If new width is less than minimum
7824 * width, sets to minimum width. Updates oColumn.width value.
7826 * @method setColumnWidth
7827 * @param oColumn {YAHOO.widget.Column} Column instance.
7828 * @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
7829 * subject to minWidth and maxAutoWidth validations.
7831 setColumnWidth : function(oColumn, nWidth) {
7832 if(!(oColumn instanceof YAHOO.widget.Column)) {
7833 oColumn = this.getColumn(oColumn);
7836 // Validate new width against minimum width
7837 if(lang.isNumber(nWidth)) {
7838 // This is why we must require a Number... :-|
7839 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
7842 oColumn.width = nWidth;
7844 // Resize the DOM elements
7845 this._setColumnWidth(oColumn, nWidth+"px");
7847 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
7849 // Unsets a width to auto-size
7850 else if(nWidth === null) {
7852 oColumn.width = nWidth;
7854 // Resize the DOM elements
7855 this._setColumnWidth(oColumn, "auto");
7856 this.validateColumnWidths(oColumn);
7857 this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
7860 // Bug 2339454: resize then sort misaligment
7861 this._clearTrTemplateEl();
7868 * Sets liner DIV elements of given Column to given width. When value should be
7869 * auto-calculated to fit content overflow is set to visible, otherwise overflow
7870 * is set to hidden. No validations against minimum width and no updating
7871 * Column.width value.
7873 * @method _setColumnWidth
7874 * @param oColumn {YAHOO.widget.Column} Column instance.
7875 * @param sWidth {String} New width value.
7876 * @param sOverflow {String} Should be "hidden" when Column width is explicitly
7877 * being set to a value, but should be "visible" when Column is meant to auto-fit content.
7880 _setColumnWidth : function(oColumn, sWidth, sOverflow) {
7881 if(oColumn && (oColumn.getKeyIndex() !== null)) {
7882 sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
7884 // Dynamic style algorithm
7885 if(!DT._bDynStylesFallback) {
7886 this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
7888 // Dynamic function algorithm
7890 this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
7898 * Updates width of a Column's liner DIV elements by dynamically creating a
7899 * STYLE node and writing and updating CSS style rules to it. If this fails during
7900 * runtime, the fallback method _setColumnWidthDynFunction() will be called.
7901 * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
7902 * nested within another TABLE element. For these cases, it is recommended to
7903 * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
7905 * @method _setColumnWidthDynStyles
7906 * @param oColumn {YAHOO.widget.Column} Column instance.
7907 * @param sWidth {String} New width value.
7910 _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
7911 var s = DT._elDynStyleNode,
7914 // Create a new STYLE node
7916 s = document.createElement('style');
7917 s.type = 'text/css';
7918 s = document.getElementsByTagName('head').item(0).appendChild(s);
7919 DT._elDynStyleNode = s;
7922 // We have a STYLE node to update
7924 // Use unique classname for this Column instance as a hook for resizing
7925 var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
7927 // Hide for performance
7929 this._elTbody.style.display = 'none';
7932 rule = DT._oDynStyles[sClassname];
7934 // The Column does not yet have a rule
7936 if(s.styleSheet && s.styleSheet.addRule) {
7937 s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
7938 s.styleSheet.addRule(sClassname,'width:'+sWidth);
7939 rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
7940 DT._oDynStyles[sClassname] = rule;
7942 else if(s.sheet && s.sheet.insertRule) {
7943 s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
7944 rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
7945 DT._oDynStyles[sClassname] = rule;
7948 // We have a rule to update
7950 rule.style.overflow = sOverflow;
7951 rule.style.width = sWidth;
7956 this._elTbody.style.display = '';
7960 // That was not a success, we must call the fallback routine
7962 DT._bDynStylesFallback = true;
7963 this._setColumnWidthDynFunction(oColumn, sWidth);
7968 * Updates width of a Column's liner DIV elements by dynamically creating a
7969 * function to update all element style properties in one pass. Note: This
7970 * technique is not supported in sandboxed environments that prohibit EVALs.
7972 * @method _setColumnWidthDynFunction
7973 * @param oColumn {YAHOO.widget.Column} Column instance.
7974 * @param sWidth {String} New width value.
7977 _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
7978 // TODO: why is this here?
7979 if(sWidth == 'auto') {
7983 // Create one function for each value of rows.length
7984 var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
7986 // Dynamically create the function
7987 if (!this._aDynFunctions[rowslen]) {
7989 //Compile a custom function to do all the liner div width
7990 //assignments at the same time. A unique function is required
7991 //for each unique number of rows in _elTbody. This will
7992 //result in a function declaration like:
7993 //function (oColumn,sWidth,sOverflow) {
7994 // var colIdx = oColumn.getKeyIndex();
7995 // oColumn.getThLinerEl().style.overflow =
7996 // this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
7997 // this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
7998 // ... (for all row indices in this._elTbody.rows.length - 1)
7999 // this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
8001 // oColumn.getThLinerEl().style.width =
8002 // this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
8003 // this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
8004 // ... (for all row indices in this._elTbody.rows.length - 1)
8005 // this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
8011 'var colIdx=oColumn.getKeyIndex();',
8012 'oColumn.getThLinerEl().style.overflow='
8014 for (i=rowslen-1, j=2; i >= 0; --i) {
8015 resizerDef[j++] = 'this._elTbody.rows[';
8016 resizerDef[j++] = i;
8017 resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
8019 resizerDef[j] = 'sOverflow;';
8020 resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
8021 for (i=rowslen-1, k=j+2; i >= 0; --i) {
8022 resizerDef[k++] = 'this._elTbody.rows[';
8023 resizerDef[k++] = i;
8024 resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
8026 resizerDef[k] = 'sWidth;';
8027 this._aDynFunctions[rowslen] =
8028 new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
8031 // Get the function to execute
8032 var resizerFn = this._aDynFunctions[rowslen];
8034 // TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
8036 resizerFn.call(this,oColumn,sWidth,sOverflow);
8041 * For one or all Columns, when Column is not hidden, width is not set, and minWidth
8042 * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
8044 * @method validateColumnWidths
8045 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
8047 validateColumnWidths : function(oColumn) {
8048 var elColgroup = this._elColgroup;
8049 var elColgroupClone = elColgroup.cloneNode(true);
8050 var bNeedsValidation = false;
8051 var allKeys = this._oColumnSet.keys;
8053 // Validate just one Column's minWidth and/or maxAutoWidth
8054 if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
8055 elThLiner = oColumn.getThLinerEl();
8056 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8057 elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width =
8059 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8060 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8061 bNeedsValidation = true;
8063 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8064 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8067 // Validate all Columns
8069 for(var i=0, len=allKeys.length; i<len; i++) {
8070 oColumn = allKeys[i];
8071 if(!oColumn.hidden && !oColumn.width) {
8072 elThLiner = oColumn.getThLinerEl();
8073 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8074 elColgroupClone.childNodes[i].style.width =
8076 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8077 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8078 bNeedsValidation = true;
8080 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8081 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8086 if(bNeedsValidation) {
8087 elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
8088 this._elColgroup = elColgroupClone;
8095 * @method _clearMinWidth
8096 * @param oColumn {YAHOO.widget.Column} Which Column.
8099 _clearMinWidth : function(oColumn) {
8100 if(oColumn.getKeyIndex() !== null) {
8101 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
8106 * Restores minWidth.
8108 * @method _restoreMinWidth
8109 * @param oColumn {YAHOO.widget.Column} Which Column.
8112 _restoreMinWidth : function(oColumn) {
8113 if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
8114 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
8119 * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
8120 * hide/show non-nested Columns, and top-level parent Columns (which will
8121 * hide/show all children Columns).
8123 * @method hideColumn
8124 * @param oColumn {YAHOO.widget.Column} Column instance.
8126 hideColumn : function(oColumn) {
8127 if(!(oColumn instanceof YAHOO.widget.Column)) {
8128 oColumn = this.getColumn(oColumn);
8130 // Only top-level Columns can get hidden due to issues in FF2 and SF3
8131 if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
8133 var allrows = this.getTbodyEl().rows;
8134 var l = allrows.length;
8135 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8137 // Hide each nested Column
8138 for(var i=0; i<allDescendants.length; i++) {
8139 var thisColumn = allDescendants[i];
8140 thisColumn.hidden = true;
8142 // Style the head cell
8143 Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8145 // Does this Column have body cells?
8146 var thisKeyIndex = thisColumn.getKeyIndex();
8147 if(thisKeyIndex !== null) {
8149 this._clearMinWidth(oColumn);
8151 // Style the body cells
8152 for(var j=0;j<l;j++) {
8153 Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8157 this.fireEvent("columnHideEvent",{column:thisColumn});
8160 this._repaintOpera();
8161 this._clearTrTemplateEl();
8168 * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
8169 * hide/show non-nested Columns, and top-level parent Columns (which will
8170 * hide/show all children Columns).
8172 * @method showColumn
8173 * @param oColumn {YAHOO.widget.Column} Column instance.
8175 showColumn : function(oColumn) {
8176 if(!(oColumn instanceof YAHOO.widget.Column)) {
8177 oColumn = this.getColumn(oColumn);
8179 // Only top-level Columns can get hidden
8180 if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
8181 var allrows = this.getTbodyEl().rows;
8182 var l = allrows.length;
8183 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8185 // Show each nested Column
8186 for(var i=0; i<allDescendants.length; i++) {
8187 var thisColumn = allDescendants[i];
8188 thisColumn.hidden = false;
8190 // Unstyle the head cell
8191 Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8193 // Does this Column have body cells?
8194 var thisKeyIndex = thisColumn.getKeyIndex();
8195 if(thisKeyIndex !== null) {
8197 this._restoreMinWidth(oColumn);
8200 // Unstyle the body cells
8201 for(var j=0;j<l;j++) {
8202 Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8206 this.fireEvent("columnShowEvent",{column:thisColumn});
8208 this._clearTrTemplateEl();
8215 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
8216 * non-nested Columns, and top-level parent Columns (which will remove all
8217 * children Columns).
8219 * @method removeColumn
8220 * @param oColumn {YAHOO.widget.Column} Column instance.
8221 * @return oColumn {YAHOO.widget.Column} Removed Column instance.
8223 removeColumn : function(oColumn) {
8225 if(!(oColumn instanceof YAHOO.widget.Column)) {
8226 oColumn = this.getColumn(oColumn);
8229 var nColTreeIndex = oColumn.getTreeIndex();
8230 if(nColTreeIndex !== null) {
8231 // Which key index(es)
8233 aKeyIndexes = oColumn.getKeyIndex();
8234 // Must be a parent Column
8235 if(aKeyIndexes === null) {
8236 var descKeyIndexes = [];
8237 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8238 for(i=0, len=allDescendants.length; i<len; i++) {
8239 // Is this descendant a key Column?
8240 var thisKey = allDescendants[i].getKeyIndex();
8241 if(thisKey !== null) {
8242 descKeyIndexes[descKeyIndexes.length] = thisKey;
8245 if(descKeyIndexes.length > 0) {
8246 aKeyIndexes = descKeyIndexes;
8249 // Must be a key Column
8251 aKeyIndexes = [aKeyIndexes];
8254 if(aKeyIndexes !== null) {
8255 // Sort the indexes so we can remove from the right
8256 aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8258 // Destroy previous THEAD
8259 this._destroyTheadEl();
8262 var aOrigColumnDefs = this._oColumnSet.getDefinitions();
8263 oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
8264 this._initColumnSet(aOrigColumnDefs);
8265 this._initTheadEl();
8268 for(i=aKeyIndexes.length-1; i>-1; i--) {
8269 this._removeColgroupColEl(aKeyIndexes[i]);
8273 var allRows = this._elTbody.rows;
8274 if(allRows.length > 0) {
8275 var loopN = this.get("renderLoopSize"),
8276 loopEnd = allRows.length;
8277 this._oChainRender.add({
8278 method: function(oArg) {
8279 if((this instanceof DT) && this._sId) {
8280 var i = oArg.nCurrentRow,
8281 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8282 aIndexes = oArg.aIndexes,
8284 for(; i < len; ++i) {
8285 for(j = aIndexes.length-1; j>-1; j--) {
8286 allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
8289 oArg.nCurrentRow = i;
8292 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8293 argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
8295 timeout: (loopN > 0) ? 0 : -1
8297 this._runRenderChain();
8300 this.fireEvent("columnRemoveEvent",{column:oColumn});
8308 * Inserts given Column at the index if given, otherwise at the end. NOTE: You
8309 * can only add non-nested Columns and top-level parent Columns. You cannot add
8310 * a nested Column to an existing parent.
8312 * @method insertColumn
8313 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
8314 * definition or a Column instance.
8315 * @param index {Number} (optional) New tree index.
8316 * @return oColumn {YAHOO.widget.Column} Inserted Column instance.
8318 insertColumn : function(oColumn, index) {
8320 if(oColumn instanceof YAHOO.widget.Column) {
8321 oColumn = oColumn.getDefinition();
8323 else if(oColumn.constructor !== Object) {
8327 // Validate index or append new Column to the end of the ColumnSet
8328 var oColumnSet = this._oColumnSet;
8329 if(!lang.isValue(index) || !lang.isNumber(index)) {
8330 index = oColumnSet.tree[0].length;
8333 // Destroy previous THEAD
8334 this._destroyTheadEl();
8337 var aNewColumnDefs = this._oColumnSet.getDefinitions();
8338 aNewColumnDefs.splice(index, 0, oColumn);
8339 this._initColumnSet(aNewColumnDefs);
8340 this._initTheadEl();
8342 // Need to refresh the reference
8343 oColumnSet = this._oColumnSet;
8344 var oNewColumn = oColumnSet.tree[0][index];
8346 // Get key index(es) for new Column
8348 descKeyIndexes = [];
8349 var allDescendants = oColumnSet.getDescendants(oNewColumn);
8350 for(i=0, len=allDescendants.length; i<len; i++) {
8351 // Is this descendant a key Column?
8352 var thisKey = allDescendants[i].getKeyIndex();
8353 if(thisKey !== null) {
8354 descKeyIndexes[descKeyIndexes.length] = thisKey;
8358 if(descKeyIndexes.length > 0) {
8360 var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8363 for(i=descKeyIndexes.length-1; i>-1; i--) {
8364 this._insertColgroupColEl(descKeyIndexes[i]);
8368 var allRows = this._elTbody.rows;
8369 if(allRows.length > 0) {
8370 var loopN = this.get("renderLoopSize"),
8371 loopEnd = allRows.length;
8373 // Get templates for each new TD
8374 var aTdTemplates = [],
8376 for(i=0, len=descKeyIndexes.length; i<len; i++) {
8377 var thisKeyIndex = descKeyIndexes[i];
8378 elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
8379 elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
8380 aTdTemplates[thisKeyIndex] = elTdTemplate;
8383 this._oChainRender.add({
8384 method: function(oArg) {
8385 if((this instanceof DT) && this._sId) {
8386 var i = oArg.nCurrentRow, j,
8387 descKeyIndexes = oArg.descKeyIndexes,
8388 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8390 for(; i < len; ++i) {
8391 nextSibling = allRows[i].childNodes[newIndex] || null;
8392 for(j=descKeyIndexes.length-1; j>-1; j--) {
8393 allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
8396 oArg.nCurrentRow = i;
8399 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8400 argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
8402 timeout: (loopN > 0) ? 0 : -1
8404 this._runRenderChain();
8407 this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
8413 * Removes given Column and inserts into given tree index. NOTE: You
8414 * can only reorder non-nested Columns and top-level parent Columns. You cannot
8415 * reorder a nested Column to an existing parent.
8417 * @method reorderColumn
8418 * @param oColumn {YAHOO.widget.Column} Column instance.
8419 * @param index {Number} New tree index.
8420 * @return oColumn {YAHOO.widget.Column} Reordered Column instance.
8422 reorderColumn : function(oColumn, index) {
8423 // Validate Column and new index
8424 if(!(oColumn instanceof YAHOO.widget.Column)) {
8425 oColumn = this.getColumn(oColumn);
8427 if(oColumn && YAHOO.lang.isNumber(index)) {
8428 var nOrigTreeIndex = oColumn.getTreeIndex();
8429 if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
8430 // Which key index(es)
8432 aOrigKeyIndexes = oColumn.getKeyIndex(),
8434 descKeyIndexes = [],
8436 // Must be a parent Column...
8437 if(aOrigKeyIndexes === null) {
8438 allDescendants = this._oColumnSet.getDescendants(oColumn);
8439 for(i=0, len=allDescendants.length; i<len; i++) {
8440 // Is this descendant a key Column?
8441 thisKey = allDescendants[i].getKeyIndex();
8442 if(thisKey !== null) {
8443 descKeyIndexes[descKeyIndexes.length] = thisKey;
8446 if(descKeyIndexes.length > 0) {
8447 aOrigKeyIndexes = descKeyIndexes;
8450 // ...or else must be a key Column
8452 aOrigKeyIndexes = [aOrigKeyIndexes];
8455 if(aOrigKeyIndexes !== null) {
8457 aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8459 // Destroy previous THEAD
8460 this._destroyTheadEl();
8463 var aColumnDefs = this._oColumnSet.getDefinitions();
8464 var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
8465 aColumnDefs.splice(index, 0, oColumnDef);
8466 this._initColumnSet(aColumnDefs);
8467 this._initTheadEl();
8469 // Need to refresh the reference
8470 var oNewColumn = this._oColumnSet.tree[0][index];
8472 // What are new key index(es)
8473 var aNewKeyIndexes = oNewColumn.getKeyIndex();
8474 // Must be a parent Column
8475 if(aNewKeyIndexes === null) {
8476 descKeyIndexes = [];
8477 allDescendants = this._oColumnSet.getDescendants(oNewColumn);
8478 for(i=0, len=allDescendants.length; i<len; i++) {
8479 // Is this descendant a key Column?
8480 thisKey = allDescendants[i].getKeyIndex();
8481 if(thisKey !== null) {
8482 descKeyIndexes[descKeyIndexes.length] = thisKey;
8485 if(descKeyIndexes.length > 0) {
8486 aNewKeyIndexes = descKeyIndexes;
8489 // Must be a key Column
8491 aNewKeyIndexes = [aNewKeyIndexes];
8494 // Sort the new indexes and grab the first one for the new location
8495 var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8498 this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
8501 var allRows = this._elTbody.rows;
8502 if(allRows.length > 0) {
8503 var loopN = this.get("renderLoopSize"),
8504 loopEnd = allRows.length;
8505 this._oChainRender.add({
8506 method: function(oArg) {
8507 if((this instanceof DT) && this._sId) {
8508 var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
8509 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8510 aIndexes = oArg.aIndexes, thisTr;
8512 for(; i < len; ++i) {
8514 thisTr = allRows[i];
8517 for(j=aIndexes.length-1; j>-1; j--) {
8518 tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
8522 nextSibling = thisTr.childNodes[newIndex] || null;
8523 for(j=tmpTds.length-1; j>-1; j--) {
8524 thisTr.insertBefore(tmpTds[j], nextSibling);
8527 oArg.nCurrentRow = i;
8530 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8531 argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
8533 timeout: (loopN > 0) ? 0 : -1
8535 this._runRenderChain();
8538 this.fireEvent("columnReorderEvent",{column:oNewColumn});
8546 * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8547 * select/unselect non-nested Columns, and bottom-level key Columns.
8549 * @method selectColumn
8550 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8551 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8553 selectColumn : function(oColumn) {
8554 oColumn = this.getColumn(oColumn);
8555 if(oColumn && !oColumn.selected) {
8556 // Only bottom-level Columns can get hidden
8557 if(oColumn.getKeyIndex() !== null) {
8558 oColumn.selected = true;
8561 var elTh = oColumn.getThEl();
8562 Dom.addClass(elTh,DT.CLASS_SELECTED);
8564 // Update body cells
8565 var allRows = this.getTbodyEl().rows;
8566 var oChainRender = this._oChainRender;
8568 method: function(oArg) {
8569 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8570 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);
8575 iterations: allRows.length,
8576 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8579 this._clearTrTemplateEl();
8581 this._elTbody.style.display = "none";
8582 this._runRenderChain();
8583 this._elTbody.style.display = "";
8585 this.fireEvent("columnSelectEvent",{column:oColumn});
8593 * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8594 * select/unselect non-nested Columns, and bottom-level key Columns.
8596 * @method unselectColumn
8597 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8598 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8600 unselectColumn : function(oColumn) {
8601 oColumn = this.getColumn(oColumn);
8602 if(oColumn && oColumn.selected) {
8603 // Only bottom-level Columns can get hidden
8604 if(oColumn.getKeyIndex() !== null) {
8605 oColumn.selected = false;
8608 var elTh = oColumn.getThEl();
8609 Dom.removeClass(elTh,DT.CLASS_SELECTED);
8611 // Update body cells
8612 var allRows = this.getTbodyEl().rows;
8613 var oChainRender = this._oChainRender;
8615 method: function(oArg) {
8616 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8617 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);
8622 iterations:allRows.length,
8623 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8626 this._clearTrTemplateEl();
8628 this._elTbody.style.display = "none";
8629 this._runRenderChain();
8630 this._elTbody.style.display = "";
8632 this.fireEvent("columnUnselectEvent",{column:oColumn});
8640 * Returns an array selected Column instances.
8642 * @method getSelectedColumns
8643 * @return {YAHOO.widget.Column[]} Array of Column instances.
8645 getSelectedColumns : function(oColumn) {
8646 var selectedColumns = [];
8647 var aKeys = this._oColumnSet.keys;
8648 for(var i=0,len=aKeys.length; i<len; i++) {
8649 if(aKeys[i].selected) {
8650 selectedColumns[selectedColumns.length] = aKeys[i];
8653 return selectedColumns;
8657 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8658 * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8659 * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8661 * @method highlightColumn
8662 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8663 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8665 highlightColumn : function(column) {
8666 var oColumn = this.getColumn(column);
8667 // Only bottom-level Columns can get highlighted
8668 if(oColumn && (oColumn.getKeyIndex() !== null)) {
8670 var elTh = oColumn.getThEl();
8671 Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
8673 // Update body cells
8674 var allRows = this.getTbodyEl().rows;
8675 var oChainRender = this._oChainRender;
8677 method: function(oArg) {
8678 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8679 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
8684 iterations:allRows.length,
8685 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8688 this._elTbody.style.display = "none";
8689 this._runRenderChain();
8690 this._elTbody.style.display = "";
8692 this.fireEvent("columnHighlightEvent",{column:oColumn});
8699 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8700 * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8701 * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8703 * @method unhighlightColumn
8704 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8705 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8707 unhighlightColumn : function(column) {
8708 var oColumn = this.getColumn(column);
8709 // Only bottom-level Columns can get highlighted
8710 if(oColumn && (oColumn.getKeyIndex() !== null)) {
8712 var elTh = oColumn.getThEl();
8713 Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
8715 // Update body cells
8716 var allRows = this.getTbodyEl().rows;
8717 var oChainRender = this._oChainRender;
8719 method: function(oArg) {
8720 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8721 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
8726 iterations:allRows.length,
8727 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8730 this._elTbody.style.display = "none";
8731 this._runRenderChain();
8732 this._elTbody.style.display = "";
8734 this.fireEvent("columnUnhighlightEvent",{column:oColumn});
8786 * Adds one new Record of data into the RecordSet at the index if given,
8787 * otherwise at the end. If the new Record is in page view, the
8788 * corresponding DOM elements are also updated.
8791 * @param oData {Object} Object literal of data for the row.
8792 * @param index {Number} (optional) RecordSet position index at which to add data.
8794 addRow : function(oData, index) {
8795 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
8799 if(oData && lang.isObject(oData)) {
8800 var oRecord = this._oRecordSet.addRecord(oData, index);
8803 var oPaginator = this.get('paginator');
8807 // Update the paginator's totalRecords
8808 var totalRecords = oPaginator.get('totalRecords');
8809 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
8810 oPaginator.set('totalRecords',totalRecords + 1);
8813 recIndex = this.getRecordIndex(oRecord);
8814 var endRecIndex = (oPaginator.getPageRecords())[1];
8816 // New record affects the view
8817 if (recIndex <= endRecIndex) {
8818 // Defer UI updates to the render method
8822 this.fireEvent("rowAddEvent", {record:oRecord});
8827 recIndex = this.getTrIndex(oRecord);
8828 if(lang.isNumber(recIndex)) {
8829 // Add the TR element
8830 this._oChainRender.add({
8831 method: function(oArg) {
8832 if((this instanceof DT) && this._sId) {
8833 var oRecord = oArg.record;
8834 var recIndex = oArg.recIndex;
8835 var elNewTr = this._addTrEl(oRecord);
8837 var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
8838 this._elTbody.insertBefore(elNewTr, elNext);
8841 if(recIndex === 0) {
8842 this._setFirstRow();
8844 if(elNext === null) {
8848 this._setRowStripes();
8850 this.hideTableMessage();
8852 this.fireEvent("rowAddEvent", {record:oRecord});
8856 argument: {record: oRecord, recIndex: recIndex},
8858 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
8860 this._runRenderChain();
8869 * Convenience method to add multiple rows.
8872 * @param aData {Object[]} Array of object literal data for the rows.
8873 * @param index {Number} (optional) RecordSet position index at which to add data.
8875 addRows : function(aData, index) {
8876 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
8880 if(lang.isArray(aData)) {
8881 var aRecords = this._oRecordSet.addRecords(aData, index);
8883 var recIndex = this.getRecordIndex(aRecords[0]);
8886 var oPaginator = this.get('paginator');
8888 // Update the paginator's totalRecords
8889 var totalRecords = oPaginator.get('totalRecords');
8890 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
8891 oPaginator.set('totalRecords',totalRecords + aRecords.length);
8894 var endRecIndex = (oPaginator.getPageRecords())[1];
8896 // At least one of the new records affects the view
8897 if (recIndex <= endRecIndex) {
8901 this.fireEvent("rowsAddEvent", {records:aRecords});
8906 // Add the TR elements
8907 var loopN = this.get("renderLoopSize");
8908 var loopEnd = recIndex + aData.length;
8909 var nRowsNeeded = (loopEnd - recIndex); // how many needed
8910 var isLast = (recIndex >= this._elTbody.rows.length);
8911 this._oChainRender.add({
8912 method: function(oArg) {
8913 if((this instanceof DT) && this._sId) {
8914 var aRecords = oArg.aRecords,
8915 i = oArg.nCurrentRow,
8916 j = oArg.nCurrentRecord,
8917 len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
8918 df = document.createDocumentFragment(),
8919 elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
8920 for(; i < len; i++, j++) {
8921 df.appendChild(this._addTrEl(aRecords[j]));
8923 this._elTbody.insertBefore(df, elNext);
8924 oArg.nCurrentRow = i;
8925 oArg.nCurrentRecord = j;
8928 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8929 argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
8931 timeout: (loopN > 0) ? 0 : -1
8933 this._oChainRender.add({
8934 method: function(oArg) {
8935 var recIndex = oArg.recIndex;
8937 if(recIndex === 0) {
8938 this._setFirstRow();
8944 this._setRowStripes();
8946 this.fireEvent("rowsAddEvent", {records:aRecords});
8948 argument: {recIndex: recIndex, isLast: isLast},
8950 timeout: -1 // Needs to run immediately after the DOM insertions above
8952 this._runRenderChain();
8953 this.hideTableMessage();
8961 * For the given row, updates the associated Record with the given data. If the
8962 * row is on current page, the corresponding DOM elements are also updated.
8965 * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
8966 * Which row to update: By Record instance, by Record's RecordSet
8967 * position index, by HTMLElement reference to the TR element, or by ID string
8968 * of the TR element.
8969 * @param oData {Object} Object literal of data for the row.
8971 updateRow : function(row, oData) {
8973 if (!lang.isNumber(index)) {
8974 index = this.getRecordIndex(row);
8977 // Update the Record
8978 if(lang.isNumber(index) && (index >= 0)) {
8979 var oRecordSet = this._oRecordSet,
8980 oldRecord = oRecordSet.getRecord(index);
8984 var updatedRecord = this._oRecordSet.setRecord(oData, index),
8985 elRow = this.getTrEl(oldRecord),
8986 // Copy data from the Record for the event that gets fired later
8987 oldData = oldRecord ? oldRecord.getData() : null;
8990 // Update selected rows as necessary
8991 var tracker = this._aSelections || [],
8993 oldId = oldRecord.getId(),
8994 newId = updatedRecord.getId();
8995 for(; i<tracker.length; i++) {
8996 if((tracker[i] === oldId)) {
8999 else if(tracker[i].recordId === oldId) {
9000 tracker[i].recordId = newId;
9004 // Update the TR only if row is on current page
9005 this._oChainRender.add({
9006 method: function() {
9007 if((this instanceof DT) && this._sId) {
9009 var oPaginator = this.get('paginator');
9011 var pageStartIndex = (oPaginator.getPageRecords())[0],
9012 pageLastIndex = (oPaginator.getPageRecords())[1];
9014 // At least one of the new records affects the view
9015 if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
9021 this._updateTrEl(elRow, updatedRecord);
9024 this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
9027 this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
9031 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9033 this._runRenderChain();
9042 * Starting with the given row, updates associated Records with the given data.
9043 * The number of rows to update are determined by the array of data provided.
9044 * Undefined data (i.e., not an object literal) causes a row to be skipped. If
9045 * any of the rows are on current page, the corresponding DOM elements are also
9048 * @method updateRows
9049 * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
9050 * Starting row to update: By Record instance, by Record's RecordSet
9051 * position index, by HTMLElement reference to the TR element, or by ID string
9052 * of the TR element.
9053 * @param aData {Object[]} Array of object literal of data for the rows.
9055 updateRows : function(startrow, aData) {
9056 if(lang.isArray(aData)) {
9057 var startIndex = startrow,
9058 oRecordSet = this._oRecordSet;
9060 if (!lang.isNumber(startrow)) {
9061 startIndex = this.getRecordIndex(startrow);
9064 if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
9065 var lastIndex = startIndex + aData.length,
9066 aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
9067 aNewRecords = oRecordSet.setRecords(aData, startIndex);
9069 // Update selected rows as necessary
9070 var tracker = this._aSelections || [],
9071 i=0, j, newId, oldId;
9072 for(; i<tracker.length; i++) {
9073 for(j=0; j<aOldRecords.length; j++) {
9074 oldId = aOldRecords[j].getId();
9075 if((tracker[i] === oldId)) {
9076 tracker[i] = aNewRecords[j].getId();
9078 else if(tracker[i].recordId === oldId) {
9079 tracker[i].recordId = aNewRecords[j].getId();
9085 var oPaginator = this.get('paginator');
9087 var pageStartIndex = (oPaginator.getPageRecords())[0],
9088 pageLastIndex = (oPaginator.getPageRecords())[1];
9090 // At least one of the new records affects the view
9091 if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
9095 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9100 // Update the TR elements
9101 var loopN = this.get("renderLoopSize"),
9102 rowCount = aData.length, // how many needed
9103 lastRowIndex = this._elTbody.rows.length,
9104 isLast = (lastIndex >= lastRowIndex),
9105 isAdding = (lastIndex > lastRowIndex);
9107 this._oChainRender.add({
9108 method: function(oArg) {
9109 if((this instanceof DT) && this._sId) {
9110 var aRecords = oArg.aRecords,
9111 i = oArg.nCurrentRow,
9112 j = oArg.nDataPointer,
9113 len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
9115 for(; i < len; i++,j++) {
9116 if(isAdding && (i>=lastRowIndex)) {
9117 this._elTbody.appendChild(this._addTrEl(aRecords[j]));
9120 this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
9123 oArg.nCurrentRow = i;
9124 oArg.nDataPointer = j;
9127 iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
9128 argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
9130 timeout: (loopN > 0) ? 0 : -1
9132 this._oChainRender.add({
9133 method: function(oArg) {
9134 var recIndex = oArg.recIndex;
9136 if(recIndex === 0) {
9137 this._setFirstRow();
9143 this._setRowStripes();
9145 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9147 argument: {recIndex: startIndex, isLast: isLast},
9149 timeout: -1 // Needs to run immediately after the DOM insertions above
9151 this._runRenderChain();
9152 this.hideTableMessage();
9161 * Deletes the given row's Record from the RecordSet. If the row is on current page,
9162 * the corresponding DOM elements are also deleted.
9165 * @param row {HTMLElement | String | Number} DOM element reference or ID string
9166 * to DataTable page element or RecordSet index.
9168 deleteRow : function(row) {
9169 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9170 if(lang.isNumber(nRecordIndex)) {
9171 var oRecord = this.getRecord(nRecordIndex);
9173 var nTrIndex = this.getTrIndex(nRecordIndex);
9175 // Remove from selection tracker if there
9176 var sRecordId = oRecord.getId();
9177 var tracker = this._aSelections || [];
9178 for(var j=tracker.length-1; j>-1; j--) {
9179 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9180 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9181 tracker.splice(j,1);
9185 // Delete Record from RecordSet
9186 var oData = this._oRecordSet.deleteRecord(nRecordIndex);
9190 // If paginated and the deleted row was on this or a prior page, just
9192 var oPaginator = this.get('paginator');
9194 // Update the paginator's totalRecords
9195 var totalRecords = oPaginator.get('totalRecords'),
9196 // must capture before the totalRecords change because
9197 // Paginator shifts to previous page automatically
9198 rng = oPaginator.getPageRecords();
9200 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9201 oPaginator.set('totalRecords',totalRecords - 1);
9204 // The deleted record was on this or a prior page, re-render
9205 if (!rng || nRecordIndex <= rng[1]) {
9209 this._oChainRender.add({
9210 method: function() {
9211 if((this instanceof DT) && this._sId) {
9212 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
9216 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9218 this._runRenderChain();
9222 if(lang.isNumber(nTrIndex)) {
9223 this._oChainRender.add({
9224 method: function() {
9225 if((this instanceof DT) && this._sId) {
9226 var isLast = (nTrIndex == this.getLastTrEl().sectionRowIndex);
9227 this._deleteTrEl(nTrIndex);
9229 // Post-delete tasks
9230 if(this._elTbody.rows.length > 0) {
9232 if(nTrIndex === 0) {
9233 this._setFirstRow();
9239 if(nTrIndex != this._elTbody.rows.length) {
9240 this._setRowStripes(nTrIndex);
9244 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
9248 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9250 this._runRenderChain();
9261 * Convenience method to delete multiple rows.
9263 * @method deleteRows
9264 * @param row {HTMLElement | String | Number} DOM element reference or ID string
9265 * to DataTable page element or RecordSet index.
9266 * @param count {Number} (optional) How many rows to delete. A negative value
9267 * will delete towards the beginning.
9269 deleteRows : function(row, count) {
9270 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9271 if(lang.isNumber(nRecordIndex)) {
9272 var oRecord = this.getRecord(nRecordIndex);
9274 var nTrIndex = this.getTrIndex(nRecordIndex);
9276 // Remove from selection tracker if there
9277 var sRecordId = oRecord.getId();
9278 var tracker = this._aSelections || [];
9279 for(var j=tracker.length-1; j>-1; j--) {
9280 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9281 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9282 tracker.splice(j,1);
9286 // Delete Record from RecordSet
9287 var highIndex = nRecordIndex;
9288 var lowIndex = nRecordIndex;
9290 // Validate count and account for negative value
9291 if(count && lang.isNumber(count)) {
9292 highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
9293 lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
9294 count = (count > 0) ? count : count*-1;
9297 count = highIndex - lowIndex + 1;
9304 var aData = this._oRecordSet.deleteRecords(lowIndex, count);
9308 var oPaginator = this.get('paginator'),
9309 loopN = this.get("renderLoopSize");
9310 // If paginated and the deleted row was on this or a prior page, just
9313 // Update the paginator's totalRecords
9314 var totalRecords = oPaginator.get('totalRecords'),
9315 // must capture before the totalRecords change because
9316 // Paginator shifts to previous page automatically
9317 rng = oPaginator.getPageRecords();
9319 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9320 oPaginator.set('totalRecords',totalRecords - aData.length);
9323 // The records were on this or a prior page, re-render
9324 if (!rng || lowIndex <= rng[1]) {
9328 this._oChainRender.add({
9329 method: function(oArg) {
9330 if((this instanceof DT) && this._sId) {
9331 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9335 timeout: (loopN > 0) ? 0 : -1
9337 this._runRenderChain();
9342 if(lang.isNumber(nTrIndex)) {
9343 // Delete the TR elements starting with highest index
9344 var loopEnd = lowIndex;
9345 var nRowsNeeded = count; // how many needed
9346 this._oChainRender.add({
9347 method: function(oArg) {
9348 if((this instanceof DT) && this._sId) {
9349 var i = oArg.nCurrentRow,
9350 len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
9352 this._deleteTrEl(i);
9354 oArg.nCurrentRow = i;
9357 iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
9358 argument: {nCurrentRow:highIndex},
9360 timeout: (loopN > 0) ? 0 : -1
9362 this._oChainRender.add({
9363 method: function() {
9364 // Post-delete tasks
9365 if(this._elTbody.rows.length > 0) {
9366 this._setFirstRow();
9368 this._setRowStripes();
9371 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9374 timeout: -1 // Needs to run immediately after the DOM deletions above
9376 this._runRenderChain();
9434 * Outputs markup into the given TD based on given Record.
9436 * @method formatCell
9437 * @param elCell {HTMLElement} The liner DIV element within the TD.
9438 * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
9439 * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
9441 formatCell : function(elCell, oRecord, oColumn) {
9443 oRecord = this.getRecord(elCell);
9446 oColumn = this.getColumn(elCell.parentNode.cellIndex);
9449 if(oRecord && oColumn) {
9450 var sField = oColumn.field;
9451 var oData = oRecord.getData(sField);
9453 var fnFormatter = typeof oColumn.formatter === 'function' ?
9455 DT.Formatter[oColumn.formatter+''] ||
9456 DT.Formatter.defaultFormatter;
9458 // Apply special formatter
9460 fnFormatter.call(this, elCell, oRecord, oColumn, oData);
9463 elCell.innerHTML = oData;
9466 this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elCell});
9473 * For the given row and column, updates the Record with the given data. If the
9474 * cell is on current page, the corresponding DOM elements are also updated.
9476 * @method updateCell
9477 * @param oRecord {YAHOO.widget.Record} Record instance.
9478 * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
9479 * @param oData {Object} New data value for the cell.
9481 updateCell : function(oRecord, oColumn, oData) {
9482 // Validate Column and Record
9483 oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
9484 if(oColumn && oColumn.getKey() && (oRecord instanceof YAHOO.widget.Record)) {
9485 var sKey = oColumn.getKey(),
9487 // Copy data from the Record for the event that gets fired later
9488 //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
9489 oldData = oRecord.getData(sKey);
9491 // Update Record with new data
9492 this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
9494 // Update the TD only if row is on current page
9495 var elTd = this.getTdEl({record: oRecord, column: oColumn});
9497 this._oChainRender.add({
9498 method: function() {
9499 if((this instanceof DT) && this._sId) {
9500 this.formatCell(elTd.firstChild);
9501 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9505 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9507 this._runRenderChain();
9510 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9567 * Method executed during set() operation for the "paginator" attribute.
9568 * Adds and/or severs event listeners between DataTable and Paginator
9570 * @method _updatePaginator
9571 * @param newPag {Paginator} Paginator instance (or null) for DataTable to use
9574 _updatePaginator : function (newPag) {
9575 var oldPag = this.get('paginator');
9576 if (oldPag && newPag !== oldPag) {
9577 oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9580 newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9585 * Update the UI infrastructure in response to a "paginator" attribute change.
9587 * @method _handlePaginatorChange
9588 * @param e {Object} Change event object containing keys 'type','newValue',
9592 _handlePaginatorChange : function (e) {
9593 if (e.prevValue === e.newValue) { return; }
9595 var newPag = e.newValue,
9596 oldPag = e.prevValue,
9597 containers = this._defaultPaginatorContainers();
9600 if (oldPag.getContainerNodes()[0] == containers[0]) {
9601 oldPag.set('containers',[]);
9605 // Convenience: share the default containers if possible.
9606 // Otherwise, remove the default containers from the DOM.
9607 if (containers[0]) {
9608 if (newPag && !newPag.getContainerNodes().length) {
9609 newPag.set('containers',containers);
9611 // No new Paginator to use existing containers, OR new
9612 // Paginator has configured containers.
9613 for (var i = containers.length - 1; i >= 0; --i) {
9614 if (containers[i]) {
9615 containers[i].parentNode.removeChild(containers[i]);
9628 this.renderPaginator();
9634 * Returns the default containers used for Paginators. If create param is
9635 * passed, the containers will be created and added to the DataTable container.
9637 * @method _defaultPaginatorContainers
9638 * @param create {boolean} Create the default containers if not found
9641 _defaultPaginatorContainers : function (create) {
9642 var above_id = this._sId + '-paginator0',
9643 below_id = this._sId + '-paginator1',
9644 above = Dom.get(above_id),
9645 below = Dom.get(below_id);
9647 if (create && (!above || !below)) {
9648 // One above and one below the table
9650 above = document.createElement('div');
9651 above.id = above_id;
9652 Dom.addClass(above, DT.CLASS_PAGINATOR);
9654 this._elContainer.insertBefore(above,this._elContainer.firstChild);
9658 below = document.createElement('div');
9659 below.id = below_id;
9660 Dom.addClass(below, DT.CLASS_PAGINATOR);
9662 this._elContainer.appendChild(below);
9666 return [above,below];
9670 * Renders the Paginator to the DataTable UI
9672 * @method renderPaginator
9674 renderPaginator : function () {
9675 var pag = this.get("paginator");
9676 if (!pag) { return; }
9678 // Add the containers if the Paginator is not configured with containers
9679 if (!pag.getContainerNodes().length) {
9680 pag.set('containers',this._defaultPaginatorContainers(true));
9687 * Overridable method gives implementers a hook to show loading message before
9688 * changing Paginator value.
9690 * @method doBeforePaginatorChange
9691 * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9692 * @return {Boolean} Return true to continue changing Paginator value.
9694 doBeforePaginatorChange : function(oPaginatorState) {
9695 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
9700 * Responds to new Pagination states. By default, updates the UI to reflect the
9701 * new state. If "dynamicData" is true, current selections are purged before
9702 * a request is sent to the DataSource for data for the new state (using the
9703 * request returned by "generateRequest()").
9705 * @method onPaginatorChangeRequest
9706 * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9708 onPaginatorChangeRequest : function (oPaginatorState) {
9709 var ok = this.doBeforePaginatorChange(oPaginatorState);
9711 // Server-side pagination
9712 if(this.get("dynamicData")) {
9713 // Get the current state
9714 var oState = this.getState();
9716 // Update pagination values
9717 oState.pagination = oPaginatorState;
9719 // Get the request for the new state
9720 var request = this.get("generateRequest")(oState, this);
9723 this.unselectAllRows();
9724 this.unselectAllCells();
9726 // Get the new data from the server
9728 success : this.onDataReturnSetRows,
9729 failure : this.onDataReturnSetRows,
9730 argument : oState, // Pass along the new state to the callback
9733 this._oDataSource.sendRequest(request, callback);
9735 // Client-side pagination
9737 // Set the core pagination values silently (the second param)
9738 // to avoid looping back through the changeRequest mechanism
9739 oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
9740 oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
9799 // SELECTION/HIGHLIGHTING
9802 * Reference to last highlighted cell element
9804 * @property _elLastHighlightedTd
9808 _elLastHighlightedTd : null,
9811 * ID string of last highlighted row element
9813 * @property _sLastHighlightedTrElId
9817 //_sLastHighlightedTrElId : null,
9820 * Array to track row selections (by sRecordId) and/or cell selections
9821 * (by {recordId:sRecordId, columnKey:sColumnKey})
9823 * @property _aSelections
9827 _aSelections : null,
9830 * Record instance of the row selection anchor.
9832 * @property _oAnchorRecord
9833 * @type YAHOO.widget.Record
9836 _oAnchorRecord : null,
9839 * Object literal representing cell selection anchor:
9840 * {recordId:sRecordId, columnKey:sColumnKey}.
9842 * @property _oAnchorCell
9846 _oAnchorCell : null,
9849 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
9850 * from all TR elements on the page.
9852 * @method _unselectAllTrEls
9855 _unselectAllTrEls : function() {
9856 var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
9857 Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
9861 * Returns object literal of values that represent the selection trigger. Used
9862 * to determine selection behavior resulting from a key event.
9864 * @method _getSelectionTrigger
9867 _getSelectionTrigger : function() {
9868 var sMode = this.get("selectionMode");
9870 var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
9873 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
9874 oTriggerCell = this.getLastSelectedCell();
9875 // No selected cells found
9880 oTriggerRecord = this.getRecord(oTriggerCell.recordId);
9881 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
9882 elTriggerRow = this.getTrEl(oTriggerRecord);
9883 nTriggerTrIndex = this.getTrIndex(elTriggerRow);
9885 // Selected cell not found on this page
9886 if(nTriggerTrIndex === null) {
9890 oTrigger.record = oTriggerRecord;
9891 oTrigger.recordIndex = nTriggerRecordIndex;
9892 oTrigger.el = this.getTdEl(oTriggerCell);
9893 oTrigger.trIndex = nTriggerTrIndex;
9894 oTrigger.column = this.getColumn(oTriggerCell.columnKey);
9895 oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
9896 oTrigger.cell = oTriggerCell;
9903 oTriggerRecord = this.getLastSelectedRecord();
9904 // No selected rows found
9905 if(!oTriggerRecord) {
9909 // Selected row found, but is it on current page?
9910 oTriggerRecord = this.getRecord(oTriggerRecord);
9911 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
9912 elTriggerRow = this.getTrEl(oTriggerRecord);
9913 nTriggerTrIndex = this.getTrIndex(elTriggerRow);
9915 // Selected row not found on this page
9916 if(nTriggerTrIndex === null) {
9920 oTrigger.record = oTriggerRecord;
9921 oTrigger.recordIndex = nTriggerRecordIndex;
9922 oTrigger.el = elTriggerRow;
9923 oTrigger.trIndex = nTriggerTrIndex;
9931 * Returns object literal of values that represent the selection anchor. Used
9932 * to determine selection behavior resulting from a user event.
9934 * @method _getSelectionAnchor
9935 * @param oTrigger {Object} (Optional) Object literal of selection trigger values
9939 _getSelectionAnchor : function(oTrigger) {
9940 var sMode = this.get("selectionMode");
9942 var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
9945 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
9946 // Validate anchor cell
9947 var oAnchorCell = this._oAnchorCell;
9950 oAnchorCell = this._oAnchorCell = oTrigger.cell;
9956 oAnchorRecord = this._oAnchorCell.record;
9957 nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
9958 nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
9959 // If anchor cell is not on this page...
9960 if(nAnchorTrIndex === null) {
9961 // ...set TR index equal to top TR
9962 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
9965 // ...set TR index equal to bottom TR
9967 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
9971 oAnchor.record = oAnchorRecord;
9972 oAnchor.recordIndex = nAnchorRecordIndex;
9973 oAnchor.trIndex = nAnchorTrIndex;
9974 oAnchor.column = this._oAnchorCell.column;
9975 oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
9976 oAnchor.cell = oAnchorCell;
9981 oAnchorRecord = this._oAnchorRecord;
9982 if(!oAnchorRecord) {
9984 oAnchorRecord = this._oAnchorRecord = oTrigger.record;
9991 nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
9992 nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
9993 // If anchor row is not on this page...
9994 if(nAnchorTrIndex === null) {
9995 // ...set TR index equal to top TR
9996 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
9999 // ...set TR index equal to bottom TR
10001 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10005 oAnchor.record = oAnchorRecord;
10006 oAnchor.recordIndex = nAnchorRecordIndex;
10007 oAnchor.trIndex = nAnchorTrIndex;
10013 * Determines selection behavior resulting from a mouse event when selection mode
10014 * is set to "standard".
10016 * @method _handleStandardSelectionByMouse
10017 * @param oArgs.event {HTMLEvent} Event object.
10018 * @param oArgs.target {HTMLElement} Target element.
10021 _handleStandardSelectionByMouse : function(oArgs) {
10022 var elTarget = oArgs.target;
10024 // Validate target row
10025 var elTargetRow = this.getTrEl(elTarget);
10027 var e = oArgs.event;
10028 var bSHIFT = e.shiftKey;
10029 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10031 var oTargetRecord = this.getRecord(elTargetRow);
10032 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10034 var oAnchor = this._getSelectionAnchor();
10038 // Both SHIFT and CTRL
10039 if(bSHIFT && bCTRL) {
10042 if(this.isSelected(oAnchor.record)) {
10043 // Select all rows between anchor row and target row, including target row
10044 if(oAnchor.recordIndex < nTargetRecordIndex) {
10045 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
10046 if(!this.isSelected(i)) {
10051 // Select all rows between target row and anchor row, including target row
10053 for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
10054 if(!this.isSelected(i)) {
10061 // Unselect all rows between anchor row and target row
10062 if(oAnchor.recordIndex < nTargetRecordIndex) {
10063 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
10064 if(this.isSelected(i)) {
10065 this.unselectRow(i);
10069 // Unselect all rows between target row and anchor row
10071 for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
10072 if(this.isSelected(i)) {
10073 this.unselectRow(i);
10077 // Select the target row
10078 this.selectRow(oTargetRecord);
10084 this._oAnchorRecord = oTargetRecord;
10086 // Toggle selection of target
10087 if(this.isSelected(oTargetRecord)) {
10088 this.unselectRow(oTargetRecord);
10091 this.selectRow(oTargetRecord);
10097 this.unselectAllRows();
10101 // Select all rows between anchor row and target row,
10102 // including the anchor row and target row
10103 if(oAnchor.recordIndex < nTargetRecordIndex) {
10104 for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
10108 // Select all rows between target row and anchor row,
10109 // including the target row and anchor row
10111 for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
10119 this._oAnchorRecord = oTargetRecord;
10121 // Select target row only
10122 this.selectRow(oTargetRecord);
10128 this._oAnchorRecord = oTargetRecord;
10130 // Toggle selection of target
10131 if(this.isSelected(oTargetRecord)) {
10132 this.unselectRow(oTargetRecord);
10135 this.selectRow(oTargetRecord);
10138 // Neither SHIFT nor CTRL
10140 this._handleSingleSelectionByMouse(oArgs);
10147 * Determines selection behavior resulting from a key event when selection mode
10148 * is set to "standard".
10150 * @method _handleStandardSelectionByKey
10151 * @param e {HTMLEvent} Event object.
10154 _handleStandardSelectionByKey : function(e) {
10155 var nKey = Ev.getCharCode(e);
10157 if((nKey == 38) || (nKey == 40)) {
10158 var bSHIFT = e.shiftKey;
10160 // Validate trigger
10161 var oTrigger = this._getSelectionTrigger();
10162 // Arrow selection only works if last selected row is on current page
10170 var oAnchor = this._getSelectionAnchor(oTrigger);
10172 // Determine which direction we're going to
10174 // Selecting down away from anchor row
10175 if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
10176 this.selectRow(this.getNextTrEl(oTrigger.el));
10178 // Selecting up away from anchor row
10179 else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
10180 this.selectRow(this.getPreviousTrEl(oTrigger.el));
10182 // Unselect trigger
10184 this.unselectRow(oTrigger.el);
10188 this._handleSingleSelectionByKey(e);
10194 * Determines selection behavior resulting from a mouse event when selection mode
10195 * is set to "single".
10197 * @method _handleSingleSelectionByMouse
10198 * @param oArgs.event {HTMLEvent} Event object.
10199 * @param oArgs.target {HTMLElement} Target element.
10202 _handleSingleSelectionByMouse : function(oArgs) {
10203 var elTarget = oArgs.target;
10205 // Validate target row
10206 var elTargetRow = this.getTrEl(elTarget);
10208 var oTargetRecord = this.getRecord(elTargetRow);
10211 this._oAnchorRecord = oTargetRecord;
10213 // Select only target
10214 this.unselectAllRows();
10215 this.selectRow(oTargetRecord);
10220 * Determines selection behavior resulting from a key event when selection mode
10221 * is set to "single".
10223 * @method _handleSingleSelectionByKey
10224 * @param e {HTMLEvent} Event object.
10227 _handleSingleSelectionByKey : function(e) {
10228 var nKey = Ev.getCharCode(e);
10230 if((nKey == 38) || (nKey == 40)) {
10231 // Validate trigger
10232 var oTrigger = this._getSelectionTrigger();
10233 // Arrow selection only works if last selected row is on current page
10240 // Determine the new row to select
10242 if(nKey == 38) { // arrow up
10243 elNew = this.getPreviousTrEl(oTrigger.el);
10245 // Validate new row
10246 if(elNew === null) {
10247 //TODO: wrap around to last tr on current page
10248 //elNew = this.getLastTrEl();
10250 //TODO: wrap back to last tr of previous page
10252 // Top row selection is sticky
10253 elNew = this.getFirstTrEl();
10256 else if(nKey == 40) { // arrow down
10257 elNew = this.getNextTrEl(oTrigger.el);
10259 // Validate new row
10260 if(elNew === null) {
10261 //TODO: wrap around to first tr on current page
10262 //elNew = this.getFirstTrEl();
10264 //TODO: wrap forward to first tr of previous page
10266 // Bottom row selection is sticky
10267 elNew = this.getLastTrEl();
10271 // Unselect all rows
10272 this.unselectAllRows();
10274 // Select the new row
10275 this.selectRow(elNew);
10278 this._oAnchorRecord = this.getRecord(elNew);
10283 * Determines selection behavior resulting from a mouse event when selection mode
10284 * is set to "cellblock".
10286 * @method _handleCellBlockSelectionByMouse
10287 * @param oArgs.event {HTMLEvent} Event object.
10288 * @param oArgs.target {HTMLElement} Target element.
10291 _handleCellBlockSelectionByMouse : function(oArgs) {
10292 var elTarget = oArgs.target;
10294 // Validate target cell
10295 var elTargetCell = this.getTdEl(elTarget);
10297 var e = oArgs.event;
10298 var bSHIFT = e.shiftKey;
10299 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10301 var elTargetRow = this.getTrEl(elTargetCell);
10302 var nTargetTrIndex = this.getTrIndex(elTargetRow);
10303 var oTargetColumn = this.getColumn(elTargetCell);
10304 var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10305 var oTargetRecord = this.getRecord(elTargetRow);
10306 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10307 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10309 var oAnchor = this._getSelectionAnchor();
10311 var allRows = this.getTbodyEl().rows;
10312 var startIndex, endIndex, currentRow, i, j;
10314 // Both SHIFT and CTRL
10315 if(bSHIFT && bCTRL) {
10319 // Anchor is selected
10320 if(this.isSelected(oAnchor.cell)) {
10321 // All cells are on the same row
10322 if(oAnchor.recordIndex === nTargetRecordIndex) {
10323 // Select all cells between anchor cell and target cell, including target cell
10324 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10325 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10326 this.selectCell(elTargetRow.cells[i]);
10329 // Select all cells between target cell and anchor cell, including target cell
10330 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10331 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10332 this.selectCell(elTargetRow.cells[i]);
10336 // Anchor row is above target row
10337 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10338 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10339 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10341 // Select all cells from startIndex to endIndex on rows between anchor row and target row
10342 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10343 for(j=startIndex; j<=endIndex; j++) {
10344 this.selectCell(allRows[i].cells[j]);
10348 // Anchor row is below target row
10350 startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
10351 endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
10353 // Select all cells from startIndex to endIndex on rows between target row and anchor row
10354 for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
10355 for(j=endIndex; j>=startIndex; j--) {
10356 this.selectCell(allRows[i].cells[j]);
10361 // Anchor cell is unselected
10363 // All cells are on the same row
10364 if(oAnchor.recordIndex === nTargetRecordIndex) {
10365 // Unselect all cells between anchor cell and target cell
10366 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10367 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10368 this.unselectCell(elTargetRow.cells[i]);
10371 // Select all cells between target cell and anchor cell
10372 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10373 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10374 this.unselectCell(elTargetRow.cells[i]);
10378 // Anchor row is above target row
10379 if(oAnchor.recordIndex < nTargetRecordIndex) {
10380 // Unselect all cells from anchor cell to target cell
10381 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10382 currentRow = allRows[i];
10383 for(j=0; j<currentRow.cells.length; j++) {
10384 // This is the anchor row, only unselect cells after the anchor cell
10385 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
10386 if(j>oAnchor.colKeyIndex) {
10387 this.unselectCell(currentRow.cells[j]);
10390 // This is the target row, only unelect cells before the target cell
10391 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
10392 if(j<nTargetColKeyIndex) {
10393 this.unselectCell(currentRow.cells[j]);
10396 // Unselect all cells on this row
10398 this.unselectCell(currentRow.cells[j]);
10403 // Anchor row is below target row
10405 // Unselect all cells from target cell to anchor cell
10406 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10407 currentRow = allRows[i];
10408 for(j=0; j<currentRow.cells.length; j++) {
10409 // This is the target row, only unselect cells after the target cell
10410 if(currentRow.sectionRowIndex == nTargetTrIndex) {
10411 if(j>nTargetColKeyIndex) {
10412 this.unselectCell(currentRow.cells[j]);
10415 // This is the anchor row, only unselect cells before the anchor cell
10416 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10417 if(j<oAnchor.colKeyIndex) {
10418 this.unselectCell(currentRow.cells[j]);
10421 // Unselect all cells on this row
10423 this.unselectCell(currentRow.cells[j]);
10429 // Select the target cell
10430 this.selectCell(elTargetCell);
10436 this._oAnchorCell = oTargetCell;
10438 // Toggle selection of target
10439 if(this.isSelected(oTargetCell)) {
10440 this.unselectCell(oTargetCell);
10443 this.selectCell(oTargetCell);
10450 this.unselectAllCells();
10454 // All cells are on the same row
10455 if(oAnchor.recordIndex === nTargetRecordIndex) {
10456 // Select all cells between anchor cell and target cell,
10457 // including the anchor cell and target cell
10458 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10459 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
10460 this.selectCell(elTargetRow.cells[i]);
10463 // Select all cells between target cell and anchor cell
10464 // including the target cell and anchor cell
10465 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10466 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
10467 this.selectCell(elTargetRow.cells[i]);
10471 // Anchor row is above target row
10472 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10473 // Select the cellblock from anchor cell to target cell
10474 // including the anchor cell and the target cell
10475 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10476 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10478 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10479 for(j=startIndex; j<=endIndex; j++) {
10480 this.selectCell(allRows[i].cells[j]);
10484 // Anchor row is below target row
10486 // Select the cellblock from target cell to anchor cell
10487 // including the target cell and the anchor cell
10488 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10489 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10491 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10492 for(j=startIndex; j<=endIndex; j++) {
10493 this.selectCell(allRows[i].cells[j]);
10501 this._oAnchorCell = oTargetCell;
10503 // Select target only
10504 this.selectCell(oTargetCell);
10511 this._oAnchorCell = oTargetCell;
10513 // Toggle selection of target
10514 if(this.isSelected(oTargetCell)) {
10515 this.unselectCell(oTargetCell);
10518 this.selectCell(oTargetCell);
10522 // Neither SHIFT nor CTRL
10524 this._handleSingleCellSelectionByMouse(oArgs);
10530 * Determines selection behavior resulting from a key event when selection mode
10531 * is set to "cellblock".
10533 * @method _handleCellBlockSelectionByKey
10534 * @param e {HTMLEvent} Event object.
10537 _handleCellBlockSelectionByKey : function(e) {
10538 var nKey = Ev.getCharCode(e);
10539 var bSHIFT = e.shiftKey;
10540 if((nKey == 9) || !bSHIFT) {
10541 this._handleSingleCellSelectionByKey(e);
10545 if((nKey > 36) && (nKey < 41)) {
10546 // Validate trigger
10547 var oTrigger = this._getSelectionTrigger();
10548 // Arrow selection only works if last selected row is on current page
10556 var oAnchor = this._getSelectionAnchor(oTrigger);
10558 var i, startIndex, endIndex, elNew, elNewRow;
10559 var allRows = this.getTbodyEl().rows;
10560 var elThisRow = oTrigger.el.parentNode;
10562 // Determine which direction we're going to
10564 if(nKey == 40) { // arrow down
10565 // Selecting away from anchor cell
10566 if(oAnchor.recordIndex <= oTrigger.recordIndex) {
10567 // Select the horiz block on the next row...
10568 // ...making sure there is room below the trigger row
10569 elNewRow = this.getNextTrEl(oTrigger.el);
10571 startIndex = oAnchor.colKeyIndex;
10572 endIndex = oTrigger.colKeyIndex;
10574 if(startIndex > endIndex) {
10575 for(i=startIndex; i>=endIndex; i--) {
10576 elNew = elNewRow.cells[i];
10577 this.selectCell(elNew);
10582 for(i=startIndex; i<=endIndex; i++) {
10583 elNew = elNewRow.cells[i];
10584 this.selectCell(elNew);
10589 // Unselecting towards anchor cell
10591 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10592 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10593 // Unselect the horiz block on this row towards the next row
10594 for(i=startIndex; i<=endIndex; i++) {
10595 this.unselectCell(elThisRow.cells[i]);
10600 else if(nKey == 38) {
10601 // Selecting away from anchor cell
10602 if(oAnchor.recordIndex >= oTrigger.recordIndex) {
10603 // Select the horiz block on the previous row...
10604 // ...making sure there is room
10605 elNewRow = this.getPreviousTrEl(oTrigger.el);
10607 // Select in order from anchor to trigger...
10608 startIndex = oAnchor.colKeyIndex;
10609 endIndex = oTrigger.colKeyIndex;
10611 if(startIndex > endIndex) {
10612 for(i=startIndex; i>=endIndex; i--) {
10613 elNew = elNewRow.cells[i];
10614 this.selectCell(elNew);
10619 for(i=startIndex; i<=endIndex; i++) {
10620 elNew = elNewRow.cells[i];
10621 this.selectCell(elNew);
10626 // Unselecting towards anchor cell
10628 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10629 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10630 // Unselect the horiz block on this row towards the previous row
10631 for(i=startIndex; i<=endIndex; i++) {
10632 this.unselectCell(elThisRow.cells[i]);
10637 else if(nKey == 39) {
10638 // Selecting away from anchor cell
10639 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
10640 // Select the next vert block to the right...
10641 // ...making sure there is room
10642 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
10643 // Select in order from anchor to trigger...
10644 startIndex = oAnchor.trIndex;
10645 endIndex = oTrigger.trIndex;
10647 if(startIndex > endIndex) {
10648 for(i=startIndex; i>=endIndex; i--) {
10649 elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10650 this.selectCell(elNew);
10655 for(i=startIndex; i<=endIndex; i++) {
10656 elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10657 this.selectCell(elNew);
10662 // Unselecting towards anchor cell
10664 // Unselect the vert block on this column towards the right
10665 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10666 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10667 for(i=startIndex; i<=endIndex; i++) {
10668 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10673 else if(nKey == 37) {
10674 // Selecting away from anchor cell
10675 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
10676 //Select the previous vert block to the left
10677 if(oTrigger.colKeyIndex > 0) {
10678 // Select in order from anchor to trigger...
10679 startIndex = oAnchor.trIndex;
10680 endIndex = oTrigger.trIndex;
10682 if(startIndex > endIndex) {
10683 for(i=startIndex; i>=endIndex; i--) {
10684 elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10685 this.selectCell(elNew);
10690 for(i=startIndex; i<=endIndex; i++) {
10691 elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10692 this.selectCell(elNew);
10697 // Unselecting towards anchor cell
10699 // Unselect the vert block on this column towards the left
10700 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10701 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10702 for(i=startIndex; i<=endIndex; i++) {
10703 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10711 * Determines selection behavior resulting from a mouse event when selection mode
10712 * is set to "cellrange".
10714 * @method _handleCellRangeSelectionByMouse
10715 * @param oArgs.event {HTMLEvent} Event object.
10716 * @param oArgs.target {HTMLElement} Target element.
10719 _handleCellRangeSelectionByMouse : function(oArgs) {
10720 var elTarget = oArgs.target;
10722 // Validate target cell
10723 var elTargetCell = this.getTdEl(elTarget);
10725 var e = oArgs.event;
10726 var bSHIFT = e.shiftKey;
10727 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10729 var elTargetRow = this.getTrEl(elTargetCell);
10730 var nTargetTrIndex = this.getTrIndex(elTargetRow);
10731 var oTargetColumn = this.getColumn(elTargetCell);
10732 var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10733 var oTargetRecord = this.getRecord(elTargetRow);
10734 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10735 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10737 var oAnchor = this._getSelectionAnchor();
10739 var allRows = this.getTbodyEl().rows;
10740 var currentRow, i, j;
10742 // Both SHIFT and CTRL
10743 if(bSHIFT && bCTRL) {
10747 // Anchor is selected
10748 if(this.isSelected(oAnchor.cell)) {
10749 // All cells are on the same row
10750 if(oAnchor.recordIndex === nTargetRecordIndex) {
10751 // Select all cells between anchor cell and target cell, including target cell
10752 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10753 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10754 this.selectCell(elTargetRow.cells[i]);
10757 // Select all cells between target cell and anchor cell, including target cell
10758 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10759 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10760 this.selectCell(elTargetRow.cells[i]);
10764 // Anchor row is above target row
10765 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10766 // Select all cells on anchor row from anchor cell to the end of the row
10767 for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
10768 this.selectCell(elTargetRow.cells[i]);
10771 // Select all cells on all rows between anchor row and target row
10772 for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
10773 for(j=0; j<allRows[i].cells.length; j++){
10774 this.selectCell(allRows[i].cells[j]);
10778 // Select all cells on target row from first cell to the target cell
10779 for(i=0; i<=nTargetColKeyIndex; i++) {
10780 this.selectCell(elTargetRow.cells[i]);
10783 // Anchor row is below target row
10785 // Select all cells on target row from target cell to the end of the row
10786 for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
10787 this.selectCell(elTargetRow.cells[i]);
10790 // Select all cells on all rows between target row and anchor row
10791 for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
10792 for(j=0; j<allRows[i].cells.length; j++){
10793 this.selectCell(allRows[i].cells[j]);
10797 // Select all cells on anchor row from first cell to the anchor cell
10798 for(i=0; i<oAnchor.colKeyIndex; i++) {
10799 this.selectCell(elTargetRow.cells[i]);
10803 // Anchor cell is unselected
10805 // All cells are on the same row
10806 if(oAnchor.recordIndex === nTargetRecordIndex) {
10807 // Unselect all cells between anchor cell and target cell
10808 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10809 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10810 this.unselectCell(elTargetRow.cells[i]);
10813 // Select all cells between target cell and anchor cell
10814 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10815 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10816 this.unselectCell(elTargetRow.cells[i]);
10820 // Anchor row is above target row
10821 if(oAnchor.recordIndex < nTargetRecordIndex) {
10822 // Unselect all cells from anchor cell to target cell
10823 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10824 currentRow = allRows[i];
10825 for(j=0; j<currentRow.cells.length; j++) {
10826 // This is the anchor row, only unselect cells after the anchor cell
10827 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
10828 if(j>oAnchor.colKeyIndex) {
10829 this.unselectCell(currentRow.cells[j]);
10832 // This is the target row, only unelect cells before the target cell
10833 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
10834 if(j<nTargetColKeyIndex) {
10835 this.unselectCell(currentRow.cells[j]);
10838 // Unselect all cells on this row
10840 this.unselectCell(currentRow.cells[j]);
10845 // Anchor row is below target row
10847 // Unselect all cells from target cell to anchor cell
10848 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10849 currentRow = allRows[i];
10850 for(j=0; j<currentRow.cells.length; j++) {
10851 // This is the target row, only unselect cells after the target cell
10852 if(currentRow.sectionRowIndex == nTargetTrIndex) {
10853 if(j>nTargetColKeyIndex) {
10854 this.unselectCell(currentRow.cells[j]);
10857 // This is the anchor row, only unselect cells before the anchor cell
10858 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10859 if(j<oAnchor.colKeyIndex) {
10860 this.unselectCell(currentRow.cells[j]);
10863 // Unselect all cells on this row
10865 this.unselectCell(currentRow.cells[j]);
10871 // Select the target cell
10872 this.selectCell(elTargetCell);
10878 this._oAnchorCell = oTargetCell;
10880 // Toggle selection of target
10881 if(this.isSelected(oTargetCell)) {
10882 this.unselectCell(oTargetCell);
10885 this.selectCell(oTargetCell);
10892 this.unselectAllCells();
10896 // All cells are on the same row
10897 if(oAnchor.recordIndex === nTargetRecordIndex) {
10898 // Select all cells between anchor cell and target cell,
10899 // including the anchor cell and target cell
10900 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10901 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
10902 this.selectCell(elTargetRow.cells[i]);
10905 // Select all cells between target cell and anchor cell
10906 // including the target cell and anchor cell
10907 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10908 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
10909 this.selectCell(elTargetRow.cells[i]);
10913 // Anchor row is above target row
10914 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10915 // Select all cells from anchor cell to target cell
10916 // including the anchor cell and target cell
10917 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10918 currentRow = allRows[i];
10919 for(j=0; j<currentRow.cells.length; j++) {
10920 // This is the anchor row, only select the anchor cell and after
10921 if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10922 if(j>=oAnchor.colKeyIndex) {
10923 this.selectCell(currentRow.cells[j]);
10926 // This is the target row, only select the target cell and before
10927 else if(currentRow.sectionRowIndex == nTargetTrIndex) {
10928 if(j<=nTargetColKeyIndex) {
10929 this.selectCell(currentRow.cells[j]);
10932 // Select all cells on this row
10934 this.selectCell(currentRow.cells[j]);
10939 // Anchor row is below target row
10941 // Select all cells from target cell to anchor cell,
10942 // including the target cell and anchor cell
10943 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10944 currentRow = allRows[i];
10945 for(j=0; j<currentRow.cells.length; j++) {
10946 // This is the target row, only select the target cell and after
10947 if(currentRow.sectionRowIndex == nTargetTrIndex) {
10948 if(j>=nTargetColKeyIndex) {
10949 this.selectCell(currentRow.cells[j]);
10952 // This is the anchor row, only select the anchor cell and before
10953 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10954 if(j<=oAnchor.colKeyIndex) {
10955 this.selectCell(currentRow.cells[j]);
10958 // Select all cells on this row
10960 this.selectCell(currentRow.cells[j]);
10969 this._oAnchorCell = oTargetCell;
10971 // Select target only
10972 this.selectCell(oTargetCell);
10981 this._oAnchorCell = oTargetCell;
10983 // Toggle selection of target
10984 if(this.isSelected(oTargetCell)) {
10985 this.unselectCell(oTargetCell);
10988 this.selectCell(oTargetCell);
10992 // Neither SHIFT nor CTRL
10994 this._handleSingleCellSelectionByMouse(oArgs);
11000 * Determines selection behavior resulting from a key event when selection mode
11001 * is set to "cellrange".
11003 * @method _handleCellRangeSelectionByKey
11004 * @param e {HTMLEvent} Event object.
11007 _handleCellRangeSelectionByKey : function(e) {
11008 var nKey = Ev.getCharCode(e);
11009 var bSHIFT = e.shiftKey;
11010 if((nKey == 9) || !bSHIFT) {
11011 this._handleSingleCellSelectionByKey(e);
11015 if((nKey > 36) && (nKey < 41)) {
11016 // Validate trigger
11017 var oTrigger = this._getSelectionTrigger();
11018 // Arrow selection only works if last selected row is on current page
11026 var oAnchor = this._getSelectionAnchor(oTrigger);
11028 var i, elNewRow, elNew;
11029 var allRows = this.getTbodyEl().rows;
11030 var elThisRow = oTrigger.el.parentNode;
11034 elNewRow = this.getNextTrEl(oTrigger.el);
11036 // Selecting away from anchor cell
11037 if(oAnchor.recordIndex <= oTrigger.recordIndex) {
11038 // Select all cells to the end of this row
11039 for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){
11040 elNew = elThisRow.cells[i];
11041 this.selectCell(elNew);
11044 // Select some of the cells on the next row down
11046 for(i=0; i<=oTrigger.colKeyIndex; i++){
11047 elNew = elNewRow.cells[i];
11048 this.selectCell(elNew);
11052 // Unselecting towards anchor cell
11054 // Unselect all cells to the end of this row
11055 for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){
11056 this.unselectCell(elThisRow.cells[i]);
11059 // Unselect some of the cells on the next row down
11061 for(i=0; i<oTrigger.colKeyIndex; i++){
11062 this.unselectCell(elNewRow.cells[i]);
11068 else if(nKey == 38) {
11069 elNewRow = this.getPreviousTrEl(oTrigger.el);
11071 // Selecting away from anchor cell
11072 if(oAnchor.recordIndex >= oTrigger.recordIndex) {
11073 // Select all the cells to the beginning of this row
11074 for(i=oTrigger.colKeyIndex-1; i>-1; i--){
11075 elNew = elThisRow.cells[i];
11076 this.selectCell(elNew);
11079 // Select some of the cells from the end of the previous row
11081 for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){
11082 elNew = elNewRow.cells[i];
11083 this.selectCell(elNew);
11087 // Unselecting towards anchor cell
11089 // Unselect all the cells to the beginning of this row
11090 for(i=oTrigger.colKeyIndex; i>-1; i--){
11091 this.unselectCell(elThisRow.cells[i]);
11094 // Unselect some of the cells from the end of the previous row
11096 for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){
11097 this.unselectCell(elNewRow.cells[i]);
11103 else if(nKey == 39) {
11104 elNewRow = this.getNextTrEl(oTrigger.el);
11106 // Selecting away from anchor cell
11107 if(oAnchor.recordIndex < oTrigger.recordIndex) {
11108 // Select the next cell to the right
11109 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11110 elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11111 this.selectCell(elNew);
11113 // Select the first cell of the next row
11114 else if(elNewRow) {
11115 elNew = elNewRow.cells[0];
11116 this.selectCell(elNew);
11119 // Unselecting towards anchor cell
11120 else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11121 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11123 // Unselect this cell towards the right
11124 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11126 // Unselect this cells towards the first cell of the next row
11130 // Anchor is on this row
11132 // Selecting away from anchor
11133 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
11134 // Select the next cell to the right
11135 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11136 elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11137 this.selectCell(elNew);
11139 // Select the first cell on the next row
11140 else if(oTrigger.trIndex < allRows.length-1){
11141 elNew = elNewRow.cells[0];
11142 this.selectCell(elNew);
11145 // Unselecting towards anchor
11147 // Unselect this cell towards the right
11148 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11153 else if(nKey == 37) {
11154 elNewRow = this.getPreviousTrEl(oTrigger.el);
11156 // Unselecting towards the anchor
11157 if(oAnchor.recordIndex < oTrigger.recordIndex) {
11158 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11160 // Unselect this cell towards the left
11161 if(oTrigger.colKeyIndex > 0) {
11163 // Unselect this cell towards the last cell of the previous row
11167 // Selecting towards the anchor
11168 else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11169 // Select the next cell to the left
11170 if(oTrigger.colKeyIndex > 0) {
11171 elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11172 this.selectCell(elNew);
11174 // Select the last cell of the previous row
11175 else if(oTrigger.trIndex > 0){
11176 elNew = elNewRow.cells[elNewRow.cells.length-1];
11177 this.selectCell(elNew);
11180 // Anchor is on this row
11182 // Selecting away from anchor cell
11183 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
11184 // Select the next cell to the left
11185 if(oTrigger.colKeyIndex > 0) {
11186 elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11187 this.selectCell(elNew);
11189 // Select the last cell of the previous row
11190 else if(oTrigger.trIndex > 0){
11191 elNew = elNewRow.cells[elNewRow.cells.length-1];
11192 this.selectCell(elNew);
11195 // Unselecting towards anchor cell
11197 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11199 // Unselect this cell towards the left
11200 if(oTrigger.colKeyIndex > 0) {
11202 // Unselect this cell towards the last cell of the previous row
11212 * Determines selection behavior resulting from a mouse event when selection mode
11213 * is set to "singlecell".
11215 * @method _handleSingleCellSelectionByMouse
11216 * @param oArgs.event {HTMLEvent} Event object.
11217 * @param oArgs.target {HTMLElement} Target element.
11220 _handleSingleCellSelectionByMouse : function(oArgs) {
11221 var elTarget = oArgs.target;
11223 // Validate target cell
11224 var elTargetCell = this.getTdEl(elTarget);
11226 var elTargetRow = this.getTrEl(elTargetCell);
11227 var oTargetRecord = this.getRecord(elTargetRow);
11228 var oTargetColumn = this.getColumn(elTargetCell);
11229 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
11232 this._oAnchorCell = oTargetCell;
11234 // Select only target
11235 this.unselectAllCells();
11236 this.selectCell(oTargetCell);
11241 * Determines selection behavior resulting from a key event when selection mode
11242 * is set to "singlecell".
11244 * @method _handleSingleCellSelectionByKey
11245 * @param e {HTMLEvent} Event object.
11248 _handleSingleCellSelectionByKey : function(e) {
11249 var nKey = Ev.getCharCode(e);
11250 if((nKey == 9) || ((nKey > 36) && (nKey < 41))) {
11251 var bSHIFT = e.shiftKey;
11253 // Validate trigger
11254 var oTrigger = this._getSelectionTrigger();
11255 // Arrow selection only works if last selected row is on current page
11260 // Determine the new cell to select
11262 if(nKey == 40) { // Arrow down
11263 elNew = this.getBelowTdEl(oTrigger.el);
11265 // Validate new cell
11266 if(elNew === null) {
11267 //TODO: wrap around to first tr on current page
11269 //TODO: wrap forward to first tr of next page
11271 // Bottom selection is sticky
11272 elNew = oTrigger.el;
11275 else if(nKey == 38) { // Arrow up
11276 elNew = this.getAboveTdEl(oTrigger.el);
11278 // Validate new cell
11279 if(elNew === null) {
11280 //TODO: wrap around to last tr on current page
11282 //TODO: wrap back to last tr of previous page
11284 // Top selection is sticky
11285 elNew = oTrigger.el;
11288 else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab
11289 elNew = this.getNextTdEl(oTrigger.el);
11291 // Validate new cell
11292 if(elNew === null) {
11293 //TODO: wrap around to first td on current page
11295 //TODO: wrap forward to first td of next page
11297 // Top-left selection is sticky, and release TAB focus
11298 //elNew = oTrigger.el;
11302 else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab
11303 elNew = this.getPreviousTdEl(oTrigger.el);
11305 // Validate new cell
11306 if(elNew === null) {
11307 //TODO: wrap around to last td on current page
11309 //TODO: wrap back to last td of previous page
11311 // Bottom-right selection is sticky, and release TAB focus
11312 //elNew = oTrigger.el;
11319 // Unselect all cells
11320 this.unselectAllCells();
11322 // Select the new cell
11323 this.selectCell(elNew);
11326 this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)};
11331 * Returns array of selected TR elements on the page.
11333 * @method getSelectedTrEls
11334 * @return {HTMLElement[]} Array of selected TR elements.
11336 getSelectedTrEls : function() {
11337 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
11341 * Sets given row to the selected state.
11343 * @method selectRow
11344 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11345 * reference or ID string, Record instance, or RecordSet position index.
11347 selectRow : function(row) {
11348 var oRecord, elRow;
11350 if(row instanceof YAHOO.widget.Record) {
11351 oRecord = this._oRecordSet.getRecord(row);
11352 elRow = this.getTrEl(oRecord);
11354 else if(lang.isNumber(row)) {
11355 oRecord = this.getRecord(row);
11356 elRow = this.getTrEl(oRecord);
11359 elRow = this.getTrEl(row);
11360 oRecord = this.getRecord(elRow);
11364 // Update selection trackers
11365 var tracker = this._aSelections || [];
11366 var sRecordId = oRecord.getId();
11369 // Remove if already there:
11370 // Use Array.indexOf if available...
11371 /*if(tracker.indexOf && (tracker.indexOf(sRecordId) > -1)) {
11372 tracker.splice(tracker.indexOf(sRecordId),1);
11374 if(tracker.indexOf) {
11375 index = tracker.indexOf(sRecordId);
11378 // ...or do it the old-fashioned way
11380 for(var j=tracker.length-1; j>-1; j--) {
11381 if(tracker[j] === sRecordId){
11388 tracker.splice(index,1);
11392 tracker.push(sRecordId);
11393 this._aSelections = tracker;
11396 if(!this._oAnchorRecord) {
11397 this._oAnchorRecord = oRecord;
11402 Dom.addClass(elRow, DT.CLASS_SELECTED);
11405 this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
11412 * Sets given row to the unselected state.
11414 * @method unselectRow
11415 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11416 * reference or ID string, Record instance, or RecordSet position index.
11418 unselectRow : function(row) {
11419 var elRow = this.getTrEl(row);
11422 if(row instanceof YAHOO.widget.Record) {
11423 oRecord = this._oRecordSet.getRecord(row);
11425 else if(lang.isNumber(row)) {
11426 oRecord = this.getRecord(row);
11429 oRecord = this.getRecord(elRow);
11433 // Update selection trackers
11434 var tracker = this._aSelections || [];
11435 var sRecordId = oRecord.getId();
11438 // Use Array.indexOf if available...
11439 if(tracker.indexOf) {
11440 index = tracker.indexOf(sRecordId);
11442 // ...or do it the old-fashioned way
11444 for(var j=tracker.length-1; j>-1; j--) {
11445 if(tracker[j] === sRecordId){
11453 tracker.splice(index,1);
11454 this._aSelections = tracker;
11457 Dom.removeClass(elRow, DT.CLASS_SELECTED);
11459 this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
11467 * Clears out all row selections.
11469 * @method unselectAllRows
11471 unselectAllRows : function() {
11472 // Remove all rows from tracker
11473 var tracker = this._aSelections || [],
11476 for(var j=tracker.length-1; j>-1; j--) {
11477 if(lang.isString(tracker[j])){
11478 recId = tracker.splice(j,1);
11479 removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId);
11484 this._aSelections = tracker;
11487 this._unselectAllTrEls();
11489 this.fireEvent("unselectAllRowsEvent", {records: removed});
11493 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
11494 * from all TD elements in the internal tracker.
11496 * @method _unselectAllTdEls
11499 _unselectAllTdEls : function() {
11500 var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11501 Dom.removeClass(selectedCells, DT.CLASS_SELECTED);
11505 * Returns array of selected TD elements on the page.
11507 * @method getSelectedTdEls
11508 * @return {HTMLElement[]} Array of selected TD elements.
11510 getSelectedTdEls : function() {
11511 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11515 * Sets given cell to the selected state.
11517 * @method selectCell
11518 * @param cell {HTMLElement | String} DOM element reference or ID string
11519 * to DataTable page element or RecordSet index.
11521 selectCell : function(cell) {
11522 //TODO: accept {record} in selectCell()
11523 var elCell = this.getTdEl(cell);
11526 var oRecord = this.getRecord(elCell);
11527 var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11529 if(oRecord && sColumnKey) {
11531 var tracker = this._aSelections || [];
11532 var sRecordId = oRecord.getId();
11535 for(var j=tracker.length-1; j>-1; j--) {
11536 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11537 tracker.splice(j,1);
11543 tracker.push({recordId:sRecordId, columnKey:sColumnKey});
11546 this._aSelections = tracker;
11547 if(!this._oAnchorCell) {
11548 this._oAnchorCell = {record:oRecord, column:this.getColumn(sColumnKey)};
11552 Dom.addClass(elCell, DT.CLASS_SELECTED);
11554 this.fireEvent("cellSelectEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key: this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11561 * Sets given cell to the unselected state.
11563 * @method unselectCell
11564 * @param cell {HTMLElement | String} DOM element reference or ID string
11565 * to DataTable page element or RecordSet index.
11567 unselectCell : function(cell) {
11568 var elCell = this.getTdEl(cell);
11571 var oRecord = this.getRecord(elCell);
11572 var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11574 if(oRecord && sColumnKey) {
11576 var tracker = this._aSelections || [];
11577 var id = oRecord.getId();
11580 for(var j=tracker.length-1; j>-1; j--) {
11581 if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){
11582 // Remove from tracker
11583 tracker.splice(j,1);
11586 this._aSelections = tracker;
11589 Dom.removeClass(elCell, DT.CLASS_SELECTED);
11591 this.fireEvent("cellUnselectEvent", {record:oRecord, column: this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11600 * Clears out all cell selections.
11602 * @method unselectAllCells
11604 unselectAllCells : function() {
11605 // Remove all cells from tracker
11606 var tracker = this._aSelections || [];
11607 for(var j=tracker.length-1; j>-1; j--) {
11608 if(lang.isObject(tracker[j])){
11609 tracker.splice(j,1);
11614 this._aSelections = tracker;
11617 this._unselectAllTdEls();
11619 //TODO: send data to unselectAllCellsEvent handler
11620 this.fireEvent("unselectAllCellsEvent");
11624 * Returns true if given item is selected, false otherwise.
11626 * @method isSelected
11627 * @param o {String | HTMLElement | YAHOO.widget.Record | Number
11628 * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by
11629 * reference or ID string, a Record instance, a RecordSet position index,
11630 * or an object literal representation
11632 * @return {Boolean} True if item is selected.
11634 isSelected : function(o) {
11635 if(o && (o.ownerDocument == document)) {
11636 return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED));
11639 var oRecord, sRecordId, j;
11640 var tracker = this._aSelections;
11641 if(tracker && tracker.length > 0) {
11642 // Looking for a Record?
11643 if(o instanceof YAHOO.widget.Record) {
11646 else if(lang.isNumber(o)) {
11647 oRecord = this.getRecord(o);
11650 sRecordId = oRecord.getId();
11653 // Use Array.indexOf if available...
11654 if(tracker.indexOf) {
11655 if(tracker.indexOf(sRecordId) > -1) {
11659 // ...or do it the old-fashioned way
11661 for(j=tracker.length-1; j>-1; j--) {
11662 if(tracker[j] === sRecordId){
11668 // Looking for a cell
11669 else if(o.record && o.column){
11670 sRecordId = o.record.getId();
11671 var sColumnKey = o.column.getKey();
11673 for(j=tracker.length-1; j>-1; j--) {
11674 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11685 * Returns selected rows as an array of Record IDs.
11687 * @method getSelectedRows
11688 * @return {String[]} Array of selected rows by Record ID.
11690 getSelectedRows : function() {
11691 var aSelectedRows = [];
11692 var tracker = this._aSelections || [];
11693 for(var j=0; j<tracker.length; j++) {
11694 if(lang.isString(tracker[j])){
11695 aSelectedRows.push(tracker[j]);
11698 return aSelectedRows;
11702 * Returns selected cells as an array of object literals:
11703 * {recordId:sRecordId, columnKey:sColumnKey}.
11705 * @method getSelectedCells
11706 * @return {Object[]} Array of selected cells by Record ID and Column ID.
11708 getSelectedCells : function() {
11709 var aSelectedCells = [];
11710 var tracker = this._aSelections || [];
11711 for(var j=0; j<tracker.length; j++) {
11712 if(tracker[j] && lang.isObject(tracker[j])){
11713 aSelectedCells.push(tracker[j]);
11716 return aSelectedCells;
11720 * Returns last selected Record ID.
11722 * @method getLastSelectedRecord
11723 * @return {String} Record ID of last selected row.
11725 getLastSelectedRecord : function() {
11726 var tracker = this._aSelections;
11727 if(tracker && tracker.length > 0) {
11728 for(var i=tracker.length-1; i>-1; i--) {
11729 if(lang.isString(tracker[i])){
11737 * Returns last selected cell as an object literal:
11738 * {recordId:sRecordId, columnKey:sColumnKey}.
11740 * @method getLastSelectedCell
11741 * @return {Object} Object literal representation of a cell.
11743 getLastSelectedCell : function() {
11744 var tracker = this._aSelections;
11745 if(tracker && tracker.length > 0) {
11746 for(var i=tracker.length-1; i>-1; i--) {
11747 if(tracker[i].recordId && tracker[i].columnKey){
11755 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
11757 * @method highlightRow
11758 * @param row {HTMLElement | String} DOM element reference or ID string.
11760 highlightRow : function(row) {
11761 var elRow = this.getTrEl(row);
11764 // Make sure previous row is unhighlighted
11765 /* if(this._sLastHighlightedTrElId) {
11766 Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED);
11768 var oRecord = this.getRecord(elRow);
11769 Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED);
11770 //this._sLastHighlightedTrElId = elRow.id;
11771 this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
11777 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
11779 * @method unhighlightRow
11780 * @param row {HTMLElement | String} DOM element reference or ID string.
11782 unhighlightRow : function(row) {
11783 var elRow = this.getTrEl(row);
11786 var oRecord = this.getRecord(elRow);
11787 Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED);
11788 this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
11794 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
11796 * @method highlightCell
11797 * @param cell {HTMLElement | String} DOM element reference or ID string.
11799 highlightCell : function(cell) {
11800 var elCell = this.getTdEl(cell);
11803 // Make sure previous cell is unhighlighted
11804 if(this._elLastHighlightedTd) {
11805 this.unhighlightCell(this._elLastHighlightedTd);
11808 var oRecord = this.getRecord(elCell);
11809 var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11810 Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED);
11811 this._elLastHighlightedTd = elCell;
11812 this.fireEvent("cellHighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11818 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
11820 * @method unhighlightCell
11821 * @param cell {HTMLElement | String} DOM element reference or ID string.
11823 unhighlightCell : function(cell) {
11824 var elCell = this.getTdEl(cell);
11827 var oRecord = this.getRecord(elCell);
11828 Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED);
11829 this._elLastHighlightedTd = null;
11830 this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11882 * Returns current CellEditor instance, or null.
11883 * @method getCellEditor
11884 * @return {YAHOO.widget.CellEditor} CellEditor instance.
11886 getCellEditor : function() {
11887 return this._oCellEditor;
11892 * Activates and shows CellEditor instance for the given cell while deactivating and
11893 * canceling previous CellEditor. It is baked into DataTable that only one CellEditor
11894 * can be active at any given time.
11896 * @method showCellEditor
11897 * @param elCell {HTMLElement | String} Cell to edit.
11899 showCellEditor : function(elCell, oRecord, oColumn) {
11900 // Get a particular CellEditor
11901 elCell = this.getTdEl(elCell);
11903 oColumn = this.getColumn(elCell);
11904 if(oColumn && oColumn.editor) {
11905 var oCellEditor = this._oCellEditor;
11906 // Clean up active CellEditor
11908 if(this._oCellEditor.cancel) {
11909 this._oCellEditor.cancel();
11911 else if(oCellEditor.isActive) {
11912 this.cancelCellEditor();
11916 if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) {
11918 oCellEditor = oColumn.editor;
11919 var ok = oCellEditor.attach(this, elCell);
11921 oCellEditor.move();
11922 ok = this.doBeforeShowCellEditor(oCellEditor);
11924 oCellEditor.show();
11925 this._oCellEditor = oCellEditor;
11929 // Backward compatibility
11931 if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
11932 oRecord = this.getRecord(elCell);
11934 if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
11935 oColumn = this.getColumn(elCell);
11937 if(oRecord && oColumn) {
11938 if(!this._oCellEditor || this._oCellEditor.container) {
11939 this._initCellEditorEl();
11942 // Update Editor values
11943 oCellEditor = this._oCellEditor;
11944 oCellEditor.cell = elCell;
11945 oCellEditor.record = oRecord;
11946 oCellEditor.column = oColumn;
11947 oCellEditor.validator = (oColumn.editorOptions &&
11948 lang.isFunction(oColumn.editorOptions.validator)) ?
11949 oColumn.editorOptions.validator : null;
11950 oCellEditor.value = oRecord.getData(oColumn.key);
11951 oCellEditor.defaultValue = null;
11954 var elContainer = oCellEditor.container;
11955 var x = Dom.getX(elCell);
11956 var y = Dom.getY(elCell);
11958 // SF doesn't get xy for cells in scrolling table
11959 // when tbody display is set to block
11960 if(isNaN(x) || isNaN(y)) {
11961 x = elCell.offsetLeft + // cell pos relative to table
11962 Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document
11963 this._elTbody.scrollLeft; // minus tbody scroll
11964 y = elCell.offsetTop + // cell pos relative to table
11965 Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document
11966 this._elTbody.scrollTop + // minus tbody scroll
11967 this._elThead.offsetHeight; // account for fixed THEAD cells
11970 elContainer.style.left = x + "px";
11971 elContainer.style.top = y + "px";
11973 // Hook to customize the UI
11974 this.doBeforeShowCellEditor(this._oCellEditor);
11976 //TODO: This is temporarily up here due so elements can be focused
11978 elContainer.style.display = "";
11981 Ev.addListener(elContainer, "keydown", function(e, oSelf) {
11982 // ESC hides Cell Editor
11983 if((e.keyCode == 27)) {
11984 oSelf.cancelCellEditor();
11985 oSelf.focusTbodyEl();
11988 oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
11992 // Render Editor markup
11994 if(lang.isString(oColumn.editor)) {
11995 switch(oColumn.editor) {
11997 fnEditor = DT.editCheckbox;
12000 fnEditor = DT.editDate;
12003 fnEditor = DT.editDropdown;
12006 fnEditor = DT.editRadio;
12009 fnEditor = DT.editTextarea;
12012 fnEditor = DT.editTextbox;
12018 else if(lang.isFunction(oColumn.editor)) {
12019 fnEditor = oColumn.editor;
12023 // Create DOM input elements
12024 fnEditor(this._oCellEditor, this);
12026 // Show Save/Cancel buttons
12027 if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
12028 this.showCellEditorBtns(elContainer);
12031 oCellEditor.isActive = true;
12033 //TODO: verify which args to pass
12034 this.fireEvent("editorShowEvent", {editor:oCellEditor});
12048 * Backward compatibility.
12050 * @method _initCellEditorEl
12054 _initCellEditorEl : function() {
12055 // Attach Cell Editor container element as first child of body
12056 var elCellEditor = document.createElement("div");
12057 elCellEditor.id = this._sId + "-celleditor";
12058 elCellEditor.style.display = "none";
12059 elCellEditor.tabIndex = 0;
12060 Dom.addClass(elCellEditor, DT.CLASS_EDITOR);
12061 var elFirstChild = Dom.getFirstChild(document.body);
12063 elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild);
12066 elCellEditor = document.body.appendChild(elCellEditor);
12069 // Internal tracker of Cell Editor values
12070 var oCellEditor = {};
12071 oCellEditor.container = elCellEditor;
12072 oCellEditor.value = null;
12073 oCellEditor.isActive = false;
12074 this._oCellEditor = oCellEditor;
12078 * Overridable abstract method to customize CellEditor before showing.
12080 * @method doBeforeShowCellEditor
12081 * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance.
12082 * @return {Boolean} Return true to continue showing CellEditor.
12084 doBeforeShowCellEditor : function(oCellEditor) {
12089 * Saves active CellEditor input to Record and upates DOM UI.
12091 * @method saveCellEditor
12093 saveCellEditor : function() {
12094 if(this._oCellEditor) {
12095 if(this._oCellEditor.save) {
12096 this._oCellEditor.save();
12098 // Backward compatibility
12099 else if(this._oCellEditor.isActive) {
12100 var newData = this._oCellEditor.value;
12101 // Copy the data to pass to the event
12102 //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key));
12103 var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
12105 // Validate input data
12106 if(this._oCellEditor.validator) {
12107 newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor);
12108 if(newData === null ) {
12109 this.resetCellEditor();
12110 this.fireEvent("editorRevertEvent",
12111 {editor:this._oCellEditor, oldData:oldData, newData:newData});
12115 // Update the Record
12116 this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
12118 this.formatCell(this._oCellEditor.cell.firstChild);
12121 this._oChainRender.add({
12122 method: function() {
12123 this.validateColumnWidths();
12127 this._oChainRender.run();
12128 // Clear out the Cell Editor
12129 this.resetCellEditor();
12131 this.fireEvent("editorSaveEvent",
12132 {editor:this._oCellEditor, oldData:oldData, newData:newData});
12138 * Cancels active CellEditor.
12140 * @method cancelCellEditor
12142 cancelCellEditor : function() {
12143 if(this._oCellEditor) {
12144 if(this._oCellEditor.cancel) {
12145 this._oCellEditor.cancel();
12147 // Backward compatibility
12148 else if(this._oCellEditor.isActive) {
12149 this.resetCellEditor();
12150 //TODO: preserve values for the event?
12151 this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
12158 * Destroys active CellEditor instance and UI.
12160 * @method destroyCellEditor
12162 destroyCellEditor : function() {
12163 if(this._oCellEditor) {
12164 this._oCellEditor.destroy();
12165 this._oCellEditor = null;
12170 * Passes through showEvent of the active CellEditor.
12172 * @method _onEditorShowEvent
12173 * @param oArgs {Object} Custom Event args.
12176 _onEditorShowEvent : function(oArgs) {
12177 this.fireEvent("editorShowEvent", oArgs);
12181 * Passes through keydownEvent of the active CellEditor.
12182 * @param oArgs {Object} Custom Event args.
12184 * @method _onEditorKeydownEvent
12187 _onEditorKeydownEvent : function(oArgs) {
12188 this.fireEvent("editorKeydownEvent", oArgs);
12192 * Passes through revertEvent of the active CellEditor.
12194 * @method _onEditorRevertEvent
12195 * @param oArgs {Object} Custom Event args.
12198 _onEditorRevertEvent : function(oArgs) {
12199 this.fireEvent("editorRevertEvent", oArgs);
12203 * Passes through saveEvent of the active CellEditor.
12205 * @method _onEditorSaveEvent
12206 * @param oArgs {Object} Custom Event args.
12209 _onEditorSaveEvent : function(oArgs) {
12210 this.fireEvent("editorSaveEvent", oArgs);
12214 * Passes through cancelEvent of the active CellEditor.
12216 * @method _onEditorCancelEvent
12217 * @param oArgs {Object} Custom Event args.
12220 _onEditorCancelEvent : function(oArgs) {
12221 this.fireEvent("editorCancelEvent", oArgs);
12225 * Passes through blurEvent of the active CellEditor.
12227 * @method _onEditorBlurEvent
12228 * @param oArgs {Object} Custom Event args.
12231 _onEditorBlurEvent : function(oArgs) {
12232 this.fireEvent("editorBlurEvent", oArgs);
12236 * Passes through blockEvent of the active CellEditor.
12238 * @method _onEditorBlockEvent
12239 * @param oArgs {Object} Custom Event args.
12242 _onEditorBlockEvent : function(oArgs) {
12243 this.fireEvent("editorBlockEvent", oArgs);
12247 * Passes through unblockEvent of the active CellEditor.
12249 * @method _onEditorUnblockEvent
12250 * @param oArgs {Object} Custom Event args.
12253 _onEditorUnblockEvent : function(oArgs) {
12254 this.fireEvent("editorUnblockEvent", oArgs);
12258 * Public handler of the editorBlurEvent. By default, saves on blur if
12259 * disableBtns is true, otherwise cancels on blur.
12261 * @method onEditorBlurEvent
12262 * @param oArgs {Object} Custom Event args.
12264 onEditorBlurEvent : function(oArgs) {
12265 if(oArgs.editor.disableBtns) {
12267 if(oArgs.editor.save) { // Backward incompatible
12268 oArgs.editor.save();
12271 else if(oArgs.editor.cancel) { // Backward incompatible
12273 oArgs.editor.cancel();
12278 * Public handler of the editorBlockEvent. By default, disables DataTable UI.
12280 * @method onEditorBlockEvent
12281 * @param oArgs {Object} Custom Event args.
12283 onEditorBlockEvent : function(oArgs) {
12288 * Public handler of the editorUnblockEvent. By default, undisables DataTable UI.
12290 * @method onEditorUnblockEvent
12291 * @param oArgs {Object} Custom Event args.
12293 onEditorUnblockEvent : function(oArgs) {
12334 // ABSTRACT METHODS
12337 * Overridable method gives implementers a hook to access data before
12338 * it gets added to RecordSet and rendered to the TBODY.
12340 * @method doBeforeLoadData
12341 * @param sRequest {String} Original request.
12342 * @param oResponse {Object} Response object.
12343 * @param oPayload {MIXED} additional arguments
12344 * @return {Boolean} Return true to continue loading data into RecordSet and
12345 * updating DataTable with new Records, false to cancel.
12347 doBeforeLoadData : function(sRequest, oResponse, oPayload) {
12413 /////////////////////////////////////////////////////////////////////////////
12415 // Public Custom Event Handlers
12417 /////////////////////////////////////////////////////////////////////////////
12420 * Overridable custom event handler to sort Column.
12422 * @method onEventSortColumn
12423 * @param oArgs.event {HTMLEvent} Event object.
12424 * @param oArgs.target {HTMLElement} Target element.
12426 onEventSortColumn : function(oArgs) {
12427 //TODO: support form elements in sortable columns
12428 var evt = oArgs.event;
12429 var target = oArgs.target;
12431 var el = this.getThEl(target) || this.getTdEl(target);
12433 var oColumn = this.getColumn(el);
12434 if(oColumn.sortable) {
12436 this.sortColumn(oColumn);
12444 * Overridable custom event handler to select Column.
12446 * @method onEventSelectColumn
12447 * @param oArgs.event {HTMLEvent} Event object.
12448 * @param oArgs.target {HTMLElement} Target element.
12450 onEventSelectColumn : function(oArgs) {
12451 this.selectColumn(oArgs.target);
12455 * Overridable custom event handler to highlight Column. Accounts for spurious
12456 * caused-by-child events.
12458 * @method onEventHighlightColumn
12459 * @param oArgs.event {HTMLEvent} Event object.
12460 * @param oArgs.target {HTMLElement} Target element.
12462 onEventHighlightColumn : function(oArgs) {
12463 //TODO: filter for all spurious events at a lower level
12464 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12465 this.highlightColumn(oArgs.target);
12470 * Overridable custom event handler to unhighlight Column. Accounts for spurious
12471 * caused-by-child events.
12473 * @method onEventUnhighlightColumn
12474 * @param oArgs.event {HTMLEvent} Event object.
12475 * @param oArgs.target {HTMLElement} Target element.
12477 onEventUnhighlightColumn : function(oArgs) {
12478 //TODO: filter for all spurious events at a lower level
12479 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12480 this.unhighlightColumn(oArgs.target);
12485 * Overridable custom event handler to manage selection according to desktop paradigm.
12487 * @method onEventSelectRow
12488 * @param oArgs.event {HTMLEvent} Event object.
12489 * @param oArgs.target {HTMLElement} Target element.
12491 onEventSelectRow : function(oArgs) {
12492 var sMode = this.get("selectionMode");
12493 if(sMode == "single") {
12494 this._handleSingleSelectionByMouse(oArgs);
12497 this._handleStandardSelectionByMouse(oArgs);
12502 * Overridable custom event handler to select cell.
12504 * @method onEventSelectCell
12505 * @param oArgs.event {HTMLEvent} Event object.
12506 * @param oArgs.target {HTMLElement} Target element.
12508 onEventSelectCell : function(oArgs) {
12509 var sMode = this.get("selectionMode");
12510 if(sMode == "cellblock") {
12511 this._handleCellBlockSelectionByMouse(oArgs);
12513 else if(sMode == "cellrange") {
12514 this._handleCellRangeSelectionByMouse(oArgs);
12517 this._handleSingleCellSelectionByMouse(oArgs);
12522 * Overridable custom event handler to highlight row. Accounts for spurious
12523 * caused-by-child events.
12525 * @method onEventHighlightRow
12526 * @param oArgs.event {HTMLEvent} Event object.
12527 * @param oArgs.target {HTMLElement} Target element.
12529 onEventHighlightRow : function(oArgs) {
12530 //TODO: filter for all spurious events at a lower level
12531 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12532 this.highlightRow(oArgs.target);
12537 * Overridable custom event handler to unhighlight row. Accounts for spurious
12538 * caused-by-child events.
12540 * @method onEventUnhighlightRow
12541 * @param oArgs.event {HTMLEvent} Event object.
12542 * @param oArgs.target {HTMLElement} Target element.
12544 onEventUnhighlightRow : function(oArgs) {
12545 //TODO: filter for all spurious events at a lower level
12546 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12547 this.unhighlightRow(oArgs.target);
12552 * Overridable custom event handler to highlight cell. Accounts for spurious
12553 * caused-by-child events.
12555 * @method onEventHighlightCell
12556 * @param oArgs.event {HTMLEvent} Event object.
12557 * @param oArgs.target {HTMLElement} Target element.
12559 onEventHighlightCell : function(oArgs) {
12560 //TODO: filter for all spurious events at a lower level
12561 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12562 this.highlightCell(oArgs.target);
12567 * Overridable custom event handler to unhighlight cell. Accounts for spurious
12568 * caused-by-child events.
12570 * @method onEventUnhighlightCell
12571 * @param oArgs.event {HTMLEvent} Event object.
12572 * @param oArgs.target {HTMLElement} Target element.
12574 onEventUnhighlightCell : function(oArgs) {
12575 //TODO: filter for all spurious events at a lower level
12576 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12577 this.unhighlightCell(oArgs.target);
12582 * Overridable custom event handler to format cell.
12584 * @method onEventFormatCell
12585 * @param oArgs.event {HTMLEvent} Event object.
12586 * @param oArgs.target {HTMLElement} Target element.
12588 onEventFormatCell : function(oArgs) {
12589 var target = oArgs.target;
12591 var elCell = this.getTdEl(target);
12593 var oColumn = this.getColumn(elCell.cellIndex);
12594 this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn);
12601 * Overridable custom event handler to edit cell.
12603 * @method onEventShowCellEditor
12604 * @param oArgs.event {HTMLEvent} Event object.
12605 * @param oArgs.target {HTMLElement} Target element.
12607 onEventShowCellEditor : function(oArgs) {
12608 this.showCellEditor(oArgs.target);
12612 * Overridable custom event handler to save active CellEditor input.
12614 * @method onEventSaveCellEditor
12616 onEventSaveCellEditor : function(oArgs) {
12617 if(this._oCellEditor) {
12618 if(this._oCellEditor.save) {
12619 this._oCellEditor.save();
12621 // Backward compatibility
12623 this.saveCellEditor();
12629 * Overridable custom event handler to cancel active CellEditor.
12631 * @method onEventCancelCellEditor
12633 onEventCancelCellEditor : function(oArgs) {
12634 if(this._oCellEditor) {
12635 if(this._oCellEditor.cancel) {
12636 this._oCellEditor.cancel();
12638 // Backward compatibility
12640 this.cancelCellEditor();
12646 * Callback function receives data from DataSource and populates an entire
12647 * DataTable with Records and TR elements, clearing previous Records, if any.
12649 * @method onDataReturnInitializeTable
12650 * @param sRequest {String} Original request.
12651 * @param oResponse {Object} Response object.
12652 * @param oPayload {MIXED} (optional) Additional argument(s)
12654 onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) {
12655 if((this instanceof DT) && this._sId) {
12656 this.initializeTable();
12658 this.onDataReturnSetRows(sRequest,oResponse,oPayload);
12663 * Callback function receives reponse from DataSource, replaces all existing
12664 * Records in RecordSet, updates TR elements with new data, and updates state
12665 * UI for pagination and sorting from payload data, if necessary.
12667 * @method onDataReturnReplaceRows
12668 * @param oRequest {MIXED} Original generated request.
12669 * @param oResponse {Object} Response object.
12670 * @param oPayload {MIXED} (optional) Additional argument(s)
12672 onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) {
12673 if((this instanceof DT) && this._sId) {
12674 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
12676 // Pass data through abstract method for any transformations
12677 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload),
12678 pag = this.get('paginator'),
12682 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12684 this._oRecordSet.reset();
12686 if (this.get('dynamicData')) {
12687 if (oPayload && oPayload.pagination &&
12688 lang.isNumber(oPayload.pagination.recordOffset)) {
12689 index = oPayload.pagination.recordOffset;
12691 index = pag.getStartIndex();
12695 this._oRecordSet.setRecords(oResponse.results, index | 0);
12698 this._handleDataReturnPayload(oRequest, oResponse, oPayload);
12704 else if(ok && oResponse.error) {
12705 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12711 * Callback function receives data from DataSource and appends to an existing
12712 * DataTable new Records and, if applicable, creates or updates
12713 * corresponding TR elements.
12715 * @method onDataReturnAppendRows
12716 * @param sRequest {String} Original request.
12717 * @param oResponse {Object} Response object.
12718 * @param oPayload {MIXED} (optional) Additional argument(s)
12720 onDataReturnAppendRows : function(sRequest, oResponse, oPayload) {
12721 if((this instanceof DT) && this._sId) {
12722 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12724 // Pass data through abstract method for any transformations
12725 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12727 // Data ok to append
12728 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12730 this.addRows(oResponse.results);
12733 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12736 else if(ok && oResponse.error) {
12737 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12743 * Callback function receives data from DataSource and inserts new records
12744 * starting at the index specified in oPayload.insertIndex. The value for
12745 * oPayload.insertIndex can be populated when sending the request to the DataSource,
12746 * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime.
12747 * If applicable, creates or updates corresponding TR elements.
12749 * @method onDataReturnInsertRows
12750 * @param sRequest {String} Original request.
12751 * @param oResponse {Object} Response object.
12752 * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex.
12754 onDataReturnInsertRows : function(sRequest, oResponse, oPayload) {
12755 if((this instanceof DT) && this._sId) {
12756 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12758 // Pass data through abstract method for any transformations
12759 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12761 // Data ok to append
12762 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12764 this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0));
12767 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12770 else if(ok && oResponse.error) {
12771 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12777 * Callback function receives data from DataSource and incrementally updates Records
12778 * starting at the index specified in oPayload.updateIndex. The value for
12779 * oPayload.updateIndex can be populated when sending the request to the DataSource,
12780 * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime.
12781 * If applicable, creates or updates corresponding TR elements.
12783 * @method onDataReturnUpdateRows
12784 * @param sRequest {String} Original request.
12785 * @param oResponse {Object} Response object.
12786 * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex.
12788 onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) {
12789 if((this instanceof DT) && this._sId) {
12790 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12792 // Pass data through abstract method for any transformations
12793 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12795 // Data ok to append
12796 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12798 this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results);
12801 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12804 else if(ok && oResponse.error) {
12805 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12811 * Callback function receives reponse from DataSource and populates the
12812 * RecordSet with the results.
12814 * @method onDataReturnSetRows
12815 * @param oRequest {MIXED} Original generated request.
12816 * @param oResponse {Object} Response object.
12817 * @param oPayload {MIXED} (optional) Additional argument(s)
12819 onDataReturnSetRows : function(oRequest, oResponse, oPayload) {
12820 if((this instanceof DT) && this._sId) {
12821 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
12823 // Pass data through abstract method for any transformations
12824 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload),
12825 pag = this.get('paginator'),
12829 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12831 if (this.get('dynamicData')) {
12832 if (oPayload && oPayload.pagination &&
12833 lang.isNumber(oPayload.pagination.recordOffset)) {
12834 index = oPayload.pagination.recordOffset;
12836 index = pag.getStartIndex();
12839 this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default
12842 this._oRecordSet.setRecords(oResponse.results, index | 0);
12845 this._handleDataReturnPayload(oRequest, oResponse, oPayload);
12851 else if(ok && oResponse.error) {
12852 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12860 * Hook to update oPayload before consumption.
12862 * @method handleDataReturnPayload
12863 * @param oRequest {MIXED} Original generated request.
12864 * @param oResponse {Object} Response object.
12865 * @param oPayload {MIXED} State values.
12866 * @return oPayload {MIXED} State values.
12868 handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
12873 * Updates the DataTable with state data sent in an onDataReturn* payload.
12875 * @method handleDataReturnPayload
12876 * @param oRequest {MIXED} Original generated request.
12877 * @param oResponse {Object} Response object.
12878 * @param oPayload {MIXED} State values
12880 _handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
12881 oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload);
12883 // Update pagination
12884 var oPaginator = this.get('paginator');
12886 // Update totalRecords
12887 if(this.get("dynamicData")) {
12888 if (widget.Paginator.isNumeric(oPayload.totalRecords)) {
12889 oPaginator.set('totalRecords',oPayload.totalRecords);
12893 oPaginator.set('totalRecords',this._oRecordSet.getLength());
12895 // Update other paginator values
12896 if (lang.isObject(oPayload.pagination)) {
12897 oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage);
12898 oPaginator.set('recordOffset',oPayload.pagination.recordOffset);
12903 if (oPayload.sortedBy) {
12904 // Set the sorting values in preparation for refresh
12905 this.set('sortedBy', oPayload.sortedBy);
12907 // Backwards compatibility for sorting
12908 else if (oPayload.sorting) {
12909 // Set the sorting values in preparation for refresh
12910 this.set('sortedBy', oPayload.sorting);
12947 /////////////////////////////////////////////////////////////////////////////
12951 /////////////////////////////////////////////////////////////////////////////
12954 * Fired when the DataTable's rows are rendered from an initialized state.
12960 * Fired when the DataTable's DOM is rendered or modified.
12962 * @event renderEvent
12966 * Fired when the DataTable's post-render routine is complete, including
12967 * Column width validations.
12969 * @event postRenderEvent
12973 * Fired when the DataTable is disabled.
12975 * @event disableEvent
12979 * Fired when the DataTable is undisabled.
12981 * @event undisableEvent
12985 * Fired when data is returned from DataSource but before it is consumed by
12988 * @event dataReturnEvent
12989 * @param oArgs.request {String} Original request.
12990 * @param oArgs.response {Object} Response object.
12994 * Fired when the DataTable has a focus event.
12996 * @event tableFocusEvent
13000 * Fired when the DataTable THEAD element has a focus event.
13002 * @event theadFocusEvent
13006 * Fired when the DataTable TBODY element has a focus event.
13008 * @event tbodyFocusEvent
13012 * Fired when the DataTable has a blur event.
13014 * @event tableBlurEvent
13017 /*TODO implement theadBlurEvent
13018 * Fired when the DataTable THEAD element has a blur event.
13020 * @event theadBlurEvent
13023 /*TODO: implement tbodyBlurEvent
13024 * Fired when the DataTable TBODY element has a blur event.
13026 * @event tbodyBlurEvent
13030 * Fired when the DataTable has a key event.
13032 * @event tableKeyEvent
13033 * @param oArgs.event {HTMLEvent} The event object.
13034 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13038 * Fired when the DataTable THEAD element has a key event.
13040 * @event theadKeyEvent
13041 * @param oArgs.event {HTMLEvent} The event object.
13042 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13046 * Fired when the DataTable TBODY element has a key event.
13048 * @event tbodyKeyEvent
13049 * @param oArgs.event {HTMLEvent} The event object.
13050 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13054 * Fired when the DataTable has a mouseover.
13056 * @event tableMouseoverEvent
13057 * @param oArgs.event {HTMLEvent} The event object.
13058 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13063 * Fired when the DataTable has a mouseout.
13065 * @event tableMouseoutEvent
13066 * @param oArgs.event {HTMLEvent} The event object.
13067 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13072 * Fired when the DataTable has a mousedown.
13074 * @event tableMousedownEvent
13075 * @param oArgs.event {HTMLEvent} The event object.
13076 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13081 * Fired when the DataTable has a mouseup.
13083 * @event tableMouseupEvent
13084 * @param oArgs.event {HTMLEvent} The event object.
13085 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13090 * Fired when the DataTable has a click.
13092 * @event tableClickEvent
13093 * @param oArgs.event {HTMLEvent} The event object.
13094 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13099 * Fired when the DataTable has a dblclick.
13101 * @event tableDblclickEvent
13102 * @param oArgs.event {HTMLEvent} The event object.
13103 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13108 * Fired when a message is shown in the DataTable's message element.
13110 * @event tableMsgShowEvent
13111 * @param oArgs.html {String} The HTML displayed.
13112 * @param oArgs.className {String} The className assigned.
13117 * Fired when the DataTable's message element is hidden.
13119 * @event tableMsgHideEvent
13123 * Fired when a THEAD row has a mouseover.
13125 * @event theadRowMouseoverEvent
13126 * @param oArgs.event {HTMLEvent} The event object.
13127 * @param oArgs.target {HTMLElement} The TR element.
13131 * Fired when a THEAD row has a mouseout.
13133 * @event theadRowMouseoutEvent
13134 * @param oArgs.event {HTMLEvent} The event object.
13135 * @param oArgs.target {HTMLElement} The TR element.
13139 * Fired when a THEAD row has a mousedown.
13141 * @event theadRowMousedownEvent
13142 * @param oArgs.event {HTMLEvent} The event object.
13143 * @param oArgs.target {HTMLElement} The TR element.
13147 * Fired when a THEAD row has a mouseup.
13149 * @event theadRowMouseupEvent
13150 * @param oArgs.event {HTMLEvent} The event object.
13151 * @param oArgs.target {HTMLElement} The TR element.
13155 * Fired when a THEAD row has a click.
13157 * @event theadRowClickEvent
13158 * @param oArgs.event {HTMLEvent} The event object.
13159 * @param oArgs.target {HTMLElement} The TR element.
13163 * Fired when a THEAD row has a dblclick.
13165 * @event theadRowDblclickEvent
13166 * @param oArgs.event {HTMLEvent} The event object.
13167 * @param oArgs.target {HTMLElement} The TR element.
13171 * Fired when a THEAD cell has a mouseover.
13173 * @event theadCellMouseoverEvent
13174 * @param oArgs.event {HTMLEvent} The event object.
13175 * @param oArgs.target {HTMLElement} The TH element.
13180 * Fired when a THEAD cell has a mouseout.
13182 * @event theadCellMouseoutEvent
13183 * @param oArgs.event {HTMLEvent} The event object.
13184 * @param oArgs.target {HTMLElement} The TH element.
13189 * Fired when a THEAD cell has a mousedown.
13191 * @event theadCellMousedownEvent
13192 * @param oArgs.event {HTMLEvent} The event object.
13193 * @param oArgs.target {HTMLElement} The TH element.
13197 * Fired when a THEAD cell has a mouseup.
13199 * @event theadCellMouseupEvent
13200 * @param oArgs.event {HTMLEvent} The event object.
13201 * @param oArgs.target {HTMLElement} The TH element.
13205 * Fired when a THEAD cell has a click.
13207 * @event theadCellClickEvent
13208 * @param oArgs.event {HTMLEvent} The event object.
13209 * @param oArgs.target {HTMLElement} The TH element.
13213 * Fired when a THEAD cell has a dblclick.
13215 * @event theadCellDblclickEvent
13216 * @param oArgs.event {HTMLEvent} The event object.
13217 * @param oArgs.target {HTMLElement} The TH element.
13221 * Fired when a THEAD label has a mouseover.
13223 * @event theadLabelMouseoverEvent
13224 * @param oArgs.event {HTMLEvent} The event object.
13225 * @param oArgs.target {HTMLElement} The SPAN element.
13230 * Fired when a THEAD label has a mouseout.
13232 * @event theadLabelMouseoutEvent
13233 * @param oArgs.event {HTMLEvent} The event object.
13234 * @param oArgs.target {HTMLElement} The SPAN element.
13239 * Fired when a THEAD label has a mousedown.
13241 * @event theadLabelMousedownEvent
13242 * @param oArgs.event {HTMLEvent} The event object.
13243 * @param oArgs.target {HTMLElement} The SPAN element.
13247 * Fired when a THEAD label has a mouseup.
13249 * @event theadLabelMouseupEvent
13250 * @param oArgs.event {HTMLEvent} The event object.
13251 * @param oArgs.target {HTMLElement} The SPAN element.
13255 * Fired when a THEAD label has a click.
13257 * @event theadLabelClickEvent
13258 * @param oArgs.event {HTMLEvent} The event object.
13259 * @param oArgs.target {HTMLElement} The SPAN element.
13263 * Fired when a THEAD label has a dblclick.
13265 * @event theadLabelDblclickEvent
13266 * @param oArgs.event {HTMLEvent} The event object.
13267 * @param oArgs.target {HTMLElement} The SPAN element.
13271 * Fired when a column is sorted.
13273 * @event columnSortEvent
13274 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13275 * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC
13276 * or YAHOO.widget.DataTable.CLASS_DESC.
13280 * Fired when a column width is set.
13282 * @event columnSetWidthEvent
13283 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13284 * @param oArgs.width {Number} The width in pixels.
13288 * Fired when a column width is unset.
13290 * @event columnUnsetWidthEvent
13291 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13295 * Fired when a column is drag-resized.
13297 * @event columnResizeEvent
13298 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13299 * @param oArgs.target {HTMLElement} The TH element.
13300 * @param oArgs.width {Number} Width in pixels.
13304 * Fired when a Column is moved to a new index.
13306 * @event columnReorderEvent
13307 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13308 * @param oArgs.oldIndex {Number} The previous index position.
13312 * Fired when a column is hidden.
13314 * @event columnHideEvent
13315 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13319 * Fired when a column is shown.
13321 * @event columnShowEvent
13322 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13326 * Fired when a column is selected.
13328 * @event columnSelectEvent
13329 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13333 * Fired when a column is unselected.
13335 * @event columnUnselectEvent
13336 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13339 * Fired when a column is removed.
13341 * @event columnRemoveEvent
13342 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13346 * Fired when a column is inserted.
13348 * @event columnInsertEvent
13349 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13350 * @param oArgs.index {Number} The index position.
13354 * Fired when a column is highlighted.
13356 * @event columnHighlightEvent
13357 * @param oArgs.column {YAHOO.widget.Column} The highlighted Column.
13361 * Fired when a column is unhighlighted.
13363 * @event columnUnhighlightEvent
13364 * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column.
13369 * Fired when a row has a mouseover.
13371 * @event rowMouseoverEvent
13372 * @param oArgs.event {HTMLEvent} The event object.
13373 * @param oArgs.target {HTMLElement} The TR element.
13377 * Fired when a row has a mouseout.
13379 * @event rowMouseoutEvent
13380 * @param oArgs.event {HTMLEvent} The event object.
13381 * @param oArgs.target {HTMLElement} The TR element.
13385 * Fired when a row has a mousedown.
13387 * @event rowMousedownEvent
13388 * @param oArgs.event {HTMLEvent} The event object.
13389 * @param oArgs.target {HTMLElement} The TR element.
13393 * Fired when a row has a mouseup.
13395 * @event rowMouseupEvent
13396 * @param oArgs.event {HTMLEvent} The event object.
13397 * @param oArgs.target {HTMLElement} The TR element.
13401 * Fired when a row has a click.
13403 * @event rowClickEvent
13404 * @param oArgs.event {HTMLEvent} The event object.
13405 * @param oArgs.target {HTMLElement} The TR element.
13409 * Fired when a row has a dblclick.
13411 * @event rowDblclickEvent
13412 * @param oArgs.event {HTMLEvent} The event object.
13413 * @param oArgs.target {HTMLElement} The TR element.
13417 * Fired when a row is added.
13419 * @event rowAddEvent
13420 * @param oArgs.record {YAHOO.widget.Record} The added Record.
13424 * Fired when rows are added.
13426 * @event rowsAddEvent
13427 * @param oArgs.record {YAHOO.widget.Record[]} The added Records.
13431 * Fired when a row is updated.
13433 * @event rowUpdateEvent
13434 * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13435 * @param oArgs.oldData {Object} Object literal of the old data.
13439 * Fired when a row is deleted.
13441 * @event rowDeleteEvent
13442 * @param oArgs.oldData {Object} Object literal of the deleted data.
13443 * @param oArgs.recordIndex {Number} Index of the deleted Record.
13444 * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page.
13448 * Fired when rows are deleted.
13450 * @event rowsDeleteEvent
13451 * @param oArgs.oldData {Object[]} Array of object literals of the deleted data.
13452 * @param oArgs.recordIndex {Number} Index of the first deleted Record.
13453 * @param oArgs.count {Number} Number of deleted Records.
13457 * Fired when a row is selected.
13459 * @event rowSelectEvent
13460 * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
13461 * @param oArgs.record {YAHOO.widget.Record} The selected Record.
13465 * Fired when a row is unselected.
13467 * @event rowUnselectEvent
13468 * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
13469 * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
13473 * Fired when all row selections are cleared.
13475 * @event unselectAllRowsEvent
13479 * Fired when a row is highlighted.
13481 * @event rowHighlightEvent
13482 * @param oArgs.el {HTMLElement} The highlighted TR element.
13483 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13487 * Fired when a row is unhighlighted.
13489 * @event rowUnhighlightEvent
13490 * @param oArgs.el {HTMLElement} The highlighted TR element.
13491 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13495 * Fired when a cell is updated.
13497 * @event cellUpdateEvent
13498 * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13499 * @param oArgs.column {YAHOO.widget.Column} The updated Column.
13500 * @param oArgs.oldData {Object} Original data value of the updated cell.
13504 * Fired when a cell has a mouseover.
13506 * @event cellMouseoverEvent
13507 * @param oArgs.event {HTMLEvent} The event object.
13508 * @param oArgs.target {HTMLElement} The TD element.
13512 * Fired when a cell has a mouseout.
13514 * @event cellMouseoutEvent
13515 * @param oArgs.event {HTMLEvent} The event object.
13516 * @param oArgs.target {HTMLElement} The TD element.
13520 * Fired when a cell has a mousedown.
13522 * @event cellMousedownEvent
13523 * @param oArgs.event {HTMLEvent} The event object.
13524 * @param oArgs.target {HTMLElement} The TD element.
13528 * Fired when a cell has a mouseup.
13530 * @event cellMouseupEvent
13531 * @param oArgs.event {HTMLEvent} The event object.
13532 * @param oArgs.target {HTMLElement} The TD element.
13536 * Fired when a cell has a click.
13538 * @event cellClickEvent
13539 * @param oArgs.event {HTMLEvent} The event object.
13540 * @param oArgs.target {HTMLElement} The TD element.
13544 * Fired when a cell has a dblclick.
13546 * @event cellDblclickEvent
13547 * @param oArgs.event {HTMLEvent} The event object.
13548 * @param oArgs.target {HTMLElement} The TD element.
13552 * Fired when a cell is formatted.
13554 * @event cellFormatEvent
13555 * @param oArgs.el {HTMLElement} The formatted TD element.
13556 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13557 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13558 * @param oArgs.key {String} (deprecated) The key of the formatted cell.
13562 * Fired when a cell is selected.
13564 * @event cellSelectEvent
13565 * @param oArgs.el {HTMLElement} The selected TD element.
13566 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13567 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13568 * @param oArgs.key {String} (deprecated) The key of the selected cell.
13572 * Fired when a cell is unselected.
13574 * @event cellUnselectEvent
13575 * @param oArgs.el {HTMLElement} The unselected TD element.
13576 * @param oArgs.record {YAHOO.widget.Record} The associated Record.
13577 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13578 * @param oArgs.key {String} (deprecated) The key of the unselected cell.
13583 * Fired when a cell is highlighted.
13585 * @event cellHighlightEvent
13586 * @param oArgs.el {HTMLElement} The highlighted TD element.
13587 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13588 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13589 * @param oArgs.key {String} (deprecated) The key of the highlighted cell.
13594 * Fired when a cell is unhighlighted.
13596 * @event cellUnhighlightEvent
13597 * @param oArgs.el {HTMLElement} The unhighlighted TD element.
13598 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13599 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13600 * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell.
13605 * Fired when all cell selections are cleared.
13607 * @event unselectAllCellsEvent
13611 * Fired when a CellEditor is shown.
13613 * @event editorShowEvent
13614 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13618 * Fired when a CellEditor has a keydown.
13620 * @event editorKeydownEvent
13621 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13622 * @param oArgs.event {HTMLEvent} The event object.
13626 * Fired when a CellEditor input is reverted.
13628 * @event editorRevertEvent
13629 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13630 * @param oArgs.newData {Object} New data value from form input field.
13631 * @param oArgs.oldData {Object} Old data value.
13635 * Fired when a CellEditor input is saved.
13637 * @event editorSaveEvent
13638 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13639 * @param oArgs.newData {Object} New data value from form input field.
13640 * @param oArgs.oldData {Object} Old data value.
13644 * Fired when a CellEditor input is canceled.
13646 * @event editorCancelEvent
13647 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13651 * Fired when a CellEditor has a blur event.
13653 * @event editorBlurEvent
13654 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13658 * Fired when a CellEditor is blocked.
13660 * @event editorBlockEvent
13661 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13665 * Fired when a CellEditor is unblocked.
13667 * @event editorUnblockEvent
13668 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13676 * Fired when a link is clicked.
13678 * @event linkClickEvent
13679 * @param oArgs.event {HTMLEvent} The event object.
13680 * @param oArgs.target {HTMLElement} The A element.
13684 * Fired when a BUTTON element or INPUT element of type "button", "image",
13685 * "submit", "reset" is clicked.
13687 * @event buttonClickEvent
13688 * @param oArgs.event {HTMLEvent} The event object.
13689 * @param oArgs.target {HTMLElement} The BUTTON element.
13693 * Fired when a CHECKBOX element is clicked.
13695 * @event checkboxClickEvent
13696 * @param oArgs.event {HTMLEvent} The event object.
13697 * @param oArgs.target {HTMLElement} The CHECKBOX element.
13701 * Fired when a SELECT element is changed.
13703 * @event dropdownChangeEvent
13704 * @param oArgs.event {HTMLEvent} The event object.
13705 * @param oArgs.target {HTMLElement} The SELECT element.
13709 * Fired when a RADIO element is clicked.
13711 * @event radioClickEvent
13712 * @param oArgs.event {HTMLEvent} The event object.
13713 * @param oArgs.target {HTMLElement} The RADIO element.
13741 /////////////////////////////////////////////////////////////////////////////
13745 /////////////////////////////////////////////////////////////////////////////
13748 * @method showCellEditorBtns
13749 * @deprecated Use CellEditor.renderBtns()
13751 showCellEditorBtns : function(elContainer) {
13753 var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
13754 Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON);
13757 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
13758 Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT);
13759 elSaveBtn.innerHTML = "OK";
13760 Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) {
13761 oSelf.onEventSaveCellEditor(oArgs, oSelf);
13762 oSelf.focusTbodyEl();
13766 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
13767 elCancelBtn.innerHTML = "Cancel";
13768 Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) {
13769 oSelf.onEventCancelCellEditor(oArgs, oSelf);
13770 oSelf.focusTbodyEl();
13776 * @method resetCellEditor
13777 * @deprecated Use destroyCellEditor
13779 resetCellEditor : function() {
13780 var elContainer = this._oCellEditor.container;
13781 elContainer.style.display = "none";
13782 Ev.purgeElement(elContainer, true);
13783 elContainer.innerHTML = "";
13784 this._oCellEditor.value = null;
13785 this._oCellEditor.isActive = false;
13790 * @event editorUpdateEvent
13791 * @deprecated Use CellEditor class.
13796 * @deprecated Use getTbodyEl().
13798 getBody : function() {
13799 // Backward compatibility
13800 return this.getTbodyEl();
13805 * @deprecated Use getTdEl().
13807 getCell : function(index) {
13808 // Backward compatibility
13809 return this.getTdEl(index);
13814 * @deprecated Use getTrEl().
13816 getRow : function(index) {
13817 // Backward compatibility
13818 return this.getTrEl(index);
13822 * @method refreshView
13823 * @deprecated Use render.
13825 refreshView : function() {
13826 // Backward compatibility
13832 * @deprecated Use selectRow.
13834 select : function(els) {
13835 // Backward compatibility
13836 if(!lang.isArray(els)) {
13839 for(var i=0; i<els.length; i++) {
13840 this.selectRow(els[i]);
13845 * @method onEventEditCell
13846 * @deprecated Use onEventShowCellEditor.
13848 onEventEditCell : function(oArgs) {
13849 // Backward compatibility
13850 this.onEventShowCellEditor(oArgs);
13854 * @method _syncColWidths
13855 * @deprecated Use validateColumnWidths.
13857 _syncColWidths : function() {
13858 // Backward compatibility
13859 this.validateColumnWidths();
13863 * @event headerRowMouseoverEvent
13864 * @deprecated Use theadRowMouseoverEvent.
13868 * @event headerRowMouseoutEvent
13869 * @deprecated Use theadRowMouseoutEvent.
13873 * @event headerRowMousedownEvent
13874 * @deprecated Use theadRowMousedownEvent.
13878 * @event headerRowClickEvent
13879 * @deprecated Use theadRowClickEvent.
13883 * @event headerRowDblclickEvent
13884 * @deprecated Use theadRowDblclickEvent.
13888 * @event headerCellMouseoverEvent
13889 * @deprecated Use theadCellMouseoverEvent.
13893 * @event headerCellMouseoutEvent
13894 * @deprecated Use theadCellMouseoutEvent.
13898 * @event headerCellMousedownEvent
13899 * @deprecated Use theadCellMousedownEvent.
13903 * @event headerCellClickEvent
13904 * @deprecated Use theadCellClickEvent.
13908 * @event headerCellDblclickEvent
13909 * @deprecated Use theadCellDblclickEvent.
13913 * @event headerLabelMouseoverEvent
13914 * @deprecated Use theadLabelMouseoverEvent.
13918 * @event headerLabelMouseoutEvent
13919 * @deprecated Use theadLabelMouseoutEvent.
13923 * @event headerLabelMousedownEvent
13924 * @deprecated Use theadLabelMousedownEvent.
13928 * @event headerLabelClickEvent
13929 * @deprecated Use theadLabelClickEvent.
13933 * @event headerLabelDbllickEvent
13934 * @deprecated Use theadLabelDblclickEvent.
13940 * Alias for onDataReturnSetRows for backward compatibility
13941 * @method onDataReturnSetRecords
13942 * @deprecated Use onDataReturnSetRows
13944 DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows;
13947 * Alias for onPaginatorChange for backward compatibility
13948 * @method onPaginatorChange
13949 * @deprecated Use onPaginatorChangeRequest
13951 DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest;
13953 /////////////////////////////////////////////////////////////////////////////
13955 // Deprecated static APIs
13957 /////////////////////////////////////////////////////////////////////////////
13959 * @method DataTable.formatTheadCell
13960 * @deprecated Use formatTheadCell.
13962 DT.formatTheadCell = function() {};
13965 * @method DataTable.editCheckbox
13966 * @deprecated Use YAHOO.widget.CheckboxCellEditor.
13968 DT.editCheckbox = function() {};
13971 * @method DataTable.editDate
13972 * @deprecated Use YAHOO.widget.DateCellEditor.
13974 DT.editDate = function() {};
13977 * @method DataTable.editDropdown
13978 * @deprecated Use YAHOO.widget.DropdownCellEditor.
13980 DT.editDropdown = function() {};
13983 * @method DataTable.editRadio
13984 * @deprecated Use YAHOO.widget.RadioCellEditor.
13986 DT.editRadio = function() {};
13989 * @method DataTable.editTextarea
13990 * @deprecated Use YAHOO.widget.TextareaCellEditor
13992 DT.editTextarea = function() {};
13995 * @method DataTable.editTextbox
13996 * @deprecated Use YAHOO.widget.TextboxCellEditor
13998 DT.editTextbox= function() {};
14004 var lang = YAHOO.lang,
14006 widget = YAHOO.widget,
14011 DS = util.DataSourceBase,
14012 DT = widget.DataTable,
14013 Pag = widget.Paginator;
14016 * The ScrollingDataTable class extends the DataTable class to provide
14017 * functionality for x-scrolling, y-scrolling, and xy-scrolling.
14019 * @namespace YAHOO.widget
14020 * @class ScrollingDataTable
14021 * @extends YAHOO.widget.DataTable
14023 * @param elContainer {HTMLElement} Container element for the TABLE.
14024 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
14025 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
14026 * @param oConfigs {object} (optional) Object literal of configuration values.
14028 widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
14029 oConfigs = oConfigs || {};
14031 // Prevent infinite loop
14032 if(oConfigs.scrollable) {
14033 oConfigs.scrollable = false;
14036 widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs);
14038 // Once per instance
14039 this.subscribe("columnShowEvent", this._onColumnChange);
14042 var SDT = widget.ScrollingDataTable;
14044 /////////////////////////////////////////////////////////////////////////////
14046 // Public constants
14048 /////////////////////////////////////////////////////////////////////////////
14049 lang.augmentObject(SDT, {
14052 * Class name assigned to inner DataTable header container.
14054 * @property DataTable.CLASS_HEADER
14058 * @default "yui-dt-hd"
14060 CLASS_HEADER : "yui-dt-hd",
14063 * Class name assigned to inner DataTable body container.
14065 * @property DataTable.CLASS_BODY
14069 * @default "yui-dt-bd"
14071 CLASS_BODY : "yui-dt-bd"
14074 lang.extend(SDT, DT, {
14077 * Container for fixed header TABLE element.
14079 * @property _elHdContainer
14080 * @type HTMLElement
14083 _elHdContainer : null,
14086 * Fixed header TABLE element.
14088 * @property _elHdTable
14089 * @type HTMLElement
14095 * Container for scrolling body TABLE element.
14097 * @property _elBdContainer
14098 * @type HTMLElement
14101 _elBdContainer : null,
14104 * Body THEAD element.
14106 * @property _elBdThead
14107 * @type HTMLElement
14113 * Offscreen container to temporarily clone SDT for auto-width calculation.
14115 * @property _elTmpContainer
14116 * @type HTMLElement
14119 _elTmpContainer : null,
14122 * Offscreen TABLE element for auto-width calculation.
14124 * @property _elTmpTable
14125 * @type HTMLElement
14128 _elTmpTable : null,
14131 * True if x-scrollbar is currently visible.
14132 * @property _bScrollbarX
14136 _bScrollbarX : null,
14152 /////////////////////////////////////////////////////////////////////////////
14154 // Superclass methods
14156 /////////////////////////////////////////////////////////////////////////////
14159 * Implementation of Element's abstract method. Sets up config values.
14161 * @method initAttributes
14162 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
14166 initAttributes : function(oConfigs) {
14167 oConfigs = oConfigs || {};
14168 SDT.superclass.initAttributes.call(this, oConfigs);
14172 * @description Table width for scrollable tables (e.g., "40em").
14175 this.setAttributeConfig("width", {
14177 validator: lang.isString,
14178 method: function(oParam) {
14179 if(this._elHdContainer && this._elBdContainer) {
14180 this._elHdContainer.style.width = oParam;
14181 this._elBdContainer.style.width = oParam;
14182 this._syncScrollX();
14183 this._syncScrollOverhang();
14189 * @attribute height
14190 * @description Table body height for scrollable tables, not including headers (e.g., "40em").
14193 this.setAttributeConfig("height", {
14195 validator: lang.isString,
14196 method: function(oParam) {
14197 if(this._elHdContainer && this._elBdContainer) {
14198 this._elBdContainer.style.height = oParam;
14199 this._syncScrollX();
14200 this._syncScrollY();
14201 this._syncScrollOverhang();
14207 * @attribute COLOR_COLUMNFILLER
14208 * @description CSS color value assigned to header filler on scrollable tables.
14210 * @default "#F2F2F2"
14212 this.setAttributeConfig("COLOR_COLUMNFILLER", {
14214 validator: lang.isString,
14215 method: function(oParam) {
14216 this._elHdContainer.style.backgroundColor = oParam;
14222 * Initializes DOM elements for a ScrollingDataTable, including creation of
14223 * two separate TABLE elements.
14225 * @method _initDomElements
14226 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
14227 * return {Boolean} False in case of error, otherwise true
14230 _initDomElements : function(elContainer) {
14231 // Outer and inner containers
14232 this._initContainerEl(elContainer);
14233 if(this._elContainer && this._elHdContainer && this._elBdContainer) {
14235 this._initTableEl();
14237 if(this._elHdTable && this._elTable) {
14239 ///this._initColgroupEl(this._elHdTable, this._elTable);
14240 this._initColgroupEl(this._elHdTable);
14243 this._initTheadEl(this._elHdTable, this._elTable);
14246 this._initTbodyEl(this._elTable);
14248 this._initMsgTbodyEl(this._elTable);
14251 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody ||
14252 !this._elHdTable || !this._elBdThead) {
14261 * Destroy's the DataTable outer and inner container elements, if available.
14263 * @method _destroyContainerEl
14264 * @param elContainer {HTMLElement} Reference to the container element.
14267 _destroyContainerEl : function(elContainer) {
14268 Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE);
14269 SDT.superclass._destroyContainerEl.call(this, elContainer);
14270 this._elHdContainer = null;
14271 this._elBdContainer = null;
14275 * Initializes the DataTable outer container element and creates inner header
14276 * and body container elements.
14278 * @method _initContainerEl
14279 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
14282 _initContainerEl : function(elContainer) {
14283 SDT.superclass._initContainerEl.call(this, elContainer);
14285 if(this._elContainer) {
14286 elContainer = this._elContainer; // was constructor input, now is DOM ref
14287 Dom.addClass(elContainer, DT.CLASS_SCROLLABLE);
14289 // Container for header TABLE
14290 var elHdContainer = document.createElement("div");
14291 elHdContainer.style.width = this.get("width") || "";
14292 elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER");
14293 Dom.addClass(elHdContainer, SDT.CLASS_HEADER);
14294 this._elHdContainer = elHdContainer;
14295 elContainer.appendChild(elHdContainer);
14297 // Container for body TABLE
14298 var elBdContainer = document.createElement("div");
14299 elBdContainer.style.width = this.get("width") || "";
14300 elBdContainer.style.height = this.get("height") || "";
14301 Dom.addClass(elBdContainer, SDT.CLASS_BODY);
14302 Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers
14303 this._elBdContainer = elBdContainer;
14304 elContainer.appendChild(elBdContainer);
14309 * Creates HTML markup CAPTION element.
14311 * @method _initCaptionEl
14312 * @param sCaption {String} Text for caption.
14315 _initCaptionEl : function(sCaption) {
14316 // Not yet supported
14317 /*if(this._elHdTable && sCaption) {
14318 // Create CAPTION element
14319 if(!this._elCaption) {
14320 this._elCaption = this._elHdTable.createCaption();
14322 // Set CAPTION value
14323 this._elCaption.innerHTML = sCaption;
14325 else if(this._elCaption) {
14326 this._elCaption.parentNode.removeChild(this._elCaption);
14331 * Destroy's the DataTable head TABLE element, if available.
14333 * @method _destroyHdTableEl
14336 _destroyHdTableEl : function() {
14337 var elTable = this._elHdTable;
14339 Ev.purgeElement(elTable, true);
14340 elTable.parentNode.removeChild(elTable);
14342 // A little out of place, but where else can we null out these extra elements?
14343 ///this._elBdColgroup = null;
14344 this._elBdThead = null;
14349 * Initializes ScrollingDataTable TABLE elements into the two inner containers.
14351 * @method _initTableEl
14354 _initTableEl : function() {
14356 if(this._elHdContainer) {
14357 this._destroyHdTableEl();
14360 this._elHdTable = this._elHdContainer.appendChild(document.createElement("table"));
14363 SDT.superclass._initTableEl.call(this, this._elBdContainer);
14367 * Initializes ScrollingDataTable THEAD elements into the two inner containers.
14369 * @method _initTheadEl
14370 * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference.
14371 * @param elTable {HTMLElement} (optional) TABLE element reference.
14374 _initTheadEl : function(elHdTable, elTable) {
14375 elHdTable = elHdTable || this._elHdTable;
14376 elTable = elTable || this._elTable;
14378 // Scrolling body's THEAD
14379 this._initBdTheadEl(elTable);
14380 // Standard fixed head THEAD
14381 SDT.superclass._initTheadEl.call(this, elHdTable);
14385 * SDT changes ID so as not to duplicate the accessibility TH IDs.
14387 * @method _initThEl
14388 * @param elTh {HTMLElement} TH element reference.
14389 * @param oColumn {YAHOO.widget.Column} Column object.
14392 _initThEl : function(elTh, oColumn) {
14393 SDT.superclass._initThEl.call(this, elTh, oColumn);
14394 elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD
14398 * Destroy's the DataTable body THEAD element, if available.
14400 * @method _destroyBdTheadEl
14403 _destroyBdTheadEl : function() {
14404 var elBdThead = this._elBdThead;
14406 var elTable = elBdThead.parentNode;
14407 Ev.purgeElement(elBdThead, true);
14408 elTable.removeChild(elBdThead);
14409 this._elBdThead = null;
14411 this._destroyColumnHelpers();
14416 * Initializes body THEAD element.
14418 * @method _initBdTheadEl
14419 * @param elTable {HTMLElement} TABLE element into which to create THEAD.
14420 * @return {HTMLElement} Initialized THEAD element.
14423 _initBdTheadEl : function(elTable) {
14425 // Destroy previous
14426 this._destroyBdTheadEl();
14428 var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild);
14430 // Add TRs to the THEAD;
14431 var oColumnSet = this._oColumnSet,
14432 colTree = oColumnSet.tree,
14433 elTh, elTheadTr, oColumn, i, j, k, len;
14435 for(i=0, k=colTree.length; i<k; i++) {
14436 elTheadTr = elThead.appendChild(document.createElement("tr"));
14438 // ...and create TH cells
14439 for(j=0, len=colTree[i].length; j<len; j++) {
14440 oColumn = colTree[i][j];
14441 elTh = elTheadTr.appendChild(document.createElement("th"));
14442 this._initBdThEl(elTh,oColumn,i,j);
14445 this._elBdThead = elThead;
14450 * Populates TH element for the body THEAD element.
14452 * @method _initBdThEl
14453 * @param elTh {HTMLElement} TH element reference.
14454 * @param oColumn {YAHOO.widget.Column} Column object.
14457 _initBdThEl : function(elTh, oColumn) {
14458 elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility
14459 elTh.rowSpan = oColumn.getRowspan();
14460 elTh.colSpan = oColumn.getColspan();
14461 // Assign abbr attribute
14463 elTh.abbr = oColumn.abbr;
14466 // TODO: strip links and form elements
14467 var sKey = oColumn.getKey();
14468 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
14469 elTh.innerHTML = sLabel;
14473 * Initializes ScrollingDataTable TBODY element for data
14475 * @method _initTbodyEl
14476 * @param elTable {HTMLElement} TABLE element into which to create TBODY .
14479 _initTbodyEl : function(elTable) {
14480 SDT.superclass._initTbodyEl.call(this, elTable);
14482 // Bug 2105534 - Safari 3 gap
14483 // Bug 2492591 - IE8 offsetTop
14484 elTable.style.marginTop = (this._elTbody.offsetTop > 0) ?
14485 "-"+this._elTbody.offsetTop+"px" : 0;
14517 * Sets focus on the given element.
14520 * @param el {HTMLElement} Element.
14523 _focusEl : function(el) {
14524 el = el || this._elTbody;
14526 this._storeScrollPositions();
14527 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
14528 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
14529 // strange unexpected things as the user clicks on buttons and other controls.
14531 // Bug 1921135: Wrap the whole thing in a setTimeout
14532 setTimeout(function() {
14533 setTimeout(function() {
14536 oSelf._restoreScrollPositions();
14563 * Internal wrapper calls run() on render Chain instance.
14565 * @method _runRenderChain
14568 _runRenderChain : function() {
14569 this._storeScrollPositions();
14570 this._oChainRender.run();
14574 * Stores scroll positions so they can be restored after a render.
14576 * @method _storeScrollPositions
14579 _storeScrollPositions : function() {
14580 this._nScrollTop = this._elBdContainer.scrollTop;
14581 this._nScrollLeft = this._elBdContainer.scrollLeft;
14585 * Restores scroll positions to stored value.
14587 * @method _retoreScrollPositions
14590 _restoreScrollPositions : function() {
14591 // Reset scroll positions
14592 if(this._nScrollTop) {
14593 this._elBdContainer.scrollTop = this._nScrollTop;
14594 this._nScrollTop = null;
14596 if(this._nScrollLeft) {
14597 this._elBdContainer.scrollLeft = this._nScrollLeft;
14598 this._nScrollLeft = null;
14603 * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable.
14605 * @method _validateColumnWidth
14606 * @param oColumn {YAHOO.widget.Column} Column instance.
14607 * @param elTd {HTMLElement} TD element to validate against.
14610 _validateColumnWidth : function(oColumn, elTd) {
14611 // Only Columns without widths that are not hidden
14612 if(!oColumn.width && !oColumn.hidden) {
14613 var elTh = oColumn.getThEl();
14614 // Unset a calculated auto-width
14615 if(oColumn._calculatedWidth) {
14616 this._setColumnWidth(oColumn, "auto", "visible");
14618 // Compare auto-widths
14619 if(elTh.offsetWidth !== elTd.offsetWidth) {
14620 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14621 oColumn.getThLinerEl() : elTd.firstChild;
14623 // Grab the wider liner width, unless the minWidth is wider
14624 var newWidth = Math.max(0,
14625 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14628 var sOverflow = 'visible';
14630 // Now validate against maxAutoWidth
14631 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14632 newWidth = oColumn.maxAutoWidth;
14633 sOverflow = "hidden";
14636 // Set to the wider auto-width
14637 this._elTbody.style.display = "none";
14638 this._setColumnWidth(oColumn, newWidth+'px', sOverflow);
14639 oColumn._calculatedWidth = newWidth;
14640 this._elTbody.style.display = "";
14646 * For one or all Columns of a ScrollingDataTable, when Column is not hidden,
14647 * and width is not set, syncs widths of header and body cells and
14648 * validates that width against minWidth and/or maxAutoWidth as necessary.
14650 * @method validateColumnWidths
14651 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
14653 validateColumnWidths : function(oColumn) {
14654 // Validate there is at least one TR with proper TDs
14655 var allKeys = this._oColumnSet.keys,
14656 allKeysLength = allKeys.length,
14657 elRow = this.getFirstTrEl();
14659 // Reset overhang for IE
14661 this._setOverhangValue(1);
14664 if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) {
14665 // Temporarily unsnap container since it causes inaccurate calculations
14666 var sWidth = this.get("width");
14668 this._elHdContainer.style.width = "";
14669 this._elBdContainer.style.width = "";
14671 this._elContainer.style.width = "";
14673 //Validate just one Column
14674 if(oColumn && lang.isNumber(oColumn.getKeyIndex())) {
14675 this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]);
14677 // Iterate through all Columns to unset calculated widths in one pass
14679 var elTd, todos = [], thisTodo, i, len;
14680 for(i=0; i<allKeysLength; i++) {
14681 oColumn = allKeys[i];
14682 // Only Columns without widths that are not hidden, unset a calculated auto-width
14683 if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) {
14684 todos[todos.length] = oColumn;
14688 this._elTbody.style.display = "none";
14689 for(i=0, len=todos.length; i<len; i++) {
14690 this._setColumnWidth(todos[i], "auto", "visible");
14692 this._elTbody.style.display = "";
14696 // Iterate through all Columns and make the store the adjustments to make in one pass
14697 for(i=0; i<allKeysLength; i++) {
14698 oColumn = allKeys[i];
14699 elTd = elRow.childNodes[i];
14700 // Only Columns without widths that are not hidden
14701 if(!oColumn.width && !oColumn.hidden) {
14702 var elTh = oColumn.getThEl();
14704 // Compare auto-widths
14705 if(elTh.offsetWidth !== elTd.offsetWidth) {
14706 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14707 oColumn.getThLinerEl() : elTd.firstChild;
14709 // Grab the wider liner width, unless the minWidth is wider
14710 var newWidth = Math.max(0,
14711 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14714 var sOverflow = 'visible';
14716 // Now validate against maxAutoWidth
14717 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14718 newWidth = oColumn.maxAutoWidth;
14719 sOverflow = "hidden";
14722 todos[todos.length] = [oColumn, newWidth, sOverflow];
14727 this._elTbody.style.display = "none";
14728 for(i=0, len=todos.length; i<len; i++) {
14729 thisTodo = todos[i];
14730 // Set to the wider auto-width
14731 this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]);
14732 thisTodo[0]._calculatedWidth = thisTodo[1];
14734 this._elTbody.style.display = "";
14737 // Resnap unsnapped containers
14739 this._elHdContainer.style.width = sWidth;
14740 this._elBdContainer.style.width = sWidth;
14744 this._syncScroll();
14745 this._restoreScrollPositions();
14749 * Syncs padding around scrollable tables, including Column header right-padding
14750 * and container width and height.
14752 * @method _syncScroll
14755 _syncScroll : function() {
14756 this._syncScrollX();
14757 this._syncScrollY();
14758 this._syncScrollOverhang();
14761 this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft;
14762 if(!this.get("width")) {
14764 document.body.style += '';
14770 * Snaps container width for y-scrolling tables.
14772 * @method _syncScrollY
14775 _syncScrollY : function() {
14776 var elTbody = this._elTbody,
14777 elBdContainer = this._elBdContainer;
14779 // X-scrolling not enabled
14780 if(!this.get("width")) {
14781 // Snap outer container width to content
14782 this._elContainer.style.width =
14783 (elBdContainer.scrollHeight > elBdContainer.clientHeight) ?
14784 // but account for y-scrollbar since it is visible
14785 (elTbody.parentNode.clientWidth + 19) + "px" :
14786 // no y-scrollbar, just borders
14787 (elTbody.parentNode.clientWidth + 2) + "px";
14792 * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
14794 * @method _syncScrollX
14797 _syncScrollX : function() {
14798 var elTbody = this._elTbody,
14799 elBdContainer = this._elBdContainer;
14801 // IE 6 and 7 only when y-scrolling not enabled
14802 if(!this.get("height") && (ua.ie)) {
14803 // Snap outer container height to content
14804 elBdContainer.style.height =
14805 // but account for x-scrollbar if it is visible
14806 (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ?
14807 (elTbody.parentNode.offsetHeight + 18) + "px" :
14808 elTbody.parentNode.offsetHeight + "px";
14811 // Sync message tbody
14812 if(this._elTbody.rows.length === 0) {
14813 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
14816 this._elMsgTbody.parentNode.style.width = "";
14821 * Adds/removes Column header overhang as necesary.
14823 * @method _syncScrollOverhang
14826 _syncScrollOverhang : function() {
14827 var elBdContainer = this._elBdContainer,
14828 // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table
14831 // Y-scrollbar is visible, which is when the overhang needs to jut out
14832 if((elBdContainer.scrollHeight > elBdContainer.clientHeight) &&
14833 // X-scrollbar is also visible, which means the right is jagged, not flush with the Column
14834 (elBdContainer.scrollWidth > elBdContainer.clientWidth)) {
14838 this._setOverhangValue(nPadding);
14843 * Sets Column header overhang to given width.
14845 * @method _setOverhangValue
14846 * @param nBorderWidth {Number} Value of new border for overhang.
14849 _setOverhangValue : function(nBorderWidth) {
14850 var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [],
14851 len = aLastHeaders.length,
14852 sPrefix = this._sId+"-fixedth-",
14853 sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER");
14855 this._elThead.style.display = "none";
14856 for(var i=0; i<len; i++) {
14857 Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue;
14859 this._elThead.style.display = "";
14900 * Returns DOM reference to the DataTable's fixed header container element.
14902 * @method getHdContainerEl
14903 * @return {HTMLElement} Reference to DIV element.
14905 getHdContainerEl : function() {
14906 return this._elHdContainer;
14910 * Returns DOM reference to the DataTable's scrolling body container element.
14912 * @method getBdContainerEl
14913 * @return {HTMLElement} Reference to DIV element.
14915 getBdContainerEl : function() {
14916 return this._elBdContainer;
14920 * Returns DOM reference to the DataTable's fixed header TABLE element.
14922 * @method getHdTableEl
14923 * @return {HTMLElement} Reference to TABLE element.
14925 getHdTableEl : function() {
14926 return this._elHdTable;
14930 * Returns DOM reference to the DataTable's scrolling body TABLE element.
14932 * @method getBdTableEl
14933 * @return {HTMLElement} Reference to TABLE element.
14935 getBdTableEl : function() {
14936 return this._elTable;
14940 * Disables ScrollingDataTable UI.
14944 disable : function() {
14945 var elMask = this._elMask;
14946 elMask.style.width = this._elBdContainer.offsetWidth + "px";
14947 elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px";
14948 elMask.style.display = "";
14949 this.fireEvent("disableEvent");
14953 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
14954 * non-nested Columns, and top-level parent Columns (which will remove all
14955 * children Columns).
14957 * @method removeColumn
14958 * @param oColumn {YAHOO.widget.Column} Column instance.
14959 * @return oColumn {YAHOO.widget.Column} Removed Column instance.
14961 removeColumn : function(oColumn) {
14962 // Store scroll pos
14963 var hdPos = this._elHdContainer.scrollLeft;
14964 var bdPos = this._elBdContainer.scrollLeft;
14966 // Call superclass method
14967 oColumn = SDT.superclass.removeColumn.call(this, oColumn);
14969 // Restore scroll pos
14970 this._elHdContainer.scrollLeft = hdPos;
14971 this._elBdContainer.scrollLeft = bdPos;
14977 * Inserts given Column at the index if given, otherwise at the end. NOTE: You
14978 * can only add non-nested Columns and top-level parent Columns. You cannot add
14979 * a nested Column to an existing parent.
14981 * @method insertColumn
14982 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
14983 * definition or a Column instance.
14984 * @param index {Number} (optional) New tree index.
14985 * @return oColumn {YAHOO.widget.Column} Inserted Column instance.
14987 insertColumn : function(oColumn, index) {
14988 // Store scroll pos
14989 var hdPos = this._elHdContainer.scrollLeft;
14990 var bdPos = this._elBdContainer.scrollLeft;
14992 // Call superclass method
14993 var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index);
14995 // Restore scroll pos
14996 this._elHdContainer.scrollLeft = hdPos;
14997 this._elBdContainer.scrollLeft = bdPos;
15003 * Removes given Column and inserts into given tree index. NOTE: You
15004 * can only reorder non-nested Columns and top-level parent Columns. You cannot
15005 * reorder a nested Column to an existing parent.
15007 * @method reorderColumn
15008 * @param oColumn {YAHOO.widget.Column} Column instance.
15009 * @param index {Number} New tree index.
15011 reorderColumn : function(oColumn, index) {
15012 // Store scroll pos
15013 var hdPos = this._elHdContainer.scrollLeft;
15014 var bdPos = this._elBdContainer.scrollLeft;
15016 // Call superclass method
15017 var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index);
15019 // Restore scroll pos
15020 this._elHdContainer.scrollLeft = hdPos;
15021 this._elBdContainer.scrollLeft = bdPos;
15027 * Sets given Column to given pixel width. If new width is less than minWidth
15028 * width, sets to minWidth. Updates oColumn.width value.
15030 * @method setColumnWidth
15031 * @param oColumn {YAHOO.widget.Column} Column instance.
15032 * @param nWidth {Number} New width in pixels.
15034 setColumnWidth : function(oColumn, nWidth) {
15035 oColumn = this.getColumn(oColumn);
15037 // Validate new width against minWidth
15038 if(lang.isNumber(nWidth)) {
15039 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
15042 oColumn.width = nWidth;
15044 // Resize the DOM elements
15045 this._setColumnWidth(oColumn, nWidth+"px");
15046 this._syncScroll();
15048 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
15050 // Unsets a width to auto-size
15051 else if(nWidth === null) {
15053 oColumn.width = nWidth;
15055 // Resize the DOM elements
15056 this._setColumnWidth(oColumn, "auto");
15057 this.validateColumnWidths(oColumn);
15058 this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
15061 // Bug 2339454: resize then sort misaligment
15062 this._clearTrTemplateEl();
15069 * Displays message within secondary TBODY.
15071 * @method showTableMessage
15072 * @param sHTML {String} (optional) Value for innerHTMlang.
15073 * @param sClassName {String} (optional) Classname.
15075 showTableMessage : function(sHTML, sClassName) {
15076 var elCell = this._elMsgTd;
15077 if(lang.isString(sHTML)) {
15078 elCell.firstChild.innerHTML = sHTML;
15080 if(lang.isString(sClassName)) {
15081 Dom.addClass(elCell.firstChild, sClassName);
15084 // Needed for SDT only
15085 var elThead = this.getTheadEl();
15086 var elTable = elThead.parentNode;
15087 var newWidth = elTable.offsetWidth;
15088 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
15090 this._elMsgTbody.style.display = "";
15092 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
15107 /////////////////////////////////////////////////////////////////////////////
15109 // Private Custom Event Handlers
15111 /////////////////////////////////////////////////////////////////////////////
15114 * Handles Column mutations
15116 * @method onColumnChange
15117 * @param oArgs {Object} Custom Event data.
15119 _onColumnChange : function(oArg) {
15120 // Figure out which Column changed
15121 var oColumn = (oArg.column) ? oArg.column :
15122 (oArg.editor) ? oArg.editor.column : null;
15123 this._storeScrollPositions();
15124 this.validateColumnWidths(oColumn);
15141 /////////////////////////////////////////////////////////////////////////////
15143 // Private DOM Event Handlers
15145 /////////////////////////////////////////////////////////////////////////////
15148 * Syncs scrolltop and scrollleft of all TABLEs.
15150 * @method _onScroll
15151 * @param e {HTMLEvent} The scroll event.
15152 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15155 _onScroll : function(e, oSelf) {
15156 oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft;
15158 if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
15159 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
15160 oSelf.cancelCellEditor();
15163 var elTarget = Ev.getTarget(e);
15164 var elTag = elTarget.nodeName.toLowerCase();
15165 oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
15169 * Handles keydown events on the THEAD element.
15171 * @method _onTheadKeydown
15172 * @param e {HTMLEvent} The key event.
15173 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15176 _onTheadKeydown : function(e, oSelf) {
15177 // If tabbing to next TH label link causes THEAD to scroll,
15178 // need to sync scrollLeft with TBODY
15179 if(Ev.getCharCode(e) === 9) {
15180 setTimeout(function() {
15181 if((oSelf instanceof SDT) && oSelf._sId) {
15182 oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft;
15187 var elTarget = Ev.getTarget(e);
15188 var elTag = elTarget.nodeName.toLowerCase();
15189 var bKeepBubbling = true;
15190 while(elTarget && (elTag != "table")) {
15196 // TODO: implement textareaKeyEvent
15199 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
15204 if(bKeepBubbling === false) {
15208 elTarget = elTarget.parentNode;
15210 elTag = elTarget.nodeName.toLowerCase();
15214 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
15221 * Fired when a fixed scrolling DataTable has a scroll.
15223 * @event tableScrollEvent
15224 * @param oArgs.event {HTMLEvent} The event object.
15225 * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
15226 * or the DataTable's TBODY element (everyone else).
15239 var lang = YAHOO.lang,
15241 widget = YAHOO.widget,
15247 DT = widget.DataTable;
15248 /****************************************************************************/
15249 /****************************************************************************/
15250 /****************************************************************************/
15253 * The BaseCellEditor class provides base functionality common to all inline cell
15254 * editors for a DataTable widget.
15256 * @namespace YAHOO.widget
15257 * @class BaseCellEditor
15258 * @uses YAHOO.util.EventProvider
15260 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
15261 * @param oConfigs {Object} (Optional) Object literal of configs.
15263 widget.BaseCellEditor = function(sType, oConfigs) {
15264 this._sId = this._sId || "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++;
15265 this._sType = sType;
15268 this._initConfigs(oConfigs);
15270 // Create Custom Events
15271 this._initEvents();
15277 var BCE = widget.BaseCellEditor;
15279 /////////////////////////////////////////////////////////////////////////////
15283 /////////////////////////////////////////////////////////////////////////////
15284 lang.augmentObject(BCE, {
15287 * Global instance counter.
15289 * @property CellEditor._nCount
15298 * Class applied to CellEditor container.
15300 * @property CellEditor.CLASS_CELLEDITOR
15303 * @default "yui-ceditor"
15305 CLASS_CELLEDITOR : "yui-ceditor"
15310 /////////////////////////////////////////////////////////////////////////////
15314 /////////////////////////////////////////////////////////////////////////////
15316 * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique
15317 * DOM ID strings and log messages.
15335 * DataTable instance.
15337 * @property _oDataTable
15338 * @type YAHOO.widget.DataTable
15341 _oDataTable : null,
15346 * @property _oColumn
15347 * @type YAHOO.widget.Column
15355 * @property _oRecord
15356 * @type YAHOO.widget.Record
15366 * @type HTMLElement
15373 * Container for inline editor.
15375 * @property _elContainer
15376 * @type HTMLElement
15379 _elContainer : null,
15382 * Reference to Cancel button, if available.
15384 * @property _elCancelBtn
15385 * @type HTMLElement
15389 _elCancelBtn : null,
15392 * Reference to Save button, if available.
15394 * @property _elSaveBtn
15395 * @type HTMLElement
15408 /////////////////////////////////////////////////////////////////////////////
15412 /////////////////////////////////////////////////////////////////////////////
15415 * Initialize configs.
15417 * @method _initConfigs
15420 _initConfigs : function(oConfigs) {
15421 // Object literal defines CellEditor configs
15422 if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
15423 for(var sConfig in oConfigs) {
15425 this[sConfig] = oConfigs[sConfig];
15432 * Initialize Custom Events.
15434 * @method _initEvents
15437 _initEvents : function() {
15438 this.createEvent("showEvent");
15439 this.createEvent("keydownEvent");
15440 this.createEvent("invalidDataEvent");
15441 this.createEvent("revertEvent");
15442 this.createEvent("saveEvent");
15443 this.createEvent("cancelEvent");
15444 this.createEvent("blurEvent");
15445 this.createEvent("blockEvent");
15446 this.createEvent("unblockEvent");
15461 /////////////////////////////////////////////////////////////////////////////
15463 // Public properties
15465 /////////////////////////////////////////////////////////////////////////////
15467 * Implementer defined function that can submit the input value to a server. This
15468 * function must accept the arguments fnCallback and oNewValue. When the submission
15469 * is complete, the function must also call fnCallback(bSuccess, oNewValue) to
15470 * finish the save routine in the CellEditor. This function can also be used to
15471 * perform extra validation or input value manipulation.
15473 * @property asyncSubmitter
15474 * @type HTMLFunction
15476 asyncSubmitter : null,
15487 * Default value in case Record data is undefined. NB: Null values will not trigger
15488 * the default value.
15490 * @property defaultValue
15494 defaultValue : null,
15497 * Validator function for input data, called from the DataTable instance scope,
15498 * receives the arguments (inputValue, currentValue, editorInstance) and returns
15499 * either the validated (or type-converted) value or undefined.
15501 * @property validator
15502 * @type HTMLFunction
15508 * If validation is enabled, resets input field of invalid data.
15510 * @property resetInvalidData
15514 resetInvalidData : true,
15517 * True if currently active.
15519 * @property isActive
15525 * Text to display on Save button.
15527 * @property LABEL_SAVE
15531 LABEL_SAVE : "Save",
15534 * Text to display on Cancel button.
15536 * @property LABEL_CANCEL
15538 * @default "Cancel"
15540 LABEL_CANCEL : "Cancel",
15543 * True if Save/Cancel buttons should not be displayed in the CellEditor.
15545 * @property disableBtns
15549 disableBtns : false,
15557 /////////////////////////////////////////////////////////////////////////////
15561 /////////////////////////////////////////////////////////////////////////////
15563 * CellEditor instance name, for logging.
15566 * @return {String} Unique name of the CellEditor instance.
15569 toString : function() {
15570 return "CellEditor instance " + this._sId;
15574 * CellEditor unique ID.
15577 * @return {String} Unique ID of the CellEditor instance.
15580 getId : function() {
15585 * Returns reference to associated DataTable instance.
15587 * @method getDataTable
15588 * @return {YAHOO.widget.DataTable} DataTable instance.
15591 getDataTable : function() {
15592 return this._oDataTable;
15596 * Returns reference to associated Column instance.
15598 * @method getColumn
15599 * @return {YAHOO.widget.Column} Column instance.
15602 getColumn : function() {
15603 return this._oColumn;
15607 * Returns reference to associated Record instance.
15609 * @method getRecord
15610 * @return {YAHOO.widget.Record} Record instance.
15613 getRecord : function() {
15614 return this._oRecord;
15620 * Returns reference to associated TD element.
15623 * @return {HTMLElement} TD element.
15626 getTdEl : function() {
15631 * Returns container element.
15633 * @method getContainerEl
15634 * @return {HTMLElement} Reference to container element.
15637 getContainerEl : function() {
15638 return this._elContainer;
15642 * Nulls out the entire CellEditor instance and related objects, removes attached
15643 * event listeners, and clears out DOM elements inside the container, removes
15644 * container from the DOM.
15648 destroy : function() {
15649 this.unsubscribeAll();
15651 // Column is late-binding in attach()
15652 var oColumn = this.getColumn();
15654 oColumn.editor = null;
15657 var elContainer = this.getContainerEl();
15658 Ev.purgeElement(elContainer, true);
15659 elContainer.parentNode.removeChild(elContainer);
15663 * Renders DOM elements and attaches event listeners.
15667 render : function() {
15668 if(this._elContainer) {
15669 YAHOO.util.Event.purgeElement(this._elContainer, true);
15670 this._elContainer.innerHTML = "";
15673 // Render Cell Editor container element as first child of body
15674 var elContainer = document.createElement("div");
15675 elContainer.id = this.getId() + "-container"; // Needed for tracking blur event
15676 elContainer.style.display = "none";
15677 elContainer.tabIndex = 0;
15678 elContainer.className = DT.CLASS_EDITOR;
15679 document.body.insertBefore(elContainer, document.body.firstChild);
15680 this._elContainer = elContainer;
15683 Ev.addListener(elContainer, "keydown", function(e, oSelf) {
15684 // ESC cancels Cell Editor
15685 if((e.keyCode == 27)) {
15686 var target = Ev.getTarget(e);
15687 // workaround for Mac FF3 bug that disabled clicks when ESC hit when
15688 // select is open. [bug 2273056]
15689 if (target.nodeName && target.nodeName.toLowerCase() === 'select') {
15694 // Pass through event
15695 oSelf.fireEvent("keydownEvent", {editor:this, event:e});
15700 // Show Save/Cancel buttons
15701 if(!this.disableBtns) {
15705 this.doAfterRender();
15709 * Renders Save/Cancel buttons.
15711 * @method renderBtns
15713 renderBtns : function() {
15715 var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div"));
15716 elBtnsDiv.className = DT.CLASS_BUTTON;
15719 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
15720 elSaveBtn.className = DT.CLASS_DEFAULT;
15721 elSaveBtn.innerHTML = this.LABEL_SAVE;
15722 Ev.addListener(elSaveBtn, "click", function(oArgs) {
15725 this._elSaveBtn = elSaveBtn;
15728 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
15729 elCancelBtn.innerHTML = this.LABEL_CANCEL;
15730 Ev.addListener(elCancelBtn, "click", function(oArgs) {
15733 this._elCancelBtn = elCancelBtn;
15737 * Attach CellEditor for a new interaction.
15740 * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance.
15741 * @param elCell {HTMLElement} Cell to edit.
15743 attach : function(oDataTable, elCell) {
15745 if(oDataTable instanceof YAHOO.widget.DataTable) {
15746 this._oDataTable = oDataTable;
15749 elCell = oDataTable.getTdEl(elCell);
15751 this._elTd = elCell;
15754 var oColumn = oDataTable.getColumn(elCell);
15756 this._oColumn = oColumn;
15759 var oRecord = oDataTable.getRecord(elCell);
15761 this._oRecord = oRecord;
15762 var value = oRecord.getData(this.getColumn().getKey());
15763 this.value = (value !== undefined) ? value : this.defaultValue;
15773 * Moves container into position for display.
15777 move : function() {
15779 var elContainer = this.getContainerEl(),
15780 elTd = this.getTdEl(),
15781 x = Dom.getX(elTd),
15782 y = Dom.getY(elTd);
15784 //TODO: remove scrolling logic
15785 // SF doesn't get xy for cells in scrolling table
15786 // when tbody display is set to block
15787 if(isNaN(x) || isNaN(y)) {
15788 var elTbody = this.getDataTable().getTbodyEl();
15789 x = elTd.offsetLeft + // cell pos relative to table
15790 Dom.getX(elTbody.parentNode) - // plus table pos relative to document
15791 elTbody.scrollLeft; // minus tbody scroll
15792 y = elTd.offsetTop + // cell pos relative to table
15793 Dom.getY(elTbody.parentNode) - // plus table pos relative to document
15794 elTbody.scrollTop + // minus tbody scroll
15795 this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells
15798 elContainer.style.left = x + "px";
15799 elContainer.style.top = y + "px";
15803 * Displays CellEditor UI in the correct position.
15807 show : function() {
15809 this.isActive = true;
15810 this.getContainerEl().style.display = "";
15812 this.fireEvent("showEvent", {editor:this});
15820 block : function() {
15821 this.fireEvent("blockEvent", {editor:this});
15825 * Fires unblockEvent
15829 unblock : function() {
15830 this.fireEvent("unblockEvent", {editor:this});
15834 * Saves value of CellEditor and hides UI.
15838 save : function() {
15840 var inputValue = this.getInputValue();
15841 var validValue = inputValue;
15843 // Validate new value
15844 if(this.validator) {
15845 validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this);
15846 if(validValue === undefined ) {
15847 if(this.resetInvalidData) {
15850 this.fireEvent("invalidDataEvent",
15851 {editor:this, oldData:this.value, newData:inputValue});
15857 var finishSave = function(bSuccess, oNewValue) {
15858 var oOrigValue = oSelf.value;
15860 // Update new value
15861 oSelf.value = oNewValue;
15862 oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue);
15865 oSelf.getContainerEl().style.display = "none";
15866 oSelf.isActive = false;
15867 oSelf.getDataTable()._oCellEditor = null;
15869 oSelf.fireEvent("saveEvent",
15870 {editor:oSelf, oldData:oOrigValue, newData:oSelf.value});
15874 oSelf.fireEvent("revertEvent",
15875 {editor:oSelf, oldData:oOrigValue, newData:oNewValue});
15881 if(lang.isFunction(this.asyncSubmitter)) {
15882 this.asyncSubmitter.call(this, finishSave, validValue);
15885 finishSave(true, validValue);
15890 * Cancels CellEditor input and hides UI.
15894 cancel : function() {
15895 if(this.isActive) {
15896 this.getContainerEl().style.display = "none";
15897 this.isActive = false;
15898 this.getDataTable()._oCellEditor = null;
15899 this.fireEvent("cancelEvent", {editor:this});
15906 * Renders form elements.
15908 * @method renderForm
15910 renderForm : function() {
15911 // To be implemented by subclass
15915 * Access to add additional event listeners.
15917 * @method doAfterRender
15919 doAfterRender : function() {
15920 // To be implemented by subclass
15925 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
15926 * to save input without them.
15928 * @method handleDisabledBtns
15930 handleDisabledBtns : function() {
15931 // To be implemented by subclass
15935 * Resets CellEditor UI to initial state.
15937 * @method resetForm
15939 resetForm : function() {
15940 // To be implemented by subclass
15944 * Sets focus in CellEditor.
15948 focus : function() {
15949 // To be implemented by subclass
15953 * Retrieves input value from CellEditor.
15955 * @method getInputValue
15957 getInputValue : function() {
15958 // To be implemented by subclass
15963 lang.augmentProto(BCE, util.EventProvider);
15966 /////////////////////////////////////////////////////////////////////////////
15970 /////////////////////////////////////////////////////////////////////////////
15973 * Fired when a CellEditor is shown.
15976 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15980 * Fired when a CellEditor has a keydown.
15982 * @event keydownEvent
15983 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15984 * @param oArgs.event {HTMLEvent} The event object.
15988 * Fired when a CellEditor input is reverted due to invalid data.
15990 * @event invalidDataEvent
15991 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15992 * @param oArgs.newData {Object} New data value from form input field.
15993 * @param oArgs.oldData {Object} Old data value.
15997 * Fired when a CellEditor input is reverted due to asyncSubmitter failure.
15999 * @event revertEvent
16000 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16001 * @param oArgs.newData {Object} New data value from form input field.
16002 * @param oArgs.oldData {Object} Old data value.
16006 * Fired when a CellEditor input is saved.
16009 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16010 * @param oArgs.newData {Object} New data value from form input field.
16011 * @param oArgs.oldData {Object} Old data value.
16015 * Fired when a CellEditor input is canceled.
16017 * @event cancelEvent
16018 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16022 * Fired when a CellEditor has a blur event.
16025 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16041 /****************************************************************************/
16042 /****************************************************************************/
16043 /****************************************************************************/
16046 * The CheckboxCellEditor class provides functionality for inline editing
16047 * DataTable cell data with checkboxes.
16049 * @namespace YAHOO.widget
16050 * @class CheckboxCellEditor
16051 * @extends YAHOO.widget.BaseCellEditor
16053 * @param oConfigs {Object} (Optional) Object literal of configs.
16055 widget.CheckboxCellEditor = function(oConfigs) {
16056 this._sId = "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16057 widget.CheckboxCellEditor.superclass.constructor.call(this, "checkbox", oConfigs);
16060 // CheckboxCellEditor extends BaseCellEditor
16061 lang.extend(widget.CheckboxCellEditor, BCE, {
16063 /////////////////////////////////////////////////////////////////////////////
16065 // CheckboxCellEditor public properties
16067 /////////////////////////////////////////////////////////////////////////////
16069 * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"])
16070 * or a an array of objects (e.g., [{label:"red", value:"#FF0000"},
16071 * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]).
16073 * @property checkboxOptions
16074 * @type String[] | Object[]
16076 checkboxOptions : null,
16079 * Reference to the checkbox elements.
16081 * @property checkboxes
16082 * @type HTMLElement[]
16087 * Array of checked values
16094 /////////////////////////////////////////////////////////////////////////////
16096 // CheckboxCellEditor public methods
16098 /////////////////////////////////////////////////////////////////////////////
16101 * Render a form with input(s) type=checkbox.
16103 * @method renderForm
16105 renderForm : function() {
16106 if(lang.isArray(this.checkboxOptions)) {
16107 var checkboxOption, checkboxValue, checkboxId, elLabel, j, len;
16109 // Create the checkbox buttons in an IE-friendly way...
16110 for(j=0,len=this.checkboxOptions.length; j<len; j++) {
16111 checkboxOption = this.checkboxOptions[j];
16112 checkboxValue = lang.isValue(checkboxOption.value) ?
16113 checkboxOption.value : checkboxOption;
16115 checkboxId = this.getId() + "-chk" + j;
16116 this.getContainerEl().innerHTML += "<input type=\"checkbox\"" +
16117 " id=\"" + checkboxId + "\"" + // Needed for label
16118 " value=\"" + checkboxValue + "\" />";
16120 // Create the labels in an IE-friendly way
16121 elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16122 elLabel.htmlFor = checkboxId;
16123 elLabel.innerHTML = lang.isValue(checkboxOption.label) ?
16124 checkboxOption.label : checkboxOption;
16127 // Store the reference to the checkbox elements
16128 var allCheckboxes = [];
16129 for(j=0; j<len; j++) {
16130 allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2];
16132 this.checkboxes = allCheckboxes;
16134 if(this.disableBtns) {
16135 this.handleDisabledBtns();
16143 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16144 * to save input without them.
16146 * @method handleDisabledBtns
16148 handleDisabledBtns : function() {
16149 Ev.addListener(this.getContainerEl(), "click", function(v){
16150 if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16158 * Resets CheckboxCellEditor UI to initial state.
16160 * @method resetForm
16162 resetForm : function() {
16163 // Normalize to array
16164 var originalValues = lang.isArray(this.value) ? this.value : [this.value];
16166 // Match checks to value
16167 for(var i=0, j=this.checkboxes.length; i<j; i++) {
16168 this.checkboxes[i].checked = false;
16169 for(var k=0, len=originalValues.length; k<len; k++) {
16170 if(this.checkboxes[i].value === originalValues[k]) {
16171 this.checkboxes[i].checked = true;
16178 * Sets focus in CheckboxCellEditor.
16182 focus : function() {
16183 this.checkboxes[0].focus();
16187 * Retrieves input value from CheckboxCellEditor.
16189 * @method getInputValue
16191 getInputValue : function() {
16192 var checkedValues = [];
16193 for(var i=0, j=this.checkboxes.length; i<j; i++) {
16194 if(this.checkboxes[i].checked) {
16195 checkedValues[checkedValues.length] = this.checkboxes[i].value;
16198 return checkedValues;
16203 // Copy static members to CheckboxCellEditor class
16204 lang.augmentObject(widget.CheckboxCellEditor, BCE);
16213 /****************************************************************************/
16214 /****************************************************************************/
16215 /****************************************************************************/
16218 * The DataCellEditor class provides functionality for inline editing
16219 * DataTable cell data with a YUI Calendar.
16221 * @namespace YAHOO.widget
16222 * @class DateCellEditor
16223 * @extends YAHOO.widget.BaseCellEditor
16225 * @param oConfigs {Object} (Optional) Object literal of configs.
16227 widget.DateCellEditor = function(oConfigs) {
16228 this._sId = "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16229 widget.DateCellEditor.superclass.constructor.call(this, "date", oConfigs);
16232 // CheckboxCellEditor extends BaseCellEditor
16233 lang.extend(widget.DateCellEditor, BCE, {
16235 /////////////////////////////////////////////////////////////////////////////
16237 // DateCellEditor public properties
16239 /////////////////////////////////////////////////////////////////////////////
16241 * Reference to Calendar instance.
16243 * @property calendar
16244 * @type YAHOO.widget.Calendar
16249 * Configs for the calendar instance, to be passed to Calendar constructor.
16251 * @property calendarOptions
16254 calendarOptions : null,
16259 * @property defaultValue
16261 * @default new Date()
16263 defaultValue : new Date(),
16266 /////////////////////////////////////////////////////////////////////////////
16268 // DateCellEditor public methods
16270 /////////////////////////////////////////////////////////////////////////////
16273 * Render a Calendar.
16275 * @method renderForm
16277 renderForm : function() {
16279 if(YAHOO.widget.Calendar) {
16280 var calContainer = this.getContainerEl().appendChild(document.createElement("div"));
16281 calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor
16283 new YAHOO.widget.Calendar(this.getId() + "-date",
16284 calContainer.id, this.calendarOptions);
16286 calContainer.style.cssFloat = "none";
16289 var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div"));
16290 calFloatClearer.style.clear = "both";
16293 this.calendar = calendar;
16295 if(this.disableBtns) {
16296 this.handleDisabledBtns();
16305 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16306 * to save input without them.
16308 * @method handleDisabledBtns
16310 handleDisabledBtns : function() {
16311 this.calendar.selectEvent.subscribe(function(v){
16318 * Resets DateCellEditor UI to initial state.
16320 * @method resetForm
16322 resetForm : function() {
16323 var value = this.value;
16324 var selectedValue = (value.getMonth()+1)+"/"+value.getDate()+"/"+value.getFullYear();
16325 this.calendar.cfg.setProperty("selected",selectedValue,false);
16326 this.calendar.render();
16330 * Sets focus in DateCellEditor.
16334 focus : function() {
16335 // To be impmlemented by subclass
16339 * Retrieves input value from DateCellEditor.
16341 * @method getInputValue
16343 getInputValue : function() {
16344 return this.calendar.getSelectedDates()[0];
16349 // Copy static members to DateCellEditor class
16350 lang.augmentObject(widget.DateCellEditor, BCE);
16360 /****************************************************************************/
16361 /****************************************************************************/
16362 /****************************************************************************/
16365 * The DropdownCellEditor class provides functionality for inline editing
16366 * DataTable cell data a SELECT element.
16368 * @namespace YAHOO.widget
16369 * @class DropdownCellEditor
16370 * @extends YAHOO.widget.BaseCellEditor
16372 * @param oConfigs {Object} (Optional) Object literal of configs.
16374 widget.DropdownCellEditor = function(oConfigs) {
16375 this._sId = "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16376 widget.DropdownCellEditor.superclass.constructor.call(this, "dropdown", oConfigs);
16379 // DropdownCellEditor extends BaseCellEditor
16380 lang.extend(widget.DropdownCellEditor, BCE, {
16382 /////////////////////////////////////////////////////////////////////////////
16384 // DropdownCellEditor public properties
16386 /////////////////////////////////////////////////////////////////////////////
16388 * Array of dropdown values. Can either be a simple array (e.g.,
16389 * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g.,
16390 * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
16391 * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]).
16393 * @property dropdownOptions
16394 * @type String[] | Object[]
16396 dropdownOptions : null,
16399 * Reference to Dropdown element.
16401 * @property dropdown
16402 * @type HTMLElement
16407 /////////////////////////////////////////////////////////////////////////////
16409 // DropdownCellEditor public methods
16411 /////////////////////////////////////////////////////////////////////////////
16414 * Render a form with select element.
16416 * @method renderForm
16418 renderForm : function() {
16419 var elDropdown = this.getContainerEl().appendChild(document.createElement("select"));
16420 elDropdown.style.zoom = 1;
16421 this.dropdown = elDropdown;
16423 if(lang.isArray(this.dropdownOptions)) {
16424 var dropdownOption, elOption;
16425 for(var i=0, j=this.dropdownOptions.length; i<j; i++) {
16426 dropdownOption = this.dropdownOptions[i];
16427 elOption = document.createElement("option");
16428 elOption.value = (lang.isValue(dropdownOption.value)) ?
16429 dropdownOption.value : dropdownOption;
16430 elOption.innerHTML = (lang.isValue(dropdownOption.label)) ?
16431 dropdownOption.label : dropdownOption;
16432 elOption = elDropdown.appendChild(elOption);
16435 if(this.disableBtns) {
16436 this.handleDisabledBtns();
16442 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16443 * to save input without them.
16445 * @method handleDisabledBtns
16447 handleDisabledBtns : function() {
16448 Ev.addListener(this.dropdown, "change", function(v){
16455 * Resets DropdownCellEditor UI to initial state.
16457 * @method resetForm
16459 resetForm : function() {
16460 for(var i=0, j=this.dropdown.options.length; i<j; i++) {
16461 if(this.value === this.dropdown.options[i].value) {
16462 this.dropdown.options[i].selected = true;
16468 * Sets focus in DropdownCellEditor.
16472 focus : function() {
16473 this.getDataTable()._focusEl(this.dropdown);
16477 * Retrieves input value from DropdownCellEditor.
16479 * @method getInputValue
16481 getInputValue : function() {
16482 return this.dropdown.options[this.dropdown.options.selectedIndex].value;
16487 // Copy static members to DropdownCellEditor class
16488 lang.augmentObject(widget.DropdownCellEditor, BCE);
16495 /****************************************************************************/
16496 /****************************************************************************/
16497 /****************************************************************************/
16500 * The RadioCellEditor class provides functionality for inline editing
16501 * DataTable cell data with radio buttons.
16503 * @namespace YAHOO.widget
16504 * @class RadioCellEditor
16505 * @extends YAHOO.widget.BaseCellEditor
16507 * @param oConfigs {Object} (Optional) Object literal of configs.
16509 widget.RadioCellEditor = function(oConfigs) {
16510 this._sId = "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16511 widget.RadioCellEditor.superclass.constructor.call(this, "radio", oConfigs);
16514 // RadioCellEditor extends BaseCellEditor
16515 lang.extend(widget.RadioCellEditor, BCE, {
16517 /////////////////////////////////////////////////////////////////////////////
16519 // RadioCellEditor public properties
16521 /////////////////////////////////////////////////////////////////////////////
16523 * Reference to radio elements.
16526 * @type HTMLElement[]
16531 * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"])
16532 * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1},
16533 * {label:"maybe", value:0}]).
16535 * @property radioOptions
16536 * @type String[] | Object[]
16538 radioOptions : null,
16540 /////////////////////////////////////////////////////////////////////////////
16542 // RadioCellEditor public methods
16544 /////////////////////////////////////////////////////////////////////////////
16547 * Render a form with input(s) type=radio.
16549 * @method renderForm
16551 renderForm : function() {
16552 if(lang.isArray(this.radioOptions)) {
16553 var radioOption, radioValue, radioId, elLabel;
16555 // Create the radio buttons in an IE-friendly way
16556 for(var i=0, len=this.radioOptions.length; i<len; i++) {
16557 radioOption = this.radioOptions[i];
16558 radioValue = lang.isValue(radioOption.value) ?
16559 radioOption.value : radioOption;
16560 radioId = this.getId() + "-radio" + i;
16561 this.getContainerEl().innerHTML += "<input type=\"radio\"" +
16562 " name=\"" + this.getId() + "\"" +
16563 " value=\"" + radioValue + "\"" +
16564 " id=\"" + radioId + "\" />"; // Needed for label
16566 // Create the labels in an IE-friendly way
16567 elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16568 elLabel.htmlFor = radioId;
16569 elLabel.innerHTML = (lang.isValue(radioOption.label)) ?
16570 radioOption.label : radioOption;
16573 // Store the reference to the checkbox elements
16574 var allRadios = [],
16576 for(var j=0; j<len; j++) {
16577 elRadio = this.getContainerEl().childNodes[j*2];
16578 allRadios[allRadios.length] = elRadio;
16580 this.radios = allRadios;
16582 if(this.disableBtns) {
16583 this.handleDisabledBtns();
16591 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16592 * to save input without them.
16594 * @method handleDisabledBtns
16596 handleDisabledBtns : function() {
16597 Ev.addListener(this.getContainerEl(), "click", function(v){
16598 if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16606 * Resets RadioCellEditor UI to initial state.
16608 * @method resetForm
16610 resetForm : function() {
16611 for(var i=0, j=this.radios.length; i<j; i++) {
16612 var elRadio = this.radios[i];
16613 if(this.value === elRadio.value) {
16614 elRadio.checked = true;
16621 * Sets focus in RadioCellEditor.
16625 focus : function() {
16626 for(var i=0, j=this.radios.length; i<j; i++) {
16627 if(this.radios[i].checked) {
16628 this.radios[i].focus();
16635 * Retrieves input value from RadioCellEditor.
16637 * @method getInputValue
16639 getInputValue : function() {
16640 for(var i=0, j=this.radios.length; i<j; i++) {
16641 if(this.radios[i].checked) {
16642 return this.radios[i].value;
16649 // Copy static members to RadioCellEditor class
16650 lang.augmentObject(widget.RadioCellEditor, BCE);
16657 /****************************************************************************/
16658 /****************************************************************************/
16659 /****************************************************************************/
16662 * The TextareaCellEditor class provides functionality for inline editing
16663 * DataTable cell data with a TEXTAREA element.
16665 * @namespace YAHOO.widget
16666 * @class TextareaCellEditor
16667 * @extends YAHOO.widget.BaseCellEditor
16669 * @param oConfigs {Object} (Optional) Object literal of configs.
16671 widget.TextareaCellEditor = function(oConfigs) {
16672 this._sId = "yui-textareaceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16673 widget.TextareaCellEditor.superclass.constructor.call(this, "textarea", oConfigs);
16676 // TextareaCellEditor extends BaseCellEditor
16677 lang.extend(widget.TextareaCellEditor, BCE, {
16679 /////////////////////////////////////////////////////////////////////////////
16681 // TextareaCellEditor public properties
16683 /////////////////////////////////////////////////////////////////////////////
16685 * Reference to textarea element.
16687 * @property textarea
16688 * @type HTMLElement
16693 /////////////////////////////////////////////////////////////////////////////
16695 // TextareaCellEditor public methods
16697 /////////////////////////////////////////////////////////////////////////////
16700 * Render a form with textarea.
16702 * @method renderForm
16704 renderForm : function() {
16705 var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea"));
16706 this.textarea = elTextarea;
16708 if(this.disableBtns) {
16709 this.handleDisabledBtns();
16714 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16715 * to save input without them.
16717 * @method handleDisabledBtns
16719 handleDisabledBtns : function() {
16720 Ev.addListener(this.textarea, "blur", function(v){
16727 * Moves TextareaCellEditor UI to a cell.
16731 move : function() {
16732 this.textarea.style.width = this.getTdEl().offsetWidth + "px";
16733 this.textarea.style.height = "3em";
16734 YAHOO.widget.TextareaCellEditor.superclass.move.call(this);
16738 * Resets TextareaCellEditor UI to initial state.
16740 * @method resetForm
16742 resetForm : function() {
16743 this.textarea.value = this.value;
16747 * Sets focus in TextareaCellEditor.
16751 focus : function() {
16752 // Bug 2303181, Bug 2263600
16753 this.getDataTable()._focusEl(this.textarea);
16754 this.textarea.select();
16758 * Retrieves input value from TextareaCellEditor.
16760 * @method getInputValue
16762 getInputValue : function() {
16763 return this.textarea.value;
16768 // Copy static members to TextareaCellEditor class
16769 lang.augmentObject(widget.TextareaCellEditor, BCE);
16779 /****************************************************************************/
16780 /****************************************************************************/
16781 /****************************************************************************/
16784 * The TextboxCellEditor class provides functionality for inline editing
16785 * DataTable cell data with an INPUT TYPE=TEXT element.
16787 * @namespace YAHOO.widget
16788 * @class TextboxCellEditor
16789 * @extends YAHOO.widget.BaseCellEditor
16791 * @param oConfigs {Object} (Optional) Object literal of configs.
16793 widget.TextboxCellEditor = function(oConfigs) {
16794 this._sId = "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16795 widget.TextboxCellEditor.superclass.constructor.call(this, "textbox", oConfigs);
16798 // TextboxCellEditor extends BaseCellEditor
16799 lang.extend(widget.TextboxCellEditor, BCE, {
16801 /////////////////////////////////////////////////////////////////////////////
16803 // TextboxCellEditor public properties
16805 /////////////////////////////////////////////////////////////////////////////
16807 * Reference to the textbox element.
16809 * @property textbox
16813 /////////////////////////////////////////////////////////////////////////////
16815 // TextboxCellEditor public methods
16817 /////////////////////////////////////////////////////////////////////////////
16820 * Render a form with input type=text.
16822 * @method renderForm
16824 renderForm : function() {
16826 // Bug 1802582: SF3/Mac needs a form element wrapping the input
16827 if(ua.webkit>420) {
16828 elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));
16831 elTextbox = this.getContainerEl().appendChild(document.createElement("input"));
16833 elTextbox.type = "text";
16834 this.textbox = elTextbox;
16836 // Save on enter by default
16837 // Bug: 1802582 Set up a listener on each textbox to track on keypress
16838 // since SF/OP can't preventDefault on keydown
16839 Ev.addListener(elTextbox, "keypress", function(v){
16840 if((v.keyCode === 13)) {
16841 // Prevent form submit
16842 YAHOO.util.Event.preventDefault(v);
16847 if(this.disableBtns) {
16848 // By default this is no-op since enter saves by default
16849 this.handleDisabledBtns();
16854 * Moves TextboxCellEditor UI to a cell.
16858 move : function() {
16859 this.textbox.style.width = this.getTdEl().offsetWidth + "px";
16860 widget.TextboxCellEditor.superclass.move.call(this);
16864 * Resets TextboxCellEditor UI to initial state.
16866 * @method resetForm
16868 resetForm : function() {
16869 this.textbox.value = lang.isValue(this.value) ? this.value.toString() : "";
16873 * Sets focus in TextboxCellEditor.
16877 focus : function() {
16878 // Bug 2303181, Bug 2263600
16879 this.getDataTable()._focusEl(this.textbox);
16880 this.textbox.select();
16884 * Returns new value for TextboxCellEditor.
16886 * @method getInputValue
16888 getInputValue : function() {
16889 return this.textbox.value;
16894 // Copy static members to TextboxCellEditor class
16895 lang.augmentObject(widget.TextboxCellEditor, BCE);
16903 /////////////////////////////////////////////////////////////////////////////
16905 // DataTable extension
16907 /////////////////////////////////////////////////////////////////////////////
16910 * CellEditor subclasses.
16911 * @property DataTable.Editors
16916 checkbox : widget.CheckboxCellEditor,
16917 "date" : widget.DateCellEditor,
16918 dropdown : widget.DropdownCellEditor,
16919 radio : widget.RadioCellEditor,
16920 textarea : widget.TextareaCellEditor,
16921 textbox : widget.TextboxCellEditor
16924 /****************************************************************************/
16925 /****************************************************************************/
16926 /****************************************************************************/
16929 * Factory class for instantiating a BaseCellEditor subclass.
16931 * @namespace YAHOO.widget
16932 * @class CellEditor
16933 * @extends YAHOO.widget.BaseCellEditor
16935 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
16936 * @param oConfigs {Object} (Optional) Object literal of configs.
16938 widget.CellEditor = function(sType, oConfigs) {
16939 // Point to one of the subclasses
16940 if(sType && DT.Editors[sType]) {
16941 lang.augmentObject(BCE, DT.Editors[sType]);
16942 return new DT.Editors[sType](oConfigs);
16945 return new BCE(null, oConfigs);
16949 var CE = widget.CellEditor;
16951 // Copy static members to CellEditor class
16952 lang.augmentObject(CE, BCE);
16957 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.7.0", build: "1799"});