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 // Conditional added for Caja compatibility
153 clearTimeout(this.id);
160 * Stop and clear the Chain's queue after the current execution of the
161 * current callback completes.
163 * @return {Chain} the Chain instance
171 YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);
173 /****************************************************************************/
174 /****************************************************************************/
175 /****************************************************************************/
178 * The ColumnSet class defines and manages a DataTable's Columns,
179 * including nested hierarchies and access to individual Column instances.
181 * @namespace YAHOO.widget
183 * @uses YAHOO.util.EventProvider
185 * @param aDefinitions {Object[]} Array of object literals that define cells in
188 YAHOO.widget.ColumnSet = function(aDefinitions) {
189 this._sId = "yui-cs" + YAHOO.widget.ColumnSet._nCount;
191 // First clone the defs
192 aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions);
193 this._init(aDefinitions);
195 YAHOO.widget.ColumnSet._nCount++;
198 /////////////////////////////////////////////////////////////////////////////
200 // Private member variables
202 /////////////////////////////////////////////////////////////////////////////
205 * Internal class variable to index multiple ColumnSet instances.
207 * @property ColumnSet._nCount
212 YAHOO.widget.ColumnSet._nCount = 0;
214 YAHOO.widget.ColumnSet.prototype = {
216 * Unique instance name.
225 * Array of object literal Column definitions passed to the constructor.
227 * @property _aDefinitions
231 _aDefinitions : null,
233 /////////////////////////////////////////////////////////////////////////////
235 // Public member variables
237 /////////////////////////////////////////////////////////////////////////////
240 * Top-down tree representation of Column hierarchy.
243 * @type YAHOO.widget.Column[]
248 * Flattened representation of all Columns.
251 * @type YAHOO.widget.Column[]
257 * Array of Columns that map one-to-one to a table column.
260 * @type YAHOO.widget.Column[]
266 * ID index of nested parent hierarchies for HEADERS accessibility attribute.
274 /////////////////////////////////////////////////////////////////////////////
278 /////////////////////////////////////////////////////////////////////////////
281 * Initializes ColumnSet instance with data from Column definitions.
284 * @param aDefinitions {Object[]} Array of object literals that define cells in
289 _init : function(aDefinitions) {
290 // DOM tree representation of all Columns
292 // Flat representation of all Columns
294 // Flat representation of only Columns that are meant to display data
296 // Array of HEADERS attribute values for all keys in the "keys" array
299 // Tracks current node list depth being tracked
302 // Internal recursive function to define Column instances
303 var parseColumns = function(nodeList, parent) {
307 // Create corresponding tree node if not already there for this depth
308 if(!tree[nodeDepth]) {
309 tree[nodeDepth] = [];
313 // Parse each node at this depth for attributes and any children
314 for(var j=0; j<nodeList.length; j++) {
315 var currentNode = nodeList[j];
317 // Instantiate a new Column for each node
318 var oColumn = new YAHOO.widget.Column(currentNode);
320 // Cross-reference Column ID back to the original object literal definition
321 currentNode.yuiColumnId = oColumn._sId;
323 // Add the new Column to the flat list
326 // Assign its parent as an attribute, if applicable
328 oColumn._oParent = parent;
331 // The Column has descendants
332 if(YAHOO.lang.isArray(currentNode.children)) {
333 oColumn.children = currentNode.children;
335 // Determine COLSPAN value for this Column
336 var terminalChildNodes = 0;
337 var countTerminalChildNodes = function(ancestor) {
338 var descendants = ancestor.children;
339 // Drill down each branch and count terminal nodes
340 for(var k=0; k<descendants.length; k++) {
341 // Keep drilling down
342 if(YAHOO.lang.isArray(descendants[k].children)) {
343 countTerminalChildNodes(descendants[k]);
345 // Reached branch terminus
347 terminalChildNodes++;
351 countTerminalChildNodes(currentNode);
352 oColumn._nColspan = terminalChildNodes;
354 // Cascade certain properties to children if not defined on their own
355 var currentChildren = currentNode.children;
356 for(var k=0; k<currentChildren.length; k++) {
357 var child = currentChildren[k];
358 if(oColumn.className && (child.className === undefined)) {
359 child.className = oColumn.className;
361 if(oColumn.editor && (child.editor === undefined)) {
362 child.editor = oColumn.editor;
365 if(oColumn.editorOptions && (child.editorOptions === undefined)) {
366 child.editorOptions = oColumn.editorOptions;
368 if(oColumn.formatter && (child.formatter === undefined)) {
369 child.formatter = oColumn.formatter;
371 if(oColumn.resizeable && (child.resizeable === undefined)) {
372 child.resizeable = oColumn.resizeable;
374 if(oColumn.sortable && (child.sortable === undefined)) {
375 child.sortable = oColumn.sortable;
380 if(oColumn.width && (child.width === undefined)) {
381 child.width = oColumn.width;
383 if(oColumn.minWidth && (child.minWidth === undefined)) {
384 child.minWidth = oColumn.minWidth;
386 if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) {
387 child.maxAutoWidth = oColumn.maxAutoWidth;
389 // Backward compatibility
390 if(oColumn.type && (child.type === undefined)) {
391 child.type = oColumn.type;
393 if(oColumn.type && !oColumn.formatter) {
394 oColumn.formatter = oColumn.type;
396 if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
397 oColumn.label = oColumn.text;
401 if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
402 (oColumn.sortOptions.descFunction))) {
406 // The children themselves must also be parsed for Column instances
407 if(!tree[nodeDepth+1]) {
408 tree[nodeDepth+1] = [];
410 parseColumns(currentChildren, oColumn);
412 // This Column does not have any children
414 oColumn._nKeyIndex = keys.length;
415 oColumn._nColspan = 1;
419 // Add the Column to the top-down tree
420 tree[nodeDepth].push(oColumn);
425 // Parse out Column instances from the array of object literals
426 if(YAHOO.lang.isArray(aDefinitions)) {
427 parseColumns(aDefinitions);
430 this._aDefinitions = aDefinitions;
438 // Determine ROWSPAN value for each Column in the tree
439 var parseTreeForRowspan = function(tree) {
444 // Calculate the max depth of descendants for this row
445 var countMaxRowDepth = function(row, tmpRowDepth) {
446 tmpRowDepth = tmpRowDepth || 1;
448 for(var n=0; n<row.length; n++) {
450 // Column has children, so keep counting
451 if(YAHOO.lang.isArray(col.children)) {
453 countMaxRowDepth(col.children, tmpRowDepth);
456 // No children, is it the max depth?
458 if(tmpRowDepth > maxRowDepth) {
459 maxRowDepth = tmpRowDepth;
466 // Count max row depth for each row
467 for(var m=0; m<tree.length; m++) {
468 currentRow = tree[m];
469 countMaxRowDepth(currentRow);
471 // Assign the right ROWSPAN values to each Column in the row
472 for(var p=0; p<currentRow.length; p++) {
473 currentColumn = currentRow[p];
474 if(!YAHOO.lang.isArray(currentColumn.children)) {
475 currentColumn._nRowspan = maxRowDepth;
478 currentColumn._nRowspan = 1;
482 // Reset counter for next row
486 parseTreeForRowspan(tree);
488 // Store tree index values
489 for(i=0; i<tree[0].length; i++) {
490 tree[0][i]._nTreeIndex = i;
493 // Store header relationships in an array for HEADERS attribute
494 var recurseAncestorsForHeaders = function(i, oColumn) {
495 headers[i].push(oColumn.getSanitizedKey());
496 if(oColumn._oParent) {
497 recurseAncestorsForHeaders(i, oColumn._oParent);
500 for(i=0; i<keys.length; i++) {
502 recurseAncestorsForHeaders(i, keys[i]);
503 headers[i] = headers[i].reverse();
506 // Save to the ColumnSet instance
510 this.headers = headers;
513 /////////////////////////////////////////////////////////////////////////////
517 /////////////////////////////////////////////////////////////////////////////
520 * Returns unique name of the ColumnSet instance.
523 * @return {String} Unique name of the ColumnSet instance.
531 * ColumnSet instance name, for logging.
534 * @return {String} Unique name of the ColumnSet instance.
537 toString : function() {
538 return "ColumnSet instance " + this._sId;
542 * Public accessor to the definitions array.
544 * @method getDefinitions
545 * @return {Object[]} Array of object literal Column definitions.
548 getDefinitions : function() {
549 var aDefinitions = this._aDefinitions;
551 // Internal recursive function to define Column instances
552 var parseColumns = function(nodeList, oSelf) {
553 // Parse each node at this depth for attributes and any children
554 for(var j=0; j<nodeList.length; j++) {
555 var currentNode = nodeList[j];
557 // Get the Column for each node
558 var oColumn = oSelf.getColumnById(currentNode.yuiColumnId);
561 // Update the current values
562 var oDefinition = oColumn.getDefinition();
563 for(var name in oDefinition) {
564 if(YAHOO.lang.hasOwnProperty(oDefinition, name)) {
565 currentNode[name] = oDefinition[name];
570 // The Column has descendants
571 if(YAHOO.lang.isArray(currentNode.children)) {
572 // The children themselves must also be parsed for Column instances
573 parseColumns(currentNode.children, oSelf);
578 parseColumns(aDefinitions, this);
579 this._aDefinitions = aDefinitions;
584 * Returns Column instance with given ID.
586 * @method getColumnById
587 * @param column {String} Column ID.
588 * @return {YAHOO.widget.Column} Column instance.
591 getColumnById : function(column) {
592 if(YAHOO.lang.isString(column)) {
593 var allColumns = this.flat;
594 for(var i=allColumns.length-1; i>-1; i--) {
595 if(allColumns[i]._sId === column) {
596 return allColumns[i];
604 * Returns Column instance with given key or ColumnSet key index.
607 * @param column {String | Number} Column key or ColumnSet key index.
608 * @return {YAHOO.widget.Column} Column instance.
611 getColumn : function(column) {
612 if(YAHOO.lang.isNumber(column) && this.keys[column]) {
613 return this.keys[column];
615 else if(YAHOO.lang.isString(column)) {
616 var allColumns = this.flat;
618 for(var i=0; i<allColumns.length; i++) {
619 if(allColumns[i].key === column) {
620 aColumns.push(allColumns[i]);
623 if(aColumns.length === 1) {
626 else if(aColumns.length > 1) {
634 * Public accessor returns array of given Column's desendants (if any), including itself.
636 * @method getDescendants
637 * @parem {YAHOO.widget.Column} Column instance.
638 * @return {Array} Array including the Column itself and all descendants (if any).
640 getDescendants : function(oColumn) {
642 var allDescendants = [];
645 // Recursive function to loop thru all children
646 var parse = function(oParent) {
647 allDescendants.push(oParent);
648 // This Column has children
649 if(oParent.children) {
650 for(i=0; i<oParent.children.length; i++) {
651 parse(oSelf.getColumn(oParent.children[i].key));
657 return allDescendants;
661 /****************************************************************************/
662 /****************************************************************************/
663 /****************************************************************************/
666 * The Column class defines and manages attributes of DataTable Columns
668 * @namespace YAHOO.widget
671 * @param oConfigs {Object} Object literal of definitions.
673 YAHOO.widget.Column = function(oConfigs) {
674 this._sId = "yui-col" + YAHOO.widget.Column._nCount;
676 // Object literal defines Column attributes
677 if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
678 for(var sConfig in oConfigs) {
680 this[sConfig] = oConfigs[sConfig];
685 // Assign a key if not found
686 if(!YAHOO.lang.isValue(this.key)) {
687 this.key = "yui-dt-col" + YAHOO.widget.Column._nCount;
690 // Assign a field if not found, defaults to key
691 if(!YAHOO.lang.isValue(this.field)) {
692 this.field = this.key;
696 YAHOO.widget.Column._nCount++;
698 // Backward compatibility
699 if(this.width && !YAHOO.lang.isNumber(this.width)) {
702 if(this.editor && YAHOO.lang.isString(this.editor)) {
703 this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions);
707 /////////////////////////////////////////////////////////////////////////////
709 // Private member variables
711 /////////////////////////////////////////////////////////////////////////////
713 YAHOO.lang.augmentObject(YAHOO.widget.Column, {
715 * Internal class variable to index multiple Column instances.
717 * @property Column._nCount
724 formatCheckbox : function(elCell, oRecord, oColumn, oData) {
725 YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
728 formatCurrency : function(elCell, oRecord, oColumn, oData) {
729 YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
732 formatDate : function(elCell, oRecord, oColumn, oData) {
733 YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
736 formatEmail : function(elCell, oRecord, oColumn, oData) {
737 YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
740 formatLink : function(elCell, oRecord, oColumn, oData) {
741 YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
744 formatNumber : function(elCell, oRecord, oColumn, oData) {
745 YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
748 formatSelect : function(elCell, oRecord, oColumn, oData) {
749 YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
753 YAHOO.widget.Column.prototype = {
755 * Unique String identifier assigned at instantiation.
764 * Reference to Column's current position index within its ColumnSet's keys
765 * array, if applicable. This property only applies to non-nested and bottom-
766 * level child Columns.
768 * @property _nKeyIndex
775 * Reference to Column's current position index within its ColumnSet's tree
776 * array, if applicable. This property only applies to non-nested and top-
777 * level parent Columns.
779 * @property _nTreeIndex
786 * Number of table cells the Column spans.
788 * @property _nColspan
795 * Number of table rows the Column spans.
797 * @property _nRowspan
804 * Column's parent Column instance, or null.
807 * @type YAHOO.widget.Column
813 * The DOM reference to the associated TH element.
822 * The DOM reference to the associated TH element's liner DIV element.
824 * @property _elThLiner
831 * The DOM reference to the associated TH element's label SPAN element.
833 * @property _elThLabel
840 * The DOM reference to the associated resizerelement (if any).
842 * @property _elResizer
849 * Internal width tracker.
858 * For unreg() purposes, a reference to the Column's DragDrop instance.
861 * @type YAHOO.util.DragDrop
867 * For unreg() purposes, a reference to the Column resizer's DragDrop instance.
869 * @property _ddResizer
870 * @type YAHOO.util.DragDrop
875 /////////////////////////////////////////////////////////////////////////////
877 // Public member variables
879 /////////////////////////////////////////////////////////////////////////////
882 * Unique name, required.
890 * Associated database field, or null.
898 * Text or HTML for display as Column's label in the TH element.
906 * Column head cell ABBR for accessibility.
914 * Array of object literals that define children (nested headers) of a Column.
922 * Column width (in pixels).
930 * Minimum Column width (in pixels).
939 * When a width is not defined for a Column, maxAutoWidth defines an upper
940 * limit that the Column should be auto-sized to. If resizeable is enabled,
941 * users may still resize to a greater width. Most useful for Columns intended
942 * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very
943 * wide Columns from disrupting visual readability by inducing truncation.
945 * @property maxAutoWidth
952 * True if Column is in hidden state.
961 * True if Column is in selected state.
970 * Custom CSS class or array of classes to be applied to every cell in the Column.
972 * @property className
973 * @type String || String[]
978 * Defines a format function.
980 * @property formatter
981 * @type String || HTMLFunction
986 * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
988 * @property currencyOptions
992 currencyOptions : null,
995 * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
997 * @property dateOptions
1004 * Array of dropdown values for formatter:"dropdown" cases. Can either be a simple array (e.g.,
1005 * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g.,
1006 * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
1007 * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]).
1009 * @property dropdownOptions
1010 * @type String[] | Object[]
1012 dropdownOptions : null,
1015 * A CellEditor instance, otherwise Column is not editable.
1018 * @type YAHOO.widget.CellEditor
1023 * True if Column is resizeable, false otherwise. The Drag & Drop Utility is
1024 * required to enable this feature. Only bottom-level and non-nested Columns are
1027 * @property resizeable
1034 * True if Column is sortable, false otherwise.
1036 * @property sortable
1043 * @property sortOptions.defaultOrder
1044 * @deprecated Use sortOptions.defaultDir.
1047 * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
1049 * @property sortOptions.defaultDir
1054 * Custom field to sort on.
1056 * @property sortOptions.field
1061 * Custom sort handler. Signature: sortFunction(a, b, desc, field) where field is the sortOptions.field value
1063 * @property sortOptions.sortFunction
1083 /////////////////////////////////////////////////////////////////////////////
1087 /////////////////////////////////////////////////////////////////////////////
1090 * Returns unique ID string.
1093 * @return {String} Unique ID string.
1095 getId : function() {
1100 * Column instance name, for logging.
1103 * @return {String} Column's unique name.
1105 toString : function() {
1106 return "Column instance " + this._sId;
1110 * Returns object literal definition.
1112 * @method getDefinition
1113 * @return {Object} Object literal definition.
1115 getDefinition : function() {
1116 var oDefinition = {};
1118 // Update the definition
1119 oDefinition.abbr = this.abbr;
1120 oDefinition.className = this.className;
1121 oDefinition.editor = this.editor;
1122 oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
1123 oDefinition.field = this.field;
1124 oDefinition.formatter = this.formatter;
1125 oDefinition.hidden = this.hidden;
1126 oDefinition.key = this.key;
1127 oDefinition.label = this.label;
1128 oDefinition.minWidth = this.minWidth;
1129 oDefinition.maxAutoWidth = this.maxAutoWidth;
1130 oDefinition.resizeable = this.resizeable;
1131 oDefinition.selected = this.selected;
1132 oDefinition.sortable = this.sortable;
1133 oDefinition.sortOptions = this.sortOptions;
1134 oDefinition.width = this.width;
1140 * Returns unique Column key.
1143 * @return {String} Column key.
1145 getKey : function() {
1153 * @return {String} Column field.
1155 getField : function() {
1160 * Returns Column key which has been sanitized for DOM (class and ID) usage
1161 * starts with letter, contains only letters, numbers, hyphen, or period.
1163 * @method getSanitizedKey
1164 * @return {String} Sanitized Column key.
1166 getSanitizedKey : function() {
1167 return this.getKey().replace(/[^\w\-]/g,"");
1171 * Public accessor returns Column's current position index within its
1172 * ColumnSet's keys array, if applicable. Only non-nested and bottom-level
1173 * child Columns will return a value.
1175 * @method getKeyIndex
1176 * @return {Number} Position index, or null.
1178 getKeyIndex : function() {
1179 return this._nKeyIndex;
1183 * Public accessor returns Column's current position index within its
1184 * ColumnSet's tree array, if applicable. Only non-nested and top-level parent
1185 * Columns will return a value;
1187 * @method getTreeIndex
1188 * @return {Number} Position index, or null.
1190 getTreeIndex : function() {
1191 return this._nTreeIndex;
1195 * Public accessor returns Column's parent instance if any, or null otherwise.
1198 * @return {YAHOO.widget.Column} Column's parent instance.
1200 getParent : function() {
1201 return this._oParent;
1205 * Public accessor returns Column's calculated COLSPAN value.
1207 * @method getColspan
1208 * @return {Number} Column's COLSPAN value.
1210 getColspan : function() {
1211 return this._nColspan;
1213 // Backward compatibility
1214 getColSpan : function() {
1215 return this.getColspan();
1219 * Public accessor returns Column's calculated ROWSPAN value.
1221 * @method getRowspan
1222 * @return {Number} Column's ROWSPAN value.
1224 getRowspan : function() {
1225 return this._nRowspan;
1229 * Returns DOM reference to the key TH element.
1232 * @return {HTMLElement} TH element.
1234 getThEl : function() {
1239 * Returns DOM reference to the TH's liner DIV element. Introduced since
1240 * resizeable Columns may have an extra resizer liner, making the DIV liner
1241 * not reliably the TH element's first child.
1243 * @method getThLInerEl
1244 * @return {HTMLElement} TH element.
1246 getThLinerEl : function() {
1247 return this._elThLiner;
1251 * Returns DOM reference to the resizer element, or null.
1253 * @method getResizerEl
1254 * @return {HTMLElement} DIV element.
1256 getResizerEl : function() {
1257 return this._elResizer;
1260 // Backward compatibility
1263 * @deprecated Use getThEl
1265 getColEl : function() {
1266 return this.getThEl();
1268 getIndex : function() {
1269 return this.getKeyIndex();
1271 format : function() {
1275 /****************************************************************************/
1276 /****************************************************************************/
1277 /****************************************************************************/
1280 * Sort static utility to support Column sorting.
1282 * @namespace YAHOO.util
1287 /////////////////////////////////////////////////////////////////////////////
1291 /////////////////////////////////////////////////////////////////////////////
1294 * Comparator function for simple case-insensitive string sorting.
1297 * @param a {Object} First sort argument.
1298 * @param b {Object} Second sort argument.
1299 * @param desc {Boolean} True if sort direction is descending, false if
1300 * sort direction is ascending.
1302 compare: function(a, b, desc) {
1303 if((a === null) || (typeof a == "undefined")) {
1304 if((b === null) || (typeof b == "undefined")) {
1311 else if((b === null) || (typeof b == "undefined")) {
1315 if(a.constructor == String) {
1316 a = a.toLowerCase();
1318 if(b.constructor == String) {
1319 b = b.toLowerCase();
1322 return (desc) ? 1 : -1;
1325 return (desc) ? -1 : 1;
1333 /****************************************************************************/
1334 /****************************************************************************/
1335 /****************************************************************************/
1338 * ColumnDD subclasses DragDrop to support rearrangeable Columns.
1340 * @namespace YAHOO.util
1342 * @extends YAHOO.util.DDProxy
1344 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1345 * @param oColumn {YAHOO.widget.Column} Column instance.
1346 * @param elTh {HTMLElement} TH element reference.
1347 * @param elTarget {HTMLElement} Drag target element.
1349 YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
1350 if(oDataTable && oColumn && elTh && elTarget) {
1351 this.datatable = oDataTable;
1352 this.table = oDataTable.getTableEl();
1353 this.column = oColumn;
1354 this.headCell = elTh;
1355 this.pointer = elTarget;
1356 this.newIndex = null;
1358 this.initFrame(); // Needed for DDProxy
1359 this.invalidHandleTypes = {};
1361 // Set top/bottom padding to account for children of nested columns
1362 this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
1364 YAHOO.util.Event.on(window, 'resize', function() {
1365 this.initConstraints();
1372 if(YAHOO.util.DDProxy) {
1373 YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
1374 initConstraints: function() {
1375 //Get the top, right, bottom and left positions
1376 var region = YAHOO.util.Dom.getRegion(this.table),
1377 //Get the element we are working on
1379 //Get the xy position of it
1380 xy = YAHOO.util.Dom.getXY(el),
1381 //Get the width and height
1382 width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
1383 height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
1384 //Set left to x minus left
1385 left = ((xy[0] - region.left) + 15), //Buffer of 15px
1386 //Set right to right minus x minus width
1387 right = ((region.right - xy[0] - width) + 15);
1389 //Set the constraints based on the above calculations
1390 this.setXConstraint(left, right);
1391 this.setYConstraint(10, 10);
1393 _resizeProxy: function() {
1394 YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this, arguments);
1395 var dragEl = this.getDragEl(),
1398 YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
1399 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
1400 var xy = YAHOO.util.Dom.getXY(el);
1401 YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
1403 YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
1404 YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
1405 YAHOO.util.Dom.setXY(this.dragEl, xy);
1407 onMouseDown: function() {
1408 this.initConstraints();
1409 this.resetConstraints();
1411 clickValidator: function(e) {
1412 if(!this.column.hidden) {
1413 var target = YAHOO.util.Event.getTarget(e);
1414 return ( this.isValidHandleChild(target) &&
1415 (this.id == this.handleElId ||
1416 this.DDM.handleWasClicked(target, this.id)) );
1419 onDragOver: function(ev, id) {
1420 // Validate target as a Column
1421 var target = this.datatable.getColumn(id);
1423 // Validate target as a top-level parent
1424 var targetIndex = target.getTreeIndex();
1425 while((targetIndex === null) && target.getParent()) {
1426 target = target.getParent();
1427 targetIndex = target.getTreeIndex();
1429 if(targetIndex !== null) {
1430 // Are we placing to left or right of target?
1431 var elTarget = target.getThEl();
1432 var newIndex = targetIndex;
1433 var mouseX = YAHOO.util.Event.getPageX(ev),
1434 targetX = YAHOO.util.Dom.getX(elTarget),
1435 midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
1436 currentIndex = this.column.getTreeIndex();
1438 if (mouseX < midX) {
1439 YAHOO.util.Dom.setX(this.pointer, targetX);
1441 var targetWidth = parseInt(elTarget.offsetWidth, 10);
1442 YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
1445 if (targetIndex > currentIndex) {
1451 else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
1452 newIndex = this.datatable.getColumnSet().tree[0].length;
1454 this.newIndex = newIndex;
1458 onDragDrop: function() {
1459 this.datatable.reorderColumn(this.column, this.newIndex);
1461 endDrag: function() {
1462 this.newIndex = null;
1463 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
1468 /****************************************************************************/
1469 /****************************************************************************/
1470 /****************************************************************************/
1473 * ColumnResizer subclasses DragDrop to support resizeable Columns.
1475 * @namespace YAHOO.util
1476 * @class ColumnResizer
1477 * @extends YAHOO.util.DDProxy
1479 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1480 * @param oColumn {YAHOO.widget.Column} Column instance.
1481 * @param elTh {HTMLElement} TH element reference.
1482 * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
1483 * @param elProxy {HTMLElement} Resizer proxy element.
1485 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
1486 if(oDataTable && oColumn && elTh && sHandleId) {
1487 this.datatable = oDataTable;
1488 this.column = oColumn;
1489 this.headCell = elTh;
1490 this.headCellLiner = oColumn.getThLinerEl();
1491 this.resizerLiner = elTh.firstChild;
1492 this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
1493 this.initFrame(); // Needed for proxy
1494 this.resetResizerEl(); // Needed when rowspan > 0
1496 // Set right padding for bug 1858462
1497 this.setPadding(0, 1, 0, 0);
1504 YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
1505 /////////////////////////////////////////////////////////////////////////////
1509 /////////////////////////////////////////////////////////////////////////////
1511 * Resets resizer element.
1513 * @method resetResizerEl
1515 resetResizerEl : function() {
1516 var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
1517 resizerStyle.left = "auto";
1518 resizerStyle.right = 0;
1519 resizerStyle.top = "auto";
1520 resizerStyle.bottom = 0;
1521 resizerStyle.height = this.headCell.offsetHeight+"px";
1524 /////////////////////////////////////////////////////////////////////////////
1526 // Public DOM event handlers
1528 /////////////////////////////////////////////////////////////////////////////
1531 * Handles mouseup events on the Column resizer.
1534 * @param e {string} The mouseup event
1536 onMouseUp : function(e) {
1537 // Reset height of all resizer els in case TH's have changed height
1538 var allKeys = this.datatable.getColumnSet().keys,
1540 for(var i=0, len=allKeys.length; i<len; i++) {
1542 if(col._ddResizer) {
1543 col._ddResizer.resetResizerEl();
1546 this.resetResizerEl();
1548 var el = this.headCellLiner;
1549 var newWidth = el.offsetWidth -
1550 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) -
1551 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0);
1553 this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth});
1557 * Handles mousedown events on the Column resizer.
1559 * @method onMouseDown
1560 * @param e {string} The mousedown event
1562 onMouseDown : function(e) {
1563 this.startWidth = this.headCellLiner.offsetWidth;
1564 this.startX = YAHOO.util.Event.getXY(e)[0];
1565 this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) +
1566 (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
1570 * Custom clickValidator to ensure Column is not in hidden state.
1572 * @method clickValidator
1576 clickValidator : function(e) {
1577 if(!this.column.hidden) {
1578 var target = YAHOO.util.Event.getTarget(e);
1579 return ( this.isValidHandleChild(target) &&
1580 (this.id == this.handleElId ||
1581 this.DDM.handleWasClicked(target, this.id)) );
1586 * Handles start drag on the Column resizer.
1589 * @param e {string} The drag event
1591 startDrag : function() {
1592 // Shrinks height of all resizer els to not hold open TH els
1593 var allKeys = this.datatable.getColumnSet().keys,
1594 thisKey = this.column.getKeyIndex(),
1596 for(var i=0, len=allKeys.length; i<len; i++) {
1598 if(col._ddResizer) {
1599 YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em";
1605 * Handles drag events on the Column resizer.
1608 * @param e {string} The drag event
1610 onDrag : function(e) {
1611 var newX = YAHOO.util.Event.getXY(e)[0];
1612 if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) {
1613 var offsetX = newX - this.startX;
1614 var newWidth = this.startWidth + offsetX - this.nLinerPadding;
1616 this.datatable.setColumnWidth(this.column, newWidth);
1623 /////////////////////////////////////////////////////////////////////////////
1627 /////////////////////////////////////////////////////////////////////////////
1630 * @property editorOptions
1631 * @deprecated Pass configs directly to CellEditor constructor.
1637 var lang = YAHOO.lang,
1639 widget = YAHOO.widget,
1643 DT = widget.DataTable;
1645 /****************************************************************************/
1646 /****************************************************************************/
1647 /****************************************************************************/
1650 * A RecordSet defines and manages a set of Records.
1652 * @namespace YAHOO.widget
1654 * @param data {Object || Object[]} An object literal or an array of data.
1657 YAHOO.widget.RecordSet = function(data) {
1658 // Internal variables
1659 this._sId = "yui-rs" + widget.RecordSet._nCount;
1660 widget.RecordSet._nCount++;
1665 if(lang.isArray(data)) {
1666 this.addRecords(data);
1668 else if(lang.isObject(data)) {
1669 this.addRecord(data);
1675 var RS = widget.RecordSet;
1678 * Internal class variable to name multiple Recordset instances.
1680 * @property RecordSet._nCount
1689 /////////////////////////////////////////////////////////////////////////////
1691 // Private member variables
1693 /////////////////////////////////////////////////////////////////////////////
1695 * Unique String identifier assigned at instantiation.
1704 * Internal counter of how many Records are in the RecordSet.
1709 * @deprecated No longer used
1713 /////////////////////////////////////////////////////////////////////////////
1717 /////////////////////////////////////////////////////////////////////////////
1720 * Adds one Record to the RecordSet at the given index. If index is null,
1721 * then adds the Record to the end of the RecordSet.
1723 * @method _addRecord
1724 * @param oData {Object} An object literal of data.
1725 * @param index {Number} (optional) Position index.
1726 * @return {YAHOO.widget.Record} A Record instance.
1729 _addRecord : function(oData, index) {
1730 var oRecord = new YAHOO.widget.Record(oData);
1732 if(YAHOO.lang.isNumber(index) && (index > -1)) {
1733 this._records.splice(index,0,oRecord);
1736 //index = this.getLength();
1737 //this._records[index] = oRecord;
1738 this._records[this._records.length] = oRecord;
1745 * Sets/replaces one Record to the RecordSet at the given index. Existing
1746 * Records with higher indexes are not shifted. If no index specified, the
1747 * Record is added to the end of the RecordSet.
1749 * @method _setRecord
1750 * @param oData {Object} An object literal of data.
1751 * @param index {Number} (optional) Position index.
1752 * @return {YAHOO.widget.Record} A Record instance.
1755 _setRecord : function(oData, index) {
1756 if (!lang.isNumber(index) || index < 0) {
1757 index = this._records.length;
1759 return (this._records[index] = new widget.Record(oData));
1761 if(lang.isNumber(index) && (index > -1)) {
1762 this._records[index] = oRecord;
1763 if((index+1) > this.getLength()) {
1764 this._length = index+1;
1768 this._records[this.getLength()] = oRecord;
1776 * Deletes Records from the RecordSet at the given index. If range is null,
1777 * then only one Record is deleted.
1779 * @method _deleteRecord
1780 * @param index {Number} Position index.
1781 * @param range {Number} (optional) How many Records to delete
1784 _deleteRecord : function(index, range) {
1785 if(!lang.isNumber(range) || (range < 0)) {
1788 this._records.splice(index, range);
1789 //this._length = this._length - range;
1792 /////////////////////////////////////////////////////////////////////////////
1796 /////////////////////////////////////////////////////////////////////////////
1799 * Returns unique name of the RecordSet instance.
1802 * @return {String} Unique name of the RecordSet instance.
1804 getId : function() {
1809 * Public accessor to the unique name of the RecordSet instance.
1812 * @return {String} Unique name of the RecordSet instance.
1814 toString : function() {
1815 return "RecordSet instance " + this._sId;
1819 * Returns the number of Records held in the RecordSet.
1822 * @return {Number} Number of records in the RecordSet.
1824 getLength : function() {
1825 //return this._length;
1826 return this._records.length;
1830 * Returns Record by ID or RecordSet position index.
1833 * @param record {YAHOO.widget.Record | Number | String} Record instance,
1834 * RecordSet position index, or Record ID.
1835 * @return {YAHOO.widget.Record} Record object.
1837 getRecord : function(record) {
1839 if(record instanceof widget.Record) {
1840 for(i=0; i<this._records.length; i++) {
1841 if(this._records[i] && (this._records[i]._sId === record._sId)) {
1846 else if(lang.isNumber(record)) {
1847 if((record > -1) && (record < this.getLength())) {
1848 return this._records[record];
1851 else if(lang.isString(record)) {
1852 for(i=0; i<this._records.length; i++) {
1853 if(this._records[i] && (this._records[i]._sId === record)) {
1854 return this._records[i];
1858 // Not a valid Record for this RecordSet
1864 * Returns an array of Records from the RecordSet.
1866 * @method getRecords
1867 * @param index {Number} (optional) Recordset position index of which Record to
1869 * @param range {Number} (optional) Number of Records to get.
1870 * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
1871 * length equal to given range. If index is not given, all Records are returned.
1873 getRecords : function(index, range) {
1874 if(!lang.isNumber(index)) {
1875 return this._records;
1877 if(!lang.isNumber(range)) {
1878 return this._records.slice(index);
1880 return this._records.slice(index, index+range);
1884 * Returns a boolean indicating whether Records exist in the RecordSet at the
1885 * specified index range. Returns true if and only if a Record exists at each
1886 * index in the range.
1887 * @method hasRecords
1890 * @return {Boolean} true if all indices are populated in the RecordSet
1892 hasRecords : function (index, range) {
1893 var recs = this.getRecords(index,range);
1894 for (var i = 0; i < range; ++i) {
1895 if (typeof recs[i] === 'undefined') {
1903 * Returns current position index for the given Record.
1905 * @method getRecordIndex
1906 * @param oRecord {YAHOO.widget.Record} Record instance.
1907 * @return {Number} Record's RecordSet position index.
1910 getRecordIndex : function(oRecord) {
1912 for(var i=this._records.length-1; i>-1; i--) {
1913 if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
1923 * Adds one Record to the RecordSet at the given index. If index is null,
1924 * then adds the Record to the end of the RecordSet.
1927 * @param oData {Object} An object literal of data.
1928 * @param index {Number} (optional) Position index.
1929 * @return {YAHOO.widget.Record} A Record instance.
1931 addRecord : function(oData, index) {
1932 if(lang.isObject(oData)) {
1933 var oRecord = this._addRecord(oData, index);
1934 this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
1943 * Adds multiple Records at once to the RecordSet at the given index with the
1944 * given object literal data. If index is null, then the new Records are
1945 * added to the end of the RecordSet.
1947 * @method addRecords
1948 * @param aData {Object[]} An object literal data or an array of data object literals.
1949 * @param index {Number} (optional) Position index.
1950 * @return {YAHOO.widget.Record[]} An array of Record instances.
1952 addRecords : function(aData, index) {
1953 if(lang.isArray(aData)) {
1954 var newRecords = [],
1957 index = lang.isNumber(index) ? index : this._records.length;
1960 // Can't go backwards bc we need to preserve order
1961 for(i=0,len=aData.length; i<len; ++i) {
1962 if(lang.isObject(aData[i])) {
1963 var record = this._addRecord(aData[i], idx++);
1964 newRecords.push(record);
1967 this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
1970 else if(lang.isObject(aData)) {
1971 var oRecord = this._addRecord(aData);
1972 this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
1981 * Sets or replaces one Record to the RecordSet at the given index. Unlike
1982 * addRecord, an existing Record at that index is not shifted to preserve it.
1983 * If no index is specified, it adds the Record to the end of the RecordSet.
1986 * @param oData {Object} An object literal of data.
1987 * @param index {Number} (optional) Position index.
1988 * @return {YAHOO.widget.Record} A Record instance.
1990 setRecord : function(oData, index) {
1991 if(lang.isObject(oData)) {
1992 var oRecord = this._setRecord(oData, index);
1993 this.fireEvent("recordSetEvent",{record:oRecord,data:oData});
2002 * Sets or replaces multiple Records at once to the RecordSet with the given
2003 * data, starting at the given index. If index is not specified, then the new
2004 * Records are added to the end of the RecordSet.
2006 * @method setRecords
2007 * @param aData {Object[]} An array of object literal data.
2008 * @param index {Number} (optional) Position index.
2009 * @return {YAHOO.widget.Record[]} An array of Record instances.
2011 setRecords : function(aData, index) {
2012 var Rec = widget.Record,
2013 a = lang.isArray(aData) ? aData : [aData],
2015 i = 0, l = a.length, j = 0;
2017 index = parseInt(index,10)|0;
2020 if (typeof a[i] === 'object' && a[i]) {
2021 added[j++] = this._records[index + i] = new Rec(a[i]);
2025 this.fireEvent("recordsSetEvent",{records:added,data:aData});
2026 // Backward compatibility for bug 1918245
2027 this.fireEvent("recordsSet",{records:added,data:aData});
2029 if (a.length && !added.length) {
2032 return added.length > 1 ? added : added[0];
2036 * Updates given Record with given data.
2038 * @method updateRecord
2039 * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2040 * a RecordSet position index, or a Record ID.
2041 * @param oData {Object} Object literal of new data.
2042 * @return {YAHOO.widget.Record} Updated Record, or null.
2044 updateRecord : function(record, oData) {
2045 var oRecord = this.getRecord(record);
2046 if(oRecord && lang.isObject(oData)) {
2047 // Copy data from the Record for the event that gets fired later
2049 for(var key in oRecord._oData) {
2050 if(lang.hasOwnProperty(oRecord._oData, key)) {
2051 oldData[key] = oRecord._oData[key];
2054 oRecord._oData = oData;
2055 this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
2065 * @deprecated Use updateRecordValue
2067 updateKey : function(record, sKey, oData) {
2068 this.updateRecordValue(record, sKey, oData);
2071 * Sets given Record at given key to given data.
2073 * @method updateRecordValue
2074 * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2075 * a RecordSet position index, or a Record ID.
2076 * @param sKey {String} Key name.
2077 * @param oData {Object} New data.
2079 updateRecordValue : function(record, sKey, oData) {
2080 var oRecord = this.getRecord(record);
2083 var keyValue = oRecord._oData[sKey];
2084 // Copy data from the Record for the event that gets fired later
2085 if(keyValue && lang.isObject(keyValue)) {
2087 for(var key in keyValue) {
2088 if(lang.hasOwnProperty(keyValue, key)) {
2089 oldData[key] = keyValue[key];
2098 oRecord._oData[sKey] = oData;
2099 this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2100 this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2107 * Replaces all Records in RecordSet with new object literal data.
2109 * @method replaceRecords
2110 * @param data {Object || Object[]} An object literal of data or an array of
2111 * data object literals.
2112 * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
2113 * an array of Records.
2115 replaceRecords : function(data) {
2117 return this.addRecords(data);
2121 * Sorts all Records by given function. Records keep their unique IDs but will
2122 * have new RecordSet position indexes.
2124 * @method sortRecords
2125 * @param fnSort {Function} Reference to a sort function.
2126 * @param desc {Boolean} True if sort direction is descending, false if sort
2127 * direction is ascending.
2128 * @param field {String} The field to sort by, from sortOptions.field
2129 * @return {YAHOO.widget.Record[]} Sorted array of Records.
2131 sortRecords : function(fnSort, desc, field) {
2132 return this._records.sort(function(a, b) {return fnSort(a, b, desc, field);});
2136 * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
2138 * @method reverseRecords
2139 * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
2141 reverseRecords : function() {
2142 return this._records.reverse();
2146 * Removes the Record at the given position index from the RecordSet. If a range
2147 * is also provided, removes that many Records, starting from the index. Length
2148 * of RecordSet is correspondingly shortened.
2150 * @method deleteRecord
2151 * @param index {Number} Record's RecordSet position index.
2152 * @param range {Number} (optional) How many Records to delete.
2153 * @return {Object} A copy of the data held by the deleted Record.
2155 deleteRecord : function(index) {
2156 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2157 // Copy data from the Record for the event that gets fired later
2158 var oData = widget.DataTable._cloneObject(this.getRecord(index).getData());
2160 this._deleteRecord(index);
2161 this.fireEvent("recordDeleteEvent",{data:oData,index:index});
2170 * Removes the Record at the given position index from the RecordSet. If a range
2171 * is also provided, removes that many Records, starting from the index. Length
2172 * of RecordSet is correspondingly shortened.
2174 * @method deleteRecords
2175 * @param index {Number} Record's RecordSet position index.
2176 * @param range {Number} (optional) How many Records to delete.
2177 * @return {Object[]} An array of copies of the data held by the deleted Records.
2179 deleteRecords : function(index, range) {
2180 if(!lang.isNumber(range)) {
2183 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2184 var recordsToDelete = this.getRecords(index, range);
2185 // Copy data from each Record for the event that gets fired later
2186 var deletedData = [];
2188 for(var i=0; i<recordsToDelete.length; i++) {
2189 deletedData[deletedData.length] = widget.DataTable._cloneObject(recordsToDelete[i]);
2191 this._deleteRecord(index, range);
2193 this.fireEvent("recordsDeleteEvent",{data:deletedData,index:index});
2203 * Deletes all Records from the RecordSet.
2207 reset : function() {
2210 this.fireEvent("resetEvent");
2214 /////////////////////////////////////////////////////////////////////////////
2218 /////////////////////////////////////////////////////////////////////////////
2220 // RecordSet uses EventProvider
2221 lang.augmentProto(RS, util.EventProvider);
2224 * Fired when a new Record is added to the RecordSet.
2226 * @event recordAddEvent
2227 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2228 * @param oArgs.data {Object} Data added.
2232 * Fired when multiple Records are added to the RecordSet at once.
2234 * @event recordsAddEvent
2235 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2236 * @param oArgs.data {Object[]} Data added.
2240 * Fired when a Record is set in the RecordSet.
2242 * @event recordSetEvent
2243 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2244 * @param oArgs.data {Object} Data added.
2248 * Fired when multiple Records are set in the RecordSet at once.
2250 * @event recordsSetEvent
2251 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2252 * @param oArgs.data {Object[]} Data added.
2256 * Fired when a Record is updated with new data.
2258 * @event recordUpdateEvent
2259 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2260 * @param oArgs.newData {Object} New data.
2261 * @param oArgs.oldData {Object} Old data.
2265 * Fired when a Record is deleted from the RecordSet.
2267 * @event recordDeleteEvent
2268 * @param oArgs.data {Object} A copy of the data held by the Record,
2269 * or an array of data object literals if multiple Records were deleted at once.
2270 * @param oArgs.index {Object} Index of the deleted Record.
2274 * Fired when multiple Records are deleted from the RecordSet at once.
2276 * @event recordsDeleteEvent
2277 * @param oArgs.data {Object[]} An array of data object literals copied
2279 * @param oArgs.index {Object} Index of the first deleted Record.
2283 * Fired when all Records are deleted from the RecordSet at once.
2289 * @event keyUpdateEvent
2290 * @deprecated Use recordValueUpdateEvent
2294 * Fired when a Record value is updated with new data.
2296 * @event recordValueUpdateEvent
2297 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2298 * @param oArgs.key {String} The updated key.
2299 * @param oArgs.newData {Object} New data.
2300 * @param oArgs.oldData {Object} Old data.
2305 /****************************************************************************/
2306 /****************************************************************************/
2307 /****************************************************************************/
2310 * The Record class defines a DataTable record.
2312 * @namespace YAHOO.widget
2315 * @param oConfigs {Object} (optional) Object literal of key/value pairs.
2317 YAHOO.widget.Record = function(oLiteral) {
2318 this._nCount = widget.Record._nCount;
2319 this._sId = "yui-rec" + this._nCount;
2320 widget.Record._nCount++;
2322 if(lang.isObject(oLiteral)) {
2323 for(var sKey in oLiteral) {
2324 if(lang.hasOwnProperty(oLiteral, sKey)) {
2325 this._oData[sKey] = oLiteral[sKey];
2331 /////////////////////////////////////////////////////////////////////////////
2333 // Private member variables
2335 /////////////////////////////////////////////////////////////////////////////
2338 * Internal class variable to give unique IDs to Record instances.
2340 * @property Record._nCount
2344 YAHOO.widget.Record._nCount = 0;
2346 YAHOO.widget.Record.prototype = {
2348 * Immutable unique count assigned at instantiation. Remains constant while a
2349 * Record's position index can change from sorting.
2358 * Immutable unique ID assigned at instantiation. Remains constant while a
2359 * Record's position index can change from sorting.
2368 * Holds data for the Record in an object literal.
2376 /////////////////////////////////////////////////////////////////////////////
2378 // Public member variables
2380 /////////////////////////////////////////////////////////////////////////////
2382 /////////////////////////////////////////////////////////////////////////////
2386 /////////////////////////////////////////////////////////////////////////////
2389 * Returns unique count assigned at instantiation.
2394 getCount : function() {
2395 return this._nCount;
2399 * Returns unique ID assigned at instantiation.
2404 getId : function() {
2409 * Returns data for the Record for a field if given, or the entire object
2410 * literal otherwise.
2413 * @param sField {String} (Optional) The field from which to retrieve data value.
2416 getData : function(sField) {
2417 if(lang.isString(sField)) {
2418 return this._oData[sField];
2426 * Sets given data at the given key. Use the RecordSet method updateRecordValue to trigger
2430 * @param sKey {String} The key of the new value.
2431 * @param oData {MIXED} The new value.
2433 setData : function(sKey, oData) {
2434 this._oData[sKey] = oData;
2442 var lang = YAHOO.lang,
2444 widget = YAHOO.widget,
2449 DS = util.DataSourceBase;
2452 * The DataTable widget provides a progressively enhanced DHTML control for
2453 * displaying tabular data across A-grade browsers.
2456 * @requires yahoo, dom, event, element, datasource
2457 * @optional dragdrop, dragdrop
2458 * @title DataTable Widget
2461 /****************************************************************************/
2462 /****************************************************************************/
2463 /****************************************************************************/
2466 * DataTable class for the YUI DataTable widget.
2468 * @namespace YAHOO.widget
2470 * @extends YAHOO.util.Element
2472 * @param elContainer {HTMLElement} Container element for the TABLE.
2473 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
2474 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
2475 * @param oConfigs {object} (optional) Object literal of configuration values.
2477 YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
2478 var DT = widget.DataTable;
2480 ////////////////////////////////////////////////////////////////////////////
2481 // Backward compatibility for SDT, but prevent infinite loops
2483 if(oConfigs && oConfigs.scrollable) {
2484 return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
2487 ////////////////////////////////////////////////////////////////////////////
2491 this._nIndex = DT._nCount;
2492 this._sId = "yui-dt"+this._nIndex;
2493 this._oChainRender = new YAHOO.util.Chain();
2494 this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
2496 // Initialize configs
2497 this._initConfigs(oConfigs);
2499 // Initialize DataSource
2500 this._initDataSource(oDataSource);
2501 if(!this._oDataSource) {
2505 // Initialize ColumnSet
2506 this._initColumnSet(aColumnDefs);
2507 if(!this._oColumnSet) {
2511 // Initialize RecordSet
2512 this._initRecordSet();
2513 if(!this._oRecordSet) {
2516 // Initialize Attributes
2517 DT.superclass.constructor.call(this, elContainer, this.configs);
2519 // Initialize DOM elements
2520 var okDom = this._initDomElements(elContainer);
2525 // Show message as soon as config is available
2526 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
2528 ////////////////////////////////////////////////////////////////////////////
2529 // Once per instance
2533 DT._nCurrentCount++;
2535 ////////////////////////////////////////////////////////////////////////////
2538 // Send a simple initial request
2540 success : this.onDataReturnSetRows,
2541 failure : this.onDataReturnSetRows,
2543 argument: this.getState()
2546 var initialLoad = this.get("initialLoad");
2547 if(initialLoad === true) {
2548 this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
2550 // Do not send an initial request at all
2551 else if(initialLoad === false) {
2552 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
2554 // Send an initial request with a custom payload
2556 var oCustom = initialLoad || {};
2557 oCallback.argument = oCustom.argument || {};
2558 this._oDataSource.sendRequest(oCustom.request, oCallback);
2562 var DT = widget.DataTable;
2564 /////////////////////////////////////////////////////////////////////////////
2568 /////////////////////////////////////////////////////////////////////////////
2570 lang.augmentObject(DT, {
2573 * Class name assigned to outer DataTable container.
2575 * @property DataTable.CLASS_DATATABLE
2581 CLASS_DATATABLE : "yui-dt",
2584 * Class name assigned to liner DIV elements.
2586 * @property DataTable.CLASS_LINER
2590 * @default "yui-dt-liner"
2592 CLASS_LINER : "yui-dt-liner",
2595 * Class name assigned to display label elements.
2597 * @property DataTable.CLASS_LABEL
2601 * @default "yui-dt-label"
2603 CLASS_LABEL : "yui-dt-label",
2606 * Class name assigned to messaging elements.
2608 * @property DataTable.CLASS_MESSAGE
2612 * @default "yui-dt-message"
2614 CLASS_MESSAGE : "yui-dt-message",
2617 * Class name assigned to mask element when DataTable is disabled.
2619 * @property DataTable.CLASS_MASK
2623 * @default "yui-dt-mask"
2625 CLASS_MASK : "yui-dt-mask",
2628 * Class name assigned to data elements.
2630 * @property DataTable.CLASS_DATA
2634 * @default "yui-dt-data"
2636 CLASS_DATA : "yui-dt-data",
2639 * Class name assigned to Column drag target.
2641 * @property DataTable.CLASS_COLTARGET
2645 * @default "yui-dt-coltarget"
2647 CLASS_COLTARGET : "yui-dt-coltarget",
2650 * Class name assigned to resizer handle elements.
2652 * @property DataTable.CLASS_RESIZER
2656 * @default "yui-dt-resizer"
2658 CLASS_RESIZER : "yui-dt-resizer",
2661 * Class name assigned to resizer liner elements.
2663 * @property DataTable.CLASS_RESIZERLINER
2667 * @default "yui-dt-resizerliner"
2669 CLASS_RESIZERLINER : "yui-dt-resizerliner",
2672 * Class name assigned to resizer proxy elements.
2674 * @property DataTable.CLASS_RESIZERPROXY
2678 * @default "yui-dt-resizerproxy"
2680 CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
2683 * Class name assigned to CellEditor container elements.
2685 * @property DataTable.CLASS_EDITOR
2689 * @default "yui-dt-editor"
2691 CLASS_EDITOR : "yui-dt-editor",
2694 * Class name assigned to paginator container elements.
2696 * @property DataTable.CLASS_PAGINATOR
2700 * @default "yui-dt-paginator"
2702 CLASS_PAGINATOR : "yui-dt-paginator",
2705 * Class name assigned to page number indicators.
2707 * @property DataTable.CLASS_PAGE
2711 * @default "yui-dt-page"
2713 CLASS_PAGE : "yui-dt-page",
2716 * Class name assigned to default indicators.
2718 * @property DataTable.CLASS_DEFAULT
2722 * @default "yui-dt-default"
2724 CLASS_DEFAULT : "yui-dt-default",
2727 * Class name assigned to previous indicators.
2729 * @property DataTable.CLASS_PREVIOUS
2733 * @default "yui-dt-previous"
2735 CLASS_PREVIOUS : "yui-dt-previous",
2738 * Class name assigned next indicators.
2740 * @property DataTable.CLASS_NEXT
2744 * @default "yui-dt-next"
2746 CLASS_NEXT : "yui-dt-next",
2749 * Class name assigned to first elements.
2751 * @property DataTable.CLASS_FIRST
2755 * @default "yui-dt-first"
2757 CLASS_FIRST : "yui-dt-first",
2760 * Class name assigned to last elements.
2762 * @property DataTable.CLASS_LAST
2766 * @default "yui-dt-last"
2768 CLASS_LAST : "yui-dt-last",
2771 * Class name assigned to even elements.
2773 * @property DataTable.CLASS_EVEN
2777 * @default "yui-dt-even"
2779 CLASS_EVEN : "yui-dt-even",
2782 * Class name assigned to odd elements.
2784 * @property DataTable.CLASS_ODD
2788 * @default "yui-dt-odd"
2790 CLASS_ODD : "yui-dt-odd",
2793 * Class name assigned to selected elements.
2795 * @property DataTable.CLASS_SELECTED
2799 * @default "yui-dt-selected"
2801 CLASS_SELECTED : "yui-dt-selected",
2804 * Class name assigned to highlighted elements.
2806 * @property DataTable.CLASS_HIGHLIGHTED
2810 * @default "yui-dt-highlighted"
2812 CLASS_HIGHLIGHTED : "yui-dt-highlighted",
2815 * Class name assigned to hidden elements.
2817 * @property DataTable.CLASS_HIDDEN
2821 * @default "yui-dt-hidden"
2823 CLASS_HIDDEN : "yui-dt-hidden",
2826 * Class name assigned to disabled elements.
2828 * @property DataTable.CLASS_DISABLED
2832 * @default "yui-dt-disabled"
2834 CLASS_DISABLED : "yui-dt-disabled",
2837 * Class name assigned to empty indicators.
2839 * @property DataTable.CLASS_EMPTY
2843 * @default "yui-dt-empty"
2845 CLASS_EMPTY : "yui-dt-empty",
2848 * Class name assigned to loading indicatorx.
2850 * @property DataTable.CLASS_LOADING
2854 * @default "yui-dt-loading"
2856 CLASS_LOADING : "yui-dt-loading",
2859 * Class name assigned to error indicators.
2861 * @property DataTable.CLASS_ERROR
2865 * @default "yui-dt-error"
2867 CLASS_ERROR : "yui-dt-error",
2870 * Class name assigned to editable elements.
2872 * @property DataTable.CLASS_EDITABLE
2876 * @default "yui-dt-editable"
2878 CLASS_EDITABLE : "yui-dt-editable",
2881 * Class name assigned to draggable elements.
2883 * @property DataTable.CLASS_DRAGGABLE
2887 * @default "yui-dt-draggable"
2889 CLASS_DRAGGABLE : "yui-dt-draggable",
2892 * Class name assigned to resizeable elements.
2894 * @property DataTable.CLASS_RESIZEABLE
2898 * @default "yui-dt-resizeable"
2900 CLASS_RESIZEABLE : "yui-dt-resizeable",
2903 * Class name assigned to scrollable elements.
2905 * @property DataTable.CLASS_SCROLLABLE
2909 * @default "yui-dt-scrollable"
2911 CLASS_SCROLLABLE : "yui-dt-scrollable",
2914 * Class name assigned to sortable elements.
2916 * @property DataTable.CLASS_SORTABLE
2920 * @default "yui-dt-sortable"
2922 CLASS_SORTABLE : "yui-dt-sortable",
2925 * Class name assigned to ascending elements.
2927 * @property DataTable.CLASS_ASC
2931 * @default "yui-dt-asc"
2933 CLASS_ASC : "yui-dt-asc",
2936 * Class name assigned to descending elements.
2938 * @property DataTable.CLASS_DESC
2942 * @default "yui-dt-desc"
2944 CLASS_DESC : "yui-dt-desc",
2947 * Class name assigned to BUTTON elements and/or container elements.
2949 * @property DataTable.CLASS_BUTTON
2953 * @default "yui-dt-button"
2955 CLASS_BUTTON : "yui-dt-button",
2958 * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
2960 * @property DataTable.CLASS_CHECKBOX
2964 * @default "yui-dt-checkbox"
2966 CLASS_CHECKBOX : "yui-dt-checkbox",
2969 * Class name assigned to SELECT elements and/or container elements.
2971 * @property DataTable.CLASS_DROPDOWN
2975 * @default "yui-dt-dropdown"
2977 CLASS_DROPDOWN : "yui-dt-dropdown",
2980 * Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
2982 * @property DataTable.CLASS_RADIO
2986 * @default "yui-dt-radio"
2988 CLASS_RADIO : "yui-dt-radio",
2990 /////////////////////////////////////////////////////////////////////////
2992 // Private static properties
2994 /////////////////////////////////////////////////////////////////////////
2997 * Internal class variable for indexing multiple DataTable instances.
2999 * @property DataTable._nCount
3007 * Internal class variable tracking current number of DataTable instances,
3008 * so that certain class values can be reset when all instances are destroyed.
3010 * @property DataTable._nCurrentCount
3018 * Reference to the STYLE node that is dynamically created and updated
3019 * in order to manage Column widths.
3021 * @property DataTable._elDynStyleNode
3026 _elDynStyleNode : null,
3029 * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
3031 * @property DataTable._bDynStylesFallback
3036 _bDynStylesFallback : (ua.ie) ? true : false,
3039 * Object literal hash of Columns and their dynamically create style rules.
3041 * @property DataTable._oDynStyles
3049 * Element reference to shared Column drag target.
3051 * @property DataTable._elColumnDragTarget
3056 _elColumnDragTarget : null,
3059 * Element reference to shared Column resizer proxy.
3061 * @property DataTable._elColumnResizerProxy
3066 _elColumnResizerProxy : null,
3068 /////////////////////////////////////////////////////////////////////////
3070 // Private static methods
3072 /////////////////////////////////////////////////////////////////////////
3075 * Clones object literal or array of object literals.
3077 * @method DataTable._cloneObject
3078 * @param o {Object} Object.
3082 _cloneObject : function(o) {
3083 if(!lang.isValue(o)) {
3089 if(o instanceof YAHOO.widget.BaseCellEditor) {
3092 else if(lang.isFunction(o)) {
3095 else if(lang.isArray(o)) {
3097 for(var i=0,len=o.length;i<len;i++) {
3098 array[i] = DT._cloneObject(o[i]);
3102 else if(lang.isObject(o)) {
3104 if(lang.hasOwnProperty(o, x)) {
3105 if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
3106 copy[x] = DT._cloneObject(o[x]);
3122 * Destroys shared Column drag target.
3124 * @method DataTable._destroyColumnDragTargetEl
3128 _destroyColumnDragTargetEl : function() {
3129 if(DT._elColumnDragTarget) {
3130 var el = DT._elColumnDragTarget;
3131 YAHOO.util.Event.purgeElement(el);
3132 el.parentNode.removeChild(el);
3133 DT._elColumnDragTarget = null;
3139 * Creates HTML markup for shared Column drag target.
3141 * @method DataTable._initColumnDragTargetEl
3142 * @return {HTMLElement} Reference to Column drag target.
3146 _initColumnDragTargetEl : function() {
3147 if(!DT._elColumnDragTarget) {
3148 // Attach Column drag target element as first child of body
3149 var elColumnDragTarget = document.createElement('div');
3150 elColumnDragTarget.className = DT.CLASS_COLTARGET;
3151 elColumnDragTarget.style.display = "none";
3152 document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
3154 // Internal tracker of Column drag target
3155 DT._elColumnDragTarget = elColumnDragTarget;
3158 return DT._elColumnDragTarget;
3162 * Destroys shared Column resizer proxy.
3164 * @method DataTable._destroyColumnResizerProxyEl
3165 * @return {HTMLElement} Reference to Column resizer proxy.
3169 _destroyColumnResizerProxyEl : function() {
3170 if(DT._elColumnResizerProxy) {
3171 var el = DT._elColumnResizerProxy;
3172 YAHOO.util.Event.purgeElement(el);
3173 el.parentNode.removeChild(el);
3174 DT._elColumnResizerProxy = null;
3179 * Creates HTML markup for shared Column resizer proxy.
3181 * @method DataTable._initColumnResizerProxyEl
3182 * @return {HTMLElement} Reference to Column resizer proxy.
3186 _initColumnResizerProxyEl : function() {
3187 if(!DT._elColumnResizerProxy) {
3188 // Attach Column resizer element as first child of body
3189 var elColumnResizerProxy = document.createElement("div");
3190 elColumnResizerProxy.id = "yui-dt-colresizerproxy"; // Needed for ColumnResizer
3191 elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
3192 document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
3194 // Internal tracker of Column resizer proxy
3195 DT._elColumnResizerProxy = elColumnResizerProxy;
3197 return DT._elColumnResizerProxy;
3201 * Formats a BUTTON element.
3203 * @method DataTable.formatButton
3204 * @param el {HTMLElement} The element to format with markup.
3205 * @param oRecord {YAHOO.widget.Record} Record instance.
3206 * @param oColumn {YAHOO.widget.Column} Column instance.
3207 * @param oData {Object | Boolean} Data value for the cell. By default, the value
3208 * is what gets written to the BUTTON.
3211 formatButton : function(el, oRecord, oColumn, oData) {
3212 var sValue = lang.isValue(oData) ? oData : "Click";
3213 //TODO: support YAHOO.widget.Button
3214 //if(YAHOO.widget.Button) {
3218 el.innerHTML = "<button type=\"button\" class=\""+
3219 DT.CLASS_BUTTON + "\">" + sValue + "</button>";
3224 * Formats a CHECKBOX element.
3226 * @method DataTable.formatCheckbox
3227 * @param el {HTMLElement} The element to format with markup.
3228 * @param oRecord {YAHOO.widget.Record} Record instance.
3229 * @param oColumn {YAHOO.widget.Column} Column instance.
3230 * @param oData {Object | Boolean} Data value for the cell. Can be a simple
3231 * Boolean to indicate whether checkbox is checked or not. Can be object literal
3232 * {checked:bBoolean, label:sLabel}. Other forms of oData require a custom
3236 formatCheckbox : function(el, oRecord, oColumn, oData) {
3237 var bChecked = oData;
3238 bChecked = (bChecked) ? " checked=\"checked\"" : "";
3239 el.innerHTML = "<input type=\"checkbox\"" + bChecked +
3240 " class=\"" + DT.CLASS_CHECKBOX + "\" />";
3244 * Formats currency. Default unit is USD.
3246 * @method DataTable.formatCurrency
3247 * @param el {HTMLElement} The element to format with markup.
3248 * @param oRecord {YAHOO.widget.Record} Record instance.
3249 * @param oColumn {YAHOO.widget.Column} Column instance.
3250 * @param oData {Number} Data value for the cell.
3253 formatCurrency : function(el, oRecord, oColumn, oData) {
3254 el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || this.get("currencyOptions"));
3258 * Formats JavaScript Dates.
3260 * @method DataTable.formatDate
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 formatDate : function(el, oRecord, oColumn, oData) {
3268 var oConfig = oColumn.dateOptions || this.get("dateOptions");
3269 el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
3273 * Formats SELECT elements.
3275 * @method DataTable.formatDropdown
3276 * @param el {HTMLElement} The element to format with markup.
3277 * @param oRecord {YAHOO.widget.Record} Record instance.
3278 * @param oColumn {YAHOO.widget.Column} Column instance.
3279 * @param oData {Object} Data value for the cell, or null.
3282 formatDropdown : function(el, oRecord, oColumn, oData) {
3283 var selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
3284 options = (lang.isArray(oColumn.dropdownOptions)) ?
3285 oColumn.dropdownOptions : null,
3288 collection = el.getElementsByTagName("select");
3290 // Create the form element only once, so we can attach the onChange listener
3291 if(collection.length === 0) {
3292 // Create SELECT element
3293 selectEl = document.createElement("select");
3294 selectEl.className = DT.CLASS_DROPDOWN;
3295 selectEl = el.appendChild(selectEl);
3297 // Add event listener
3298 Ev.addListener(selectEl,"change",this._onDropdownChange,this);
3301 selectEl = collection[0];
3303 // Update the form element
3305 // Clear out previous options
3306 selectEl.innerHTML = "";
3308 // We have options to populate
3310 // Create OPTION elements
3311 for(var i=0; i<options.length; i++) {
3312 var option = options[i];
3313 var optionEl = document.createElement("option");
3314 optionEl.value = (lang.isValue(option.value)) ?
3315 option.value : option;
3316 // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
3317 optionEl.innerHTML = (lang.isValue(option.text)) ?
3318 option.text : (lang.isValue(option.label)) ? option.label : option;
3319 optionEl = selectEl.appendChild(optionEl);
3320 if (optionEl.value == selectedValue) {
3321 optionEl.selected = true;
3325 // Selected value is our only option
3327 selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
3331 el.innerHTML = lang.isValue(oData) ? oData : "";
3338 * @method DataTable.formatEmail
3339 * @param el {HTMLElement} The element to format with markup.
3340 * @param oRecord {YAHOO.widget.Record} Record instance.
3341 * @param oColumn {YAHOO.widget.Column} Column instance.
3342 * @param oData {Object} Data value for the cell, or null.
3345 formatEmail : function(el, oRecord, oColumn, oData) {
3346 if(lang.isString(oData)) {
3347 el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
3350 el.innerHTML = lang.isValue(oData) ? oData : "";
3357 * @method DataTable.formatLink
3358 * @param el {HTMLElement} The element to format with markup.
3359 * @param oRecord {YAHOO.widget.Record} Record instance.
3360 * @param oColumn {YAHOO.widget.Column} Column instance.
3361 * @param oData {Object} Data value for the cell, or null.
3364 formatLink : function(el, oRecord, oColumn, oData) {
3365 if(lang.isString(oData)) {
3366 el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
3369 el.innerHTML = lang.isValue(oData) ? oData : "";
3376 * @method DataTable.formatNumber
3377 * @param el {HTMLElement} The element to format with markup.
3378 * @param oRecord {YAHOO.widget.Record} Record instance.
3379 * @param oColumn {YAHOO.widget.Column} Column instance.
3380 * @param oData {Object} Data value for the cell, or null.
3383 formatNumber : function(el, oRecord, oColumn, oData) {
3384 el.innerHTML = util.Number.format(oData, oColumn.numberOptions || this.get("numberOptions"));
3388 * Formats INPUT TYPE=RADIO elements.
3390 * @method DataTable.formatRadio
3391 * @param el {HTMLElement} The element to format with markup.
3392 * @param oRecord {YAHOO.widget.Record} Record instance.
3393 * @param oColumn {YAHOO.widget.Column} Column instance.
3394 * @param oData {Object} (Optional) Data value for the cell.
3397 formatRadio : function(el, oRecord, oColumn, oData) {
3398 var bChecked = oData;
3399 bChecked = (bChecked) ? " checked=\"checked\"" : "";
3400 el.innerHTML = "<input type=\"radio\"" + bChecked +
3401 " name=\""+this.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
3402 " class=\"" + DT.CLASS_RADIO+ "\" />";
3406 * Formats text strings.
3408 * @method DataTable.formatText
3409 * @param el {HTMLElement} The element to format with markup.
3410 * @param oRecord {YAHOO.widget.Record} Record instance.
3411 * @param oColumn {YAHOO.widget.Column} Column instance.
3412 * @param oData {Object} (Optional) Data value for the cell.
3415 formatText : function(el, oRecord, oColumn, oData) {
3416 var value = (lang.isValue(oData)) ? oData : "";
3417 //TODO: move to util function
3418 el.innerHTML = value.toString().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
3422 * Formats TEXTAREA elements.
3424 * @method DataTable.formatTextarea
3425 * @param el {HTMLElement} The element to format with markup.
3426 * @param oRecord {YAHOO.widget.Record} Record instance.
3427 * @param oColumn {YAHOO.widget.Column} Column instance.
3428 * @param oData {Object} (Optional) Data value for the cell.
3431 formatTextarea : function(el, oRecord, oColumn, oData) {
3432 var value = (lang.isValue(oData)) ? oData : "",
3433 markup = "<textarea>" + value + "</textarea>";
3434 el.innerHTML = markup;
3438 * Formats INPUT TYPE=TEXT elements.
3440 * @method DataTable.formatTextbox
3441 * @param el {HTMLElement} The element to format with markup.
3442 * @param oRecord {YAHOO.widget.Record} Record instance.
3443 * @param oColumn {YAHOO.widget.Column} Column instance.
3444 * @param oData {Object} (Optional) Data value for the cell.
3447 formatTextbox : function(el, oRecord, oColumn, oData) {
3448 var value = (lang.isValue(oData)) ? oData : "",
3449 markup = "<input type=\"text\" value=\"" + value + "\" />";
3450 el.innerHTML = markup;
3454 * Default cell formatter
3456 * @method DataTable.formatDefault
3457 * @param el {HTMLElement} The element to format with markup.
3458 * @param oRecord {YAHOO.widget.Record} Record instance.
3459 * @param oColumn {YAHOO.widget.Column} Column instance.
3460 * @param oData {Object} (Optional) Data value for the cell.
3463 formatDefault : function(el, oRecord, oColumn, oData) {
3464 el.innerHTML = oData === undefined ||
3466 (typeof oData === 'number' && isNaN(oData)) ?
3467 " " : oData.toString();
3471 * Validates data value to type Number, doing type conversion as
3472 * necessary. A valid Number value is return, else null is returned
3473 * if input value does not validate.
3476 * @method DataTable.validateNumber
3477 * @param oData {Object} Data to validate.
3480 validateNumber : function(oData) {
3482 var number = oData * 1;
3485 if(lang.isNumber(number)) {
3494 // Done in separate step so referenced functions are defined.
3496 * Cell formatting functions.
3497 * @property DataTable.Formatter
3502 button : DT.formatButton,
3503 checkbox : DT.formatCheckbox,
3504 currency : DT.formatCurrency,
3505 "date" : DT.formatDate,
3506 dropdown : DT.formatDropdown,
3507 email : DT.formatEmail,
3508 link : DT.formatLink,
3509 "number" : DT.formatNumber,
3510 radio : DT.formatRadio,
3511 text : DT.formatText,
3512 textarea : DT.formatTextarea,
3513 textbox : DT.formatTextbox,
3515 defaultFormatter : DT.formatDefault
3518 lang.extend(DT, util.Element, {
3520 /////////////////////////////////////////////////////////////////////////////
3522 // Superclass methods
3524 /////////////////////////////////////////////////////////////////////////////
3527 * Implementation of Element's abstract method. Sets up config values.
3529 * @method initAttributes
3530 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
3534 initAttributes : function(oConfigs) {
3535 oConfigs = oConfigs || {};
3536 DT.superclass.initAttributes.call(this, oConfigs);
3539 * @attribute summary
3540 * @description Value for the SUMMARY attribute.
3544 this.setAttributeConfig("summary", {
3546 validator: lang.isString,
3547 method: function(sSummary) {
3549 this._elTable.summary = sSummary;
3555 * @attribute selectionMode
3556 * @description Specifies row or cell selection mode. Accepts the following strings:
3558 * <dt>"standard"</dt>
3559 * <dd>Standard row selection with support for modifier keys to enable
3560 * multiple selections.</dd>
3563 * <dd>Row selection with modifier keys disabled to not allow
3564 * multiple selections.</dd>
3566 * <dt>"singlecell"</dt>
3567 * <dd>Cell selection with modifier keys disabled to not allow
3568 * multiple selections.</dd>
3570 * <dt>"cellblock"</dt>
3571 * <dd>Cell selection with support for modifier keys to enable multiple
3572 * selections in a block-fashion, like a spreadsheet.</dd>
3574 * <dt>"cellrange"</dt>
3575 * <dd>Cell selection with support for modifier keys to enable multiple
3576 * selections in a range-fashion, like a calendar.</dd>
3579 * @default "standard"
3582 this.setAttributeConfig("selectionMode", {
3584 validator: lang.isString
3588 * @attribute sortedBy
3589 * @description Object literal provides metadata for initial sort values if
3590 * data will arrive pre-sorted:
3592 * <dt>sortedBy.key</dt>
3593 * <dd>{String} Key of sorted Column</dd>
3594 * <dt>sortedBy.dir</dt>
3595 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3597 * @type Object | null
3599 this.setAttributeConfig("sortedBy", {
3601 // TODO: accepted array for nested sorts
3602 validator: function(oNewSortedBy) {
3604 return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
3607 return (oNewSortedBy === null);
3610 method: function(oNewSortedBy) {
3611 // Stash the previous value
3612 var oOldSortedBy = this.get("sortedBy");
3614 // Workaround for bug 1827195
3615 this._configs.sortedBy.value = oNewSortedBy;
3617 // Remove ASC/DESC from TH
3624 if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
3625 oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
3626 nOldColumnKeyIndex = oOldColumn.getKeyIndex();
3628 // Remove previous UI from THEAD
3629 var elOldTh = oOldColumn.getThEl();
3630 Dom.removeClass(elOldTh, oOldSortedBy.dir);
3631 this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
3634 oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
3635 nNewColumnKeyIndex = oNewColumn.getKeyIndex();
3637 // Update THEAD with new UI
3638 var elNewTh = oNewColumn.getThEl();
3639 // Backward compatibility
3640 if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") || (oNewSortedBy.dir == "desc"))) {
3641 var newClass = (oNewSortedBy.dir == "desc") ?
3644 Dom.addClass(elNewTh, newClass);
3647 var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
3648 Dom.addClass(elNewTh, sortClass);
3650 this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
3656 this._elTbody.style.display = "none";
3657 var allRows = this._elTbody.rows,
3659 for(var i=allRows.length-1; i>-1; i--) {
3660 allCells = allRows[i].childNodes;
3661 if(allCells[nOldColumnKeyIndex]) {
3662 Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
3664 if(allCells[nNewColumnKeyIndex]) {
3665 Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
3668 this._elTbody.style.display = "";
3671 this._clearTrTemplateEl();
3676 * @attribute paginator
3677 * @description An instance of YAHOO.widget.Paginator.
3679 * @type {Object|YAHOO.widget.Paginator}
3681 this.setAttributeConfig("paginator", {
3683 validator : function (val) {
3684 return val === null || val instanceof widget.Paginator;
3686 method : function () { this._updatePaginator.apply(this,arguments); }
3690 * @attribute caption
3691 * @description Value for the CAPTION element. NB: Not supported in
3692 * ScrollingDataTable.
3695 this.setAttributeConfig("caption", {
3697 validator: lang.isString,
3698 method: function(sCaption) {
3699 this._initCaptionEl(sCaption);
3704 * @attribute draggableColumns
3705 * @description True if Columns are draggable to reorder, false otherwise.
3706 * The Drag & Drop Utility is required to enable this feature. Only top-level
3707 * and non-nested Columns are draggable. Write once.
3711 this.setAttributeConfig("draggableColumns", {
3713 validator: lang.isBoolean,
3714 method: function(oParam) {
3717 this._initDraggableColumns();
3720 this._destroyDraggableColumns();
3727 * @attribute renderLoopSize
3728 * @description A value greater than 0 enables DOM rendering of rows to be
3729 * executed from a non-blocking timeout queue and sets how many rows to be
3730 * rendered per timeout. Recommended for very large data sets.
3734 this.setAttributeConfig("renderLoopSize", {
3736 validator: lang.isNumber
3740 * @attribute formatRow
3741 * @description A function that accepts a TR element and its associated Record
3742 * for custom formatting. The function must return TRUE in order to automatically
3743 * continue formatting of child TD elements, else TD elements will not be
3744 * automatically formatted.
3748 this.setAttributeConfig("formatRow", {
3750 validator: lang.isFunction
3754 * @attribute generateRequest
3755 * @description A function that converts an object literal of desired DataTable
3756 * states into a request value which is then passed to the DataSource's
3757 * sendRequest method in order to retrieve data for those states. This
3758 * function is passed an object literal of state data and a reference to the
3759 * DataTable instance:
3762 * <dt>pagination<dt>
3764 * <dt>offsetRecord</dt>
3765 * <dd>{Number} Index of the first Record of the desired page</dd>
3766 * <dt>rowsPerPage</dt>
3767 * <dd>{Number} Number of rows per page</dd>
3772 * <dd>{String} Key of sorted Column</dd>
3774 * <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3777 * <dd>The DataTable instance</dd>
3780 * and by default returns a String of syntax:
3781 * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
3783 * @default HTMLFunction
3785 this.setAttributeConfig("generateRequest", {
3786 value: function(oState, oSelf) {
3788 oState = oState || {pagination:null, sortedBy:null};
3789 var sort = encodeURIComponent((oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey());
3790 var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
3791 var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
3792 var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
3794 // Build the request
3795 return "sort=" + sort +
3797 "&startIndex=" + startIndex +
3798 ((results !== null) ? "&results=" + results : "");
3800 validator: lang.isFunction
3804 * @attribute initialRequest
3805 * @description Defines the initial request that gets sent to the DataSource
3806 * during initialization. Value is ignored if initialLoad is set to any value
3811 this.setAttributeConfig("initialRequest", {
3816 * @attribute initialLoad
3817 * @description Determines whether or not to load data at instantiation. By
3818 * default, will trigger a sendRequest() to the DataSource and pass in the
3819 * request defined by initialRequest. If set to false, data will not load
3820 * at instantiation. Alternatively, implementers who wish to work with a
3821 * custom payload may pass in an object literal with the following values:
3824 * <dt>request (MIXED)</dt>
3825 * <dd>Request value.</dd>
3827 * <dt>argument (MIXED)</dt>
3828 * <dd>Custom data that will be passed through to the callback function.</dd>
3832 * @type Boolean | Object
3835 this.setAttributeConfig("initialLoad", {
3840 * @attribute dynamicData
3841 * @description If true, sorting and pagination are relegated to the DataSource
3842 * for handling, using the request returned by the "generateRequest" function.
3843 * Each new DataSource response blows away all previous Records. False by default, so
3844 * sorting and pagination will be handled directly on the client side, without
3845 * causing any new requests for data from the DataSource.
3849 this.setAttributeConfig("dynamicData", {
3851 validator: lang.isBoolean
3855 * @attribute MSG_EMPTY
3856 * @description Message to display if DataTable has no data.
3858 * @default "No records found."
3860 this.setAttributeConfig("MSG_EMPTY", {
3861 value: "No records found.",
3862 validator: lang.isString
3866 * @attribute MSG_LOADING
3867 * @description Message to display while DataTable is loading data.
3869 * @default "Loading..."
3871 this.setAttributeConfig("MSG_LOADING", {
3872 value: "Loading...",
3873 validator: lang.isString
3877 * @attribute MSG_ERROR
3878 * @description Message to display while DataTable has data error.
3880 * @default "Data error."
3882 this.setAttributeConfig("MSG_ERROR", {
3883 value: "Data error.",
3884 validator: lang.isString
3888 * @attribute MSG_SORTASC
3889 * @description Message to display in tooltip to sort Column in ascending order.
3891 * @default "Click to sort ascending"
3893 this.setAttributeConfig("MSG_SORTASC", {
3894 value: "Click to sort ascending",
3895 validator: lang.isString,
3896 method: function(sParam) {
3898 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3899 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
3900 allKeys[i]._elThLabel.firstChild.title = sParam;
3908 * @attribute MSG_SORTDESC
3909 * @description Message to display in tooltip to sort Column in descending order.
3911 * @default "Click to sort descending"
3913 this.setAttributeConfig("MSG_SORTDESC", {
3914 value: "Click to sort descending",
3915 validator: lang.isString,
3916 method: function(sParam) {
3918 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3919 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
3920 allKeys[i]._elThLabel.firstChild.title = sParam;
3928 * @attribute currencySymbol
3931 this.setAttributeConfig("currencySymbol", {
3933 validator: lang.isString
3937 * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
3938 * @attribute currencyOptions
3940 * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
3942 this.setAttributeConfig("currencyOptions", {
3944 prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
3946 decimalSeparator:".",
3947 thousandsSeparator:","
3952 * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
3953 * @attribute dateOptions
3955 * @default {format:"%m/%d/%Y", locale:"en"}
3957 this.setAttributeConfig("dateOptions", {
3958 value: {format:"%m/%d/%Y", locale:"en"}
3962 * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
3963 * @attribute numberOptions
3965 * @default {decimalPlaces:0, thousandsSeparator:","}
3967 this.setAttributeConfig("numberOptions", {
3970 thousandsSeparator:","
3976 /////////////////////////////////////////////////////////////////////////////
3978 // Private member variables
3980 /////////////////////////////////////////////////////////////////////////////
3983 * True if instance is initialized, so as to fire the initEvent after render.
3993 * Index assigned to instance.
4002 * Counter for IDs assigned to TR elements.
4004 * @property _nTrCount
4011 * Counter for IDs assigned to TD elements.
4013 * @property _nTdCount
4020 * Unique id assigned to instance "yui-dtN", useful prefix for generating unique
4021 * DOM ID strings and log messages.
4032 * @property _oChainRender
4033 * @type YAHOO.util.Chain
4036 _oChainRender : null,
4039 * DOM reference to the container element for the DataTable instance into which
4040 * all other elements get created.
4042 * @property _elContainer
4046 _elContainer : null,
4049 * DOM reference to the mask element for the DataTable instance which disables it.
4058 * DOM reference to the TABLE element for the DataTable instance.
4060 * @property _elTable
4067 * DOM reference to the CAPTION element for the DataTable instance.
4069 * @property _elCaption
4076 * DOM reference to the COLGROUP element for the DataTable instance.
4078 * @property _elColgroup
4085 * DOM reference to the THEAD element for the DataTable instance.
4087 * @property _elThead
4094 * DOM reference to the primary TBODY element for the DataTable instance.
4096 * @property _elTbody
4103 * DOM reference to the secondary TBODY element used to display DataTable messages.
4105 * @property _elMsgTbody
4112 * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
4114 * @property _elMsgTr
4121 * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
4123 * @property _elMsgTd
4130 * DataSource instance for the DataTable instance.
4132 * @property _oDataSource
4133 * @type YAHOO.util.DataSource
4136 _oDataSource : null,
4139 * ColumnSet instance for the DataTable instance.
4141 * @property _oColumnSet
4142 * @type YAHOO.widget.ColumnSet
4148 * RecordSet instance for the DataTable instance.
4150 * @property _oRecordSet
4151 * @type YAHOO.widget.RecordSet
4157 * The active CellEditor instance for the DataTable instance.
4159 * @property _oCellEditor
4160 * @type YAHOO.widget.CellEditor
4163 _oCellEditor : null,
4166 * ID string of first TR element of the current DataTable page.
4168 * @property _sFirstTrId
4175 * ID string of the last TR element of the current DataTable page.
4177 * @property _sLastTrId
4184 * Template row to create all new rows from.
4185 * @property _elTrTemplate
4186 * @type {HTMLElement}
4189 _elTrTemplate : null,
4192 * Sparse array of custom functions to set column widths for browsers that don't
4193 * support dynamic CSS rules. Functions are added at the index representing
4194 * the number of rows they update.
4196 * @property _aDynFunctions
4200 _aDynFunctions : [],
4230 /////////////////////////////////////////////////////////////////////////////
4234 /////////////////////////////////////////////////////////////////////////////
4237 * Clears browser text selection. Useful to call on rowSelectEvent or
4238 * cellSelectEvent to prevent clicks or dblclicks from selecting text in the
4241 * @method clearTextSelection
4243 clearTextSelection : function() {
4245 if(window.getSelection) {
4246 sel = window.getSelection();
4248 else if(document.getSelection) {
4249 sel = document.getSelection();
4251 else if(document.selection) {
4252 sel = document.selection;
4258 else if (sel.removeAllRanges) {
4259 sel.removeAllRanges();
4261 else if(sel.collapse) {
4268 * Sets focus on the given element.
4271 * @param el {HTMLElement} Element.
4274 _focusEl : function(el) {
4275 el = el || this._elTbody;
4276 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
4277 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
4278 // strange unexpected things as the user clicks on buttons and other controls.
4279 setTimeout(function() {
4289 * Forces Gecko repaint.
4291 * @method _repaintGecko
4292 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4295 _repaintGecko : (ua.gecko) ?
4297 el = el || this._elContainer;
4298 var parent = el.parentNode;
4299 var nextSibling = el.nextSibling;
4300 parent.insertBefore(parent.removeChild(el), nextSibling);
4304 * Forces Opera repaint.
4306 * @method _repaintOpera
4309 _repaintOpera : (ua.opera) ?
4312 document.documentElement.className += " ";
4313 document.documentElement.className = YAHOO.lang.trim(document.documentElement.className);
4318 * Forces Webkit repaint.
4320 * @method _repaintWebkit
4321 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4324 _repaintWebkit : (ua.webkit) ?
4326 el = el || this._elContainer;
4327 var parent = el.parentNode;
4328 var nextSibling = el.nextSibling;
4329 parent.insertBefore(parent.removeChild(el), nextSibling);
4356 * Initializes object literal of config values.
4358 * @method _initConfigs
4359 * @param oConfig {Object} Object literal of config values.
4362 _initConfigs : function(oConfigs) {
4363 if(!oConfigs || !lang.isObject(oConfigs)) {
4366 this.configs = oConfigs;
4370 * Initializes ColumnSet.
4372 * @method _initColumnSet
4373 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
4376 _initColumnSet : function(aColumnDefs) {
4377 var oColumn, i, len;
4379 if(this._oColumnSet) {
4380 // First clear _oDynStyles for existing ColumnSet and
4381 // uregister CellEditor Custom Events
4382 for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
4383 oColumn = this._oColumnSet.keys[i];
4384 DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
4385 if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
4386 oColumn.editor.unsubscribeAll();
4390 this._oColumnSet = null;
4391 this._clearTrTemplateEl();
4394 if(lang.isArray(aColumnDefs)) {
4395 this._oColumnSet = new YAHOO.widget.ColumnSet(aColumnDefs);
4397 // Backward compatibility
4398 else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
4399 this._oColumnSet = aColumnDefs;
4402 // Register CellEditor Custom Events
4403 var allKeys = this._oColumnSet.keys;
4404 for(i=0, len=allKeys.length; i<len; i++) {
4405 oColumn = allKeys[i];
4406 if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
4407 oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
4408 oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
4409 oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
4410 oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
4411 oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
4412 oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
4413 oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
4414 oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
4420 * Initializes DataSource.
4422 * @method _initDataSource
4423 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
4426 _initDataSource : function(oDataSource) {
4427 this._oDataSource = null;
4428 if(oDataSource && (lang.isFunction(oDataSource.sendRequest))) {
4429 this._oDataSource = oDataSource;
4431 // Backward compatibility
4433 var tmpTable = null;
4434 var tmpContainer = this._elContainer;
4436 //TODO: this will break if re-initing DS at runtime for SDT
4437 // Peek in container child nodes to see if TABLE already exists
4438 if(tmpContainer.hasChildNodes()) {
4439 var tmpChildren = tmpContainer.childNodes;
4440 for(i=0; i<tmpChildren.length; i++) {
4441 if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
4442 tmpTable = tmpChildren[i];
4447 var tmpFieldsArray = [];
4448 for(; i<this._oColumnSet.keys.length; i++) {
4449 tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
4452 this._oDataSource = new DS(tmpTable);
4453 this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
4454 this._oDataSource.responseSchema = {fields: tmpFieldsArray};
4461 * Initializes RecordSet.
4463 * @method _initRecordSet
4466 _initRecordSet : function() {
4467 if(this._oRecordSet) {
4468 this._oRecordSet.reset();
4471 this._oRecordSet = new YAHOO.widget.RecordSet();
4476 * Initializes DOM elements.
4478 * @method _initDomElements
4479 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
4480 * return {Boolean} False in case of error, otherwise true
4483 _initDomElements : function(elContainer) {
4485 this._initContainerEl(elContainer);
4487 this._initTableEl(this._elContainer);
4489 this._initColgroupEl(this._elTable);
4491 this._initTheadEl(this._elTable);
4494 this._initMsgTbodyEl(this._elTable);
4497 this._initTbodyEl(this._elTable);
4499 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody) {
4508 * Destroy's the DataTable outer container element, if available.
4510 * @method _destroyContainerEl
4511 * @param elContainer {HTMLElement} Reference to the container element.
4514 _destroyContainerEl : function(elContainer) {
4515 Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
4516 Ev.purgeElement(elContainer, true);
4517 elContainer.innerHTML = "";
4519 this._elContainer = null;
4520 this._elColgroup = null;
4521 this._elThead = null;
4522 this._elTbody = null;
4526 * Initializes the DataTable outer container element, including a mask.
4528 * @method _initContainerEl
4529 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
4532 _initContainerEl : function(elContainer) {
4533 // Validate container
4534 elContainer = Dom.get(elContainer);
4536 if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
4538 this._destroyContainerEl(elContainer);
4540 Dom.addClass(elContainer, DT.CLASS_DATATABLE);
4541 Ev.addListener(elContainer, "focus", this._onTableFocus, this);
4542 Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
4543 this._elContainer = elContainer;
4545 var elMask = document.createElement("div");
4546 elMask.className = DT.CLASS_MASK;
4547 elMask.style.display = "none";
4548 this._elMask = elContainer.appendChild(elMask);
4553 * Destroy's the DataTable TABLE element, if available.
4555 * @method _destroyTableEl
4558 _destroyTableEl : function() {
4559 var elTable = this._elTable;
4561 Ev.purgeElement(elTable, true);
4562 elTable.parentNode.removeChild(elTable);
4563 this._elCaption = null;
4564 this._elColgroup = null;
4565 this._elThead = null;
4566 this._elTbody = null;
4571 * Creates HTML markup CAPTION element.
4573 * @method _initCaptionEl
4574 * @param sCaption {String} Text for caption.
4577 _initCaptionEl : function(sCaption) {
4578 if(this._elTable && sCaption) {
4579 // Create CAPTION element
4580 if(!this._elCaption) {
4581 this._elCaption = this._elTable.createCaption();
4583 // Set CAPTION value
4584 this._elCaption.innerHTML = sCaption;
4586 else if(this._elCaption) {
4587 this._elCaption.parentNode.removeChild(this._elCaption);
4592 * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
4593 * container element.
4595 * @method _initTableEl
4596 * @param elContainer {HTMLElement} Container element into which to create TABLE.
4599 _initTableEl : function(elContainer) {
4602 this._destroyTableEl();
4605 this._elTable = elContainer.appendChild(document.createElement("table"));
4607 // Set SUMMARY attribute
4608 this._elTable.summary = this.get("summary");
4610 // Create CAPTION element
4611 if(this.get("caption")) {
4612 this._initCaptionEl(this.get("caption"));
4618 * Destroy's the DataTable COLGROUP element, if available.
4620 * @method _destroyColgroupEl
4623 _destroyColgroupEl : function() {
4624 var elColgroup = this._elColgroup;
4626 var elTable = elColgroup.parentNode;
4627 Ev.purgeElement(elColgroup, true);
4628 elTable.removeChild(elColgroup);
4629 this._elColgroup = null;
4634 * Initializes COLGROUP and COL elements for managing minWidth.
4636 * @method _initColgroupEl
4637 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4640 _initColgroupEl : function(elTable) {
4643 this._destroyColgroupEl();
4645 // Add COLs to DOCUMENT FRAGMENT
4646 var allCols = this._aColIds || [],
4647 allKeys = this._oColumnSet.keys,
4648 i = 0, len = allCols.length,
4650 elFragment = document.createDocumentFragment(),
4651 elColTemplate = document.createElement("col");
4653 for(i=0,len=allKeys.length; i<len; i++) {
4654 oColumn = allKeys[i];
4655 elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
4659 var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
4660 elColgroup.appendChild(elFragment);
4661 this._elColgroup = elColgroup;
4666 * Adds a COL element to COLGROUP at given index.
4668 * @method _insertColgroupColEl
4669 * @param index {Number} Index of new COL element.
4672 _insertColgroupColEl : function(index) {
4673 if(lang.isNumber(index)&& this._elColgroup) {
4674 var nextSibling = this._elColgroup.childNodes[index] || null;
4675 this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
4680 * Removes a COL element to COLGROUP at given index.
4682 * @method _removeColgroupColEl
4683 * @param index {Number} Index of removed COL element.
4686 _removeColgroupColEl : function(index) {
4687 if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
4688 this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
4693 * Reorders a COL element from old index(es) to new index.
4695 * @method _reorderColgroupColEl
4696 * @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
4697 * @param newIndex {Number} New index.
4700 _reorderColgroupColEl : function(aKeyIndexes, newIndex) {
4701 if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) {
4705 for(i=aKeyIndexes.length-1; i>-1; i--) {
4706 tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
4709 var nextSibling = this._elColgroup.childNodes[newIndex] || null;
4710 for(i=tmpCols.length-1; i>-1; i--) {
4711 this._elColgroup.insertBefore(tmpCols[i], nextSibling);
4717 * Destroy's the DataTable THEAD element, if available.
4719 * @method _destroyTheadEl
4722 _destroyTheadEl : function() {
4723 var elThead = this._elThead;
4725 var elTable = elThead.parentNode;
4726 Ev.purgeElement(elThead, true);
4727 this._destroyColumnHelpers();
4728 elTable.removeChild(elThead);
4729 this._elThead = null;
4734 * Initializes THEAD element.
4736 * @method _initTheadEl
4737 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4738 * @param {HTMLElement} Initialized THEAD element.
4741 _initTheadEl : function(elTable) {
4742 elTable = elTable || this._elTable;
4746 this._destroyTheadEl();
4748 //TODO: append to DOM later for performance
4749 var elThead = (this._elColgroup) ?
4750 elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
4751 elTable.appendChild(document.createElement("thead"));
4753 // Set up DOM events for THEAD
4754 Ev.addListener(elThead, "focus", this._onTheadFocus, this);
4755 Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
4756 Ev.addListener(elThead, "mouseover", this._onTableMouseover, this);
4757 Ev.addListener(elThead, "mouseout", this._onTableMouseout, this);
4758 Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
4759 Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
4760 Ev.addListener(elThead, "click", this._onTheadClick, this);
4762 // Since we can't listen for click and dblclick on the same element...
4763 // Attach separately to THEAD and TBODY
4764 ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
4766 var oColumnSet = this._oColumnSet,
4769 // Add TRs to the THEAD
4770 var colTree = oColumnSet.tree;
4772 for(i=0; i<colTree.length; i++) {
4773 var elTheadTr = elThead.appendChild(document.createElement("tr"));
4775 // ...and create TH cells
4776 for(j=0; j<colTree[i].length; j++) {
4777 oColumn = colTree[i][j];
4778 elTh = elTheadTr.appendChild(document.createElement("th"));
4779 this._initThEl(elTh,oColumn);
4782 // Set FIRST/LAST on THEAD rows
4784 Dom.addClass(elTheadTr, DT.CLASS_FIRST);
4786 if(i === (colTree.length-1)) {
4787 Dom.addClass(elTheadTr, DT.CLASS_LAST);
4792 // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
4793 var aFirstHeaders = oColumnSet.headers[0] || [];
4794 for(i=0; i<aFirstHeaders.length; i++) {
4795 Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
4797 var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
4798 for(i=0; i<aLastHeaders.length; i++) {
4799 Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
4803 ///TODO: try _repaintGecko(this._elContainer) instead
4805 if(ua.webkit && ua.webkit < 420) {
4807 setTimeout(function() {
4808 elThead.style.display = "";
4810 elThead.style.display = 'none';
4813 this._elThead = elThead;
4815 // Column helpers needs _elThead to exist
4816 this._initColumnHelpers();
4821 * Populates TH element as defined by Column.
4824 * @param elTh {HTMLElement} TH element reference.
4825 * @param oColumn {YAHOO.widget.Column} Column object.
4828 _initThEl : function(elTh, oColumn) {
4829 elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
4830 elTh.innerHTML = "";
4831 elTh.rowSpan = oColumn.getRowspan();
4832 elTh.colSpan = oColumn.getColspan();
4833 oColumn._elTh = elTh;
4835 var elThLiner = elTh.appendChild(document.createElement("div"));
4836 elThLiner.id = elTh.id + "-liner"; // Needed for resizer
4837 elThLiner.className = DT.CLASS_LINER;
4838 oColumn._elThLiner = elThLiner;
4840 var elThLabel = elThLiner.appendChild(document.createElement("span"));
4841 elThLabel.className = DT.CLASS_LABEL;
4843 // Assign abbr attribute
4845 elTh.abbr = oColumn.abbr;
4847 // Clear minWidth on hidden Columns
4848 if(oColumn.hidden) {
4849 this._clearMinWidth(oColumn);
4852 elTh.className = this._getColumnClassNames(oColumn);
4854 // Set Column width...
4856 // Validate minWidth
4857 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
4858 oColumn.minWidth : oColumn.width;
4859 // ...for fallback cases
4860 if(DT._bDynStylesFallback) {
4861 elTh.firstChild.style.overflow = 'hidden';
4862 elTh.firstChild.style.width = nWidth + 'px';
4864 // ...for non fallback cases
4866 this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
4870 this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
4871 oColumn._elThLabel = elThLabel;
4875 * Outputs markup into the given TH based on given Column.
4877 * @method DataTable.formatTheadCell
4878 * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
4879 * not the liner DIV element.
4880 * @param oColumn {YAHOO.widget.Column} Column instance.
4881 * @param oSortedBy {Object} Sort state object literal.
4883 formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
4884 var sKey = oColumn.getKey();
4885 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
4887 // Add accessibility link for sortable Columns
4888 if(oColumn.sortable) {
4889 // Calculate the direction
4890 var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
4891 var bDesc = (sSortClass === DT.CLASS_DESC);
4893 // This is the sorted Column
4894 if(oSortedBy && (oColumn.key === oSortedBy.key)) {
4895 bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
4898 // Generate a unique HREF for visited status
4899 var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
4901 // Generate a dynamic TITLE for sort status
4902 var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
4904 // Format the element
4905 elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
4907 // Just display the label for non-sortable Columns
4909 elCellLabel.innerHTML = sLabel;
4914 * Disables DD from top-level Column TH elements.
4916 * @method _destroyDraggableColumns
4919 _destroyDraggableColumns : function() {
4921 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
4922 oColumn = this._oColumnSet.tree[0][i];
4924 oColumn._dd = oColumn._dd.unreg();
4925 Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);
4931 * Initializes top-level Column TH elements into DD instances.
4933 * @method _initDraggableColumns
4936 _initDraggableColumns : function() {
4937 this._destroyDraggableColumns();
4939 var oColumn, elTh, elDragTarget;
4940 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
4941 oColumn = this._oColumnSet.tree[0][i];
4942 elTh = oColumn.getThEl();
4943 Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
4944 elDragTarget = DT._initColumnDragTargetEl();
4945 oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
4953 * Disables resizeability on key Column TH elements.
4955 * @method _destroyResizeableColumns
4958 _destroyResizeableColumns : function() {
4959 var aKeys = this._oColumnSet.keys;
4960 for(var i=0, len=aKeys.length; i<len; i++) {
4961 if(aKeys[i]._ddResizer) {
4962 aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
4963 Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
4969 * Initializes resizeability on key Column TH elements.
4971 * @method _initResizeableColumns
4974 _initResizeableColumns : function() {
4975 this._destroyResizeableColumns();
4977 var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
4978 for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
4979 oColumn = this._oColumnSet.keys[i];
4980 if(oColumn.resizeable) {
4981 elTh = oColumn.getThEl();
4982 Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
4983 elThLiner = oColumn.getThLinerEl();
4985 // Bug 1915349: So resizer is as tall as TH when rowspan > 1
4986 // Create a separate resizer liner with position:relative
4987 elThResizerLiner = elTh.appendChild(document.createElement("div"));
4988 elThResizerLiner.className = DT.CLASS_RESIZERLINER;
4990 // Move TH contents into the new resizer liner
4991 elThResizerLiner.appendChild(elThLiner);
4993 // Create the resizer
4994 elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
4995 elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
4996 elThResizer.className = DT.CLASS_RESIZER;
4997 oColumn._elResizer = elThResizer;
4999 // Create the resizer proxy, once globally
5000 elResizerProxy = DT._initColumnResizerProxyEl();
5001 oColumn._ddResizer = new YAHOO.util.ColumnResizer(
5002 this, oColumn, elTh, elThResizer, elResizerProxy);
5003 cancelClick = function(e) {
5004 Ev.stopPropagation(e);
5006 Ev.addListener(elThResizer,"click",cancelClick);
5015 * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
5017 * @method _destroyColumnHelpers
5020 _destroyColumnHelpers : function() {
5021 this._destroyDraggableColumns();
5022 this._destroyResizeableColumns();
5026 * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
5028 * @method _initColumnHelpers
5031 _initColumnHelpers : function() {
5032 if(this.get("draggableColumns")) {
5033 this._initDraggableColumns();
5035 this._initResizeableColumns();
5039 * Destroy's the DataTable TBODY element, if available.
5041 * @method _destroyTbodyEl
5044 _destroyTbodyEl : function() {
5045 var elTbody = this._elTbody;
5047 var elTable = elTbody.parentNode;
5048 Ev.purgeElement(elTbody, true);
5049 elTable.removeChild(elTbody);
5050 this._elTbody = null;
5055 * Initializes TBODY element for data.
5057 * @method _initTbodyEl
5058 * @param elTable {HTMLElement} TABLE element into which to create TBODY .
5061 _initTbodyEl : function(elTable) {
5064 this._destroyTbodyEl();
5067 var elTbody = elTable.appendChild(document.createElement("tbody"));
5068 elTbody.tabIndex = 0;
5069 elTbody.className = DT.CLASS_DATA;
5071 // Set up DOM events for TBODY
5072 Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
5073 Ev.addListener(elTbody, "mouseover", this._onTableMouseover, this);
5074 Ev.addListener(elTbody, "mouseout", this._onTableMouseout, this);
5075 Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
5076 Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
5077 Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
5078 Ev.addListener(elTbody, "keypress", this._onTableKeypress, this);
5079 Ev.addListener(elTbody, "click", this._onTbodyClick, this);
5081 // Since we can't listen for click and dblclick on the same element...
5082 // Attach separately to THEAD and TBODY
5083 ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
5086 // IE puts focus outline in the wrong place
5088 elTbody.hideFocus=true;
5091 this._elTbody = elTbody;
5096 * Destroy's the DataTable message TBODY element, if available.
5098 * @method _destroyMsgTbodyEl
5101 _destroyMsgTbodyEl : function() {
5102 var elMsgTbody = this._elMsgTbody;
5104 var elTable = elMsgTbody.parentNode;
5105 Ev.purgeElement(elMsgTbody, true);
5106 elTable.removeChild(elMsgTbody);
5107 this._elTbody = null;
5112 * Initializes TBODY element for messaging.
5114 * @method _initMsgTbodyEl
5115 * @param elTable {HTMLElement} TABLE element into which to create TBODY
5118 _initMsgTbodyEl : function(elTable) {
5120 var elMsgTbody = document.createElement("tbody");
5121 elMsgTbody.className = DT.CLASS_MESSAGE;
5122 var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
5123 elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5124 this._elMsgTr = elMsgTr;
5125 var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
5126 elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
5127 elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5128 this._elMsgTd = elMsgTd;
5129 elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
5130 var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
5131 elMsgLiner.className = DT.CLASS_LINER;
5132 this._elMsgTbody = elMsgTbody;
5134 // Set up DOM events for TBODY
5135 Ev.addListener(elMsgTbody, "focus", this._onTbodyFocus, this);
5136 Ev.addListener(elMsgTbody, "mouseover", this._onTableMouseover, this);
5137 Ev.addListener(elMsgTbody, "mouseout", this._onTableMouseout, this);
5138 Ev.addListener(elMsgTbody, "mousedown", this._onTableMousedown, this);
5139 Ev.addListener(elMsgTbody, "mouseup", this._onTableMouseup, this);
5140 Ev.addListener(elMsgTbody, "keydown", this._onTbodyKeydown, this);
5141 Ev.addListener(elMsgTbody, "keypress", this._onTableKeypress, this);
5142 Ev.addListener(elMsgTbody, "click", this._onTbodyClick, this);
5147 * Initialize internal event listeners
5149 * @method _initEvents
5152 _initEvents : function () {
5153 // Initialize Column sort
5154 this._initColumnSort();
5156 // Add the document level click listener
5157 YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
5159 // Paginator integration
5160 this.subscribe("paginatorChange",function () {
5161 this._handlePaginatorChange.apply(this,arguments);
5164 this.subscribe("initEvent",function () {
5165 this.renderPaginator();
5168 // Initialize CellEditor integration
5169 this._initCellEditing();
5173 * Initializes Column sorting.
5175 * @method _initColumnSort
5178 _initColumnSort : function() {
5179 this.subscribe("theadCellClickEvent", this.onEventSortColumn);
5181 // Backward compatibility
5182 var oSortedBy = this.get("sortedBy");
5184 if(oSortedBy.dir == "desc") {
5185 this._configs.sortedBy.value.dir = DT.CLASS_DESC;
5187 else if(oSortedBy.dir == "asc") {
5188 this._configs.sortedBy.value.dir = DT.CLASS_ASC;
5194 * Initializes CellEditor integration.
5196 * @method _initCellEditing
5199 _initCellEditing : function() {
5200 this.subscribe("editorBlurEvent",function () {
5201 this.onEditorBlurEvent.apply(this,arguments);
5203 this.subscribe("editorBlockEvent",function () {
5204 this.onEditorBlockEvent.apply(this,arguments);
5206 this.subscribe("editorUnblockEvent",function () {
5207 this.onEditorUnblockEvent.apply(this,arguments);
5243 // DOM MUTATION FUNCTIONS
5246 * Retruns classnames to represent current Column states.
5247 * @method _getColumnClassnames
5248 * @param oColumn {YAHOO.widget.Column} Column instance.
5249 * @param aAddClasses {String[]} An array of additional classnames to add to the
5251 * @return {String} A String of classnames to be assigned to TH or TD elements
5255 _getColumnClassNames : function (oColumn, aAddClasses) {
5259 if(lang.isString(oColumn.className)) {
5260 // Single custom class
5261 allClasses = [oColumn.className];
5263 else if(lang.isArray(oColumn.className)) {
5264 // Array of custom classes
5265 allClasses = oColumn.className;
5268 // no custom classes
5272 // Hook for setting width with via dynamic style uses key since ID is too disposable
5273 allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
5275 // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
5276 allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
5278 var isSortedBy = this.get("sortedBy") || {};
5280 if(oColumn.key === isSortedBy.key) {
5281 allClasses[allClasses.length] = isSortedBy.dir || '';
5284 if(oColumn.hidden) {
5285 allClasses[allClasses.length] = DT.CLASS_HIDDEN;
5288 if(oColumn.selected) {
5289 allClasses[allClasses.length] = DT.CLASS_SELECTED;
5292 if(oColumn.sortable) {
5293 allClasses[allClasses.length] = DT.CLASS_SORTABLE;
5296 if(oColumn.resizeable) {
5297 allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
5300 if(oColumn.editor) {
5301 allClasses[allClasses.length] = DT.CLASS_EDITABLE;
5304 // Addtnl classes, including First/Last
5306 allClasses = allClasses.concat(aAddClasses);
5309 return allClasses.join(' ');
5313 * Clears TR element template in response to any Column state change.
5314 * @method _clearTrTemplateEl
5317 _clearTrTemplateEl : function () {
5318 this._elTrTemplate = null;
5322 * Returns a new TR element template with TD elements classed with current
5324 * @method _getTrTemplateEl
5325 * @return {HTMLElement} A TR element to be cloned and added to the DOM.
5328 _getTrTemplateEl : function (oRecord, index) {
5329 // Template is already available
5330 if(this._elTrTemplate) {
5331 return this._elTrTemplate;
5333 // Template needs to be created
5336 tr = d.createElement('tr'),
5337 td = d.createElement('td'),
5338 div = d.createElement('div');
5340 // Append the liner element
5341 td.appendChild(div);
5343 // Create TD elements into DOCUMENT FRAGMENT
5344 var df = document.createDocumentFragment(),
5345 allKeys = this._oColumnSet.keys,
5348 // Set state for each TD;
5350 for(var i=0, keysLen=allKeys.length; i<keysLen; i++) {
5351 // Clone the TD template
5352 elTd = td.cloneNode(true);
5354 // Format the base TD
5355 elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
5357 df.appendChild(elTd);
5360 this._elTrTemplate = tr;
5366 * Formats a basic TD element.
5367 * @method _formatTdEl
5368 * @param oColumn {YAHOO.widget.Column} Associated Column instance.
5369 * @param elTd {HTMLElement} An unformatted TD element.
5370 * @param index {Number} Column key index.
5371 * @param isLast {Boolean} True if Column is last key of the ColumnSet.
5372 * @return {HTMLElement} A formatted TD element.
5375 _formatTdEl : function (oColumn, elTd, index, isLast) {
5376 var oColumnSet = this._oColumnSet;
5378 // Set the TD's accessibility headers
5379 var allHeaders = oColumnSet.headers,
5380 allColHeaders = allHeaders[index],
5383 for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
5384 sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
5385 sTdHeaders += sHeader;
5387 elTd.headers = sTdHeaders;
5389 // Class the TD element
5390 var aAddClasses = [];
5392 aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
5395 aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
5397 elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
5399 // Class the liner element
5400 elTd.firstChild.className = DT.CLASS_LINER;
5402 // Set Column width for fallback cases
5403 if(oColumn.width && DT._bDynStylesFallback) {
5404 // Validate minWidth
5405 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
5406 oColumn.minWidth : oColumn.width;
5407 elTd.firstChild.style.overflow = 'hidden';
5408 elTd.firstChild.style.width = nWidth + 'px';
5416 * Create a new TR element for a given Record and appends it with the correct
5417 * number of Column-state-classed TD elements. Striping is the responsibility of
5418 * the calling function, which may decide to stripe the single row, a subset of
5419 * rows, or all the rows.
5420 * @method _createTrEl
5421 * @param oRecord {YAHOO.widget.Record} Record instance
5422 * @return {HTMLElement} The new TR element. This must be added to the DOM.
5425 _addTrEl : function (oRecord) {
5426 var elTrTemplate = this._getTrTemplateEl();
5428 // Clone the TR template.
5429 var elTr = elTrTemplate.cloneNode(true);
5432 return this._updateTrEl(elTr,oRecord);
5436 * Formats the contents of the given TR's TD elements with data from the given
5437 * Record. Only innerHTML should change, nothing structural.
5439 * @method _updateTrEl
5440 * @param elTr {HTMLElement} The TR element to update.
5441 * @param oRecord {YAHOO.widget.Record} The associated Record instance.
5442 * @return {HTMLElement} DOM reference to the new TR element.
5445 _updateTrEl : function(elTr, oRecord) {
5446 var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
5448 // Hide the row to prevent constant reflows
5449 elTr.style.display = 'none';
5451 // Update TD elements with new data
5452 var allTds = elTr.childNodes,
5454 for(var i=0,len=allTds.length; i<len; ++i) {
5457 // Set the cell content
5458 this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
5461 // Redisplay the row for reflow
5462 elTr.style.display = '';
5465 elTr.id = oRecord.getId(); // Needed for Record association and tracking of FIRST/LAST
5471 * Deletes TR element by DOM reference or by DataTable page row index.
5473 * @method _deleteTrEl
5474 * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
5475 * @return {Boolean} Returns true if successful, else returns false.
5478 _deleteTrEl : function(row) {
5481 // Get page row index for the element
5482 if(!lang.isNumber(row)) {
5483 rowIndex = Dom.get(row).sectionRowIndex;
5488 if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
5489 // Cannot use tbody.deleteRow due to IE6 instability
5490 //return this._elTbody.deleteRow(rowIndex);
5491 return this._elTbody.removeChild(this.getTrEl(row));
5524 // CSS/STATE FUNCTIONS
5530 * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
5531 * of the DataTable page and updates internal tracker.
5533 * @method _unsetFirstRow
5536 _unsetFirstRow : function() {
5538 if(this._sFirstTrId) {
5539 Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
5540 this._sFirstTrId = null;
5545 * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
5546 * of the DataTable page and updates internal tracker.
5548 * @method _setFirstRow
5551 _setFirstRow : function() {
5552 this._unsetFirstRow();
5553 var elTr = this.getFirstTrEl();
5556 Dom.addClass(elTr, DT.CLASS_FIRST);
5557 this._sFirstTrId = elTr.id;
5562 * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
5563 * of the DataTable page and updates internal tracker.
5565 * @method _unsetLastRow
5568 _unsetLastRow : function() {
5569 // Unassign previous class
5570 if(this._sLastTrId) {
5571 Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
5572 this._sLastTrId = null;
5577 * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
5578 * of the DataTable page and updates internal tracker.
5580 * @method _setLastRow
5583 _setLastRow : function() {
5584 this._unsetLastRow();
5585 var elTr = this.getLastTrEl();
5588 Dom.addClass(elTr, DT.CLASS_LAST);
5589 this._sLastTrId = elTr.id;
5594 * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
5596 * @method _setRowStripes
5597 * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
5598 * or string ID, or page row index of where to start striping.
5599 * @param range {Number} (optional) If given, how many rows to stripe, otherwise
5600 * stripe all the rows until the end.
5603 _setRowStripes : function(row, range) {
5604 // Default values stripe all rows
5605 var allRows = this._elTbody.rows,
5607 nEndIndex = allRows.length,
5608 aOdds = [], nOddIdx = 0,
5609 aEvens = [], nEvenIdx = 0;
5612 if((row !== null) && (row !== undefined)) {
5613 // Validate given start row
5614 var elStartRow = this.getTrEl(row);
5616 nStartIndex = elStartRow.sectionRowIndex;
5618 // Validate given range
5619 if(lang.isNumber(range) && (range > 1)) {
5620 nEndIndex = nStartIndex + range;
5625 for(var i=nStartIndex; i<nEndIndex; i++) {
5627 aOdds[nOddIdx++] = allRows[i];
5629 aEvens[nEvenIdx++] = allRows[i];
5634 Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
5637 if (aEvens.length) {
5638 Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
5643 * Assigns the class DT.CLASS_SELECTED to TR and TD elements.
5645 * @method _setSelections
5648 _setSelections : function() {
5649 // Keep track of selected rows
5650 var allSelectedRows = this.getSelectedRows();
5651 // Keep track of selected cells
5652 var allSelectedCells = this.getSelectedCells();
5653 // Anything to select?
5654 if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
5655 var oColumnSet = this._oColumnSet,
5657 // Loop over each row
5658 for(var i=0; i<allSelectedRows.length; i++) {
5659 el = Dom.get(allSelectedRows[i]);
5661 Dom.addClass(el, DT.CLASS_SELECTED);
5664 // Loop over each cell
5665 for(i=0; i<allSelectedCells.length; i++) {
5666 el = Dom.get(allSelectedCells[i].recordId);
5668 Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
5716 /////////////////////////////////////////////////////////////////////////////
5718 // Private DOM Event Handlers
5720 /////////////////////////////////////////////////////////////////////////////
5723 * Validates minWidths whenever the render chain ends.
5725 * @method _onRenderChainEnd
5728 _onRenderChainEnd : function() {
5729 // Hide loading message
5730 this.hideTableMessage();
5732 // Show empty message
5733 if(this._elTbody.rows.length === 0) {
5734 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
5737 // Execute in timeout thread to give implementers a chance
5738 // to subscribe after the constructor
5740 setTimeout(function() {
5741 if((oSelf instanceof DT) && oSelf._sId) {
5744 oSelf._bInit = false;
5745 oSelf.fireEvent("initEvent");
5749 oSelf.fireEvent("renderEvent");
5750 // Backward compatibility
5751 oSelf.fireEvent("refreshEvent");
5753 // Post-render routine
5754 oSelf.validateColumnWidths();
5756 // Post-render event
5757 oSelf.fireEvent("postRenderEvent");
5759 /*if(YAHOO.example.Performance.trialStart) {
5760 YAHOO.example.Performance.trialStart = null;
5768 * Handles click events on the DOCUMENT.
5770 * @method _onDocumentClick
5771 * @param e {HTMLEvent} The click event.
5772 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5775 _onDocumentClick : function(e, oSelf) {
5776 var elTarget = Ev.getTarget(e);
5777 var elTag = elTarget.nodeName.toLowerCase();
5779 if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
5780 oSelf.fireEvent("tableBlurEvent");
5782 // Fires editorBlurEvent when click is not within the TABLE.
5783 // For cases when click is within the TABLE, due to timing issues,
5784 // the editorBlurEvent needs to get fired by the lower-level DOM click
5785 // handlers below rather than by the TABLE click handler directly.
5786 if(oSelf._oCellEditor) {
5787 if(oSelf._oCellEditor.getContainerEl) {
5788 var elContainer = oSelf._oCellEditor.getContainerEl();
5789 // Only if the click was not within the CellEditor container
5790 if(!Dom.isAncestor(elContainer, elTarget) &&
5791 (elContainer.id !== elTarget.id)) {
5792 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
5795 // Backward Compatibility
5796 else if(oSelf._oCellEditor.isActive) {
5797 // Only if the click was not within the Cell Editor container
5798 if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
5799 (oSelf._oCellEditor.container.id !== elTarget.id)) {
5800 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
5808 * Handles focus events on the DataTable instance.
5810 * @method _onTableFocus
5811 * @param e {HTMLEvent} The focus event.
5812 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5815 _onTableFocus : function(e, oSelf) {
5816 oSelf.fireEvent("tableFocusEvent");
5820 * Handles focus events on the THEAD element.
5822 * @method _onTheadFocus
5823 * @param e {HTMLEvent} The focus event.
5824 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5827 _onTheadFocus : function(e, oSelf) {
5828 oSelf.fireEvent("theadFocusEvent");
5829 oSelf.fireEvent("tableFocusEvent");
5833 * Handles focus events on the TBODY element.
5835 * @method _onTbodyFocus
5836 * @param e {HTMLEvent} The focus event.
5837 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5840 _onTbodyFocus : function(e, oSelf) {
5841 oSelf.fireEvent("tbodyFocusEvent");
5842 oSelf.fireEvent("tableFocusEvent");
5846 * Handles mouseover events on the DataTable instance.
5848 * @method _onTableMouseover
5849 * @param e {HTMLEvent} The mouseover event.
5850 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5853 _onTableMouseover : function(e, oSelf) {
5854 var elTarget = Ev.getTarget(e);
5855 var elTag = elTarget.nodeName.toLowerCase();
5856 var bKeepBubbling = true;
5857 while(elTarget && (elTag != "table")) {
5864 bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
5867 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5868 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
5869 // Backward compatibility
5870 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
5874 bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
5875 // Backward compatibility
5876 bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
5879 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5880 bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
5881 // Backward compatibility
5882 bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
5885 bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
5891 if(bKeepBubbling === false) {
5895 elTarget = elTarget.parentNode;
5897 elTag = elTarget.nodeName.toLowerCase();
5901 oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
5905 * Handles mouseout events on the DataTable instance.
5907 * @method _onTableMouseout
5908 * @param e {HTMLEvent} The mouseout event.
5909 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5912 _onTableMouseout : function(e, oSelf) {
5913 var elTarget = Ev.getTarget(e);
5914 var elTag = elTarget.nodeName.toLowerCase();
5915 var bKeepBubbling = true;
5916 while(elTarget && (elTag != "table")) {
5923 bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
5926 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5927 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
5928 // Backward compatibility
5929 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
5933 bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
5934 // Backward compatibility
5935 bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
5938 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5939 bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
5940 // Backward compatibility
5941 bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
5944 bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
5950 if(bKeepBubbling === false) {
5954 elTarget = elTarget.parentNode;
5956 elTag = elTarget.nodeName.toLowerCase();
5960 oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
5964 * Handles mousedown events on the DataTable instance.
5966 * @method _onTableMousedown
5967 * @param e {HTMLEvent} The mousedown event.
5968 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5971 _onTableMousedown : function(e, oSelf) {
5972 var elTarget = Ev.getTarget(e);
5973 var elTag = elTarget.nodeName.toLowerCase();
5974 var bKeepBubbling = true;
5975 while(elTarget && (elTag != "table")) {
5982 bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
5985 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5986 bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
5987 // Backward compatibility
5988 bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
5992 bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
5993 // Backward compatibility
5994 bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
5997 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5998 bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
5999 // Backward compatibility
6000 bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
6003 bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
6009 if(bKeepBubbling === false) {
6013 elTarget = elTarget.parentNode;
6015 elTag = elTarget.nodeName.toLowerCase();
6019 oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
6023 * Handles mouseup events on the DataTable instance.
6025 * @method _onTableMouseup
6026 * @param e {HTMLEvent} The mouseup event.
6027 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6030 _onTableMouseup : function(e, oSelf) {
6031 var elTarget = Ev.getTarget(e);
6032 var elTag = elTarget.nodeName.toLowerCase();
6033 var bKeepBubbling = true;
6034 while(elTarget && (elTag != "table")) {
6041 bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
6044 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6045 bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
6046 // Backward compatibility
6047 bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
6051 bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
6052 // Backward compatibility
6053 bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
6056 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6057 bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
6058 // Backward compatibility
6059 bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
6062 bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
6068 if(bKeepBubbling === false) {
6072 elTarget = elTarget.parentNode;
6074 elTag = elTarget.nodeName.toLowerCase();
6078 oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
6082 * Handles dblclick events on the DataTable instance.
6084 * @method _onTableDblclick
6085 * @param e {HTMLEvent} The dblclick event.
6086 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6089 _onTableDblclick : function(e, oSelf) {
6090 var elTarget = Ev.getTarget(e);
6091 var elTag = elTarget.nodeName.toLowerCase();
6092 var bKeepBubbling = true;
6093 while(elTarget && (elTag != "table")) {
6098 bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
6101 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6102 bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
6103 // Backward compatibility
6104 bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
6108 bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
6109 // Backward compatibility
6110 bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
6113 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6114 bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
6115 // Backward compatibility
6116 bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
6119 bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
6125 if(bKeepBubbling === false) {
6129 elTarget = elTarget.parentNode;
6131 elTag = elTarget.nodeName.toLowerCase();
6135 oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6138 * Handles keydown events on the THEAD element.
6140 * @method _onTheadKeydown
6141 * @param e {HTMLEvent} The key event.
6142 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6145 _onTheadKeydown : function(e, oSelf) {
6146 var elTarget = Ev.getTarget(e);
6147 var elTag = elTarget.nodeName.toLowerCase();
6148 var bKeepBubbling = true;
6149 while(elTarget && (elTag != "table")) {
6155 // TODO: implement textareaKeyEvent
6158 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
6163 if(bKeepBubbling === false) {
6167 elTarget = elTarget.parentNode;
6169 elTag = elTarget.nodeName.toLowerCase();
6173 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6177 * Handles keydown events on the TBODY element. Handles selection behavior,
6178 * provides hooks for ENTER to edit functionality.
6180 * @method _onTbodyKeydown
6181 * @param e {HTMLEvent} The key event.
6182 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6185 _onTbodyKeydown : function(e, oSelf) {
6186 var sMode = oSelf.get("selectionMode");
6188 if(sMode == "standard") {
6189 oSelf._handleStandardSelectionByKey(e);
6191 else if(sMode == "single") {
6192 oSelf._handleSingleSelectionByKey(e);
6194 else if(sMode == "cellblock") {
6195 oSelf._handleCellBlockSelectionByKey(e);
6197 else if(sMode == "cellrange") {
6198 oSelf._handleCellRangeSelectionByKey(e);
6200 else if(sMode == "singlecell") {
6201 oSelf._handleSingleCellSelectionByKey(e);
6204 if(oSelf._oCellEditor) {
6205 if(oSelf._oCellEditor.fireEvent) {
6206 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6208 else if(oSelf._oCellEditor.isActive) {
6209 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6213 var elTarget = Ev.getTarget(e);
6214 var elTag = elTarget.nodeName.toLowerCase();
6215 var bKeepBubbling = true;
6216 while(elTarget && (elTag != "table")) {
6221 bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
6226 if(bKeepBubbling === false) {
6230 elTarget = elTarget.parentNode;
6232 elTag = elTarget.nodeName.toLowerCase();
6236 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6240 * Handles keypress events on the TABLE. Mainly to support stopEvent on Mac.
6242 * @method _onTableKeypress
6243 * @param e {HTMLEvent} The key event.
6244 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6247 _onTableKeypress : function(e, oSelf) {
6248 if(ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") !== -1) && (ua.webkit < 420)) {
6249 var nKey = Ev.getCharCode(e);
6255 else if(nKey == 38) {
6262 * Handles click events on the THEAD element.
6264 * @method _onTheadClick
6265 * @param e {HTMLEvent} The click event.
6266 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6269 _onTheadClick : function(e, oSelf) {
6270 // This blurs the CellEditor
6271 if(oSelf._oCellEditor) {
6272 if(oSelf._oCellEditor.fireEvent) {
6273 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6275 // Backward compatibility
6276 else if(oSelf._oCellEditor.isActive) {
6277 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6281 var elTarget = Ev.getTarget(e),
6282 elTag = elTarget.nodeName.toLowerCase(),
6283 bKeepBubbling = true;
6284 while(elTarget && (elTag != "table")) {
6289 var sType = elTarget.type.toLowerCase();
6290 if(sType == "checkbox") {
6291 bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
6293 else if(sType == "radio") {
6294 bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
6296 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6297 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6301 bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
6304 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6307 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6308 bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
6309 // Backward compatibility
6310 bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
6314 bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
6315 // Backward compatibility
6316 bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
6319 bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
6320 // Backward compatibility
6321 bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
6326 if(bKeepBubbling === false) {
6330 elTarget = elTarget.parentNode;
6332 elTag = elTarget.nodeName.toLowerCase();
6336 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6340 * Handles click events on the primary TBODY element.
6342 * @method _onTbodyClick
6343 * @param e {HTMLEvent} The click event.
6344 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6347 _onTbodyClick : function(e, oSelf) {
6348 // This blurs the CellEditor
6349 if(oSelf._oCellEditor) {
6350 if(oSelf._oCellEditor.fireEvent) {
6351 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6353 else if(oSelf._oCellEditor.isActive) {
6354 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6358 // Fire Custom Events
6359 var elTarget = Ev.getTarget(e),
6360 elTag = elTarget.nodeName.toLowerCase(),
6361 bKeepBubbling = true;
6362 while(elTarget && (elTag != "table")) {
6367 var sType = elTarget.type.toLowerCase();
6368 if(sType == "checkbox") {
6369 bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
6371 else if(sType == "radio") {
6372 bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
6374 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6375 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6379 bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
6382 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6385 bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
6388 bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
6393 if(bKeepBubbling === false) {
6397 elTarget = elTarget.parentNode;
6399 elTag = elTarget.nodeName.toLowerCase();
6403 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6407 * Handles change events on SELECT elements within DataTable.
6409 * @method _onDropdownChange
6410 * @param e {HTMLEvent} The change event.
6411 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6414 _onDropdownChange : function(e, oSelf) {
6415 var elTarget = Ev.getTarget(e);
6416 oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
6450 /////////////////////////////////////////////////////////////////////////////
6452 // Public member variables
6454 /////////////////////////////////////////////////////////////////////////////
6456 * Returns object literal of initial configs.
6465 /////////////////////////////////////////////////////////////////////////////
6469 /////////////////////////////////////////////////////////////////////////////
6472 * Returns unique id assigned to instance, which is a useful prefix for
6473 * generating unique DOM ID strings.
6476 * @return {String} Unique ID of the DataSource instance.
6478 getId : function() {
6483 * DataSource instance name, for logging.
6486 * @return {String} Unique name of the DataSource instance.
6489 toString : function() {
6490 return "DataTable instance " + this._sId;
6494 * Returns the DataTable instance's DataSource instance.
6496 * @method getDataSource
6497 * @return {YAHOO.util.DataSource} DataSource instance.
6499 getDataSource : function() {
6500 return this._oDataSource;
6504 * Returns the DataTable instance's ColumnSet instance.
6506 * @method getColumnSet
6507 * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
6509 getColumnSet : function() {
6510 return this._oColumnSet;
6514 * Returns the DataTable instance's RecordSet instance.
6516 * @method getRecordSet
6517 * @return {YAHOO.widget.RecordSet} RecordSet instance.
6519 getRecordSet : function() {
6520 return this._oRecordSet;
6524 * Returns on object literal representing the DataTable instance's current
6525 * state with the following properties:
6527 * <dt>pagination</dt>
6528 * <dd>Instance of YAHOO.widget.Paginator</dd>
6533 * <dt>sortedBy.key</dt>
6534 * <dd>{String} Key of sorted Column</dd>
6535 * <dt>sortedBy.dir</dt>
6536 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
6540 * <dt>selectedRows</dt>
6541 * <dd>Array of selected rows by Record ID.</dd>
6543 * <dt>selectedCells</dt>
6544 * <dd>Selected cells as an array of object literals:
6545 * {recordId:sRecordId, columnKey:sColumnKey}</dd>
6549 * @return {Object} DataTable instance state object literal values.
6551 getState : function() {
6553 totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
6554 pagination: this.get("paginator") ? this.get("paginator").getState() : null,
6555 sortedBy: this.get("sortedBy"),
6556 selectedRows: this.getSelectedRows(),
6557 selectedCells: this.getSelectedCells()
6606 * Returns DOM reference to the DataTable's container element.
6608 * @method getContainerEl
6609 * @return {HTMLElement} Reference to DIV element.
6611 getContainerEl : function() {
6612 return this._elContainer;
6616 * Returns DOM reference to the DataTable's TABLE element.
6618 * @method getTableEl
6619 * @return {HTMLElement} Reference to TABLE element.
6621 getTableEl : function() {
6622 return this._elTable;
6626 * Returns DOM reference to the DataTable's THEAD element.
6628 * @method getTheadEl
6629 * @return {HTMLElement} Reference to THEAD element.
6631 getTheadEl : function() {
6632 return this._elThead;
6636 * Returns DOM reference to the DataTable's primary TBODY element.
6638 * @method getTbodyEl
6639 * @return {HTMLElement} Reference to TBODY element.
6641 getTbodyEl : function() {
6642 return this._elTbody;
6646 * Returns DOM reference to the DataTable's secondary TBODY element that is
6647 * used to display messages.
6649 * @method getMsgTbodyEl
6650 * @return {HTMLElement} Reference to TBODY element.
6652 getMsgTbodyEl : function() {
6653 return this._elMsgTbody;
6657 * Returns DOM reference to the TD element within the secondary TBODY that is
6658 * used to display messages.
6660 * @method getMsgTdEl
6661 * @return {HTMLElement} Reference to TD element.
6663 getMsgTdEl : function() {
6664 return this._elMsgTd;
6668 * Returns the corresponding TR reference for a given DOM element, ID string or
6669 * directly page row index. If the given identifier is a child of a TR element,
6670 * then DOM tree is traversed until a parent TR element is returned, otherwise
6674 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
6675 * get: by element reference, ID string, page row index, or Record.
6676 * @return {HTMLElement} Reference to TR element, or null.
6678 getTrEl : function(row) {
6680 if(row instanceof YAHOO.widget.Record) {
6681 return document.getElementById(row.getId());
6683 // By page row index
6684 else if(lang.isNumber(row)) {
6685 var allRows = this._elTbody.rows;
6686 return ((row > -1) && (row < allRows.length)) ? allRows[row] : null;
6688 // By ID string or element reference
6690 var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
6692 // Validate HTML element
6693 if(elRow && (elRow.ownerDocument == document)) {
6694 // Validate TR element
6695 if(elRow.nodeName.toLowerCase() != "tr") {
6696 // Traverse up the DOM to find the corresponding TR element
6697 elRow = Dom.getAncestorByTagName(elRow,"tr");
6708 * Returns DOM reference to the first TR element in the DataTable page, or null.
6710 * @method getFirstTrEl
6711 * @return {HTMLElement} Reference to TR element.
6713 getFirstTrEl : function() {
6714 return this._elTbody.rows[0] || null;
6718 * Returns DOM reference to the last TR element in the DataTable page, or null.
6720 * @method getLastTrEl
6721 * @return {HTMLElement} Reference to last TR element.
6723 getLastTrEl : function() {
6724 var allRows = this._elTbody.rows;
6725 if(allRows.length > 0) {
6726 return allRows[allRows.length-1] || null;
6731 * Returns DOM reference to the next TR element from the given TR element, or null.
6733 * @method getNextTrEl
6734 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6735 * reference, ID string, page row index, or Record from which to get next TR element.
6736 * @return {HTMLElement} Reference to next TR element.
6738 getNextTrEl : function(row) {
6739 var nThisTrIndex = this.getTrIndex(row);
6740 if(nThisTrIndex !== null) {
6741 var allRows = this._elTbody.rows;
6742 if(nThisTrIndex < allRows.length-1) {
6743 return allRows[nThisTrIndex+1];
6751 * Returns DOM reference to the previous TR element from the given TR element, or null.
6753 * @method getPreviousTrEl
6754 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6755 * reference, ID string, page row index, or Record from which to get previous TR element.
6756 * @return {HTMLElement} Reference to previous TR element.
6758 getPreviousTrEl : function(row) {
6759 var nThisTrIndex = this.getTrIndex(row);
6760 if(nThisTrIndex !== null) {
6761 var allRows = this._elTbody.rows;
6762 if(nThisTrIndex > 0) {
6763 return allRows[nThisTrIndex-1];
6771 * Returns DOM reference to a TD liner element.
6773 * @method getTdLinerEl
6774 * @param cell {HTMLElement | Object} TD element or child of a TD element, or
6775 * object literal of syntax {record:oRecord, column:oColumn}.
6776 * @return {HTMLElement} Reference to TD liner element.
6778 getTdLinerEl : function(cell) {
6779 var elCell = this.getTdEl(cell);
6780 return elCell.firstChild || null;
6784 * Returns DOM reference to a TD element.
6787 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
6788 * object literal of syntax {record:oRecord, column:oColumn}.
6789 * @return {HTMLElement} Reference to TD element.
6791 getTdEl : function(cell) {
6793 var el = Dom.get(cell);
6795 // Validate HTML element
6796 if(el && (el.ownerDocument == document)) {
6797 // Validate TD element
6798 if(el.nodeName.toLowerCase() != "td") {
6799 // Traverse up the DOM to find the corresponding TR element
6800 elCell = Dom.getAncestorByTagName(el, "td");
6806 // Make sure the TD is in this TBODY
6807 // Bug 2527707 and bug 2263558
6808 if(elCell && ((elCell.parentNode.parentNode == this._elTbody) || (elCell.parentNode.parentNode === null))) {
6809 // Now we can return the TD element
6814 var oRecord, nColKeyIndex;
6816 if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
6817 oRecord = this.getRecord(cell.recordId);
6818 var oColumn = this.getColumn(cell.columnKey);
6820 nColKeyIndex = oColumn.getKeyIndex();
6824 if(cell.record && cell.column && cell.column.getKeyIndex) {
6825 oRecord = cell.record;
6826 nColKeyIndex = cell.column.getKeyIndex();
6828 var elRow = this.getTrEl(oRecord);
6829 if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
6830 return elRow.cells[nColKeyIndex] || null;
6838 * Returns DOM reference to the first TD element in the DataTable page (by default),
6839 * the first TD element of the optionally given row, or null.
6841 * @method getFirstTdEl
6842 * @param row {HTMLElement} (optional) row from which to get first TD
6843 * @return {HTMLElement} Reference to TD element.
6845 getFirstTdEl : function(row) {
6846 var elRow = this.getTrEl(row) || this.getFirstTrEl();
6847 if(elRow && (elRow.cells.length > 0)) {
6848 return elRow.cells[0];
6854 * Returns DOM reference to the last TD element in the DataTable page (by default),
6855 * the first TD element of the optionally given row, or null.
6857 * @method getLastTdEl
6858 * @return {HTMLElement} Reference to last TD element.
6860 getLastTdEl : function(row) {
6861 var elRow = this.getTrEl(row) || this.getLastTrEl();
6862 if(elRow && (elRow.cells.length > 0)) {
6863 return elRow.cells[elRow.cells.length-1];
6869 * Returns DOM reference to the next TD element from the given cell, or null.
6871 * @method getNextTdEl
6872 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6873 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6874 * @return {HTMLElement} Reference to next TD element, or null.
6876 getNextTdEl : function(cell) {
6877 var elCell = this.getTdEl(cell);
6879 var nThisTdIndex = elCell.cellIndex;
6880 var elRow = this.getTrEl(elCell);
6881 if(nThisTdIndex < elRow.cells.length-1) {
6882 return elRow.cells[nThisTdIndex+1];
6885 var elNextRow = this.getNextTrEl(elRow);
6887 return elNextRow.cells[0];
6895 * Returns DOM reference to the previous TD element from the given cell, or null.
6897 * @method getPreviousTdEl
6898 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6899 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
6900 * @return {HTMLElement} Reference to previous TD element, or null.
6902 getPreviousTdEl : function(cell) {
6903 var elCell = this.getTdEl(cell);
6905 var nThisTdIndex = elCell.cellIndex;
6906 var elRow = this.getTrEl(elCell);
6907 if(nThisTdIndex > 0) {
6908 return elRow.cells[nThisTdIndex-1];
6911 var elPreviousRow = this.getPreviousTrEl(elRow);
6913 return this.getLastTdEl(elPreviousRow);
6921 * Returns DOM reference to the above TD element from the given cell, or null.
6923 * @method getAboveTdEl
6924 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6925 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6926 * @return {HTMLElement} Reference to next TD element, or null.
6928 getAboveTdEl : function(cell) {
6929 var elCell = this.getTdEl(cell);
6931 var elPreviousRow = this.getPreviousTrEl(elCell);
6933 return elPreviousRow.cells[elCell.cellIndex];
6940 * Returns DOM reference to the below TD element from the given cell, or null.
6942 * @method getBelowTdEl
6943 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6944 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
6945 * @return {HTMLElement} Reference to previous TD element, or null.
6947 getBelowTdEl : function(cell) {
6948 var elCell = this.getTdEl(cell);
6950 var elNextRow = this.getNextTrEl(elCell);
6952 return elNextRow.cells[elCell.cellIndex];
6959 * Returns DOM reference to a TH liner element. Needed to normalize for resizeable
6960 * Columns, which have an additional resizer liner DIV element between the TH
6961 * element and the liner DIV element.
6963 * @method getThLinerEl
6964 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
6965 * DOM element reference, or string ID.
6966 * @return {HTMLElement} Reference to TH liner element.
6968 getThLinerEl : function(theadCell) {
6969 var oColumn = this.getColumn(theadCell);
6970 return (oColumn) ? oColumn.getThLinerEl() : null;
6974 * Returns DOM reference to a TH element.
6977 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
6978 * DOM element reference, or string ID.
6979 * @return {HTMLElement} Reference to TH element.
6981 getThEl : function(theadCell) {
6984 // Validate Column instance
6985 if(theadCell instanceof YAHOO.widget.Column) {
6986 var oColumn = theadCell;
6987 elTh = oColumn.getThEl();
6992 // Validate HTML element
6994 var el = Dom.get(theadCell);
6996 if(el && (el.ownerDocument == document)) {
6997 // Validate TH element
6998 if(el.nodeName.toLowerCase() != "th") {
6999 // Traverse up the DOM to find the corresponding TR element
7000 elTh = Dom.getAncestorByTagName(el,"th");
7014 * Returns the page row index of given row. Returns null if the row is not on the
7015 * current DataTable page.
7017 * @method getTrIndex
7018 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
7019 * string reference to an element within the DataTable page, a Record instance,
7020 * or a Record's RecordSet index.
7021 * @return {Number} Page row index, or null if row does not exist or is not on current page.
7023 getTrIndex : function(row) {
7027 if(row instanceof YAHOO.widget.Record) {
7028 nRecordIndex = this._oRecordSet.getRecordIndex(row);
7029 if(nRecordIndex === null) {
7030 // Not a valid Record
7034 // Calculate page row index from Record index
7035 else if(lang.isNumber(row)) {
7038 if(lang.isNumber(nRecordIndex)) {
7039 // Validate the number
7040 if((nRecordIndex > -1) && (nRecordIndex < this._oRecordSet.getLength())) {
7041 // DataTable is paginated
7042 var oPaginator = this.get('paginator');
7044 // Check the record index is within the indices of the
7046 var rng = oPaginator.getPageRecords();
7047 if (rng && nRecordIndex >= rng[0] && nRecordIndex <= rng[1]) {
7048 // This Record is on current page
7049 return nRecordIndex - rng[0];
7051 // This Record is not on current page
7056 // Not paginated, just return the Record index
7058 return nRecordIndex;
7061 // RecordSet index is out of range
7066 // By element reference or ID string
7068 // Validate TR element
7069 var elRow = this.getTrEl(row);
7070 if(elRow && (elRow.ownerDocument == document) &&
7071 (elRow.parentNode == this._elTbody)) {
7072 return elRow.sectionRowIndex;
7127 * Resets a RecordSet with the given data and populates the page view
7128 * with the new data. Any previous data, and selection and sort states are
7129 * cleared. New data should be added as a separate step.
7131 * @method initializeTable
7133 initializeTable : function() {
7137 // Clear the RecordSet
7138 this._oRecordSet.reset();
7140 // Clear the Paginator's totalRecords if paginating
7141 var pag = this.get('paginator');
7143 pag.set('totalRecords',0);
7147 this._unselectAllTrEls();
7148 this._unselectAllTdEls();
7149 this._aSelections = null;
7150 this._oAnchorRecord = null;
7151 this._oAnchorCell = null;
7154 this.set("sortedBy", null);
7158 * Internal wrapper calls run() on render Chain instance.
7160 * @method _runRenderChain
7163 _runRenderChain : function() {
7164 this._oChainRender.run();
7168 * Renders the view with existing Records from the RecordSet while
7169 * maintaining sort, pagination, and selection states. For performance, reuses
7170 * existing DOM elements when possible while deleting extraneous elements.
7174 render : function() {
7175 //YAHOO.example.Performance.trialStart = new Date();
7177 this._oChainRender.stop();
7179 this.fireEvent("beforeRenderEvent");
7181 var i, j, k, len, allRecords;
7183 var oPaginator = this.get('paginator');
7184 // Paginator is enabled, show a subset of Records and update Paginator UI
7186 allRecords = this._oRecordSet.getRecords(
7187 oPaginator.getStartIndex(),
7188 oPaginator.getRowsPerPage());
7190 // Not paginated, show all records
7192 allRecords = this._oRecordSet.getRecords();
7195 // From the top, update in-place existing rows, so as to reuse DOM elements
7196 var elTbody = this._elTbody,
7197 loopN = this.get("renderLoopSize"),
7198 nRecordsLength = allRecords.length;
7201 if(nRecordsLength > 0) {
7202 elTbody.style.display = "none";
7203 while(elTbody.lastChild) {
7204 elTbody.removeChild(elTbody.lastChild);
7206 elTbody.style.display = "";
7208 // Set up the loop Chain to render rows
7209 this._oChainRender.add({
7210 method: function(oArg) {
7211 if((this instanceof DT) && this._sId) {
7212 var i = oArg.nCurrentRecord,
7213 endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
7214 nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
7217 elTbody.style.display = "none";
7219 for(; i<endRecordIndex; i++) {
7220 elRow = Dom.get(allRecords[i].getId());
7221 elRow = elRow || this._addTrEl(allRecords[i]);
7222 nextSibling = elTbody.childNodes[i] || null;
7223 elTbody.insertBefore(elRow, nextSibling);
7225 elTbody.style.display = "";
7227 // Set up for the next loop
7228 oArg.nCurrentRecord = i;
7232 iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
7234 nCurrentRecord: 0,//nRecordsLength-1, // Start at first Record
7235 nLoopLength: (loopN > 0) ? loopN : nRecordsLength
7237 timeout: (loopN > 0) ? 0 : -1
7240 // Post-render tasks
7241 this._oChainRender.add({
7242 method: function(oArg) {
7243 if((this instanceof DT) && this._sId) {
7244 while(elTbody.rows.length > nRecordsLength) {
7245 elTbody.removeChild(elTbody.lastChild);
7247 this._setFirstRow();
7249 this._setRowStripes();
7250 this._setSelections();
7254 timeout: (loopN > 0) ? 0 : -1
7258 // Table has no rows
7260 // Set up the loop Chain to delete rows
7261 var nTotal = elTbody.rows.length;
7263 this._oChainRender.add({
7264 method: function(oArg) {
7265 if((this instanceof DT) && this._sId) {
7266 var i = oArg.nCurrent,
7267 loopN = oArg.nLoopLength,
7268 nIterEnd = (i - loopN < 0) ? -1 : i - loopN;
7270 elTbody.style.display = "none";
7272 for(; i>nIterEnd; i--) {
7273 elTbody.deleteRow(-1);
7275 elTbody.style.display = "";
7277 // Set up for the next loop
7282 iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
7285 nLoopLength: (loopN > 0) ? loopN : nTotal
7287 timeout: (loopN > 0) ? 0 : -1
7291 this._runRenderChain();
7295 * Disables DataTable UI.
7299 disable : function() {
7300 var elTable = this._elTable;
7301 var elMask = this._elMask;
7302 elMask.style.width = elTable.offsetWidth + "px";
7303 elMask.style.height = elTable.offsetHeight + "px";
7304 elMask.style.display = "";
7305 this.fireEvent("disableEvent");
7309 * Undisables DataTable UI.
7313 undisable : function() {
7314 this._elMask.style.display = "none";
7315 this.fireEvent("undisableEvent");
7319 * Nulls out the entire DataTable instance and related objects, removes attached
7320 * event listeners, and clears out DOM elements inside the container. After
7321 * calling this method, the instance reference should be expliclitly nulled by
7322 * implementer, as in myDataTable = null. Use with caution!
7326 destroy : function() {
7328 var instanceName = this.toString();
7330 this._oChainRender.stop();
7332 // Destroy static resizer proxy and column proxy
7333 DT._destroyColumnDragTargetEl();
7334 DT._destroyColumnResizerProxyEl();
7336 // Destroy ColumnDD and ColumnResizers
7337 this._destroyColumnHelpers();
7339 // Destroy all CellEditors
7341 for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
7342 oCellEditor = this._oColumnSet.flat[i].editor;
7343 if(oCellEditor && oCellEditor.destroy) {
7344 oCellEditor.destroy();
7345 this._oColumnSet.flat[i].editor = null;
7349 // Destroy Paginator
7350 this._destroyPaginator();
7352 // Unhook custom events
7353 this._oRecordSet.unsubscribeAll();
7354 this.unsubscribeAll();
7356 // Unhook DOM events
7357 Ev.removeListener(document, "click", this._onDocumentClick);
7359 // Clear out the container
7360 this._destroyContainerEl(this._elContainer);
7363 for(var param in this) {
7364 if(lang.hasOwnProperty(this, param)) {
7369 // Clean up static values
7370 DT._nCurrentCount--;
7372 if(DT._nCurrentCount < 1) {
7373 if(DT._elDynStyleNode) {
7374 document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
7375 DT._elDynStyleNode = null;
7382 * Displays message within secondary TBODY.
7384 * @method showTableMessage
7385 * @param sHTML {String} (optional) Value for innerHTMlang.
7386 * @param sClassName {String} (optional) Classname.
7388 showTableMessage : function(sHTML, sClassName) {
7389 var elCell = this._elMsgTd;
7390 if(lang.isString(sHTML)) {
7391 elCell.firstChild.innerHTML = sHTML;
7393 if(lang.isString(sClassName)) {
7394 elCell.className = sClassName;
7397 this._elMsgTbody.style.display = "";
7399 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
7403 * Hides secondary TBODY.
7405 * @method hideTableMessage
7407 hideTableMessage : function() {
7408 if(this._elMsgTbody.style.display != "none") {
7409 this._elMsgTbody.style.display = "none";
7410 this._elMsgTbody.parentNode.style.width = "";
7411 this.fireEvent("tableMsgHideEvent");
7416 * Brings focus to the TBODY element. Alias to focusTbodyEl.
7420 focus : function() {
7421 this.focusTbodyEl();
7425 * Brings focus to the THEAD element.
7427 * @method focusTheadEl
7429 focusTheadEl : function() {
7430 this._focusEl(this._elThead);
7434 * Brings focus to the TBODY element.
7436 * @method focusTbodyEl
7438 focusTbodyEl : function() {
7439 this._focusEl(this._elTbody);
7443 * Setting display:none on DataTable or any parent may impact width validations.
7444 * After setting display back to "", implementers should call this method to
7445 * manually perform those validations.
7449 onShow : function() {
7450 this.validateColumnWidths();
7452 for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
7454 if(col._ddResizer) {
7455 col._ddResizer.resetResizerEl();
7526 // RECORDSET FUNCTIONS
7529 * Returns Record index for given TR element or page row index.
7531 * @method getRecordIndex
7532 * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
7533 * element reference or page row index.
7534 * @return {Number} Record's RecordSet index, or null.
7536 getRecordIndex : function(row) {
7539 if(!lang.isNumber(row)) {
7541 if(row instanceof YAHOO.widget.Record) {
7542 return this._oRecordSet.getRecordIndex(row);
7544 // By element reference
7546 // Find the TR element
7547 var el = this.getTrEl(row);
7549 nTrIndex = el.sectionRowIndex;
7553 // By page row index
7558 if(lang.isNumber(nTrIndex)) {
7559 var oPaginator = this.get("paginator");
7561 return oPaginator.get('recordOffset') + nTrIndex;
7572 * For the given identifier, returns the associated Record instance.
7575 * @param row {HTMLElement | Number | String} DOM reference to a TR element (or
7576 * child of a TR element), RecordSet position index, or Record ID.
7577 * @return {YAHOO.widget.Record} Record instance.
7579 getRecord : function(row) {
7580 var oRecord = this._oRecordSet.getRecord(row);
7583 // Validate TR element
7584 var elRow = this.getTrEl(row);
7586 oRecord = this._oRecordSet.getRecord(elRow.id);
7590 if(oRecord instanceof YAHOO.widget.Record) {
7591 return this._oRecordSet.getRecord(oRecord);
7646 * For the given identifier, returns the associated Column instance. Note: For
7647 * getting Columns by Column ID string, please use the method getColumnById().
7650 * @param column {HTMLElement | String | Number} TH/TD element (or child of a
7651 * TH/TD element), a Column key, or a ColumnSet key index.
7652 * @return {YAHOO.widget.Column} Column instance.
7654 getColumn : function(column) {
7655 var oColumn = this._oColumnSet.getColumn(column);
7658 // Validate TD element
7659 var elCell = this.getTdEl(column);
7661 oColumn = this._oColumnSet.getColumn(elCell.cellIndex);
7663 // Validate TH element
7665 elCell = this.getThEl(column);
7668 var allColumns = this._oColumnSet.flat;
7669 for(var i=0, len=allColumns.length; i<len; i++) {
7670 if(allColumns[i].getThEl().id === elCell.id) {
7671 oColumn = allColumns[i];
7683 * For the given Column ID, returns the associated Column instance. Note: For
7684 * getting Columns by key, please use the method getColumn().
7686 * @method getColumnById
7687 * @param column {String} Column ID string.
7688 * @return {YAHOO.widget.Column} Column instance.
7690 getColumnById : function(column) {
7691 return this._oColumnSet.getColumnById(column);
7695 * For the given Column instance, returns next direction to sort.
7697 * @method getColumnSortDir
7698 * @param oColumn {YAHOO.widget.Column} Column instance.
7699 * @param oSortedBy {Object} (optional) Specify the state, or use current state.
7700 * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
7702 getColumnSortDir : function(oColumn, oSortedBy) {
7703 // Backward compatibility
7704 if(oColumn.sortOptions && oColumn.sortOptions.defaultOrder) {
7705 if(oColumn.sortOptions.defaultOrder == "asc") {
7706 oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
7708 else if (oColumn.sortOptions.defaultOrder == "desc") {
7709 oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
7713 // What is the Column's default sort direction?
7714 var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
7716 // Is the Column currently sorted?
7717 var bSorted = false;
7718 oSortedBy = oSortedBy || this.get("sortedBy");
7719 if(oSortedBy && (oSortedBy.key === oColumn.key)) {
7722 sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7725 sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7732 * Overridable method gives implementers a hook to show loading message before
7735 * @method doBeforeSortColumn
7736 * @param oColumn {YAHOO.widget.Column} Column instance.
7737 * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
7738 * YAHOO.widget.DataTable.CLASS_DESC.
7739 * @return {Boolean} Return true to continue sorting Column.
7741 doBeforeSortColumn : function(oColumn, sSortDir) {
7742 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
7747 * Sorts given Column. If "dynamicData" is true, current selections are purged before
7748 * a request is sent to the DataSource for data for the new state (using the
7749 * request returned by "generateRequest()").
7751 * @method sortColumn
7752 * @param oColumn {YAHOO.widget.Column} Column instance.
7753 * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
7754 * YAHOO.widget.DataTable.CLASS_DESC
7756 sortColumn : function(oColumn, sDir) {
7757 if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
7758 if(!oColumn.sortable) {
7759 Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
7762 // Validate given direction
7763 if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
7768 var sSortDir = sDir || this.getColumnSortDir(oColumn);
7770 // Is the Column currently sorted?
7771 var oSortedBy = this.get("sortedBy") || {};
7772 var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
7774 var ok = this.doBeforeSortColumn(oColumn, sSortDir);
7777 if(this.get("dynamicData")) {
7778 // Get current state
7779 var oState = this.getState();
7781 // Reset record offset, if paginated
7782 if(oState.pagination) {
7783 oState.pagination.recordOffset = 0;
7786 // Update sortedBy to new values
7792 // Get the request for the new state
7793 var request = this.get("generateRequest")(oState, this);
7796 this.unselectAllRows();
7797 this.unselectAllCells();
7799 // Send request for new data
7801 success : this.onDataReturnSetRows,
7802 failure : this.onDataReturnSetRows,
7803 argument : oState, // Pass along the new state to the callback
7806 this._oDataSource.sendRequest(request, callback);
7810 // Is there a custom sort handler function defined?
7811 var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
7812 // Custom sort function
7813 oColumn.sortOptions.sortFunction : null;
7816 if(!bSorted || sDir || sortFnc) {
7817 // Shortcut for the frequently-used compare method
7818 var compare = YAHOO.util.Sort.compare;
7820 // Default sort function if necessary
7821 sortFnc = sortFnc ||
7822 function(a, b, desc, field) {
7823 var sorted = compare(a.getData(field),b.getData(field), desc);
7825 return compare(a.getCount(),b.getCount(), desc); // Bug 1932978
7832 // Get the field to sort
7833 var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
7836 this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false), sField);
7838 // Just reverse the Records
7840 this._oRecordSet.reverseRecords();
7843 // Reset to first page if paginated
7844 var oPaginator = this.get('paginator');
7846 // Set page silently, so as not to fire change event.
7847 oPaginator.setPage(1,true);
7850 // Update UI via sortedBy
7852 this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn});
7855 this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
7862 * Sets given Column to given pixel width. If new width is less than minimum
7863 * width, sets to minimum width. Updates oColumn.width value.
7865 * @method setColumnWidth
7866 * @param oColumn {YAHOO.widget.Column} Column instance.
7867 * @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
7868 * subject to minWidth and maxAutoWidth validations.
7870 setColumnWidth : function(oColumn, nWidth) {
7871 if(!(oColumn instanceof YAHOO.widget.Column)) {
7872 oColumn = this.getColumn(oColumn);
7875 // Validate new width against minimum width
7876 if(lang.isNumber(nWidth)) {
7877 // This is why we must require a Number... :-|
7878 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
7881 oColumn.width = nWidth;
7883 // Resize the DOM elements
7884 this._setColumnWidth(oColumn, nWidth+"px");
7886 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
7888 // Unsets a width to auto-size
7889 else if(nWidth === null) {
7891 oColumn.width = nWidth;
7893 // Resize the DOM elements
7894 this._setColumnWidth(oColumn, "auto");
7895 this.validateColumnWidths(oColumn);
7896 this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
7899 // Bug 2339454: resize then sort misaligment
7900 this._clearTrTemplateEl();
7907 * Sets liner DIV elements of given Column to given width. When value should be
7908 * auto-calculated to fit content overflow is set to visible, otherwise overflow
7909 * is set to hidden. No validations against minimum width and no updating
7910 * Column.width value.
7912 * @method _setColumnWidth
7913 * @param oColumn {YAHOO.widget.Column} Column instance.
7914 * @param sWidth {String} New width value.
7915 * @param sOverflow {String} Should be "hidden" when Column width is explicitly
7916 * being set to a value, but should be "visible" when Column is meant to auto-fit content.
7919 _setColumnWidth : function(oColumn, sWidth, sOverflow) {
7920 if(oColumn && (oColumn.getKeyIndex() !== null)) {
7921 sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
7923 // Dynamic style algorithm
7924 if(!DT._bDynStylesFallback) {
7925 this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
7927 // Dynamic function algorithm
7929 this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
7937 * Updates width of a Column's liner DIV elements by dynamically creating a
7938 * STYLE node and writing and updating CSS style rules to it. If this fails during
7939 * runtime, the fallback method _setColumnWidthDynFunction() will be called.
7940 * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
7941 * nested within another TABLE element. For these cases, it is recommended to
7942 * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
7944 * @method _setColumnWidthDynStyles
7945 * @param oColumn {YAHOO.widget.Column} Column instance.
7946 * @param sWidth {String} New width value.
7949 _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
7950 var s = DT._elDynStyleNode,
7953 // Create a new STYLE node
7955 s = document.createElement('style');
7956 s.type = 'text/css';
7957 s = document.getElementsByTagName('head').item(0).appendChild(s);
7958 DT._elDynStyleNode = s;
7961 // We have a STYLE node to update
7963 // Use unique classname for this Column instance as a hook for resizing
7964 var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
7966 // Hide for performance
7968 this._elTbody.style.display = 'none';
7971 rule = DT._oDynStyles[sClassname];
7973 // The Column does not yet have a rule
7975 if(s.styleSheet && s.styleSheet.addRule) {
7976 s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
7977 s.styleSheet.addRule(sClassname,'width:'+sWidth);
7978 rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
7979 DT._oDynStyles[sClassname] = rule;
7981 else if(s.sheet && s.sheet.insertRule) {
7982 s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
7983 rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
7984 DT._oDynStyles[sClassname] = rule;
7987 // We have a rule to update
7989 rule.style.overflow = sOverflow;
7990 rule.style.width = sWidth;
7995 this._elTbody.style.display = '';
7999 // That was not a success, we must call the fallback routine
8001 DT._bDynStylesFallback = true;
8002 this._setColumnWidthDynFunction(oColumn, sWidth);
8007 * Updates width of a Column's liner DIV elements by dynamically creating a
8008 * function to update all element style properties in one pass. Note: This
8009 * technique is not supported in sandboxed environments that prohibit EVALs.
8011 * @method _setColumnWidthDynFunction
8012 * @param oColumn {YAHOO.widget.Column} Column instance.
8013 * @param sWidth {String} New width value.
8016 _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
8017 // TODO: why is this here?
8018 if(sWidth == 'auto') {
8022 // Create one function for each value of rows.length
8023 var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
8025 // Dynamically create the function
8026 if (!this._aDynFunctions[rowslen]) {
8028 //Compile a custom function to do all the liner div width
8029 //assignments at the same time. A unique function is required
8030 //for each unique number of rows in _elTbody. This will
8031 //result in a function declaration like:
8032 //function (oColumn,sWidth,sOverflow) {
8033 // var colIdx = oColumn.getKeyIndex();
8034 // oColumn.getThLinerEl().style.overflow =
8035 // this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
8036 // this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
8037 // ... (for all row indices in this._elTbody.rows.length - 1)
8038 // this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
8040 // oColumn.getThLinerEl().style.width =
8041 // this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
8042 // this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
8043 // ... (for all row indices in this._elTbody.rows.length - 1)
8044 // this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
8050 'var colIdx=oColumn.getKeyIndex();',
8051 'oColumn.getThLinerEl().style.overflow='
8053 for (i=rowslen-1, j=2; i >= 0; --i) {
8054 resizerDef[j++] = 'this._elTbody.rows[';
8055 resizerDef[j++] = i;
8056 resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
8058 resizerDef[j] = 'sOverflow;';
8059 resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
8060 for (i=rowslen-1, k=j+2; i >= 0; --i) {
8061 resizerDef[k++] = 'this._elTbody.rows[';
8062 resizerDef[k++] = i;
8063 resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
8065 resizerDef[k] = 'sWidth;';
8066 this._aDynFunctions[rowslen] =
8067 new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
8070 // Get the function to execute
8071 var resizerFn = this._aDynFunctions[rowslen];
8073 // TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
8075 resizerFn.call(this,oColumn,sWidth,sOverflow);
8080 * For one or all Columns, when Column is not hidden, width is not set, and minWidth
8081 * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
8083 * @method validateColumnWidths
8084 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
8086 validateColumnWidths : function(oColumn) {
8087 var elColgroup = this._elColgroup;
8088 var elColgroupClone = elColgroup.cloneNode(true);
8089 var bNeedsValidation = false;
8090 var allKeys = this._oColumnSet.keys;
8092 // Validate just one Column's minWidth and/or maxAutoWidth
8093 if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
8094 elThLiner = oColumn.getThLinerEl();
8095 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8096 elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width =
8098 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8099 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8100 bNeedsValidation = true;
8102 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8103 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8106 // Validate all Columns
8108 for(var i=0, len=allKeys.length; i<len; i++) {
8109 oColumn = allKeys[i];
8110 if(!oColumn.hidden && !oColumn.width) {
8111 elThLiner = oColumn.getThLinerEl();
8112 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8113 elColgroupClone.childNodes[i].style.width =
8115 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8116 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8117 bNeedsValidation = true;
8119 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8120 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8125 if(bNeedsValidation) {
8126 elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
8127 this._elColgroup = elColgroupClone;
8134 * @method _clearMinWidth
8135 * @param oColumn {YAHOO.widget.Column} Which Column.
8138 _clearMinWidth : function(oColumn) {
8139 if(oColumn.getKeyIndex() !== null) {
8140 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
8145 * Restores minWidth.
8147 * @method _restoreMinWidth
8148 * @param oColumn {YAHOO.widget.Column} Which Column.
8151 _restoreMinWidth : function(oColumn) {
8152 if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
8153 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
8158 * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
8159 * hide/show non-nested Columns, and top-level parent Columns (which will
8160 * hide/show all children Columns).
8162 * @method hideColumn
8163 * @param oColumn {YAHOO.widget.Column} Column instance.
8165 hideColumn : function(oColumn) {
8166 if(!(oColumn instanceof YAHOO.widget.Column)) {
8167 oColumn = this.getColumn(oColumn);
8169 // Only top-level Columns can get hidden due to issues in FF2 and SF3
8170 if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
8172 var allrows = this.getTbodyEl().rows;
8173 var l = allrows.length;
8174 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8176 // Hide each nested Column
8177 for(var i=0; i<allDescendants.length; i++) {
8178 var thisColumn = allDescendants[i];
8179 thisColumn.hidden = true;
8181 // Style the head cell
8182 Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8184 // Does this Column have body cells?
8185 var thisKeyIndex = thisColumn.getKeyIndex();
8186 if(thisKeyIndex !== null) {
8188 this._clearMinWidth(oColumn);
8190 // Style the body cells
8191 for(var j=0;j<l;j++) {
8192 Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8196 this.fireEvent("columnHideEvent",{column:thisColumn});
8199 this._repaintOpera();
8200 this._clearTrTemplateEl();
8207 * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
8208 * hide/show non-nested Columns, and top-level parent Columns (which will
8209 * hide/show all children Columns).
8211 * @method showColumn
8212 * @param oColumn {YAHOO.widget.Column} Column instance.
8214 showColumn : function(oColumn) {
8215 if(!(oColumn instanceof YAHOO.widget.Column)) {
8216 oColumn = this.getColumn(oColumn);
8218 // Only top-level Columns can get hidden
8219 if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
8220 var allrows = this.getTbodyEl().rows;
8221 var l = allrows.length;
8222 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8224 // Show each nested Column
8225 for(var i=0; i<allDescendants.length; i++) {
8226 var thisColumn = allDescendants[i];
8227 thisColumn.hidden = false;
8229 // Unstyle the head cell
8230 Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8232 // Does this Column have body cells?
8233 var thisKeyIndex = thisColumn.getKeyIndex();
8234 if(thisKeyIndex !== null) {
8236 this._restoreMinWidth(oColumn);
8239 // Unstyle the body cells
8240 for(var j=0;j<l;j++) {
8241 Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8245 this.fireEvent("columnShowEvent",{column:thisColumn});
8247 this._clearTrTemplateEl();
8254 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
8255 * non-nested Columns, and top-level parent Columns (which will remove all
8256 * children Columns).
8258 * @method removeColumn
8259 * @param oColumn {YAHOO.widget.Column} Column instance.
8260 * @return oColumn {YAHOO.widget.Column} Removed Column instance.
8262 removeColumn : function(oColumn) {
8264 if(!(oColumn instanceof YAHOO.widget.Column)) {
8265 oColumn = this.getColumn(oColumn);
8268 var nColTreeIndex = oColumn.getTreeIndex();
8269 if(nColTreeIndex !== null) {
8270 // Which key index(es)
8272 aKeyIndexes = oColumn.getKeyIndex();
8273 // Must be a parent Column
8274 if(aKeyIndexes === null) {
8275 var descKeyIndexes = [];
8276 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8277 for(i=0, len=allDescendants.length; i<len; i++) {
8278 // Is this descendant a key Column?
8279 var thisKey = allDescendants[i].getKeyIndex();
8280 if(thisKey !== null) {
8281 descKeyIndexes[descKeyIndexes.length] = thisKey;
8284 if(descKeyIndexes.length > 0) {
8285 aKeyIndexes = descKeyIndexes;
8288 // Must be a key Column
8290 aKeyIndexes = [aKeyIndexes];
8293 if(aKeyIndexes !== null) {
8294 // Sort the indexes so we can remove from the right
8295 aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8297 // Destroy previous THEAD
8298 this._destroyTheadEl();
8301 var aOrigColumnDefs = this._oColumnSet.getDefinitions();
8302 oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
8303 this._initColumnSet(aOrigColumnDefs);
8304 this._initTheadEl();
8307 for(i=aKeyIndexes.length-1; i>-1; i--) {
8308 this._removeColgroupColEl(aKeyIndexes[i]);
8312 var allRows = this._elTbody.rows;
8313 if(allRows.length > 0) {
8314 var loopN = this.get("renderLoopSize"),
8315 loopEnd = allRows.length;
8316 this._oChainRender.add({
8317 method: function(oArg) {
8318 if((this instanceof DT) && this._sId) {
8319 var i = oArg.nCurrentRow,
8320 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8321 aIndexes = oArg.aIndexes,
8323 for(; i < len; ++i) {
8324 for(j = aIndexes.length-1; j>-1; j--) {
8325 allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
8328 oArg.nCurrentRow = i;
8331 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8332 argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
8334 timeout: (loopN > 0) ? 0 : -1
8336 this._runRenderChain();
8339 this.fireEvent("columnRemoveEvent",{column:oColumn});
8347 * Inserts given Column at the index if given, otherwise at the end. NOTE: You
8348 * can only add non-nested Columns and top-level parent Columns. You cannot add
8349 * a nested Column to an existing parent.
8351 * @method insertColumn
8352 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
8353 * definition or a Column instance.
8354 * @param index {Number} (optional) New tree index.
8355 * @return oColumn {YAHOO.widget.Column} Inserted Column instance.
8357 insertColumn : function(oColumn, index) {
8359 if(oColumn instanceof YAHOO.widget.Column) {
8360 oColumn = oColumn.getDefinition();
8362 else if(oColumn.constructor !== Object) {
8366 // Validate index or append new Column to the end of the ColumnSet
8367 var oColumnSet = this._oColumnSet;
8368 if(!lang.isValue(index) || !lang.isNumber(index)) {
8369 index = oColumnSet.tree[0].length;
8372 // Destroy previous THEAD
8373 this._destroyTheadEl();
8376 var aNewColumnDefs = this._oColumnSet.getDefinitions();
8377 aNewColumnDefs.splice(index, 0, oColumn);
8378 this._initColumnSet(aNewColumnDefs);
8379 this._initTheadEl();
8381 // Need to refresh the reference
8382 oColumnSet = this._oColumnSet;
8383 var oNewColumn = oColumnSet.tree[0][index];
8385 // Get key index(es) for new Column
8387 descKeyIndexes = [];
8388 var allDescendants = oColumnSet.getDescendants(oNewColumn);
8389 for(i=0, len=allDescendants.length; i<len; i++) {
8390 // Is this descendant a key Column?
8391 var thisKey = allDescendants[i].getKeyIndex();
8392 if(thisKey !== null) {
8393 descKeyIndexes[descKeyIndexes.length] = thisKey;
8397 if(descKeyIndexes.length > 0) {
8399 var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8402 for(i=descKeyIndexes.length-1; i>-1; i--) {
8403 this._insertColgroupColEl(descKeyIndexes[i]);
8407 var allRows = this._elTbody.rows;
8408 if(allRows.length > 0) {
8409 var loopN = this.get("renderLoopSize"),
8410 loopEnd = allRows.length;
8412 // Get templates for each new TD
8413 var aTdTemplates = [],
8415 for(i=0, len=descKeyIndexes.length; i<len; i++) {
8416 var thisKeyIndex = descKeyIndexes[i];
8417 elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
8418 elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
8419 aTdTemplates[thisKeyIndex] = elTdTemplate;
8422 this._oChainRender.add({
8423 method: function(oArg) {
8424 if((this instanceof DT) && this._sId) {
8425 var i = oArg.nCurrentRow, j,
8426 descKeyIndexes = oArg.descKeyIndexes,
8427 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8429 for(; i < len; ++i) {
8430 nextSibling = allRows[i].childNodes[newIndex] || null;
8431 for(j=descKeyIndexes.length-1; j>-1; j--) {
8432 allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
8435 oArg.nCurrentRow = i;
8438 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8439 argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
8441 timeout: (loopN > 0) ? 0 : -1
8443 this._runRenderChain();
8446 this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
8452 * Removes given Column and inserts into given tree index. NOTE: You
8453 * can only reorder non-nested Columns and top-level parent Columns. You cannot
8454 * reorder a nested Column to an existing parent.
8456 * @method reorderColumn
8457 * @param oColumn {YAHOO.widget.Column} Column instance.
8458 * @param index {Number} New tree index.
8459 * @return oColumn {YAHOO.widget.Column} Reordered Column instance.
8461 reorderColumn : function(oColumn, index) {
8462 // Validate Column and new index
8463 if(!(oColumn instanceof YAHOO.widget.Column)) {
8464 oColumn = this.getColumn(oColumn);
8466 if(oColumn && YAHOO.lang.isNumber(index)) {
8467 var nOrigTreeIndex = oColumn.getTreeIndex();
8468 if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
8469 // Which key index(es)
8471 aOrigKeyIndexes = oColumn.getKeyIndex(),
8473 descKeyIndexes = [],
8475 // Must be a parent Column...
8476 if(aOrigKeyIndexes === null) {
8477 allDescendants = this._oColumnSet.getDescendants(oColumn);
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 aOrigKeyIndexes = descKeyIndexes;
8489 // ...or else must be a key Column
8491 aOrigKeyIndexes = [aOrigKeyIndexes];
8494 if(aOrigKeyIndexes !== null) {
8496 aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8498 // Destroy previous THEAD
8499 this._destroyTheadEl();
8502 var aColumnDefs = this._oColumnSet.getDefinitions();
8503 var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
8504 aColumnDefs.splice(index, 0, oColumnDef);
8505 this._initColumnSet(aColumnDefs);
8506 this._initTheadEl();
8508 // Need to refresh the reference
8509 var oNewColumn = this._oColumnSet.tree[0][index];
8511 // What are new key index(es)
8512 var aNewKeyIndexes = oNewColumn.getKeyIndex();
8513 // Must be a parent Column
8514 if(aNewKeyIndexes === null) {
8515 descKeyIndexes = [];
8516 allDescendants = this._oColumnSet.getDescendants(oNewColumn);
8517 for(i=0, len=allDescendants.length; i<len; i++) {
8518 // Is this descendant a key Column?
8519 thisKey = allDescendants[i].getKeyIndex();
8520 if(thisKey !== null) {
8521 descKeyIndexes[descKeyIndexes.length] = thisKey;
8524 if(descKeyIndexes.length > 0) {
8525 aNewKeyIndexes = descKeyIndexes;
8528 // Must be a key Column
8530 aNewKeyIndexes = [aNewKeyIndexes];
8533 // Sort the new indexes and grab the first one for the new location
8534 var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8537 this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
8540 var allRows = this._elTbody.rows;
8541 if(allRows.length > 0) {
8542 var loopN = this.get("renderLoopSize"),
8543 loopEnd = allRows.length;
8544 this._oChainRender.add({
8545 method: function(oArg) {
8546 if((this instanceof DT) && this._sId) {
8547 var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
8548 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8549 aIndexes = oArg.aIndexes, thisTr;
8551 for(; i < len; ++i) {
8553 thisTr = allRows[i];
8556 for(j=aIndexes.length-1; j>-1; j--) {
8557 tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
8561 nextSibling = thisTr.childNodes[newIndex] || null;
8562 for(j=tmpTds.length-1; j>-1; j--) {
8563 thisTr.insertBefore(tmpTds[j], nextSibling);
8566 oArg.nCurrentRow = i;
8569 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8570 argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
8572 timeout: (loopN > 0) ? 0 : -1
8574 this._runRenderChain();
8577 this.fireEvent("columnReorderEvent",{column:oNewColumn});
8585 * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8586 * select/unselect non-nested Columns, and bottom-level key Columns.
8588 * @method selectColumn
8589 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8590 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8592 selectColumn : function(oColumn) {
8593 oColumn = this.getColumn(oColumn);
8594 if(oColumn && !oColumn.selected) {
8595 // Only bottom-level Columns can get hidden
8596 if(oColumn.getKeyIndex() !== null) {
8597 oColumn.selected = true;
8600 var elTh = oColumn.getThEl();
8601 Dom.addClass(elTh,DT.CLASS_SELECTED);
8603 // Update body cells
8604 var allRows = this.getTbodyEl().rows;
8605 var oChainRender = this._oChainRender;
8607 method: function(oArg) {
8608 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8609 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);
8614 iterations: allRows.length,
8615 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8618 this._clearTrTemplateEl();
8620 this._elTbody.style.display = "none";
8621 this._runRenderChain();
8622 this._elTbody.style.display = "";
8624 this.fireEvent("columnSelectEvent",{column:oColumn});
8632 * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8633 * select/unselect non-nested Columns, and bottom-level key Columns.
8635 * @method unselectColumn
8636 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8637 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8639 unselectColumn : function(oColumn) {
8640 oColumn = this.getColumn(oColumn);
8641 if(oColumn && oColumn.selected) {
8642 // Only bottom-level Columns can get hidden
8643 if(oColumn.getKeyIndex() !== null) {
8644 oColumn.selected = false;
8647 var elTh = oColumn.getThEl();
8648 Dom.removeClass(elTh,DT.CLASS_SELECTED);
8650 // Update body cells
8651 var allRows = this.getTbodyEl().rows;
8652 var oChainRender = this._oChainRender;
8654 method: function(oArg) {
8655 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8656 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);
8661 iterations:allRows.length,
8662 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8665 this._clearTrTemplateEl();
8667 this._elTbody.style.display = "none";
8668 this._runRenderChain();
8669 this._elTbody.style.display = "";
8671 this.fireEvent("columnUnselectEvent",{column:oColumn});
8679 * Returns an array selected Column instances.
8681 * @method getSelectedColumns
8682 * @return {YAHOO.widget.Column[]} Array of Column instances.
8684 getSelectedColumns : function(oColumn) {
8685 var selectedColumns = [];
8686 var aKeys = this._oColumnSet.keys;
8687 for(var i=0,len=aKeys.length; i<len; i++) {
8688 if(aKeys[i].selected) {
8689 selectedColumns[selectedColumns.length] = aKeys[i];
8692 return selectedColumns;
8696 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8697 * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8698 * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8700 * @method highlightColumn
8701 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8702 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8704 highlightColumn : function(column) {
8705 var oColumn = this.getColumn(column);
8706 // Only bottom-level Columns can get highlighted
8707 if(oColumn && (oColumn.getKeyIndex() !== null)) {
8709 var elTh = oColumn.getThEl();
8710 Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
8712 // Update body cells
8713 var allRows = this.getTbodyEl().rows;
8714 var oChainRender = this._oChainRender;
8716 method: function(oArg) {
8717 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8718 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
8723 iterations:allRows.length,
8724 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8727 this._elTbody.style.display = "none";
8728 this._runRenderChain();
8729 this._elTbody.style.display = "";
8731 this.fireEvent("columnHighlightEvent",{column:oColumn});
8738 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8739 * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8740 * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8742 * @method unhighlightColumn
8743 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8744 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8746 unhighlightColumn : function(column) {
8747 var oColumn = this.getColumn(column);
8748 // Only bottom-level Columns can get highlighted
8749 if(oColumn && (oColumn.getKeyIndex() !== null)) {
8751 var elTh = oColumn.getThEl();
8752 Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
8754 // Update body cells
8755 var allRows = this.getTbodyEl().rows;
8756 var oChainRender = this._oChainRender;
8758 method: function(oArg) {
8759 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8760 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
8765 iterations:allRows.length,
8766 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8769 this._elTbody.style.display = "none";
8770 this._runRenderChain();
8771 this._elTbody.style.display = "";
8773 this.fireEvent("columnUnhighlightEvent",{column:oColumn});
8825 * Adds one new Record of data into the RecordSet at the index if given,
8826 * otherwise at the end. If the new Record is in page view, the
8827 * corresponding DOM elements are also updated.
8830 * @param oData {Object} Object literal of data for the row.
8831 * @param index {Number} (optional) RecordSet position index at which to add data.
8833 addRow : function(oData, index) {
8834 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
8838 if(oData && lang.isObject(oData)) {
8839 var oRecord = this._oRecordSet.addRecord(oData, index);
8842 var oPaginator = this.get('paginator');
8846 // Update the paginator's totalRecords
8847 var totalRecords = oPaginator.get('totalRecords');
8848 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
8849 oPaginator.set('totalRecords',totalRecords + 1);
8852 recIndex = this.getRecordIndex(oRecord);
8853 var endRecIndex = (oPaginator.getPageRecords())[1];
8855 // New record affects the view
8856 if (recIndex <= endRecIndex) {
8857 // Defer UI updates to the render method
8861 this.fireEvent("rowAddEvent", {record:oRecord});
8866 recIndex = this.getTrIndex(oRecord);
8867 if(lang.isNumber(recIndex)) {
8868 // Add the TR element
8869 this._oChainRender.add({
8870 method: function(oArg) {
8871 if((this instanceof DT) && this._sId) {
8872 var oRecord = oArg.record;
8873 var recIndex = oArg.recIndex;
8874 var elNewTr = this._addTrEl(oRecord);
8876 var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
8877 this._elTbody.insertBefore(elNewTr, elNext);
8880 if(recIndex === 0) {
8881 this._setFirstRow();
8883 if(elNext === null) {
8887 this._setRowStripes();
8889 this.hideTableMessage();
8891 this.fireEvent("rowAddEvent", {record:oRecord});
8895 argument: {record: oRecord, recIndex: recIndex},
8897 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
8899 this._runRenderChain();
8908 * Convenience method to add multiple rows.
8911 * @param aData {Object[]} Array of object literal data for the rows.
8912 * @param index {Number} (optional) RecordSet position index at which to add data.
8914 addRows : function(aData, index) {
8915 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
8919 if(lang.isArray(aData)) {
8920 var aRecords = this._oRecordSet.addRecords(aData, index);
8922 var recIndex = this.getRecordIndex(aRecords[0]);
8925 var oPaginator = this.get('paginator');
8927 // Update the paginator's totalRecords
8928 var totalRecords = oPaginator.get('totalRecords');
8929 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
8930 oPaginator.set('totalRecords',totalRecords + aRecords.length);
8933 var endRecIndex = (oPaginator.getPageRecords())[1];
8935 // At least one of the new records affects the view
8936 if (recIndex <= endRecIndex) {
8940 this.fireEvent("rowsAddEvent", {records:aRecords});
8945 // Add the TR elements
8946 var loopN = this.get("renderLoopSize");
8947 var loopEnd = recIndex + aData.length;
8948 var nRowsNeeded = (loopEnd - recIndex); // how many needed
8949 var isLast = (recIndex >= this._elTbody.rows.length);
8950 this._oChainRender.add({
8951 method: function(oArg) {
8952 if((this instanceof DT) && this._sId) {
8953 var aRecords = oArg.aRecords,
8954 i = oArg.nCurrentRow,
8955 j = oArg.nCurrentRecord,
8956 len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
8957 df = document.createDocumentFragment(),
8958 elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
8959 for(; i < len; i++, j++) {
8960 df.appendChild(this._addTrEl(aRecords[j]));
8962 this._elTbody.insertBefore(df, elNext);
8963 oArg.nCurrentRow = i;
8964 oArg.nCurrentRecord = j;
8967 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8968 argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
8970 timeout: (loopN > 0) ? 0 : -1
8972 this._oChainRender.add({
8973 method: function(oArg) {
8974 var recIndex = oArg.recIndex;
8976 if(recIndex === 0) {
8977 this._setFirstRow();
8983 this._setRowStripes();
8985 this.fireEvent("rowsAddEvent", {records:aRecords});
8987 argument: {recIndex: recIndex, isLast: isLast},
8989 timeout: -1 // Needs to run immediately after the DOM insertions above
8991 this._runRenderChain();
8992 this.hideTableMessage();
9000 * For the given row, updates the associated Record with the given data. If the
9001 * row is on current page, the corresponding DOM elements are also updated.
9004 * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
9005 * Which row to update: By Record instance, by Record's RecordSet
9006 * position index, by HTMLElement reference to the TR element, or by ID string
9007 * of the TR element.
9008 * @param oData {Object} Object literal of data for the row.
9010 updateRow : function(row, oData) {
9012 if (!lang.isNumber(index)) {
9013 index = this.getRecordIndex(row);
9016 // Update the Record
9017 if(lang.isNumber(index) && (index >= 0)) {
9018 var oRecordSet = this._oRecordSet,
9019 oldRecord = oRecordSet.getRecord(index);
9023 var updatedRecord = this._oRecordSet.setRecord(oData, index),
9024 elRow = this.getTrEl(oldRecord),
9025 // Copy data from the Record for the event that gets fired later
9026 oldData = oldRecord ? oldRecord.getData() : null;
9029 // Update selected rows as necessary
9030 var tracker = this._aSelections || [],
9032 oldId = oldRecord.getId(),
9033 newId = updatedRecord.getId();
9034 for(; i<tracker.length; i++) {
9035 if((tracker[i] === oldId)) {
9038 else if(tracker[i].recordId === oldId) {
9039 tracker[i].recordId = newId;
9043 // Update the TR only if row is on current page
9044 this._oChainRender.add({
9045 method: function() {
9046 if((this instanceof DT) && this._sId) {
9048 var oPaginator = this.get('paginator');
9050 var pageStartIndex = (oPaginator.getPageRecords())[0],
9051 pageLastIndex = (oPaginator.getPageRecords())[1];
9053 // At least one of the new records affects the view
9054 if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
9060 this._updateTrEl(elRow, updatedRecord);
9063 this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
9066 this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
9070 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9072 this._runRenderChain();
9081 * Starting with the given row, updates associated Records with the given data.
9082 * The number of rows to update are determined by the array of data provided.
9083 * Undefined data (i.e., not an object literal) causes a row to be skipped. If
9084 * any of the rows are on current page, the corresponding DOM elements are also
9087 * @method updateRows
9088 * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
9089 * Starting row to update: By Record instance, by Record's RecordSet
9090 * position index, by HTMLElement reference to the TR element, or by ID string
9091 * of the TR element.
9092 * @param aData {Object[]} Array of object literal of data for the rows.
9094 updateRows : function(startrow, aData) {
9095 if(lang.isArray(aData)) {
9096 var startIndex = startrow,
9097 oRecordSet = this._oRecordSet;
9099 if (!lang.isNumber(startrow)) {
9100 startIndex = this.getRecordIndex(startrow);
9103 if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
9104 var lastIndex = startIndex + aData.length,
9105 aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
9106 aNewRecords = oRecordSet.setRecords(aData, startIndex);
9108 // Update selected rows as necessary
9109 var tracker = this._aSelections || [],
9110 i=0, j, newId, oldId;
9111 for(; i<tracker.length; i++) {
9112 for(j=0; j<aOldRecords.length; j++) {
9113 oldId = aOldRecords[j].getId();
9114 if((tracker[i] === oldId)) {
9115 tracker[i] = aNewRecords[j].getId();
9117 else if(tracker[i].recordId === oldId) {
9118 tracker[i].recordId = aNewRecords[j].getId();
9124 var oPaginator = this.get('paginator');
9126 var pageStartIndex = (oPaginator.getPageRecords())[0],
9127 pageLastIndex = (oPaginator.getPageRecords())[1];
9129 // At least one of the new records affects the view
9130 if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
9134 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9139 // Update the TR elements
9140 var loopN = this.get("renderLoopSize"),
9141 rowCount = aData.length, // how many needed
9142 lastRowIndex = this._elTbody.rows.length,
9143 isLast = (lastIndex >= lastRowIndex),
9144 isAdding = (lastIndex > lastRowIndex);
9146 this._oChainRender.add({
9147 method: function(oArg) {
9148 if((this instanceof DT) && this._sId) {
9149 var aRecords = oArg.aRecords,
9150 i = oArg.nCurrentRow,
9151 j = oArg.nDataPointer,
9152 len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
9154 for(; i < len; i++,j++) {
9155 if(isAdding && (i>=lastRowIndex)) {
9156 this._elTbody.appendChild(this._addTrEl(aRecords[j]));
9159 this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
9162 oArg.nCurrentRow = i;
9163 oArg.nDataPointer = j;
9166 iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
9167 argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
9169 timeout: (loopN > 0) ? 0 : -1
9171 this._oChainRender.add({
9172 method: function(oArg) {
9173 var recIndex = oArg.recIndex;
9175 if(recIndex === 0) {
9176 this._setFirstRow();
9182 this._setRowStripes();
9184 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9186 argument: {recIndex: startIndex, isLast: isLast},
9188 timeout: -1 // Needs to run immediately after the DOM insertions above
9190 this._runRenderChain();
9191 this.hideTableMessage();
9200 * Deletes the given row's Record from the RecordSet. If the row is on current page,
9201 * the corresponding DOM elements are also deleted.
9204 * @param row {HTMLElement | String | Number} DOM element reference or ID string
9205 * to DataTable page element or RecordSet index.
9207 deleteRow : function(row) {
9208 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9209 if(lang.isNumber(nRecordIndex)) {
9210 var oRecord = this.getRecord(nRecordIndex);
9212 var nTrIndex = this.getTrIndex(nRecordIndex);
9214 // Remove from selection tracker if there
9215 var sRecordId = oRecord.getId();
9216 var tracker = this._aSelections || [];
9217 for(var j=tracker.length-1; j>-1; j--) {
9218 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9219 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9220 tracker.splice(j,1);
9224 // Delete Record from RecordSet
9225 var oData = this._oRecordSet.deleteRecord(nRecordIndex);
9229 // If paginated and the deleted row was on this or a prior page, just
9231 var oPaginator = this.get('paginator');
9233 // Update the paginator's totalRecords
9234 var totalRecords = oPaginator.get('totalRecords'),
9235 // must capture before the totalRecords change because
9236 // Paginator shifts to previous page automatically
9237 rng = oPaginator.getPageRecords();
9239 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9240 oPaginator.set('totalRecords',totalRecords - 1);
9243 // The deleted record was on this or a prior page, re-render
9244 if (!rng || nRecordIndex <= rng[1]) {
9248 this._oChainRender.add({
9249 method: function() {
9250 if((this instanceof DT) && this._sId) {
9251 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
9255 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9257 this._runRenderChain();
9261 if(lang.isNumber(nTrIndex)) {
9262 this._oChainRender.add({
9263 method: function() {
9264 if((this instanceof DT) && this._sId) {
9265 var isLast = (nRecordIndex === this._oRecordSet.getLength());//(nTrIndex == this.getLastTrEl().sectionRowIndex);
9266 this._deleteTrEl(nTrIndex);
9268 // Post-delete tasks
9269 if(this._elTbody.rows.length > 0) {
9271 if(nTrIndex === 0) {
9272 this._setFirstRow();
9278 if(nTrIndex != this._elTbody.rows.length) {
9279 this._setRowStripes(nTrIndex);
9283 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
9287 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9289 this._runRenderChain();
9300 * Convenience method to delete multiple rows.
9302 * @method deleteRows
9303 * @param row {HTMLElement | String | Number} DOM element reference or ID string
9304 * to DataTable page element or RecordSet index.
9305 * @param count {Number} (optional) How many rows to delete. A negative value
9306 * will delete towards the beginning.
9308 deleteRows : function(row, count) {
9309 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9310 if(lang.isNumber(nRecordIndex)) {
9311 var oRecord = this.getRecord(nRecordIndex);
9313 var nTrIndex = this.getTrIndex(nRecordIndex);
9315 // Remove from selection tracker if there
9316 var sRecordId = oRecord.getId();
9317 var tracker = this._aSelections || [];
9318 for(var j=tracker.length-1; j>-1; j--) {
9319 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9320 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9321 tracker.splice(j,1);
9325 // Delete Record from RecordSet
9326 var highIndex = nRecordIndex;
9327 var lowIndex = nRecordIndex;
9329 // Validate count and account for negative value
9330 if(count && lang.isNumber(count)) {
9331 highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
9332 lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
9333 count = (count > 0) ? count : count*-1;
9336 count = highIndex - lowIndex + 1;
9343 var aData = this._oRecordSet.deleteRecords(lowIndex, count);
9347 var oPaginator = this.get('paginator'),
9348 loopN = this.get("renderLoopSize");
9349 // If paginated and the deleted row was on this or a prior page, just
9352 // Update the paginator's totalRecords
9353 var totalRecords = oPaginator.get('totalRecords'),
9354 // must capture before the totalRecords change because
9355 // Paginator shifts to previous page automatically
9356 rng = oPaginator.getPageRecords();
9358 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9359 oPaginator.set('totalRecords',totalRecords - aData.length);
9362 // The records were on this or a prior page, re-render
9363 if (!rng || lowIndex <= rng[1]) {
9367 this._oChainRender.add({
9368 method: function(oArg) {
9369 if((this instanceof DT) && this._sId) {
9370 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9374 timeout: (loopN > 0) ? 0 : -1
9376 this._runRenderChain();
9381 if(lang.isNumber(nTrIndex)) {
9382 // Delete the TR elements starting with highest index
9383 var loopEnd = lowIndex;
9384 var nRowsNeeded = count; // how many needed
9385 this._oChainRender.add({
9386 method: function(oArg) {
9387 if((this instanceof DT) && this._sId) {
9388 var i = oArg.nCurrentRow,
9389 len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
9391 this._deleteTrEl(i);
9393 oArg.nCurrentRow = i;
9396 iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
9397 argument: {nCurrentRow:highIndex},
9399 timeout: (loopN > 0) ? 0 : -1
9401 this._oChainRender.add({
9402 method: function() {
9403 // Post-delete tasks
9404 if(this._elTbody.rows.length > 0) {
9405 this._setFirstRow();
9407 this._setRowStripes();
9410 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9413 timeout: -1 // Needs to run immediately after the DOM deletions above
9415 this._runRenderChain();
9473 * Outputs markup into the given TD based on given Record.
9475 * @method formatCell
9476 * @param elLiner {HTMLElement} The liner DIV element within the TD.
9477 * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
9478 * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
9480 formatCell : function(elLiner, oRecord, oColumn) {
9482 oRecord = this.getRecord(elLiner);
9485 oColumn = this.getColumn(elLiner.parentNode.cellIndex);
9488 if(oRecord && oColumn) {
9489 var sField = oColumn.field;
9490 var oData = oRecord.getData(sField);
9492 var fnFormatter = typeof oColumn.formatter === 'function' ?
9494 DT.Formatter[oColumn.formatter+''] ||
9495 DT.Formatter.defaultFormatter;
9497 // Apply special formatter
9499 fnFormatter.call(this, elLiner, oRecord, oColumn, oData);
9502 elLiner.innerHTML = oData;
9505 this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elLiner});
9512 * For the given row and column, updates the Record with the given data. If the
9513 * cell is on current page, the corresponding DOM elements are also updated.
9515 * @method updateCell
9516 * @param oRecord {YAHOO.widget.Record} Record instance.
9517 * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
9518 * @param oData {Object} New data value for the cell.
9520 updateCell : function(oRecord, oColumn, oData) {
9521 // Validate Column and Record
9522 oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
9523 if(oColumn && oColumn.getField() && (oRecord instanceof YAHOO.widget.Record)) {
9524 var sKey = oColumn.getField(),
9526 // Copy data from the Record for the event that gets fired later
9527 //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
9528 oldData = oRecord.getData(sKey);
9530 // Update Record with new data
9531 this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
9533 // Update the TD only if row is on current page
9534 var elTd = this.getTdEl({record: oRecord, column: oColumn});
9536 this._oChainRender.add({
9537 method: function() {
9538 if((this instanceof DT) && this._sId) {
9539 this.formatCell(elTd.firstChild);
9540 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9544 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9546 this._runRenderChain();
9549 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9606 * Method executed during set() operation for the "paginator" attribute.
9607 * Adds and/or severs event listeners between DataTable and Paginator
9609 * @method _updatePaginator
9610 * @param newPag {Paginator} Paginator instance (or null) for DataTable to use
9613 _updatePaginator : function (newPag) {
9614 var oldPag = this.get('paginator');
9615 if (oldPag && newPag !== oldPag) {
9616 oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9619 newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9624 * Update the UI infrastructure in response to a "paginator" attribute change.
9626 * @method _handlePaginatorChange
9627 * @param e {Object} Change event object containing keys 'type','newValue',
9631 _handlePaginatorChange : function (e) {
9632 if (e.prevValue === e.newValue) { return; }
9634 var newPag = e.newValue,
9635 oldPag = e.prevValue,
9636 containers = this._defaultPaginatorContainers();
9639 if (oldPag.getContainerNodes()[0] == containers[0]) {
9640 oldPag.set('containers',[]);
9644 // Convenience: share the default containers if possible.
9645 // Otherwise, remove the default containers from the DOM.
9646 if (containers[0]) {
9647 if (newPag && !newPag.getContainerNodes().length) {
9648 newPag.set('containers',containers);
9650 // No new Paginator to use existing containers, OR new
9651 // Paginator has configured containers.
9652 for (var i = containers.length - 1; i >= 0; --i) {
9653 if (containers[i]) {
9654 containers[i].parentNode.removeChild(containers[i]);
9667 this.renderPaginator();
9673 * Returns the default containers used for Paginators. If create param is
9674 * passed, the containers will be created and added to the DataTable container.
9676 * @method _defaultPaginatorContainers
9677 * @param create {boolean} Create the default containers if not found
9680 _defaultPaginatorContainers : function (create) {
9681 var above_id = this._sId + '-paginator0',
9682 below_id = this._sId + '-paginator1',
9683 above = Dom.get(above_id),
9684 below = Dom.get(below_id);
9686 if (create && (!above || !below)) {
9687 // One above and one below the table
9689 above = document.createElement('div');
9690 above.id = above_id;
9691 Dom.addClass(above, DT.CLASS_PAGINATOR);
9693 this._elContainer.insertBefore(above,this._elContainer.firstChild);
9697 below = document.createElement('div');
9698 below.id = below_id;
9699 Dom.addClass(below, DT.CLASS_PAGINATOR);
9701 this._elContainer.appendChild(below);
9705 return [above,below];
9709 * Calls Paginator's destroy() method
9711 * @method _destroyPaginator
9714 _destroyPaginator : function () {
9715 var oldPag = this.get('paginator');
9722 * Renders the Paginator to the DataTable UI
9724 * @method renderPaginator
9726 renderPaginator : function () {
9727 var pag = this.get("paginator");
9728 if (!pag) { return; }
9730 // Add the containers if the Paginator is not configured with containers
9731 if (!pag.getContainerNodes().length) {
9732 pag.set('containers',this._defaultPaginatorContainers(true));
9739 * Overridable method gives implementers a hook to show loading message before
9740 * changing Paginator value.
9742 * @method doBeforePaginatorChange
9743 * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9744 * @return {Boolean} Return true to continue changing Paginator value.
9746 doBeforePaginatorChange : function(oPaginatorState) {
9747 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
9752 * Responds to new Pagination states. By default, updates the UI to reflect the
9753 * new state. If "dynamicData" is true, current selections are purged before
9754 * a request is sent to the DataSource for data for the new state (using the
9755 * request returned by "generateRequest()").
9757 * @method onPaginatorChangeRequest
9758 * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9760 onPaginatorChangeRequest : function (oPaginatorState) {
9761 var ok = this.doBeforePaginatorChange(oPaginatorState);
9763 // Server-side pagination
9764 if(this.get("dynamicData")) {
9765 // Get the current state
9766 var oState = this.getState();
9768 // Update pagination values
9769 oState.pagination = oPaginatorState;
9771 // Get the request for the new state
9772 var request = this.get("generateRequest")(oState, this);
9775 this.unselectAllRows();
9776 this.unselectAllCells();
9778 // Get the new data from the server
9780 success : this.onDataReturnSetRows,
9781 failure : this.onDataReturnSetRows,
9782 argument : oState, // Pass along the new state to the callback
9785 this._oDataSource.sendRequest(request, callback);
9787 // Client-side pagination
9789 // Set the core pagination values silently (the second param)
9790 // to avoid looping back through the changeRequest mechanism
9791 oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
9792 oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
9851 // SELECTION/HIGHLIGHTING
9854 * Reference to last highlighted cell element
9856 * @property _elLastHighlightedTd
9860 _elLastHighlightedTd : null,
9863 * ID string of last highlighted row element
9865 * @property _sLastHighlightedTrElId
9869 //_sLastHighlightedTrElId : null,
9872 * Array to track row selections (by sRecordId) and/or cell selections
9873 * (by {recordId:sRecordId, columnKey:sColumnKey})
9875 * @property _aSelections
9879 _aSelections : null,
9882 * Record instance of the row selection anchor.
9884 * @property _oAnchorRecord
9885 * @type YAHOO.widget.Record
9888 _oAnchorRecord : null,
9891 * Object literal representing cell selection anchor:
9892 * {recordId:sRecordId, columnKey:sColumnKey}.
9894 * @property _oAnchorCell
9898 _oAnchorCell : null,
9901 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
9902 * from all TR elements on the page.
9904 * @method _unselectAllTrEls
9907 _unselectAllTrEls : function() {
9908 var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
9909 Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
9913 * Returns object literal of values that represent the selection trigger. Used
9914 * to determine selection behavior resulting from a key event.
9916 * @method _getSelectionTrigger
9919 _getSelectionTrigger : function() {
9920 var sMode = this.get("selectionMode");
9922 var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
9925 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
9926 oTriggerCell = this.getLastSelectedCell();
9927 // No selected cells found
9932 oTriggerRecord = this.getRecord(oTriggerCell.recordId);
9933 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
9934 elTriggerRow = this.getTrEl(oTriggerRecord);
9935 nTriggerTrIndex = this.getTrIndex(elTriggerRow);
9937 // Selected cell not found on this page
9938 if(nTriggerTrIndex === null) {
9942 oTrigger.record = oTriggerRecord;
9943 oTrigger.recordIndex = nTriggerRecordIndex;
9944 oTrigger.el = this.getTdEl(oTriggerCell);
9945 oTrigger.trIndex = nTriggerTrIndex;
9946 oTrigger.column = this.getColumn(oTriggerCell.columnKey);
9947 oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
9948 oTrigger.cell = oTriggerCell;
9955 oTriggerRecord = this.getLastSelectedRecord();
9956 // No selected rows found
9957 if(!oTriggerRecord) {
9961 // Selected row found, but is it on current page?
9962 oTriggerRecord = this.getRecord(oTriggerRecord);
9963 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
9964 elTriggerRow = this.getTrEl(oTriggerRecord);
9965 nTriggerTrIndex = this.getTrIndex(elTriggerRow);
9967 // Selected row not found on this page
9968 if(nTriggerTrIndex === null) {
9972 oTrigger.record = oTriggerRecord;
9973 oTrigger.recordIndex = nTriggerRecordIndex;
9974 oTrigger.el = elTriggerRow;
9975 oTrigger.trIndex = nTriggerTrIndex;
9983 * Returns object literal of values that represent the selection anchor. Used
9984 * to determine selection behavior resulting from a user event.
9986 * @method _getSelectionAnchor
9987 * @param oTrigger {Object} (Optional) Object literal of selection trigger values
9991 _getSelectionAnchor : function(oTrigger) {
9992 var sMode = this.get("selectionMode");
9994 var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
9997 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
9998 // Validate anchor cell
9999 var oAnchorCell = this._oAnchorCell;
10002 oAnchorCell = this._oAnchorCell = oTrigger.cell;
10008 oAnchorRecord = this._oAnchorCell.record;
10009 nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
10010 nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
10011 // If anchor cell is not on this page...
10012 if(nAnchorTrIndex === null) {
10013 // ...set TR index equal to top TR
10014 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
10015 nAnchorTrIndex = 0;
10017 // ...set TR index equal to bottom TR
10019 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10023 oAnchor.record = oAnchorRecord;
10024 oAnchor.recordIndex = nAnchorRecordIndex;
10025 oAnchor.trIndex = nAnchorTrIndex;
10026 oAnchor.column = this._oAnchorCell.column;
10027 oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
10028 oAnchor.cell = oAnchorCell;
10033 oAnchorRecord = this._oAnchorRecord;
10034 if(!oAnchorRecord) {
10036 oAnchorRecord = this._oAnchorRecord = oTrigger.record;
10043 nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
10044 nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
10045 // If anchor row is not on this page...
10046 if(nAnchorTrIndex === null) {
10047 // ...set TR index equal to top TR
10048 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
10049 nAnchorTrIndex = 0;
10051 // ...set TR index equal to bottom TR
10053 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10057 oAnchor.record = oAnchorRecord;
10058 oAnchor.recordIndex = nAnchorRecordIndex;
10059 oAnchor.trIndex = nAnchorTrIndex;
10065 * Determines selection behavior resulting from a mouse event when selection mode
10066 * is set to "standard".
10068 * @method _handleStandardSelectionByMouse
10069 * @param oArgs.event {HTMLEvent} Event object.
10070 * @param oArgs.target {HTMLElement} Target element.
10073 _handleStandardSelectionByMouse : function(oArgs) {
10074 var elTarget = oArgs.target;
10076 // Validate target row
10077 var elTargetRow = this.getTrEl(elTarget);
10079 var e = oArgs.event;
10080 var bSHIFT = e.shiftKey;
10081 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10083 var oTargetRecord = this.getRecord(elTargetRow);
10084 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10086 var oAnchor = this._getSelectionAnchor();
10090 // Both SHIFT and CTRL
10091 if(bSHIFT && bCTRL) {
10094 if(this.isSelected(oAnchor.record)) {
10095 // Select all rows between anchor row and target row, including target row
10096 if(oAnchor.recordIndex < nTargetRecordIndex) {
10097 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
10098 if(!this.isSelected(i)) {
10103 // Select all rows between target row and anchor row, including target row
10105 for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
10106 if(!this.isSelected(i)) {
10113 // Unselect all rows between anchor row and target row
10114 if(oAnchor.recordIndex < nTargetRecordIndex) {
10115 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
10116 if(this.isSelected(i)) {
10117 this.unselectRow(i);
10121 // Unselect all rows between target row and anchor row
10123 for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
10124 if(this.isSelected(i)) {
10125 this.unselectRow(i);
10129 // Select the target row
10130 this.selectRow(oTargetRecord);
10136 this._oAnchorRecord = oTargetRecord;
10138 // Toggle selection of target
10139 if(this.isSelected(oTargetRecord)) {
10140 this.unselectRow(oTargetRecord);
10143 this.selectRow(oTargetRecord);
10149 this.unselectAllRows();
10153 // Select all rows between anchor row and target row,
10154 // including the anchor row and target row
10155 if(oAnchor.recordIndex < nTargetRecordIndex) {
10156 for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
10160 // Select all rows between target row and anchor row,
10161 // including the target row and anchor row
10163 for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
10171 this._oAnchorRecord = oTargetRecord;
10173 // Select target row only
10174 this.selectRow(oTargetRecord);
10180 this._oAnchorRecord = oTargetRecord;
10182 // Toggle selection of target
10183 if(this.isSelected(oTargetRecord)) {
10184 this.unselectRow(oTargetRecord);
10187 this.selectRow(oTargetRecord);
10190 // Neither SHIFT nor CTRL
10192 this._handleSingleSelectionByMouse(oArgs);
10199 * Determines selection behavior resulting from a key event when selection mode
10200 * is set to "standard".
10202 * @method _handleStandardSelectionByKey
10203 * @param e {HTMLEvent} Event object.
10206 _handleStandardSelectionByKey : function(e) {
10207 var nKey = Ev.getCharCode(e);
10209 if((nKey == 38) || (nKey == 40)) {
10210 var bSHIFT = e.shiftKey;
10212 // Validate trigger
10213 var oTrigger = this._getSelectionTrigger();
10214 // Arrow selection only works if last selected row is on current page
10222 var oAnchor = this._getSelectionAnchor(oTrigger);
10224 // Determine which direction we're going to
10226 // Selecting down away from anchor row
10227 if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
10228 this.selectRow(this.getNextTrEl(oTrigger.el));
10230 // Selecting up away from anchor row
10231 else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
10232 this.selectRow(this.getPreviousTrEl(oTrigger.el));
10234 // Unselect trigger
10236 this.unselectRow(oTrigger.el);
10240 this._handleSingleSelectionByKey(e);
10246 * Determines selection behavior resulting from a mouse event when selection mode
10247 * is set to "single".
10249 * @method _handleSingleSelectionByMouse
10250 * @param oArgs.event {HTMLEvent} Event object.
10251 * @param oArgs.target {HTMLElement} Target element.
10254 _handleSingleSelectionByMouse : function(oArgs) {
10255 var elTarget = oArgs.target;
10257 // Validate target row
10258 var elTargetRow = this.getTrEl(elTarget);
10260 var oTargetRecord = this.getRecord(elTargetRow);
10263 this._oAnchorRecord = oTargetRecord;
10265 // Select only target
10266 this.unselectAllRows();
10267 this.selectRow(oTargetRecord);
10272 * Determines selection behavior resulting from a key event when selection mode
10273 * is set to "single".
10275 * @method _handleSingleSelectionByKey
10276 * @param e {HTMLEvent} Event object.
10279 _handleSingleSelectionByKey : function(e) {
10280 var nKey = Ev.getCharCode(e);
10282 if((nKey == 38) || (nKey == 40)) {
10283 // Validate trigger
10284 var oTrigger = this._getSelectionTrigger();
10285 // Arrow selection only works if last selected row is on current page
10292 // Determine the new row to select
10294 if(nKey == 38) { // arrow up
10295 elNew = this.getPreviousTrEl(oTrigger.el);
10297 // Validate new row
10298 if(elNew === null) {
10299 //TODO: wrap around to last tr on current page
10300 //elNew = this.getLastTrEl();
10302 //TODO: wrap back to last tr of previous page
10304 // Top row selection is sticky
10305 elNew = this.getFirstTrEl();
10308 else if(nKey == 40) { // arrow down
10309 elNew = this.getNextTrEl(oTrigger.el);
10311 // Validate new row
10312 if(elNew === null) {
10313 //TODO: wrap around to first tr on current page
10314 //elNew = this.getFirstTrEl();
10316 //TODO: wrap forward to first tr of previous page
10318 // Bottom row selection is sticky
10319 elNew = this.getLastTrEl();
10323 // Unselect all rows
10324 this.unselectAllRows();
10326 // Select the new row
10327 this.selectRow(elNew);
10330 this._oAnchorRecord = this.getRecord(elNew);
10335 * Determines selection behavior resulting from a mouse event when selection mode
10336 * is set to "cellblock".
10338 * @method _handleCellBlockSelectionByMouse
10339 * @param oArgs.event {HTMLEvent} Event object.
10340 * @param oArgs.target {HTMLElement} Target element.
10343 _handleCellBlockSelectionByMouse : function(oArgs) {
10344 var elTarget = oArgs.target;
10346 // Validate target cell
10347 var elTargetCell = this.getTdEl(elTarget);
10349 var e = oArgs.event;
10350 var bSHIFT = e.shiftKey;
10351 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10353 var elTargetRow = this.getTrEl(elTargetCell);
10354 var nTargetTrIndex = this.getTrIndex(elTargetRow);
10355 var oTargetColumn = this.getColumn(elTargetCell);
10356 var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10357 var oTargetRecord = this.getRecord(elTargetRow);
10358 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10359 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10361 var oAnchor = this._getSelectionAnchor();
10363 var allRows = this.getTbodyEl().rows;
10364 var startIndex, endIndex, currentRow, i, j;
10366 // Both SHIFT and CTRL
10367 if(bSHIFT && bCTRL) {
10371 // Anchor is selected
10372 if(this.isSelected(oAnchor.cell)) {
10373 // All cells are on the same row
10374 if(oAnchor.recordIndex === nTargetRecordIndex) {
10375 // Select all cells between anchor cell and target cell, including target cell
10376 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10377 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10378 this.selectCell(elTargetRow.cells[i]);
10381 // Select all cells between target cell and anchor cell, including target cell
10382 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10383 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10384 this.selectCell(elTargetRow.cells[i]);
10388 // Anchor row is above target row
10389 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10390 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10391 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10393 // Select all cells from startIndex to endIndex on rows between anchor row and target row
10394 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10395 for(j=startIndex; j<=endIndex; j++) {
10396 this.selectCell(allRows[i].cells[j]);
10400 // Anchor row is below target row
10402 startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
10403 endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
10405 // Select all cells from startIndex to endIndex on rows between target row and anchor row
10406 for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
10407 for(j=endIndex; j>=startIndex; j--) {
10408 this.selectCell(allRows[i].cells[j]);
10413 // Anchor cell is unselected
10415 // All cells are on the same row
10416 if(oAnchor.recordIndex === nTargetRecordIndex) {
10417 // Unselect all cells between anchor cell and target cell
10418 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10419 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10420 this.unselectCell(elTargetRow.cells[i]);
10423 // Select all cells between target cell and anchor cell
10424 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10425 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10426 this.unselectCell(elTargetRow.cells[i]);
10430 // Anchor row is above target row
10431 if(oAnchor.recordIndex < nTargetRecordIndex) {
10432 // Unselect all cells from anchor cell to target cell
10433 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10434 currentRow = allRows[i];
10435 for(j=0; j<currentRow.cells.length; j++) {
10436 // This is the anchor row, only unselect cells after the anchor cell
10437 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
10438 if(j>oAnchor.colKeyIndex) {
10439 this.unselectCell(currentRow.cells[j]);
10442 // This is the target row, only unelect cells before the target cell
10443 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
10444 if(j<nTargetColKeyIndex) {
10445 this.unselectCell(currentRow.cells[j]);
10448 // Unselect all cells on this row
10450 this.unselectCell(currentRow.cells[j]);
10455 // Anchor row is below target row
10457 // Unselect all cells from target cell to anchor cell
10458 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10459 currentRow = allRows[i];
10460 for(j=0; j<currentRow.cells.length; j++) {
10461 // This is the target row, only unselect cells after the target cell
10462 if(currentRow.sectionRowIndex == nTargetTrIndex) {
10463 if(j>nTargetColKeyIndex) {
10464 this.unselectCell(currentRow.cells[j]);
10467 // This is the anchor row, only unselect cells before the anchor cell
10468 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10469 if(j<oAnchor.colKeyIndex) {
10470 this.unselectCell(currentRow.cells[j]);
10473 // Unselect all cells on this row
10475 this.unselectCell(currentRow.cells[j]);
10481 // Select the target cell
10482 this.selectCell(elTargetCell);
10488 this._oAnchorCell = oTargetCell;
10490 // Toggle selection of target
10491 if(this.isSelected(oTargetCell)) {
10492 this.unselectCell(oTargetCell);
10495 this.selectCell(oTargetCell);
10502 this.unselectAllCells();
10506 // All cells are on the same row
10507 if(oAnchor.recordIndex === nTargetRecordIndex) {
10508 // Select all cells between anchor cell and target cell,
10509 // including the anchor cell and target cell
10510 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10511 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
10512 this.selectCell(elTargetRow.cells[i]);
10515 // Select all cells between target cell and anchor cell
10516 // including the target cell and anchor cell
10517 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10518 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
10519 this.selectCell(elTargetRow.cells[i]);
10523 // Anchor row is above target row
10524 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10525 // Select the cellblock from anchor cell to target cell
10526 // including the anchor cell and the target cell
10527 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10528 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10530 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10531 for(j=startIndex; j<=endIndex; j++) {
10532 this.selectCell(allRows[i].cells[j]);
10536 // Anchor row is below target row
10538 // Select the cellblock from target cell to anchor cell
10539 // including the target cell and the anchor cell
10540 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10541 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10543 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10544 for(j=startIndex; j<=endIndex; j++) {
10545 this.selectCell(allRows[i].cells[j]);
10553 this._oAnchorCell = oTargetCell;
10555 // Select target only
10556 this.selectCell(oTargetCell);
10563 this._oAnchorCell = oTargetCell;
10565 // Toggle selection of target
10566 if(this.isSelected(oTargetCell)) {
10567 this.unselectCell(oTargetCell);
10570 this.selectCell(oTargetCell);
10574 // Neither SHIFT nor CTRL
10576 this._handleSingleCellSelectionByMouse(oArgs);
10582 * Determines selection behavior resulting from a key event when selection mode
10583 * is set to "cellblock".
10585 * @method _handleCellBlockSelectionByKey
10586 * @param e {HTMLEvent} Event object.
10589 _handleCellBlockSelectionByKey : function(e) {
10590 var nKey = Ev.getCharCode(e);
10591 var bSHIFT = e.shiftKey;
10592 if((nKey == 9) || !bSHIFT) {
10593 this._handleSingleCellSelectionByKey(e);
10597 if((nKey > 36) && (nKey < 41)) {
10598 // Validate trigger
10599 var oTrigger = this._getSelectionTrigger();
10600 // Arrow selection only works if last selected row is on current page
10608 var oAnchor = this._getSelectionAnchor(oTrigger);
10610 var i, startIndex, endIndex, elNew, elNewRow;
10611 var allRows = this.getTbodyEl().rows;
10612 var elThisRow = oTrigger.el.parentNode;
10614 // Determine which direction we're going to
10616 if(nKey == 40) { // arrow down
10617 // Selecting away from anchor cell
10618 if(oAnchor.recordIndex <= oTrigger.recordIndex) {
10619 // Select the horiz block on the next row...
10620 // ...making sure there is room below the trigger row
10621 elNewRow = this.getNextTrEl(oTrigger.el);
10623 startIndex = oAnchor.colKeyIndex;
10624 endIndex = oTrigger.colKeyIndex;
10626 if(startIndex > endIndex) {
10627 for(i=startIndex; i>=endIndex; i--) {
10628 elNew = elNewRow.cells[i];
10629 this.selectCell(elNew);
10634 for(i=startIndex; i<=endIndex; i++) {
10635 elNew = elNewRow.cells[i];
10636 this.selectCell(elNew);
10641 // Unselecting towards anchor cell
10643 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10644 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10645 // Unselect the horiz block on this row towards the next row
10646 for(i=startIndex; i<=endIndex; i++) {
10647 this.unselectCell(elThisRow.cells[i]);
10652 else if(nKey == 38) {
10653 // Selecting away from anchor cell
10654 if(oAnchor.recordIndex >= oTrigger.recordIndex) {
10655 // Select the horiz block on the previous row...
10656 // ...making sure there is room
10657 elNewRow = this.getPreviousTrEl(oTrigger.el);
10659 // Select in order from anchor to trigger...
10660 startIndex = oAnchor.colKeyIndex;
10661 endIndex = oTrigger.colKeyIndex;
10663 if(startIndex > endIndex) {
10664 for(i=startIndex; i>=endIndex; i--) {
10665 elNew = elNewRow.cells[i];
10666 this.selectCell(elNew);
10671 for(i=startIndex; i<=endIndex; i++) {
10672 elNew = elNewRow.cells[i];
10673 this.selectCell(elNew);
10678 // Unselecting towards anchor cell
10680 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10681 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10682 // Unselect the horiz block on this row towards the previous row
10683 for(i=startIndex; i<=endIndex; i++) {
10684 this.unselectCell(elThisRow.cells[i]);
10689 else if(nKey == 39) {
10690 // Selecting away from anchor cell
10691 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
10692 // Select the next vert block to the right...
10693 // ...making sure there is room
10694 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
10695 // Select in order from anchor to trigger...
10696 startIndex = oAnchor.trIndex;
10697 endIndex = oTrigger.trIndex;
10699 if(startIndex > endIndex) {
10700 for(i=startIndex; i>=endIndex; i--) {
10701 elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10702 this.selectCell(elNew);
10707 for(i=startIndex; i<=endIndex; i++) {
10708 elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10709 this.selectCell(elNew);
10714 // Unselecting towards anchor cell
10716 // Unselect the vert block on this column towards the right
10717 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10718 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10719 for(i=startIndex; i<=endIndex; i++) {
10720 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10725 else if(nKey == 37) {
10726 // Selecting away from anchor cell
10727 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
10728 //Select the previous vert block to the left
10729 if(oTrigger.colKeyIndex > 0) {
10730 // Select in order from anchor to trigger...
10731 startIndex = oAnchor.trIndex;
10732 endIndex = oTrigger.trIndex;
10734 if(startIndex > endIndex) {
10735 for(i=startIndex; i>=endIndex; i--) {
10736 elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10737 this.selectCell(elNew);
10742 for(i=startIndex; i<=endIndex; i++) {
10743 elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10744 this.selectCell(elNew);
10749 // Unselecting towards anchor cell
10751 // Unselect the vert block on this column towards the left
10752 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10753 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10754 for(i=startIndex; i<=endIndex; i++) {
10755 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10763 * Determines selection behavior resulting from a mouse event when selection mode
10764 * is set to "cellrange".
10766 * @method _handleCellRangeSelectionByMouse
10767 * @param oArgs.event {HTMLEvent} Event object.
10768 * @param oArgs.target {HTMLElement} Target element.
10771 _handleCellRangeSelectionByMouse : function(oArgs) {
10772 var elTarget = oArgs.target;
10774 // Validate target cell
10775 var elTargetCell = this.getTdEl(elTarget);
10777 var e = oArgs.event;
10778 var bSHIFT = e.shiftKey;
10779 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10781 var elTargetRow = this.getTrEl(elTargetCell);
10782 var nTargetTrIndex = this.getTrIndex(elTargetRow);
10783 var oTargetColumn = this.getColumn(elTargetCell);
10784 var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10785 var oTargetRecord = this.getRecord(elTargetRow);
10786 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10787 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10789 var oAnchor = this._getSelectionAnchor();
10791 var allRows = this.getTbodyEl().rows;
10792 var currentRow, i, j;
10794 // Both SHIFT and CTRL
10795 if(bSHIFT && bCTRL) {
10799 // Anchor is selected
10800 if(this.isSelected(oAnchor.cell)) {
10801 // All cells are on the same row
10802 if(oAnchor.recordIndex === nTargetRecordIndex) {
10803 // Select all cells between anchor cell and target cell, including target cell
10804 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10805 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10806 this.selectCell(elTargetRow.cells[i]);
10809 // Select all cells between target cell and anchor cell, including target cell
10810 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10811 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10812 this.selectCell(elTargetRow.cells[i]);
10816 // Anchor row is above target row
10817 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10818 // Select all cells on anchor row from anchor cell to the end of the row
10819 for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
10820 this.selectCell(elTargetRow.cells[i]);
10823 // Select all cells on all rows between anchor row and target row
10824 for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
10825 for(j=0; j<allRows[i].cells.length; j++){
10826 this.selectCell(allRows[i].cells[j]);
10830 // Select all cells on target row from first cell to the target cell
10831 for(i=0; i<=nTargetColKeyIndex; i++) {
10832 this.selectCell(elTargetRow.cells[i]);
10835 // Anchor row is below target row
10837 // Select all cells on target row from target cell to the end of the row
10838 for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
10839 this.selectCell(elTargetRow.cells[i]);
10842 // Select all cells on all rows between target row and anchor row
10843 for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
10844 for(j=0; j<allRows[i].cells.length; j++){
10845 this.selectCell(allRows[i].cells[j]);
10849 // Select all cells on anchor row from first cell to the anchor cell
10850 for(i=0; i<oAnchor.colKeyIndex; i++) {
10851 this.selectCell(elTargetRow.cells[i]);
10855 // Anchor cell is unselected
10857 // All cells are on the same row
10858 if(oAnchor.recordIndex === nTargetRecordIndex) {
10859 // Unselect all cells between anchor cell and target cell
10860 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10861 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10862 this.unselectCell(elTargetRow.cells[i]);
10865 // Select all cells between target cell and anchor cell
10866 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10867 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10868 this.unselectCell(elTargetRow.cells[i]);
10872 // Anchor row is above target row
10873 if(oAnchor.recordIndex < nTargetRecordIndex) {
10874 // Unselect all cells from anchor cell to target cell
10875 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10876 currentRow = allRows[i];
10877 for(j=0; j<currentRow.cells.length; j++) {
10878 // This is the anchor row, only unselect cells after the anchor cell
10879 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
10880 if(j>oAnchor.colKeyIndex) {
10881 this.unselectCell(currentRow.cells[j]);
10884 // This is the target row, only unelect cells before the target cell
10885 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
10886 if(j<nTargetColKeyIndex) {
10887 this.unselectCell(currentRow.cells[j]);
10890 // Unselect all cells on this row
10892 this.unselectCell(currentRow.cells[j]);
10897 // Anchor row is below target row
10899 // Unselect all cells from target cell to anchor cell
10900 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10901 currentRow = allRows[i];
10902 for(j=0; j<currentRow.cells.length; j++) {
10903 // This is the target row, only unselect cells after the target cell
10904 if(currentRow.sectionRowIndex == nTargetTrIndex) {
10905 if(j>nTargetColKeyIndex) {
10906 this.unselectCell(currentRow.cells[j]);
10909 // This is the anchor row, only unselect cells before the anchor cell
10910 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10911 if(j<oAnchor.colKeyIndex) {
10912 this.unselectCell(currentRow.cells[j]);
10915 // Unselect all cells on this row
10917 this.unselectCell(currentRow.cells[j]);
10923 // Select the target cell
10924 this.selectCell(elTargetCell);
10930 this._oAnchorCell = oTargetCell;
10932 // Toggle selection of target
10933 if(this.isSelected(oTargetCell)) {
10934 this.unselectCell(oTargetCell);
10937 this.selectCell(oTargetCell);
10944 this.unselectAllCells();
10948 // All cells are on the same row
10949 if(oAnchor.recordIndex === nTargetRecordIndex) {
10950 // Select all cells between anchor cell and target cell,
10951 // including the anchor cell and target cell
10952 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10953 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
10954 this.selectCell(elTargetRow.cells[i]);
10957 // Select all cells between target cell and anchor cell
10958 // including the target cell and anchor cell
10959 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10960 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
10961 this.selectCell(elTargetRow.cells[i]);
10965 // Anchor row is above target row
10966 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10967 // Select all cells from anchor cell to target cell
10968 // including the anchor cell and target cell
10969 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10970 currentRow = allRows[i];
10971 for(j=0; j<currentRow.cells.length; j++) {
10972 // This is the anchor row, only select the anchor cell and after
10973 if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10974 if(j>=oAnchor.colKeyIndex) {
10975 this.selectCell(currentRow.cells[j]);
10978 // This is the target row, only select the target cell and before
10979 else if(currentRow.sectionRowIndex == nTargetTrIndex) {
10980 if(j<=nTargetColKeyIndex) {
10981 this.selectCell(currentRow.cells[j]);
10984 // Select all cells on this row
10986 this.selectCell(currentRow.cells[j]);
10991 // Anchor row is below target row
10993 // Select all cells from target cell to anchor cell,
10994 // including the target cell and anchor cell
10995 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10996 currentRow = allRows[i];
10997 for(j=0; j<currentRow.cells.length; j++) {
10998 // This is the target row, only select the target cell and after
10999 if(currentRow.sectionRowIndex == nTargetTrIndex) {
11000 if(j>=nTargetColKeyIndex) {
11001 this.selectCell(currentRow.cells[j]);
11004 // This is the anchor row, only select the anchor cell and before
11005 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11006 if(j<=oAnchor.colKeyIndex) {
11007 this.selectCell(currentRow.cells[j]);
11010 // Select all cells on this row
11012 this.selectCell(currentRow.cells[j]);
11021 this._oAnchorCell = oTargetCell;
11023 // Select target only
11024 this.selectCell(oTargetCell);
11033 this._oAnchorCell = oTargetCell;
11035 // Toggle selection of target
11036 if(this.isSelected(oTargetCell)) {
11037 this.unselectCell(oTargetCell);
11040 this.selectCell(oTargetCell);
11044 // Neither SHIFT nor CTRL
11046 this._handleSingleCellSelectionByMouse(oArgs);
11052 * Determines selection behavior resulting from a key event when selection mode
11053 * is set to "cellrange".
11055 * @method _handleCellRangeSelectionByKey
11056 * @param e {HTMLEvent} Event object.
11059 _handleCellRangeSelectionByKey : function(e) {
11060 var nKey = Ev.getCharCode(e);
11061 var bSHIFT = e.shiftKey;
11062 if((nKey == 9) || !bSHIFT) {
11063 this._handleSingleCellSelectionByKey(e);
11067 if((nKey > 36) && (nKey < 41)) {
11068 // Validate trigger
11069 var oTrigger = this._getSelectionTrigger();
11070 // Arrow selection only works if last selected row is on current page
11078 var oAnchor = this._getSelectionAnchor(oTrigger);
11080 var i, elNewRow, elNew;
11081 var allRows = this.getTbodyEl().rows;
11082 var elThisRow = oTrigger.el.parentNode;
11086 elNewRow = this.getNextTrEl(oTrigger.el);
11088 // Selecting away from anchor cell
11089 if(oAnchor.recordIndex <= oTrigger.recordIndex) {
11090 // Select all cells to the end of this row
11091 for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){
11092 elNew = elThisRow.cells[i];
11093 this.selectCell(elNew);
11096 // Select some of the cells on the next row down
11098 for(i=0; i<=oTrigger.colKeyIndex; i++){
11099 elNew = elNewRow.cells[i];
11100 this.selectCell(elNew);
11104 // Unselecting towards anchor cell
11106 // Unselect all cells to the end of this row
11107 for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){
11108 this.unselectCell(elThisRow.cells[i]);
11111 // Unselect some of the cells on the next row down
11113 for(i=0; i<oTrigger.colKeyIndex; i++){
11114 this.unselectCell(elNewRow.cells[i]);
11120 else if(nKey == 38) {
11121 elNewRow = this.getPreviousTrEl(oTrigger.el);
11123 // Selecting away from anchor cell
11124 if(oAnchor.recordIndex >= oTrigger.recordIndex) {
11125 // Select all the cells to the beginning of this row
11126 for(i=oTrigger.colKeyIndex-1; i>-1; i--){
11127 elNew = elThisRow.cells[i];
11128 this.selectCell(elNew);
11131 // Select some of the cells from the end of the previous row
11133 for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){
11134 elNew = elNewRow.cells[i];
11135 this.selectCell(elNew);
11139 // Unselecting towards anchor cell
11141 // Unselect all the cells to the beginning of this row
11142 for(i=oTrigger.colKeyIndex; i>-1; i--){
11143 this.unselectCell(elThisRow.cells[i]);
11146 // Unselect some of the cells from the end of the previous row
11148 for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){
11149 this.unselectCell(elNewRow.cells[i]);
11155 else if(nKey == 39) {
11156 elNewRow = this.getNextTrEl(oTrigger.el);
11158 // Selecting away from anchor cell
11159 if(oAnchor.recordIndex < oTrigger.recordIndex) {
11160 // Select the next cell to the right
11161 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11162 elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11163 this.selectCell(elNew);
11165 // Select the first cell of the next row
11166 else if(elNewRow) {
11167 elNew = elNewRow.cells[0];
11168 this.selectCell(elNew);
11171 // Unselecting towards anchor cell
11172 else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11173 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11175 // Unselect this cell towards the right
11176 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11178 // Unselect this cells towards the first cell of the next row
11182 // Anchor is on this row
11184 // Selecting away from anchor
11185 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
11186 // Select the next cell to the right
11187 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11188 elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11189 this.selectCell(elNew);
11191 // Select the first cell on the next row
11192 else if(oTrigger.trIndex < allRows.length-1){
11193 elNew = elNewRow.cells[0];
11194 this.selectCell(elNew);
11197 // Unselecting towards anchor
11199 // Unselect this cell towards the right
11200 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11205 else if(nKey == 37) {
11206 elNewRow = this.getPreviousTrEl(oTrigger.el);
11208 // Unselecting towards the anchor
11209 if(oAnchor.recordIndex < oTrigger.recordIndex) {
11210 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11212 // Unselect this cell towards the left
11213 if(oTrigger.colKeyIndex > 0) {
11215 // Unselect this cell towards the last cell of the previous row
11219 // Selecting towards the anchor
11220 else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11221 // Select the next cell to the left
11222 if(oTrigger.colKeyIndex > 0) {
11223 elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11224 this.selectCell(elNew);
11226 // Select the last cell of the previous row
11227 else if(oTrigger.trIndex > 0){
11228 elNew = elNewRow.cells[elNewRow.cells.length-1];
11229 this.selectCell(elNew);
11232 // Anchor is on this row
11234 // Selecting away from anchor cell
11235 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
11236 // Select the next cell to the left
11237 if(oTrigger.colKeyIndex > 0) {
11238 elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11239 this.selectCell(elNew);
11241 // Select the last cell of the previous row
11242 else if(oTrigger.trIndex > 0){
11243 elNew = elNewRow.cells[elNewRow.cells.length-1];
11244 this.selectCell(elNew);
11247 // Unselecting towards anchor cell
11249 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11251 // Unselect this cell towards the left
11252 if(oTrigger.colKeyIndex > 0) {
11254 // Unselect this cell towards the last cell of the previous row
11264 * Determines selection behavior resulting from a mouse event when selection mode
11265 * is set to "singlecell".
11267 * @method _handleSingleCellSelectionByMouse
11268 * @param oArgs.event {HTMLEvent} Event object.
11269 * @param oArgs.target {HTMLElement} Target element.
11272 _handleSingleCellSelectionByMouse : function(oArgs) {
11273 var elTarget = oArgs.target;
11275 // Validate target cell
11276 var elTargetCell = this.getTdEl(elTarget);
11278 var elTargetRow = this.getTrEl(elTargetCell);
11279 var oTargetRecord = this.getRecord(elTargetRow);
11280 var oTargetColumn = this.getColumn(elTargetCell);
11281 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
11284 this._oAnchorCell = oTargetCell;
11286 // Select only target
11287 this.unselectAllCells();
11288 this.selectCell(oTargetCell);
11293 * Determines selection behavior resulting from a key event when selection mode
11294 * is set to "singlecell".
11296 * @method _handleSingleCellSelectionByKey
11297 * @param e {HTMLEvent} Event object.
11300 _handleSingleCellSelectionByKey : function(e) {
11301 var nKey = Ev.getCharCode(e);
11302 if((nKey == 9) || ((nKey > 36) && (nKey < 41))) {
11303 var bSHIFT = e.shiftKey;
11305 // Validate trigger
11306 var oTrigger = this._getSelectionTrigger();
11307 // Arrow selection only works if last selected row is on current page
11312 // Determine the new cell to select
11314 if(nKey == 40) { // Arrow down
11315 elNew = this.getBelowTdEl(oTrigger.el);
11317 // Validate new cell
11318 if(elNew === null) {
11319 //TODO: wrap around to first tr on current page
11321 //TODO: wrap forward to first tr of next page
11323 // Bottom selection is sticky
11324 elNew = oTrigger.el;
11327 else if(nKey == 38) { // Arrow up
11328 elNew = this.getAboveTdEl(oTrigger.el);
11330 // Validate new cell
11331 if(elNew === null) {
11332 //TODO: wrap around to last tr on current page
11334 //TODO: wrap back to last tr of previous page
11336 // Top selection is sticky
11337 elNew = oTrigger.el;
11340 else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab
11341 elNew = this.getNextTdEl(oTrigger.el);
11343 // Validate new cell
11344 if(elNew === null) {
11345 //TODO: wrap around to first td on current page
11347 //TODO: wrap forward to first td of next page
11349 // Top-left selection is sticky, and release TAB focus
11350 //elNew = oTrigger.el;
11354 else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab
11355 elNew = this.getPreviousTdEl(oTrigger.el);
11357 // Validate new cell
11358 if(elNew === null) {
11359 //TODO: wrap around to last td on current page
11361 //TODO: wrap back to last td of previous page
11363 // Bottom-right selection is sticky, and release TAB focus
11364 //elNew = oTrigger.el;
11371 // Unselect all cells
11372 this.unselectAllCells();
11374 // Select the new cell
11375 this.selectCell(elNew);
11378 this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)};
11383 * Returns array of selected TR elements on the page.
11385 * @method getSelectedTrEls
11386 * @return {HTMLElement[]} Array of selected TR elements.
11388 getSelectedTrEls : function() {
11389 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
11393 * Sets given row to the selected state.
11395 * @method selectRow
11396 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11397 * reference or ID string, Record instance, or RecordSet position index.
11399 selectRow : function(row) {
11400 var oRecord, elRow;
11402 if(row instanceof YAHOO.widget.Record) {
11403 oRecord = this._oRecordSet.getRecord(row);
11404 elRow = this.getTrEl(oRecord);
11406 else if(lang.isNumber(row)) {
11407 oRecord = this.getRecord(row);
11408 elRow = this.getTrEl(oRecord);
11411 elRow = this.getTrEl(row);
11412 oRecord = this.getRecord(elRow);
11416 // Update selection trackers
11417 var tracker = this._aSelections || [];
11418 var sRecordId = oRecord.getId();
11421 // Remove if already there:
11422 // Use Array.indexOf if available...
11423 /*if(tracker.indexOf && (tracker.indexOf(sRecordId) > -1)) {
11424 tracker.splice(tracker.indexOf(sRecordId),1);
11426 if(tracker.indexOf) {
11427 index = tracker.indexOf(sRecordId);
11430 // ...or do it the old-fashioned way
11432 for(var j=tracker.length-1; j>-1; j--) {
11433 if(tracker[j] === sRecordId){
11440 tracker.splice(index,1);
11444 tracker.push(sRecordId);
11445 this._aSelections = tracker;
11448 if(!this._oAnchorRecord) {
11449 this._oAnchorRecord = oRecord;
11454 Dom.addClass(elRow, DT.CLASS_SELECTED);
11457 this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
11464 * Sets given row to the unselected state.
11466 * @method unselectRow
11467 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11468 * reference or ID string, Record instance, or RecordSet position index.
11470 unselectRow : function(row) {
11471 var elRow = this.getTrEl(row);
11474 if(row instanceof YAHOO.widget.Record) {
11475 oRecord = this._oRecordSet.getRecord(row);
11477 else if(lang.isNumber(row)) {
11478 oRecord = this.getRecord(row);
11481 oRecord = this.getRecord(elRow);
11485 // Update selection trackers
11486 var tracker = this._aSelections || [];
11487 var sRecordId = oRecord.getId();
11490 // Use Array.indexOf if available...
11491 if(tracker.indexOf) {
11492 index = tracker.indexOf(sRecordId);
11494 // ...or do it the old-fashioned way
11496 for(var j=tracker.length-1; j>-1; j--) {
11497 if(tracker[j] === sRecordId){
11505 tracker.splice(index,1);
11506 this._aSelections = tracker;
11509 Dom.removeClass(elRow, DT.CLASS_SELECTED);
11511 this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
11519 * Clears out all row selections.
11521 * @method unselectAllRows
11523 unselectAllRows : function() {
11524 // Remove all rows from tracker
11525 var tracker = this._aSelections || [],
11528 for(var j=tracker.length-1; j>-1; j--) {
11529 if(lang.isString(tracker[j])){
11530 recId = tracker.splice(j,1);
11531 removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId);
11536 this._aSelections = tracker;
11539 this._unselectAllTrEls();
11541 this.fireEvent("unselectAllRowsEvent", {records: removed});
11545 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
11546 * from all TD elements in the internal tracker.
11548 * @method _unselectAllTdEls
11551 _unselectAllTdEls : function() {
11552 var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11553 Dom.removeClass(selectedCells, DT.CLASS_SELECTED);
11557 * Returns array of selected TD elements on the page.
11559 * @method getSelectedTdEls
11560 * @return {HTMLElement[]} Array of selected TD elements.
11562 getSelectedTdEls : function() {
11563 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11567 * Sets given cell to the selected state.
11569 * @method selectCell
11570 * @param cell {HTMLElement | String} DOM element reference or ID string
11571 * to DataTable page element or RecordSet index.
11573 selectCell : function(cell) {
11574 //TODO: accept {record} in selectCell()
11575 var elCell = this.getTdEl(cell);
11578 var oRecord = this.getRecord(elCell);
11579 var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11581 if(oRecord && sColumnKey) {
11583 var tracker = this._aSelections || [];
11584 var sRecordId = oRecord.getId();
11587 for(var j=tracker.length-1; j>-1; j--) {
11588 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11589 tracker.splice(j,1);
11595 tracker.push({recordId:sRecordId, columnKey:sColumnKey});
11598 this._aSelections = tracker;
11599 if(!this._oAnchorCell) {
11600 this._oAnchorCell = {record:oRecord, column:this.getColumn(sColumnKey)};
11604 Dom.addClass(elCell, DT.CLASS_SELECTED);
11606 this.fireEvent("cellSelectEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key: this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11613 * Sets given cell to the unselected state.
11615 * @method unselectCell
11616 * @param cell {HTMLElement | String} DOM element reference or ID string
11617 * to DataTable page element or RecordSet index.
11619 unselectCell : function(cell) {
11620 var elCell = this.getTdEl(cell);
11623 var oRecord = this.getRecord(elCell);
11624 var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11626 if(oRecord && sColumnKey) {
11628 var tracker = this._aSelections || [];
11629 var id = oRecord.getId();
11632 for(var j=tracker.length-1; j>-1; j--) {
11633 if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){
11634 // Remove from tracker
11635 tracker.splice(j,1);
11638 this._aSelections = tracker;
11641 Dom.removeClass(elCell, DT.CLASS_SELECTED);
11643 this.fireEvent("cellUnselectEvent", {record:oRecord, column: this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11652 * Clears out all cell selections.
11654 * @method unselectAllCells
11656 unselectAllCells : function() {
11657 // Remove all cells from tracker
11658 var tracker = this._aSelections || [];
11659 for(var j=tracker.length-1; j>-1; j--) {
11660 if(lang.isObject(tracker[j])){
11661 tracker.splice(j,1);
11666 this._aSelections = tracker;
11669 this._unselectAllTdEls();
11671 //TODO: send data to unselectAllCellsEvent handler
11672 this.fireEvent("unselectAllCellsEvent");
11676 * Returns true if given item is selected, false otherwise.
11678 * @method isSelected
11679 * @param o {String | HTMLElement | YAHOO.widget.Record | Number
11680 * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by
11681 * reference or ID string, a Record instance, a RecordSet position index,
11682 * or an object literal representation
11684 * @return {Boolean} True if item is selected.
11686 isSelected : function(o) {
11687 if(o && (o.ownerDocument == document)) {
11688 return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED));
11691 var oRecord, sRecordId, j;
11692 var tracker = this._aSelections;
11693 if(tracker && tracker.length > 0) {
11694 // Looking for a Record?
11695 if(o instanceof YAHOO.widget.Record) {
11698 else if(lang.isNumber(o)) {
11699 oRecord = this.getRecord(o);
11702 sRecordId = oRecord.getId();
11705 // Use Array.indexOf if available...
11706 if(tracker.indexOf) {
11707 if(tracker.indexOf(sRecordId) > -1) {
11711 // ...or do it the old-fashioned way
11713 for(j=tracker.length-1; j>-1; j--) {
11714 if(tracker[j] === sRecordId){
11720 // Looking for a cell
11721 else if(o.record && o.column){
11722 sRecordId = o.record.getId();
11723 var sColumnKey = o.column.getKey();
11725 for(j=tracker.length-1; j>-1; j--) {
11726 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11737 * Returns selected rows as an array of Record IDs.
11739 * @method getSelectedRows
11740 * @return {String[]} Array of selected rows by Record ID.
11742 getSelectedRows : function() {
11743 var aSelectedRows = [];
11744 var tracker = this._aSelections || [];
11745 for(var j=0; j<tracker.length; j++) {
11746 if(lang.isString(tracker[j])){
11747 aSelectedRows.push(tracker[j]);
11750 return aSelectedRows;
11754 * Returns selected cells as an array of object literals:
11755 * {recordId:sRecordId, columnKey:sColumnKey}.
11757 * @method getSelectedCells
11758 * @return {Object[]} Array of selected cells by Record ID and Column ID.
11760 getSelectedCells : function() {
11761 var aSelectedCells = [];
11762 var tracker = this._aSelections || [];
11763 for(var j=0; j<tracker.length; j++) {
11764 if(tracker[j] && lang.isObject(tracker[j])){
11765 aSelectedCells.push(tracker[j]);
11768 return aSelectedCells;
11772 * Returns last selected Record ID.
11774 * @method getLastSelectedRecord
11775 * @return {String} Record ID of last selected row.
11777 getLastSelectedRecord : function() {
11778 var tracker = this._aSelections;
11779 if(tracker && tracker.length > 0) {
11780 for(var i=tracker.length-1; i>-1; i--) {
11781 if(lang.isString(tracker[i])){
11789 * Returns last selected cell as an object literal:
11790 * {recordId:sRecordId, columnKey:sColumnKey}.
11792 * @method getLastSelectedCell
11793 * @return {Object} Object literal representation of a cell.
11795 getLastSelectedCell : function() {
11796 var tracker = this._aSelections;
11797 if(tracker && tracker.length > 0) {
11798 for(var i=tracker.length-1; i>-1; i--) {
11799 if(tracker[i].recordId && tracker[i].columnKey){
11807 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
11809 * @method highlightRow
11810 * @param row {HTMLElement | String} DOM element reference or ID string.
11812 highlightRow : function(row) {
11813 var elRow = this.getTrEl(row);
11816 // Make sure previous row is unhighlighted
11817 /* if(this._sLastHighlightedTrElId) {
11818 Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED);
11820 var oRecord = this.getRecord(elRow);
11821 Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED);
11822 //this._sLastHighlightedTrElId = elRow.id;
11823 this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
11829 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
11831 * @method unhighlightRow
11832 * @param row {HTMLElement | String} DOM element reference or ID string.
11834 unhighlightRow : function(row) {
11835 var elRow = this.getTrEl(row);
11838 var oRecord = this.getRecord(elRow);
11839 Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED);
11840 this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
11846 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
11848 * @method highlightCell
11849 * @param cell {HTMLElement | String} DOM element reference or ID string.
11851 highlightCell : function(cell) {
11852 var elCell = this.getTdEl(cell);
11855 // Make sure previous cell is unhighlighted
11856 if(this._elLastHighlightedTd) {
11857 this.unhighlightCell(this._elLastHighlightedTd);
11860 var oRecord = this.getRecord(elCell);
11861 var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11862 Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED);
11863 this._elLastHighlightedTd = elCell;
11864 this.fireEvent("cellHighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11870 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
11872 * @method unhighlightCell
11873 * @param cell {HTMLElement | String} DOM element reference or ID string.
11875 unhighlightCell : function(cell) {
11876 var elCell = this.getTdEl(cell);
11879 var oRecord = this.getRecord(elCell);
11880 Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED);
11881 this._elLastHighlightedTd = null;
11882 this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11934 * Returns current CellEditor instance, or null.
11935 * @method getCellEditor
11936 * @return {YAHOO.widget.CellEditor} CellEditor instance.
11938 getCellEditor : function() {
11939 return this._oCellEditor;
11944 * Activates and shows CellEditor instance for the given cell while deactivating and
11945 * canceling previous CellEditor. It is baked into DataTable that only one CellEditor
11946 * can be active at any given time.
11948 * @method showCellEditor
11949 * @param elCell {HTMLElement | String} Cell to edit.
11951 showCellEditor : function(elCell, oRecord, oColumn) {
11952 // Get a particular CellEditor
11953 elCell = this.getTdEl(elCell);
11955 oColumn = this.getColumn(elCell);
11956 if(oColumn && oColumn.editor) {
11957 var oCellEditor = this._oCellEditor;
11958 // Clean up active CellEditor
11960 if(this._oCellEditor.cancel) {
11961 this._oCellEditor.cancel();
11963 else if(oCellEditor.isActive) {
11964 this.cancelCellEditor();
11968 if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) {
11970 oCellEditor = oColumn.editor;
11971 var ok = oCellEditor.attach(this, elCell);
11973 oCellEditor.move();
11974 ok = this.doBeforeShowCellEditor(oCellEditor);
11976 oCellEditor.show();
11977 this._oCellEditor = oCellEditor;
11981 // Backward compatibility
11983 if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
11984 oRecord = this.getRecord(elCell);
11986 if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
11987 oColumn = this.getColumn(elCell);
11989 if(oRecord && oColumn) {
11990 if(!this._oCellEditor || this._oCellEditor.container) {
11991 this._initCellEditorEl();
11994 // Update Editor values
11995 oCellEditor = this._oCellEditor;
11996 oCellEditor.cell = elCell;
11997 oCellEditor.record = oRecord;
11998 oCellEditor.column = oColumn;
11999 oCellEditor.validator = (oColumn.editorOptions &&
12000 lang.isFunction(oColumn.editorOptions.validator)) ?
12001 oColumn.editorOptions.validator : null;
12002 oCellEditor.value = oRecord.getData(oColumn.key);
12003 oCellEditor.defaultValue = null;
12006 var elContainer = oCellEditor.container;
12007 var x = Dom.getX(elCell);
12008 var y = Dom.getY(elCell);
12010 // SF doesn't get xy for cells in scrolling table
12011 // when tbody display is set to block
12012 if(isNaN(x) || isNaN(y)) {
12013 x = elCell.offsetLeft + // cell pos relative to table
12014 Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document
12015 this._elTbody.scrollLeft; // minus tbody scroll
12016 y = elCell.offsetTop + // cell pos relative to table
12017 Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document
12018 this._elTbody.scrollTop + // minus tbody scroll
12019 this._elThead.offsetHeight; // account for fixed THEAD cells
12022 elContainer.style.left = x + "px";
12023 elContainer.style.top = y + "px";
12025 // Hook to customize the UI
12026 this.doBeforeShowCellEditor(this._oCellEditor);
12028 //TODO: This is temporarily up here due so elements can be focused
12030 elContainer.style.display = "";
12033 Ev.addListener(elContainer, "keydown", function(e, oSelf) {
12034 // ESC hides Cell Editor
12035 if((e.keyCode == 27)) {
12036 oSelf.cancelCellEditor();
12037 oSelf.focusTbodyEl();
12040 oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
12044 // Render Editor markup
12046 if(lang.isString(oColumn.editor)) {
12047 switch(oColumn.editor) {
12049 fnEditor = DT.editCheckbox;
12052 fnEditor = DT.editDate;
12055 fnEditor = DT.editDropdown;
12058 fnEditor = DT.editRadio;
12061 fnEditor = DT.editTextarea;
12064 fnEditor = DT.editTextbox;
12070 else if(lang.isFunction(oColumn.editor)) {
12071 fnEditor = oColumn.editor;
12075 // Create DOM input elements
12076 fnEditor(this._oCellEditor, this);
12078 // Show Save/Cancel buttons
12079 if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
12080 this.showCellEditorBtns(elContainer);
12083 oCellEditor.isActive = true;
12085 //TODO: verify which args to pass
12086 this.fireEvent("editorShowEvent", {editor:oCellEditor});
12100 * Backward compatibility.
12102 * @method _initCellEditorEl
12106 _initCellEditorEl : function() {
12107 // Attach Cell Editor container element as first child of body
12108 var elCellEditor = document.createElement("div");
12109 elCellEditor.id = this._sId + "-celleditor";
12110 elCellEditor.style.display = "none";
12111 elCellEditor.tabIndex = 0;
12112 Dom.addClass(elCellEditor, DT.CLASS_EDITOR);
12113 var elFirstChild = Dom.getFirstChild(document.body);
12115 elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild);
12118 elCellEditor = document.body.appendChild(elCellEditor);
12121 // Internal tracker of Cell Editor values
12122 var oCellEditor = {};
12123 oCellEditor.container = elCellEditor;
12124 oCellEditor.value = null;
12125 oCellEditor.isActive = false;
12126 this._oCellEditor = oCellEditor;
12130 * Overridable abstract method to customize CellEditor before showing.
12132 * @method doBeforeShowCellEditor
12133 * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance.
12134 * @return {Boolean} Return true to continue showing CellEditor.
12136 doBeforeShowCellEditor : function(oCellEditor) {
12141 * Saves active CellEditor input to Record and upates DOM UI.
12143 * @method saveCellEditor
12145 saveCellEditor : function() {
12146 if(this._oCellEditor) {
12147 if(this._oCellEditor.save) {
12148 this._oCellEditor.save();
12150 // Backward compatibility
12151 else if(this._oCellEditor.isActive) {
12152 var newData = this._oCellEditor.value;
12153 // Copy the data to pass to the event
12154 //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key));
12155 var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
12157 // Validate input data
12158 if(this._oCellEditor.validator) {
12159 newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor);
12160 if(newData === null ) {
12161 this.resetCellEditor();
12162 this.fireEvent("editorRevertEvent",
12163 {editor:this._oCellEditor, oldData:oldData, newData:newData});
12167 // Update the Record
12168 this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
12170 this.formatCell(this._oCellEditor.cell.firstChild);
12173 this._oChainRender.add({
12174 method: function() {
12175 this.validateColumnWidths();
12179 this._oChainRender.run();
12180 // Clear out the Cell Editor
12181 this.resetCellEditor();
12183 this.fireEvent("editorSaveEvent",
12184 {editor:this._oCellEditor, oldData:oldData, newData:newData});
12190 * Cancels active CellEditor.
12192 * @method cancelCellEditor
12194 cancelCellEditor : function() {
12195 if(this._oCellEditor) {
12196 if(this._oCellEditor.cancel) {
12197 this._oCellEditor.cancel();
12199 // Backward compatibility
12200 else if(this._oCellEditor.isActive) {
12201 this.resetCellEditor();
12202 //TODO: preserve values for the event?
12203 this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
12210 * Destroys active CellEditor instance and UI.
12212 * @method destroyCellEditor
12214 destroyCellEditor : function() {
12215 if(this._oCellEditor) {
12216 this._oCellEditor.destroy();
12217 this._oCellEditor = null;
12222 * Passes through showEvent of the active CellEditor.
12224 * @method _onEditorShowEvent
12225 * @param oArgs {Object} Custom Event args.
12228 _onEditorShowEvent : function(oArgs) {
12229 this.fireEvent("editorShowEvent", oArgs);
12233 * Passes through keydownEvent of the active CellEditor.
12234 * @param oArgs {Object} Custom Event args.
12236 * @method _onEditorKeydownEvent
12239 _onEditorKeydownEvent : function(oArgs) {
12240 this.fireEvent("editorKeydownEvent", oArgs);
12244 * Passes through revertEvent of the active CellEditor.
12246 * @method _onEditorRevertEvent
12247 * @param oArgs {Object} Custom Event args.
12250 _onEditorRevertEvent : function(oArgs) {
12251 this.fireEvent("editorRevertEvent", oArgs);
12255 * Passes through saveEvent of the active CellEditor.
12257 * @method _onEditorSaveEvent
12258 * @param oArgs {Object} Custom Event args.
12261 _onEditorSaveEvent : function(oArgs) {
12262 this.fireEvent("editorSaveEvent", oArgs);
12266 * Passes through cancelEvent of the active CellEditor.
12268 * @method _onEditorCancelEvent
12269 * @param oArgs {Object} Custom Event args.
12272 _onEditorCancelEvent : function(oArgs) {
12273 this.fireEvent("editorCancelEvent", oArgs);
12277 * Passes through blurEvent of the active CellEditor.
12279 * @method _onEditorBlurEvent
12280 * @param oArgs {Object} Custom Event args.
12283 _onEditorBlurEvent : function(oArgs) {
12284 this.fireEvent("editorBlurEvent", oArgs);
12288 * Passes through blockEvent of the active CellEditor.
12290 * @method _onEditorBlockEvent
12291 * @param oArgs {Object} Custom Event args.
12294 _onEditorBlockEvent : function(oArgs) {
12295 this.fireEvent("editorBlockEvent", oArgs);
12299 * Passes through unblockEvent of the active CellEditor.
12301 * @method _onEditorUnblockEvent
12302 * @param oArgs {Object} Custom Event args.
12305 _onEditorUnblockEvent : function(oArgs) {
12306 this.fireEvent("editorUnblockEvent", oArgs);
12310 * Public handler of the editorBlurEvent. By default, saves on blur if
12311 * disableBtns is true, otherwise cancels on blur.
12313 * @method onEditorBlurEvent
12314 * @param oArgs {Object} Custom Event args.
12316 onEditorBlurEvent : function(oArgs) {
12317 if(oArgs.editor.disableBtns) {
12319 if(oArgs.editor.save) { // Backward incompatible
12320 oArgs.editor.save();
12323 else if(oArgs.editor.cancel) { // Backward incompatible
12325 oArgs.editor.cancel();
12330 * Public handler of the editorBlockEvent. By default, disables DataTable UI.
12332 * @method onEditorBlockEvent
12333 * @param oArgs {Object} Custom Event args.
12335 onEditorBlockEvent : function(oArgs) {
12340 * Public handler of the editorUnblockEvent. By default, undisables DataTable UI.
12342 * @method onEditorUnblockEvent
12343 * @param oArgs {Object} Custom Event args.
12345 onEditorUnblockEvent : function(oArgs) {
12386 // ABSTRACT METHODS
12389 * Overridable method gives implementers a hook to access data before
12390 * it gets added to RecordSet and rendered to the TBODY.
12392 * @method doBeforeLoadData
12393 * @param sRequest {String} Original request.
12394 * @param oResponse {Object} Response object.
12395 * @param oPayload {MIXED} additional arguments
12396 * @return {Boolean} Return true to continue loading data into RecordSet and
12397 * updating DataTable with new Records, false to cancel.
12399 doBeforeLoadData : function(sRequest, oResponse, oPayload) {
12465 /////////////////////////////////////////////////////////////////////////////
12467 // Public Custom Event Handlers
12469 /////////////////////////////////////////////////////////////////////////////
12472 * Overridable custom event handler to sort Column.
12474 * @method onEventSortColumn
12475 * @param oArgs.event {HTMLEvent} Event object.
12476 * @param oArgs.target {HTMLElement} Target element.
12478 onEventSortColumn : function(oArgs) {
12479 //TODO: support form elements in sortable columns
12480 var evt = oArgs.event;
12481 var target = oArgs.target;
12483 var el = this.getThEl(target) || this.getTdEl(target);
12485 var oColumn = this.getColumn(el);
12486 if(oColumn.sortable) {
12488 this.sortColumn(oColumn);
12496 * Overridable custom event handler to select Column.
12498 * @method onEventSelectColumn
12499 * @param oArgs.event {HTMLEvent} Event object.
12500 * @param oArgs.target {HTMLElement} Target element.
12502 onEventSelectColumn : function(oArgs) {
12503 this.selectColumn(oArgs.target);
12507 * Overridable custom event handler to highlight Column. Accounts for spurious
12508 * caused-by-child events.
12510 * @method onEventHighlightColumn
12511 * @param oArgs.event {HTMLEvent} Event object.
12512 * @param oArgs.target {HTMLElement} Target element.
12514 onEventHighlightColumn : function(oArgs) {
12515 //TODO: filter for all spurious events at a lower level
12516 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12517 this.highlightColumn(oArgs.target);
12522 * Overridable custom event handler to unhighlight Column. Accounts for spurious
12523 * caused-by-child events.
12525 * @method onEventUnhighlightColumn
12526 * @param oArgs.event {HTMLEvent} Event object.
12527 * @param oArgs.target {HTMLElement} Target element.
12529 onEventUnhighlightColumn : 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.unhighlightColumn(oArgs.target);
12537 * Overridable custom event handler to manage selection according to desktop paradigm.
12539 * @method onEventSelectRow
12540 * @param oArgs.event {HTMLEvent} Event object.
12541 * @param oArgs.target {HTMLElement} Target element.
12543 onEventSelectRow : function(oArgs) {
12544 var sMode = this.get("selectionMode");
12545 if(sMode == "single") {
12546 this._handleSingleSelectionByMouse(oArgs);
12549 this._handleStandardSelectionByMouse(oArgs);
12554 * Overridable custom event handler to select cell.
12556 * @method onEventSelectCell
12557 * @param oArgs.event {HTMLEvent} Event object.
12558 * @param oArgs.target {HTMLElement} Target element.
12560 onEventSelectCell : function(oArgs) {
12561 var sMode = this.get("selectionMode");
12562 if(sMode == "cellblock") {
12563 this._handleCellBlockSelectionByMouse(oArgs);
12565 else if(sMode == "cellrange") {
12566 this._handleCellRangeSelectionByMouse(oArgs);
12569 this._handleSingleCellSelectionByMouse(oArgs);
12574 * Overridable custom event handler to highlight row. Accounts for spurious
12575 * caused-by-child events.
12577 * @method onEventHighlightRow
12578 * @param oArgs.event {HTMLEvent} Event object.
12579 * @param oArgs.target {HTMLElement} Target element.
12581 onEventHighlightRow : function(oArgs) {
12582 //TODO: filter for all spurious events at a lower level
12583 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12584 this.highlightRow(oArgs.target);
12589 * Overridable custom event handler to unhighlight row. Accounts for spurious
12590 * caused-by-child events.
12592 * @method onEventUnhighlightRow
12593 * @param oArgs.event {HTMLEvent} Event object.
12594 * @param oArgs.target {HTMLElement} Target element.
12596 onEventUnhighlightRow : function(oArgs) {
12597 //TODO: filter for all spurious events at a lower level
12598 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12599 this.unhighlightRow(oArgs.target);
12604 * Overridable custom event handler to highlight cell. Accounts for spurious
12605 * caused-by-child events.
12607 * @method onEventHighlightCell
12608 * @param oArgs.event {HTMLEvent} Event object.
12609 * @param oArgs.target {HTMLElement} Target element.
12611 onEventHighlightCell : function(oArgs) {
12612 //TODO: filter for all spurious events at a lower level
12613 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12614 this.highlightCell(oArgs.target);
12619 * Overridable custom event handler to unhighlight cell. Accounts for spurious
12620 * caused-by-child events.
12622 * @method onEventUnhighlightCell
12623 * @param oArgs.event {HTMLEvent} Event object.
12624 * @param oArgs.target {HTMLElement} Target element.
12626 onEventUnhighlightCell : function(oArgs) {
12627 //TODO: filter for all spurious events at a lower level
12628 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12629 this.unhighlightCell(oArgs.target);
12634 * Overridable custom event handler to format cell.
12636 * @method onEventFormatCell
12637 * @param oArgs.event {HTMLEvent} Event object.
12638 * @param oArgs.target {HTMLElement} Target element.
12640 onEventFormatCell : function(oArgs) {
12641 var target = oArgs.target;
12643 var elCell = this.getTdEl(target);
12645 var oColumn = this.getColumn(elCell.cellIndex);
12646 this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn);
12653 * Overridable custom event handler to edit cell.
12655 * @method onEventShowCellEditor
12656 * @param oArgs.event {HTMLEvent} Event object.
12657 * @param oArgs.target {HTMLElement} Target element.
12659 onEventShowCellEditor : function(oArgs) {
12660 this.showCellEditor(oArgs.target);
12664 * Overridable custom event handler to save active CellEditor input.
12666 * @method onEventSaveCellEditor
12668 onEventSaveCellEditor : function(oArgs) {
12669 if(this._oCellEditor) {
12670 if(this._oCellEditor.save) {
12671 this._oCellEditor.save();
12673 // Backward compatibility
12675 this.saveCellEditor();
12681 * Overridable custom event handler to cancel active CellEditor.
12683 * @method onEventCancelCellEditor
12685 onEventCancelCellEditor : function(oArgs) {
12686 if(this._oCellEditor) {
12687 if(this._oCellEditor.cancel) {
12688 this._oCellEditor.cancel();
12690 // Backward compatibility
12692 this.cancelCellEditor();
12698 * Callback function receives data from DataSource and populates an entire
12699 * DataTable with Records and TR elements, clearing previous Records, if any.
12701 * @method onDataReturnInitializeTable
12702 * @param sRequest {String} Original request.
12703 * @param oResponse {Object} Response object.
12704 * @param oPayload {MIXED} (optional) Additional argument(s)
12706 onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) {
12707 if((this instanceof DT) && this._sId) {
12708 this.initializeTable();
12710 this.onDataReturnSetRows(sRequest,oResponse,oPayload);
12715 * Callback function receives reponse from DataSource, replaces all existing
12716 * Records in RecordSet, updates TR elements with new data, and updates state
12717 * UI for pagination and sorting from payload data, if necessary.
12719 * @method onDataReturnReplaceRows
12720 * @param oRequest {MIXED} Original generated request.
12721 * @param oResponse {Object} Response object.
12722 * @param oPayload {MIXED} (optional) Additional argument(s)
12724 onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) {
12725 if((this instanceof DT) && this._sId) {
12726 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
12728 // Pass data through abstract method for any transformations
12729 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload),
12730 pag = this.get('paginator'),
12734 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12736 this._oRecordSet.reset();
12738 if (this.get('dynamicData')) {
12739 if (oPayload && oPayload.pagination &&
12740 lang.isNumber(oPayload.pagination.recordOffset)) {
12741 index = oPayload.pagination.recordOffset;
12743 index = pag.getStartIndex();
12747 this._oRecordSet.setRecords(oResponse.results, index | 0);
12750 this._handleDataReturnPayload(oRequest, oResponse, oPayload);
12756 else if(ok && oResponse.error) {
12757 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12763 * Callback function receives data from DataSource and appends to an existing
12764 * DataTable new Records and, if applicable, creates or updates
12765 * corresponding TR elements.
12767 * @method onDataReturnAppendRows
12768 * @param sRequest {String} Original request.
12769 * @param oResponse {Object} Response object.
12770 * @param oPayload {MIXED} (optional) Additional argument(s)
12772 onDataReturnAppendRows : function(sRequest, oResponse, oPayload) {
12773 if((this instanceof DT) && this._sId) {
12774 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12776 // Pass data through abstract method for any transformations
12777 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12779 // Data ok to append
12780 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12782 this.addRows(oResponse.results);
12785 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12788 else if(ok && oResponse.error) {
12789 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12795 * Callback function receives data from DataSource and inserts new records
12796 * starting at the index specified in oPayload.insertIndex. The value for
12797 * oPayload.insertIndex can be populated when sending the request to the DataSource,
12798 * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime.
12799 * If applicable, creates or updates corresponding TR elements.
12801 * @method onDataReturnInsertRows
12802 * @param sRequest {String} Original request.
12803 * @param oResponse {Object} Response object.
12804 * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex.
12806 onDataReturnInsertRows : function(sRequest, oResponse, oPayload) {
12807 if((this instanceof DT) && this._sId) {
12808 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12810 // Pass data through abstract method for any transformations
12811 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12813 // Data ok to append
12814 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12816 this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0));
12819 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12822 else if(ok && oResponse.error) {
12823 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12829 * Callback function receives data from DataSource and incrementally updates Records
12830 * starting at the index specified in oPayload.updateIndex. The value for
12831 * oPayload.updateIndex can be populated when sending the request to the DataSource,
12832 * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime.
12833 * If applicable, creates or updates corresponding TR elements.
12835 * @method onDataReturnUpdateRows
12836 * @param sRequest {String} Original request.
12837 * @param oResponse {Object} Response object.
12838 * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex.
12840 onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) {
12841 if((this instanceof DT) && this._sId) {
12842 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12844 // Pass data through abstract method for any transformations
12845 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12847 // Data ok to append
12848 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12850 this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results);
12853 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12856 else if(ok && oResponse.error) {
12857 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12863 * Callback function receives reponse from DataSource and populates the
12864 * RecordSet with the results.
12866 * @method onDataReturnSetRows
12867 * @param oRequest {MIXED} Original generated request.
12868 * @param oResponse {Object} Response object.
12869 * @param oPayload {MIXED} (optional) Additional argument(s)
12871 onDataReturnSetRows : function(oRequest, oResponse, oPayload) {
12872 if((this instanceof DT) && this._sId) {
12873 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
12875 // Pass data through abstract method for any transformations
12876 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload),
12877 pag = this.get('paginator'),
12881 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12883 if (this.get('dynamicData')) {
12884 if (oPayload && oPayload.pagination &&
12885 lang.isNumber(oPayload.pagination.recordOffset)) {
12886 index = oPayload.pagination.recordOffset;
12888 index = pag.getStartIndex();
12891 this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default
12894 this._oRecordSet.setRecords(oResponse.results, index | 0);
12897 this._handleDataReturnPayload(oRequest, oResponse, oPayload);
12903 else if(ok && oResponse.error) {
12904 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12912 * Hook to update oPayload before consumption.
12914 * @method handleDataReturnPayload
12915 * @param oRequest {MIXED} Original generated request.
12916 * @param oResponse {Object} Response object.
12917 * @param oPayload {MIXED} State values.
12918 * @return oPayload {MIXED} State values.
12920 handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
12925 * Updates the DataTable with state data sent in an onDataReturn* payload.
12927 * @method handleDataReturnPayload
12928 * @param oRequest {MIXED} Original generated request.
12929 * @param oResponse {Object} Response object.
12930 * @param oPayload {MIXED} State values
12932 _handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
12933 oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload);
12935 // Update pagination
12936 var oPaginator = this.get('paginator');
12938 // Update totalRecords
12939 if(this.get("dynamicData")) {
12940 if (widget.Paginator.isNumeric(oPayload.totalRecords)) {
12941 oPaginator.set('totalRecords',oPayload.totalRecords);
12945 oPaginator.set('totalRecords',this._oRecordSet.getLength());
12947 // Update other paginator values
12948 if (lang.isObject(oPayload.pagination)) {
12949 oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage);
12950 oPaginator.set('recordOffset',oPayload.pagination.recordOffset);
12955 if (oPayload.sortedBy) {
12956 // Set the sorting values in preparation for refresh
12957 this.set('sortedBy', oPayload.sortedBy);
12959 // Backwards compatibility for sorting
12960 else if (oPayload.sorting) {
12961 // Set the sorting values in preparation for refresh
12962 this.set('sortedBy', oPayload.sorting);
12999 /////////////////////////////////////////////////////////////////////////////
13003 /////////////////////////////////////////////////////////////////////////////
13006 * Fired when the DataTable's rows are rendered from an initialized state.
13012 * Fired before the DataTable's DOM is rendered or modified.
13014 * @event beforeRenderEvent
13018 * Fired when the DataTable's DOM is rendered or modified.
13020 * @event renderEvent
13024 * Fired when the DataTable's post-render routine is complete, including
13025 * Column width validations.
13027 * @event postRenderEvent
13031 * Fired when the DataTable is disabled.
13033 * @event disableEvent
13037 * Fired when the DataTable is undisabled.
13039 * @event undisableEvent
13043 * Fired when data is returned from DataSource but before it is consumed by
13046 * @event dataReturnEvent
13047 * @param oArgs.request {String} Original request.
13048 * @param oArgs.response {Object} Response object.
13052 * Fired when the DataTable has a focus event.
13054 * @event tableFocusEvent
13058 * Fired when the DataTable THEAD element has a focus event.
13060 * @event theadFocusEvent
13064 * Fired when the DataTable TBODY element has a focus event.
13066 * @event tbodyFocusEvent
13070 * Fired when the DataTable has a blur event.
13072 * @event tableBlurEvent
13075 /*TODO implement theadBlurEvent
13076 * Fired when the DataTable THEAD element has a blur event.
13078 * @event theadBlurEvent
13081 /*TODO: implement tbodyBlurEvent
13082 * Fired when the DataTable TBODY element has a blur event.
13084 * @event tbodyBlurEvent
13088 * Fired when the DataTable has a key event.
13090 * @event tableKeyEvent
13091 * @param oArgs.event {HTMLEvent} The event object.
13092 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13096 * Fired when the DataTable THEAD element has a key event.
13098 * @event theadKeyEvent
13099 * @param oArgs.event {HTMLEvent} The event object.
13100 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13104 * Fired when the DataTable TBODY element has a key event.
13106 * @event tbodyKeyEvent
13107 * @param oArgs.event {HTMLEvent} The event object.
13108 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13112 * Fired when the DataTable has a mouseover.
13114 * @event tableMouseoverEvent
13115 * @param oArgs.event {HTMLEvent} The event object.
13116 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13121 * Fired when the DataTable has a mouseout.
13123 * @event tableMouseoutEvent
13124 * @param oArgs.event {HTMLEvent} The event object.
13125 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13130 * Fired when the DataTable has a mousedown.
13132 * @event tableMousedownEvent
13133 * @param oArgs.event {HTMLEvent} The event object.
13134 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13139 * Fired when the DataTable has a mouseup.
13141 * @event tableMouseupEvent
13142 * @param oArgs.event {HTMLEvent} The event object.
13143 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13148 * Fired when the DataTable has a click.
13150 * @event tableClickEvent
13151 * @param oArgs.event {HTMLEvent} The event object.
13152 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13157 * Fired when the DataTable has a dblclick.
13159 * @event tableDblclickEvent
13160 * @param oArgs.event {HTMLEvent} The event object.
13161 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13166 * Fired when a message is shown in the DataTable's message element.
13168 * @event tableMsgShowEvent
13169 * @param oArgs.html {String} The HTML displayed.
13170 * @param oArgs.className {String} The className assigned.
13175 * Fired when the DataTable's message element is hidden.
13177 * @event tableMsgHideEvent
13181 * Fired when a THEAD row has a mouseover.
13183 * @event theadRowMouseoverEvent
13184 * @param oArgs.event {HTMLEvent} The event object.
13185 * @param oArgs.target {HTMLElement} The TR element.
13189 * Fired when a THEAD row has a mouseout.
13191 * @event theadRowMouseoutEvent
13192 * @param oArgs.event {HTMLEvent} The event object.
13193 * @param oArgs.target {HTMLElement} The TR element.
13197 * Fired when a THEAD row has a mousedown.
13199 * @event theadRowMousedownEvent
13200 * @param oArgs.event {HTMLEvent} The event object.
13201 * @param oArgs.target {HTMLElement} The TR element.
13205 * Fired when a THEAD row has a mouseup.
13207 * @event theadRowMouseupEvent
13208 * @param oArgs.event {HTMLEvent} The event object.
13209 * @param oArgs.target {HTMLElement} The TR element.
13213 * Fired when a THEAD row has a click.
13215 * @event theadRowClickEvent
13216 * @param oArgs.event {HTMLEvent} The event object.
13217 * @param oArgs.target {HTMLElement} The TR element.
13221 * Fired when a THEAD row has a dblclick.
13223 * @event theadRowDblclickEvent
13224 * @param oArgs.event {HTMLEvent} The event object.
13225 * @param oArgs.target {HTMLElement} The TR element.
13229 * Fired when a THEAD cell has a mouseover.
13231 * @event theadCellMouseoverEvent
13232 * @param oArgs.event {HTMLEvent} The event object.
13233 * @param oArgs.target {HTMLElement} The TH element.
13238 * Fired when a THEAD cell has a mouseout.
13240 * @event theadCellMouseoutEvent
13241 * @param oArgs.event {HTMLEvent} The event object.
13242 * @param oArgs.target {HTMLElement} The TH element.
13247 * Fired when a THEAD cell has a mousedown.
13249 * @event theadCellMousedownEvent
13250 * @param oArgs.event {HTMLEvent} The event object.
13251 * @param oArgs.target {HTMLElement} The TH element.
13255 * Fired when a THEAD cell has a mouseup.
13257 * @event theadCellMouseupEvent
13258 * @param oArgs.event {HTMLEvent} The event object.
13259 * @param oArgs.target {HTMLElement} The TH element.
13263 * Fired when a THEAD cell has a click.
13265 * @event theadCellClickEvent
13266 * @param oArgs.event {HTMLEvent} The event object.
13267 * @param oArgs.target {HTMLElement} The TH element.
13271 * Fired when a THEAD cell has a dblclick.
13273 * @event theadCellDblclickEvent
13274 * @param oArgs.event {HTMLEvent} The event object.
13275 * @param oArgs.target {HTMLElement} The TH element.
13279 * Fired when a THEAD label has a mouseover.
13281 * @event theadLabelMouseoverEvent
13282 * @param oArgs.event {HTMLEvent} The event object.
13283 * @param oArgs.target {HTMLElement} The SPAN element.
13288 * Fired when a THEAD label has a mouseout.
13290 * @event theadLabelMouseoutEvent
13291 * @param oArgs.event {HTMLEvent} The event object.
13292 * @param oArgs.target {HTMLElement} The SPAN element.
13297 * Fired when a THEAD label has a mousedown.
13299 * @event theadLabelMousedownEvent
13300 * @param oArgs.event {HTMLEvent} The event object.
13301 * @param oArgs.target {HTMLElement} The SPAN element.
13305 * Fired when a THEAD label has a mouseup.
13307 * @event theadLabelMouseupEvent
13308 * @param oArgs.event {HTMLEvent} The event object.
13309 * @param oArgs.target {HTMLElement} The SPAN element.
13313 * Fired when a THEAD label has a click.
13315 * @event theadLabelClickEvent
13316 * @param oArgs.event {HTMLEvent} The event object.
13317 * @param oArgs.target {HTMLElement} The SPAN element.
13321 * Fired when a THEAD label has a dblclick.
13323 * @event theadLabelDblclickEvent
13324 * @param oArgs.event {HTMLEvent} The event object.
13325 * @param oArgs.target {HTMLElement} The SPAN element.
13329 * Fired when a column is sorted.
13331 * @event columnSortEvent
13332 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13333 * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC
13334 * or YAHOO.widget.DataTable.CLASS_DESC.
13338 * Fired when a column width is set.
13340 * @event columnSetWidthEvent
13341 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13342 * @param oArgs.width {Number} The width in pixels.
13346 * Fired when a column width is unset.
13348 * @event columnUnsetWidthEvent
13349 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13353 * Fired when a column is drag-resized.
13355 * @event columnResizeEvent
13356 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13357 * @param oArgs.target {HTMLElement} The TH element.
13358 * @param oArgs.width {Number} Width in pixels.
13362 * Fired when a Column is moved to a new index.
13364 * @event columnReorderEvent
13365 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13366 * @param oArgs.oldIndex {Number} The previous index position.
13370 * Fired when a column is hidden.
13372 * @event columnHideEvent
13373 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13377 * Fired when a column is shown.
13379 * @event columnShowEvent
13380 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13384 * Fired when a column is selected.
13386 * @event columnSelectEvent
13387 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13391 * Fired when a column is unselected.
13393 * @event columnUnselectEvent
13394 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13397 * Fired when a column is removed.
13399 * @event columnRemoveEvent
13400 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13404 * Fired when a column is inserted.
13406 * @event columnInsertEvent
13407 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13408 * @param oArgs.index {Number} The index position.
13412 * Fired when a column is highlighted.
13414 * @event columnHighlightEvent
13415 * @param oArgs.column {YAHOO.widget.Column} The highlighted Column.
13419 * Fired when a column is unhighlighted.
13421 * @event columnUnhighlightEvent
13422 * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column.
13427 * Fired when a row has a mouseover.
13429 * @event rowMouseoverEvent
13430 * @param oArgs.event {HTMLEvent} The event object.
13431 * @param oArgs.target {HTMLElement} The TR element.
13435 * Fired when a row has a mouseout.
13437 * @event rowMouseoutEvent
13438 * @param oArgs.event {HTMLEvent} The event object.
13439 * @param oArgs.target {HTMLElement} The TR element.
13443 * Fired when a row has a mousedown.
13445 * @event rowMousedownEvent
13446 * @param oArgs.event {HTMLEvent} The event object.
13447 * @param oArgs.target {HTMLElement} The TR element.
13451 * Fired when a row has a mouseup.
13453 * @event rowMouseupEvent
13454 * @param oArgs.event {HTMLEvent} The event object.
13455 * @param oArgs.target {HTMLElement} The TR element.
13459 * Fired when a row has a click.
13461 * @event rowClickEvent
13462 * @param oArgs.event {HTMLEvent} The event object.
13463 * @param oArgs.target {HTMLElement} The TR element.
13467 * Fired when a row has a dblclick.
13469 * @event rowDblclickEvent
13470 * @param oArgs.event {HTMLEvent} The event object.
13471 * @param oArgs.target {HTMLElement} The TR element.
13475 * Fired when a row is added.
13477 * @event rowAddEvent
13478 * @param oArgs.record {YAHOO.widget.Record} The added Record.
13482 * Fired when rows are added.
13484 * @event rowsAddEvent
13485 * @param oArgs.record {YAHOO.widget.Record[]} The added Records.
13489 * Fired when a row is updated.
13491 * @event rowUpdateEvent
13492 * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13493 * @param oArgs.oldData {Object} Object literal of the old data.
13497 * Fired when a row is deleted.
13499 * @event rowDeleteEvent
13500 * @param oArgs.oldData {Object} Object literal of the deleted data.
13501 * @param oArgs.recordIndex {Number} Index of the deleted Record.
13502 * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page.
13506 * Fired when rows are deleted.
13508 * @event rowsDeleteEvent
13509 * @param oArgs.oldData {Object[]} Array of object literals of the deleted data.
13510 * @param oArgs.recordIndex {Number} Index of the first deleted Record.
13511 * @param oArgs.count {Number} Number of deleted Records.
13515 * Fired when a row is selected.
13517 * @event rowSelectEvent
13518 * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
13519 * @param oArgs.record {YAHOO.widget.Record} The selected Record.
13523 * Fired when a row is unselected.
13525 * @event rowUnselectEvent
13526 * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
13527 * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
13531 * Fired when all row selections are cleared.
13533 * @event unselectAllRowsEvent
13537 * Fired when a row is highlighted.
13539 * @event rowHighlightEvent
13540 * @param oArgs.el {HTMLElement} The highlighted TR element.
13541 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13545 * Fired when a row is unhighlighted.
13547 * @event rowUnhighlightEvent
13548 * @param oArgs.el {HTMLElement} The highlighted TR element.
13549 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13553 * Fired when a cell is updated.
13555 * @event cellUpdateEvent
13556 * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13557 * @param oArgs.column {YAHOO.widget.Column} The updated Column.
13558 * @param oArgs.oldData {Object} Original data value of the updated cell.
13562 * Fired when a cell has a mouseover.
13564 * @event cellMouseoverEvent
13565 * @param oArgs.event {HTMLEvent} The event object.
13566 * @param oArgs.target {HTMLElement} The TD element.
13570 * Fired when a cell has a mouseout.
13572 * @event cellMouseoutEvent
13573 * @param oArgs.event {HTMLEvent} The event object.
13574 * @param oArgs.target {HTMLElement} The TD element.
13578 * Fired when a cell has a mousedown.
13580 * @event cellMousedownEvent
13581 * @param oArgs.event {HTMLEvent} The event object.
13582 * @param oArgs.target {HTMLElement} The TD element.
13586 * Fired when a cell has a mouseup.
13588 * @event cellMouseupEvent
13589 * @param oArgs.event {HTMLEvent} The event object.
13590 * @param oArgs.target {HTMLElement} The TD element.
13594 * Fired when a cell has a click.
13596 * @event cellClickEvent
13597 * @param oArgs.event {HTMLEvent} The event object.
13598 * @param oArgs.target {HTMLElement} The TD element.
13602 * Fired when a cell has a dblclick.
13604 * @event cellDblclickEvent
13605 * @param oArgs.event {HTMLEvent} The event object.
13606 * @param oArgs.target {HTMLElement} The TD element.
13610 * Fired when a cell is formatted.
13612 * @event cellFormatEvent
13613 * @param oArgs.el {HTMLElement} The formatted TD element.
13614 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13615 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13616 * @param oArgs.key {String} (deprecated) The key of the formatted cell.
13620 * Fired when a cell is selected.
13622 * @event cellSelectEvent
13623 * @param oArgs.el {HTMLElement} The selected TD element.
13624 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13625 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13626 * @param oArgs.key {String} (deprecated) The key of the selected cell.
13630 * Fired when a cell is unselected.
13632 * @event cellUnselectEvent
13633 * @param oArgs.el {HTMLElement} The unselected TD element.
13634 * @param oArgs.record {YAHOO.widget.Record} The associated Record.
13635 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13636 * @param oArgs.key {String} (deprecated) The key of the unselected cell.
13641 * Fired when a cell is highlighted.
13643 * @event cellHighlightEvent
13644 * @param oArgs.el {HTMLElement} The highlighted TD element.
13645 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13646 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13647 * @param oArgs.key {String} (deprecated) The key of the highlighted cell.
13652 * Fired when a cell is unhighlighted.
13654 * @event cellUnhighlightEvent
13655 * @param oArgs.el {HTMLElement} The unhighlighted TD element.
13656 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13657 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13658 * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell.
13663 * Fired when all cell selections are cleared.
13665 * @event unselectAllCellsEvent
13669 * Fired when a CellEditor is shown.
13671 * @event editorShowEvent
13672 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13676 * Fired when a CellEditor has a keydown.
13678 * @event editorKeydownEvent
13679 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13680 * @param oArgs.event {HTMLEvent} The event object.
13684 * Fired when a CellEditor input is reverted.
13686 * @event editorRevertEvent
13687 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13688 * @param oArgs.newData {Object} New data value from form input field.
13689 * @param oArgs.oldData {Object} Old data value.
13693 * Fired when a CellEditor input is saved.
13695 * @event editorSaveEvent
13696 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13697 * @param oArgs.newData {Object} New data value from form input field.
13698 * @param oArgs.oldData {Object} Old data value.
13702 * Fired when a CellEditor input is canceled.
13704 * @event editorCancelEvent
13705 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13709 * Fired when a CellEditor has a blur event.
13711 * @event editorBlurEvent
13712 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13716 * Fired when a CellEditor is blocked.
13718 * @event editorBlockEvent
13719 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13723 * Fired when a CellEditor is unblocked.
13725 * @event editorUnblockEvent
13726 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13734 * Fired when a link is clicked.
13736 * @event linkClickEvent
13737 * @param oArgs.event {HTMLEvent} The event object.
13738 * @param oArgs.target {HTMLElement} The A element.
13742 * Fired when a BUTTON element or INPUT element of type "button", "image",
13743 * "submit", "reset" is clicked.
13745 * @event buttonClickEvent
13746 * @param oArgs.event {HTMLEvent} The event object.
13747 * @param oArgs.target {HTMLElement} The BUTTON element.
13751 * Fired when a CHECKBOX element is clicked.
13753 * @event checkboxClickEvent
13754 * @param oArgs.event {HTMLEvent} The event object.
13755 * @param oArgs.target {HTMLElement} The CHECKBOX element.
13759 * Fired when a SELECT element is changed.
13761 * @event dropdownChangeEvent
13762 * @param oArgs.event {HTMLEvent} The event object.
13763 * @param oArgs.target {HTMLElement} The SELECT element.
13767 * Fired when a RADIO element is clicked.
13769 * @event radioClickEvent
13770 * @param oArgs.event {HTMLEvent} The event object.
13771 * @param oArgs.target {HTMLElement} The RADIO element.
13799 /////////////////////////////////////////////////////////////////////////////
13803 /////////////////////////////////////////////////////////////////////////////
13806 * @method showCellEditorBtns
13807 * @deprecated Use CellEditor.renderBtns()
13809 showCellEditorBtns : function(elContainer) {
13811 var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
13812 Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON);
13815 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
13816 Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT);
13817 elSaveBtn.innerHTML = "OK";
13818 Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) {
13819 oSelf.onEventSaveCellEditor(oArgs, oSelf);
13820 oSelf.focusTbodyEl();
13824 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
13825 elCancelBtn.innerHTML = "Cancel";
13826 Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) {
13827 oSelf.onEventCancelCellEditor(oArgs, oSelf);
13828 oSelf.focusTbodyEl();
13834 * @method resetCellEditor
13835 * @deprecated Use destroyCellEditor
13837 resetCellEditor : function() {
13838 var elContainer = this._oCellEditor.container;
13839 elContainer.style.display = "none";
13840 Ev.purgeElement(elContainer, true);
13841 elContainer.innerHTML = "";
13842 this._oCellEditor.value = null;
13843 this._oCellEditor.isActive = false;
13848 * @event editorUpdateEvent
13849 * @deprecated Use CellEditor class.
13854 * @deprecated Use getTbodyEl().
13856 getBody : function() {
13857 // Backward compatibility
13858 return this.getTbodyEl();
13863 * @deprecated Use getTdEl().
13865 getCell : function(index) {
13866 // Backward compatibility
13867 return this.getTdEl(index);
13872 * @deprecated Use getTrEl().
13874 getRow : function(index) {
13875 // Backward compatibility
13876 return this.getTrEl(index);
13880 * @method refreshView
13881 * @deprecated Use render.
13883 refreshView : function() {
13884 // Backward compatibility
13890 * @deprecated Use selectRow.
13892 select : function(els) {
13893 // Backward compatibility
13894 if(!lang.isArray(els)) {
13897 for(var i=0; i<els.length; i++) {
13898 this.selectRow(els[i]);
13903 * @method onEventEditCell
13904 * @deprecated Use onEventShowCellEditor.
13906 onEventEditCell : function(oArgs) {
13907 // Backward compatibility
13908 this.onEventShowCellEditor(oArgs);
13912 * @method _syncColWidths
13913 * @deprecated Use validateColumnWidths.
13915 _syncColWidths : function() {
13916 // Backward compatibility
13917 this.validateColumnWidths();
13921 * @event headerRowMouseoverEvent
13922 * @deprecated Use theadRowMouseoverEvent.
13926 * @event headerRowMouseoutEvent
13927 * @deprecated Use theadRowMouseoutEvent.
13931 * @event headerRowMousedownEvent
13932 * @deprecated Use theadRowMousedownEvent.
13936 * @event headerRowClickEvent
13937 * @deprecated Use theadRowClickEvent.
13941 * @event headerRowDblclickEvent
13942 * @deprecated Use theadRowDblclickEvent.
13946 * @event headerCellMouseoverEvent
13947 * @deprecated Use theadCellMouseoverEvent.
13951 * @event headerCellMouseoutEvent
13952 * @deprecated Use theadCellMouseoutEvent.
13956 * @event headerCellMousedownEvent
13957 * @deprecated Use theadCellMousedownEvent.
13961 * @event headerCellClickEvent
13962 * @deprecated Use theadCellClickEvent.
13966 * @event headerCellDblclickEvent
13967 * @deprecated Use theadCellDblclickEvent.
13971 * @event headerLabelMouseoverEvent
13972 * @deprecated Use theadLabelMouseoverEvent.
13976 * @event headerLabelMouseoutEvent
13977 * @deprecated Use theadLabelMouseoutEvent.
13981 * @event headerLabelMousedownEvent
13982 * @deprecated Use theadLabelMousedownEvent.
13986 * @event headerLabelClickEvent
13987 * @deprecated Use theadLabelClickEvent.
13991 * @event headerLabelDbllickEvent
13992 * @deprecated Use theadLabelDblclickEvent.
13998 * Alias for onDataReturnSetRows for backward compatibility
13999 * @method onDataReturnSetRecords
14000 * @deprecated Use onDataReturnSetRows
14002 DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows;
14005 * Alias for onPaginatorChange for backward compatibility
14006 * @method onPaginatorChange
14007 * @deprecated Use onPaginatorChangeRequest
14009 DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest;
14011 /////////////////////////////////////////////////////////////////////////////
14013 // Deprecated static APIs
14015 /////////////////////////////////////////////////////////////////////////////
14017 * @method DataTable.formatTheadCell
14018 * @deprecated Use formatTheadCell.
14020 DT.formatTheadCell = function() {};
14023 * @method DataTable.editCheckbox
14024 * @deprecated Use YAHOO.widget.CheckboxCellEditor.
14026 DT.editCheckbox = function() {};
14029 * @method DataTable.editDate
14030 * @deprecated Use YAHOO.widget.DateCellEditor.
14032 DT.editDate = function() {};
14035 * @method DataTable.editDropdown
14036 * @deprecated Use YAHOO.widget.DropdownCellEditor.
14038 DT.editDropdown = function() {};
14041 * @method DataTable.editRadio
14042 * @deprecated Use YAHOO.widget.RadioCellEditor.
14044 DT.editRadio = function() {};
14047 * @method DataTable.editTextarea
14048 * @deprecated Use YAHOO.widget.TextareaCellEditor
14050 DT.editTextarea = function() {};
14053 * @method DataTable.editTextbox
14054 * @deprecated Use YAHOO.widget.TextboxCellEditor
14056 DT.editTextbox= function() {};
14062 var lang = YAHOO.lang,
14064 widget = YAHOO.widget,
14069 DS = util.DataSourceBase,
14070 DT = widget.DataTable,
14071 Pag = widget.Paginator;
14074 * The ScrollingDataTable class extends the DataTable class to provide
14075 * functionality for x-scrolling, y-scrolling, and xy-scrolling.
14077 * @namespace YAHOO.widget
14078 * @class ScrollingDataTable
14079 * @extends YAHOO.widget.DataTable
14081 * @param elContainer {HTMLElement} Container element for the TABLE.
14082 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
14083 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
14084 * @param oConfigs {object} (optional) Object literal of configuration values.
14086 widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
14087 oConfigs = oConfigs || {};
14089 // Prevent infinite loop
14090 if(oConfigs.scrollable) {
14091 oConfigs.scrollable = false;
14094 widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs);
14096 // Once per instance
14097 this.subscribe("columnShowEvent", this._onColumnChange);
14100 var SDT = widget.ScrollingDataTable;
14102 /////////////////////////////////////////////////////////////////////////////
14104 // Public constants
14106 /////////////////////////////////////////////////////////////////////////////
14107 lang.augmentObject(SDT, {
14110 * Class name assigned to inner DataTable header container.
14112 * @property DataTable.CLASS_HEADER
14116 * @default "yui-dt-hd"
14118 CLASS_HEADER : "yui-dt-hd",
14121 * Class name assigned to inner DataTable body container.
14123 * @property DataTable.CLASS_BODY
14127 * @default "yui-dt-bd"
14129 CLASS_BODY : "yui-dt-bd"
14132 lang.extend(SDT, DT, {
14135 * Container for fixed header TABLE element.
14137 * @property _elHdContainer
14138 * @type HTMLElement
14141 _elHdContainer : null,
14144 * Fixed header TABLE element.
14146 * @property _elHdTable
14147 * @type HTMLElement
14153 * Container for scrolling body TABLE element.
14155 * @property _elBdContainer
14156 * @type HTMLElement
14159 _elBdContainer : null,
14162 * Body THEAD element.
14164 * @property _elBdThead
14165 * @type HTMLElement
14171 * Offscreen container to temporarily clone SDT for auto-width calculation.
14173 * @property _elTmpContainer
14174 * @type HTMLElement
14177 _elTmpContainer : null,
14180 * Offscreen TABLE element for auto-width calculation.
14182 * @property _elTmpTable
14183 * @type HTMLElement
14186 _elTmpTable : null,
14189 * True if x-scrollbar is currently visible.
14190 * @property _bScrollbarX
14194 _bScrollbarX : null,
14210 /////////////////////////////////////////////////////////////////////////////
14212 // Superclass methods
14214 /////////////////////////////////////////////////////////////////////////////
14217 * Implementation of Element's abstract method. Sets up config values.
14219 * @method initAttributes
14220 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
14224 initAttributes : function(oConfigs) {
14225 oConfigs = oConfigs || {};
14226 SDT.superclass.initAttributes.call(this, oConfigs);
14230 * @description Table width for scrollable tables (e.g., "40em").
14233 this.setAttributeConfig("width", {
14235 validator: lang.isString,
14236 method: function(oParam) {
14237 if(this._elHdContainer && this._elBdContainer) {
14238 this._elHdContainer.style.width = oParam;
14239 this._elBdContainer.style.width = oParam;
14240 this._syncScrollX();
14241 this._syncScrollOverhang();
14247 * @attribute height
14248 * @description Table body height for scrollable tables, not including headers (e.g., "40em").
14251 this.setAttributeConfig("height", {
14253 validator: lang.isString,
14254 method: function(oParam) {
14255 if(this._elHdContainer && this._elBdContainer) {
14256 this._elBdContainer.style.height = oParam;
14257 this._syncScrollX();
14258 this._syncScrollY();
14259 this._syncScrollOverhang();
14265 * @attribute COLOR_COLUMNFILLER
14266 * @description CSS color value assigned to header filler on scrollable tables.
14268 * @default "#F2F2F2"
14270 this.setAttributeConfig("COLOR_COLUMNFILLER", {
14272 validator: lang.isString,
14273 method: function(oParam) {
14274 this._elHdContainer.style.backgroundColor = oParam;
14280 * Initializes DOM elements for a ScrollingDataTable, including creation of
14281 * two separate TABLE elements.
14283 * @method _initDomElements
14284 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
14285 * return {Boolean} False in case of error, otherwise true
14288 _initDomElements : function(elContainer) {
14289 // Outer and inner containers
14290 this._initContainerEl(elContainer);
14291 if(this._elContainer && this._elHdContainer && this._elBdContainer) {
14293 this._initTableEl();
14295 if(this._elHdTable && this._elTable) {
14297 ///this._initColgroupEl(this._elHdTable, this._elTable);
14298 this._initColgroupEl(this._elHdTable);
14301 this._initTheadEl(this._elHdTable, this._elTable);
14304 this._initTbodyEl(this._elTable);
14306 this._initMsgTbodyEl(this._elTable);
14309 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody ||
14310 !this._elHdTable || !this._elBdThead) {
14319 * Destroy's the DataTable outer and inner container elements, if available.
14321 * @method _destroyContainerEl
14322 * @param elContainer {HTMLElement} Reference to the container element.
14325 _destroyContainerEl : function(elContainer) {
14326 Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE);
14327 SDT.superclass._destroyContainerEl.call(this, elContainer);
14328 this._elHdContainer = null;
14329 this._elBdContainer = null;
14333 * Initializes the DataTable outer container element and creates inner header
14334 * and body container elements.
14336 * @method _initContainerEl
14337 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
14340 _initContainerEl : function(elContainer) {
14341 SDT.superclass._initContainerEl.call(this, elContainer);
14343 if(this._elContainer) {
14344 elContainer = this._elContainer; // was constructor input, now is DOM ref
14345 Dom.addClass(elContainer, DT.CLASS_SCROLLABLE);
14347 // Container for header TABLE
14348 var elHdContainer = document.createElement("div");
14349 elHdContainer.style.width = this.get("width") || "";
14350 elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER");
14351 Dom.addClass(elHdContainer, SDT.CLASS_HEADER);
14352 this._elHdContainer = elHdContainer;
14353 elContainer.appendChild(elHdContainer);
14355 // Container for body TABLE
14356 var elBdContainer = document.createElement("div");
14357 elBdContainer.style.width = this.get("width") || "";
14358 elBdContainer.style.height = this.get("height") || "";
14359 Dom.addClass(elBdContainer, SDT.CLASS_BODY);
14360 Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers
14361 this._elBdContainer = elBdContainer;
14362 elContainer.appendChild(elBdContainer);
14367 * Creates HTML markup CAPTION element.
14369 * @method _initCaptionEl
14370 * @param sCaption {String} Text for caption.
14373 _initCaptionEl : function(sCaption) {
14374 // Not yet supported
14375 /*if(this._elHdTable && sCaption) {
14376 // Create CAPTION element
14377 if(!this._elCaption) {
14378 this._elCaption = this._elHdTable.createCaption();
14380 // Set CAPTION value
14381 this._elCaption.innerHTML = sCaption;
14383 else if(this._elCaption) {
14384 this._elCaption.parentNode.removeChild(this._elCaption);
14389 * Destroy's the DataTable head TABLE element, if available.
14391 * @method _destroyHdTableEl
14394 _destroyHdTableEl : function() {
14395 var elTable = this._elHdTable;
14397 Ev.purgeElement(elTable, true);
14398 elTable.parentNode.removeChild(elTable);
14400 // A little out of place, but where else can we null out these extra elements?
14401 ///this._elBdColgroup = null;
14402 this._elBdThead = null;
14407 * Initializes ScrollingDataTable TABLE elements into the two inner containers.
14409 * @method _initTableEl
14412 _initTableEl : function() {
14414 if(this._elHdContainer) {
14415 this._destroyHdTableEl();
14418 this._elHdTable = this._elHdContainer.appendChild(document.createElement("table"));
14421 SDT.superclass._initTableEl.call(this, this._elBdContainer);
14425 * Initializes ScrollingDataTable THEAD elements into the two inner containers.
14427 * @method _initTheadEl
14428 * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference.
14429 * @param elTable {HTMLElement} (optional) TABLE element reference.
14432 _initTheadEl : function(elHdTable, elTable) {
14433 elHdTable = elHdTable || this._elHdTable;
14434 elTable = elTable || this._elTable;
14436 // Scrolling body's THEAD
14437 this._initBdTheadEl(elTable);
14438 // Standard fixed head THEAD
14439 SDT.superclass._initTheadEl.call(this, elHdTable);
14443 * SDT changes ID so as not to duplicate the accessibility TH IDs.
14445 * @method _initThEl
14446 * @param elTh {HTMLElement} TH element reference.
14447 * @param oColumn {YAHOO.widget.Column} Column object.
14450 _initThEl : function(elTh, oColumn) {
14451 SDT.superclass._initThEl.call(this, elTh, oColumn);
14452 elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD
14456 * Destroy's the DataTable body THEAD element, if available.
14458 * @method _destroyBdTheadEl
14461 _destroyBdTheadEl : function() {
14462 var elBdThead = this._elBdThead;
14464 var elTable = elBdThead.parentNode;
14465 Ev.purgeElement(elBdThead, true);
14466 elTable.removeChild(elBdThead);
14467 this._elBdThead = null;
14469 this._destroyColumnHelpers();
14474 * Initializes body THEAD element.
14476 * @method _initBdTheadEl
14477 * @param elTable {HTMLElement} TABLE element into which to create THEAD.
14478 * @return {HTMLElement} Initialized THEAD element.
14481 _initBdTheadEl : function(elTable) {
14483 // Destroy previous
14484 this._destroyBdTheadEl();
14486 var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild);
14488 // Add TRs to the THEAD;
14489 var oColumnSet = this._oColumnSet,
14490 colTree = oColumnSet.tree,
14491 elTh, elTheadTr, oColumn, i, j, k, len;
14493 for(i=0, k=colTree.length; i<k; i++) {
14494 elTheadTr = elThead.appendChild(document.createElement("tr"));
14496 // ...and create TH cells
14497 for(j=0, len=colTree[i].length; j<len; j++) {
14498 oColumn = colTree[i][j];
14499 elTh = elTheadTr.appendChild(document.createElement("th"));
14500 this._initBdThEl(elTh,oColumn,i,j);
14503 this._elBdThead = elThead;
14508 * Populates TH element for the body THEAD element.
14510 * @method _initBdThEl
14511 * @param elTh {HTMLElement} TH element reference.
14512 * @param oColumn {YAHOO.widget.Column} Column object.
14515 _initBdThEl : function(elTh, oColumn) {
14516 elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility
14517 elTh.rowSpan = oColumn.getRowspan();
14518 elTh.colSpan = oColumn.getColspan();
14519 // Assign abbr attribute
14521 elTh.abbr = oColumn.abbr;
14524 // TODO: strip links and form elements
14525 var sKey = oColumn.getKey();
14526 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
14527 elTh.innerHTML = sLabel;
14531 * Initializes ScrollingDataTable TBODY element for data
14533 * @method _initTbodyEl
14534 * @param elTable {HTMLElement} TABLE element into which to create TBODY .
14537 _initTbodyEl : function(elTable) {
14538 SDT.superclass._initTbodyEl.call(this, elTable);
14540 // Bug 2105534 - Safari 3 gap
14541 // Bug 2492591 - IE8 offsetTop
14542 elTable.style.marginTop = (this._elTbody.offsetTop > 0) ?
14543 "-"+this._elTbody.offsetTop+"px" : 0;
14575 * Sets focus on the given element.
14578 * @param el {HTMLElement} Element.
14581 _focusEl : function(el) {
14582 el = el || this._elTbody;
14584 this._storeScrollPositions();
14585 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
14586 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
14587 // strange unexpected things as the user clicks on buttons and other controls.
14589 // Bug 1921135: Wrap the whole thing in a setTimeout
14590 setTimeout(function() {
14591 setTimeout(function() {
14594 oSelf._restoreScrollPositions();
14621 * Internal wrapper calls run() on render Chain instance.
14623 * @method _runRenderChain
14626 _runRenderChain : function() {
14627 this._storeScrollPositions();
14628 this._oChainRender.run();
14632 * Stores scroll positions so they can be restored after a render.
14634 * @method _storeScrollPositions
14637 _storeScrollPositions : function() {
14638 this._nScrollTop = this._elBdContainer.scrollTop;
14639 this._nScrollLeft = this._elBdContainer.scrollLeft;
14643 * Clears stored scroll positions to interrupt the automatic restore mechanism.
14644 * Useful for setting scroll positions programmatically rather than as part of
14645 * the post-render cleanup process.
14647 * @method clearScrollPositions
14650 clearScrollPositions : function() {
14651 this._nScrollTop = 0;
14652 this._nScrollLeft = 0;
14656 * Restores scroll positions to stored value.
14658 * @method _retoreScrollPositions
14661 _restoreScrollPositions : function() {
14662 // Reset scroll positions
14663 if(this._nScrollTop) {
14664 this._elBdContainer.scrollTop = this._nScrollTop;
14665 this._nScrollTop = null;
14667 if(this._nScrollLeft) {
14668 this._elBdContainer.scrollLeft = this._nScrollLeft;
14669 this._nScrollLeft = null;
14674 * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable.
14676 * @method _validateColumnWidth
14677 * @param oColumn {YAHOO.widget.Column} Column instance.
14678 * @param elTd {HTMLElement} TD element to validate against.
14681 _validateColumnWidth : function(oColumn, elTd) {
14682 // Only Columns without widths that are not hidden
14683 if(!oColumn.width && !oColumn.hidden) {
14684 var elTh = oColumn.getThEl();
14685 // Unset a calculated auto-width
14686 if(oColumn._calculatedWidth) {
14687 this._setColumnWidth(oColumn, "auto", "visible");
14689 // Compare auto-widths
14690 if(elTh.offsetWidth !== elTd.offsetWidth) {
14691 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14692 oColumn.getThLinerEl() : elTd.firstChild;
14694 // Grab the wider liner width, unless the minWidth is wider
14695 var newWidth = Math.max(0,
14696 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14699 var sOverflow = 'visible';
14701 // Now validate against maxAutoWidth
14702 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14703 newWidth = oColumn.maxAutoWidth;
14704 sOverflow = "hidden";
14707 // Set to the wider auto-width
14708 this._elTbody.style.display = "none";
14709 this._setColumnWidth(oColumn, newWidth+'px', sOverflow);
14710 oColumn._calculatedWidth = newWidth;
14711 this._elTbody.style.display = "";
14717 * For one or all Columns of a ScrollingDataTable, when Column is not hidden,
14718 * and width is not set, syncs widths of header and body cells and
14719 * validates that width against minWidth and/or maxAutoWidth as necessary.
14721 * @method validateColumnWidths
14722 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
14724 validateColumnWidths : function(oColumn) {
14725 // Validate there is at least one TR with proper TDs
14726 var allKeys = this._oColumnSet.keys,
14727 allKeysLength = allKeys.length,
14728 elRow = this.getFirstTrEl();
14730 // Reset overhang for IE
14732 this._setOverhangValue(1);
14735 if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) {
14736 // Temporarily unsnap container since it causes inaccurate calculations
14737 var sWidth = this.get("width");
14739 this._elHdContainer.style.width = "";
14740 this._elBdContainer.style.width = "";
14742 this._elContainer.style.width = "";
14744 //Validate just one Column
14745 if(oColumn && lang.isNumber(oColumn.getKeyIndex())) {
14746 this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]);
14748 // Iterate through all Columns to unset calculated widths in one pass
14750 var elTd, todos = [], thisTodo, i, len;
14751 for(i=0; i<allKeysLength; i++) {
14752 oColumn = allKeys[i];
14753 // Only Columns without widths that are not hidden, unset a calculated auto-width
14754 if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) {
14755 todos[todos.length] = oColumn;
14759 this._elTbody.style.display = "none";
14760 for(i=0, len=todos.length; i<len; i++) {
14761 this._setColumnWidth(todos[i], "auto", "visible");
14763 this._elTbody.style.display = "";
14767 // Iterate through all Columns and make the store the adjustments to make in one pass
14768 for(i=0; i<allKeysLength; i++) {
14769 oColumn = allKeys[i];
14770 elTd = elRow.childNodes[i];
14771 // Only Columns without widths that are not hidden
14772 if(!oColumn.width && !oColumn.hidden) {
14773 var elTh = oColumn.getThEl();
14775 // Compare auto-widths
14776 if(elTh.offsetWidth !== elTd.offsetWidth) {
14777 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14778 oColumn.getThLinerEl() : elTd.firstChild;
14780 // Grab the wider liner width, unless the minWidth is wider
14781 var newWidth = Math.max(0,
14782 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14785 var sOverflow = 'visible';
14787 // Now validate against maxAutoWidth
14788 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14789 newWidth = oColumn.maxAutoWidth;
14790 sOverflow = "hidden";
14793 todos[todos.length] = [oColumn, newWidth, sOverflow];
14798 this._elTbody.style.display = "none";
14799 for(i=0, len=todos.length; i<len; i++) {
14800 thisTodo = todos[i];
14801 // Set to the wider auto-width
14802 this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]);
14803 thisTodo[0]._calculatedWidth = thisTodo[1];
14805 this._elTbody.style.display = "";
14808 // Resnap unsnapped containers
14810 this._elHdContainer.style.width = sWidth;
14811 this._elBdContainer.style.width = sWidth;
14815 this._syncScroll();
14816 this._restoreScrollPositions();
14820 * Syncs padding around scrollable tables, including Column header right-padding
14821 * and container width and height.
14823 * @method _syncScroll
14826 _syncScroll : function() {
14827 this._syncScrollX();
14828 this._syncScrollY();
14829 this._syncScrollOverhang();
14832 this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft;
14833 if(!this.get("width")) {
14835 document.body.style += '';
14841 * Snaps container width for y-scrolling tables.
14843 * @method _syncScrollY
14846 _syncScrollY : function() {
14847 var elTbody = this._elTbody,
14848 elBdContainer = this._elBdContainer;
14850 // X-scrolling not enabled
14851 if(!this.get("width")) {
14852 // Snap outer container width to content
14853 this._elContainer.style.width =
14854 (elBdContainer.scrollHeight > elBdContainer.clientHeight) ?
14855 // but account for y-scrollbar since it is visible
14856 (elTbody.parentNode.clientWidth + 19) + "px" :
14857 // no y-scrollbar, just borders
14858 (elTbody.parentNode.clientWidth + 2) + "px";
14863 * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
14865 * @method _syncScrollX
14868 _syncScrollX : function() {
14869 var elTbody = this._elTbody,
14870 elBdContainer = this._elBdContainer;
14872 // IE 6 and 7 only when y-scrolling not enabled
14873 if(!this.get("height") && (ua.ie)) {
14874 // Snap outer container height to content
14875 elBdContainer.style.height =
14876 // but account for x-scrollbar if it is visible
14877 (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ?
14878 (elTbody.parentNode.offsetHeight + 18) + "px" :
14879 elTbody.parentNode.offsetHeight + "px";
14882 // Sync message tbody
14883 if(this._elTbody.rows.length === 0) {
14884 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
14887 this._elMsgTbody.parentNode.style.width = "";
14892 * Adds/removes Column header overhang as necesary.
14894 * @method _syncScrollOverhang
14897 _syncScrollOverhang : function() {
14898 var elBdContainer = this._elBdContainer,
14899 // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table
14902 // Y-scrollbar is visible, which is when the overhang needs to jut out
14903 if((elBdContainer.scrollHeight > elBdContainer.clientHeight) &&
14904 // X-scrollbar is also visible, which means the right is jagged, not flush with the Column
14905 (elBdContainer.scrollWidth > elBdContainer.clientWidth)) {
14909 this._setOverhangValue(nPadding);
14914 * Sets Column header overhang to given width.
14916 * @method _setOverhangValue
14917 * @param nBorderWidth {Number} Value of new border for overhang.
14920 _setOverhangValue : function(nBorderWidth) {
14921 var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [],
14922 len = aLastHeaders.length,
14923 sPrefix = this._sId+"-fixedth-",
14924 sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER");
14926 this._elThead.style.display = "none";
14927 for(var i=0; i<len; i++) {
14928 Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue;
14930 this._elThead.style.display = "";
14971 * Returns DOM reference to the DataTable's fixed header container element.
14973 * @method getHdContainerEl
14974 * @return {HTMLElement} Reference to DIV element.
14976 getHdContainerEl : function() {
14977 return this._elHdContainer;
14981 * Returns DOM reference to the DataTable's scrolling body container element.
14983 * @method getBdContainerEl
14984 * @return {HTMLElement} Reference to DIV element.
14986 getBdContainerEl : function() {
14987 return this._elBdContainer;
14991 * Returns DOM reference to the DataTable's fixed header TABLE element.
14993 * @method getHdTableEl
14994 * @return {HTMLElement} Reference to TABLE element.
14996 getHdTableEl : function() {
14997 return this._elHdTable;
15001 * Returns DOM reference to the DataTable's scrolling body TABLE element.
15003 * @method getBdTableEl
15004 * @return {HTMLElement} Reference to TABLE element.
15006 getBdTableEl : function() {
15007 return this._elTable;
15011 * Disables ScrollingDataTable UI.
15015 disable : function() {
15016 var elMask = this._elMask;
15017 elMask.style.width = this._elBdContainer.offsetWidth + "px";
15018 elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px";
15019 elMask.style.display = "";
15020 this.fireEvent("disableEvent");
15024 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
15025 * non-nested Columns, and top-level parent Columns (which will remove all
15026 * children Columns).
15028 * @method removeColumn
15029 * @param oColumn {YAHOO.widget.Column} Column instance.
15030 * @return oColumn {YAHOO.widget.Column} Removed Column instance.
15032 removeColumn : function(oColumn) {
15033 // Store scroll pos
15034 var hdPos = this._elHdContainer.scrollLeft;
15035 var bdPos = this._elBdContainer.scrollLeft;
15037 // Call superclass method
15038 oColumn = SDT.superclass.removeColumn.call(this, oColumn);
15040 // Restore scroll pos
15041 this._elHdContainer.scrollLeft = hdPos;
15042 this._elBdContainer.scrollLeft = bdPos;
15048 * Inserts given Column at the index if given, otherwise at the end. NOTE: You
15049 * can only add non-nested Columns and top-level parent Columns. You cannot add
15050 * a nested Column to an existing parent.
15052 * @method insertColumn
15053 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
15054 * definition or a Column instance.
15055 * @param index {Number} (optional) New tree index.
15056 * @return oColumn {YAHOO.widget.Column} Inserted Column instance.
15058 insertColumn : function(oColumn, index) {
15059 // Store scroll pos
15060 var hdPos = this._elHdContainer.scrollLeft;
15061 var bdPos = this._elBdContainer.scrollLeft;
15063 // Call superclass method
15064 var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index);
15066 // Restore scroll pos
15067 this._elHdContainer.scrollLeft = hdPos;
15068 this._elBdContainer.scrollLeft = bdPos;
15074 * Removes given Column and inserts into given tree index. NOTE: You
15075 * can only reorder non-nested Columns and top-level parent Columns. You cannot
15076 * reorder a nested Column to an existing parent.
15078 * @method reorderColumn
15079 * @param oColumn {YAHOO.widget.Column} Column instance.
15080 * @param index {Number} New tree index.
15082 reorderColumn : function(oColumn, index) {
15083 // Store scroll pos
15084 var hdPos = this._elHdContainer.scrollLeft;
15085 var bdPos = this._elBdContainer.scrollLeft;
15087 // Call superclass method
15088 var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index);
15090 // Restore scroll pos
15091 this._elHdContainer.scrollLeft = hdPos;
15092 this._elBdContainer.scrollLeft = bdPos;
15098 * Sets given Column to given pixel width. If new width is less than minWidth
15099 * width, sets to minWidth. Updates oColumn.width value.
15101 * @method setColumnWidth
15102 * @param oColumn {YAHOO.widget.Column} Column instance.
15103 * @param nWidth {Number} New width in pixels.
15105 setColumnWidth : function(oColumn, nWidth) {
15106 oColumn = this.getColumn(oColumn);
15108 this._storeScrollPositions();
15110 // Validate new width against minWidth
15111 if(lang.isNumber(nWidth)) {
15112 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
15115 oColumn.width = nWidth;
15117 // Resize the DOM elements
15118 this._setColumnWidth(oColumn, nWidth+"px");
15119 this._syncScroll();
15121 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
15123 // Unsets a width to auto-size
15124 else if(nWidth === null) {
15126 oColumn.width = nWidth;
15128 // Resize the DOM elements
15129 this._setColumnWidth(oColumn, "auto");
15130 this.validateColumnWidths(oColumn);
15131 this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
15134 // Bug 2339454: resize then sort misaligment
15135 this._clearTrTemplateEl();
15142 * Scrolls to given row or cell
15145 * @param to {YAHOO.widget.Record | HTMLElement } Itme to scroll to.
15147 scrollTo : function(to) {
15148 var td = this.getTdEl(to);
15150 this.clearScrollPositions();
15151 this.getBdContainerEl().scrollLeft = td.offsetLeft;
15152 this.getBdContainerEl().scrollTop = td.parentNode.offsetTop;
15155 var tr = this.getTrEl(to);
15157 this.clearScrollPositions();
15158 this.getBdContainerEl().scrollTop = tr.offsetTop;
15164 * Displays message within secondary TBODY.
15166 * @method showTableMessage
15167 * @param sHTML {String} (optional) Value for innerHTMlang.
15168 * @param sClassName {String} (optional) Classname.
15170 showTableMessage : function(sHTML, sClassName) {
15171 var elCell = this._elMsgTd;
15172 if(lang.isString(sHTML)) {
15173 elCell.firstChild.innerHTML = sHTML;
15175 if(lang.isString(sClassName)) {
15176 Dom.addClass(elCell.firstChild, sClassName);
15179 // Needed for SDT only
15180 var elThead = this.getTheadEl();
15181 var elTable = elThead.parentNode;
15182 var newWidth = elTable.offsetWidth;
15183 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
15185 this._elMsgTbody.style.display = "";
15187 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
15202 /////////////////////////////////////////////////////////////////////////////
15204 // Private Custom Event Handlers
15206 /////////////////////////////////////////////////////////////////////////////
15209 * Handles Column mutations
15211 * @method onColumnChange
15212 * @param oArgs {Object} Custom Event data.
15214 _onColumnChange : function(oArg) {
15215 // Figure out which Column changed
15216 var oColumn = (oArg.column) ? oArg.column :
15217 (oArg.editor) ? oArg.editor.column : null;
15218 this._storeScrollPositions();
15219 this.validateColumnWidths(oColumn);
15236 /////////////////////////////////////////////////////////////////////////////
15238 // Private DOM Event Handlers
15240 /////////////////////////////////////////////////////////////////////////////
15243 * Syncs scrolltop and scrollleft of all TABLEs.
15245 * @method _onScroll
15246 * @param e {HTMLEvent} The scroll event.
15247 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15250 _onScroll : function(e, oSelf) {
15251 oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft;
15253 if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
15254 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
15255 oSelf.cancelCellEditor();
15258 var elTarget = Ev.getTarget(e);
15259 var elTag = elTarget.nodeName.toLowerCase();
15260 oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
15264 * Handles keydown events on the THEAD element.
15266 * @method _onTheadKeydown
15267 * @param e {HTMLEvent} The key event.
15268 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15271 _onTheadKeydown : function(e, oSelf) {
15272 // If tabbing to next TH label link causes THEAD to scroll,
15273 // need to sync scrollLeft with TBODY
15274 if(Ev.getCharCode(e) === 9) {
15275 setTimeout(function() {
15276 if((oSelf instanceof SDT) && oSelf._sId) {
15277 oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft;
15282 var elTarget = Ev.getTarget(e);
15283 var elTag = elTarget.nodeName.toLowerCase();
15284 var bKeepBubbling = true;
15285 while(elTarget && (elTag != "table")) {
15291 // TODO: implement textareaKeyEvent
15294 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
15299 if(bKeepBubbling === false) {
15303 elTarget = elTarget.parentNode;
15305 elTag = elTarget.nodeName.toLowerCase();
15309 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
15316 * Fired when a fixed scrolling DataTable has a scroll.
15318 * @event tableScrollEvent
15319 * @param oArgs.event {HTMLEvent} The event object.
15320 * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
15321 * or the DataTable's TBODY element (everyone else).
15334 var lang = YAHOO.lang,
15336 widget = YAHOO.widget,
15342 DT = widget.DataTable;
15343 /****************************************************************************/
15344 /****************************************************************************/
15345 /****************************************************************************/
15348 * The BaseCellEditor class provides base functionality common to all inline cell
15349 * editors for a DataTable widget.
15351 * @namespace YAHOO.widget
15352 * @class BaseCellEditor
15353 * @uses YAHOO.util.EventProvider
15355 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
15356 * @param oConfigs {Object} (Optional) Object literal of configs.
15358 widget.BaseCellEditor = function(sType, oConfigs) {
15359 this._sId = this._sId || "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++;
15360 this._sType = sType;
15363 this._initConfigs(oConfigs);
15365 // Create Custom Events
15366 this._initEvents();
15372 var BCE = widget.BaseCellEditor;
15374 /////////////////////////////////////////////////////////////////////////////
15378 /////////////////////////////////////////////////////////////////////////////
15379 lang.augmentObject(BCE, {
15382 * Global instance counter.
15384 * @property CellEditor._nCount
15393 * Class applied to CellEditor container.
15395 * @property CellEditor.CLASS_CELLEDITOR
15398 * @default "yui-ceditor"
15400 CLASS_CELLEDITOR : "yui-ceditor"
15405 /////////////////////////////////////////////////////////////////////////////
15409 /////////////////////////////////////////////////////////////////////////////
15411 * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique
15412 * DOM ID strings and log messages.
15430 * DataTable instance.
15432 * @property _oDataTable
15433 * @type YAHOO.widget.DataTable
15436 _oDataTable : null,
15441 * @property _oColumn
15442 * @type YAHOO.widget.Column
15451 * @property _oRecord
15452 * @type YAHOO.widget.Record
15462 * @type HTMLElement
15469 * Container for inline editor.
15471 * @property _elContainer
15472 * @type HTMLElement
15475 _elContainer : null,
15478 * Reference to Cancel button, if available.
15480 * @property _elCancelBtn
15481 * @type HTMLElement
15485 _elCancelBtn : null,
15488 * Reference to Save button, if available.
15490 * @property _elSaveBtn
15491 * @type HTMLElement
15504 /////////////////////////////////////////////////////////////////////////////
15508 /////////////////////////////////////////////////////////////////////////////
15511 * Initialize configs.
15513 * @method _initConfigs
15516 _initConfigs : function(oConfigs) {
15517 // Object literal defines CellEditor configs
15518 if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
15519 for(var sConfig in oConfigs) {
15521 this[sConfig] = oConfigs[sConfig];
15528 * Initialize Custom Events.
15530 * @method _initEvents
15533 _initEvents : function() {
15534 this.createEvent("showEvent");
15535 this.createEvent("keydownEvent");
15536 this.createEvent("invalidDataEvent");
15537 this.createEvent("revertEvent");
15538 this.createEvent("saveEvent");
15539 this.createEvent("cancelEvent");
15540 this.createEvent("blurEvent");
15541 this.createEvent("blockEvent");
15542 this.createEvent("unblockEvent");
15557 /////////////////////////////////////////////////////////////////////////////
15559 // Public properties
15561 /////////////////////////////////////////////////////////////////////////////
15563 * Implementer defined function that can submit the input value to a server. This
15564 * function must accept the arguments fnCallback and oNewValue. When the submission
15565 * is complete, the function must also call fnCallback(bSuccess, oNewValue) to
15566 * finish the save routine in the CellEditor. This function can also be used to
15567 * perform extra validation or input value manipulation.
15569 * @property asyncSubmitter
15570 * @type HTMLFunction
15572 asyncSubmitter : null,
15583 * Default value in case Record data is undefined. NB: Null values will not trigger
15584 * the default value.
15586 * @property defaultValue
15590 defaultValue : null,
15593 * Validator function for input data, called from the DataTable instance scope,
15594 * receives the arguments (inputValue, currentValue, editorInstance) and returns
15595 * either the validated (or type-converted) value or undefined.
15597 * @property validator
15598 * @type HTMLFunction
15604 * If validation is enabled, resets input field of invalid data.
15606 * @property resetInvalidData
15610 resetInvalidData : true,
15613 * True if currently active.
15615 * @property isActive
15621 * Text to display on Save button.
15623 * @property LABEL_SAVE
15627 LABEL_SAVE : "Save",
15630 * Text to display on Cancel button.
15632 * @property LABEL_CANCEL
15634 * @default "Cancel"
15636 LABEL_CANCEL : "Cancel",
15639 * True if Save/Cancel buttons should not be displayed in the CellEditor.
15641 * @property disableBtns
15645 disableBtns : false,
15653 /////////////////////////////////////////////////////////////////////////////
15657 /////////////////////////////////////////////////////////////////////////////
15659 * CellEditor instance name, for logging.
15662 * @return {String} Unique name of the CellEditor instance.
15665 toString : function() {
15666 return "CellEditor instance " + this._sId;
15670 * CellEditor unique ID.
15673 * @return {String} Unique ID of the CellEditor instance.
15676 getId : function() {
15681 * Returns reference to associated DataTable instance.
15683 * @method getDataTable
15684 * @return {YAHOO.widget.DataTable} DataTable instance.
15687 getDataTable : function() {
15688 return this._oDataTable;
15692 * Returns reference to associated Column instance.
15694 * @method getColumn
15695 * @return {YAHOO.widget.Column} Column instance.
15698 getColumn : function() {
15699 return this._oColumn;
15703 * Returns reference to associated Record instance.
15705 * @method getRecord
15706 * @return {YAHOO.widget.Record} Record instance.
15709 getRecord : function() {
15710 return this._oRecord;
15716 * Returns reference to associated TD element.
15719 * @return {HTMLElement} TD element.
15722 getTdEl : function() {
15727 * Returns container element.
15729 * @method getContainerEl
15730 * @return {HTMLElement} Reference to container element.
15733 getContainerEl : function() {
15734 return this._elContainer;
15738 * Nulls out the entire CellEditor instance and related objects, removes attached
15739 * event listeners, and clears out DOM elements inside the container, removes
15740 * container from the DOM.
15744 destroy : function() {
15745 this.unsubscribeAll();
15747 // Column is late-binding in attach()
15748 var oColumn = this.getColumn();
15750 oColumn.editor = null;
15753 var elContainer = this.getContainerEl();
15754 Ev.purgeElement(elContainer, true);
15755 elContainer.parentNode.removeChild(elContainer);
15759 * Renders DOM elements and attaches event listeners.
15763 render : function() {
15764 if(this._elContainer) {
15765 YAHOO.util.Event.purgeElement(this._elContainer, true);
15766 this._elContainer.innerHTML = "";
15769 // Render Cell Editor container element as first child of body
15770 var elContainer = document.createElement("div");
15771 elContainer.id = this.getId() + "-container"; // Needed for tracking blur event
15772 elContainer.style.display = "none";
15773 elContainer.tabIndex = 0;
15774 elContainer.className = DT.CLASS_EDITOR;
15775 document.body.insertBefore(elContainer, document.body.firstChild);
15776 this._elContainer = elContainer;
15779 Ev.addListener(elContainer, "keydown", function(e, oSelf) {
15780 // ESC cancels Cell Editor
15781 if((e.keyCode == 27)) {
15782 var target = Ev.getTarget(e);
15783 // workaround for Mac FF3 bug that disabled clicks when ESC hit when
15784 // select is open. [bug 2273056]
15785 if (target.nodeName && target.nodeName.toLowerCase() === 'select') {
15790 // Pass through event
15791 oSelf.fireEvent("keydownEvent", {editor:this, event:e});
15796 // Show Save/Cancel buttons
15797 if(!this.disableBtns) {
15801 this.doAfterRender();
15805 * Renders Save/Cancel buttons.
15807 * @method renderBtns
15809 renderBtns : function() {
15811 var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div"));
15812 elBtnsDiv.className = DT.CLASS_BUTTON;
15815 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
15816 elSaveBtn.className = DT.CLASS_DEFAULT;
15817 elSaveBtn.innerHTML = this.LABEL_SAVE;
15818 Ev.addListener(elSaveBtn, "click", function(oArgs) {
15821 this._elSaveBtn = elSaveBtn;
15824 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
15825 elCancelBtn.innerHTML = this.LABEL_CANCEL;
15826 Ev.addListener(elCancelBtn, "click", function(oArgs) {
15829 this._elCancelBtn = elCancelBtn;
15833 * Attach CellEditor for a new interaction.
15836 * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance.
15837 * @param elCell {HTMLElement} Cell to edit.
15839 attach : function(oDataTable, elCell) {
15841 if(oDataTable instanceof YAHOO.widget.DataTable) {
15842 this._oDataTable = oDataTable;
15845 elCell = oDataTable.getTdEl(elCell);
15847 this._elTd = elCell;
15850 var oColumn = oDataTable.getColumn(elCell);
15852 this._oColumn = oColumn;
15855 var oRecord = oDataTable.getRecord(elCell);
15857 this._oRecord = oRecord;
15858 var value = oRecord.getData(this.getColumn().getField());
15859 this.value = (value !== undefined) ? value : this.defaultValue;
15869 * Moves container into position for display.
15873 move : function() {
15875 var elContainer = this.getContainerEl(),
15876 elTd = this.getTdEl(),
15877 x = Dom.getX(elTd),
15878 y = Dom.getY(elTd);
15880 //TODO: remove scrolling logic
15881 // SF doesn't get xy for cells in scrolling table
15882 // when tbody display is set to block
15883 if(isNaN(x) || isNaN(y)) {
15884 var elTbody = this.getDataTable().getTbodyEl();
15885 x = elTd.offsetLeft + // cell pos relative to table
15886 Dom.getX(elTbody.parentNode) - // plus table pos relative to document
15887 elTbody.scrollLeft; // minus tbody scroll
15888 y = elTd.offsetTop + // cell pos relative to table
15889 Dom.getY(elTbody.parentNode) - // plus table pos relative to document
15890 elTbody.scrollTop + // minus tbody scroll
15891 this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells
15894 elContainer.style.left = x + "px";
15895 elContainer.style.top = y + "px";
15899 * Displays CellEditor UI in the correct position.
15903 show : function() {
15905 this.isActive = true;
15906 this.getContainerEl().style.display = "";
15908 this.fireEvent("showEvent", {editor:this});
15916 block : function() {
15917 this.fireEvent("blockEvent", {editor:this});
15921 * Fires unblockEvent
15925 unblock : function() {
15926 this.fireEvent("unblockEvent", {editor:this});
15930 * Saves value of CellEditor and hides UI.
15934 save : function() {
15936 var inputValue = this.getInputValue();
15937 var validValue = inputValue;
15939 // Validate new value
15940 if(this.validator) {
15941 validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this);
15942 if(validValue === undefined ) {
15943 if(this.resetInvalidData) {
15946 this.fireEvent("invalidDataEvent",
15947 {editor:this, oldData:this.value, newData:inputValue});
15953 var finishSave = function(bSuccess, oNewValue) {
15954 var oOrigValue = oSelf.value;
15956 // Update new value
15957 oSelf.value = oNewValue;
15958 oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue);
15961 oSelf.getContainerEl().style.display = "none";
15962 oSelf.isActive = false;
15963 oSelf.getDataTable()._oCellEditor = null;
15965 oSelf.fireEvent("saveEvent",
15966 {editor:oSelf, oldData:oOrigValue, newData:oSelf.value});
15970 oSelf.fireEvent("revertEvent",
15971 {editor:oSelf, oldData:oOrigValue, newData:oNewValue});
15977 if(lang.isFunction(this.asyncSubmitter)) {
15978 this.asyncSubmitter.call(this, finishSave, validValue);
15981 finishSave(true, validValue);
15986 * Cancels CellEditor input and hides UI.
15990 cancel : function() {
15991 if(this.isActive) {
15992 this.getContainerEl().style.display = "none";
15993 this.isActive = false;
15994 this.getDataTable()._oCellEditor = null;
15995 this.fireEvent("cancelEvent", {editor:this});
16002 * Renders form elements.
16004 * @method renderForm
16006 renderForm : function() {
16007 // To be implemented by subclass
16011 * Access to add additional event listeners.
16013 * @method doAfterRender
16015 doAfterRender : function() {
16016 // To be implemented by subclass
16021 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16022 * to save input without them.
16024 * @method handleDisabledBtns
16026 handleDisabledBtns : function() {
16027 // To be implemented by subclass
16031 * Resets CellEditor UI to initial state.
16033 * @method resetForm
16035 resetForm : function() {
16036 // To be implemented by subclass
16040 * Sets focus in CellEditor.
16044 focus : function() {
16045 // To be implemented by subclass
16049 * Retrieves input value from CellEditor.
16051 * @method getInputValue
16053 getInputValue : function() {
16054 // To be implemented by subclass
16059 lang.augmentProto(BCE, util.EventProvider);
16062 /////////////////////////////////////////////////////////////////////////////
16066 /////////////////////////////////////////////////////////////////////////////
16069 * Fired when a CellEditor is shown.
16072 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16076 * Fired when a CellEditor has a keydown.
16078 * @event keydownEvent
16079 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16080 * @param oArgs.event {HTMLEvent} The event object.
16084 * Fired when a CellEditor input is reverted due to invalid data.
16086 * @event invalidDataEvent
16087 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16088 * @param oArgs.newData {Object} New data value from form input field.
16089 * @param oArgs.oldData {Object} Old data value.
16093 * Fired when a CellEditor input is reverted due to asyncSubmitter failure.
16095 * @event revertEvent
16096 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16097 * @param oArgs.newData {Object} New data value from form input field.
16098 * @param oArgs.oldData {Object} Old data value.
16102 * Fired when a CellEditor input is saved.
16105 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16106 * @param oArgs.newData {Object} New data value from form input field.
16107 * @param oArgs.oldData {Object} Old data value.
16111 * Fired when a CellEditor input is canceled.
16113 * @event cancelEvent
16114 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16118 * Fired when a CellEditor has a blur event.
16121 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16137 /****************************************************************************/
16138 /****************************************************************************/
16139 /****************************************************************************/
16142 * The CheckboxCellEditor class provides functionality for inline editing
16143 * DataTable cell data with checkboxes.
16145 * @namespace YAHOO.widget
16146 * @class CheckboxCellEditor
16147 * @extends YAHOO.widget.BaseCellEditor
16149 * @param oConfigs {Object} (Optional) Object literal of configs.
16151 widget.CheckboxCellEditor = function(oConfigs) {
16152 this._sId = "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16153 widget.CheckboxCellEditor.superclass.constructor.call(this, "checkbox", oConfigs);
16156 // CheckboxCellEditor extends BaseCellEditor
16157 lang.extend(widget.CheckboxCellEditor, BCE, {
16159 /////////////////////////////////////////////////////////////////////////////
16161 // CheckboxCellEditor public properties
16163 /////////////////////////////////////////////////////////////////////////////
16165 * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"])
16166 * or a an array of objects (e.g., [{label:"red", value:"#FF0000"},
16167 * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]).
16169 * @property checkboxOptions
16170 * @type String[] | Object[]
16172 checkboxOptions : null,
16175 * Reference to the checkbox elements.
16177 * @property checkboxes
16178 * @type HTMLElement[]
16183 * Array of checked values
16190 /////////////////////////////////////////////////////////////////////////////
16192 // CheckboxCellEditor public methods
16194 /////////////////////////////////////////////////////////////////////////////
16197 * Render a form with input(s) type=checkbox.
16199 * @method renderForm
16201 renderForm : function() {
16202 if(lang.isArray(this.checkboxOptions)) {
16203 var checkboxOption, checkboxValue, checkboxId, elLabel, j, len;
16205 // Create the checkbox buttons in an IE-friendly way...
16206 for(j=0,len=this.checkboxOptions.length; j<len; j++) {
16207 checkboxOption = this.checkboxOptions[j];
16208 checkboxValue = lang.isValue(checkboxOption.value) ?
16209 checkboxOption.value : checkboxOption;
16211 checkboxId = this.getId() + "-chk" + j;
16212 this.getContainerEl().innerHTML += "<input type=\"checkbox\"" +
16213 " id=\"" + checkboxId + "\"" + // Needed for label
16214 " value=\"" + checkboxValue + "\" />";
16216 // Create the labels in an IE-friendly way
16217 elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16218 elLabel.htmlFor = checkboxId;
16219 elLabel.innerHTML = lang.isValue(checkboxOption.label) ?
16220 checkboxOption.label : checkboxOption;
16223 // Store the reference to the checkbox elements
16224 var allCheckboxes = [];
16225 for(j=0; j<len; j++) {
16226 allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2];
16228 this.checkboxes = allCheckboxes;
16230 if(this.disableBtns) {
16231 this.handleDisabledBtns();
16239 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16240 * to save input without them.
16242 * @method handleDisabledBtns
16244 handleDisabledBtns : function() {
16245 Ev.addListener(this.getContainerEl(), "click", function(v){
16246 if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16254 * Resets CheckboxCellEditor UI to initial state.
16256 * @method resetForm
16258 resetForm : function() {
16259 // Normalize to array
16260 var originalValues = lang.isArray(this.value) ? this.value : [this.value];
16262 // Match checks to value
16263 for(var i=0, j=this.checkboxes.length; i<j; i++) {
16264 this.checkboxes[i].checked = false;
16265 for(var k=0, len=originalValues.length; k<len; k++) {
16266 if(this.checkboxes[i].value === originalValues[k]) {
16267 this.checkboxes[i].checked = true;
16274 * Sets focus in CheckboxCellEditor.
16278 focus : function() {
16279 this.checkboxes[0].focus();
16283 * Retrieves input value from CheckboxCellEditor.
16285 * @method getInputValue
16287 getInputValue : function() {
16288 var checkedValues = [];
16289 for(var i=0, j=this.checkboxes.length; i<j; i++) {
16290 if(this.checkboxes[i].checked) {
16291 checkedValues[checkedValues.length] = this.checkboxes[i].value;
16294 return checkedValues;
16299 // Copy static members to CheckboxCellEditor class
16300 lang.augmentObject(widget.CheckboxCellEditor, BCE);
16309 /****************************************************************************/
16310 /****************************************************************************/
16311 /****************************************************************************/
16314 * The DataCellEditor class provides functionality for inline editing
16315 * DataTable cell data with a YUI Calendar.
16317 * @namespace YAHOO.widget
16318 * @class DateCellEditor
16319 * @extends YAHOO.widget.BaseCellEditor
16321 * @param oConfigs {Object} (Optional) Object literal of configs.
16323 widget.DateCellEditor = function(oConfigs) {
16324 this._sId = "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16325 widget.DateCellEditor.superclass.constructor.call(this, "date", oConfigs);
16328 // CheckboxCellEditor extends BaseCellEditor
16329 lang.extend(widget.DateCellEditor, BCE, {
16331 /////////////////////////////////////////////////////////////////////////////
16333 // DateCellEditor public properties
16335 /////////////////////////////////////////////////////////////////////////////
16337 * Reference to Calendar instance.
16339 * @property calendar
16340 * @type YAHOO.widget.Calendar
16345 * Configs for the calendar instance, to be passed to Calendar constructor.
16347 * @property calendarOptions
16350 calendarOptions : null,
16355 * @property defaultValue
16357 * @default new Date()
16359 defaultValue : new Date(),
16362 /////////////////////////////////////////////////////////////////////////////
16364 // DateCellEditor public methods
16366 /////////////////////////////////////////////////////////////////////////////
16369 * Render a Calendar.
16371 * @method renderForm
16373 renderForm : function() {
16375 if(YAHOO.widget.Calendar) {
16376 var calContainer = this.getContainerEl().appendChild(document.createElement("div"));
16377 calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor
16379 new YAHOO.widget.Calendar(this.getId() + "-date",
16380 calContainer.id, this.calendarOptions);
16382 calContainer.style.cssFloat = "none";
16385 var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div"));
16386 calFloatClearer.style.clear = "both";
16389 this.calendar = calendar;
16391 if(this.disableBtns) {
16392 this.handleDisabledBtns();
16401 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16402 * to save input without them.
16404 * @method handleDisabledBtns
16406 handleDisabledBtns : function() {
16407 this.calendar.selectEvent.subscribe(function(v){
16414 * Resets DateCellEditor UI to initial state.
16416 * @method resetForm
16418 resetForm : function() {
16419 var value = this.value;
16420 var selectedValue = (value.getMonth()+1)+"/"+value.getDate()+"/"+value.getFullYear();
16421 this.calendar.cfg.setProperty("selected",selectedValue,false);
16422 this.calendar.render();
16426 * Sets focus in DateCellEditor.
16430 focus : function() {
16431 // To be impmlemented by subclass
16435 * Retrieves input value from DateCellEditor.
16437 * @method getInputValue
16439 getInputValue : function() {
16440 return this.calendar.getSelectedDates()[0];
16445 // Copy static members to DateCellEditor class
16446 lang.augmentObject(widget.DateCellEditor, BCE);
16456 /****************************************************************************/
16457 /****************************************************************************/
16458 /****************************************************************************/
16461 * The DropdownCellEditor class provides functionality for inline editing
16462 * DataTable cell data a SELECT element.
16464 * @namespace YAHOO.widget
16465 * @class DropdownCellEditor
16466 * @extends YAHOO.widget.BaseCellEditor
16468 * @param oConfigs {Object} (Optional) Object literal of configs.
16470 widget.DropdownCellEditor = function(oConfigs) {
16471 this._sId = "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16472 widget.DropdownCellEditor.superclass.constructor.call(this, "dropdown", oConfigs);
16475 // DropdownCellEditor extends BaseCellEditor
16476 lang.extend(widget.DropdownCellEditor, BCE, {
16478 /////////////////////////////////////////////////////////////////////////////
16480 // DropdownCellEditor public properties
16482 /////////////////////////////////////////////////////////////////////////////
16484 * Array of dropdown values. Can either be a simple array (e.g.,
16485 * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g.,
16486 * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
16487 * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]).
16489 * @property dropdownOptions
16490 * @type String[] | Object[]
16492 dropdownOptions : null,
16495 * Reference to Dropdown element.
16497 * @property dropdown
16498 * @type HTMLElement
16503 * Enables multi-select.
16505 * @property multiple
16511 * Specifies number of visible options.
16518 /////////////////////////////////////////////////////////////////////////////
16520 // DropdownCellEditor public methods
16522 /////////////////////////////////////////////////////////////////////////////
16525 * Render a form with select element.
16527 * @method renderForm
16529 renderForm : function() {
16530 var elDropdown = this.getContainerEl().appendChild(document.createElement("select"));
16531 elDropdown.style.zoom = 1;
16532 if(this.multiple) {
16533 elDropdown.multiple = "multiple";
16535 if(lang.isNumber(this.size)) {
16536 elDropdown.size = this.size;
16538 this.dropdown = elDropdown;
16540 if(lang.isArray(this.dropdownOptions)) {
16541 var dropdownOption, elOption;
16542 for(var i=0, j=this.dropdownOptions.length; i<j; i++) {
16543 dropdownOption = this.dropdownOptions[i];
16544 elOption = document.createElement("option");
16545 elOption.value = (lang.isValue(dropdownOption.value)) ?
16546 dropdownOption.value : dropdownOption;
16547 elOption.innerHTML = (lang.isValue(dropdownOption.label)) ?
16548 dropdownOption.label : dropdownOption;
16549 elOption = elDropdown.appendChild(elOption);
16552 if(this.disableBtns) {
16553 this.handleDisabledBtns();
16559 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16560 * to save input without them.
16562 * @method handleDisabledBtns
16564 handleDisabledBtns : function() {
16565 // Save on blur for multi-select
16566 if(this.multiple) {
16567 Ev.addListener(this.dropdown, "blur", function(v){
16572 // Save on change for single-select
16574 Ev.addListener(this.dropdown, "change", function(v){
16582 * Resets DropdownCellEditor UI to initial state.
16584 * @method resetForm
16586 resetForm : function() {
16587 var allOptions = this.dropdown.options,
16588 i=0, j=allOptions.length;
16590 // Look for multi-select selections
16591 if(lang.isArray(this.value)) {
16592 var allValues = this.value,
16593 m=0, n=allValues.length,
16595 // Reset all selections and stash options in a value hash
16597 allOptions[i].selected = false;
16598 hash[allOptions[i].value] = allOptions[i];
16601 if(hash[allValues[m]]) {
16602 hash[allValues[m]].selected = true;
16606 // Only need to look for a single selection
16609 if(this.value === allOptions[i].value) {
16610 allOptions[i].selected = true;
16617 * Sets focus in DropdownCellEditor.
16621 focus : function() {
16622 this.getDataTable()._focusEl(this.dropdown);
16626 * Retrieves input value from DropdownCellEditor.
16628 * @method getInputValue
16630 getInputValue : function() {
16631 var allOptions = this.dropdown.options;
16633 // Look for multiple selections
16634 if(this.multiple) {
16636 i=0, j=allOptions.length;
16638 if(allOptions[i].selected) {
16639 values.push(allOptions[i].value);
16644 // Only need to look for single selection
16646 return allOptions[allOptions.selectedIndex].value;
16652 // Copy static members to DropdownCellEditor class
16653 lang.augmentObject(widget.DropdownCellEditor, BCE);
16660 /****************************************************************************/
16661 /****************************************************************************/
16662 /****************************************************************************/
16665 * The RadioCellEditor class provides functionality for inline editing
16666 * DataTable cell data with radio buttons.
16668 * @namespace YAHOO.widget
16669 * @class RadioCellEditor
16670 * @extends YAHOO.widget.BaseCellEditor
16672 * @param oConfigs {Object} (Optional) Object literal of configs.
16674 widget.RadioCellEditor = function(oConfigs) {
16675 this._sId = "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16676 widget.RadioCellEditor.superclass.constructor.call(this, "radio", oConfigs);
16679 // RadioCellEditor extends BaseCellEditor
16680 lang.extend(widget.RadioCellEditor, BCE, {
16682 /////////////////////////////////////////////////////////////////////////////
16684 // RadioCellEditor public properties
16686 /////////////////////////////////////////////////////////////////////////////
16688 * Reference to radio elements.
16691 * @type HTMLElement[]
16696 * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"])
16697 * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1},
16698 * {label:"maybe", value:0}]).
16700 * @property radioOptions
16701 * @type String[] | Object[]
16703 radioOptions : null,
16705 /////////////////////////////////////////////////////////////////////////////
16707 // RadioCellEditor public methods
16709 /////////////////////////////////////////////////////////////////////////////
16712 * Render a form with input(s) type=radio.
16714 * @method renderForm
16716 renderForm : function() {
16717 if(lang.isArray(this.radioOptions)) {
16718 var radioOption, radioValue, radioId, elLabel;
16720 // Create the radio buttons in an IE-friendly way
16721 for(var i=0, len=this.radioOptions.length; i<len; i++) {
16722 radioOption = this.radioOptions[i];
16723 radioValue = lang.isValue(radioOption.value) ?
16724 radioOption.value : radioOption;
16725 radioId = this.getId() + "-radio" + i;
16726 this.getContainerEl().innerHTML += "<input type=\"radio\"" +
16727 " name=\"" + this.getId() + "\"" +
16728 " value=\"" + radioValue + "\"" +
16729 " id=\"" + radioId + "\" />"; // Needed for label
16731 // Create the labels in an IE-friendly way
16732 elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16733 elLabel.htmlFor = radioId;
16734 elLabel.innerHTML = (lang.isValue(radioOption.label)) ?
16735 radioOption.label : radioOption;
16738 // Store the reference to the checkbox elements
16739 var allRadios = [],
16741 for(var j=0; j<len; j++) {
16742 elRadio = this.getContainerEl().childNodes[j*2];
16743 allRadios[allRadios.length] = elRadio;
16745 this.radios = allRadios;
16747 if(this.disableBtns) {
16748 this.handleDisabledBtns();
16756 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16757 * to save input without them.
16759 * @method handleDisabledBtns
16761 handleDisabledBtns : function() {
16762 Ev.addListener(this.getContainerEl(), "click", function(v){
16763 if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16771 * Resets RadioCellEditor UI to initial state.
16773 * @method resetForm
16775 resetForm : function() {
16776 for(var i=0, j=this.radios.length; i<j; i++) {
16777 var elRadio = this.radios[i];
16778 if(this.value === elRadio.value) {
16779 elRadio.checked = true;
16786 * Sets focus in RadioCellEditor.
16790 focus : function() {
16791 for(var i=0, j=this.radios.length; i<j; i++) {
16792 if(this.radios[i].checked) {
16793 this.radios[i].focus();
16800 * Retrieves input value from RadioCellEditor.
16802 * @method getInputValue
16804 getInputValue : function() {
16805 for(var i=0, j=this.radios.length; i<j; i++) {
16806 if(this.radios[i].checked) {
16807 return this.radios[i].value;
16814 // Copy static members to RadioCellEditor class
16815 lang.augmentObject(widget.RadioCellEditor, BCE);
16822 /****************************************************************************/
16823 /****************************************************************************/
16824 /****************************************************************************/
16827 * The TextareaCellEditor class provides functionality for inline editing
16828 * DataTable cell data with a TEXTAREA element.
16830 * @namespace YAHOO.widget
16831 * @class TextareaCellEditor
16832 * @extends YAHOO.widget.BaseCellEditor
16834 * @param oConfigs {Object} (Optional) Object literal of configs.
16836 widget.TextareaCellEditor = function(oConfigs) {
16837 this._sId = "yui-textareaceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16838 widget.TextareaCellEditor.superclass.constructor.call(this, "textarea", oConfigs);
16841 // TextareaCellEditor extends BaseCellEditor
16842 lang.extend(widget.TextareaCellEditor, BCE, {
16844 /////////////////////////////////////////////////////////////////////////////
16846 // TextareaCellEditor public properties
16848 /////////////////////////////////////////////////////////////////////////////
16850 * Reference to textarea element.
16852 * @property textarea
16853 * @type HTMLElement
16858 /////////////////////////////////////////////////////////////////////////////
16860 // TextareaCellEditor public methods
16862 /////////////////////////////////////////////////////////////////////////////
16865 * Render a form with textarea.
16867 * @method renderForm
16869 renderForm : function() {
16870 var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea"));
16871 this.textarea = elTextarea;
16873 if(this.disableBtns) {
16874 this.handleDisabledBtns();
16879 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16880 * to save input without them.
16882 * @method handleDisabledBtns
16884 handleDisabledBtns : function() {
16885 Ev.addListener(this.textarea, "blur", function(v){
16892 * Moves TextareaCellEditor UI to a cell.
16896 move : function() {
16897 this.textarea.style.width = this.getTdEl().offsetWidth + "px";
16898 this.textarea.style.height = "3em";
16899 YAHOO.widget.TextareaCellEditor.superclass.move.call(this);
16903 * Resets TextareaCellEditor UI to initial state.
16905 * @method resetForm
16907 resetForm : function() {
16908 this.textarea.value = this.value;
16912 * Sets focus in TextareaCellEditor.
16916 focus : function() {
16917 // Bug 2303181, Bug 2263600
16918 this.getDataTable()._focusEl(this.textarea);
16919 this.textarea.select();
16923 * Retrieves input value from TextareaCellEditor.
16925 * @method getInputValue
16927 getInputValue : function() {
16928 return this.textarea.value;
16933 // Copy static members to TextareaCellEditor class
16934 lang.augmentObject(widget.TextareaCellEditor, BCE);
16944 /****************************************************************************/
16945 /****************************************************************************/
16946 /****************************************************************************/
16949 * The TextboxCellEditor class provides functionality for inline editing
16950 * DataTable cell data with an INPUT TYPE=TEXT element.
16952 * @namespace YAHOO.widget
16953 * @class TextboxCellEditor
16954 * @extends YAHOO.widget.BaseCellEditor
16956 * @param oConfigs {Object} (Optional) Object literal of configs.
16958 widget.TextboxCellEditor = function(oConfigs) {
16959 this._sId = "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16960 widget.TextboxCellEditor.superclass.constructor.call(this, "textbox", oConfigs);
16963 // TextboxCellEditor extends BaseCellEditor
16964 lang.extend(widget.TextboxCellEditor, BCE, {
16966 /////////////////////////////////////////////////////////////////////////////
16968 // TextboxCellEditor public properties
16970 /////////////////////////////////////////////////////////////////////////////
16972 * Reference to the textbox element.
16974 * @property textbox
16978 /////////////////////////////////////////////////////////////////////////////
16980 // TextboxCellEditor public methods
16982 /////////////////////////////////////////////////////////////////////////////
16985 * Render a form with input type=text.
16987 * @method renderForm
16989 renderForm : function() {
16991 // Bug 1802582: SF3/Mac needs a form element wrapping the input
16992 if(ua.webkit>420) {
16993 elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));
16996 elTextbox = this.getContainerEl().appendChild(document.createElement("input"));
16998 elTextbox.type = "text";
16999 this.textbox = elTextbox;
17001 // Save on enter by default
17002 // Bug: 1802582 Set up a listener on each textbox to track on keypress
17003 // since SF/OP can't preventDefault on keydown
17004 Ev.addListener(elTextbox, "keypress", function(v){
17005 if((v.keyCode === 13)) {
17006 // Prevent form submit
17007 YAHOO.util.Event.preventDefault(v);
17012 if(this.disableBtns) {
17013 // By default this is no-op since enter saves by default
17014 this.handleDisabledBtns();
17019 * Moves TextboxCellEditor UI to a cell.
17023 move : function() {
17024 this.textbox.style.width = this.getTdEl().offsetWidth + "px";
17025 widget.TextboxCellEditor.superclass.move.call(this);
17029 * Resets TextboxCellEditor UI to initial state.
17031 * @method resetForm
17033 resetForm : function() {
17034 this.textbox.value = lang.isValue(this.value) ? this.value.toString() : "";
17038 * Sets focus in TextboxCellEditor.
17042 focus : function() {
17043 // Bug 2303181, Bug 2263600
17044 this.getDataTable()._focusEl(this.textbox);
17045 this.textbox.select();
17049 * Returns new value for TextboxCellEditor.
17051 * @method getInputValue
17053 getInputValue : function() {
17054 return this.textbox.value;
17059 // Copy static members to TextboxCellEditor class
17060 lang.augmentObject(widget.TextboxCellEditor, BCE);
17068 /////////////////////////////////////////////////////////////////////////////
17070 // DataTable extension
17072 /////////////////////////////////////////////////////////////////////////////
17075 * CellEditor subclasses.
17076 * @property DataTable.Editors
17081 checkbox : widget.CheckboxCellEditor,
17082 "date" : widget.DateCellEditor,
17083 dropdown : widget.DropdownCellEditor,
17084 radio : widget.RadioCellEditor,
17085 textarea : widget.TextareaCellEditor,
17086 textbox : widget.TextboxCellEditor
17089 /****************************************************************************/
17090 /****************************************************************************/
17091 /****************************************************************************/
17094 * Factory class for instantiating a BaseCellEditor subclass.
17096 * @namespace YAHOO.widget
17097 * @class CellEditor
17098 * @extends YAHOO.widget.BaseCellEditor
17100 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
17101 * @param oConfigs {Object} (Optional) Object literal of configs.
17103 widget.CellEditor = function(sType, oConfigs) {
17104 // Point to one of the subclasses
17105 if(sType && DT.Editors[sType]) {
17106 lang.augmentObject(BCE, DT.Editors[sType]);
17107 return new DT.Editors[sType](oConfigs);
17110 return new BCE(null, oConfigs);
17114 var CE = widget.CellEditor;
17116 // Copy static members to CellEditor class
17117 lang.augmentObject(CE, BCE);
17122 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.8.0r4", build: "2449"});