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++;
196 YAHOO.log("ColumnSet initialized", "info", this.toString());
199 /////////////////////////////////////////////////////////////////////////////
201 // Private member variables
203 /////////////////////////////////////////////////////////////////////////////
206 * Internal class variable to index multiple ColumnSet instances.
208 * @property ColumnSet._nCount
213 YAHOO.widget.ColumnSet._nCount = 0;
215 YAHOO.widget.ColumnSet.prototype = {
217 * Unique instance name.
226 * Array of object literal Column definitions passed to the constructor.
228 * @property _aDefinitions
232 _aDefinitions : null,
234 /////////////////////////////////////////////////////////////////////////////
236 // Public member variables
238 /////////////////////////////////////////////////////////////////////////////
241 * Top-down tree representation of Column hierarchy.
244 * @type YAHOO.widget.Column[]
249 * Flattened representation of all Columns.
252 * @type YAHOO.widget.Column[]
258 * Array of Columns that map one-to-one to a table column.
261 * @type YAHOO.widget.Column[]
267 * ID index of nested parent hierarchies for HEADERS accessibility attribute.
275 /////////////////////////////////////////////////////////////////////////////
279 /////////////////////////////////////////////////////////////////////////////
282 * Initializes ColumnSet instance with data from Column definitions.
285 * @param aDefinitions {Object[]} Array of object literals that define cells in
290 _init : function(aDefinitions) {
291 // DOM tree representation of all Columns
293 // Flat representation of all Columns
295 // Flat representation of only Columns that are meant to display data
297 // Array of HEADERS attribute values for all keys in the "keys" array
300 // Tracks current node list depth being tracked
303 // Internal recursive function to define Column instances
304 var parseColumns = function(nodeList, parent) {
308 // Create corresponding tree node if not already there for this depth
309 if(!tree[nodeDepth]) {
310 tree[nodeDepth] = [];
314 // Parse each node at this depth for attributes and any children
315 for(var j=0; j<nodeList.length; j++) {
316 var currentNode = nodeList[j];
318 // Instantiate a new Column for each node
319 var oColumn = new YAHOO.widget.Column(currentNode);
321 // Cross-reference Column ID back to the original object literal definition
322 currentNode.yuiColumnId = oColumn._sId;
324 // Add the new Column to the flat list
327 // Assign its parent as an attribute, if applicable
329 oColumn._oParent = parent;
332 // The Column has descendants
333 if(YAHOO.lang.isArray(currentNode.children)) {
334 oColumn.children = currentNode.children;
336 // Determine COLSPAN value for this Column
337 var terminalChildNodes = 0;
338 var countTerminalChildNodes = function(ancestor) {
339 var descendants = ancestor.children;
340 // Drill down each branch and count terminal nodes
341 for(var k=0; k<descendants.length; k++) {
342 // Keep drilling down
343 if(YAHOO.lang.isArray(descendants[k].children)) {
344 countTerminalChildNodes(descendants[k]);
346 // Reached branch terminus
348 terminalChildNodes++;
352 countTerminalChildNodes(currentNode);
353 oColumn._nColspan = terminalChildNodes;
355 // Cascade certain properties to children if not defined on their own
356 var currentChildren = currentNode.children;
357 for(var k=0; k<currentChildren.length; k++) {
358 var child = currentChildren[k];
359 if(oColumn.className && (child.className === undefined)) {
360 child.className = oColumn.className;
362 if(oColumn.editor && (child.editor === undefined)) {
363 child.editor = oColumn.editor;
366 if(oColumn.editorOptions && (child.editorOptions === undefined)) {
367 child.editorOptions = oColumn.editorOptions;
369 if(oColumn.formatter && (child.formatter === undefined)) {
370 child.formatter = oColumn.formatter;
372 if(oColumn.resizeable && (child.resizeable === undefined)) {
373 child.resizeable = oColumn.resizeable;
375 if(oColumn.sortable && (child.sortable === undefined)) {
376 child.sortable = oColumn.sortable;
381 if(oColumn.width && (child.width === undefined)) {
382 child.width = oColumn.width;
384 if(oColumn.minWidth && (child.minWidth === undefined)) {
385 child.minWidth = oColumn.minWidth;
387 if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) {
388 child.maxAutoWidth = oColumn.maxAutoWidth;
390 // Backward compatibility
391 if(oColumn.type && (child.type === undefined)) {
392 child.type = oColumn.type;
394 if(oColumn.type && !oColumn.formatter) {
395 YAHOO.log("The property type has been" +
396 " deprecated in favor of formatter", "warn", oColumn.toString());
397 oColumn.formatter = oColumn.type;
399 if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
400 YAHOO.log("The property text has been" +
401 " deprecated in favor of label", "warn", oColumn.toString());
402 oColumn.label = oColumn.text;
405 YAHOO.log("The property parser is no longer supported",
406 "warn", this.toString());
408 if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
409 (oColumn.sortOptions.descFunction))) {
410 YAHOO.log("The properties sortOptions.ascFunction and " +
411 " sortOptions.descFunction have been deprecated in favor " +
412 " of sortOptions.sortFunction", "warn", oColumn.toString());
416 // The children themselves must also be parsed for Column instances
417 if(!tree[nodeDepth+1]) {
418 tree[nodeDepth+1] = [];
420 parseColumns(currentChildren, oColumn);
422 // This Column does not have any children
424 oColumn._nKeyIndex = keys.length;
425 oColumn._nColspan = 1;
429 // Add the Column to the top-down tree
430 tree[nodeDepth].push(oColumn);
435 // Parse out Column instances from the array of object literals
436 if(YAHOO.lang.isArray(aDefinitions)) {
437 parseColumns(aDefinitions);
440 this._aDefinitions = aDefinitions;
443 YAHOO.log("Could not initialize ColumnSet due to invalid definitions","error");
449 // Determine ROWSPAN value for each Column in the tree
450 var parseTreeForRowspan = function(tree) {
455 // Calculate the max depth of descendants for this row
456 var countMaxRowDepth = function(row, tmpRowDepth) {
457 tmpRowDepth = tmpRowDepth || 1;
459 for(var n=0; n<row.length; n++) {
461 // Column has children, so keep counting
462 if(YAHOO.lang.isArray(col.children)) {
464 countMaxRowDepth(col.children, tmpRowDepth);
467 // No children, is it the max depth?
469 if(tmpRowDepth > maxRowDepth) {
470 maxRowDepth = tmpRowDepth;
477 // Count max row depth for each row
478 for(var m=0; m<tree.length; m++) {
479 currentRow = tree[m];
480 countMaxRowDepth(currentRow);
482 // Assign the right ROWSPAN values to each Column in the row
483 for(var p=0; p<currentRow.length; p++) {
484 currentColumn = currentRow[p];
485 if(!YAHOO.lang.isArray(currentColumn.children)) {
486 currentColumn._nRowspan = maxRowDepth;
489 currentColumn._nRowspan = 1;
493 // Reset counter for next row
497 parseTreeForRowspan(tree);
499 // Store tree index values
500 for(i=0; i<tree[0].length; i++) {
501 tree[0][i]._nTreeIndex = i;
504 // Store header relationships in an array for HEADERS attribute
505 var recurseAncestorsForHeaders = function(i, oColumn) {
506 headers[i].push(oColumn.getSanitizedKey());
507 if(oColumn._oParent) {
508 recurseAncestorsForHeaders(i, oColumn._oParent);
511 for(i=0; i<keys.length; i++) {
513 recurseAncestorsForHeaders(i, keys[i]);
514 headers[i] = headers[i].reverse();
517 // Save to the ColumnSet instance
521 this.headers = headers;
524 /////////////////////////////////////////////////////////////////////////////
528 /////////////////////////////////////////////////////////////////////////////
531 * Returns unique name of the ColumnSet instance.
534 * @return {String} Unique name of the ColumnSet instance.
542 * ColumnSet instance name, for logging.
545 * @return {String} Unique name of the ColumnSet instance.
548 toString : function() {
549 return "ColumnSet instance " + this._sId;
553 * Public accessor to the definitions array.
555 * @method getDefinitions
556 * @return {Object[]} Array of object literal Column definitions.
559 getDefinitions : function() {
560 var aDefinitions = this._aDefinitions;
562 // Internal recursive function to define Column instances
563 var parseColumns = function(nodeList, oSelf) {
564 // Parse each node at this depth for attributes and any children
565 for(var j=0; j<nodeList.length; j++) {
566 var currentNode = nodeList[j];
568 // Get the Column for each node
569 var oColumn = oSelf.getColumnById(currentNode.yuiColumnId);
572 // Update the current values
573 var oDefinition = oColumn.getDefinition();
574 for(var name in oDefinition) {
575 if(YAHOO.lang.hasOwnProperty(oDefinition, name)) {
576 currentNode[name] = oDefinition[name];
581 // The Column has descendants
582 if(YAHOO.lang.isArray(currentNode.children)) {
583 // The children themselves must also be parsed for Column instances
584 parseColumns(currentNode.children, oSelf);
589 parseColumns(aDefinitions, this);
590 this._aDefinitions = aDefinitions;
595 * Returns Column instance with given ID.
597 * @method getColumnById
598 * @param column {String} Column ID.
599 * @return {YAHOO.widget.Column} Column instance.
602 getColumnById : function(column) {
603 if(YAHOO.lang.isString(column)) {
604 var allColumns = this.flat;
605 for(var i=allColumns.length-1; i>-1; i--) {
606 if(allColumns[i]._sId === column) {
607 return allColumns[i];
615 * Returns Column instance with given key or ColumnSet key index.
618 * @param column {String | Number} Column key or ColumnSet key index.
619 * @return {YAHOO.widget.Column} Column instance.
622 getColumn : function(column) {
623 if(YAHOO.lang.isNumber(column) && this.keys[column]) {
624 return this.keys[column];
626 else if(YAHOO.lang.isString(column)) {
627 var allColumns = this.flat;
629 for(var i=0; i<allColumns.length; i++) {
630 if(allColumns[i].key === column) {
631 aColumns.push(allColumns[i]);
634 if(aColumns.length === 1) {
637 else if(aColumns.length > 1) {
645 * Public accessor returns array of given Column's desendants (if any), including itself.
647 * @method getDescendants
648 * @parem {YAHOO.widget.Column} Column instance.
649 * @return {Array} Array including the Column itself and all descendants (if any).
651 getDescendants : function(oColumn) {
653 var allDescendants = [];
656 // Recursive function to loop thru all children
657 var parse = function(oParent) {
658 allDescendants.push(oParent);
659 // This Column has children
660 if(oParent.children) {
661 for(i=0; i<oParent.children.length; i++) {
662 parse(oSelf.getColumn(oParent.children[i].key));
668 return allDescendants;
672 /****************************************************************************/
673 /****************************************************************************/
674 /****************************************************************************/
677 * The Column class defines and manages attributes of DataTable Columns
679 * @namespace YAHOO.widget
682 * @param oConfigs {Object} Object literal of definitions.
684 YAHOO.widget.Column = function(oConfigs) {
685 this._sId = "yui-col" + YAHOO.widget.Column._nCount;
687 // Object literal defines Column attributes
688 if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
689 for(var sConfig in oConfigs) {
691 this[sConfig] = oConfigs[sConfig];
696 // Assign a key if not found
697 if(!YAHOO.lang.isValue(this.key)) {
698 this.key = "yui-dt-col" + YAHOO.widget.Column._nCount;
701 // Assign a field if not found, defaults to key
702 if(!YAHOO.lang.isValue(this.field)) {
703 this.field = this.key;
707 YAHOO.widget.Column._nCount++;
709 // Backward compatibility
710 if(this.width && !YAHOO.lang.isNumber(this.width)) {
712 YAHOO.log("The Column property width must be a number", "warn", this.toString());
714 if(this.editor && YAHOO.lang.isString(this.editor)) {
715 this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions);
716 YAHOO.log("The Column property editor must be an instance of YAHOO.widget.CellEditor", "warn", this.toString());
720 /////////////////////////////////////////////////////////////////////////////
722 // Private member variables
724 /////////////////////////////////////////////////////////////////////////////
726 YAHOO.lang.augmentObject(YAHOO.widget.Column, {
728 * Internal class variable to index multiple Column instances.
730 * @property Column._nCount
737 formatCheckbox : function(elCell, oRecord, oColumn, oData) {
738 YAHOO.log("The method YAHOO.widget.Column.formatCheckbox() has been" +
739 " deprecated in favor of YAHOO.widget.DataTable.formatCheckbox()", "warn",
740 "YAHOO.widget.Column.formatCheckbox");
741 YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
744 formatCurrency : function(elCell, oRecord, oColumn, oData) {
745 YAHOO.log("The method YAHOO.widget.Column.formatCurrency() has been" +
746 " deprecated in favor of YAHOO.widget.DataTable.formatCurrency()", "warn",
747 "YAHOO.widget.Column.formatCurrency");
748 YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
751 formatDate : function(elCell, oRecord, oColumn, oData) {
752 YAHOO.log("The method YAHOO.widget.Column.formatDate() has been" +
753 " deprecated in favor of YAHOO.widget.DataTable.formatDate()", "warn",
754 "YAHOO.widget.Column.formatDate");
755 YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
758 formatEmail : function(elCell, oRecord, oColumn, oData) {
759 YAHOO.log("The method YAHOO.widget.Column.formatEmail() has been" +
760 " deprecated in favor of YAHOO.widget.DataTable.formatEmail()", "warn",
761 "YAHOO.widget.Column.formatEmail");
762 YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
765 formatLink : function(elCell, oRecord, oColumn, oData) {
766 YAHOO.log("The method YAHOO.widget.Column.formatLink() has been" +
767 " deprecated in favor of YAHOO.widget.DataTable.formatLink()", "warn",
768 "YAHOO.widget.Column.formatLink");
769 YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
772 formatNumber : function(elCell, oRecord, oColumn, oData) {
773 YAHOO.log("The method YAHOO.widget.Column.formatNumber() has been" +
774 " deprecated in favor of YAHOO.widget.DataTable.formatNumber()", "warn",
775 "YAHOO.widget.Column.formatNumber");
776 YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
779 formatSelect : function(elCell, oRecord, oColumn, oData) {
780 YAHOO.log("The method YAHOO.widget.Column.formatSelect() has been" +
781 " deprecated in favor of YAHOO.widget.DataTable.formatDropdown()", "warn",
782 "YAHOO.widget.Column.formatSelect");
783 YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
787 YAHOO.widget.Column.prototype = {
789 * Unique String identifier assigned at instantiation.
798 * Reference to Column's current position index within its ColumnSet's keys
799 * array, if applicable. This property only applies to non-nested and bottom-
800 * level child Columns.
802 * @property _nKeyIndex
809 * Reference to Column's current position index within its ColumnSet's tree
810 * array, if applicable. This property only applies to non-nested and top-
811 * level parent Columns.
813 * @property _nTreeIndex
820 * Number of table cells the Column spans.
822 * @property _nColspan
829 * Number of table rows the Column spans.
831 * @property _nRowspan
838 * Column's parent Column instance, or null.
841 * @type YAHOO.widget.Column
847 * The DOM reference to the associated TH element.
856 * The DOM reference to the associated TH element's liner DIV element.
858 * @property _elThLiner
865 * The DOM reference to the associated TH element's label SPAN element.
867 * @property _elThLabel
874 * The DOM reference to the associated resizerelement (if any).
876 * @property _elResizer
883 * Internal width tracker.
892 * For unreg() purposes, a reference to the Column's DragDrop instance.
895 * @type YAHOO.util.DragDrop
901 * For unreg() purposes, a reference to the Column resizer's DragDrop instance.
903 * @property _ddResizer
904 * @type YAHOO.util.DragDrop
909 /////////////////////////////////////////////////////////////////////////////
911 // Public member variables
913 /////////////////////////////////////////////////////////////////////////////
916 * Unique name, required.
924 * Associated database field, or null.
932 * Text or HTML for display as Column's label in the TH element.
940 * Column head cell ABBR for accessibility.
948 * Array of object literals that define children (nested headers) of a Column.
956 * Column width (in pixels).
964 * Minimum Column width (in pixels).
973 * When a width is not defined for a Column, maxAutoWidth defines an upper
974 * limit that the Column should be auto-sized to. If resizeable is enabled,
975 * users may still resize to a greater width. Most useful for Columns intended
976 * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very
977 * wide Columns from disrupting visual readability by inducing truncation.
979 * @property maxAutoWidth
986 * True if Column is in hidden state.
995 * True if Column is in selected state.
1004 * Custom CSS class or array of classes to be applied to every cell in the Column.
1006 * @property className
1007 * @type String || String[]
1012 * Defines a format function.
1014 * @property formatter
1015 * @type String || HTMLFunction
1020 * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
1022 * @property currencyOptions
1026 currencyOptions : null,
1029 * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
1031 * @property dateOptions
1038 * Array of dropdown values for formatter:"dropdown" cases. Can either be a simple array (e.g.,
1039 * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g.,
1040 * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
1041 * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]).
1043 * @property dropdownOptions
1044 * @type String[] | Object[]
1046 dropdownOptions : null,
1049 * A CellEditor instance, otherwise Column is not editable.
1052 * @type YAHOO.widget.CellEditor
1057 * True if Column is resizeable, false otherwise. The Drag & Drop Utility is
1058 * required to enable this feature. Only bottom-level and non-nested Columns are
1061 * @property resizeable
1068 * True if Column is sortable, false otherwise.
1070 * @property sortable
1077 * @property sortOptions.defaultOrder
1078 * @deprecated Use sortOptions.defaultDir.
1081 * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
1083 * @property sortOptions.defaultDir
1088 * Custom field to sort on.
1090 * @property sortOptions.field
1095 * Custom sort handler. Signature: sortFunction(a, b, desc, field) where field is the sortOptions.field value
1097 * @property sortOptions.sortFunction
1117 /////////////////////////////////////////////////////////////////////////////
1121 /////////////////////////////////////////////////////////////////////////////
1124 * Returns unique ID string.
1127 * @return {String} Unique ID string.
1129 getId : function() {
1134 * Column instance name, for logging.
1137 * @return {String} Column's unique name.
1139 toString : function() {
1140 return "Column instance " + this._sId;
1144 * Returns object literal definition.
1146 * @method getDefinition
1147 * @return {Object} Object literal definition.
1149 getDefinition : function() {
1150 var oDefinition = {};
1152 // Update the definition
1153 oDefinition.abbr = this.abbr;
1154 oDefinition.className = this.className;
1155 oDefinition.editor = this.editor;
1156 oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
1157 oDefinition.field = this.field;
1158 oDefinition.formatter = this.formatter;
1159 oDefinition.hidden = this.hidden;
1160 oDefinition.key = this.key;
1161 oDefinition.label = this.label;
1162 oDefinition.minWidth = this.minWidth;
1163 oDefinition.maxAutoWidth = this.maxAutoWidth;
1164 oDefinition.resizeable = this.resizeable;
1165 oDefinition.selected = this.selected;
1166 oDefinition.sortable = this.sortable;
1167 oDefinition.sortOptions = this.sortOptions;
1168 oDefinition.width = this.width;
1174 * Returns unique Column key.
1177 * @return {String} Column key.
1179 getKey : function() {
1187 * @return {String} Column field.
1189 getField : function() {
1194 * Returns Column key which has been sanitized for DOM (class and ID) usage
1195 * starts with letter, contains only letters, numbers, hyphen, or period.
1197 * @method getSanitizedKey
1198 * @return {String} Sanitized Column key.
1200 getSanitizedKey : function() {
1201 return this.getKey().replace(/[^\w\-]/g,"");
1205 * Public accessor returns Column's current position index within its
1206 * ColumnSet's keys array, if applicable. Only non-nested and bottom-level
1207 * child Columns will return a value.
1209 * @method getKeyIndex
1210 * @return {Number} Position index, or null.
1212 getKeyIndex : function() {
1213 return this._nKeyIndex;
1217 * Public accessor returns Column's current position index within its
1218 * ColumnSet's tree array, if applicable. Only non-nested and top-level parent
1219 * Columns will return a value;
1221 * @method getTreeIndex
1222 * @return {Number} Position index, or null.
1224 getTreeIndex : function() {
1225 return this._nTreeIndex;
1229 * Public accessor returns Column's parent instance if any, or null otherwise.
1232 * @return {YAHOO.widget.Column} Column's parent instance.
1234 getParent : function() {
1235 return this._oParent;
1239 * Public accessor returns Column's calculated COLSPAN value.
1241 * @method getColspan
1242 * @return {Number} Column's COLSPAN value.
1244 getColspan : function() {
1245 return this._nColspan;
1247 // Backward compatibility
1248 getColSpan : function() {
1249 YAHOO.log("The method getColSpan() has been" +
1250 " deprecated in favor of getColspan()", "warn", this.toString());
1251 return this.getColspan();
1255 * Public accessor returns Column's calculated ROWSPAN value.
1257 * @method getRowspan
1258 * @return {Number} Column's ROWSPAN value.
1260 getRowspan : function() {
1261 return this._nRowspan;
1265 * Returns DOM reference to the key TH element.
1268 * @return {HTMLElement} TH element.
1270 getThEl : function() {
1275 * Returns DOM reference to the TH's liner DIV element. Introduced since
1276 * resizeable Columns may have an extra resizer liner, making the DIV liner
1277 * not reliably the TH element's first child.
1279 * @method getThLInerEl
1280 * @return {HTMLElement} TH element.
1282 getThLinerEl : function() {
1283 return this._elThLiner;
1287 * Returns DOM reference to the resizer element, or null.
1289 * @method getResizerEl
1290 * @return {HTMLElement} DIV element.
1292 getResizerEl : function() {
1293 return this._elResizer;
1296 // Backward compatibility
1299 * @deprecated Use getThEl
1301 getColEl : function() {
1302 YAHOO.log("The method getColEl() has been" +
1303 " deprecated in favor of getThEl()", "warn",
1305 return this.getThEl();
1307 getIndex : function() {
1308 YAHOO.log("The method getIndex() has been" +
1309 " deprecated in favor of getKeyIndex()", "warn",
1311 return this.getKeyIndex();
1313 format : function() {
1314 YAHOO.log("The method format() has been deprecated in favor of the " +
1315 "DataTable method formatCell()", "error", this.toString());
1319 /****************************************************************************/
1320 /****************************************************************************/
1321 /****************************************************************************/
1324 * Sort static utility to support Column sorting.
1326 * @namespace YAHOO.util
1331 /////////////////////////////////////////////////////////////////////////////
1335 /////////////////////////////////////////////////////////////////////////////
1338 * Comparator function for simple case-insensitive string sorting.
1341 * @param a {Object} First sort argument.
1342 * @param b {Object} Second sort argument.
1343 * @param desc {Boolean} True if sort direction is descending, false if
1344 * sort direction is ascending.
1346 compare: function(a, b, desc) {
1347 if((a === null) || (typeof a == "undefined")) {
1348 if((b === null) || (typeof b == "undefined")) {
1355 else if((b === null) || (typeof b == "undefined")) {
1359 if(a.constructor == String) {
1360 a = a.toLowerCase();
1362 if(b.constructor == String) {
1363 b = b.toLowerCase();
1366 return (desc) ? 1 : -1;
1369 return (desc) ? -1 : 1;
1377 /****************************************************************************/
1378 /****************************************************************************/
1379 /****************************************************************************/
1382 * ColumnDD subclasses DragDrop to support rearrangeable Columns.
1384 * @namespace YAHOO.util
1386 * @extends YAHOO.util.DDProxy
1388 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1389 * @param oColumn {YAHOO.widget.Column} Column instance.
1390 * @param elTh {HTMLElement} TH element reference.
1391 * @param elTarget {HTMLElement} Drag target element.
1393 YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
1394 if(oDataTable && oColumn && elTh && elTarget) {
1395 this.datatable = oDataTable;
1396 this.table = oDataTable.getTableEl();
1397 this.column = oColumn;
1398 this.headCell = elTh;
1399 this.pointer = elTarget;
1400 this.newIndex = null;
1402 this.initFrame(); // Needed for DDProxy
1403 this.invalidHandleTypes = {};
1405 // Set top/bottom padding to account for children of nested columns
1406 this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
1408 YAHOO.util.Event.on(window, 'resize', function() {
1409 this.initConstraints();
1413 YAHOO.log("Column dragdrop could not be created","warn",oDataTable.toString());
1417 if(YAHOO.util.DDProxy) {
1418 YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
1419 initConstraints: function() {
1420 //Get the top, right, bottom and left positions
1421 var region = YAHOO.util.Dom.getRegion(this.table),
1422 //Get the element we are working on
1424 //Get the xy position of it
1425 xy = YAHOO.util.Dom.getXY(el),
1426 //Get the width and height
1427 width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
1428 height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
1429 //Set left to x minus left
1430 left = ((xy[0] - region.left) + 15), //Buffer of 15px
1431 //Set right to right minus x minus width
1432 right = ((region.right - xy[0] - width) + 15);
1434 //Set the constraints based on the above calculations
1435 this.setXConstraint(left, right);
1436 this.setYConstraint(10, 10);
1438 _resizeProxy: function() {
1439 YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this, arguments);
1440 var dragEl = this.getDragEl(),
1443 YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
1444 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
1445 var xy = YAHOO.util.Dom.getXY(el);
1446 YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
1448 YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
1449 YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
1450 YAHOO.util.Dom.setXY(this.dragEl, xy);
1452 onMouseDown: function() {
1453 this.initConstraints();
1454 this.resetConstraints();
1456 clickValidator: function(e) {
1457 if(!this.column.hidden) {
1458 var target = YAHOO.util.Event.getTarget(e);
1459 return ( this.isValidHandleChild(target) &&
1460 (this.id == this.handleElId ||
1461 this.DDM.handleWasClicked(target, this.id)) );
1464 onDragOver: function(ev, id) {
1465 // Validate target as a Column
1466 var target = this.datatable.getColumn(id);
1468 // Validate target as a top-level parent
1469 var targetIndex = target.getTreeIndex();
1470 while((targetIndex === null) && target.getParent()) {
1471 target = target.getParent();
1472 targetIndex = target.getTreeIndex();
1474 if(targetIndex !== null) {
1475 // Are we placing to left or right of target?
1476 var elTarget = target.getThEl();
1477 var newIndex = targetIndex;
1478 var mouseX = YAHOO.util.Event.getPageX(ev),
1479 targetX = YAHOO.util.Dom.getX(elTarget),
1480 midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
1481 currentIndex = this.column.getTreeIndex();
1483 if (mouseX < midX) {
1484 YAHOO.util.Dom.setX(this.pointer, targetX);
1486 var targetWidth = parseInt(elTarget.offsetWidth, 10);
1487 YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
1490 if (targetIndex > currentIndex) {
1496 else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
1497 newIndex = this.datatable.getColumnSet().tree[0].length;
1499 this.newIndex = newIndex;
1503 onDragDrop: function() {
1504 this.datatable.reorderColumn(this.column, this.newIndex);
1506 endDrag: function() {
1507 this.newIndex = null;
1508 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
1513 /****************************************************************************/
1514 /****************************************************************************/
1515 /****************************************************************************/
1518 * ColumnResizer subclasses DragDrop to support resizeable Columns.
1520 * @namespace YAHOO.util
1521 * @class ColumnResizer
1522 * @extends YAHOO.util.DDProxy
1524 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1525 * @param oColumn {YAHOO.widget.Column} Column instance.
1526 * @param elTh {HTMLElement} TH element reference.
1527 * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
1528 * @param elProxy {HTMLElement} Resizer proxy element.
1530 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
1531 if(oDataTable && oColumn && elTh && sHandleId) {
1532 this.datatable = oDataTable;
1533 this.column = oColumn;
1534 this.headCell = elTh;
1535 this.headCellLiner = oColumn.getThLinerEl();
1536 this.resizerLiner = elTh.firstChild;
1537 this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
1538 this.initFrame(); // Needed for proxy
1539 this.resetResizerEl(); // Needed when rowspan > 0
1541 // Set right padding for bug 1858462
1542 this.setPadding(0, 1, 0, 0);
1545 YAHOO.log("Column resizer could not be created","warn",oDataTable.toString());
1550 YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
1551 /////////////////////////////////////////////////////////////////////////////
1555 /////////////////////////////////////////////////////////////////////////////
1557 * Resets resizer element.
1559 * @method resetResizerEl
1561 resetResizerEl : function() {
1562 var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
1563 resizerStyle.left = "auto";
1564 resizerStyle.right = 0;
1565 resizerStyle.top = "auto";
1566 resizerStyle.bottom = 0;
1567 resizerStyle.height = this.headCell.offsetHeight+"px";
1570 /////////////////////////////////////////////////////////////////////////////
1572 // Public DOM event handlers
1574 /////////////////////////////////////////////////////////////////////////////
1577 * Handles mouseup events on the Column resizer.
1580 * @param e {string} The mouseup event
1582 onMouseUp : function(e) {
1583 // Reset height of all resizer els in case TH's have changed height
1584 var allKeys = this.datatable.getColumnSet().keys,
1586 for(var i=0, len=allKeys.length; i<len; i++) {
1588 if(col._ddResizer) {
1589 col._ddResizer.resetResizerEl();
1592 this.resetResizerEl();
1594 var el = this.headCellLiner;
1595 var newWidth = el.offsetWidth -
1596 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) -
1597 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0);
1599 this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth});
1603 * Handles mousedown events on the Column resizer.
1605 * @method onMouseDown
1606 * @param e {string} The mousedown event
1608 onMouseDown : function(e) {
1609 this.startWidth = this.headCellLiner.offsetWidth;
1610 this.startX = YAHOO.util.Event.getXY(e)[0];
1611 this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) +
1612 (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
1616 * Custom clickValidator to ensure Column is not in hidden state.
1618 * @method clickValidator
1622 clickValidator : function(e) {
1623 if(!this.column.hidden) {
1624 var target = YAHOO.util.Event.getTarget(e);
1625 return ( this.isValidHandleChild(target) &&
1626 (this.id == this.handleElId ||
1627 this.DDM.handleWasClicked(target, this.id)) );
1632 * Handles start drag on the Column resizer.
1635 * @param e {string} The drag event
1637 startDrag : function() {
1638 // Shrinks height of all resizer els to not hold open TH els
1639 var allKeys = this.datatable.getColumnSet().keys,
1640 thisKey = this.column.getKeyIndex(),
1642 for(var i=0, len=allKeys.length; i<len; i++) {
1644 if(col._ddResizer) {
1645 YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em";
1651 * Handles drag events on the Column resizer.
1654 * @param e {string} The drag event
1656 onDrag : function(e) {
1657 var newX = YAHOO.util.Event.getXY(e)[0];
1658 if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) {
1659 var offsetX = newX - this.startX;
1660 var newWidth = this.startWidth + offsetX - this.nLinerPadding;
1662 this.datatable.setColumnWidth(this.column, newWidth);
1669 /////////////////////////////////////////////////////////////////////////////
1673 /////////////////////////////////////////////////////////////////////////////
1676 * @property editorOptions
1677 * @deprecated Pass configs directly to CellEditor constructor.
1683 var lang = YAHOO.lang,
1685 widget = YAHOO.widget,
1689 DT = widget.DataTable;
1691 /****************************************************************************/
1692 /****************************************************************************/
1693 /****************************************************************************/
1696 * A RecordSet defines and manages a set of Records.
1698 * @namespace YAHOO.widget
1700 * @param data {Object || Object[]} An object literal or an array of data.
1703 YAHOO.widget.RecordSet = function(data) {
1704 // Internal variables
1705 this._sId = "yui-rs" + widget.RecordSet._nCount;
1706 widget.RecordSet._nCount++;
1711 if(lang.isArray(data)) {
1712 this.addRecords(data);
1714 else if(lang.isObject(data)) {
1715 this.addRecord(data);
1719 YAHOO.log("RecordSet initialized", "info", this.toString());
1722 var RS = widget.RecordSet;
1725 * Internal class variable to name multiple Recordset instances.
1727 * @property RecordSet._nCount
1736 /////////////////////////////////////////////////////////////////////////////
1738 // Private member variables
1740 /////////////////////////////////////////////////////////////////////////////
1742 * Unique String identifier assigned at instantiation.
1751 * Internal counter of how many Records are in the RecordSet.
1756 * @deprecated No longer used
1760 /////////////////////////////////////////////////////////////////////////////
1764 /////////////////////////////////////////////////////////////////////////////
1767 * Adds one Record to the RecordSet at the given index. If index is null,
1768 * then adds the Record to the end of the RecordSet.
1770 * @method _addRecord
1771 * @param oData {Object} An object literal of data.
1772 * @param index {Number} (optional) Position index.
1773 * @return {YAHOO.widget.Record} A Record instance.
1776 _addRecord : function(oData, index) {
1777 var oRecord = new YAHOO.widget.Record(oData);
1779 if(YAHOO.lang.isNumber(index) && (index > -1)) {
1780 this._records.splice(index,0,oRecord);
1783 //index = this.getLength();
1784 //this._records[index] = oRecord;
1785 this._records[this._records.length] = oRecord;
1792 * Sets/replaces one Record to the RecordSet at the given index. Existing
1793 * Records with higher indexes are not shifted. If no index specified, the
1794 * Record is added to the end of the RecordSet.
1796 * @method _setRecord
1797 * @param oData {Object} An object literal of data.
1798 * @param index {Number} (optional) Position index.
1799 * @return {YAHOO.widget.Record} A Record instance.
1802 _setRecord : function(oData, index) {
1803 if (!lang.isNumber(index) || index < 0) {
1804 index = this._records.length;
1806 return (this._records[index] = new widget.Record(oData));
1808 if(lang.isNumber(index) && (index > -1)) {
1809 this._records[index] = oRecord;
1810 if((index+1) > this.getLength()) {
1811 this._length = index+1;
1815 this._records[this.getLength()] = oRecord;
1823 * Deletes Records from the RecordSet at the given index. If range is null,
1824 * then only one Record is deleted.
1826 * @method _deleteRecord
1827 * @param index {Number} Position index.
1828 * @param range {Number} (optional) How many Records to delete
1831 _deleteRecord : function(index, range) {
1832 if(!lang.isNumber(range) || (range < 0)) {
1835 this._records.splice(index, range);
1836 //this._length = this._length - range;
1839 /////////////////////////////////////////////////////////////////////////////
1843 /////////////////////////////////////////////////////////////////////////////
1846 * Returns unique name of the RecordSet instance.
1849 * @return {String} Unique name of the RecordSet instance.
1851 getId : function() {
1856 * Public accessor to the unique name of the RecordSet instance.
1859 * @return {String} Unique name of the RecordSet instance.
1861 toString : function() {
1862 return "RecordSet instance " + this._sId;
1866 * Returns the number of Records held in the RecordSet.
1869 * @return {Number} Number of records in the RecordSet.
1871 getLength : function() {
1872 //return this._length;
1873 return this._records.length;
1877 * Returns Record by ID or RecordSet position index.
1880 * @param record {YAHOO.widget.Record | Number | String} Record instance,
1881 * RecordSet position index, or Record ID.
1882 * @return {YAHOO.widget.Record} Record object.
1884 getRecord : function(record) {
1886 if(record instanceof widget.Record) {
1887 for(i=0; i<this._records.length; i++) {
1888 if(this._records[i] && (this._records[i]._sId === record._sId)) {
1893 else if(lang.isNumber(record)) {
1894 if((record > -1) && (record < this.getLength())) {
1895 return this._records[record];
1898 else if(lang.isString(record)) {
1899 for(i=0; i<this._records.length; i++) {
1900 if(this._records[i] && (this._records[i]._sId === record)) {
1901 return this._records[i];
1905 // Not a valid Record for this RecordSet
1911 * Returns an array of Records from the RecordSet.
1913 * @method getRecords
1914 * @param index {Number} (optional) Recordset position index of which Record to
1916 * @param range {Number} (optional) Number of Records to get.
1917 * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
1918 * length equal to given range. If index is not given, all Records are returned.
1920 getRecords : function(index, range) {
1921 if(!lang.isNumber(index)) {
1922 return this._records;
1924 if(!lang.isNumber(range)) {
1925 return this._records.slice(index);
1927 return this._records.slice(index, index+range);
1931 * Returns a boolean indicating whether Records exist in the RecordSet at the
1932 * specified index range. Returns true if and only if a Record exists at each
1933 * index in the range.
1934 * @method hasRecords
1937 * @return {Boolean} true if all indices are populated in the RecordSet
1939 hasRecords : function (index, range) {
1940 var recs = this.getRecords(index,range);
1941 for (var i = 0; i < range; ++i) {
1942 if (typeof recs[i] === 'undefined') {
1950 * Returns current position index for the given Record.
1952 * @method getRecordIndex
1953 * @param oRecord {YAHOO.widget.Record} Record instance.
1954 * @return {Number} Record's RecordSet position index.
1957 getRecordIndex : function(oRecord) {
1959 for(var i=this._records.length-1; i>-1; i--) {
1960 if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
1970 * Adds one Record to the RecordSet at the given index. If index is null,
1971 * then adds the Record to the end of the RecordSet.
1974 * @param oData {Object} An object literal of data.
1975 * @param index {Number} (optional) Position index.
1976 * @return {YAHOO.widget.Record} A Record instance.
1978 addRecord : function(oData, index) {
1979 if(lang.isObject(oData)) {
1980 var oRecord = this._addRecord(oData, index);
1981 this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
1982 YAHOO.log("Added Record at index " + index +
1983 " with data " + lang.dump(oData), "info", this.toString());
1987 YAHOO.log("Could not add Record with data" +
1988 lang.dump(oData), "info", this.toString());
1994 * Adds multiple Records at once to the RecordSet at the given index with the
1995 * given object literal data. If index is null, then the new Records are
1996 * added to the end of the RecordSet.
1998 * @method addRecords
1999 * @param aData {Object[]} An object literal data or an array of data object literals.
2000 * @param index {Number} (optional) Position index.
2001 * @return {YAHOO.widget.Record[]} An array of Record instances.
2003 addRecords : function(aData, index) {
2004 if(lang.isArray(aData)) {
2005 var newRecords = [],
2008 index = lang.isNumber(index) ? index : this._records.length;
2011 // Can't go backwards bc we need to preserve order
2012 for(i=0,len=aData.length; i<len; ++i) {
2013 if(lang.isObject(aData[i])) {
2014 var record = this._addRecord(aData[i], idx++);
2015 newRecords.push(record);
2018 this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
2019 YAHOO.log("Added " + newRecords.length + " Record(s) at index " + index +
2020 " with data " + lang.dump(aData), "info", this.toString());
2023 else if(lang.isObject(aData)) {
2024 var oRecord = this._addRecord(aData);
2025 this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
2026 YAHOO.log("Added 1 Record at index " + index +
2027 " with data " + lang.dump(aData), "info", this.toString());
2031 YAHOO.log("Could not add Records with data " +
2032 lang.dump(aData), "info", this.toString());
2038 * Sets or replaces one Record to the RecordSet at the given index. Unlike
2039 * addRecord, an existing Record at that index is not shifted to preserve it.
2040 * If no index is specified, it adds the Record to the end of the RecordSet.
2043 * @param oData {Object} An object literal of data.
2044 * @param index {Number} (optional) Position index.
2045 * @return {YAHOO.widget.Record} A Record instance.
2047 setRecord : function(oData, index) {
2048 if(lang.isObject(oData)) {
2049 var oRecord = this._setRecord(oData, index);
2050 this.fireEvent("recordSetEvent",{record:oRecord,data:oData});
2051 YAHOO.log("Set Record at index " + index +
2052 " with data " + lang.dump(oData), "info", this.toString());
2056 YAHOO.log("Could not set Record with data" +
2057 lang.dump(oData), "info", this.toString());
2063 * Sets or replaces multiple Records at once to the RecordSet with the given
2064 * data, starting at the given index. If index is not specified, then the new
2065 * Records are added to the end of the RecordSet.
2067 * @method setRecords
2068 * @param aData {Object[]} An array of object literal data.
2069 * @param index {Number} (optional) Position index.
2070 * @return {YAHOO.widget.Record[]} An array of Record instances.
2072 setRecords : function(aData, index) {
2073 var Rec = widget.Record,
2074 a = lang.isArray(aData) ? aData : [aData],
2076 i = 0, l = a.length, j = 0;
2078 index = parseInt(index,10)|0;
2081 if (typeof a[i] === 'object' && a[i]) {
2082 added[j++] = this._records[index + i] = new Rec(a[i]);
2086 this.fireEvent("recordsSetEvent",{records:added,data:aData});
2087 // Backward compatibility for bug 1918245
2088 this.fireEvent("recordsSet",{records:added,data:aData});
2089 YAHOO.log("Set "+j+" Record(s) at index "+index, "info",
2092 if (a.length && !added.length) {
2093 YAHOO.log("Could not set Records with data " +
2094 lang.dump(aData), "info", this.toString());
2097 return added.length > 1 ? added : added[0];
2101 * Updates given Record with given data.
2103 * @method updateRecord
2104 * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2105 * a RecordSet position index, or a Record ID.
2106 * @param oData {Object} Object literal of new data.
2107 * @return {YAHOO.widget.Record} Updated Record, or null.
2109 updateRecord : function(record, oData) {
2110 var oRecord = this.getRecord(record);
2111 if(oRecord && lang.isObject(oData)) {
2112 // Copy data from the Record for the event that gets fired later
2114 for(var key in oRecord._oData) {
2115 if(lang.hasOwnProperty(oRecord._oData, key)) {
2116 oldData[key] = oRecord._oData[key];
2119 oRecord._oData = oData;
2120 this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
2121 YAHOO.log("Record at index " + this.getRecordIndex(oRecord) +
2122 " updated with data " + lang.dump(oData), "info", this.toString());
2126 YAHOO.log("Could not update Record " + record, "error", this.toString());
2133 * @deprecated Use updateRecordValue
2135 updateKey : function(record, sKey, oData) {
2136 this.updateRecordValue(record, sKey, oData);
2139 * Sets given Record at given key to given data.
2141 * @method updateRecordValue
2142 * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2143 * a RecordSet position index, or a Record ID.
2144 * @param sKey {String} Key name.
2145 * @param oData {Object} New data.
2147 updateRecordValue : function(record, sKey, oData) {
2148 var oRecord = this.getRecord(record);
2151 var keyValue = oRecord._oData[sKey];
2152 // Copy data from the Record for the event that gets fired later
2153 if(keyValue && lang.isObject(keyValue)) {
2155 for(var key in keyValue) {
2156 if(lang.hasOwnProperty(keyValue, key)) {
2157 oldData[key] = keyValue[key];
2166 oRecord._oData[sKey] = oData;
2167 this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2168 this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2169 YAHOO.log("Key \"" + sKey +
2170 "\" for Record at index " + this.getRecordIndex(oRecord) +
2171 " updated to \"" + lang.dump(oData) + "\"", "info", this.toString());
2174 YAHOO.log("Could not update key " + sKey + " for Record " + record, "error", this.toString());
2179 * Replaces all Records in RecordSet with new object literal data.
2181 * @method replaceRecords
2182 * @param data {Object || Object[]} An object literal of data or an array of
2183 * data object literals.
2184 * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
2185 * an array of Records.
2187 replaceRecords : function(data) {
2189 return this.addRecords(data);
2193 * Sorts all Records by given function. Records keep their unique IDs but will
2194 * have new RecordSet position indexes.
2196 * @method sortRecords
2197 * @param fnSort {Function} Reference to a sort function.
2198 * @param desc {Boolean} True if sort direction is descending, false if sort
2199 * direction is ascending.
2200 * @param field {String} The field to sort by, from sortOptions.field
2201 * @return {YAHOO.widget.Record[]} Sorted array of Records.
2203 sortRecords : function(fnSort, desc, field) {
2204 return this._records.sort(function(a, b) {return fnSort(a, b, desc, field);});
2208 * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
2210 * @method reverseRecords
2211 * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
2213 reverseRecords : function() {
2214 return this._records.reverse();
2218 * Removes the Record at the given position index from the RecordSet. If a range
2219 * is also provided, removes that many Records, starting from the index. Length
2220 * of RecordSet is correspondingly shortened.
2222 * @method deleteRecord
2223 * @param index {Number} Record's RecordSet position index.
2224 * @param range {Number} (optional) How many Records to delete.
2225 * @return {Object} A copy of the data held by the deleted Record.
2227 deleteRecord : function(index) {
2228 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2229 // Copy data from the Record for the event that gets fired later
2230 var oData = widget.DataTable._cloneObject(this.getRecord(index).getData());
2232 this._deleteRecord(index);
2233 this.fireEvent("recordDeleteEvent",{data:oData,index:index});
2234 YAHOO.log("Record deleted at index " + index +
2235 " and containing data " + lang.dump(oData), "info", this.toString());
2239 YAHOO.log("Could not delete Record at index " + index, "error", this.toString());
2245 * Removes the Record at the given position index from the RecordSet. If a range
2246 * is also provided, removes that many Records, starting from the index. Length
2247 * of RecordSet is correspondingly shortened.
2249 * @method deleteRecords
2250 * @param index {Number} Record's RecordSet position index.
2251 * @param range {Number} (optional) How many Records to delete.
2252 * @return {Object[]} An array of copies of the data held by the deleted Records.
2254 deleteRecords : function(index, range) {
2255 if(!lang.isNumber(range)) {
2258 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2259 var recordsToDelete = this.getRecords(index, range);
2260 // Copy data from each Record for the event that gets fired later
2261 var deletedData = [];
2263 for(var i=0; i<recordsToDelete.length; i++) {
2264 deletedData[deletedData.length] = widget.DataTable._cloneObject(recordsToDelete[i]);
2266 this._deleteRecord(index, range);
2268 this.fireEvent("recordsDeleteEvent",{data:deletedData,index:index});
2269 YAHOO.log(range + "Record(s) deleted at index " + index +
2270 " and containing data " + lang.dump(deletedData), "info", this.toString());
2275 YAHOO.log("Could not delete Records at index " + index, "error", this.toString());
2281 * Deletes all Records from the RecordSet.
2285 reset : function() {
2288 this.fireEvent("resetEvent");
2289 YAHOO.log("All Records deleted from RecordSet", "info", this.toString());
2293 /////////////////////////////////////////////////////////////////////////////
2297 /////////////////////////////////////////////////////////////////////////////
2299 // RecordSet uses EventProvider
2300 lang.augmentProto(RS, util.EventProvider);
2303 * Fired when a new Record is added to the RecordSet.
2305 * @event recordAddEvent
2306 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2307 * @param oArgs.data {Object} Data added.
2311 * Fired when multiple Records are added to the RecordSet at once.
2313 * @event recordsAddEvent
2314 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2315 * @param oArgs.data {Object[]} Data added.
2319 * Fired when a Record is set in the RecordSet.
2321 * @event recordSetEvent
2322 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2323 * @param oArgs.data {Object} Data added.
2327 * Fired when multiple Records are set in the RecordSet at once.
2329 * @event recordsSetEvent
2330 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2331 * @param oArgs.data {Object[]} Data added.
2335 * Fired when a Record is updated with new data.
2337 * @event recordUpdateEvent
2338 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2339 * @param oArgs.newData {Object} New data.
2340 * @param oArgs.oldData {Object} Old data.
2344 * Fired when a Record is deleted from the RecordSet.
2346 * @event recordDeleteEvent
2347 * @param oArgs.data {Object} A copy of the data held by the Record,
2348 * or an array of data object literals if multiple Records were deleted at once.
2349 * @param oArgs.index {Object} Index of the deleted Record.
2353 * Fired when multiple Records are deleted from the RecordSet at once.
2355 * @event recordsDeleteEvent
2356 * @param oArgs.data {Object[]} An array of data object literals copied
2358 * @param oArgs.index {Object} Index of the first deleted Record.
2362 * Fired when all Records are deleted from the RecordSet at once.
2368 * @event keyUpdateEvent
2369 * @deprecated Use recordValueUpdateEvent
2373 * Fired when a Record value is updated with new data.
2375 * @event recordValueUpdateEvent
2376 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2377 * @param oArgs.key {String} The updated key.
2378 * @param oArgs.newData {Object} New data.
2379 * @param oArgs.oldData {Object} Old data.
2384 /****************************************************************************/
2385 /****************************************************************************/
2386 /****************************************************************************/
2389 * The Record class defines a DataTable record.
2391 * @namespace YAHOO.widget
2394 * @param oConfigs {Object} (optional) Object literal of key/value pairs.
2396 YAHOO.widget.Record = function(oLiteral) {
2397 this._nCount = widget.Record._nCount;
2398 this._sId = "yui-rec" + this._nCount;
2399 widget.Record._nCount++;
2401 if(lang.isObject(oLiteral)) {
2402 for(var sKey in oLiteral) {
2403 if(lang.hasOwnProperty(oLiteral, sKey)) {
2404 this._oData[sKey] = oLiteral[sKey];
2410 /////////////////////////////////////////////////////////////////////////////
2412 // Private member variables
2414 /////////////////////////////////////////////////////////////////////////////
2417 * Internal class variable to give unique IDs to Record instances.
2419 * @property Record._nCount
2423 YAHOO.widget.Record._nCount = 0;
2425 YAHOO.widget.Record.prototype = {
2427 * Immutable unique count assigned at instantiation. Remains constant while a
2428 * Record's position index can change from sorting.
2437 * Immutable unique ID assigned at instantiation. Remains constant while a
2438 * Record's position index can change from sorting.
2447 * Holds data for the Record in an object literal.
2455 /////////////////////////////////////////////////////////////////////////////
2457 // Public member variables
2459 /////////////////////////////////////////////////////////////////////////////
2461 /////////////////////////////////////////////////////////////////////////////
2465 /////////////////////////////////////////////////////////////////////////////
2468 * Returns unique count assigned at instantiation.
2473 getCount : function() {
2474 return this._nCount;
2478 * Returns unique ID assigned at instantiation.
2483 getId : function() {
2488 * Returns data for the Record for a field if given, or the entire object
2489 * literal otherwise.
2492 * @param sField {String} (Optional) The field from which to retrieve data value.
2495 getData : function(sField) {
2496 if(lang.isString(sField)) {
2497 return this._oData[sField];
2505 * Sets given data at the given key. Use the RecordSet method updateRecordValue to trigger
2509 * @param sKey {String} The key of the new value.
2510 * @param oData {MIXED} The new value.
2512 setData : function(sKey, oData) {
2513 this._oData[sKey] = oData;
2521 var lang = YAHOO.lang,
2523 widget = YAHOO.widget,
2528 DS = util.DataSourceBase;
2531 * The DataTable widget provides a progressively enhanced DHTML control for
2532 * displaying tabular data across A-grade browsers.
2535 * @requires yahoo, dom, event, element, datasource
2536 * @optional dragdrop, dragdrop
2537 * @title DataTable Widget
2540 /****************************************************************************/
2541 /****************************************************************************/
2542 /****************************************************************************/
2545 * DataTable class for the YUI DataTable widget.
2547 * @namespace YAHOO.widget
2549 * @extends YAHOO.util.Element
2551 * @param elContainer {HTMLElement} Container element for the TABLE.
2552 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
2553 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
2554 * @param oConfigs {object} (optional) Object literal of configuration values.
2556 YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
2557 var DT = widget.DataTable;
2559 ////////////////////////////////////////////////////////////////////////////
2560 // Backward compatibility for SDT, but prevent infinite loops
2562 if(oConfigs && oConfigs.scrollable) {
2563 return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
2566 ////////////////////////////////////////////////////////////////////////////
2570 this._nIndex = DT._nCount;
2571 this._sId = "yui-dt"+this._nIndex;
2572 this._oChainRender = new YAHOO.util.Chain();
2573 this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
2575 // Initialize configs
2576 this._initConfigs(oConfigs);
2578 // Initialize DataSource
2579 this._initDataSource(oDataSource);
2580 if(!this._oDataSource) {
2581 YAHOO.log("Could not instantiate DataTable due to an invalid DataSource", "error", this.toString());
2585 // Initialize ColumnSet
2586 this._initColumnSet(aColumnDefs);
2587 if(!this._oColumnSet) {
2588 YAHOO.log("Could not instantiate DataTable due to an invalid ColumnSet", "error", this.toString());
2592 // Initialize RecordSet
2593 this._initRecordSet();
2594 if(!this._oRecordSet) {
2597 // Initialize Attributes
2598 DT.superclass.constructor.call(this, elContainer, this.configs);
2600 // Initialize DOM elements
2601 var okDom = this._initDomElements(elContainer);
2603 YAHOO.log("Could not instantiate DataTable due to an invalid DOM element", "error", this.toString());
2607 // Show message as soon as config is available
2608 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
2610 ////////////////////////////////////////////////////////////////////////////
2611 // Once per instance
2615 DT._nCurrentCount++;
2617 ////////////////////////////////////////////////////////////////////////////
2620 // Send a simple initial request
2622 success : this.onDataReturnSetRows,
2623 failure : this.onDataReturnSetRows,
2625 argument: this.getState()
2628 var initialLoad = this.get("initialLoad");
2629 if(initialLoad === true) {
2630 this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
2632 // Do not send an initial request at all
2633 else if(initialLoad === false) {
2634 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
2636 // Send an initial request with a custom payload
2638 var oCustom = initialLoad || {};
2639 oCallback.argument = oCustom.argument || {};
2640 this._oDataSource.sendRequest(oCustom.request, oCallback);
2644 var DT = widget.DataTable;
2646 /////////////////////////////////////////////////////////////////////////////
2650 /////////////////////////////////////////////////////////////////////////////
2652 lang.augmentObject(DT, {
2655 * Class name assigned to outer DataTable container.
2657 * @property DataTable.CLASS_DATATABLE
2663 CLASS_DATATABLE : "yui-dt",
2666 * Class name assigned to liner DIV elements.
2668 * @property DataTable.CLASS_LINER
2672 * @default "yui-dt-liner"
2674 CLASS_LINER : "yui-dt-liner",
2677 * Class name assigned to display label elements.
2679 * @property DataTable.CLASS_LABEL
2683 * @default "yui-dt-label"
2685 CLASS_LABEL : "yui-dt-label",
2688 * Class name assigned to messaging elements.
2690 * @property DataTable.CLASS_MESSAGE
2694 * @default "yui-dt-message"
2696 CLASS_MESSAGE : "yui-dt-message",
2699 * Class name assigned to mask element when DataTable is disabled.
2701 * @property DataTable.CLASS_MASK
2705 * @default "yui-dt-mask"
2707 CLASS_MASK : "yui-dt-mask",
2710 * Class name assigned to data elements.
2712 * @property DataTable.CLASS_DATA
2716 * @default "yui-dt-data"
2718 CLASS_DATA : "yui-dt-data",
2721 * Class name assigned to Column drag target.
2723 * @property DataTable.CLASS_COLTARGET
2727 * @default "yui-dt-coltarget"
2729 CLASS_COLTARGET : "yui-dt-coltarget",
2732 * Class name assigned to resizer handle elements.
2734 * @property DataTable.CLASS_RESIZER
2738 * @default "yui-dt-resizer"
2740 CLASS_RESIZER : "yui-dt-resizer",
2743 * Class name assigned to resizer liner elements.
2745 * @property DataTable.CLASS_RESIZERLINER
2749 * @default "yui-dt-resizerliner"
2751 CLASS_RESIZERLINER : "yui-dt-resizerliner",
2754 * Class name assigned to resizer proxy elements.
2756 * @property DataTable.CLASS_RESIZERPROXY
2760 * @default "yui-dt-resizerproxy"
2762 CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
2765 * Class name assigned to CellEditor container elements.
2767 * @property DataTable.CLASS_EDITOR
2771 * @default "yui-dt-editor"
2773 CLASS_EDITOR : "yui-dt-editor",
2776 * Class name assigned to paginator container elements.
2778 * @property DataTable.CLASS_PAGINATOR
2782 * @default "yui-dt-paginator"
2784 CLASS_PAGINATOR : "yui-dt-paginator",
2787 * Class name assigned to page number indicators.
2789 * @property DataTable.CLASS_PAGE
2793 * @default "yui-dt-page"
2795 CLASS_PAGE : "yui-dt-page",
2798 * Class name assigned to default indicators.
2800 * @property DataTable.CLASS_DEFAULT
2804 * @default "yui-dt-default"
2806 CLASS_DEFAULT : "yui-dt-default",
2809 * Class name assigned to previous indicators.
2811 * @property DataTable.CLASS_PREVIOUS
2815 * @default "yui-dt-previous"
2817 CLASS_PREVIOUS : "yui-dt-previous",
2820 * Class name assigned next indicators.
2822 * @property DataTable.CLASS_NEXT
2826 * @default "yui-dt-next"
2828 CLASS_NEXT : "yui-dt-next",
2831 * Class name assigned to first elements.
2833 * @property DataTable.CLASS_FIRST
2837 * @default "yui-dt-first"
2839 CLASS_FIRST : "yui-dt-first",
2842 * Class name assigned to last elements.
2844 * @property DataTable.CLASS_LAST
2848 * @default "yui-dt-last"
2850 CLASS_LAST : "yui-dt-last",
2853 * Class name assigned to even elements.
2855 * @property DataTable.CLASS_EVEN
2859 * @default "yui-dt-even"
2861 CLASS_EVEN : "yui-dt-even",
2864 * Class name assigned to odd elements.
2866 * @property DataTable.CLASS_ODD
2870 * @default "yui-dt-odd"
2872 CLASS_ODD : "yui-dt-odd",
2875 * Class name assigned to selected elements.
2877 * @property DataTable.CLASS_SELECTED
2881 * @default "yui-dt-selected"
2883 CLASS_SELECTED : "yui-dt-selected",
2886 * Class name assigned to highlighted elements.
2888 * @property DataTable.CLASS_HIGHLIGHTED
2892 * @default "yui-dt-highlighted"
2894 CLASS_HIGHLIGHTED : "yui-dt-highlighted",
2897 * Class name assigned to hidden elements.
2899 * @property DataTable.CLASS_HIDDEN
2903 * @default "yui-dt-hidden"
2905 CLASS_HIDDEN : "yui-dt-hidden",
2908 * Class name assigned to disabled elements.
2910 * @property DataTable.CLASS_DISABLED
2914 * @default "yui-dt-disabled"
2916 CLASS_DISABLED : "yui-dt-disabled",
2919 * Class name assigned to empty indicators.
2921 * @property DataTable.CLASS_EMPTY
2925 * @default "yui-dt-empty"
2927 CLASS_EMPTY : "yui-dt-empty",
2930 * Class name assigned to loading indicatorx.
2932 * @property DataTable.CLASS_LOADING
2936 * @default "yui-dt-loading"
2938 CLASS_LOADING : "yui-dt-loading",
2941 * Class name assigned to error indicators.
2943 * @property DataTable.CLASS_ERROR
2947 * @default "yui-dt-error"
2949 CLASS_ERROR : "yui-dt-error",
2952 * Class name assigned to editable elements.
2954 * @property DataTable.CLASS_EDITABLE
2958 * @default "yui-dt-editable"
2960 CLASS_EDITABLE : "yui-dt-editable",
2963 * Class name assigned to draggable elements.
2965 * @property DataTable.CLASS_DRAGGABLE
2969 * @default "yui-dt-draggable"
2971 CLASS_DRAGGABLE : "yui-dt-draggable",
2974 * Class name assigned to resizeable elements.
2976 * @property DataTable.CLASS_RESIZEABLE
2980 * @default "yui-dt-resizeable"
2982 CLASS_RESIZEABLE : "yui-dt-resizeable",
2985 * Class name assigned to scrollable elements.
2987 * @property DataTable.CLASS_SCROLLABLE
2991 * @default "yui-dt-scrollable"
2993 CLASS_SCROLLABLE : "yui-dt-scrollable",
2996 * Class name assigned to sortable elements.
2998 * @property DataTable.CLASS_SORTABLE
3002 * @default "yui-dt-sortable"
3004 CLASS_SORTABLE : "yui-dt-sortable",
3007 * Class name assigned to ascending elements.
3009 * @property DataTable.CLASS_ASC
3013 * @default "yui-dt-asc"
3015 CLASS_ASC : "yui-dt-asc",
3018 * Class name assigned to descending elements.
3020 * @property DataTable.CLASS_DESC
3024 * @default "yui-dt-desc"
3026 CLASS_DESC : "yui-dt-desc",
3029 * Class name assigned to BUTTON elements and/or container elements.
3031 * @property DataTable.CLASS_BUTTON
3035 * @default "yui-dt-button"
3037 CLASS_BUTTON : "yui-dt-button",
3040 * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
3042 * @property DataTable.CLASS_CHECKBOX
3046 * @default "yui-dt-checkbox"
3048 CLASS_CHECKBOX : "yui-dt-checkbox",
3051 * Class name assigned to SELECT elements and/or container elements.
3053 * @property DataTable.CLASS_DROPDOWN
3057 * @default "yui-dt-dropdown"
3059 CLASS_DROPDOWN : "yui-dt-dropdown",
3062 * Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
3064 * @property DataTable.CLASS_RADIO
3068 * @default "yui-dt-radio"
3070 CLASS_RADIO : "yui-dt-radio",
3072 /////////////////////////////////////////////////////////////////////////
3074 // Private static properties
3076 /////////////////////////////////////////////////////////////////////////
3079 * Internal class variable for indexing multiple DataTable instances.
3081 * @property DataTable._nCount
3089 * Internal class variable tracking current number of DataTable instances,
3090 * so that certain class values can be reset when all instances are destroyed.
3092 * @property DataTable._nCurrentCount
3100 * Reference to the STYLE node that is dynamically created and updated
3101 * in order to manage Column widths.
3103 * @property DataTable._elDynStyleNode
3108 _elDynStyleNode : null,
3111 * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
3113 * @property DataTable._bDynStylesFallback
3118 _bDynStylesFallback : (ua.ie) ? true : false,
3121 * Object literal hash of Columns and their dynamically create style rules.
3123 * @property DataTable._oDynStyles
3131 * Element reference to shared Column drag target.
3133 * @property DataTable._elColumnDragTarget
3138 _elColumnDragTarget : null,
3141 * Element reference to shared Column resizer proxy.
3143 * @property DataTable._elColumnResizerProxy
3148 _elColumnResizerProxy : null,
3150 /////////////////////////////////////////////////////////////////////////
3152 // Private static methods
3154 /////////////////////////////////////////////////////////////////////////
3157 * Clones object literal or array of object literals.
3159 * @method DataTable._cloneObject
3160 * @param o {Object} Object.
3164 _cloneObject : function(o) {
3165 if(!lang.isValue(o)) {
3171 if(o instanceof YAHOO.widget.BaseCellEditor) {
3174 else if(lang.isFunction(o)) {
3177 else if(lang.isArray(o)) {
3179 for(var i=0,len=o.length;i<len;i++) {
3180 array[i] = DT._cloneObject(o[i]);
3184 else if(lang.isObject(o)) {
3186 if(lang.hasOwnProperty(o, x)) {
3187 if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
3188 copy[x] = DT._cloneObject(o[x]);
3204 * Destroys shared Column drag target.
3206 * @method DataTable._destroyColumnDragTargetEl
3210 _destroyColumnDragTargetEl : function() {
3211 if(DT._elColumnDragTarget) {
3212 var el = DT._elColumnDragTarget;
3213 YAHOO.util.Event.purgeElement(el);
3214 el.parentNode.removeChild(el);
3215 DT._elColumnDragTarget = null;
3221 * Creates HTML markup for shared Column drag target.
3223 * @method DataTable._initColumnDragTargetEl
3224 * @return {HTMLElement} Reference to Column drag target.
3228 _initColumnDragTargetEl : function() {
3229 if(!DT._elColumnDragTarget) {
3230 // Attach Column drag target element as first child of body
3231 var elColumnDragTarget = document.createElement('div');
3232 elColumnDragTarget.className = DT.CLASS_COLTARGET;
3233 elColumnDragTarget.style.display = "none";
3234 document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
3236 // Internal tracker of Column drag target
3237 DT._elColumnDragTarget = elColumnDragTarget;
3240 return DT._elColumnDragTarget;
3244 * Destroys shared Column resizer proxy.
3246 * @method DataTable._destroyColumnResizerProxyEl
3247 * @return {HTMLElement} Reference to Column resizer proxy.
3251 _destroyColumnResizerProxyEl : function() {
3252 if(DT._elColumnResizerProxy) {
3253 var el = DT._elColumnResizerProxy;
3254 YAHOO.util.Event.purgeElement(el);
3255 el.parentNode.removeChild(el);
3256 DT._elColumnResizerProxy = null;
3261 * Creates HTML markup for shared Column resizer proxy.
3263 * @method DataTable._initColumnResizerProxyEl
3264 * @return {HTMLElement} Reference to Column resizer proxy.
3268 _initColumnResizerProxyEl : function() {
3269 if(!DT._elColumnResizerProxy) {
3270 // Attach Column resizer element as first child of body
3271 var elColumnResizerProxy = document.createElement("div");
3272 elColumnResizerProxy.id = "yui-dt-colresizerproxy"; // Needed for ColumnResizer
3273 elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
3274 document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
3276 // Internal tracker of Column resizer proxy
3277 DT._elColumnResizerProxy = elColumnResizerProxy;
3279 return DT._elColumnResizerProxy;
3283 * Formats a BUTTON element.
3285 * @method DataTable.formatButton
3286 * @param el {HTMLElement} The element to format with markup.
3287 * @param oRecord {YAHOO.widget.Record} Record instance.
3288 * @param oColumn {YAHOO.widget.Column} Column instance.
3289 * @param oData {Object | Boolean} Data value for the cell. By default, the value
3290 * is what gets written to the BUTTON.
3293 formatButton : function(el, oRecord, oColumn, oData) {
3294 var sValue = lang.isValue(oData) ? oData : "Click";
3295 //TODO: support YAHOO.widget.Button
3296 //if(YAHOO.widget.Button) {
3300 el.innerHTML = "<button type=\"button\" class=\""+
3301 DT.CLASS_BUTTON + "\">" + sValue + "</button>";
3306 * Formats a CHECKBOX element.
3308 * @method DataTable.formatCheckbox
3309 * @param el {HTMLElement} The element to format with markup.
3310 * @param oRecord {YAHOO.widget.Record} Record instance.
3311 * @param oColumn {YAHOO.widget.Column} Column instance.
3312 * @param oData {Object | Boolean} Data value for the cell. Can be a simple
3313 * Boolean to indicate whether checkbox is checked or not. Can be object literal
3314 * {checked:bBoolean, label:sLabel}. Other forms of oData require a custom
3318 formatCheckbox : function(el, oRecord, oColumn, oData) {
3319 var bChecked = oData;
3320 bChecked = (bChecked) ? " checked=\"checked\"" : "";
3321 el.innerHTML = "<input type=\"checkbox\"" + bChecked +
3322 " class=\"" + DT.CLASS_CHECKBOX + "\" />";
3326 * Formats currency. Default unit is USD.
3328 * @method DataTable.formatCurrency
3329 * @param el {HTMLElement} The element to format with markup.
3330 * @param oRecord {YAHOO.widget.Record} Record instance.
3331 * @param oColumn {YAHOO.widget.Column} Column instance.
3332 * @param oData {Number} Data value for the cell.
3335 formatCurrency : function(el, oRecord, oColumn, oData) {
3336 el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || this.get("currencyOptions"));
3340 * Formats JavaScript Dates.
3342 * @method DataTable.formatDate
3343 * @param el {HTMLElement} The element to format with markup.
3344 * @param oRecord {YAHOO.widget.Record} Record instance.
3345 * @param oColumn {YAHOO.widget.Column} Column instance.
3346 * @param oData {Object} Data value for the cell, or null.
3349 formatDate : function(el, oRecord, oColumn, oData) {
3350 var oConfig = oColumn.dateOptions || this.get("dateOptions");
3351 el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
3355 * Formats SELECT elements.
3357 * @method DataTable.formatDropdown
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 formatDropdown : function(el, oRecord, oColumn, oData) {
3365 var selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
3366 options = (lang.isArray(oColumn.dropdownOptions)) ?
3367 oColumn.dropdownOptions : null,
3370 collection = el.getElementsByTagName("select");
3372 // Create the form element only once, so we can attach the onChange listener
3373 if(collection.length === 0) {
3374 // Create SELECT element
3375 selectEl = document.createElement("select");
3376 selectEl.className = DT.CLASS_DROPDOWN;
3377 selectEl = el.appendChild(selectEl);
3379 // Add event listener
3380 Ev.addListener(selectEl,"change",this._onDropdownChange,this);
3383 selectEl = collection[0];
3385 // Update the form element
3387 // Clear out previous options
3388 selectEl.innerHTML = "";
3390 // We have options to populate
3392 // Create OPTION elements
3393 for(var i=0; i<options.length; i++) {
3394 var option = options[i];
3395 var optionEl = document.createElement("option");
3396 optionEl.value = (lang.isValue(option.value)) ?
3397 option.value : option;
3398 // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
3399 optionEl.innerHTML = (lang.isValue(option.text)) ?
3400 option.text : (lang.isValue(option.label)) ? option.label : option;
3401 optionEl = selectEl.appendChild(optionEl);
3402 if (optionEl.value == selectedValue) {
3403 optionEl.selected = true;
3407 // Selected value is our only option
3409 selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
3413 el.innerHTML = lang.isValue(oData) ? oData : "";
3420 * @method DataTable.formatEmail
3421 * @param el {HTMLElement} The element to format with markup.
3422 * @param oRecord {YAHOO.widget.Record} Record instance.
3423 * @param oColumn {YAHOO.widget.Column} Column instance.
3424 * @param oData {Object} Data value for the cell, or null.
3427 formatEmail : function(el, oRecord, oColumn, oData) {
3428 if(lang.isString(oData)) {
3429 el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
3432 el.innerHTML = lang.isValue(oData) ? oData : "";
3439 * @method DataTable.formatLink
3440 * @param el {HTMLElement} The element to format with markup.
3441 * @param oRecord {YAHOO.widget.Record} Record instance.
3442 * @param oColumn {YAHOO.widget.Column} Column instance.
3443 * @param oData {Object} Data value for the cell, or null.
3446 formatLink : function(el, oRecord, oColumn, oData) {
3447 if(lang.isString(oData)) {
3448 el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
3451 el.innerHTML = lang.isValue(oData) ? oData : "";
3458 * @method DataTable.formatNumber
3459 * @param el {HTMLElement} The element to format with markup.
3460 * @param oRecord {YAHOO.widget.Record} Record instance.
3461 * @param oColumn {YAHOO.widget.Column} Column instance.
3462 * @param oData {Object} Data value for the cell, or null.
3465 formatNumber : function(el, oRecord, oColumn, oData) {
3466 el.innerHTML = util.Number.format(oData, oColumn.numberOptions || this.get("numberOptions"));
3470 * Formats INPUT TYPE=RADIO elements.
3472 * @method DataTable.formatRadio
3473 * @param el {HTMLElement} The element to format with markup.
3474 * @param oRecord {YAHOO.widget.Record} Record instance.
3475 * @param oColumn {YAHOO.widget.Column} Column instance.
3476 * @param oData {Object} (Optional) Data value for the cell.
3479 formatRadio : function(el, oRecord, oColumn, oData) {
3480 var bChecked = oData;
3481 bChecked = (bChecked) ? " checked=\"checked\"" : "";
3482 el.innerHTML = "<input type=\"radio\"" + bChecked +
3483 " name=\""+this.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
3484 " class=\"" + DT.CLASS_RADIO+ "\" />";
3488 * Formats text strings.
3490 * @method DataTable.formatText
3491 * @param el {HTMLElement} The element to format with markup.
3492 * @param oRecord {YAHOO.widget.Record} Record instance.
3493 * @param oColumn {YAHOO.widget.Column} Column instance.
3494 * @param oData {Object} (Optional) Data value for the cell.
3497 formatText : function(el, oRecord, oColumn, oData) {
3498 var value = (lang.isValue(oData)) ? oData : "";
3499 //TODO: move to util function
3500 el.innerHTML = value.toString().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
3504 * Formats TEXTAREA elements.
3506 * @method DataTable.formatTextarea
3507 * @param el {HTMLElement} The element to format with markup.
3508 * @param oRecord {YAHOO.widget.Record} Record instance.
3509 * @param oColumn {YAHOO.widget.Column} Column instance.
3510 * @param oData {Object} (Optional) Data value for the cell.
3513 formatTextarea : function(el, oRecord, oColumn, oData) {
3514 var value = (lang.isValue(oData)) ? oData : "",
3515 markup = "<textarea>" + value + "</textarea>";
3516 el.innerHTML = markup;
3520 * Formats INPUT TYPE=TEXT elements.
3522 * @method DataTable.formatTextbox
3523 * @param el {HTMLElement} The element to format with markup.
3524 * @param oRecord {YAHOO.widget.Record} Record instance.
3525 * @param oColumn {YAHOO.widget.Column} Column instance.
3526 * @param oData {Object} (Optional) Data value for the cell.
3529 formatTextbox : function(el, oRecord, oColumn, oData) {
3530 var value = (lang.isValue(oData)) ? oData : "",
3531 markup = "<input type=\"text\" value=\"" + value + "\" />";
3532 el.innerHTML = markup;
3536 * Default cell formatter
3538 * @method DataTable.formatDefault
3539 * @param el {HTMLElement} The element to format with markup.
3540 * @param oRecord {YAHOO.widget.Record} Record instance.
3541 * @param oColumn {YAHOO.widget.Column} Column instance.
3542 * @param oData {Object} (Optional) Data value for the cell.
3545 formatDefault : function(el, oRecord, oColumn, oData) {
3546 el.innerHTML = oData === undefined ||
3548 (typeof oData === 'number' && isNaN(oData)) ?
3549 " " : oData.toString();
3553 * Validates data value to type Number, doing type conversion as
3554 * necessary. A valid Number value is return, else null is returned
3555 * if input value does not validate.
3558 * @method DataTable.validateNumber
3559 * @param oData {Object} Data to validate.
3562 validateNumber : function(oData) {
3564 var number = oData * 1;
3567 if(lang.isNumber(number)) {
3571 YAHOO.log("Could not validate data " + lang.dump(oData) + " to type Number", "warn", this.toString());
3577 // Done in separate step so referenced functions are defined.
3579 * Cell formatting functions.
3580 * @property DataTable.Formatter
3585 button : DT.formatButton,
3586 checkbox : DT.formatCheckbox,
3587 currency : DT.formatCurrency,
3588 "date" : DT.formatDate,
3589 dropdown : DT.formatDropdown,
3590 email : DT.formatEmail,
3591 link : DT.formatLink,
3592 "number" : DT.formatNumber,
3593 radio : DT.formatRadio,
3594 text : DT.formatText,
3595 textarea : DT.formatTextarea,
3596 textbox : DT.formatTextbox,
3598 defaultFormatter : DT.formatDefault
3601 lang.extend(DT, util.Element, {
3603 /////////////////////////////////////////////////////////////////////////////
3605 // Superclass methods
3607 /////////////////////////////////////////////////////////////////////////////
3610 * Implementation of Element's abstract method. Sets up config values.
3612 * @method initAttributes
3613 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
3617 initAttributes : function(oConfigs) {
3618 oConfigs = oConfigs || {};
3619 DT.superclass.initAttributes.call(this, oConfigs);
3622 * @attribute summary
3623 * @description Value for the SUMMARY attribute.
3627 this.setAttributeConfig("summary", {
3629 validator: lang.isString,
3630 method: function(sSummary) {
3632 this._elTable.summary = sSummary;
3638 * @attribute selectionMode
3639 * @description Specifies row or cell selection mode. Accepts the following strings:
3641 * <dt>"standard"</dt>
3642 * <dd>Standard row selection with support for modifier keys to enable
3643 * multiple selections.</dd>
3646 * <dd>Row selection with modifier keys disabled to not allow
3647 * multiple selections.</dd>
3649 * <dt>"singlecell"</dt>
3650 * <dd>Cell selection with modifier keys disabled to not allow
3651 * multiple selections.</dd>
3653 * <dt>"cellblock"</dt>
3654 * <dd>Cell selection with support for modifier keys to enable multiple
3655 * selections in a block-fashion, like a spreadsheet.</dd>
3657 * <dt>"cellrange"</dt>
3658 * <dd>Cell selection with support for modifier keys to enable multiple
3659 * selections in a range-fashion, like a calendar.</dd>
3662 * @default "standard"
3665 this.setAttributeConfig("selectionMode", {
3667 validator: lang.isString
3671 * @attribute sortedBy
3672 * @description Object literal provides metadata for initial sort values if
3673 * data will arrive pre-sorted:
3675 * <dt>sortedBy.key</dt>
3676 * <dd>{String} Key of sorted Column</dd>
3677 * <dt>sortedBy.dir</dt>
3678 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3680 * @type Object | null
3682 this.setAttributeConfig("sortedBy", {
3684 // TODO: accepted array for nested sorts
3685 validator: function(oNewSortedBy) {
3687 return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
3690 return (oNewSortedBy === null);
3693 method: function(oNewSortedBy) {
3694 // Stash the previous value
3695 var oOldSortedBy = this.get("sortedBy");
3697 // Workaround for bug 1827195
3698 this._configs.sortedBy.value = oNewSortedBy;
3700 // Remove ASC/DESC from TH
3707 if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
3708 oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
3709 nOldColumnKeyIndex = oOldColumn.getKeyIndex();
3711 // Remove previous UI from THEAD
3712 var elOldTh = oOldColumn.getThEl();
3713 Dom.removeClass(elOldTh, oOldSortedBy.dir);
3714 this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
3717 oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
3718 nNewColumnKeyIndex = oNewColumn.getKeyIndex();
3720 // Update THEAD with new UI
3721 var elNewTh = oNewColumn.getThEl();
3722 // Backward compatibility
3723 if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") || (oNewSortedBy.dir == "desc"))) {
3724 var newClass = (oNewSortedBy.dir == "desc") ?
3727 Dom.addClass(elNewTh, newClass);
3730 var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
3731 Dom.addClass(elNewTh, sortClass);
3733 this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
3739 this._elTbody.style.display = "none";
3740 var allRows = this._elTbody.rows,
3742 for(var i=allRows.length-1; i>-1; i--) {
3743 allCells = allRows[i].childNodes;
3744 if(allCells[nOldColumnKeyIndex]) {
3745 Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
3747 if(allCells[nNewColumnKeyIndex]) {
3748 Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
3751 this._elTbody.style.display = "";
3754 this._clearTrTemplateEl();
3759 * @attribute paginator
3760 * @description An instance of YAHOO.widget.Paginator.
3762 * @type {Object|YAHOO.widget.Paginator}
3764 this.setAttributeConfig("paginator", {
3766 validator : function (val) {
3767 return val === null || val instanceof widget.Paginator;
3769 method : function () { this._updatePaginator.apply(this,arguments); }
3773 * @attribute caption
3774 * @description Value for the CAPTION element. NB: Not supported in
3775 * ScrollingDataTable.
3778 this.setAttributeConfig("caption", {
3780 validator: lang.isString,
3781 method: function(sCaption) {
3782 this._initCaptionEl(sCaption);
3787 * @attribute draggableColumns
3788 * @description True if Columns are draggable to reorder, false otherwise.
3789 * The Drag & Drop Utility is required to enable this feature. Only top-level
3790 * and non-nested Columns are draggable. Write once.
3794 this.setAttributeConfig("draggableColumns", {
3796 validator: lang.isBoolean,
3797 method: function(oParam) {
3800 this._initDraggableColumns();
3803 this._destroyDraggableColumns();
3810 * @attribute renderLoopSize
3811 * @description A value greater than 0 enables DOM rendering of rows to be
3812 * executed from a non-blocking timeout queue and sets how many rows to be
3813 * rendered per timeout. Recommended for very large data sets.
3817 this.setAttributeConfig("renderLoopSize", {
3819 validator: lang.isNumber
3823 * @attribute formatRow
3824 * @description A function that accepts a TR element and its associated Record
3825 * for custom formatting. The function must return TRUE in order to automatically
3826 * continue formatting of child TD elements, else TD elements will not be
3827 * automatically formatted.
3831 this.setAttributeConfig("formatRow", {
3833 validator: lang.isFunction
3837 * @attribute generateRequest
3838 * @description A function that converts an object literal of desired DataTable
3839 * states into a request value which is then passed to the DataSource's
3840 * sendRequest method in order to retrieve data for those states. This
3841 * function is passed an object literal of state data and a reference to the
3842 * DataTable instance:
3845 * <dt>pagination<dt>
3847 * <dt>offsetRecord</dt>
3848 * <dd>{Number} Index of the first Record of the desired page</dd>
3849 * <dt>rowsPerPage</dt>
3850 * <dd>{Number} Number of rows per page</dd>
3855 * <dd>{String} Key of sorted Column</dd>
3857 * <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3860 * <dd>The DataTable instance</dd>
3863 * and by default returns a String of syntax:
3864 * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
3866 * @default HTMLFunction
3868 this.setAttributeConfig("generateRequest", {
3869 value: function(oState, oSelf) {
3871 oState = oState || {pagination:null, sortedBy:null};
3872 var sort = encodeURIComponent((oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey());
3873 var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
3874 var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
3875 var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
3877 // Build the request
3878 return "sort=" + sort +
3880 "&startIndex=" + startIndex +
3881 ((results !== null) ? "&results=" + results : "");
3883 validator: lang.isFunction
3887 * @attribute initialRequest
3888 * @description Defines the initial request that gets sent to the DataSource
3889 * during initialization. Value is ignored if initialLoad is set to any value
3894 this.setAttributeConfig("initialRequest", {
3899 * @attribute initialLoad
3900 * @description Determines whether or not to load data at instantiation. By
3901 * default, will trigger a sendRequest() to the DataSource and pass in the
3902 * request defined by initialRequest. If set to false, data will not load
3903 * at instantiation. Alternatively, implementers who wish to work with a
3904 * custom payload may pass in an object literal with the following values:
3907 * <dt>request (MIXED)</dt>
3908 * <dd>Request value.</dd>
3910 * <dt>argument (MIXED)</dt>
3911 * <dd>Custom data that will be passed through to the callback function.</dd>
3915 * @type Boolean | Object
3918 this.setAttributeConfig("initialLoad", {
3923 * @attribute dynamicData
3924 * @description If true, sorting and pagination are relegated to the DataSource
3925 * for handling, using the request returned by the "generateRequest" function.
3926 * Each new DataSource response blows away all previous Records. False by default, so
3927 * sorting and pagination will be handled directly on the client side, without
3928 * causing any new requests for data from the DataSource.
3932 this.setAttributeConfig("dynamicData", {
3934 validator: lang.isBoolean
3938 * @attribute MSG_EMPTY
3939 * @description Message to display if DataTable has no data.
3941 * @default "No records found."
3943 this.setAttributeConfig("MSG_EMPTY", {
3944 value: "No records found.",
3945 validator: lang.isString
3949 * @attribute MSG_LOADING
3950 * @description Message to display while DataTable is loading data.
3952 * @default "Loading..."
3954 this.setAttributeConfig("MSG_LOADING", {
3955 value: "Loading...",
3956 validator: lang.isString
3960 * @attribute MSG_ERROR
3961 * @description Message to display while DataTable has data error.
3963 * @default "Data error."
3965 this.setAttributeConfig("MSG_ERROR", {
3966 value: "Data error.",
3967 validator: lang.isString
3971 * @attribute MSG_SORTASC
3972 * @description Message to display in tooltip to sort Column in ascending order.
3974 * @default "Click to sort ascending"
3976 this.setAttributeConfig("MSG_SORTASC", {
3977 value: "Click to sort ascending",
3978 validator: lang.isString,
3979 method: function(sParam) {
3981 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3982 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
3983 allKeys[i]._elThLabel.firstChild.title = sParam;
3991 * @attribute MSG_SORTDESC
3992 * @description Message to display in tooltip to sort Column in descending order.
3994 * @default "Click to sort descending"
3996 this.setAttributeConfig("MSG_SORTDESC", {
3997 value: "Click to sort descending",
3998 validator: lang.isString,
3999 method: function(sParam) {
4001 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
4002 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
4003 allKeys[i]._elThLabel.firstChild.title = sParam;
4011 * @attribute currencySymbol
4014 this.setAttributeConfig("currencySymbol", {
4016 validator: lang.isString
4020 * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
4021 * @attribute currencyOptions
4023 * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
4025 this.setAttributeConfig("currencyOptions", {
4027 prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
4029 decimalSeparator:".",
4030 thousandsSeparator:","
4035 * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
4036 * @attribute dateOptions
4038 * @default {format:"%m/%d/%Y", locale:"en"}
4040 this.setAttributeConfig("dateOptions", {
4041 value: {format:"%m/%d/%Y", locale:"en"}
4045 * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
4046 * @attribute numberOptions
4048 * @default {decimalPlaces:0, thousandsSeparator:","}
4050 this.setAttributeConfig("numberOptions", {
4053 thousandsSeparator:","
4059 /////////////////////////////////////////////////////////////////////////////
4061 // Private member variables
4063 /////////////////////////////////////////////////////////////////////////////
4066 * True if instance is initialized, so as to fire the initEvent after render.
4076 * Index assigned to instance.
4085 * Counter for IDs assigned to TR elements.
4087 * @property _nTrCount
4094 * Counter for IDs assigned to TD elements.
4096 * @property _nTdCount
4103 * Unique id assigned to instance "yui-dtN", useful prefix for generating unique
4104 * DOM ID strings and log messages.
4115 * @property _oChainRender
4116 * @type YAHOO.util.Chain
4119 _oChainRender : null,
4122 * DOM reference to the container element for the DataTable instance into which
4123 * all other elements get created.
4125 * @property _elContainer
4129 _elContainer : null,
4132 * DOM reference to the mask element for the DataTable instance which disables it.
4141 * DOM reference to the TABLE element for the DataTable instance.
4143 * @property _elTable
4150 * DOM reference to the CAPTION element for the DataTable instance.
4152 * @property _elCaption
4159 * DOM reference to the COLGROUP element for the DataTable instance.
4161 * @property _elColgroup
4168 * DOM reference to the THEAD element for the DataTable instance.
4170 * @property _elThead
4177 * DOM reference to the primary TBODY element for the DataTable instance.
4179 * @property _elTbody
4186 * DOM reference to the secondary TBODY element used to display DataTable messages.
4188 * @property _elMsgTbody
4195 * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
4197 * @property _elMsgTr
4204 * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
4206 * @property _elMsgTd
4213 * DataSource instance for the DataTable instance.
4215 * @property _oDataSource
4216 * @type YAHOO.util.DataSource
4219 _oDataSource : null,
4222 * ColumnSet instance for the DataTable instance.
4224 * @property _oColumnSet
4225 * @type YAHOO.widget.ColumnSet
4231 * RecordSet instance for the DataTable instance.
4233 * @property _oRecordSet
4234 * @type YAHOO.widget.RecordSet
4240 * The active CellEditor instance for the DataTable instance.
4242 * @property _oCellEditor
4243 * @type YAHOO.widget.CellEditor
4246 _oCellEditor : null,
4249 * ID string of first TR element of the current DataTable page.
4251 * @property _sFirstTrId
4258 * ID string of the last TR element of the current DataTable page.
4260 * @property _sLastTrId
4267 * Template row to create all new rows from.
4268 * @property _elTrTemplate
4269 * @type {HTMLElement}
4272 _elTrTemplate : null,
4275 * Sparse array of custom functions to set column widths for browsers that don't
4276 * support dynamic CSS rules. Functions are added at the index representing
4277 * the number of rows they update.
4279 * @property _aDynFunctions
4283 _aDynFunctions : [],
4313 /////////////////////////////////////////////////////////////////////////////
4317 /////////////////////////////////////////////////////////////////////////////
4320 * Clears browser text selection. Useful to call on rowSelectEvent or
4321 * cellSelectEvent to prevent clicks or dblclicks from selecting text in the
4324 * @method clearTextSelection
4326 clearTextSelection : function() {
4328 if(window.getSelection) {
4329 sel = window.getSelection();
4331 else if(document.getSelection) {
4332 sel = document.getSelection();
4334 else if(document.selection) {
4335 sel = document.selection;
4341 else if (sel.removeAllRanges) {
4342 sel.removeAllRanges();
4344 else if(sel.collapse) {
4351 * Sets focus on the given element.
4354 * @param el {HTMLElement} Element.
4357 _focusEl : function(el) {
4358 el = el || this._elTbody;
4359 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
4360 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
4361 // strange unexpected things as the user clicks on buttons and other controls.
4362 setTimeout(function() {
4372 * Forces Gecko repaint.
4374 * @method _repaintGecko
4375 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4378 _repaintGecko : (ua.gecko) ?
4380 el = el || this._elContainer;
4381 var parent = el.parentNode;
4382 var nextSibling = el.nextSibling;
4383 parent.insertBefore(parent.removeChild(el), nextSibling);
4387 * Forces Opera repaint.
4389 * @method _repaintOpera
4392 _repaintOpera : (ua.opera) ?
4395 document.documentElement.className += " ";
4396 document.documentElement.className = YAHOO.lang.trim(document.documentElement.className);
4401 * Forces Webkit repaint.
4403 * @method _repaintWebkit
4404 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4407 _repaintWebkit : (ua.webkit) ?
4409 el = el || this._elContainer;
4410 var parent = el.parentNode;
4411 var nextSibling = el.nextSibling;
4412 parent.insertBefore(parent.removeChild(el), nextSibling);
4439 * Initializes object literal of config values.
4441 * @method _initConfigs
4442 * @param oConfig {Object} Object literal of config values.
4445 _initConfigs : function(oConfigs) {
4446 if(!oConfigs || !lang.isObject(oConfigs)) {
4449 this.configs = oConfigs;
4453 * Initializes ColumnSet.
4455 * @method _initColumnSet
4456 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
4459 _initColumnSet : function(aColumnDefs) {
4460 var oColumn, i, len;
4462 if(this._oColumnSet) {
4463 // First clear _oDynStyles for existing ColumnSet and
4464 // uregister CellEditor Custom Events
4465 for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
4466 oColumn = this._oColumnSet.keys[i];
4467 DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
4468 if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
4469 oColumn.editor.unsubscribeAll();
4473 this._oColumnSet = null;
4474 this._clearTrTemplateEl();
4477 if(lang.isArray(aColumnDefs)) {
4478 this._oColumnSet = new YAHOO.widget.ColumnSet(aColumnDefs);
4480 // Backward compatibility
4481 else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
4482 this._oColumnSet = aColumnDefs;
4483 YAHOO.log("DataTable's constructor now requires an array" +
4484 " of object literal Column definitions instead of a ColumnSet instance",
4485 "warn", this.toString());
4488 // Register CellEditor Custom Events
4489 var allKeys = this._oColumnSet.keys;
4490 for(i=0, len=allKeys.length; i<len; i++) {
4491 oColumn = allKeys[i];
4492 if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
4493 oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
4494 oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
4495 oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
4496 oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
4497 oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
4498 oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
4499 oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
4500 oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
4506 * Initializes DataSource.
4508 * @method _initDataSource
4509 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
4512 _initDataSource : function(oDataSource) {
4513 this._oDataSource = null;
4514 if(oDataSource && (lang.isFunction(oDataSource.sendRequest))) {
4515 this._oDataSource = oDataSource;
4517 // Backward compatibility
4519 var tmpTable = null;
4520 var tmpContainer = this._elContainer;
4522 //TODO: this will break if re-initing DS at runtime for SDT
4523 // Peek in container child nodes to see if TABLE already exists
4524 if(tmpContainer.hasChildNodes()) {
4525 var tmpChildren = tmpContainer.childNodes;
4526 for(i=0; i<tmpChildren.length; i++) {
4527 if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
4528 tmpTable = tmpChildren[i];
4533 var tmpFieldsArray = [];
4534 for(; i<this._oColumnSet.keys.length; i++) {
4535 tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
4538 this._oDataSource = new DS(tmpTable);
4539 this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
4540 this._oDataSource.responseSchema = {fields: tmpFieldsArray};
4541 YAHOO.log("Null DataSource for progressive enhancement from" +
4542 " markup has been deprecated", "warn", this.toString());
4549 * Initializes RecordSet.
4551 * @method _initRecordSet
4554 _initRecordSet : function() {
4555 if(this._oRecordSet) {
4556 this._oRecordSet.reset();
4559 this._oRecordSet = new YAHOO.widget.RecordSet();
4564 * Initializes DOM elements.
4566 * @method _initDomElements
4567 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
4568 * return {Boolean} False in case of error, otherwise true
4571 _initDomElements : function(elContainer) {
4573 this._initContainerEl(elContainer);
4575 this._initTableEl(this._elContainer);
4577 this._initColgroupEl(this._elTable);
4579 this._initTheadEl(this._elTable);
4582 this._initMsgTbodyEl(this._elTable);
4585 this._initTbodyEl(this._elTable);
4587 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody) {
4596 * Destroy's the DataTable outer container element, if available.
4598 * @method _destroyContainerEl
4599 * @param elContainer {HTMLElement} Reference to the container element.
4602 _destroyContainerEl : function(elContainer) {
4603 Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
4604 Ev.purgeElement(elContainer, true);
4605 elContainer.innerHTML = "";
4607 this._elContainer = null;
4608 this._elColgroup = null;
4609 this._elThead = null;
4610 this._elTbody = null;
4614 * Initializes the DataTable outer container element, including a mask.
4616 * @method _initContainerEl
4617 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
4620 _initContainerEl : function(elContainer) {
4621 // Validate container
4622 elContainer = Dom.get(elContainer);
4624 if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
4626 this._destroyContainerEl(elContainer);
4628 Dom.addClass(elContainer, DT.CLASS_DATATABLE);
4629 Ev.addListener(elContainer, "focus", this._onTableFocus, this);
4630 Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
4631 this._elContainer = elContainer;
4633 var elMask = document.createElement("div");
4634 elMask.className = DT.CLASS_MASK;
4635 elMask.style.display = "none";
4636 this._elMask = elContainer.appendChild(elMask);
4641 * Destroy's the DataTable TABLE element, if available.
4643 * @method _destroyTableEl
4646 _destroyTableEl : function() {
4647 var elTable = this._elTable;
4649 Ev.purgeElement(elTable, true);
4650 elTable.parentNode.removeChild(elTable);
4651 this._elCaption = null;
4652 this._elColgroup = null;
4653 this._elThead = null;
4654 this._elTbody = null;
4659 * Creates HTML markup CAPTION element.
4661 * @method _initCaptionEl
4662 * @param sCaption {String} Text for caption.
4665 _initCaptionEl : function(sCaption) {
4666 if(this._elTable && sCaption) {
4667 // Create CAPTION element
4668 if(!this._elCaption) {
4669 this._elCaption = this._elTable.createCaption();
4671 // Set CAPTION value
4672 this._elCaption.innerHTML = sCaption;
4674 else if(this._elCaption) {
4675 this._elCaption.parentNode.removeChild(this._elCaption);
4680 * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
4681 * container element.
4683 * @method _initTableEl
4684 * @param elContainer {HTMLElement} Container element into which to create TABLE.
4687 _initTableEl : function(elContainer) {
4690 this._destroyTableEl();
4693 this._elTable = elContainer.appendChild(document.createElement("table"));
4695 // Set SUMMARY attribute
4696 this._elTable.summary = this.get("summary");
4698 // Create CAPTION element
4699 if(this.get("caption")) {
4700 this._initCaptionEl(this.get("caption"));
4706 * Destroy's the DataTable COLGROUP element, if available.
4708 * @method _destroyColgroupEl
4711 _destroyColgroupEl : function() {
4712 var elColgroup = this._elColgroup;
4714 var elTable = elColgroup.parentNode;
4715 Ev.purgeElement(elColgroup, true);
4716 elTable.removeChild(elColgroup);
4717 this._elColgroup = null;
4722 * Initializes COLGROUP and COL elements for managing minWidth.
4724 * @method _initColgroupEl
4725 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4728 _initColgroupEl : function(elTable) {
4731 this._destroyColgroupEl();
4733 // Add COLs to DOCUMENT FRAGMENT
4734 var allCols = this._aColIds || [],
4735 allKeys = this._oColumnSet.keys,
4736 i = 0, len = allCols.length,
4738 elFragment = document.createDocumentFragment(),
4739 elColTemplate = document.createElement("col");
4741 for(i=0,len=allKeys.length; i<len; i++) {
4742 oColumn = allKeys[i];
4743 elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
4747 var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
4748 elColgroup.appendChild(elFragment);
4749 this._elColgroup = elColgroup;
4754 * Adds a COL element to COLGROUP at given index.
4756 * @method _insertColgroupColEl
4757 * @param index {Number} Index of new COL element.
4760 _insertColgroupColEl : function(index) {
4761 if(lang.isNumber(index)&& this._elColgroup) {
4762 var nextSibling = this._elColgroup.childNodes[index] || null;
4763 this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
4768 * Removes a COL element to COLGROUP at given index.
4770 * @method _removeColgroupColEl
4771 * @param index {Number} Index of removed COL element.
4774 _removeColgroupColEl : function(index) {
4775 if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
4776 this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
4781 * Reorders a COL element from old index(es) to new index.
4783 * @method _reorderColgroupColEl
4784 * @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
4785 * @param newIndex {Number} New index.
4788 _reorderColgroupColEl : function(aKeyIndexes, newIndex) {
4789 if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) {
4793 for(i=aKeyIndexes.length-1; i>-1; i--) {
4794 tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
4797 var nextSibling = this._elColgroup.childNodes[newIndex] || null;
4798 for(i=tmpCols.length-1; i>-1; i--) {
4799 this._elColgroup.insertBefore(tmpCols[i], nextSibling);
4805 * Destroy's the DataTable THEAD element, if available.
4807 * @method _destroyTheadEl
4810 _destroyTheadEl : function() {
4811 var elThead = this._elThead;
4813 var elTable = elThead.parentNode;
4814 Ev.purgeElement(elThead, true);
4815 this._destroyColumnHelpers();
4816 elTable.removeChild(elThead);
4817 this._elThead = null;
4822 * Initializes THEAD element.
4824 * @method _initTheadEl
4825 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4826 * @param {HTMLElement} Initialized THEAD element.
4829 _initTheadEl : function(elTable) {
4830 elTable = elTable || this._elTable;
4834 this._destroyTheadEl();
4836 //TODO: append to DOM later for performance
4837 var elThead = (this._elColgroup) ?
4838 elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
4839 elTable.appendChild(document.createElement("thead"));
4841 // Set up DOM events for THEAD
4842 Ev.addListener(elThead, "focus", this._onTheadFocus, this);
4843 Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
4844 Ev.addListener(elThead, "mouseover", this._onTableMouseover, this);
4845 Ev.addListener(elThead, "mouseout", this._onTableMouseout, this);
4846 Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
4847 Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
4848 Ev.addListener(elThead, "click", this._onTheadClick, this);
4850 // Since we can't listen for click and dblclick on the same element...
4851 // Attach separately to THEAD and TBODY
4852 ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
4854 var oColumnSet = this._oColumnSet,
4857 // Add TRs to the THEAD
4858 var colTree = oColumnSet.tree;
4860 for(i=0; i<colTree.length; i++) {
4861 var elTheadTr = elThead.appendChild(document.createElement("tr"));
4863 // ...and create TH cells
4864 for(j=0; j<colTree[i].length; j++) {
4865 oColumn = colTree[i][j];
4866 elTh = elTheadTr.appendChild(document.createElement("th"));
4867 this._initThEl(elTh,oColumn);
4870 // Set FIRST/LAST on THEAD rows
4872 Dom.addClass(elTheadTr, DT.CLASS_FIRST);
4874 if(i === (colTree.length-1)) {
4875 Dom.addClass(elTheadTr, DT.CLASS_LAST);
4880 // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
4881 var aFirstHeaders = oColumnSet.headers[0] || [];
4882 for(i=0; i<aFirstHeaders.length; i++) {
4883 Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
4885 var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
4886 for(i=0; i<aLastHeaders.length; i++) {
4887 Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
4890 YAHOO.log("TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
4892 ///TODO: try _repaintGecko(this._elContainer) instead
4894 if(ua.webkit && ua.webkit < 420) {
4896 setTimeout(function() {
4897 elThead.style.display = "";
4899 elThead.style.display = 'none';
4902 this._elThead = elThead;
4904 // Column helpers needs _elThead to exist
4905 this._initColumnHelpers();
4910 * Populates TH element as defined by Column.
4913 * @param elTh {HTMLElement} TH element reference.
4914 * @param oColumn {YAHOO.widget.Column} Column object.
4917 _initThEl : function(elTh, oColumn) {
4918 elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
4919 elTh.innerHTML = "";
4920 elTh.rowSpan = oColumn.getRowspan();
4921 elTh.colSpan = oColumn.getColspan();
4922 oColumn._elTh = elTh;
4924 var elThLiner = elTh.appendChild(document.createElement("div"));
4925 elThLiner.id = elTh.id + "-liner"; // Needed for resizer
4926 elThLiner.className = DT.CLASS_LINER;
4927 oColumn._elThLiner = elThLiner;
4929 var elThLabel = elThLiner.appendChild(document.createElement("span"));
4930 elThLabel.className = DT.CLASS_LABEL;
4932 // Assign abbr attribute
4934 elTh.abbr = oColumn.abbr;
4936 // Clear minWidth on hidden Columns
4937 if(oColumn.hidden) {
4938 this._clearMinWidth(oColumn);
4941 elTh.className = this._getColumnClassNames(oColumn);
4943 // Set Column width...
4945 // Validate minWidth
4946 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
4947 oColumn.minWidth : oColumn.width;
4948 // ...for fallback cases
4949 if(DT._bDynStylesFallback) {
4950 elTh.firstChild.style.overflow = 'hidden';
4951 elTh.firstChild.style.width = nWidth + 'px';
4953 // ...for non fallback cases
4955 this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
4959 this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
4960 oColumn._elThLabel = elThLabel;
4964 * Outputs markup into the given TH based on given Column.
4966 * @method DataTable.formatTheadCell
4967 * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
4968 * not the liner DIV element.
4969 * @param oColumn {YAHOO.widget.Column} Column instance.
4970 * @param oSortedBy {Object} Sort state object literal.
4972 formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
4973 var sKey = oColumn.getKey();
4974 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
4976 // Add accessibility link for sortable Columns
4977 if(oColumn.sortable) {
4978 // Calculate the direction
4979 var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
4980 var bDesc = (sSortClass === DT.CLASS_DESC);
4982 // This is the sorted Column
4983 if(oSortedBy && (oColumn.key === oSortedBy.key)) {
4984 bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
4987 // Generate a unique HREF for visited status
4988 var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
4990 // Generate a dynamic TITLE for sort status
4991 var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
4993 // Format the element
4994 elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
4996 // Just display the label for non-sortable Columns
4998 elCellLabel.innerHTML = sLabel;
5003 * Disables DD from top-level Column TH elements.
5005 * @method _destroyDraggableColumns
5008 _destroyDraggableColumns : function() {
5010 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
5011 oColumn = this._oColumnSet.tree[0][i];
5013 oColumn._dd = oColumn._dd.unreg();
5014 Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);
5020 * Initializes top-level Column TH elements into DD instances.
5022 * @method _initDraggableColumns
5025 _initDraggableColumns : function() {
5026 this._destroyDraggableColumns();
5028 var oColumn, elTh, elDragTarget;
5029 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
5030 oColumn = this._oColumnSet.tree[0][i];
5031 elTh = oColumn.getThEl();
5032 Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
5033 elDragTarget = DT._initColumnDragTargetEl();
5034 oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
5038 YAHOO.log("Could not find DragDrop for draggable Columns", "warn", this.toString());
5043 * Disables resizeability on key Column TH elements.
5045 * @method _destroyResizeableColumns
5048 _destroyResizeableColumns : function() {
5049 var aKeys = this._oColumnSet.keys;
5050 for(var i=0, len=aKeys.length; i<len; i++) {
5051 if(aKeys[i]._ddResizer) {
5052 aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
5053 Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
5059 * Initializes resizeability on key Column TH elements.
5061 * @method _initResizeableColumns
5064 _initResizeableColumns : function() {
5065 this._destroyResizeableColumns();
5067 var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
5068 for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
5069 oColumn = this._oColumnSet.keys[i];
5070 if(oColumn.resizeable) {
5071 elTh = oColumn.getThEl();
5072 Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
5073 elThLiner = oColumn.getThLinerEl();
5075 // Bug 1915349: So resizer is as tall as TH when rowspan > 1
5076 // Create a separate resizer liner with position:relative
5077 elThResizerLiner = elTh.appendChild(document.createElement("div"));
5078 elThResizerLiner.className = DT.CLASS_RESIZERLINER;
5080 // Move TH contents into the new resizer liner
5081 elThResizerLiner.appendChild(elThLiner);
5083 // Create the resizer
5084 elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
5085 elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
5086 elThResizer.className = DT.CLASS_RESIZER;
5087 oColumn._elResizer = elThResizer;
5089 // Create the resizer proxy, once globally
5090 elResizerProxy = DT._initColumnResizerProxyEl();
5091 oColumn._ddResizer = new YAHOO.util.ColumnResizer(
5092 this, oColumn, elTh, elThResizer, elResizerProxy);
5093 cancelClick = function(e) {
5094 Ev.stopPropagation(e);
5096 Ev.addListener(elThResizer,"click",cancelClick);
5101 YAHOO.log("Could not find DragDrop for resizeable Columns", "warn", this.toString());
5106 * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
5108 * @method _destroyColumnHelpers
5111 _destroyColumnHelpers : function() {
5112 this._destroyDraggableColumns();
5113 this._destroyResizeableColumns();
5117 * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
5119 * @method _initColumnHelpers
5122 _initColumnHelpers : function() {
5123 if(this.get("draggableColumns")) {
5124 this._initDraggableColumns();
5126 this._initResizeableColumns();
5130 * Destroy's the DataTable TBODY element, if available.
5132 * @method _destroyTbodyEl
5135 _destroyTbodyEl : function() {
5136 var elTbody = this._elTbody;
5138 var elTable = elTbody.parentNode;
5139 Ev.purgeElement(elTbody, true);
5140 elTable.removeChild(elTbody);
5141 this._elTbody = null;
5146 * Initializes TBODY element for data.
5148 * @method _initTbodyEl
5149 * @param elTable {HTMLElement} TABLE element into which to create TBODY .
5152 _initTbodyEl : function(elTable) {
5155 this._destroyTbodyEl();
5158 var elTbody = elTable.appendChild(document.createElement("tbody"));
5159 elTbody.tabIndex = 0;
5160 elTbody.className = DT.CLASS_DATA;
5162 // Set up DOM events for TBODY
5163 Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
5164 Ev.addListener(elTbody, "mouseover", this._onTableMouseover, this);
5165 Ev.addListener(elTbody, "mouseout", this._onTableMouseout, this);
5166 Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
5167 Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
5168 Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
5169 Ev.addListener(elTbody, "keypress", this._onTableKeypress, this);
5170 Ev.addListener(elTbody, "click", this._onTbodyClick, this);
5172 // Since we can't listen for click and dblclick on the same element...
5173 // Attach separately to THEAD and TBODY
5174 ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
5177 // IE puts focus outline in the wrong place
5179 elTbody.hideFocus=true;
5182 this._elTbody = elTbody;
5187 * Destroy's the DataTable message TBODY element, if available.
5189 * @method _destroyMsgTbodyEl
5192 _destroyMsgTbodyEl : function() {
5193 var elMsgTbody = this._elMsgTbody;
5195 var elTable = elMsgTbody.parentNode;
5196 Ev.purgeElement(elMsgTbody, true);
5197 elTable.removeChild(elMsgTbody);
5198 this._elTbody = null;
5203 * Initializes TBODY element for messaging.
5205 * @method _initMsgTbodyEl
5206 * @param elTable {HTMLElement} TABLE element into which to create TBODY
5209 _initMsgTbodyEl : function(elTable) {
5211 var elMsgTbody = document.createElement("tbody");
5212 elMsgTbody.className = DT.CLASS_MESSAGE;
5213 var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
5214 elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5215 this._elMsgTr = elMsgTr;
5216 var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
5217 elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
5218 elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5219 this._elMsgTd = elMsgTd;
5220 elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
5221 var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
5222 elMsgLiner.className = DT.CLASS_LINER;
5223 this._elMsgTbody = elMsgTbody;
5225 // Set up DOM events for TBODY
5226 Ev.addListener(elMsgTbody, "focus", this._onTbodyFocus, this);
5227 Ev.addListener(elMsgTbody, "mouseover", this._onTableMouseover, this);
5228 Ev.addListener(elMsgTbody, "mouseout", this._onTableMouseout, this);
5229 Ev.addListener(elMsgTbody, "mousedown", this._onTableMousedown, this);
5230 Ev.addListener(elMsgTbody, "mouseup", this._onTableMouseup, this);
5231 Ev.addListener(elMsgTbody, "keydown", this._onTbodyKeydown, this);
5232 Ev.addListener(elMsgTbody, "keypress", this._onTableKeypress, this);
5233 Ev.addListener(elMsgTbody, "click", this._onTbodyClick, this);
5238 * Initialize internal event listeners
5240 * @method _initEvents
5243 _initEvents : function () {
5244 // Initialize Column sort
5245 this._initColumnSort();
5247 // Add the document level click listener
5248 YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
5250 // Paginator integration
5251 this.subscribe("paginatorChange",function () {
5252 this._handlePaginatorChange.apply(this,arguments);
5255 this.subscribe("initEvent",function () {
5256 this.renderPaginator();
5259 // Initialize CellEditor integration
5260 this._initCellEditing();
5264 * Initializes Column sorting.
5266 * @method _initColumnSort
5269 _initColumnSort : function() {
5270 this.subscribe("theadCellClickEvent", this.onEventSortColumn);
5272 // Backward compatibility
5273 var oSortedBy = this.get("sortedBy");
5275 if(oSortedBy.dir == "desc") {
5276 this._configs.sortedBy.value.dir = DT.CLASS_DESC;
5278 else if(oSortedBy.dir == "asc") {
5279 this._configs.sortedBy.value.dir = DT.CLASS_ASC;
5285 * Initializes CellEditor integration.
5287 * @method _initCellEditing
5290 _initCellEditing : function() {
5291 this.subscribe("editorBlurEvent",function () {
5292 this.onEditorBlurEvent.apply(this,arguments);
5294 this.subscribe("editorBlockEvent",function () {
5295 this.onEditorBlockEvent.apply(this,arguments);
5297 this.subscribe("editorUnblockEvent",function () {
5298 this.onEditorUnblockEvent.apply(this,arguments);
5334 // DOM MUTATION FUNCTIONS
5337 * Retruns classnames to represent current Column states.
5338 * @method _getColumnClassnames
5339 * @param oColumn {YAHOO.widget.Column} Column instance.
5340 * @param aAddClasses {String[]} An array of additional classnames to add to the
5342 * @return {String} A String of classnames to be assigned to TH or TD elements
5346 _getColumnClassNames : function (oColumn, aAddClasses) {
5350 if(lang.isString(oColumn.className)) {
5351 // Single custom class
5352 allClasses = [oColumn.className];
5354 else if(lang.isArray(oColumn.className)) {
5355 // Array of custom classes
5356 allClasses = oColumn.className;
5359 // no custom classes
5363 // Hook for setting width with via dynamic style uses key since ID is too disposable
5364 allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
5366 // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
5367 allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
5369 var isSortedBy = this.get("sortedBy") || {};
5371 if(oColumn.key === isSortedBy.key) {
5372 allClasses[allClasses.length] = isSortedBy.dir || '';
5375 if(oColumn.hidden) {
5376 allClasses[allClasses.length] = DT.CLASS_HIDDEN;
5379 if(oColumn.selected) {
5380 allClasses[allClasses.length] = DT.CLASS_SELECTED;
5383 if(oColumn.sortable) {
5384 allClasses[allClasses.length] = DT.CLASS_SORTABLE;
5387 if(oColumn.resizeable) {
5388 allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
5391 if(oColumn.editor) {
5392 allClasses[allClasses.length] = DT.CLASS_EDITABLE;
5395 // Addtnl classes, including First/Last
5397 allClasses = allClasses.concat(aAddClasses);
5400 return allClasses.join(' ');
5404 * Clears TR element template in response to any Column state change.
5405 * @method _clearTrTemplateEl
5408 _clearTrTemplateEl : function () {
5409 this._elTrTemplate = null;
5413 * Returns a new TR element template with TD elements classed with current
5415 * @method _getTrTemplateEl
5416 * @return {HTMLElement} A TR element to be cloned and added to the DOM.
5419 _getTrTemplateEl : function (oRecord, index) {
5420 // Template is already available
5421 if(this._elTrTemplate) {
5422 return this._elTrTemplate;
5424 // Template needs to be created
5427 tr = d.createElement('tr'),
5428 td = d.createElement('td'),
5429 div = d.createElement('div');
5431 // Append the liner element
5432 td.appendChild(div);
5434 // Create TD elements into DOCUMENT FRAGMENT
5435 var df = document.createDocumentFragment(),
5436 allKeys = this._oColumnSet.keys,
5439 // Set state for each TD;
5441 for(var i=0, keysLen=allKeys.length; i<keysLen; i++) {
5442 // Clone the TD template
5443 elTd = td.cloneNode(true);
5445 // Format the base TD
5446 elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
5448 df.appendChild(elTd);
5451 this._elTrTemplate = tr;
5457 * Formats a basic TD element.
5458 * @method _formatTdEl
5459 * @param oColumn {YAHOO.widget.Column} Associated Column instance.
5460 * @param elTd {HTMLElement} An unformatted TD element.
5461 * @param index {Number} Column key index.
5462 * @param isLast {Boolean} True if Column is last key of the ColumnSet.
5463 * @return {HTMLElement} A formatted TD element.
5466 _formatTdEl : function (oColumn, elTd, index, isLast) {
5467 var oColumnSet = this._oColumnSet;
5469 // Set the TD's accessibility headers
5470 var allHeaders = oColumnSet.headers,
5471 allColHeaders = allHeaders[index],
5474 for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
5475 sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
5476 sTdHeaders += sHeader;
5478 elTd.headers = sTdHeaders;
5480 // Class the TD element
5481 var aAddClasses = [];
5483 aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
5486 aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
5488 elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
5490 // Class the liner element
5491 elTd.firstChild.className = DT.CLASS_LINER;
5493 // Set Column width for fallback cases
5494 if(oColumn.width && DT._bDynStylesFallback) {
5495 // Validate minWidth
5496 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
5497 oColumn.minWidth : oColumn.width;
5498 elTd.firstChild.style.overflow = 'hidden';
5499 elTd.firstChild.style.width = nWidth + 'px';
5507 * Create a new TR element for a given Record and appends it with the correct
5508 * number of Column-state-classed TD elements. Striping is the responsibility of
5509 * the calling function, which may decide to stripe the single row, a subset of
5510 * rows, or all the rows.
5511 * @method _createTrEl
5512 * @param oRecord {YAHOO.widget.Record} Record instance
5513 * @return {HTMLElement} The new TR element. This must be added to the DOM.
5516 _addTrEl : function (oRecord) {
5517 var elTrTemplate = this._getTrTemplateEl();
5519 // Clone the TR template.
5520 var elTr = elTrTemplate.cloneNode(true);
5523 return this._updateTrEl(elTr,oRecord);
5527 * Formats the contents of the given TR's TD elements with data from the given
5528 * Record. Only innerHTML should change, nothing structural.
5530 * @method _updateTrEl
5531 * @param elTr {HTMLElement} The TR element to update.
5532 * @param oRecord {YAHOO.widget.Record} The associated Record instance.
5533 * @return {HTMLElement} DOM reference to the new TR element.
5536 _updateTrEl : function(elTr, oRecord) {
5537 var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
5539 // Hide the row to prevent constant reflows
5540 elTr.style.display = 'none';
5542 // Update TD elements with new data
5543 var allTds = elTr.childNodes,
5545 for(var i=0,len=allTds.length; i<len; ++i) {
5548 // Set the cell content
5549 this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
5552 // Redisplay the row for reflow
5553 elTr.style.display = '';
5556 elTr.id = oRecord.getId(); // Needed for Record association and tracking of FIRST/LAST
5562 * Deletes TR element by DOM reference or by DataTable page row index.
5564 * @method _deleteTrEl
5565 * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
5566 * @return {Boolean} Returns true if successful, else returns false.
5569 _deleteTrEl : function(row) {
5572 // Get page row index for the element
5573 if(!lang.isNumber(row)) {
5574 rowIndex = Dom.get(row).sectionRowIndex;
5579 if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
5580 // Cannot use tbody.deleteRow due to IE6 instability
5581 //return this._elTbody.deleteRow(rowIndex);
5582 return this._elTbody.removeChild(this.getTrEl(row));
5615 // CSS/STATE FUNCTIONS
5621 * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
5622 * of the DataTable page and updates internal tracker.
5624 * @method _unsetFirstRow
5627 _unsetFirstRow : function() {
5629 if(this._sFirstTrId) {
5630 Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
5631 this._sFirstTrId = null;
5636 * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
5637 * of the DataTable page and updates internal tracker.
5639 * @method _setFirstRow
5642 _setFirstRow : function() {
5643 this._unsetFirstRow();
5644 var elTr = this.getFirstTrEl();
5647 Dom.addClass(elTr, DT.CLASS_FIRST);
5648 this._sFirstTrId = elTr.id;
5653 * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
5654 * of the DataTable page and updates internal tracker.
5656 * @method _unsetLastRow
5659 _unsetLastRow : function() {
5660 // Unassign previous class
5661 if(this._sLastTrId) {
5662 Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
5663 this._sLastTrId = null;
5668 * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
5669 * of the DataTable page and updates internal tracker.
5671 * @method _setLastRow
5674 _setLastRow : function() {
5675 this._unsetLastRow();
5676 var elTr = this.getLastTrEl();
5679 Dom.addClass(elTr, DT.CLASS_LAST);
5680 this._sLastTrId = elTr.id;
5685 * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
5687 * @method _setRowStripes
5688 * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
5689 * or string ID, or page row index of where to start striping.
5690 * @param range {Number} (optional) If given, how many rows to stripe, otherwise
5691 * stripe all the rows until the end.
5694 _setRowStripes : function(row, range) {
5695 // Default values stripe all rows
5696 var allRows = this._elTbody.rows,
5698 nEndIndex = allRows.length,
5699 aOdds = [], nOddIdx = 0,
5700 aEvens = [], nEvenIdx = 0;
5703 if((row !== null) && (row !== undefined)) {
5704 // Validate given start row
5705 var elStartRow = this.getTrEl(row);
5707 nStartIndex = elStartRow.sectionRowIndex;
5709 // Validate given range
5710 if(lang.isNumber(range) && (range > 1)) {
5711 nEndIndex = nStartIndex + range;
5716 for(var i=nStartIndex; i<nEndIndex; i++) {
5718 aOdds[nOddIdx++] = allRows[i];
5720 aEvens[nEvenIdx++] = allRows[i];
5725 Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
5728 if (aEvens.length) {
5729 Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
5734 * Assigns the class DT.CLASS_SELECTED to TR and TD elements.
5736 * @method _setSelections
5739 _setSelections : function() {
5740 // Keep track of selected rows
5741 var allSelectedRows = this.getSelectedRows();
5742 // Keep track of selected cells
5743 var allSelectedCells = this.getSelectedCells();
5744 // Anything to select?
5745 if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
5746 var oColumnSet = this._oColumnSet,
5748 // Loop over each row
5749 for(var i=0; i<allSelectedRows.length; i++) {
5750 el = Dom.get(allSelectedRows[i]);
5752 Dom.addClass(el, DT.CLASS_SELECTED);
5755 // Loop over each cell
5756 for(i=0; i<allSelectedCells.length; i++) {
5757 el = Dom.get(allSelectedCells[i].recordId);
5759 Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
5807 /////////////////////////////////////////////////////////////////////////////
5809 // Private DOM Event Handlers
5811 /////////////////////////////////////////////////////////////////////////////
5814 * Validates minWidths whenever the render chain ends.
5816 * @method _onRenderChainEnd
5819 _onRenderChainEnd : function() {
5820 // Hide loading message
5821 this.hideTableMessage();
5823 // Show empty message
5824 if(this._elTbody.rows.length === 0) {
5825 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
5828 // Execute in timeout thread to give implementers a chance
5829 // to subscribe after the constructor
5831 setTimeout(function() {
5832 if((oSelf instanceof DT) && oSelf._sId) {
5835 oSelf._bInit = false;
5836 oSelf.fireEvent("initEvent");
5840 oSelf.fireEvent("renderEvent");
5841 // Backward compatibility
5842 oSelf.fireEvent("refreshEvent");
5843 YAHOO.log("DataTable rendered", "info", oSelf.toString());
5845 // Post-render routine
5846 oSelf.validateColumnWidths();
5848 // Post-render event
5849 oSelf.fireEvent("postRenderEvent");
5851 /*if(YAHOO.example.Performance.trialStart) {
5852 YAHOO.log((new Date()).getTime() - YAHOO.example.Performance.trialStart.getTime() + " ms", "time");
5853 YAHOO.example.Performance.trialStart = null;
5856 YAHOO.log("Post-render routine executed", "info", oSelf.toString());
5862 * Handles click events on the DOCUMENT.
5864 * @method _onDocumentClick
5865 * @param e {HTMLEvent} The click event.
5866 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5869 _onDocumentClick : function(e, oSelf) {
5870 var elTarget = Ev.getTarget(e);
5871 var elTag = elTarget.nodeName.toLowerCase();
5873 if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
5874 oSelf.fireEvent("tableBlurEvent");
5876 // Fires editorBlurEvent when click is not within the TABLE.
5877 // For cases when click is within the TABLE, due to timing issues,
5878 // the editorBlurEvent needs to get fired by the lower-level DOM click
5879 // handlers below rather than by the TABLE click handler directly.
5880 if(oSelf._oCellEditor) {
5881 if(oSelf._oCellEditor.getContainerEl) {
5882 var elContainer = oSelf._oCellEditor.getContainerEl();
5883 // Only if the click was not within the CellEditor container
5884 if(!Dom.isAncestor(elContainer, elTarget) &&
5885 (elContainer.id !== elTarget.id)) {
5886 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
5889 // Backward Compatibility
5890 else if(oSelf._oCellEditor.isActive) {
5891 // Only if the click was not within the Cell Editor container
5892 if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
5893 (oSelf._oCellEditor.container.id !== elTarget.id)) {
5894 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
5902 * Handles focus events on the DataTable instance.
5904 * @method _onTableFocus
5905 * @param e {HTMLEvent} The focus event.
5906 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5909 _onTableFocus : function(e, oSelf) {
5910 oSelf.fireEvent("tableFocusEvent");
5914 * Handles focus events on the THEAD element.
5916 * @method _onTheadFocus
5917 * @param e {HTMLEvent} The focus event.
5918 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5921 _onTheadFocus : function(e, oSelf) {
5922 oSelf.fireEvent("theadFocusEvent");
5923 oSelf.fireEvent("tableFocusEvent");
5927 * Handles focus events on the TBODY element.
5929 * @method _onTbodyFocus
5930 * @param e {HTMLEvent} The focus event.
5931 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5934 _onTbodyFocus : function(e, oSelf) {
5935 oSelf.fireEvent("tbodyFocusEvent");
5936 oSelf.fireEvent("tableFocusEvent");
5940 * Handles mouseover events on the DataTable instance.
5942 * @method _onTableMouseover
5943 * @param e {HTMLEvent} The mouseover event.
5944 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5947 _onTableMouseover : function(e, oSelf) {
5948 var elTarget = Ev.getTarget(e);
5949 var elTag = elTarget.nodeName.toLowerCase();
5950 var bKeepBubbling = true;
5951 while(elTarget && (elTag != "table")) {
5958 bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
5961 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5962 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
5963 // Backward compatibility
5964 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
5968 bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
5969 // Backward compatibility
5970 bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
5973 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5974 bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
5975 // Backward compatibility
5976 bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
5979 bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
5985 if(bKeepBubbling === false) {
5989 elTarget = elTarget.parentNode;
5991 elTag = elTarget.nodeName.toLowerCase();
5995 oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
5999 * Handles mouseout events on the DataTable instance.
6001 * @method _onTableMouseout
6002 * @param e {HTMLEvent} The mouseout event.
6003 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6006 _onTableMouseout : function(e, oSelf) {
6007 var elTarget = Ev.getTarget(e);
6008 var elTag = elTarget.nodeName.toLowerCase();
6009 var bKeepBubbling = true;
6010 while(elTarget && (elTag != "table")) {
6017 bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
6020 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6021 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
6022 // Backward compatibility
6023 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
6027 bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
6028 // Backward compatibility
6029 bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
6032 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6033 bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
6034 // Backward compatibility
6035 bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
6038 bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
6044 if(bKeepBubbling === false) {
6048 elTarget = elTarget.parentNode;
6050 elTag = elTarget.nodeName.toLowerCase();
6054 oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
6058 * Handles mousedown events on the DataTable instance.
6060 * @method _onTableMousedown
6061 * @param e {HTMLEvent} The mousedown event.
6062 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6065 _onTableMousedown : function(e, oSelf) {
6066 var elTarget = Ev.getTarget(e);
6067 var elTag = elTarget.nodeName.toLowerCase();
6068 var bKeepBubbling = true;
6069 while(elTarget && (elTag != "table")) {
6076 bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
6079 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6080 bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
6081 // Backward compatibility
6082 bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
6086 bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
6087 // Backward compatibility
6088 bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
6091 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6092 bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
6093 // Backward compatibility
6094 bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
6097 bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
6103 if(bKeepBubbling === false) {
6107 elTarget = elTarget.parentNode;
6109 elTag = elTarget.nodeName.toLowerCase();
6113 oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
6117 * Handles mouseup events on the DataTable instance.
6119 * @method _onTableMouseup
6120 * @param e {HTMLEvent} The mouseup event.
6121 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6124 _onTableMouseup : function(e, oSelf) {
6125 var elTarget = Ev.getTarget(e);
6126 var elTag = elTarget.nodeName.toLowerCase();
6127 var bKeepBubbling = true;
6128 while(elTarget && (elTag != "table")) {
6135 bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
6138 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6139 bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
6140 // Backward compatibility
6141 bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
6145 bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
6146 // Backward compatibility
6147 bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
6150 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6151 bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
6152 // Backward compatibility
6153 bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
6156 bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
6162 if(bKeepBubbling === false) {
6166 elTarget = elTarget.parentNode;
6168 elTag = elTarget.nodeName.toLowerCase();
6172 oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
6176 * Handles dblclick events on the DataTable instance.
6178 * @method _onTableDblclick
6179 * @param e {HTMLEvent} The dblclick event.
6180 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6183 _onTableDblclick : function(e, oSelf) {
6184 var elTarget = Ev.getTarget(e);
6185 var elTag = elTarget.nodeName.toLowerCase();
6186 var bKeepBubbling = true;
6187 while(elTarget && (elTag != "table")) {
6192 bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
6195 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6196 bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
6197 // Backward compatibility
6198 bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
6202 bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
6203 // Backward compatibility
6204 bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
6207 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6208 bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
6209 // Backward compatibility
6210 bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
6213 bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
6219 if(bKeepBubbling === false) {
6223 elTarget = elTarget.parentNode;
6225 elTag = elTarget.nodeName.toLowerCase();
6229 oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6232 * Handles keydown events on the THEAD element.
6234 * @method _onTheadKeydown
6235 * @param e {HTMLEvent} The key event.
6236 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6239 _onTheadKeydown : function(e, oSelf) {
6240 var elTarget = Ev.getTarget(e);
6241 var elTag = elTarget.nodeName.toLowerCase();
6242 var bKeepBubbling = true;
6243 while(elTarget && (elTag != "table")) {
6249 // TODO: implement textareaKeyEvent
6252 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
6257 if(bKeepBubbling === false) {
6261 elTarget = elTarget.parentNode;
6263 elTag = elTarget.nodeName.toLowerCase();
6267 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6271 * Handles keydown events on the TBODY element. Handles selection behavior,
6272 * provides hooks for ENTER to edit functionality.
6274 * @method _onTbodyKeydown
6275 * @param e {HTMLEvent} The key event.
6276 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6279 _onTbodyKeydown : function(e, oSelf) {
6280 var sMode = oSelf.get("selectionMode");
6282 if(sMode == "standard") {
6283 oSelf._handleStandardSelectionByKey(e);
6285 else if(sMode == "single") {
6286 oSelf._handleSingleSelectionByKey(e);
6288 else if(sMode == "cellblock") {
6289 oSelf._handleCellBlockSelectionByKey(e);
6291 else if(sMode == "cellrange") {
6292 oSelf._handleCellRangeSelectionByKey(e);
6294 else if(sMode == "singlecell") {
6295 oSelf._handleSingleCellSelectionByKey(e);
6298 if(oSelf._oCellEditor) {
6299 if(oSelf._oCellEditor.fireEvent) {
6300 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6302 else if(oSelf._oCellEditor.isActive) {
6303 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6307 var elTarget = Ev.getTarget(e);
6308 var elTag = elTarget.nodeName.toLowerCase();
6309 var bKeepBubbling = true;
6310 while(elTarget && (elTag != "table")) {
6315 bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
6320 if(bKeepBubbling === false) {
6324 elTarget = elTarget.parentNode;
6326 elTag = elTarget.nodeName.toLowerCase();
6330 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6334 * Handles keypress events on the TABLE. Mainly to support stopEvent on Mac.
6336 * @method _onTableKeypress
6337 * @param e {HTMLEvent} The key event.
6338 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6341 _onTableKeypress : function(e, oSelf) {
6342 if(ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") !== -1) && (ua.webkit < 420)) {
6343 var nKey = Ev.getCharCode(e);
6349 else if(nKey == 38) {
6356 * Handles click events on the THEAD element.
6358 * @method _onTheadClick
6359 * @param e {HTMLEvent} The click event.
6360 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6363 _onTheadClick : function(e, oSelf) {
6364 // This blurs the CellEditor
6365 if(oSelf._oCellEditor) {
6366 if(oSelf._oCellEditor.fireEvent) {
6367 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6369 // Backward compatibility
6370 else if(oSelf._oCellEditor.isActive) {
6371 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6375 var elTarget = Ev.getTarget(e),
6376 elTag = elTarget.nodeName.toLowerCase(),
6377 bKeepBubbling = true;
6378 while(elTarget && (elTag != "table")) {
6383 var sType = elTarget.type.toLowerCase();
6384 if(sType == "checkbox") {
6385 bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
6387 else if(sType == "radio") {
6388 bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
6390 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6391 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6395 bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
6398 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6401 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6402 bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
6403 // Backward compatibility
6404 bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
6408 bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
6409 // Backward compatibility
6410 bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
6413 bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
6414 // Backward compatibility
6415 bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
6420 if(bKeepBubbling === false) {
6424 elTarget = elTarget.parentNode;
6426 elTag = elTarget.nodeName.toLowerCase();
6430 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6434 * Handles click events on the primary TBODY element.
6436 * @method _onTbodyClick
6437 * @param e {HTMLEvent} The click event.
6438 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6441 _onTbodyClick : function(e, oSelf) {
6442 // This blurs the CellEditor
6443 if(oSelf._oCellEditor) {
6444 if(oSelf._oCellEditor.fireEvent) {
6445 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6447 else if(oSelf._oCellEditor.isActive) {
6448 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6452 // Fire Custom Events
6453 var elTarget = Ev.getTarget(e),
6454 elTag = elTarget.nodeName.toLowerCase(),
6455 bKeepBubbling = true;
6456 while(elTarget && (elTag != "table")) {
6461 var sType = elTarget.type.toLowerCase();
6462 if(sType == "checkbox") {
6463 bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
6465 else if(sType == "radio") {
6466 bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
6468 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6469 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6473 bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
6476 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6479 bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
6482 bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
6487 if(bKeepBubbling === false) {
6491 elTarget = elTarget.parentNode;
6493 elTag = elTarget.nodeName.toLowerCase();
6497 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6501 * Handles change events on SELECT elements within DataTable.
6503 * @method _onDropdownChange
6504 * @param e {HTMLEvent} The change event.
6505 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6508 _onDropdownChange : function(e, oSelf) {
6509 var elTarget = Ev.getTarget(e);
6510 oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
6544 /////////////////////////////////////////////////////////////////////////////
6546 // Public member variables
6548 /////////////////////////////////////////////////////////////////////////////
6550 * Returns object literal of initial configs.
6559 /////////////////////////////////////////////////////////////////////////////
6563 /////////////////////////////////////////////////////////////////////////////
6566 * Returns unique id assigned to instance, which is a useful prefix for
6567 * generating unique DOM ID strings.
6570 * @return {String} Unique ID of the DataSource instance.
6572 getId : function() {
6577 * DataSource instance name, for logging.
6580 * @return {String} Unique name of the DataSource instance.
6583 toString : function() {
6584 return "DataTable instance " + this._sId;
6588 * Returns the DataTable instance's DataSource instance.
6590 * @method getDataSource
6591 * @return {YAHOO.util.DataSource} DataSource instance.
6593 getDataSource : function() {
6594 return this._oDataSource;
6598 * Returns the DataTable instance's ColumnSet instance.
6600 * @method getColumnSet
6601 * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
6603 getColumnSet : function() {
6604 return this._oColumnSet;
6608 * Returns the DataTable instance's RecordSet instance.
6610 * @method getRecordSet
6611 * @return {YAHOO.widget.RecordSet} RecordSet instance.
6613 getRecordSet : function() {
6614 return this._oRecordSet;
6618 * Returns on object literal representing the DataTable instance's current
6619 * state with the following properties:
6621 * <dt>pagination</dt>
6622 * <dd>Instance of YAHOO.widget.Paginator</dd>
6627 * <dt>sortedBy.key</dt>
6628 * <dd>{String} Key of sorted Column</dd>
6629 * <dt>sortedBy.dir</dt>
6630 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
6634 * <dt>selectedRows</dt>
6635 * <dd>Array of selected rows by Record ID.</dd>
6637 * <dt>selectedCells</dt>
6638 * <dd>Selected cells as an array of object literals:
6639 * {recordId:sRecordId, columnKey:sColumnKey}</dd>
6643 * @return {Object} DataTable instance state object literal values.
6645 getState : function() {
6647 totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
6648 pagination: this.get("paginator") ? this.get("paginator").getState() : null,
6649 sortedBy: this.get("sortedBy"),
6650 selectedRows: this.getSelectedRows(),
6651 selectedCells: this.getSelectedCells()
6700 * Returns DOM reference to the DataTable's container element.
6702 * @method getContainerEl
6703 * @return {HTMLElement} Reference to DIV element.
6705 getContainerEl : function() {
6706 return this._elContainer;
6710 * Returns DOM reference to the DataTable's TABLE element.
6712 * @method getTableEl
6713 * @return {HTMLElement} Reference to TABLE element.
6715 getTableEl : function() {
6716 return this._elTable;
6720 * Returns DOM reference to the DataTable's THEAD element.
6722 * @method getTheadEl
6723 * @return {HTMLElement} Reference to THEAD element.
6725 getTheadEl : function() {
6726 return this._elThead;
6730 * Returns DOM reference to the DataTable's primary TBODY element.
6732 * @method getTbodyEl
6733 * @return {HTMLElement} Reference to TBODY element.
6735 getTbodyEl : function() {
6736 return this._elTbody;
6740 * Returns DOM reference to the DataTable's secondary TBODY element that is
6741 * used to display messages.
6743 * @method getMsgTbodyEl
6744 * @return {HTMLElement} Reference to TBODY element.
6746 getMsgTbodyEl : function() {
6747 return this._elMsgTbody;
6751 * Returns DOM reference to the TD element within the secondary TBODY that is
6752 * used to display messages.
6754 * @method getMsgTdEl
6755 * @return {HTMLElement} Reference to TD element.
6757 getMsgTdEl : function() {
6758 return this._elMsgTd;
6762 * Returns the corresponding TR reference for a given DOM element, ID string or
6763 * directly page row index. If the given identifier is a child of a TR element,
6764 * then DOM tree is traversed until a parent TR element is returned, otherwise
6768 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
6769 * get: by element reference, ID string, page row index, or Record.
6770 * @return {HTMLElement} Reference to TR element, or null.
6772 getTrEl : function(row) {
6774 if(row instanceof YAHOO.widget.Record) {
6775 return document.getElementById(row.getId());
6777 // By page row index
6778 else if(lang.isNumber(row)) {
6779 var allRows = this._elTbody.rows;
6780 return ((row > -1) && (row < allRows.length)) ? allRows[row] : null;
6782 // By ID string or element reference
6784 var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
6786 // Validate HTML element
6787 if(elRow && (elRow.ownerDocument == document)) {
6788 // Validate TR element
6789 if(elRow.nodeName.toLowerCase() != "tr") {
6790 // Traverse up the DOM to find the corresponding TR element
6791 elRow = Dom.getAncestorByTagName(elRow,"tr");
6802 * Returns DOM reference to the first TR element in the DataTable page, or null.
6804 * @method getFirstTrEl
6805 * @return {HTMLElement} Reference to TR element.
6807 getFirstTrEl : function() {
6808 return this._elTbody.rows[0] || null;
6812 * Returns DOM reference to the last TR element in the DataTable page, or null.
6814 * @method getLastTrEl
6815 * @return {HTMLElement} Reference to last TR element.
6817 getLastTrEl : function() {
6818 var allRows = this._elTbody.rows;
6819 if(allRows.length > 0) {
6820 return allRows[allRows.length-1] || null;
6825 * Returns DOM reference to the next TR element from the given TR element, or null.
6827 * @method getNextTrEl
6828 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6829 * reference, ID string, page row index, or Record from which to get next TR element.
6830 * @return {HTMLElement} Reference to next TR element.
6832 getNextTrEl : function(row) {
6833 var nThisTrIndex = this.getTrIndex(row);
6834 if(nThisTrIndex !== null) {
6835 var allRows = this._elTbody.rows;
6836 if(nThisTrIndex < allRows.length-1) {
6837 return allRows[nThisTrIndex+1];
6841 YAHOO.log("Could not get next TR element for row " + row, "info", this.toString());
6846 * Returns DOM reference to the previous TR element from the given TR element, or null.
6848 * @method getPreviousTrEl
6849 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6850 * reference, ID string, page row index, or Record from which to get previous TR element.
6851 * @return {HTMLElement} Reference to previous TR element.
6853 getPreviousTrEl : function(row) {
6854 var nThisTrIndex = this.getTrIndex(row);
6855 if(nThisTrIndex !== null) {
6856 var allRows = this._elTbody.rows;
6857 if(nThisTrIndex > 0) {
6858 return allRows[nThisTrIndex-1];
6862 YAHOO.log("Could not get previous TR element for row " + row, "info", this.toString());
6867 * Returns DOM reference to a TD liner element.
6869 * @method getTdLinerEl
6870 * @param cell {HTMLElement | Object} TD element or child of a TD element, or
6871 * object literal of syntax {record:oRecord, column:oColumn}.
6872 * @return {HTMLElement} Reference to TD liner element.
6874 getTdLinerEl : function(cell) {
6875 var elCell = this.getTdEl(cell);
6876 return elCell.firstChild || null;
6880 * Returns DOM reference to a TD element.
6883 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
6884 * object literal of syntax {record:oRecord, column:oColumn}.
6885 * @return {HTMLElement} Reference to TD element.
6887 getTdEl : function(cell) {
6889 var el = Dom.get(cell);
6891 // Validate HTML element
6892 if(el && (el.ownerDocument == document)) {
6893 // Validate TD element
6894 if(el.nodeName.toLowerCase() != "td") {
6895 // Traverse up the DOM to find the corresponding TR element
6896 elCell = Dom.getAncestorByTagName(el, "td");
6902 // Make sure the TD is in this TBODY
6903 // Bug 2527707 and bug 2263558
6904 if(elCell && ((elCell.parentNode.parentNode == this._elTbody) || (elCell.parentNode.parentNode === null))) {
6905 // Now we can return the TD element
6910 var oRecord, nColKeyIndex;
6912 if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
6913 oRecord = this.getRecord(cell.recordId);
6914 var oColumn = this.getColumn(cell.columnKey);
6916 nColKeyIndex = oColumn.getKeyIndex();
6920 if(cell.record && cell.column && cell.column.getKeyIndex) {
6921 oRecord = cell.record;
6922 nColKeyIndex = cell.column.getKeyIndex();
6924 var elRow = this.getTrEl(oRecord);
6925 if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
6926 return elRow.cells[nColKeyIndex] || null;
6934 * Returns DOM reference to the first TD element in the DataTable page (by default),
6935 * the first TD element of the optionally given row, or null.
6937 * @method getFirstTdEl
6938 * @param row {HTMLElement} (optional) row from which to get first TD
6939 * @return {HTMLElement} Reference to TD element.
6941 getFirstTdEl : function(row) {
6942 var elRow = this.getTrEl(row) || this.getFirstTrEl();
6943 if(elRow && (elRow.cells.length > 0)) {
6944 return elRow.cells[0];
6946 YAHOO.log("Could not get first TD element for row " + elRow, "info", this.toString());
6951 * Returns DOM reference to the last TD element in the DataTable page (by default),
6952 * the first TD element of the optionally given row, or null.
6954 * @method getLastTdEl
6955 * @return {HTMLElement} Reference to last TD element.
6957 getLastTdEl : function(row) {
6958 var elRow = this.getTrEl(row) || this.getLastTrEl();
6959 if(elRow && (elRow.cells.length > 0)) {
6960 return elRow.cells[elRow.cells.length-1];
6962 YAHOO.log("Could not get last TD element for row " + elRow, "info", this.toString());
6967 * Returns DOM reference to the next TD element from the given cell, or null.
6969 * @method getNextTdEl
6970 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6971 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6972 * @return {HTMLElement} Reference to next TD element, or null.
6974 getNextTdEl : function(cell) {
6975 var elCell = this.getTdEl(cell);
6977 var nThisTdIndex = elCell.cellIndex;
6978 var elRow = this.getTrEl(elCell);
6979 if(nThisTdIndex < elRow.cells.length-1) {
6980 return elRow.cells[nThisTdIndex+1];
6983 var elNextRow = this.getNextTrEl(elRow);
6985 return elNextRow.cells[0];
6989 YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
6994 * Returns DOM reference to the previous TD element from the given cell, or null.
6996 * @method getPreviousTdEl
6997 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6998 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
6999 * @return {HTMLElement} Reference to previous TD element, or null.
7001 getPreviousTdEl : function(cell) {
7002 var elCell = this.getTdEl(cell);
7004 var nThisTdIndex = elCell.cellIndex;
7005 var elRow = this.getTrEl(elCell);
7006 if(nThisTdIndex > 0) {
7007 return elRow.cells[nThisTdIndex-1];
7010 var elPreviousRow = this.getPreviousTrEl(elRow);
7012 return this.getLastTdEl(elPreviousRow);
7016 YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
7021 * Returns DOM reference to the above TD element from the given cell, or null.
7023 * @method getAboveTdEl
7024 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
7025 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
7026 * @return {HTMLElement} Reference to next TD element, or null.
7028 getAboveTdEl : function(cell) {
7029 var elCell = this.getTdEl(cell);
7031 var elPreviousRow = this.getPreviousTrEl(elCell);
7033 return elPreviousRow.cells[elCell.cellIndex];
7036 YAHOO.log("Could not get above TD element for cell " + cell, "info", this.toString());
7041 * Returns DOM reference to the below TD element from the given cell, or null.
7043 * @method getBelowTdEl
7044 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
7045 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
7046 * @return {HTMLElement} Reference to previous TD element, or null.
7048 getBelowTdEl : function(cell) {
7049 var elCell = this.getTdEl(cell);
7051 var elNextRow = this.getNextTrEl(elCell);
7053 return elNextRow.cells[elCell.cellIndex];
7056 YAHOO.log("Could not get below TD element for cell " + cell, "info", this.toString());
7061 * Returns DOM reference to a TH liner element. Needed to normalize for resizeable
7062 * Columns, which have an additional resizer liner DIV element between the TH
7063 * element and the liner DIV element.
7065 * @method getThLinerEl
7066 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
7067 * DOM element reference, or string ID.
7068 * @return {HTMLElement} Reference to TH liner element.
7070 getThLinerEl : function(theadCell) {
7071 var oColumn = this.getColumn(theadCell);
7072 return (oColumn) ? oColumn.getThLinerEl() : null;
7076 * Returns DOM reference to a TH element.
7079 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
7080 * DOM element reference, or string ID.
7081 * @return {HTMLElement} Reference to TH element.
7083 getThEl : function(theadCell) {
7086 // Validate Column instance
7087 if(theadCell instanceof YAHOO.widget.Column) {
7088 var oColumn = theadCell;
7089 elTh = oColumn.getThEl();
7094 // Validate HTML element
7096 var el = Dom.get(theadCell);
7098 if(el && (el.ownerDocument == document)) {
7099 // Validate TH element
7100 if(el.nodeName.toLowerCase() != "th") {
7101 // Traverse up the DOM to find the corresponding TR element
7102 elTh = Dom.getAncestorByTagName(el,"th");
7116 * Returns the page row index of given row. Returns null if the row is not on the
7117 * current DataTable page.
7119 * @method getTrIndex
7120 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
7121 * string reference to an element within the DataTable page, a Record instance,
7122 * or a Record's RecordSet index.
7123 * @return {Number} Page row index, or null if row does not exist or is not on current page.
7125 getTrIndex : function(row) {
7129 if(row instanceof YAHOO.widget.Record) {
7130 nRecordIndex = this._oRecordSet.getRecordIndex(row);
7131 if(nRecordIndex === null) {
7132 // Not a valid Record
7136 // Calculate page row index from Record index
7137 else if(lang.isNumber(row)) {
7140 if(lang.isNumber(nRecordIndex)) {
7141 // Validate the number
7142 if((nRecordIndex > -1) && (nRecordIndex < this._oRecordSet.getLength())) {
7143 // DataTable is paginated
7144 var oPaginator = this.get('paginator');
7146 // Check the record index is within the indices of the
7148 var rng = oPaginator.getPageRecords();
7149 if (rng && nRecordIndex >= rng[0] && nRecordIndex <= rng[1]) {
7150 // This Record is on current page
7151 return nRecordIndex - rng[0];
7153 // This Record is not on current page
7158 // Not paginated, just return the Record index
7160 return nRecordIndex;
7163 // RecordSet index is out of range
7168 // By element reference or ID string
7170 // Validate TR element
7171 var elRow = this.getTrEl(row);
7172 if(elRow && (elRow.ownerDocument == document) &&
7173 (elRow.parentNode == this._elTbody)) {
7174 return elRow.sectionRowIndex;
7178 YAHOO.log("Could not get page row index for row " + row, "info", this.toString());
7230 * Resets a RecordSet with the given data and populates the page view
7231 * with the new data. Any previous data, and selection and sort states are
7232 * cleared. New data should be added as a separate step.
7234 * @method initializeTable
7236 initializeTable : function() {
7240 // Clear the RecordSet
7241 this._oRecordSet.reset();
7243 // Clear the Paginator's totalRecords if paginating
7244 var pag = this.get('paginator');
7246 pag.set('totalRecords',0);
7250 this._unselectAllTrEls();
7251 this._unselectAllTdEls();
7252 this._aSelections = null;
7253 this._oAnchorRecord = null;
7254 this._oAnchorCell = null;
7257 this.set("sortedBy", null);
7261 * Internal wrapper calls run() on render Chain instance.
7263 * @method _runRenderChain
7266 _runRenderChain : function() {
7267 this._oChainRender.run();
7271 * Renders the view with existing Records from the RecordSet while
7272 * maintaining sort, pagination, and selection states. For performance, reuses
7273 * existing DOM elements when possible while deleting extraneous elements.
7277 render : function() {
7278 //YAHOO.example.Performance.trialStart = new Date();
7280 this._oChainRender.stop();
7282 this.fireEvent("beforeRenderEvent");
7283 YAHOO.log("DataTable rendering...", "info", this.toString());
7285 var i, j, k, len, allRecords;
7287 var oPaginator = this.get('paginator');
7288 // Paginator is enabled, show a subset of Records and update Paginator UI
7290 allRecords = this._oRecordSet.getRecords(
7291 oPaginator.getStartIndex(),
7292 oPaginator.getRowsPerPage());
7294 // Not paginated, show all records
7296 allRecords = this._oRecordSet.getRecords();
7299 // From the top, update in-place existing rows, so as to reuse DOM elements
7300 var elTbody = this._elTbody,
7301 loopN = this.get("renderLoopSize"),
7302 nRecordsLength = allRecords.length;
7305 if(nRecordsLength > 0) {
7306 elTbody.style.display = "none";
7307 while(elTbody.lastChild) {
7308 elTbody.removeChild(elTbody.lastChild);
7310 elTbody.style.display = "";
7312 // Set up the loop Chain to render rows
7313 this._oChainRender.add({
7314 method: function(oArg) {
7315 if((this instanceof DT) && this._sId) {
7316 var i = oArg.nCurrentRecord,
7317 endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
7318 nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
7321 elTbody.style.display = "none";
7323 for(; i<endRecordIndex; i++) {
7324 elRow = Dom.get(allRecords[i].getId());
7325 elRow = elRow || this._addTrEl(allRecords[i]);
7326 nextSibling = elTbody.childNodes[i] || null;
7327 elTbody.insertBefore(elRow, nextSibling);
7329 elTbody.style.display = "";
7331 // Set up for the next loop
7332 oArg.nCurrentRecord = i;
7336 iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
7338 nCurrentRecord: 0,//nRecordsLength-1, // Start at first Record
7339 nLoopLength: (loopN > 0) ? loopN : nRecordsLength
7341 timeout: (loopN > 0) ? 0 : -1
7344 // Post-render tasks
7345 this._oChainRender.add({
7346 method: function(oArg) {
7347 if((this instanceof DT) && this._sId) {
7348 while(elTbody.rows.length > nRecordsLength) {
7349 elTbody.removeChild(elTbody.lastChild);
7351 this._setFirstRow();
7353 this._setRowStripes();
7354 this._setSelections();
7358 timeout: (loopN > 0) ? 0 : -1
7362 // Table has no rows
7364 // Set up the loop Chain to delete rows
7365 var nTotal = elTbody.rows.length;
7367 this._oChainRender.add({
7368 method: function(oArg) {
7369 if((this instanceof DT) && this._sId) {
7370 var i = oArg.nCurrent,
7371 loopN = oArg.nLoopLength,
7372 nIterEnd = (i - loopN < 0) ? -1 : i - loopN;
7374 elTbody.style.display = "none";
7376 for(; i>nIterEnd; i--) {
7377 elTbody.deleteRow(-1);
7379 elTbody.style.display = "";
7381 // Set up for the next loop
7386 iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
7389 nLoopLength: (loopN > 0) ? loopN : nTotal
7391 timeout: (loopN > 0) ? 0 : -1
7395 this._runRenderChain();
7399 * Disables DataTable UI.
7403 disable : function() {
7404 var elTable = this._elTable;
7405 var elMask = this._elMask;
7406 elMask.style.width = elTable.offsetWidth + "px";
7407 elMask.style.height = elTable.offsetHeight + "px";
7408 elMask.style.display = "";
7409 this.fireEvent("disableEvent");
7413 * Undisables DataTable UI.
7417 undisable : function() {
7418 this._elMask.style.display = "none";
7419 this.fireEvent("undisableEvent");
7423 * Nulls out the entire DataTable instance and related objects, removes attached
7424 * event listeners, and clears out DOM elements inside the container. After
7425 * calling this method, the instance reference should be expliclitly nulled by
7426 * implementer, as in myDataTable = null. Use with caution!
7430 destroy : function() {
7432 var instanceName = this.toString();
7434 this._oChainRender.stop();
7436 // Destroy static resizer proxy and column proxy
7437 DT._destroyColumnDragTargetEl();
7438 DT._destroyColumnResizerProxyEl();
7440 // Destroy ColumnDD and ColumnResizers
7441 this._destroyColumnHelpers();
7443 // Destroy all CellEditors
7445 for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
7446 oCellEditor = this._oColumnSet.flat[i].editor;
7447 if(oCellEditor && oCellEditor.destroy) {
7448 oCellEditor.destroy();
7449 this._oColumnSet.flat[i].editor = null;
7453 // Destroy Paginator
7454 this._destroyPaginator();
7456 // Unhook custom events
7457 this._oRecordSet.unsubscribeAll();
7458 this.unsubscribeAll();
7460 // Unhook DOM events
7461 Ev.removeListener(document, "click", this._onDocumentClick);
7463 // Clear out the container
7464 this._destroyContainerEl(this._elContainer);
7467 for(var param in this) {
7468 if(lang.hasOwnProperty(this, param)) {
7473 // Clean up static values
7474 DT._nCurrentCount--;
7476 if(DT._nCurrentCount < 1) {
7477 if(DT._elDynStyleNode) {
7478 document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
7479 DT._elDynStyleNode = null;
7483 YAHOO.log("DataTable instance destroyed: " + instanceName);
7487 * Displays message within secondary TBODY.
7489 * @method showTableMessage
7490 * @param sHTML {String} (optional) Value for innerHTMlang.
7491 * @param sClassName {String} (optional) Classname.
7493 showTableMessage : function(sHTML, sClassName) {
7494 var elCell = this._elMsgTd;
7495 if(lang.isString(sHTML)) {
7496 elCell.firstChild.innerHTML = sHTML;
7498 if(lang.isString(sClassName)) {
7499 elCell.className = sClassName;
7502 this._elMsgTbody.style.display = "";
7504 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
7505 YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
7509 * Hides secondary TBODY.
7511 * @method hideTableMessage
7513 hideTableMessage : function() {
7514 if(this._elMsgTbody.style.display != "none") {
7515 this._elMsgTbody.style.display = "none";
7516 this._elMsgTbody.parentNode.style.width = "";
7517 this.fireEvent("tableMsgHideEvent");
7518 YAHOO.log("DataTable message hidden", "info", this.toString());
7523 * Brings focus to the TBODY element. Alias to focusTbodyEl.
7527 focus : function() {
7528 this.focusTbodyEl();
7532 * Brings focus to the THEAD element.
7534 * @method focusTheadEl
7536 focusTheadEl : function() {
7537 this._focusEl(this._elThead);
7541 * Brings focus to the TBODY element.
7543 * @method focusTbodyEl
7545 focusTbodyEl : function() {
7546 this._focusEl(this._elTbody);
7550 * Setting display:none on DataTable or any parent may impact width validations.
7551 * After setting display back to "", implementers should call this method to
7552 * manually perform those validations.
7556 onShow : function() {
7557 this.validateColumnWidths();
7559 for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
7561 if(col._ddResizer) {
7562 col._ddResizer.resetResizerEl();
7633 // RECORDSET FUNCTIONS
7636 * Returns Record index for given TR element or page row index.
7638 * @method getRecordIndex
7639 * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
7640 * element reference or page row index.
7641 * @return {Number} Record's RecordSet index, or null.
7643 getRecordIndex : function(row) {
7646 if(!lang.isNumber(row)) {
7648 if(row instanceof YAHOO.widget.Record) {
7649 return this._oRecordSet.getRecordIndex(row);
7651 // By element reference
7653 // Find the TR element
7654 var el = this.getTrEl(row);
7656 nTrIndex = el.sectionRowIndex;
7660 // By page row index
7665 if(lang.isNumber(nTrIndex)) {
7666 var oPaginator = this.get("paginator");
7668 return oPaginator.get('recordOffset') + nTrIndex;
7675 YAHOO.log("Could not get Record index for row " + row, "info", this.toString());
7680 * For the given identifier, returns the associated Record instance.
7683 * @param row {HTMLElement | Number | String} DOM reference to a TR element (or
7684 * child of a TR element), RecordSet position index, or Record ID.
7685 * @return {YAHOO.widget.Record} Record instance.
7687 getRecord : function(row) {
7688 var oRecord = this._oRecordSet.getRecord(row);
7691 // Validate TR element
7692 var elRow = this.getTrEl(row);
7694 oRecord = this._oRecordSet.getRecord(elRow.id);
7698 if(oRecord instanceof YAHOO.widget.Record) {
7699 return this._oRecordSet.getRecord(oRecord);
7702 YAHOO.log("Could not get Record for row at " + row, "info", this.toString());
7755 * For the given identifier, returns the associated Column instance. Note: For
7756 * getting Columns by Column ID string, please use the method getColumnById().
7759 * @param column {HTMLElement | String | Number} TH/TD element (or child of a
7760 * TH/TD element), a Column key, or a ColumnSet key index.
7761 * @return {YAHOO.widget.Column} Column instance.
7763 getColumn : function(column) {
7764 var oColumn = this._oColumnSet.getColumn(column);
7767 // Validate TD element
7768 var elCell = this.getTdEl(column);
7770 oColumn = this._oColumnSet.getColumn(elCell.cellIndex);
7772 // Validate TH element
7774 elCell = this.getThEl(column);
7777 var allColumns = this._oColumnSet.flat;
7778 for(var i=0, len=allColumns.length; i<len; i++) {
7779 if(allColumns[i].getThEl().id === elCell.id) {
7780 oColumn = allColumns[i];
7787 YAHOO.log("Could not get Column for column at " + column, "info", this.toString());
7793 * For the given Column ID, returns the associated Column instance. Note: For
7794 * getting Columns by key, please use the method getColumn().
7796 * @method getColumnById
7797 * @param column {String} Column ID string.
7798 * @return {YAHOO.widget.Column} Column instance.
7800 getColumnById : function(column) {
7801 return this._oColumnSet.getColumnById(column);
7805 * For the given Column instance, returns next direction to sort.
7807 * @method getColumnSortDir
7808 * @param oColumn {YAHOO.widget.Column} Column instance.
7809 * @param oSortedBy {Object} (optional) Specify the state, or use current state.
7810 * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
7812 getColumnSortDir : function(oColumn, oSortedBy) {
7813 // Backward compatibility
7814 if(oColumn.sortOptions && oColumn.sortOptions.defaultOrder) {
7815 if(oColumn.sortOptions.defaultOrder == "asc") {
7816 oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
7818 else if (oColumn.sortOptions.defaultOrder == "desc") {
7819 oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
7823 // What is the Column's default sort direction?
7824 var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
7826 // Is the Column currently sorted?
7827 var bSorted = false;
7828 oSortedBy = oSortedBy || this.get("sortedBy");
7829 if(oSortedBy && (oSortedBy.key === oColumn.key)) {
7832 sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7835 sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7842 * Overridable method gives implementers a hook to show loading message before
7845 * @method doBeforeSortColumn
7846 * @param oColumn {YAHOO.widget.Column} Column instance.
7847 * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
7848 * YAHOO.widget.DataTable.CLASS_DESC.
7849 * @return {Boolean} Return true to continue sorting Column.
7851 doBeforeSortColumn : function(oColumn, sSortDir) {
7852 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
7857 * Sorts given Column. If "dynamicData" is true, current selections are purged before
7858 * a request is sent to the DataSource for data for the new state (using the
7859 * request returned by "generateRequest()").
7861 * @method sortColumn
7862 * @param oColumn {YAHOO.widget.Column} Column instance.
7863 * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
7864 * YAHOO.widget.DataTable.CLASS_DESC
7866 sortColumn : function(oColumn, sDir) {
7867 if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
7868 if(!oColumn.sortable) {
7869 Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
7872 // Validate given direction
7873 if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
7878 var sSortDir = sDir || this.getColumnSortDir(oColumn);
7880 // Is the Column currently sorted?
7881 var oSortedBy = this.get("sortedBy") || {};
7882 var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
7884 var ok = this.doBeforeSortColumn(oColumn, sSortDir);
7887 if(this.get("dynamicData")) {
7888 // Get current state
7889 var oState = this.getState();
7891 // Reset record offset, if paginated
7892 if(oState.pagination) {
7893 oState.pagination.recordOffset = 0;
7896 // Update sortedBy to new values
7902 // Get the request for the new state
7903 var request = this.get("generateRequest")(oState, this);
7906 this.unselectAllRows();
7907 this.unselectAllCells();
7909 // Send request for new data
7911 success : this.onDataReturnSetRows,
7912 failure : this.onDataReturnSetRows,
7913 argument : oState, // Pass along the new state to the callback
7916 this._oDataSource.sendRequest(request, callback);
7920 // Is there a custom sort handler function defined?
7921 var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
7922 // Custom sort function
7923 oColumn.sortOptions.sortFunction : null;
7926 if(!bSorted || sDir || sortFnc) {
7927 // Shortcut for the frequently-used compare method
7928 var compare = YAHOO.util.Sort.compare;
7930 // Default sort function if necessary
7931 sortFnc = sortFnc ||
7932 function(a, b, desc, field) {
7933 var sorted = compare(a.getData(field),b.getData(field), desc);
7935 return compare(a.getCount(),b.getCount(), desc); // Bug 1932978
7942 // Get the field to sort
7943 var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
7946 this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false), sField);
7948 // Just reverse the Records
7950 this._oRecordSet.reverseRecords();
7953 // Reset to first page if paginated
7954 var oPaginator = this.get('paginator');
7956 // Set page silently, so as not to fire change event.
7957 oPaginator.setPage(1,true);
7960 // Update UI via sortedBy
7962 this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn});
7965 this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
7966 YAHOO.log("Column \"" + oColumn.key + "\" sorted \"" + sSortDir + "\"", "info", this.toString());
7970 YAHOO.log("Could not sort Column \"" + oColumn.key + "\"", "warn", this.toString());
7974 * Sets given Column to given pixel width. If new width is less than minimum
7975 * width, sets to minimum width. Updates oColumn.width value.
7977 * @method setColumnWidth
7978 * @param oColumn {YAHOO.widget.Column} Column instance.
7979 * @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
7980 * subject to minWidth and maxAutoWidth validations.
7982 setColumnWidth : function(oColumn, nWidth) {
7983 if(!(oColumn instanceof YAHOO.widget.Column)) {
7984 oColumn = this.getColumn(oColumn);
7987 // Validate new width against minimum width
7988 if(lang.isNumber(nWidth)) {
7989 // This is why we must require a Number... :-|
7990 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
7993 oColumn.width = nWidth;
7995 // Resize the DOM elements
7996 this._setColumnWidth(oColumn, nWidth+"px");
7998 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
7999 YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString());
8001 // Unsets a width to auto-size
8002 else if(nWidth === null) {
8004 oColumn.width = nWidth;
8006 // Resize the DOM elements
8007 this._setColumnWidth(oColumn, "auto");
8008 this.validateColumnWidths(oColumn);
8009 this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
8010 YAHOO.log("Column " + oColumn + " width unset", "info", this.toString());
8013 // Bug 2339454: resize then sort misaligment
8014 this._clearTrTemplateEl();
8017 YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString());
8022 * Sets liner DIV elements of given Column to given width. When value should be
8023 * auto-calculated to fit content overflow is set to visible, otherwise overflow
8024 * is set to hidden. No validations against minimum width and no updating
8025 * Column.width value.
8027 * @method _setColumnWidth
8028 * @param oColumn {YAHOO.widget.Column} Column instance.
8029 * @param sWidth {String} New width value.
8030 * @param sOverflow {String} Should be "hidden" when Column width is explicitly
8031 * being set to a value, but should be "visible" when Column is meant to auto-fit content.
8034 _setColumnWidth : function(oColumn, sWidth, sOverflow) {
8035 if(oColumn && (oColumn.getKeyIndex() !== null)) {
8036 sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
8038 // Dynamic style algorithm
8039 if(!DT._bDynStylesFallback) {
8040 this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
8042 // Dynamic function algorithm
8044 this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
8048 YAHOO.log("Could not set width of unknown Column " + oColumn + " to " + sWidth, "warn", this.toString());
8053 * Updates width of a Column's liner DIV elements by dynamically creating a
8054 * STYLE node and writing and updating CSS style rules to it. If this fails during
8055 * runtime, the fallback method _setColumnWidthDynFunction() will be called.
8056 * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
8057 * nested within another TABLE element. For these cases, it is recommended to
8058 * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
8060 * @method _setColumnWidthDynStyles
8061 * @param oColumn {YAHOO.widget.Column} Column instance.
8062 * @param sWidth {String} New width value.
8065 _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
8066 var s = DT._elDynStyleNode,
8069 // Create a new STYLE node
8071 s = document.createElement('style');
8072 s.type = 'text/css';
8073 s = document.getElementsByTagName('head').item(0).appendChild(s);
8074 DT._elDynStyleNode = s;
8077 // We have a STYLE node to update
8079 // Use unique classname for this Column instance as a hook for resizing
8080 var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
8082 // Hide for performance
8084 this._elTbody.style.display = 'none';
8087 rule = DT._oDynStyles[sClassname];
8089 // The Column does not yet have a rule
8091 if(s.styleSheet && s.styleSheet.addRule) {
8092 s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
8093 s.styleSheet.addRule(sClassname,'width:'+sWidth);
8094 rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
8095 DT._oDynStyles[sClassname] = rule;
8097 else if(s.sheet && s.sheet.insertRule) {
8098 s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
8099 rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
8100 DT._oDynStyles[sClassname] = rule;
8103 // We have a rule to update
8105 rule.style.overflow = sOverflow;
8106 rule.style.width = sWidth;
8111 this._elTbody.style.display = '';
8115 // That was not a success, we must call the fallback routine
8117 DT._bDynStylesFallback = true;
8118 this._setColumnWidthDynFunction(oColumn, sWidth);
8123 * Updates width of a Column's liner DIV elements by dynamically creating a
8124 * function to update all element style properties in one pass. Note: This
8125 * technique is not supported in sandboxed environments that prohibit EVALs.
8127 * @method _setColumnWidthDynFunction
8128 * @param oColumn {YAHOO.widget.Column} Column instance.
8129 * @param sWidth {String} New width value.
8132 _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
8133 // TODO: why is this here?
8134 if(sWidth == 'auto') {
8138 // Create one function for each value of rows.length
8139 var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
8141 // Dynamically create the function
8142 if (!this._aDynFunctions[rowslen]) {
8144 //Compile a custom function to do all the liner div width
8145 //assignments at the same time. A unique function is required
8146 //for each unique number of rows in _elTbody. This will
8147 //result in a function declaration like:
8148 //function (oColumn,sWidth,sOverflow) {
8149 // var colIdx = oColumn.getKeyIndex();
8150 // oColumn.getThLinerEl().style.overflow =
8151 // this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
8152 // this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
8153 // ... (for all row indices in this._elTbody.rows.length - 1)
8154 // this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
8156 // oColumn.getThLinerEl().style.width =
8157 // this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
8158 // this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
8159 // ... (for all row indices in this._elTbody.rows.length - 1)
8160 // this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
8166 'var colIdx=oColumn.getKeyIndex();',
8167 'oColumn.getThLinerEl().style.overflow='
8169 for (i=rowslen-1, j=2; i >= 0; --i) {
8170 resizerDef[j++] = 'this._elTbody.rows[';
8171 resizerDef[j++] = i;
8172 resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
8174 resizerDef[j] = 'sOverflow;';
8175 resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
8176 for (i=rowslen-1, k=j+2; i >= 0; --i) {
8177 resizerDef[k++] = 'this._elTbody.rows[';
8178 resizerDef[k++] = i;
8179 resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
8181 resizerDef[k] = 'sWidth;';
8182 this._aDynFunctions[rowslen] =
8183 new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
8186 // Get the function to execute
8187 var resizerFn = this._aDynFunctions[rowslen];
8189 // TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
8191 resizerFn.call(this,oColumn,sWidth,sOverflow);
8196 * For one or all Columns, when Column is not hidden, width is not set, and minWidth
8197 * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
8199 * @method validateColumnWidths
8200 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
8202 validateColumnWidths : function(oColumn) {
8203 var elColgroup = this._elColgroup;
8204 var elColgroupClone = elColgroup.cloneNode(true);
8205 var bNeedsValidation = false;
8206 var allKeys = this._oColumnSet.keys;
8208 // Validate just one Column's minWidth and/or maxAutoWidth
8209 if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
8210 elThLiner = oColumn.getThLinerEl();
8211 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8212 elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width =
8214 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8215 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8216 bNeedsValidation = true;
8218 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8219 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8222 // Validate all Columns
8224 for(var i=0, len=allKeys.length; i<len; i++) {
8225 oColumn = allKeys[i];
8226 if(!oColumn.hidden && !oColumn.width) {
8227 elThLiner = oColumn.getThLinerEl();
8228 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8229 elColgroupClone.childNodes[i].style.width =
8231 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8232 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8233 bNeedsValidation = true;
8235 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8236 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8241 if(bNeedsValidation) {
8242 elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
8243 this._elColgroup = elColgroupClone;
8250 * @method _clearMinWidth
8251 * @param oColumn {YAHOO.widget.Column} Which Column.
8254 _clearMinWidth : function(oColumn) {
8255 if(oColumn.getKeyIndex() !== null) {
8256 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
8261 * Restores minWidth.
8263 * @method _restoreMinWidth
8264 * @param oColumn {YAHOO.widget.Column} Which Column.
8267 _restoreMinWidth : function(oColumn) {
8268 if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
8269 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
8274 * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
8275 * hide/show non-nested Columns, and top-level parent Columns (which will
8276 * hide/show all children Columns).
8278 * @method hideColumn
8279 * @param oColumn {YAHOO.widget.Column} Column instance.
8281 hideColumn : function(oColumn) {
8282 if(!(oColumn instanceof YAHOO.widget.Column)) {
8283 oColumn = this.getColumn(oColumn);
8285 // Only top-level Columns can get hidden due to issues in FF2 and SF3
8286 if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
8288 var allrows = this.getTbodyEl().rows;
8289 var l = allrows.length;
8290 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8292 // Hide each nested Column
8293 for(var i=0; i<allDescendants.length; i++) {
8294 var thisColumn = allDescendants[i];
8295 thisColumn.hidden = true;
8297 // Style the head cell
8298 Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8300 // Does this Column have body cells?
8301 var thisKeyIndex = thisColumn.getKeyIndex();
8302 if(thisKeyIndex !== null) {
8304 this._clearMinWidth(oColumn);
8306 // Style the body cells
8307 for(var j=0;j<l;j++) {
8308 Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8312 this.fireEvent("columnHideEvent",{column:thisColumn});
8313 YAHOO.log("Column \"" + oColumn.key + "\" hidden", "info", this.toString());
8316 this._repaintOpera();
8317 this._clearTrTemplateEl();
8320 YAHOO.log("Could not hide Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be hidden", "warn", this.toString());
8325 * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
8326 * hide/show non-nested Columns, and top-level parent Columns (which will
8327 * hide/show all children Columns).
8329 * @method showColumn
8330 * @param oColumn {YAHOO.widget.Column} Column instance.
8332 showColumn : function(oColumn) {
8333 if(!(oColumn instanceof YAHOO.widget.Column)) {
8334 oColumn = this.getColumn(oColumn);
8336 // Only top-level Columns can get hidden
8337 if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
8338 var allrows = this.getTbodyEl().rows;
8339 var l = allrows.length;
8340 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8342 // Show each nested Column
8343 for(var i=0; i<allDescendants.length; i++) {
8344 var thisColumn = allDescendants[i];
8345 thisColumn.hidden = false;
8347 // Unstyle the head cell
8348 Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8350 // Does this Column have body cells?
8351 var thisKeyIndex = thisColumn.getKeyIndex();
8352 if(thisKeyIndex !== null) {
8354 this._restoreMinWidth(oColumn);
8357 // Unstyle the body cells
8358 for(var j=0;j<l;j++) {
8359 Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8363 this.fireEvent("columnShowEvent",{column:thisColumn});
8364 YAHOO.log("Column \"" + oColumn.key + "\" shown", "info", this.toString());
8366 this._clearTrTemplateEl();
8369 YAHOO.log("Could not show Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be shown", "warn", this.toString());
8374 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
8375 * non-nested Columns, and top-level parent Columns (which will remove all
8376 * children Columns).
8378 * @method removeColumn
8379 * @param oColumn {YAHOO.widget.Column} Column instance.
8380 * @return oColumn {YAHOO.widget.Column} Removed Column instance.
8382 removeColumn : function(oColumn) {
8384 if(!(oColumn instanceof YAHOO.widget.Column)) {
8385 oColumn = this.getColumn(oColumn);
8388 var nColTreeIndex = oColumn.getTreeIndex();
8389 if(nColTreeIndex !== null) {
8390 // Which key index(es)
8392 aKeyIndexes = oColumn.getKeyIndex();
8393 // Must be a parent Column
8394 if(aKeyIndexes === null) {
8395 var descKeyIndexes = [];
8396 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8397 for(i=0, len=allDescendants.length; i<len; i++) {
8398 // Is this descendant a key Column?
8399 var thisKey = allDescendants[i].getKeyIndex();
8400 if(thisKey !== null) {
8401 descKeyIndexes[descKeyIndexes.length] = thisKey;
8404 if(descKeyIndexes.length > 0) {
8405 aKeyIndexes = descKeyIndexes;
8408 // Must be a key Column
8410 aKeyIndexes = [aKeyIndexes];
8413 if(aKeyIndexes !== null) {
8414 // Sort the indexes so we can remove from the right
8415 aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8417 // Destroy previous THEAD
8418 this._destroyTheadEl();
8421 var aOrigColumnDefs = this._oColumnSet.getDefinitions();
8422 oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
8423 this._initColumnSet(aOrigColumnDefs);
8424 this._initTheadEl();
8427 for(i=aKeyIndexes.length-1; i>-1; i--) {
8428 this._removeColgroupColEl(aKeyIndexes[i]);
8432 var allRows = this._elTbody.rows;
8433 if(allRows.length > 0) {
8434 var loopN = this.get("renderLoopSize"),
8435 loopEnd = allRows.length;
8436 this._oChainRender.add({
8437 method: function(oArg) {
8438 if((this instanceof DT) && this._sId) {
8439 var i = oArg.nCurrentRow,
8440 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8441 aIndexes = oArg.aIndexes,
8443 for(; i < len; ++i) {
8444 for(j = aIndexes.length-1; j>-1; j--) {
8445 allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
8448 oArg.nCurrentRow = i;
8451 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8452 argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
8454 timeout: (loopN > 0) ? 0 : -1
8456 this._runRenderChain();
8459 this.fireEvent("columnRemoveEvent",{column:oColumn});
8460 YAHOO.log("Column \"" + oColumn.key + "\" removed", "info", this.toString());
8465 YAHOO.log("Could not remove Column \"" + oColumn.key + "\". Only non-nested Columns can be removed", "warn", this.toString());
8469 * Inserts given Column at the index if given, otherwise at the end. NOTE: You
8470 * can only add non-nested Columns and top-level parent Columns. You cannot add
8471 * a nested Column to an existing parent.
8473 * @method insertColumn
8474 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
8475 * definition or a Column instance.
8476 * @param index {Number} (optional) New tree index.
8477 * @return oColumn {YAHOO.widget.Column} Inserted Column instance.
8479 insertColumn : function(oColumn, index) {
8481 if(oColumn instanceof YAHOO.widget.Column) {
8482 oColumn = oColumn.getDefinition();
8484 else if(oColumn.constructor !== Object) {
8485 YAHOO.log("Could not insert Column \"" + oColumn + "\" due to invalid argument", "warn", this.toString());
8489 // Validate index or append new Column to the end of the ColumnSet
8490 var oColumnSet = this._oColumnSet;
8491 if(!lang.isValue(index) || !lang.isNumber(index)) {
8492 index = oColumnSet.tree[0].length;
8495 // Destroy previous THEAD
8496 this._destroyTheadEl();
8499 var aNewColumnDefs = this._oColumnSet.getDefinitions();
8500 aNewColumnDefs.splice(index, 0, oColumn);
8501 this._initColumnSet(aNewColumnDefs);
8502 this._initTheadEl();
8504 // Need to refresh the reference
8505 oColumnSet = this._oColumnSet;
8506 var oNewColumn = oColumnSet.tree[0][index];
8508 // Get key index(es) for new Column
8510 descKeyIndexes = [];
8511 var allDescendants = oColumnSet.getDescendants(oNewColumn);
8512 for(i=0, len=allDescendants.length; i<len; i++) {
8513 // Is this descendant a key Column?
8514 var thisKey = allDescendants[i].getKeyIndex();
8515 if(thisKey !== null) {
8516 descKeyIndexes[descKeyIndexes.length] = thisKey;
8520 if(descKeyIndexes.length > 0) {
8522 var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8525 for(i=descKeyIndexes.length-1; i>-1; i--) {
8526 this._insertColgroupColEl(descKeyIndexes[i]);
8530 var allRows = this._elTbody.rows;
8531 if(allRows.length > 0) {
8532 var loopN = this.get("renderLoopSize"),
8533 loopEnd = allRows.length;
8535 // Get templates for each new TD
8536 var aTdTemplates = [],
8538 for(i=0, len=descKeyIndexes.length; i<len; i++) {
8539 var thisKeyIndex = descKeyIndexes[i];
8540 elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
8541 elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
8542 aTdTemplates[thisKeyIndex] = elTdTemplate;
8545 this._oChainRender.add({
8546 method: function(oArg) {
8547 if((this instanceof DT) && this._sId) {
8548 var i = oArg.nCurrentRow, j,
8549 descKeyIndexes = oArg.descKeyIndexes,
8550 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8552 for(; i < len; ++i) {
8553 nextSibling = allRows[i].childNodes[newIndex] || null;
8554 for(j=descKeyIndexes.length-1; j>-1; j--) {
8555 allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
8558 oArg.nCurrentRow = i;
8561 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8562 argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
8564 timeout: (loopN > 0) ? 0 : -1
8566 this._runRenderChain();
8569 this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
8570 YAHOO.log("Column \"" + oColumn.key + "\" inserted into index " + index, "info", this.toString());
8576 * Removes given Column and inserts into given tree index. NOTE: You
8577 * can only reorder non-nested Columns and top-level parent Columns. You cannot
8578 * reorder a nested Column to an existing parent.
8580 * @method reorderColumn
8581 * @param oColumn {YAHOO.widget.Column} Column instance.
8582 * @param index {Number} New tree index.
8583 * @return oColumn {YAHOO.widget.Column} Reordered Column instance.
8585 reorderColumn : function(oColumn, index) {
8586 // Validate Column and new index
8587 if(!(oColumn instanceof YAHOO.widget.Column)) {
8588 oColumn = this.getColumn(oColumn);
8590 if(oColumn && YAHOO.lang.isNumber(index)) {
8591 var nOrigTreeIndex = oColumn.getTreeIndex();
8592 if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
8593 // Which key index(es)
8595 aOrigKeyIndexes = oColumn.getKeyIndex(),
8597 descKeyIndexes = [],
8599 // Must be a parent Column...
8600 if(aOrigKeyIndexes === null) {
8601 allDescendants = this._oColumnSet.getDescendants(oColumn);
8602 for(i=0, len=allDescendants.length; i<len; i++) {
8603 // Is this descendant a key Column?
8604 thisKey = allDescendants[i].getKeyIndex();
8605 if(thisKey !== null) {
8606 descKeyIndexes[descKeyIndexes.length] = thisKey;
8609 if(descKeyIndexes.length > 0) {
8610 aOrigKeyIndexes = descKeyIndexes;
8613 // ...or else must be a key Column
8615 aOrigKeyIndexes = [aOrigKeyIndexes];
8618 if(aOrigKeyIndexes !== null) {
8620 aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8622 // Destroy previous THEAD
8623 this._destroyTheadEl();
8626 var aColumnDefs = this._oColumnSet.getDefinitions();
8627 var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
8628 aColumnDefs.splice(index, 0, oColumnDef);
8629 this._initColumnSet(aColumnDefs);
8630 this._initTheadEl();
8632 // Need to refresh the reference
8633 var oNewColumn = this._oColumnSet.tree[0][index];
8635 // What are new key index(es)
8636 var aNewKeyIndexes = oNewColumn.getKeyIndex();
8637 // Must be a parent Column
8638 if(aNewKeyIndexes === null) {
8639 descKeyIndexes = [];
8640 allDescendants = this._oColumnSet.getDescendants(oNewColumn);
8641 for(i=0, len=allDescendants.length; i<len; i++) {
8642 // Is this descendant a key Column?
8643 thisKey = allDescendants[i].getKeyIndex();
8644 if(thisKey !== null) {
8645 descKeyIndexes[descKeyIndexes.length] = thisKey;
8648 if(descKeyIndexes.length > 0) {
8649 aNewKeyIndexes = descKeyIndexes;
8652 // Must be a key Column
8654 aNewKeyIndexes = [aNewKeyIndexes];
8657 // Sort the new indexes and grab the first one for the new location
8658 var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8661 this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
8664 var allRows = this._elTbody.rows;
8665 if(allRows.length > 0) {
8666 var loopN = this.get("renderLoopSize"),
8667 loopEnd = allRows.length;
8668 this._oChainRender.add({
8669 method: function(oArg) {
8670 if((this instanceof DT) && this._sId) {
8671 var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
8672 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8673 aIndexes = oArg.aIndexes, thisTr;
8675 for(; i < len; ++i) {
8677 thisTr = allRows[i];
8680 for(j=aIndexes.length-1; j>-1; j--) {
8681 tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
8685 nextSibling = thisTr.childNodes[newIndex] || null;
8686 for(j=tmpTds.length-1; j>-1; j--) {
8687 thisTr.insertBefore(tmpTds[j], nextSibling);
8690 oArg.nCurrentRow = i;
8693 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8694 argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
8696 timeout: (loopN > 0) ? 0 : -1
8698 this._runRenderChain();
8701 this.fireEvent("columnReorderEvent",{column:oNewColumn});
8702 YAHOO.log("Column \"" + oNewColumn.key + "\" reordered", "info", this.toString());
8707 YAHOO.log("Could not reorder Column \"" + oColumn.key + "\". Only non-nested Columns can be reordered", "warn", this.toString());
8711 * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8712 * select/unselect non-nested Columns, and bottom-level key Columns.
8714 * @method selectColumn
8715 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8716 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8718 selectColumn : function(oColumn) {
8719 oColumn = this.getColumn(oColumn);
8720 if(oColumn && !oColumn.selected) {
8721 // Only bottom-level Columns can get hidden
8722 if(oColumn.getKeyIndex() !== null) {
8723 oColumn.selected = true;
8726 var elTh = oColumn.getThEl();
8727 Dom.addClass(elTh,DT.CLASS_SELECTED);
8729 // Update body cells
8730 var allRows = this.getTbodyEl().rows;
8731 var oChainRender = this._oChainRender;
8733 method: function(oArg) {
8734 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8735 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);
8740 iterations: allRows.length,
8741 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8744 this._clearTrTemplateEl();
8746 this._elTbody.style.display = "none";
8747 this._runRenderChain();
8748 this._elTbody.style.display = "";
8750 this.fireEvent("columnSelectEvent",{column:oColumn});
8751 YAHOO.log("Column \"" + oColumn.key + "\" selected", "info", this.toString());
8754 YAHOO.log("Could not select Column \"" + oColumn.key + "\". Only non-nested Columns can be selected", "warn", this.toString());
8760 * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8761 * select/unselect non-nested Columns, and bottom-level key Columns.
8763 * @method unselectColumn
8764 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8765 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8767 unselectColumn : function(oColumn) {
8768 oColumn = this.getColumn(oColumn);
8769 if(oColumn && oColumn.selected) {
8770 // Only bottom-level Columns can get hidden
8771 if(oColumn.getKeyIndex() !== null) {
8772 oColumn.selected = false;
8775 var elTh = oColumn.getThEl();
8776 Dom.removeClass(elTh,DT.CLASS_SELECTED);
8778 // Update body cells
8779 var allRows = this.getTbodyEl().rows;
8780 var oChainRender = this._oChainRender;
8782 method: function(oArg) {
8783 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8784 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);
8789 iterations:allRows.length,
8790 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8793 this._clearTrTemplateEl();
8795 this._elTbody.style.display = "none";
8796 this._runRenderChain();
8797 this._elTbody.style.display = "";
8799 this.fireEvent("columnUnselectEvent",{column:oColumn});
8800 YAHOO.log("Column \"" + oColumn.key + "\" unselected", "info", this.toString());
8803 YAHOO.log("Could not unselect Column \"" + oColumn.key + "\". Only non-nested Columns can be unselected", "warn", this.toString());
8809 * Returns an array selected Column instances.
8811 * @method getSelectedColumns
8812 * @return {YAHOO.widget.Column[]} Array of Column instances.
8814 getSelectedColumns : function(oColumn) {
8815 var selectedColumns = [];
8816 var aKeys = this._oColumnSet.keys;
8817 for(var i=0,len=aKeys.length; i<len; i++) {
8818 if(aKeys[i].selected) {
8819 selectedColumns[selectedColumns.length] = aKeys[i];
8822 return selectedColumns;
8826 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8827 * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8828 * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8830 * @method highlightColumn
8831 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8832 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8834 highlightColumn : function(column) {
8835 var oColumn = this.getColumn(column);
8836 // Only bottom-level Columns can get highlighted
8837 if(oColumn && (oColumn.getKeyIndex() !== null)) {
8839 var elTh = oColumn.getThEl();
8840 Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
8842 // Update body cells
8843 var allRows = this.getTbodyEl().rows;
8844 var oChainRender = this._oChainRender;
8846 method: function(oArg) {
8847 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8848 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
8853 iterations:allRows.length,
8854 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8857 this._elTbody.style.display = "none";
8858 this._runRenderChain();
8859 this._elTbody.style.display = "";
8861 this.fireEvent("columnHighlightEvent",{column:oColumn});
8862 YAHOO.log("Column \"" + oColumn.key + "\" highlighed", "info", this.toString());
8865 YAHOO.log("Could not highlight Column \"" + oColumn.key + "\". Only non-nested Columns can be highlighted", "warn", this.toString());
8870 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8871 * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8872 * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8874 * @method unhighlightColumn
8875 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8876 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8878 unhighlightColumn : function(column) {
8879 var oColumn = this.getColumn(column);
8880 // Only bottom-level Columns can get highlighted
8881 if(oColumn && (oColumn.getKeyIndex() !== null)) {
8883 var elTh = oColumn.getThEl();
8884 Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
8886 // Update body cells
8887 var allRows = this.getTbodyEl().rows;
8888 var oChainRender = this._oChainRender;
8890 method: function(oArg) {
8891 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8892 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
8897 iterations:allRows.length,
8898 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8901 this._elTbody.style.display = "none";
8902 this._runRenderChain();
8903 this._elTbody.style.display = "";
8905 this.fireEvent("columnUnhighlightEvent",{column:oColumn});
8906 YAHOO.log("Column \"" + oColumn.key + "\" unhighlighted", "info", this.toString());
8909 YAHOO.log("Could not unhighlight Column \"" + oColumn.key + "\". Only non-nested Columns can be unhighlighted", "warn", this.toString());
8959 * Adds one new Record of data into the RecordSet at the index if given,
8960 * otherwise at the end. If the new Record is in page view, the
8961 * corresponding DOM elements are also updated.
8964 * @param oData {Object} Object literal of data for the row.
8965 * @param index {Number} (optional) RecordSet position index at which to add data.
8967 addRow : function(oData, index) {
8968 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
8969 YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
8973 if(oData && lang.isObject(oData)) {
8974 var oRecord = this._oRecordSet.addRecord(oData, index);
8977 var oPaginator = this.get('paginator');
8981 // Update the paginator's totalRecords
8982 var totalRecords = oPaginator.get('totalRecords');
8983 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
8984 oPaginator.set('totalRecords',totalRecords + 1);
8987 recIndex = this.getRecordIndex(oRecord);
8988 var endRecIndex = (oPaginator.getPageRecords())[1];
8990 // New record affects the view
8991 if (recIndex <= endRecIndex) {
8992 // Defer UI updates to the render method
8996 this.fireEvent("rowAddEvent", {record:oRecord});
8997 YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString());
9002 recIndex = this.getTrIndex(oRecord);
9003 if(lang.isNumber(recIndex)) {
9004 // Add the TR element
9005 this._oChainRender.add({
9006 method: function(oArg) {
9007 if((this instanceof DT) && this._sId) {
9008 var oRecord = oArg.record;
9009 var recIndex = oArg.recIndex;
9010 var elNewTr = this._addTrEl(oRecord);
9012 var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
9013 this._elTbody.insertBefore(elNewTr, elNext);
9016 if(recIndex === 0) {
9017 this._setFirstRow();
9019 if(elNext === null) {
9023 this._setRowStripes();
9025 this.hideTableMessage();
9027 this.fireEvent("rowAddEvent", {record:oRecord});
9028 YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString());
9032 argument: {record: oRecord, recIndex: recIndex},
9034 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9036 this._runRenderChain();
9042 YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
9046 * Convenience method to add multiple rows.
9049 * @param aData {Object[]} Array of object literal data for the rows.
9050 * @param index {Number} (optional) RecordSet position index at which to add data.
9052 addRows : function(aData, index) {
9053 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
9054 YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());
9058 if(lang.isArray(aData)) {
9059 var aRecords = this._oRecordSet.addRecords(aData, index);
9061 var recIndex = this.getRecordIndex(aRecords[0]);
9064 var oPaginator = this.get('paginator');
9066 // Update the paginator's totalRecords
9067 var totalRecords = oPaginator.get('totalRecords');
9068 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9069 oPaginator.set('totalRecords',totalRecords + aRecords.length);
9072 var endRecIndex = (oPaginator.getPageRecords())[1];
9074 // At least one of the new records affects the view
9075 if (recIndex <= endRecIndex) {
9079 this.fireEvent("rowsAddEvent", {records:aRecords});
9080 YAHOO.log("Added " + aRecords.length +
9081 " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
9082 " with data " + lang.dump(aData), "info", this.toString());
9087 // Add the TR elements
9088 var loopN = this.get("renderLoopSize");
9089 var loopEnd = recIndex + aData.length;
9090 var nRowsNeeded = (loopEnd - recIndex); // how many needed
9091 var isLast = (recIndex >= this._elTbody.rows.length);
9092 this._oChainRender.add({
9093 method: function(oArg) {
9094 if((this instanceof DT) && this._sId) {
9095 var aRecords = oArg.aRecords,
9096 i = oArg.nCurrentRow,
9097 j = oArg.nCurrentRecord,
9098 len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
9099 df = document.createDocumentFragment(),
9100 elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
9101 for(; i < len; i++, j++) {
9102 df.appendChild(this._addTrEl(aRecords[j]));
9104 this._elTbody.insertBefore(df, elNext);
9105 oArg.nCurrentRow = i;
9106 oArg.nCurrentRecord = j;
9109 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
9110 argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
9112 timeout: (loopN > 0) ? 0 : -1
9114 this._oChainRender.add({
9115 method: function(oArg) {
9116 var recIndex = oArg.recIndex;
9118 if(recIndex === 0) {
9119 this._setFirstRow();
9125 this._setRowStripes();
9127 this.fireEvent("rowsAddEvent", {records:aRecords});
9128 YAHOO.log("Added " + aRecords.length +
9129 " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
9130 " with data " + lang.dump(aData), "info", this.toString());
9132 argument: {recIndex: recIndex, isLast: isLast},
9134 timeout: -1 // Needs to run immediately after the DOM insertions above
9136 this._runRenderChain();
9137 this.hideTableMessage();
9142 YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());
9146 * For the given row, updates the associated Record with the given data. If the
9147 * row is on current page, the corresponding DOM elements are also updated.
9150 * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
9151 * Which row to update: By Record instance, by Record's RecordSet
9152 * position index, by HTMLElement reference to the TR element, or by ID string
9153 * of the TR element.
9154 * @param oData {Object} Object literal of data for the row.
9156 updateRow : function(row, oData) {
9158 if (!lang.isNumber(index)) {
9159 index = this.getRecordIndex(row);
9162 // Update the Record
9163 if(lang.isNumber(index) && (index >= 0)) {
9164 var oRecordSet = this._oRecordSet,
9165 oldRecord = oRecordSet.getRecord(index);
9169 var updatedRecord = this._oRecordSet.setRecord(oData, index),
9170 elRow = this.getTrEl(oldRecord),
9171 // Copy data from the Record for the event that gets fired later
9172 oldData = oldRecord ? oldRecord.getData() : null;
9175 // Update selected rows as necessary
9176 var tracker = this._aSelections || [],
9178 oldId = oldRecord.getId(),
9179 newId = updatedRecord.getId();
9180 for(; i<tracker.length; i++) {
9181 if((tracker[i] === oldId)) {
9184 else if(tracker[i].recordId === oldId) {
9185 tracker[i].recordId = newId;
9189 // Update the TR only if row is on current page
9190 this._oChainRender.add({
9191 method: function() {
9192 if((this instanceof DT) && this._sId) {
9194 var oPaginator = this.get('paginator');
9196 var pageStartIndex = (oPaginator.getPageRecords())[0],
9197 pageLastIndex = (oPaginator.getPageRecords())[1];
9199 // At least one of the new records affects the view
9200 if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
9206 this._updateTrEl(elRow, updatedRecord);
9209 this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
9212 this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
9213 YAHOO.log("DataTable row updated: Record ID = " + updatedRecord.getId() +
9214 ", Record index = " + this.getRecordIndex(updatedRecord) +
9215 ", page row index = " + this.getTrIndex(updatedRecord), "info", this.toString());
9219 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9221 this._runRenderChain();
9226 YAHOO.log("Could not update row " + row + " with the data : " + lang.dump(oData), "warn", this.toString());
9231 * Starting with the given row, updates associated Records with the given data.
9232 * The number of rows to update are determined by the array of data provided.
9233 * Undefined data (i.e., not an object literal) causes a row to be skipped. If
9234 * any of the rows are on current page, the corresponding DOM elements are also
9237 * @method updateRows
9238 * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
9239 * Starting row to update: By Record instance, by Record's RecordSet
9240 * position index, by HTMLElement reference to the TR element, or by ID string
9241 * of the TR element.
9242 * @param aData {Object[]} Array of object literal of data for the rows.
9244 updateRows : function(startrow, aData) {
9245 if(lang.isArray(aData)) {
9246 var startIndex = startrow,
9247 oRecordSet = this._oRecordSet;
9249 if (!lang.isNumber(startrow)) {
9250 startIndex = this.getRecordIndex(startrow);
9253 if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
9254 var lastIndex = startIndex + aData.length,
9255 aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
9256 aNewRecords = oRecordSet.setRecords(aData, startIndex);
9258 // Update selected rows as necessary
9259 var tracker = this._aSelections || [],
9260 i=0, j, newId, oldId;
9261 for(; i<tracker.length; i++) {
9262 for(j=0; j<aOldRecords.length; j++) {
9263 oldId = aOldRecords[j].getId();
9264 if((tracker[i] === oldId)) {
9265 tracker[i] = aNewRecords[j].getId();
9267 else if(tracker[i].recordId === oldId) {
9268 tracker[i].recordId = aNewRecords[j].getId();
9274 var oPaginator = this.get('paginator');
9276 var pageStartIndex = (oPaginator.getPageRecords())[0],
9277 pageLastIndex = (oPaginator.getPageRecords())[1];
9279 // At least one of the new records affects the view
9280 if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
9284 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9285 YAHOO.log("Added " + aNewRecords.length +
9286 " rows starting at index " + startIndex +
9287 " with data " + lang.dump(aData), "info", this.toString());
9292 // Update the TR elements
9293 var loopN = this.get("renderLoopSize"),
9294 rowCount = aData.length, // how many needed
9295 lastRowIndex = this._elTbody.rows.length,
9296 isLast = (lastIndex >= lastRowIndex),
9297 isAdding = (lastIndex > lastRowIndex);
9299 this._oChainRender.add({
9300 method: function(oArg) {
9301 if((this instanceof DT) && this._sId) {
9302 var aRecords = oArg.aRecords,
9303 i = oArg.nCurrentRow,
9304 j = oArg.nDataPointer,
9305 len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
9307 for(; i < len; i++,j++) {
9308 if(isAdding && (i>=lastRowIndex)) {
9309 this._elTbody.appendChild(this._addTrEl(aRecords[j]));
9312 this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
9315 oArg.nCurrentRow = i;
9316 oArg.nDataPointer = j;
9319 iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
9320 argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
9322 timeout: (loopN > 0) ? 0 : -1
9324 this._oChainRender.add({
9325 method: function(oArg) {
9326 var recIndex = oArg.recIndex;
9328 if(recIndex === 0) {
9329 this._setFirstRow();
9335 this._setRowStripes();
9337 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9338 YAHOO.log("Added " + aNewRecords.length +
9339 " rows starting at index " + startIndex +
9340 " with data " + lang.dump(aData), "info", this.toString());
9342 argument: {recIndex: startIndex, isLast: isLast},
9344 timeout: -1 // Needs to run immediately after the DOM insertions above
9346 this._runRenderChain();
9347 this.hideTableMessage();
9353 YAHOO.log("Could not update rows at " + startrow + " with " + lang.dump(aData), "warn", this.toString());
9357 * Deletes the given row's Record from the RecordSet. If the row is on current page,
9358 * the corresponding DOM elements are also deleted.
9361 * @param row {HTMLElement | String | Number} DOM element reference or ID string
9362 * to DataTable page element or RecordSet index.
9364 deleteRow : function(row) {
9365 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9366 if(lang.isNumber(nRecordIndex)) {
9367 var oRecord = this.getRecord(nRecordIndex);
9369 var nTrIndex = this.getTrIndex(nRecordIndex);
9371 // Remove from selection tracker if there
9372 var sRecordId = oRecord.getId();
9373 var tracker = this._aSelections || [];
9374 for(var j=tracker.length-1; j>-1; j--) {
9375 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9376 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9377 tracker.splice(j,1);
9381 // Delete Record from RecordSet
9382 var oData = this._oRecordSet.deleteRecord(nRecordIndex);
9386 // If paginated and the deleted row was on this or a prior page, just
9388 var oPaginator = this.get('paginator');
9390 // Update the paginator's totalRecords
9391 var totalRecords = oPaginator.get('totalRecords'),
9392 // must capture before the totalRecords change because
9393 // Paginator shifts to previous page automatically
9394 rng = oPaginator.getPageRecords();
9396 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9397 oPaginator.set('totalRecords',totalRecords - 1);
9400 // The deleted record was on this or a prior page, re-render
9401 if (!rng || nRecordIndex <= rng[1]) {
9405 this._oChainRender.add({
9406 method: function() {
9407 if((this instanceof DT) && this._sId) {
9408 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
9409 YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());
9413 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9415 this._runRenderChain();
9419 if(lang.isNumber(nTrIndex)) {
9420 this._oChainRender.add({
9421 method: function() {
9422 if((this instanceof DT) && this._sId) {
9423 var isLast = (nRecordIndex === this._oRecordSet.getLength());//(nTrIndex == this.getLastTrEl().sectionRowIndex);
9424 this._deleteTrEl(nTrIndex);
9426 // Post-delete tasks
9427 if(this._elTbody.rows.length > 0) {
9429 if(nTrIndex === 0) {
9430 this._setFirstRow();
9436 if(nTrIndex != this._elTbody.rows.length) {
9437 this._setRowStripes(nTrIndex);
9441 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
9442 YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());
9446 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9448 this._runRenderChain();
9455 YAHOO.log("Could not delete row: " + row, "warn", this.toString());
9460 * Convenience method to delete multiple rows.
9462 * @method deleteRows
9463 * @param row {HTMLElement | String | Number} DOM element reference or ID string
9464 * to DataTable page element or RecordSet index.
9465 * @param count {Number} (optional) How many rows to delete. A negative value
9466 * will delete towards the beginning.
9468 deleteRows : function(row, count) {
9469 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9470 if(lang.isNumber(nRecordIndex)) {
9471 var oRecord = this.getRecord(nRecordIndex);
9473 var nTrIndex = this.getTrIndex(nRecordIndex);
9475 // Remove from selection tracker if there
9476 var sRecordId = oRecord.getId();
9477 var tracker = this._aSelections || [];
9478 for(var j=tracker.length-1; j>-1; j--) {
9479 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9480 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9481 tracker.splice(j,1);
9485 // Delete Record from RecordSet
9486 var highIndex = nRecordIndex;
9487 var lowIndex = nRecordIndex;
9489 // Validate count and account for negative value
9490 if(count && lang.isNumber(count)) {
9491 highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
9492 lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
9493 count = (count > 0) ? count : count*-1;
9496 count = highIndex - lowIndex + 1;
9503 var aData = this._oRecordSet.deleteRecords(lowIndex, count);
9507 var oPaginator = this.get('paginator'),
9508 loopN = this.get("renderLoopSize");
9509 // If paginated and the deleted row was on this or a prior page, just
9512 // Update the paginator's totalRecords
9513 var totalRecords = oPaginator.get('totalRecords'),
9514 // must capture before the totalRecords change because
9515 // Paginator shifts to previous page automatically
9516 rng = oPaginator.getPageRecords();
9518 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9519 oPaginator.set('totalRecords',totalRecords - aData.length);
9522 // The records were on this or a prior page, re-render
9523 if (!rng || lowIndex <= rng[1]) {
9527 this._oChainRender.add({
9528 method: function(oArg) {
9529 if((this instanceof DT) && this._sId) {
9530 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9531 YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
9535 timeout: (loopN > 0) ? 0 : -1
9537 this._runRenderChain();
9542 if(lang.isNumber(nTrIndex)) {
9543 // Delete the TR elements starting with highest index
9544 var loopEnd = lowIndex;
9545 var nRowsNeeded = count; // how many needed
9546 this._oChainRender.add({
9547 method: function(oArg) {
9548 if((this instanceof DT) && this._sId) {
9549 var i = oArg.nCurrentRow,
9550 len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
9552 this._deleteTrEl(i);
9554 oArg.nCurrentRow = i;
9557 iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
9558 argument: {nCurrentRow:highIndex},
9560 timeout: (loopN > 0) ? 0 : -1
9562 this._oChainRender.add({
9563 method: function() {
9564 // Post-delete tasks
9565 if(this._elTbody.rows.length > 0) {
9566 this._setFirstRow();
9568 this._setRowStripes();
9571 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9572 YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
9575 timeout: -1 // Needs to run immediately after the DOM deletions above
9577 this._runRenderChain();
9584 YAHOO.log("Could not delete " + count + " rows at row " + row, "warn", this.toString());
9636 * Outputs markup into the given TD based on given Record.
9638 * @method formatCell
9639 * @param elLiner {HTMLElement} The liner DIV element within the TD.
9640 * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
9641 * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
9643 formatCell : function(elLiner, oRecord, oColumn) {
9645 oRecord = this.getRecord(elLiner);
9648 oColumn = this.getColumn(elLiner.parentNode.cellIndex);
9651 if(oRecord && oColumn) {
9652 var sField = oColumn.field;
9653 var oData = oRecord.getData(sField);
9655 var fnFormatter = typeof oColumn.formatter === 'function' ?
9657 DT.Formatter[oColumn.formatter+''] ||
9658 DT.Formatter.defaultFormatter;
9660 // Apply special formatter
9662 fnFormatter.call(this, elLiner, oRecord, oColumn, oData);
9665 elLiner.innerHTML = oData;
9668 this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elLiner});
9671 YAHOO.log("Could not format cell " + elLiner, "error", this.toString());
9676 * For the given row and column, updates the Record with the given data. If the
9677 * cell is on current page, the corresponding DOM elements are also updated.
9679 * @method updateCell
9680 * @param oRecord {YAHOO.widget.Record} Record instance.
9681 * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
9682 * @param oData {Object} New data value for the cell.
9684 updateCell : function(oRecord, oColumn, oData) {
9685 // Validate Column and Record
9686 oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
9687 if(oColumn && oColumn.getField() && (oRecord instanceof YAHOO.widget.Record)) {
9688 var sKey = oColumn.getField(),
9690 // Copy data from the Record for the event that gets fired later
9691 //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
9692 oldData = oRecord.getData(sKey);
9694 // Update Record with new data
9695 this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
9697 // Update the TD only if row is on current page
9698 var elTd = this.getTdEl({record: oRecord, column: oColumn});
9700 this._oChainRender.add({
9701 method: function() {
9702 if((this instanceof DT) && this._sId) {
9703 this.formatCell(elTd.firstChild);
9704 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9705 YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
9706 ", Record index = " + this.getRecordIndex(oRecord) +
9707 ", page row index = " + this.getTrIndex(oRecord) +
9708 ", Column key = " + oColumn.getKey(), "info", this.toString());
9712 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9714 this._runRenderChain();
9717 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9718 YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
9719 ", Record index = " + this.getRecordIndex(oRecord) +
9720 ", page row index = " + this.getTrIndex(oRecord) +
9721 ", Column key = " + oColumn.getKey(), "info", this.toString());
9778 * Method executed during set() operation for the "paginator" attribute.
9779 * Adds and/or severs event listeners between DataTable and Paginator
9781 * @method _updatePaginator
9782 * @param newPag {Paginator} Paginator instance (or null) for DataTable to use
9785 _updatePaginator : function (newPag) {
9786 var oldPag = this.get('paginator');
9787 if (oldPag && newPag !== oldPag) {
9788 oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9791 newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9796 * Update the UI infrastructure in response to a "paginator" attribute change.
9798 * @method _handlePaginatorChange
9799 * @param e {Object} Change event object containing keys 'type','newValue',
9803 _handlePaginatorChange : function (e) {
9804 if (e.prevValue === e.newValue) { return; }
9806 var newPag = e.newValue,
9807 oldPag = e.prevValue,
9808 containers = this._defaultPaginatorContainers();
9811 if (oldPag.getContainerNodes()[0] == containers[0]) {
9812 oldPag.set('containers',[]);
9816 // Convenience: share the default containers if possible.
9817 // Otherwise, remove the default containers from the DOM.
9818 if (containers[0]) {
9819 if (newPag && !newPag.getContainerNodes().length) {
9820 newPag.set('containers',containers);
9822 // No new Paginator to use existing containers, OR new
9823 // Paginator has configured containers.
9824 for (var i = containers.length - 1; i >= 0; --i) {
9825 if (containers[i]) {
9826 containers[i].parentNode.removeChild(containers[i]);
9839 this.renderPaginator();
9845 * Returns the default containers used for Paginators. If create param is
9846 * passed, the containers will be created and added to the DataTable container.
9848 * @method _defaultPaginatorContainers
9849 * @param create {boolean} Create the default containers if not found
9852 _defaultPaginatorContainers : function (create) {
9853 var above_id = this._sId + '-paginator0',
9854 below_id = this._sId + '-paginator1',
9855 above = Dom.get(above_id),
9856 below = Dom.get(below_id);
9858 if (create && (!above || !below)) {
9859 // One above and one below the table
9861 above = document.createElement('div');
9862 above.id = above_id;
9863 Dom.addClass(above, DT.CLASS_PAGINATOR);
9865 this._elContainer.insertBefore(above,this._elContainer.firstChild);
9869 below = document.createElement('div');
9870 below.id = below_id;
9871 Dom.addClass(below, DT.CLASS_PAGINATOR);
9873 this._elContainer.appendChild(below);
9877 return [above,below];
9881 * Calls Paginator's destroy() method
9883 * @method _destroyPaginator
9886 _destroyPaginator : function () {
9887 var oldPag = this.get('paginator');
9894 * Renders the Paginator to the DataTable UI
9896 * @method renderPaginator
9898 renderPaginator : function () {
9899 var pag = this.get("paginator");
9900 if (!pag) { return; }
9902 // Add the containers if the Paginator is not configured with containers
9903 if (!pag.getContainerNodes().length) {
9904 pag.set('containers',this._defaultPaginatorContainers(true));
9911 * Overridable method gives implementers a hook to show loading message before
9912 * changing Paginator value.
9914 * @method doBeforePaginatorChange
9915 * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9916 * @return {Boolean} Return true to continue changing Paginator value.
9918 doBeforePaginatorChange : function(oPaginatorState) {
9919 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
9924 * Responds to new Pagination states. By default, updates the UI to reflect the
9925 * new state. If "dynamicData" is true, current selections are purged before
9926 * a request is sent to the DataSource for data for the new state (using the
9927 * request returned by "generateRequest()").
9929 * @method onPaginatorChangeRequest
9930 * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9932 onPaginatorChangeRequest : function (oPaginatorState) {
9933 var ok = this.doBeforePaginatorChange(oPaginatorState);
9935 // Server-side pagination
9936 if(this.get("dynamicData")) {
9937 // Get the current state
9938 var oState = this.getState();
9940 // Update pagination values
9941 oState.pagination = oPaginatorState;
9943 // Get the request for the new state
9944 var request = this.get("generateRequest")(oState, this);
9947 this.unselectAllRows();
9948 this.unselectAllCells();
9950 // Get the new data from the server
9952 success : this.onDataReturnSetRows,
9953 failure : this.onDataReturnSetRows,
9954 argument : oState, // Pass along the new state to the callback
9957 this._oDataSource.sendRequest(request, callback);
9959 // Client-side pagination
9961 // Set the core pagination values silently (the second param)
9962 // to avoid looping back through the changeRequest mechanism
9963 oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
9964 oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
9971 YAHOO.log("Could not change Paginator value \"" + oPaginatorState + "\"", "warn", this.toString());
10024 // SELECTION/HIGHLIGHTING
10027 * Reference to last highlighted cell element
10029 * @property _elLastHighlightedTd
10030 * @type HTMLElement
10033 _elLastHighlightedTd : null,
10036 * ID string of last highlighted row element
10038 * @property _sLastHighlightedTrElId
10042 //_sLastHighlightedTrElId : null,
10045 * Array to track row selections (by sRecordId) and/or cell selections
10046 * (by {recordId:sRecordId, columnKey:sColumnKey})
10048 * @property _aSelections
10052 _aSelections : null,
10055 * Record instance of the row selection anchor.
10057 * @property _oAnchorRecord
10058 * @type YAHOO.widget.Record
10061 _oAnchorRecord : null,
10064 * Object literal representing cell selection anchor:
10065 * {recordId:sRecordId, columnKey:sColumnKey}.
10067 * @property _oAnchorCell
10071 _oAnchorCell : null,
10074 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
10075 * from all TR elements on the page.
10077 * @method _unselectAllTrEls
10080 _unselectAllTrEls : function() {
10081 var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
10082 Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
10086 * Returns object literal of values that represent the selection trigger. Used
10087 * to determine selection behavior resulting from a key event.
10089 * @method _getSelectionTrigger
10092 _getSelectionTrigger : function() {
10093 var sMode = this.get("selectionMode");
10095 var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
10098 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
10099 oTriggerCell = this.getLastSelectedCell();
10100 // No selected cells found
10101 if(!oTriggerCell) {
10105 oTriggerRecord = this.getRecord(oTriggerCell.recordId);
10106 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
10107 elTriggerRow = this.getTrEl(oTriggerRecord);
10108 nTriggerTrIndex = this.getTrIndex(elTriggerRow);
10110 // Selected cell not found on this page
10111 if(nTriggerTrIndex === null) {
10115 oTrigger.record = oTriggerRecord;
10116 oTrigger.recordIndex = nTriggerRecordIndex;
10117 oTrigger.el = this.getTdEl(oTriggerCell);
10118 oTrigger.trIndex = nTriggerTrIndex;
10119 oTrigger.column = this.getColumn(oTriggerCell.columnKey);
10120 oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
10121 oTrigger.cell = oTriggerCell;
10128 oTriggerRecord = this.getLastSelectedRecord();
10129 // No selected rows found
10130 if(!oTriggerRecord) {
10134 // Selected row found, but is it on current page?
10135 oTriggerRecord = this.getRecord(oTriggerRecord);
10136 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
10137 elTriggerRow = this.getTrEl(oTriggerRecord);
10138 nTriggerTrIndex = this.getTrIndex(elTriggerRow);
10140 // Selected row not found on this page
10141 if(nTriggerTrIndex === null) {
10145 oTrigger.record = oTriggerRecord;
10146 oTrigger.recordIndex = nTriggerRecordIndex;
10147 oTrigger.el = elTriggerRow;
10148 oTrigger.trIndex = nTriggerTrIndex;
10156 * Returns object literal of values that represent the selection anchor. Used
10157 * to determine selection behavior resulting from a user event.
10159 * @method _getSelectionAnchor
10160 * @param oTrigger {Object} (Optional) Object literal of selection trigger values
10161 * (for key events).
10164 _getSelectionAnchor : function(oTrigger) {
10165 var sMode = this.get("selectionMode");
10167 var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
10170 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
10171 // Validate anchor cell
10172 var oAnchorCell = this._oAnchorCell;
10175 oAnchorCell = this._oAnchorCell = oTrigger.cell;
10181 oAnchorRecord = this._oAnchorCell.record;
10182 nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
10183 nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
10184 // If anchor cell is not on this page...
10185 if(nAnchorTrIndex === null) {
10186 // ...set TR index equal to top TR
10187 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
10188 nAnchorTrIndex = 0;
10190 // ...set TR index equal to bottom TR
10192 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10196 oAnchor.record = oAnchorRecord;
10197 oAnchor.recordIndex = nAnchorRecordIndex;
10198 oAnchor.trIndex = nAnchorTrIndex;
10199 oAnchor.column = this._oAnchorCell.column;
10200 oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
10201 oAnchor.cell = oAnchorCell;
10206 oAnchorRecord = this._oAnchorRecord;
10207 if(!oAnchorRecord) {
10209 oAnchorRecord = this._oAnchorRecord = oTrigger.record;
10216 nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
10217 nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
10218 // If anchor row is not on this page...
10219 if(nAnchorTrIndex === null) {
10220 // ...set TR index equal to top TR
10221 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
10222 nAnchorTrIndex = 0;
10224 // ...set TR index equal to bottom TR
10226 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10230 oAnchor.record = oAnchorRecord;
10231 oAnchor.recordIndex = nAnchorRecordIndex;
10232 oAnchor.trIndex = nAnchorTrIndex;
10238 * Determines selection behavior resulting from a mouse event when selection mode
10239 * is set to "standard".
10241 * @method _handleStandardSelectionByMouse
10242 * @param oArgs.event {HTMLEvent} Event object.
10243 * @param oArgs.target {HTMLElement} Target element.
10246 _handleStandardSelectionByMouse : function(oArgs) {
10247 var elTarget = oArgs.target;
10249 // Validate target row
10250 var elTargetRow = this.getTrEl(elTarget);
10252 var e = oArgs.event;
10253 var bSHIFT = e.shiftKey;
10254 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10256 var oTargetRecord = this.getRecord(elTargetRow);
10257 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10259 var oAnchor = this._getSelectionAnchor();
10263 // Both SHIFT and CTRL
10264 if(bSHIFT && bCTRL) {
10267 if(this.isSelected(oAnchor.record)) {
10268 // Select all rows between anchor row and target row, including target row
10269 if(oAnchor.recordIndex < nTargetRecordIndex) {
10270 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
10271 if(!this.isSelected(i)) {
10276 // Select all rows between target row and anchor row, including target row
10278 for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
10279 if(!this.isSelected(i)) {
10286 // Unselect all rows between anchor row and target row
10287 if(oAnchor.recordIndex < nTargetRecordIndex) {
10288 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
10289 if(this.isSelected(i)) {
10290 this.unselectRow(i);
10294 // Unselect all rows between target row and anchor row
10296 for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
10297 if(this.isSelected(i)) {
10298 this.unselectRow(i);
10302 // Select the target row
10303 this.selectRow(oTargetRecord);
10309 this._oAnchorRecord = oTargetRecord;
10311 // Toggle selection of target
10312 if(this.isSelected(oTargetRecord)) {
10313 this.unselectRow(oTargetRecord);
10316 this.selectRow(oTargetRecord);
10322 this.unselectAllRows();
10326 // Select all rows between anchor row and target row,
10327 // including the anchor row and target row
10328 if(oAnchor.recordIndex < nTargetRecordIndex) {
10329 for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
10333 // Select all rows between target row and anchor row,
10334 // including the target row and anchor row
10336 for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
10344 this._oAnchorRecord = oTargetRecord;
10346 // Select target row only
10347 this.selectRow(oTargetRecord);
10353 this._oAnchorRecord = oTargetRecord;
10355 // Toggle selection of target
10356 if(this.isSelected(oTargetRecord)) {
10357 this.unselectRow(oTargetRecord);
10360 this.selectRow(oTargetRecord);
10363 // Neither SHIFT nor CTRL
10365 this._handleSingleSelectionByMouse(oArgs);
10372 * Determines selection behavior resulting from a key event when selection mode
10373 * is set to "standard".
10375 * @method _handleStandardSelectionByKey
10376 * @param e {HTMLEvent} Event object.
10379 _handleStandardSelectionByKey : function(e) {
10380 var nKey = Ev.getCharCode(e);
10382 if((nKey == 38) || (nKey == 40)) {
10383 var bSHIFT = e.shiftKey;
10385 // Validate trigger
10386 var oTrigger = this._getSelectionTrigger();
10387 // Arrow selection only works if last selected row is on current page
10395 var oAnchor = this._getSelectionAnchor(oTrigger);
10397 // Determine which direction we're going to
10399 // Selecting down away from anchor row
10400 if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
10401 this.selectRow(this.getNextTrEl(oTrigger.el));
10403 // Selecting up away from anchor row
10404 else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
10405 this.selectRow(this.getPreviousTrEl(oTrigger.el));
10407 // Unselect trigger
10409 this.unselectRow(oTrigger.el);
10413 this._handleSingleSelectionByKey(e);
10419 * Determines selection behavior resulting from a mouse event when selection mode
10420 * is set to "single".
10422 * @method _handleSingleSelectionByMouse
10423 * @param oArgs.event {HTMLEvent} Event object.
10424 * @param oArgs.target {HTMLElement} Target element.
10427 _handleSingleSelectionByMouse : function(oArgs) {
10428 var elTarget = oArgs.target;
10430 // Validate target row
10431 var elTargetRow = this.getTrEl(elTarget);
10433 var oTargetRecord = this.getRecord(elTargetRow);
10436 this._oAnchorRecord = oTargetRecord;
10438 // Select only target
10439 this.unselectAllRows();
10440 this.selectRow(oTargetRecord);
10445 * Determines selection behavior resulting from a key event when selection mode
10446 * is set to "single".
10448 * @method _handleSingleSelectionByKey
10449 * @param e {HTMLEvent} Event object.
10452 _handleSingleSelectionByKey : function(e) {
10453 var nKey = Ev.getCharCode(e);
10455 if((nKey == 38) || (nKey == 40)) {
10456 // Validate trigger
10457 var oTrigger = this._getSelectionTrigger();
10458 // Arrow selection only works if last selected row is on current page
10465 // Determine the new row to select
10467 if(nKey == 38) { // arrow up
10468 elNew = this.getPreviousTrEl(oTrigger.el);
10470 // Validate new row
10471 if(elNew === null) {
10472 //TODO: wrap around to last tr on current page
10473 //elNew = this.getLastTrEl();
10475 //TODO: wrap back to last tr of previous page
10477 // Top row selection is sticky
10478 elNew = this.getFirstTrEl();
10481 else if(nKey == 40) { // arrow down
10482 elNew = this.getNextTrEl(oTrigger.el);
10484 // Validate new row
10485 if(elNew === null) {
10486 //TODO: wrap around to first tr on current page
10487 //elNew = this.getFirstTrEl();
10489 //TODO: wrap forward to first tr of previous page
10491 // Bottom row selection is sticky
10492 elNew = this.getLastTrEl();
10496 // Unselect all rows
10497 this.unselectAllRows();
10499 // Select the new row
10500 this.selectRow(elNew);
10503 this._oAnchorRecord = this.getRecord(elNew);
10508 * Determines selection behavior resulting from a mouse event when selection mode
10509 * is set to "cellblock".
10511 * @method _handleCellBlockSelectionByMouse
10512 * @param oArgs.event {HTMLEvent} Event object.
10513 * @param oArgs.target {HTMLElement} Target element.
10516 _handleCellBlockSelectionByMouse : function(oArgs) {
10517 var elTarget = oArgs.target;
10519 // Validate target cell
10520 var elTargetCell = this.getTdEl(elTarget);
10522 var e = oArgs.event;
10523 var bSHIFT = e.shiftKey;
10524 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10526 var elTargetRow = this.getTrEl(elTargetCell);
10527 var nTargetTrIndex = this.getTrIndex(elTargetRow);
10528 var oTargetColumn = this.getColumn(elTargetCell);
10529 var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10530 var oTargetRecord = this.getRecord(elTargetRow);
10531 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10532 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10534 var oAnchor = this._getSelectionAnchor();
10536 var allRows = this.getTbodyEl().rows;
10537 var startIndex, endIndex, currentRow, i, j;
10539 // Both SHIFT and CTRL
10540 if(bSHIFT && bCTRL) {
10544 // Anchor is selected
10545 if(this.isSelected(oAnchor.cell)) {
10546 // All cells are on the same row
10547 if(oAnchor.recordIndex === nTargetRecordIndex) {
10548 // Select all cells between anchor cell and target cell, including target cell
10549 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10550 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10551 this.selectCell(elTargetRow.cells[i]);
10554 // Select all cells between target cell and anchor cell, including target cell
10555 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10556 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10557 this.selectCell(elTargetRow.cells[i]);
10561 // Anchor row is above target row
10562 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10563 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10564 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10566 // Select all cells from startIndex to endIndex on rows between anchor row and target row
10567 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10568 for(j=startIndex; j<=endIndex; j++) {
10569 this.selectCell(allRows[i].cells[j]);
10573 // Anchor row is below target row
10575 startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
10576 endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
10578 // Select all cells from startIndex to endIndex on rows between target row and anchor row
10579 for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
10580 for(j=endIndex; j>=startIndex; j--) {
10581 this.selectCell(allRows[i].cells[j]);
10586 // Anchor cell is unselected
10588 // All cells are on the same row
10589 if(oAnchor.recordIndex === nTargetRecordIndex) {
10590 // Unselect all cells between anchor cell and target cell
10591 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10592 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10593 this.unselectCell(elTargetRow.cells[i]);
10596 // Select all cells between target cell and anchor cell
10597 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10598 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10599 this.unselectCell(elTargetRow.cells[i]);
10603 // Anchor row is above target row
10604 if(oAnchor.recordIndex < nTargetRecordIndex) {
10605 // Unselect all cells from anchor cell to target cell
10606 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10607 currentRow = allRows[i];
10608 for(j=0; j<currentRow.cells.length; j++) {
10609 // This is the anchor row, only unselect cells after the anchor cell
10610 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
10611 if(j>oAnchor.colKeyIndex) {
10612 this.unselectCell(currentRow.cells[j]);
10615 // This is the target row, only unelect cells before the target cell
10616 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
10617 if(j<nTargetColKeyIndex) {
10618 this.unselectCell(currentRow.cells[j]);
10621 // Unselect all cells on this row
10623 this.unselectCell(currentRow.cells[j]);
10628 // Anchor row is below target row
10630 // Unselect all cells from target cell to anchor cell
10631 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10632 currentRow = allRows[i];
10633 for(j=0; j<currentRow.cells.length; j++) {
10634 // This is the target row, only unselect cells after the target cell
10635 if(currentRow.sectionRowIndex == nTargetTrIndex) {
10636 if(j>nTargetColKeyIndex) {
10637 this.unselectCell(currentRow.cells[j]);
10640 // This is the anchor row, only unselect cells before the anchor cell
10641 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10642 if(j<oAnchor.colKeyIndex) {
10643 this.unselectCell(currentRow.cells[j]);
10646 // Unselect all cells on this row
10648 this.unselectCell(currentRow.cells[j]);
10654 // Select the target cell
10655 this.selectCell(elTargetCell);
10661 this._oAnchorCell = oTargetCell;
10663 // Toggle selection of target
10664 if(this.isSelected(oTargetCell)) {
10665 this.unselectCell(oTargetCell);
10668 this.selectCell(oTargetCell);
10675 this.unselectAllCells();
10679 // All cells are on the same row
10680 if(oAnchor.recordIndex === nTargetRecordIndex) {
10681 // Select all cells between anchor cell and target cell,
10682 // including the anchor cell and target cell
10683 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10684 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
10685 this.selectCell(elTargetRow.cells[i]);
10688 // Select all cells between target cell and anchor cell
10689 // including the target cell and anchor cell
10690 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10691 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
10692 this.selectCell(elTargetRow.cells[i]);
10696 // Anchor row is above target row
10697 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10698 // Select the cellblock from anchor cell to target cell
10699 // including the anchor cell and the target cell
10700 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10701 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10703 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10704 for(j=startIndex; j<=endIndex; j++) {
10705 this.selectCell(allRows[i].cells[j]);
10709 // Anchor row is below target row
10711 // Select the cellblock from target cell to anchor cell
10712 // including the target cell and the anchor cell
10713 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10714 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10716 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10717 for(j=startIndex; j<=endIndex; j++) {
10718 this.selectCell(allRows[i].cells[j]);
10726 this._oAnchorCell = oTargetCell;
10728 // Select target only
10729 this.selectCell(oTargetCell);
10736 this._oAnchorCell = oTargetCell;
10738 // Toggle selection of target
10739 if(this.isSelected(oTargetCell)) {
10740 this.unselectCell(oTargetCell);
10743 this.selectCell(oTargetCell);
10747 // Neither SHIFT nor CTRL
10749 this._handleSingleCellSelectionByMouse(oArgs);
10755 * Determines selection behavior resulting from a key event when selection mode
10756 * is set to "cellblock".
10758 * @method _handleCellBlockSelectionByKey
10759 * @param e {HTMLEvent} Event object.
10762 _handleCellBlockSelectionByKey : function(e) {
10763 var nKey = Ev.getCharCode(e);
10764 var bSHIFT = e.shiftKey;
10765 if((nKey == 9) || !bSHIFT) {
10766 this._handleSingleCellSelectionByKey(e);
10770 if((nKey > 36) && (nKey < 41)) {
10771 // Validate trigger
10772 var oTrigger = this._getSelectionTrigger();
10773 // Arrow selection only works if last selected row is on current page
10781 var oAnchor = this._getSelectionAnchor(oTrigger);
10783 var i, startIndex, endIndex, elNew, elNewRow;
10784 var allRows = this.getTbodyEl().rows;
10785 var elThisRow = oTrigger.el.parentNode;
10787 // Determine which direction we're going to
10789 if(nKey == 40) { // arrow down
10790 // Selecting away from anchor cell
10791 if(oAnchor.recordIndex <= oTrigger.recordIndex) {
10792 // Select the horiz block on the next row...
10793 // ...making sure there is room below the trigger row
10794 elNewRow = this.getNextTrEl(oTrigger.el);
10796 startIndex = oAnchor.colKeyIndex;
10797 endIndex = oTrigger.colKeyIndex;
10799 if(startIndex > endIndex) {
10800 for(i=startIndex; i>=endIndex; i--) {
10801 elNew = elNewRow.cells[i];
10802 this.selectCell(elNew);
10807 for(i=startIndex; i<=endIndex; i++) {
10808 elNew = elNewRow.cells[i];
10809 this.selectCell(elNew);
10814 // Unselecting towards anchor cell
10816 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10817 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10818 // Unselect the horiz block on this row towards the next row
10819 for(i=startIndex; i<=endIndex; i++) {
10820 this.unselectCell(elThisRow.cells[i]);
10825 else if(nKey == 38) {
10826 // Selecting away from anchor cell
10827 if(oAnchor.recordIndex >= oTrigger.recordIndex) {
10828 // Select the horiz block on the previous row...
10829 // ...making sure there is room
10830 elNewRow = this.getPreviousTrEl(oTrigger.el);
10832 // Select in order from anchor to trigger...
10833 startIndex = oAnchor.colKeyIndex;
10834 endIndex = oTrigger.colKeyIndex;
10836 if(startIndex > endIndex) {
10837 for(i=startIndex; i>=endIndex; i--) {
10838 elNew = elNewRow.cells[i];
10839 this.selectCell(elNew);
10844 for(i=startIndex; i<=endIndex; i++) {
10845 elNew = elNewRow.cells[i];
10846 this.selectCell(elNew);
10851 // Unselecting towards anchor cell
10853 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10854 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10855 // Unselect the horiz block on this row towards the previous row
10856 for(i=startIndex; i<=endIndex; i++) {
10857 this.unselectCell(elThisRow.cells[i]);
10862 else if(nKey == 39) {
10863 // Selecting away from anchor cell
10864 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
10865 // Select the next vert block to the right...
10866 // ...making sure there is room
10867 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
10868 // Select in order from anchor to trigger...
10869 startIndex = oAnchor.trIndex;
10870 endIndex = oTrigger.trIndex;
10872 if(startIndex > endIndex) {
10873 for(i=startIndex; i>=endIndex; i--) {
10874 elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10875 this.selectCell(elNew);
10880 for(i=startIndex; i<=endIndex; i++) {
10881 elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10882 this.selectCell(elNew);
10887 // Unselecting towards anchor cell
10889 // Unselect the vert block on this column towards the right
10890 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10891 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10892 for(i=startIndex; i<=endIndex; i++) {
10893 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10898 else if(nKey == 37) {
10899 // Selecting away from anchor cell
10900 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
10901 //Select the previous vert block to the left
10902 if(oTrigger.colKeyIndex > 0) {
10903 // Select in order from anchor to trigger...
10904 startIndex = oAnchor.trIndex;
10905 endIndex = oTrigger.trIndex;
10907 if(startIndex > endIndex) {
10908 for(i=startIndex; i>=endIndex; i--) {
10909 elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10910 this.selectCell(elNew);
10915 for(i=startIndex; i<=endIndex; i++) {
10916 elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10917 this.selectCell(elNew);
10922 // Unselecting towards anchor cell
10924 // Unselect the vert block on this column towards the left
10925 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10926 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10927 for(i=startIndex; i<=endIndex; i++) {
10928 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10936 * Determines selection behavior resulting from a mouse event when selection mode
10937 * is set to "cellrange".
10939 * @method _handleCellRangeSelectionByMouse
10940 * @param oArgs.event {HTMLEvent} Event object.
10941 * @param oArgs.target {HTMLElement} Target element.
10944 _handleCellRangeSelectionByMouse : function(oArgs) {
10945 var elTarget = oArgs.target;
10947 // Validate target cell
10948 var elTargetCell = this.getTdEl(elTarget);
10950 var e = oArgs.event;
10951 var bSHIFT = e.shiftKey;
10952 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10954 var elTargetRow = this.getTrEl(elTargetCell);
10955 var nTargetTrIndex = this.getTrIndex(elTargetRow);
10956 var oTargetColumn = this.getColumn(elTargetCell);
10957 var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10958 var oTargetRecord = this.getRecord(elTargetRow);
10959 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10960 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10962 var oAnchor = this._getSelectionAnchor();
10964 var allRows = this.getTbodyEl().rows;
10965 var currentRow, i, j;
10967 // Both SHIFT and CTRL
10968 if(bSHIFT && bCTRL) {
10972 // Anchor is selected
10973 if(this.isSelected(oAnchor.cell)) {
10974 // All cells are on the same row
10975 if(oAnchor.recordIndex === nTargetRecordIndex) {
10976 // Select all cells between anchor cell and target cell, including target cell
10977 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10978 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10979 this.selectCell(elTargetRow.cells[i]);
10982 // Select all cells between target cell and anchor cell, including target cell
10983 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10984 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10985 this.selectCell(elTargetRow.cells[i]);
10989 // Anchor row is above target row
10990 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10991 // Select all cells on anchor row from anchor cell to the end of the row
10992 for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
10993 this.selectCell(elTargetRow.cells[i]);
10996 // Select all cells on all rows between anchor row and target row
10997 for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
10998 for(j=0; j<allRows[i].cells.length; j++){
10999 this.selectCell(allRows[i].cells[j]);
11003 // Select all cells on target row from first cell to the target cell
11004 for(i=0; i<=nTargetColKeyIndex; i++) {
11005 this.selectCell(elTargetRow.cells[i]);
11008 // Anchor row is below target row
11010 // Select all cells on target row from target cell to the end of the row
11011 for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
11012 this.selectCell(elTargetRow.cells[i]);
11015 // Select all cells on all rows between target row and anchor row
11016 for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
11017 for(j=0; j<allRows[i].cells.length; j++){
11018 this.selectCell(allRows[i].cells[j]);
11022 // Select all cells on anchor row from first cell to the anchor cell
11023 for(i=0; i<oAnchor.colKeyIndex; i++) {
11024 this.selectCell(elTargetRow.cells[i]);
11028 // Anchor cell is unselected
11030 // All cells are on the same row
11031 if(oAnchor.recordIndex === nTargetRecordIndex) {
11032 // Unselect all cells between anchor cell and target cell
11033 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
11034 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
11035 this.unselectCell(elTargetRow.cells[i]);
11038 // Select all cells between target cell and anchor cell
11039 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
11040 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
11041 this.unselectCell(elTargetRow.cells[i]);
11045 // Anchor row is above target row
11046 if(oAnchor.recordIndex < nTargetRecordIndex) {
11047 // Unselect all cells from anchor cell to target cell
11048 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
11049 currentRow = allRows[i];
11050 for(j=0; j<currentRow.cells.length; j++) {
11051 // This is the anchor row, only unselect cells after the anchor cell
11052 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
11053 if(j>oAnchor.colKeyIndex) {
11054 this.unselectCell(currentRow.cells[j]);
11057 // This is the target row, only unelect cells before the target cell
11058 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
11059 if(j<nTargetColKeyIndex) {
11060 this.unselectCell(currentRow.cells[j]);
11063 // Unselect all cells on this row
11065 this.unselectCell(currentRow.cells[j]);
11070 // Anchor row is below target row
11072 // Unselect all cells from target cell to anchor cell
11073 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
11074 currentRow = allRows[i];
11075 for(j=0; j<currentRow.cells.length; j++) {
11076 // This is the target row, only unselect cells after the target cell
11077 if(currentRow.sectionRowIndex == nTargetTrIndex) {
11078 if(j>nTargetColKeyIndex) {
11079 this.unselectCell(currentRow.cells[j]);
11082 // This is the anchor row, only unselect cells before the anchor cell
11083 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11084 if(j<oAnchor.colKeyIndex) {
11085 this.unselectCell(currentRow.cells[j]);
11088 // Unselect all cells on this row
11090 this.unselectCell(currentRow.cells[j]);
11096 // Select the target cell
11097 this.selectCell(elTargetCell);
11103 this._oAnchorCell = oTargetCell;
11105 // Toggle selection of target
11106 if(this.isSelected(oTargetCell)) {
11107 this.unselectCell(oTargetCell);
11110 this.selectCell(oTargetCell);
11117 this.unselectAllCells();
11121 // All cells are on the same row
11122 if(oAnchor.recordIndex === nTargetRecordIndex) {
11123 // Select all cells between anchor cell and target cell,
11124 // including the anchor cell and target cell
11125 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
11126 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
11127 this.selectCell(elTargetRow.cells[i]);
11130 // Select all cells between target cell and anchor cell
11131 // including the target cell and anchor cell
11132 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
11133 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
11134 this.selectCell(elTargetRow.cells[i]);
11138 // Anchor row is above target row
11139 else if(oAnchor.recordIndex < nTargetRecordIndex) {
11140 // Select all cells from anchor cell to target cell
11141 // including the anchor cell and target cell
11142 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
11143 currentRow = allRows[i];
11144 for(j=0; j<currentRow.cells.length; j++) {
11145 // This is the anchor row, only select the anchor cell and after
11146 if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11147 if(j>=oAnchor.colKeyIndex) {
11148 this.selectCell(currentRow.cells[j]);
11151 // This is the target row, only select the target cell and before
11152 else if(currentRow.sectionRowIndex == nTargetTrIndex) {
11153 if(j<=nTargetColKeyIndex) {
11154 this.selectCell(currentRow.cells[j]);
11157 // Select all cells on this row
11159 this.selectCell(currentRow.cells[j]);
11164 // Anchor row is below target row
11166 // Select all cells from target cell to anchor cell,
11167 // including the target cell and anchor cell
11168 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
11169 currentRow = allRows[i];
11170 for(j=0; j<currentRow.cells.length; j++) {
11171 // This is the target row, only select the target cell and after
11172 if(currentRow.sectionRowIndex == nTargetTrIndex) {
11173 if(j>=nTargetColKeyIndex) {
11174 this.selectCell(currentRow.cells[j]);
11177 // This is the anchor row, only select the anchor cell and before
11178 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11179 if(j<=oAnchor.colKeyIndex) {
11180 this.selectCell(currentRow.cells[j]);
11183 // Select all cells on this row
11185 this.selectCell(currentRow.cells[j]);
11194 this._oAnchorCell = oTargetCell;
11196 // Select target only
11197 this.selectCell(oTargetCell);
11206 this._oAnchorCell = oTargetCell;
11208 // Toggle selection of target
11209 if(this.isSelected(oTargetCell)) {
11210 this.unselectCell(oTargetCell);
11213 this.selectCell(oTargetCell);
11217 // Neither SHIFT nor CTRL
11219 this._handleSingleCellSelectionByMouse(oArgs);
11225 * Determines selection behavior resulting from a key event when selection mode
11226 * is set to "cellrange".
11228 * @method _handleCellRangeSelectionByKey
11229 * @param e {HTMLEvent} Event object.
11232 _handleCellRangeSelectionByKey : function(e) {
11233 var nKey = Ev.getCharCode(e);
11234 var bSHIFT = e.shiftKey;
11235 if((nKey == 9) || !bSHIFT) {
11236 this._handleSingleCellSelectionByKey(e);
11240 if((nKey > 36) && (nKey < 41)) {
11241 // Validate trigger
11242 var oTrigger = this._getSelectionTrigger();
11243 // Arrow selection only works if last selected row is on current page
11251 var oAnchor = this._getSelectionAnchor(oTrigger);
11253 var i, elNewRow, elNew;
11254 var allRows = this.getTbodyEl().rows;
11255 var elThisRow = oTrigger.el.parentNode;
11259 elNewRow = this.getNextTrEl(oTrigger.el);
11261 // Selecting away from anchor cell
11262 if(oAnchor.recordIndex <= oTrigger.recordIndex) {
11263 // Select all cells to the end of this row
11264 for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){
11265 elNew = elThisRow.cells[i];
11266 this.selectCell(elNew);
11269 // Select some of the cells on the next row down
11271 for(i=0; i<=oTrigger.colKeyIndex; i++){
11272 elNew = elNewRow.cells[i];
11273 this.selectCell(elNew);
11277 // Unselecting towards anchor cell
11279 // Unselect all cells to the end of this row
11280 for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){
11281 this.unselectCell(elThisRow.cells[i]);
11284 // Unselect some of the cells on the next row down
11286 for(i=0; i<oTrigger.colKeyIndex; i++){
11287 this.unselectCell(elNewRow.cells[i]);
11293 else if(nKey == 38) {
11294 elNewRow = this.getPreviousTrEl(oTrigger.el);
11296 // Selecting away from anchor cell
11297 if(oAnchor.recordIndex >= oTrigger.recordIndex) {
11298 // Select all the cells to the beginning of this row
11299 for(i=oTrigger.colKeyIndex-1; i>-1; i--){
11300 elNew = elThisRow.cells[i];
11301 this.selectCell(elNew);
11304 // Select some of the cells from the end of the previous row
11306 for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){
11307 elNew = elNewRow.cells[i];
11308 this.selectCell(elNew);
11312 // Unselecting towards anchor cell
11314 // Unselect all the cells to the beginning of this row
11315 for(i=oTrigger.colKeyIndex; i>-1; i--){
11316 this.unselectCell(elThisRow.cells[i]);
11319 // Unselect some of the cells from the end of the previous row
11321 for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){
11322 this.unselectCell(elNewRow.cells[i]);
11328 else if(nKey == 39) {
11329 elNewRow = this.getNextTrEl(oTrigger.el);
11331 // Selecting away from anchor cell
11332 if(oAnchor.recordIndex < oTrigger.recordIndex) {
11333 // Select the next cell to the right
11334 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11335 elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11336 this.selectCell(elNew);
11338 // Select the first cell of the next row
11339 else if(elNewRow) {
11340 elNew = elNewRow.cells[0];
11341 this.selectCell(elNew);
11344 // Unselecting towards anchor cell
11345 else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11346 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11348 // Unselect this cell towards the right
11349 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11351 // Unselect this cells towards the first cell of the next row
11355 // Anchor is on this row
11357 // Selecting away from anchor
11358 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
11359 // Select the next cell to the right
11360 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11361 elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11362 this.selectCell(elNew);
11364 // Select the first cell on the next row
11365 else if(oTrigger.trIndex < allRows.length-1){
11366 elNew = elNewRow.cells[0];
11367 this.selectCell(elNew);
11370 // Unselecting towards anchor
11372 // Unselect this cell towards the right
11373 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11378 else if(nKey == 37) {
11379 elNewRow = this.getPreviousTrEl(oTrigger.el);
11381 // Unselecting towards the anchor
11382 if(oAnchor.recordIndex < oTrigger.recordIndex) {
11383 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11385 // Unselect this cell towards the left
11386 if(oTrigger.colKeyIndex > 0) {
11388 // Unselect this cell towards the last cell of the previous row
11392 // Selecting towards the anchor
11393 else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11394 // Select the next cell to the left
11395 if(oTrigger.colKeyIndex > 0) {
11396 elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11397 this.selectCell(elNew);
11399 // Select the last cell of the previous row
11400 else if(oTrigger.trIndex > 0){
11401 elNew = elNewRow.cells[elNewRow.cells.length-1];
11402 this.selectCell(elNew);
11405 // Anchor is on this row
11407 // Selecting away from anchor cell
11408 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
11409 // Select the next cell to the left
11410 if(oTrigger.colKeyIndex > 0) {
11411 elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11412 this.selectCell(elNew);
11414 // Select the last cell of the previous row
11415 else if(oTrigger.trIndex > 0){
11416 elNew = elNewRow.cells[elNewRow.cells.length-1];
11417 this.selectCell(elNew);
11420 // Unselecting towards anchor cell
11422 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11424 // Unselect this cell towards the left
11425 if(oTrigger.colKeyIndex > 0) {
11427 // Unselect this cell towards the last cell of the previous row
11437 * Determines selection behavior resulting from a mouse event when selection mode
11438 * is set to "singlecell".
11440 * @method _handleSingleCellSelectionByMouse
11441 * @param oArgs.event {HTMLEvent} Event object.
11442 * @param oArgs.target {HTMLElement} Target element.
11445 _handleSingleCellSelectionByMouse : function(oArgs) {
11446 var elTarget = oArgs.target;
11448 // Validate target cell
11449 var elTargetCell = this.getTdEl(elTarget);
11451 var elTargetRow = this.getTrEl(elTargetCell);
11452 var oTargetRecord = this.getRecord(elTargetRow);
11453 var oTargetColumn = this.getColumn(elTargetCell);
11454 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
11457 this._oAnchorCell = oTargetCell;
11459 // Select only target
11460 this.unselectAllCells();
11461 this.selectCell(oTargetCell);
11466 * Determines selection behavior resulting from a key event when selection mode
11467 * is set to "singlecell".
11469 * @method _handleSingleCellSelectionByKey
11470 * @param e {HTMLEvent} Event object.
11473 _handleSingleCellSelectionByKey : function(e) {
11474 var nKey = Ev.getCharCode(e);
11475 if((nKey == 9) || ((nKey > 36) && (nKey < 41))) {
11476 var bSHIFT = e.shiftKey;
11478 // Validate trigger
11479 var oTrigger = this._getSelectionTrigger();
11480 // Arrow selection only works if last selected row is on current page
11485 // Determine the new cell to select
11487 if(nKey == 40) { // Arrow down
11488 elNew = this.getBelowTdEl(oTrigger.el);
11490 // Validate new cell
11491 if(elNew === null) {
11492 //TODO: wrap around to first tr on current page
11494 //TODO: wrap forward to first tr of next page
11496 // Bottom selection is sticky
11497 elNew = oTrigger.el;
11500 else if(nKey == 38) { // Arrow up
11501 elNew = this.getAboveTdEl(oTrigger.el);
11503 // Validate new cell
11504 if(elNew === null) {
11505 //TODO: wrap around to last tr on current page
11507 //TODO: wrap back to last tr of previous page
11509 // Top selection is sticky
11510 elNew = oTrigger.el;
11513 else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab
11514 elNew = this.getNextTdEl(oTrigger.el);
11516 // Validate new cell
11517 if(elNew === null) {
11518 //TODO: wrap around to first td on current page
11520 //TODO: wrap forward to first td of next page
11522 // Top-left selection is sticky, and release TAB focus
11523 //elNew = oTrigger.el;
11527 else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab
11528 elNew = this.getPreviousTdEl(oTrigger.el);
11530 // Validate new cell
11531 if(elNew === null) {
11532 //TODO: wrap around to last td on current page
11534 //TODO: wrap back to last td of previous page
11536 // Bottom-right selection is sticky, and release TAB focus
11537 //elNew = oTrigger.el;
11544 // Unselect all cells
11545 this.unselectAllCells();
11547 // Select the new cell
11548 this.selectCell(elNew);
11551 this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)};
11556 * Returns array of selected TR elements on the page.
11558 * @method getSelectedTrEls
11559 * @return {HTMLElement[]} Array of selected TR elements.
11561 getSelectedTrEls : function() {
11562 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
11566 * Sets given row to the selected state.
11568 * @method selectRow
11569 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11570 * reference or ID string, Record instance, or RecordSet position index.
11572 selectRow : function(row) {
11573 var oRecord, elRow;
11575 if(row instanceof YAHOO.widget.Record) {
11576 oRecord = this._oRecordSet.getRecord(row);
11577 elRow = this.getTrEl(oRecord);
11579 else if(lang.isNumber(row)) {
11580 oRecord = this.getRecord(row);
11581 elRow = this.getTrEl(oRecord);
11584 elRow = this.getTrEl(row);
11585 oRecord = this.getRecord(elRow);
11589 // Update selection trackers
11590 var tracker = this._aSelections || [];
11591 var sRecordId = oRecord.getId();
11594 // Remove if already there:
11595 // Use Array.indexOf if available...
11596 /*if(tracker.indexOf && (tracker.indexOf(sRecordId) > -1)) {
11597 tracker.splice(tracker.indexOf(sRecordId),1);
11599 if(tracker.indexOf) {
11600 index = tracker.indexOf(sRecordId);
11603 // ...or do it the old-fashioned way
11605 for(var j=tracker.length-1; j>-1; j--) {
11606 if(tracker[j] === sRecordId){
11613 tracker.splice(index,1);
11617 tracker.push(sRecordId);
11618 this._aSelections = tracker;
11621 if(!this._oAnchorRecord) {
11622 this._oAnchorRecord = oRecord;
11627 Dom.addClass(elRow, DT.CLASS_SELECTED);
11630 this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
11631 YAHOO.log("Selected " + elRow, "info", this.toString());
11634 YAHOO.log("Could not select row " + row, "warn", this.toString());
11639 * Sets given row to the unselected state.
11641 * @method unselectRow
11642 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11643 * reference or ID string, Record instance, or RecordSet position index.
11645 unselectRow : function(row) {
11646 var elRow = this.getTrEl(row);
11649 if(row instanceof YAHOO.widget.Record) {
11650 oRecord = this._oRecordSet.getRecord(row);
11652 else if(lang.isNumber(row)) {
11653 oRecord = this.getRecord(row);
11656 oRecord = this.getRecord(elRow);
11660 // Update selection trackers
11661 var tracker = this._aSelections || [];
11662 var sRecordId = oRecord.getId();
11665 // Use Array.indexOf if available...
11666 if(tracker.indexOf) {
11667 index = tracker.indexOf(sRecordId);
11669 // ...or do it the old-fashioned way
11671 for(var j=tracker.length-1; j>-1; j--) {
11672 if(tracker[j] === sRecordId){
11680 tracker.splice(index,1);
11681 this._aSelections = tracker;
11684 Dom.removeClass(elRow, DT.CLASS_SELECTED);
11686 this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
11687 YAHOO.log("Unselected " + elRow, "info", this.toString());
11692 YAHOO.log("Could not unselect row " + row, "warn", this.toString());
11696 * Clears out all row selections.
11698 * @method unselectAllRows
11700 unselectAllRows : function() {
11701 // Remove all rows from tracker
11702 var tracker = this._aSelections || [],
11705 for(var j=tracker.length-1; j>-1; j--) {
11706 if(lang.isString(tracker[j])){
11707 recId = tracker.splice(j,1);
11708 removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId);
11713 this._aSelections = tracker;
11716 this._unselectAllTrEls();
11718 this.fireEvent("unselectAllRowsEvent", {records: removed});
11719 YAHOO.log("Unselected all rows", "info", this.toString());
11723 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
11724 * from all TD elements in the internal tracker.
11726 * @method _unselectAllTdEls
11729 _unselectAllTdEls : function() {
11730 var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11731 Dom.removeClass(selectedCells, DT.CLASS_SELECTED);
11735 * Returns array of selected TD elements on the page.
11737 * @method getSelectedTdEls
11738 * @return {HTMLElement[]} Array of selected TD elements.
11740 getSelectedTdEls : function() {
11741 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11745 * Sets given cell to the selected state.
11747 * @method selectCell
11748 * @param cell {HTMLElement | String} DOM element reference or ID string
11749 * to DataTable page element or RecordSet index.
11751 selectCell : function(cell) {
11752 //TODO: accept {record} in selectCell()
11753 var elCell = this.getTdEl(cell);
11756 var oRecord = this.getRecord(elCell);
11757 var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11759 if(oRecord && sColumnKey) {
11761 var tracker = this._aSelections || [];
11762 var sRecordId = oRecord.getId();
11765 for(var j=tracker.length-1; j>-1; j--) {
11766 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11767 tracker.splice(j,1);
11773 tracker.push({recordId:sRecordId, columnKey:sColumnKey});
11776 this._aSelections = tracker;
11777 if(!this._oAnchorCell) {
11778 this._oAnchorCell = {record:oRecord, column:this.getColumn(sColumnKey)};
11782 Dom.addClass(elCell, DT.CLASS_SELECTED);
11784 this.fireEvent("cellSelectEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key: this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11785 YAHOO.log("Selected " + elCell, "info", this.toString());
11789 YAHOO.log("Could not select cell " + cell, "warn", this.toString());
11793 * Sets given cell to the unselected state.
11795 * @method unselectCell
11796 * @param cell {HTMLElement | String} DOM element reference or ID string
11797 * to DataTable page element or RecordSet index.
11799 unselectCell : function(cell) {
11800 var elCell = this.getTdEl(cell);
11803 var oRecord = this.getRecord(elCell);
11804 var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11806 if(oRecord && sColumnKey) {
11808 var tracker = this._aSelections || [];
11809 var id = oRecord.getId();
11812 for(var j=tracker.length-1; j>-1; j--) {
11813 if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){
11814 // Remove from tracker
11815 tracker.splice(j,1);
11818 this._aSelections = tracker;
11821 Dom.removeClass(elCell, DT.CLASS_SELECTED);
11823 this.fireEvent("cellUnselectEvent", {record:oRecord, column: this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11824 YAHOO.log("Unselected " + elCell, "info", this.toString());
11830 YAHOO.log("Could not unselect cell " + cell, "warn", this.toString());
11834 * Clears out all cell selections.
11836 * @method unselectAllCells
11838 unselectAllCells : function() {
11839 // Remove all cells from tracker
11840 var tracker = this._aSelections || [];
11841 for(var j=tracker.length-1; j>-1; j--) {
11842 if(lang.isObject(tracker[j])){
11843 tracker.splice(j,1);
11848 this._aSelections = tracker;
11851 this._unselectAllTdEls();
11853 //TODO: send data to unselectAllCellsEvent handler
11854 this.fireEvent("unselectAllCellsEvent");
11855 YAHOO.log("Unselected all cells", "info", this.toString());
11859 * Returns true if given item is selected, false otherwise.
11861 * @method isSelected
11862 * @param o {String | HTMLElement | YAHOO.widget.Record | Number
11863 * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by
11864 * reference or ID string, a Record instance, a RecordSet position index,
11865 * or an object literal representation
11867 * @return {Boolean} True if item is selected.
11869 isSelected : function(o) {
11870 if(o && (o.ownerDocument == document)) {
11871 return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED));
11874 var oRecord, sRecordId, j;
11875 var tracker = this._aSelections;
11876 if(tracker && tracker.length > 0) {
11877 // Looking for a Record?
11878 if(o instanceof YAHOO.widget.Record) {
11881 else if(lang.isNumber(o)) {
11882 oRecord = this.getRecord(o);
11885 sRecordId = oRecord.getId();
11888 // Use Array.indexOf if available...
11889 if(tracker.indexOf) {
11890 if(tracker.indexOf(sRecordId) > -1) {
11894 // ...or do it the old-fashioned way
11896 for(j=tracker.length-1; j>-1; j--) {
11897 if(tracker[j] === sRecordId){
11903 // Looking for a cell
11904 else if(o.record && o.column){
11905 sRecordId = o.record.getId();
11906 var sColumnKey = o.column.getKey();
11908 for(j=tracker.length-1; j>-1; j--) {
11909 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11920 * Returns selected rows as an array of Record IDs.
11922 * @method getSelectedRows
11923 * @return {String[]} Array of selected rows by Record ID.
11925 getSelectedRows : function() {
11926 var aSelectedRows = [];
11927 var tracker = this._aSelections || [];
11928 for(var j=0; j<tracker.length; j++) {
11929 if(lang.isString(tracker[j])){
11930 aSelectedRows.push(tracker[j]);
11933 return aSelectedRows;
11937 * Returns selected cells as an array of object literals:
11938 * {recordId:sRecordId, columnKey:sColumnKey}.
11940 * @method getSelectedCells
11941 * @return {Object[]} Array of selected cells by Record ID and Column ID.
11943 getSelectedCells : function() {
11944 var aSelectedCells = [];
11945 var tracker = this._aSelections || [];
11946 for(var j=0; j<tracker.length; j++) {
11947 if(tracker[j] && lang.isObject(tracker[j])){
11948 aSelectedCells.push(tracker[j]);
11951 return aSelectedCells;
11955 * Returns last selected Record ID.
11957 * @method getLastSelectedRecord
11958 * @return {String} Record ID of last selected row.
11960 getLastSelectedRecord : function() {
11961 var tracker = this._aSelections;
11962 if(tracker && tracker.length > 0) {
11963 for(var i=tracker.length-1; i>-1; i--) {
11964 if(lang.isString(tracker[i])){
11972 * Returns last selected cell as an object literal:
11973 * {recordId:sRecordId, columnKey:sColumnKey}.
11975 * @method getLastSelectedCell
11976 * @return {Object} Object literal representation of a cell.
11978 getLastSelectedCell : function() {
11979 var tracker = this._aSelections;
11980 if(tracker && tracker.length > 0) {
11981 for(var i=tracker.length-1; i>-1; i--) {
11982 if(tracker[i].recordId && tracker[i].columnKey){
11990 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
11992 * @method highlightRow
11993 * @param row {HTMLElement | String} DOM element reference or ID string.
11995 highlightRow : function(row) {
11996 var elRow = this.getTrEl(row);
11999 // Make sure previous row is unhighlighted
12000 /* if(this._sLastHighlightedTrElId) {
12001 Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED);
12003 var oRecord = this.getRecord(elRow);
12004 Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED);
12005 //this._sLastHighlightedTrElId = elRow.id;
12006 this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
12007 YAHOO.log("Highlighted " + elRow, "info", this.toString());
12010 YAHOO.log("Could not highlight row " + row, "warn", this.toString());
12014 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
12016 * @method unhighlightRow
12017 * @param row {HTMLElement | String} DOM element reference or ID string.
12019 unhighlightRow : function(row) {
12020 var elRow = this.getTrEl(row);
12023 var oRecord = this.getRecord(elRow);
12024 Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED);
12025 this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
12026 YAHOO.log("Unhighlighted " + elRow, "info", this.toString());
12029 YAHOO.log("Could not unhighlight row " + row, "warn", this.toString());
12033 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
12035 * @method highlightCell
12036 * @param cell {HTMLElement | String} DOM element reference or ID string.
12038 highlightCell : function(cell) {
12039 var elCell = this.getTdEl(cell);
12042 // Make sure previous cell is unhighlighted
12043 if(this._elLastHighlightedTd) {
12044 this.unhighlightCell(this._elLastHighlightedTd);
12047 var oRecord = this.getRecord(elCell);
12048 var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
12049 Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED);
12050 this._elLastHighlightedTd = elCell;
12051 this.fireEvent("cellHighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
12052 YAHOO.log("Highlighted " + elCell, "info", this.toString());
12055 YAHOO.log("Could not highlight cell " + cell, "warn", this.toString());
12059 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
12061 * @method unhighlightCell
12062 * @param cell {HTMLElement | String} DOM element reference or ID string.
12064 unhighlightCell : function(cell) {
12065 var elCell = this.getTdEl(cell);
12068 var oRecord = this.getRecord(elCell);
12069 Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED);
12070 this._elLastHighlightedTd = null;
12071 this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
12072 YAHOO.log("Unhighlighted " + elCell, "info", this.toString());
12075 YAHOO.log("Could not unhighlight cell " + cell, "warn", this.toString());
12125 * Returns current CellEditor instance, or null.
12126 * @method getCellEditor
12127 * @return {YAHOO.widget.CellEditor} CellEditor instance.
12129 getCellEditor : function() {
12130 return this._oCellEditor;
12135 * Activates and shows CellEditor instance for the given cell while deactivating and
12136 * canceling previous CellEditor. It is baked into DataTable that only one CellEditor
12137 * can be active at any given time.
12139 * @method showCellEditor
12140 * @param elCell {HTMLElement | String} Cell to edit.
12142 showCellEditor : function(elCell, oRecord, oColumn) {
12143 // Get a particular CellEditor
12144 elCell = this.getTdEl(elCell);
12146 oColumn = this.getColumn(elCell);
12147 if(oColumn && oColumn.editor) {
12148 var oCellEditor = this._oCellEditor;
12149 // Clean up active CellEditor
12151 if(this._oCellEditor.cancel) {
12152 this._oCellEditor.cancel();
12154 else if(oCellEditor.isActive) {
12155 this.cancelCellEditor();
12159 if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) {
12161 oCellEditor = oColumn.editor;
12162 var ok = oCellEditor.attach(this, elCell);
12164 oCellEditor.move();
12165 ok = this.doBeforeShowCellEditor(oCellEditor);
12167 oCellEditor.show();
12168 this._oCellEditor = oCellEditor;
12172 // Backward compatibility
12174 if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
12175 oRecord = this.getRecord(elCell);
12177 if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
12178 oColumn = this.getColumn(elCell);
12180 if(oRecord && oColumn) {
12181 if(!this._oCellEditor || this._oCellEditor.container) {
12182 this._initCellEditorEl();
12185 // Update Editor values
12186 oCellEditor = this._oCellEditor;
12187 oCellEditor.cell = elCell;
12188 oCellEditor.record = oRecord;
12189 oCellEditor.column = oColumn;
12190 oCellEditor.validator = (oColumn.editorOptions &&
12191 lang.isFunction(oColumn.editorOptions.validator)) ?
12192 oColumn.editorOptions.validator : null;
12193 oCellEditor.value = oRecord.getData(oColumn.key);
12194 oCellEditor.defaultValue = null;
12197 var elContainer = oCellEditor.container;
12198 var x = Dom.getX(elCell);
12199 var y = Dom.getY(elCell);
12201 // SF doesn't get xy for cells in scrolling table
12202 // when tbody display is set to block
12203 if(isNaN(x) || isNaN(y)) {
12204 x = elCell.offsetLeft + // cell pos relative to table
12205 Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document
12206 this._elTbody.scrollLeft; // minus tbody scroll
12207 y = elCell.offsetTop + // cell pos relative to table
12208 Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document
12209 this._elTbody.scrollTop + // minus tbody scroll
12210 this._elThead.offsetHeight; // account for fixed THEAD cells
12213 elContainer.style.left = x + "px";
12214 elContainer.style.top = y + "px";
12216 // Hook to customize the UI
12217 this.doBeforeShowCellEditor(this._oCellEditor);
12219 //TODO: This is temporarily up here due so elements can be focused
12221 elContainer.style.display = "";
12224 Ev.addListener(elContainer, "keydown", function(e, oSelf) {
12225 // ESC hides Cell Editor
12226 if((e.keyCode == 27)) {
12227 oSelf.cancelCellEditor();
12228 oSelf.focusTbodyEl();
12231 oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
12235 // Render Editor markup
12237 if(lang.isString(oColumn.editor)) {
12238 switch(oColumn.editor) {
12240 fnEditor = DT.editCheckbox;
12243 fnEditor = DT.editDate;
12246 fnEditor = DT.editDropdown;
12249 fnEditor = DT.editRadio;
12252 fnEditor = DT.editTextarea;
12255 fnEditor = DT.editTextbox;
12261 else if(lang.isFunction(oColumn.editor)) {
12262 fnEditor = oColumn.editor;
12266 // Create DOM input elements
12267 fnEditor(this._oCellEditor, this);
12269 // Show Save/Cancel buttons
12270 if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
12271 this.showCellEditorBtns(elContainer);
12274 oCellEditor.isActive = true;
12276 //TODO: verify which args to pass
12277 this.fireEvent("editorShowEvent", {editor:oCellEditor});
12278 YAHOO.log("Cell Editor shown for " + elCell, "info", this.toString());
12292 * Backward compatibility.
12294 * @method _initCellEditorEl
12298 _initCellEditorEl : function() {
12299 // Attach Cell Editor container element as first child of body
12300 var elCellEditor = document.createElement("div");
12301 elCellEditor.id = this._sId + "-celleditor";
12302 elCellEditor.style.display = "none";
12303 elCellEditor.tabIndex = 0;
12304 Dom.addClass(elCellEditor, DT.CLASS_EDITOR);
12305 var elFirstChild = Dom.getFirstChild(document.body);
12307 elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild);
12310 elCellEditor = document.body.appendChild(elCellEditor);
12313 // Internal tracker of Cell Editor values
12314 var oCellEditor = {};
12315 oCellEditor.container = elCellEditor;
12316 oCellEditor.value = null;
12317 oCellEditor.isActive = false;
12318 this._oCellEditor = oCellEditor;
12322 * Overridable abstract method to customize CellEditor before showing.
12324 * @method doBeforeShowCellEditor
12325 * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance.
12326 * @return {Boolean} Return true to continue showing CellEditor.
12328 doBeforeShowCellEditor : function(oCellEditor) {
12333 * Saves active CellEditor input to Record and upates DOM UI.
12335 * @method saveCellEditor
12337 saveCellEditor : function() {
12338 if(this._oCellEditor) {
12339 if(this._oCellEditor.save) {
12340 this._oCellEditor.save();
12342 // Backward compatibility
12343 else if(this._oCellEditor.isActive) {
12344 var newData = this._oCellEditor.value;
12345 // Copy the data to pass to the event
12346 //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key));
12347 var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
12349 // Validate input data
12350 if(this._oCellEditor.validator) {
12351 newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor);
12352 if(newData === null ) {
12353 this.resetCellEditor();
12354 this.fireEvent("editorRevertEvent",
12355 {editor:this._oCellEditor, oldData:oldData, newData:newData});
12356 YAHOO.log("Could not save Cell Editor input due to invalid data " +
12357 lang.dump(newData), "warn", this.toString());
12361 // Update the Record
12362 this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
12364 this.formatCell(this._oCellEditor.cell.firstChild);
12367 this._oChainRender.add({
12368 method: function() {
12369 this.validateColumnWidths();
12373 this._oChainRender.run();
12374 // Clear out the Cell Editor
12375 this.resetCellEditor();
12377 this.fireEvent("editorSaveEvent",
12378 {editor:this._oCellEditor, oldData:oldData, newData:newData});
12379 YAHOO.log("Cell Editor input saved", "info", this.toString());
12385 * Cancels active CellEditor.
12387 * @method cancelCellEditor
12389 cancelCellEditor : function() {
12390 if(this._oCellEditor) {
12391 if(this._oCellEditor.cancel) {
12392 this._oCellEditor.cancel();
12394 // Backward compatibility
12395 else if(this._oCellEditor.isActive) {
12396 this.resetCellEditor();
12397 //TODO: preserve values for the event?
12398 this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
12399 YAHOO.log("Cell Editor input canceled", "info", this.toString());
12402 YAHOO.log("CellEditor input canceled", "info", this.toString());
12407 * Destroys active CellEditor instance and UI.
12409 * @method destroyCellEditor
12411 destroyCellEditor : function() {
12412 if(this._oCellEditor) {
12413 this._oCellEditor.destroy();
12414 this._oCellEditor = null;
12419 * Passes through showEvent of the active CellEditor.
12421 * @method _onEditorShowEvent
12422 * @param oArgs {Object} Custom Event args.
12425 _onEditorShowEvent : function(oArgs) {
12426 this.fireEvent("editorShowEvent", oArgs);
12430 * Passes through keydownEvent of the active CellEditor.
12431 * @param oArgs {Object} Custom Event args.
12433 * @method _onEditorKeydownEvent
12436 _onEditorKeydownEvent : function(oArgs) {
12437 this.fireEvent("editorKeydownEvent", oArgs);
12441 * Passes through revertEvent of the active CellEditor.
12443 * @method _onEditorRevertEvent
12444 * @param oArgs {Object} Custom Event args.
12447 _onEditorRevertEvent : function(oArgs) {
12448 this.fireEvent("editorRevertEvent", oArgs);
12452 * Passes through saveEvent of the active CellEditor.
12454 * @method _onEditorSaveEvent
12455 * @param oArgs {Object} Custom Event args.
12458 _onEditorSaveEvent : function(oArgs) {
12459 this.fireEvent("editorSaveEvent", oArgs);
12463 * Passes through cancelEvent of the active CellEditor.
12465 * @method _onEditorCancelEvent
12466 * @param oArgs {Object} Custom Event args.
12469 _onEditorCancelEvent : function(oArgs) {
12470 this.fireEvent("editorCancelEvent", oArgs);
12474 * Passes through blurEvent of the active CellEditor.
12476 * @method _onEditorBlurEvent
12477 * @param oArgs {Object} Custom Event args.
12480 _onEditorBlurEvent : function(oArgs) {
12481 this.fireEvent("editorBlurEvent", oArgs);
12485 * Passes through blockEvent of the active CellEditor.
12487 * @method _onEditorBlockEvent
12488 * @param oArgs {Object} Custom Event args.
12491 _onEditorBlockEvent : function(oArgs) {
12492 this.fireEvent("editorBlockEvent", oArgs);
12496 * Passes through unblockEvent of the active CellEditor.
12498 * @method _onEditorUnblockEvent
12499 * @param oArgs {Object} Custom Event args.
12502 _onEditorUnblockEvent : function(oArgs) {
12503 this.fireEvent("editorUnblockEvent", oArgs);
12507 * Public handler of the editorBlurEvent. By default, saves on blur if
12508 * disableBtns is true, otherwise cancels on blur.
12510 * @method onEditorBlurEvent
12511 * @param oArgs {Object} Custom Event args.
12513 onEditorBlurEvent : function(oArgs) {
12514 if(oArgs.editor.disableBtns) {
12516 if(oArgs.editor.save) { // Backward incompatible
12517 oArgs.editor.save();
12520 else if(oArgs.editor.cancel) { // Backward incompatible
12522 oArgs.editor.cancel();
12527 * Public handler of the editorBlockEvent. By default, disables DataTable UI.
12529 * @method onEditorBlockEvent
12530 * @param oArgs {Object} Custom Event args.
12532 onEditorBlockEvent : function(oArgs) {
12537 * Public handler of the editorUnblockEvent. By default, undisables DataTable UI.
12539 * @method onEditorUnblockEvent
12540 * @param oArgs {Object} Custom Event args.
12542 onEditorUnblockEvent : function(oArgs) {
12583 // ABSTRACT METHODS
12586 * Overridable method gives implementers a hook to access data before
12587 * it gets added to RecordSet and rendered to the TBODY.
12589 * @method doBeforeLoadData
12590 * @param sRequest {String} Original request.
12591 * @param oResponse {Object} Response object.
12592 * @param oPayload {MIXED} additional arguments
12593 * @return {Boolean} Return true to continue loading data into RecordSet and
12594 * updating DataTable with new Records, false to cancel.
12596 doBeforeLoadData : function(sRequest, oResponse, oPayload) {
12662 /////////////////////////////////////////////////////////////////////////////
12664 // Public Custom Event Handlers
12666 /////////////////////////////////////////////////////////////////////////////
12669 * Overridable custom event handler to sort Column.
12671 * @method onEventSortColumn
12672 * @param oArgs.event {HTMLEvent} Event object.
12673 * @param oArgs.target {HTMLElement} Target element.
12675 onEventSortColumn : function(oArgs) {
12676 //TODO: support form elements in sortable columns
12677 var evt = oArgs.event;
12678 var target = oArgs.target;
12680 var el = this.getThEl(target) || this.getTdEl(target);
12682 var oColumn = this.getColumn(el);
12683 if(oColumn.sortable) {
12685 this.sortColumn(oColumn);
12689 YAHOO.log("Could not find Column for " + target, "warn", this.toString());
12694 * Overridable custom event handler to select Column.
12696 * @method onEventSelectColumn
12697 * @param oArgs.event {HTMLEvent} Event object.
12698 * @param oArgs.target {HTMLElement} Target element.
12700 onEventSelectColumn : function(oArgs) {
12701 this.selectColumn(oArgs.target);
12705 * Overridable custom event handler to highlight Column. Accounts for spurious
12706 * caused-by-child events.
12708 * @method onEventHighlightColumn
12709 * @param oArgs.event {HTMLEvent} Event object.
12710 * @param oArgs.target {HTMLElement} Target element.
12712 onEventHighlightColumn : function(oArgs) {
12713 //TODO: filter for all spurious events at a lower level
12714 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12715 this.highlightColumn(oArgs.target);
12720 * Overridable custom event handler to unhighlight Column. Accounts for spurious
12721 * caused-by-child events.
12723 * @method onEventUnhighlightColumn
12724 * @param oArgs.event {HTMLEvent} Event object.
12725 * @param oArgs.target {HTMLElement} Target element.
12727 onEventUnhighlightColumn : function(oArgs) {
12728 //TODO: filter for all spurious events at a lower level
12729 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12730 this.unhighlightColumn(oArgs.target);
12735 * Overridable custom event handler to manage selection according to desktop paradigm.
12737 * @method onEventSelectRow
12738 * @param oArgs.event {HTMLEvent} Event object.
12739 * @param oArgs.target {HTMLElement} Target element.
12741 onEventSelectRow : function(oArgs) {
12742 var sMode = this.get("selectionMode");
12743 if(sMode == "single") {
12744 this._handleSingleSelectionByMouse(oArgs);
12747 this._handleStandardSelectionByMouse(oArgs);
12752 * Overridable custom event handler to select cell.
12754 * @method onEventSelectCell
12755 * @param oArgs.event {HTMLEvent} Event object.
12756 * @param oArgs.target {HTMLElement} Target element.
12758 onEventSelectCell : function(oArgs) {
12759 var sMode = this.get("selectionMode");
12760 if(sMode == "cellblock") {
12761 this._handleCellBlockSelectionByMouse(oArgs);
12763 else if(sMode == "cellrange") {
12764 this._handleCellRangeSelectionByMouse(oArgs);
12767 this._handleSingleCellSelectionByMouse(oArgs);
12772 * Overridable custom event handler to highlight row. Accounts for spurious
12773 * caused-by-child events.
12775 * @method onEventHighlightRow
12776 * @param oArgs.event {HTMLEvent} Event object.
12777 * @param oArgs.target {HTMLElement} Target element.
12779 onEventHighlightRow : function(oArgs) {
12780 //TODO: filter for all spurious events at a lower level
12781 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12782 this.highlightRow(oArgs.target);
12787 * Overridable custom event handler to unhighlight row. Accounts for spurious
12788 * caused-by-child events.
12790 * @method onEventUnhighlightRow
12791 * @param oArgs.event {HTMLEvent} Event object.
12792 * @param oArgs.target {HTMLElement} Target element.
12794 onEventUnhighlightRow : function(oArgs) {
12795 //TODO: filter for all spurious events at a lower level
12796 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12797 this.unhighlightRow(oArgs.target);
12802 * Overridable custom event handler to highlight cell. Accounts for spurious
12803 * caused-by-child events.
12805 * @method onEventHighlightCell
12806 * @param oArgs.event {HTMLEvent} Event object.
12807 * @param oArgs.target {HTMLElement} Target element.
12809 onEventHighlightCell : function(oArgs) {
12810 //TODO: filter for all spurious events at a lower level
12811 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12812 this.highlightCell(oArgs.target);
12817 * Overridable custom event handler to unhighlight cell. Accounts for spurious
12818 * caused-by-child events.
12820 * @method onEventUnhighlightCell
12821 * @param oArgs.event {HTMLEvent} Event object.
12822 * @param oArgs.target {HTMLElement} Target element.
12824 onEventUnhighlightCell : function(oArgs) {
12825 //TODO: filter for all spurious events at a lower level
12826 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12827 this.unhighlightCell(oArgs.target);
12832 * Overridable custom event handler to format cell.
12834 * @method onEventFormatCell
12835 * @param oArgs.event {HTMLEvent} Event object.
12836 * @param oArgs.target {HTMLElement} Target element.
12838 onEventFormatCell : function(oArgs) {
12839 var target = oArgs.target;
12841 var elCell = this.getTdEl(target);
12843 var oColumn = this.getColumn(elCell.cellIndex);
12844 this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn);
12847 YAHOO.log("Could not format cell " + target, "warn", this.toString());
12852 * Overridable custom event handler to edit cell.
12854 * @method onEventShowCellEditor
12855 * @param oArgs.event {HTMLEvent} Event object.
12856 * @param oArgs.target {HTMLElement} Target element.
12858 onEventShowCellEditor : function(oArgs) {
12859 this.showCellEditor(oArgs.target);
12863 * Overridable custom event handler to save active CellEditor input.
12865 * @method onEventSaveCellEditor
12867 onEventSaveCellEditor : function(oArgs) {
12868 if(this._oCellEditor) {
12869 if(this._oCellEditor.save) {
12870 this._oCellEditor.save();
12872 // Backward compatibility
12874 this.saveCellEditor();
12880 * Overridable custom event handler to cancel active CellEditor.
12882 * @method onEventCancelCellEditor
12884 onEventCancelCellEditor : function(oArgs) {
12885 if(this._oCellEditor) {
12886 if(this._oCellEditor.cancel) {
12887 this._oCellEditor.cancel();
12889 // Backward compatibility
12891 this.cancelCellEditor();
12897 * Callback function receives data from DataSource and populates an entire
12898 * DataTable with Records and TR elements, clearing previous Records, if any.
12900 * @method onDataReturnInitializeTable
12901 * @param sRequest {String} Original request.
12902 * @param oResponse {Object} Response object.
12903 * @param oPayload {MIXED} (optional) Additional argument(s)
12905 onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) {
12906 if((this instanceof DT) && this._sId) {
12907 this.initializeTable();
12909 this.onDataReturnSetRows(sRequest,oResponse,oPayload);
12914 * Callback function receives reponse from DataSource, replaces all existing
12915 * Records in RecordSet, updates TR elements with new data, and updates state
12916 * UI for pagination and sorting from payload data, if necessary.
12918 * @method onDataReturnReplaceRows
12919 * @param oRequest {MIXED} Original generated request.
12920 * @param oResponse {Object} Response object.
12921 * @param oPayload {MIXED} (optional) Additional argument(s)
12923 onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) {
12924 if((this instanceof DT) && this._sId) {
12925 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
12927 // Pass data through abstract method for any transformations
12928 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload),
12929 pag = this.get('paginator'),
12933 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12935 this._oRecordSet.reset();
12937 if (this.get('dynamicData')) {
12938 if (oPayload && oPayload.pagination &&
12939 lang.isNumber(oPayload.pagination.recordOffset)) {
12940 index = oPayload.pagination.recordOffset;
12942 index = pag.getStartIndex();
12946 this._oRecordSet.setRecords(oResponse.results, index | 0);
12949 this._handleDataReturnPayload(oRequest, oResponse, oPayload);
12955 else if(ok && oResponse.error) {
12956 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12962 * Callback function receives data from DataSource and appends to an existing
12963 * DataTable new Records and, if applicable, creates or updates
12964 * corresponding TR elements.
12966 * @method onDataReturnAppendRows
12967 * @param sRequest {String} Original request.
12968 * @param oResponse {Object} Response object.
12969 * @param oPayload {MIXED} (optional) Additional argument(s)
12971 onDataReturnAppendRows : function(sRequest, oResponse, oPayload) {
12972 if((this instanceof DT) && this._sId) {
12973 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12975 // Pass data through abstract method for any transformations
12976 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12978 // Data ok to append
12979 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12981 this.addRows(oResponse.results);
12984 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12987 else if(ok && oResponse.error) {
12988 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12994 * Callback function receives data from DataSource and inserts new records
12995 * starting at the index specified in oPayload.insertIndex. The value for
12996 * oPayload.insertIndex can be populated when sending the request to the DataSource,
12997 * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime.
12998 * If applicable, creates or updates corresponding TR elements.
13000 * @method onDataReturnInsertRows
13001 * @param sRequest {String} Original request.
13002 * @param oResponse {Object} Response object.
13003 * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex.
13005 onDataReturnInsertRows : function(sRequest, oResponse, oPayload) {
13006 if((this instanceof DT) && this._sId) {
13007 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
13009 // Pass data through abstract method for any transformations
13010 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
13012 // Data ok to append
13013 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
13015 this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0));
13018 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
13021 else if(ok && oResponse.error) {
13022 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
13028 * Callback function receives data from DataSource and incrementally updates Records
13029 * starting at the index specified in oPayload.updateIndex. The value for
13030 * oPayload.updateIndex can be populated when sending the request to the DataSource,
13031 * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime.
13032 * If applicable, creates or updates corresponding TR elements.
13034 * @method onDataReturnUpdateRows
13035 * @param sRequest {String} Original request.
13036 * @param oResponse {Object} Response object.
13037 * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex.
13039 onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) {
13040 if((this instanceof DT) && this._sId) {
13041 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
13043 // Pass data through abstract method for any transformations
13044 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
13046 // Data ok to append
13047 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
13049 this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results);
13052 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
13055 else if(ok && oResponse.error) {
13056 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
13062 * Callback function receives reponse from DataSource and populates the
13063 * RecordSet with the results.
13065 * @method onDataReturnSetRows
13066 * @param oRequest {MIXED} Original generated request.
13067 * @param oResponse {Object} Response object.
13068 * @param oPayload {MIXED} (optional) Additional argument(s)
13070 onDataReturnSetRows : function(oRequest, oResponse, oPayload) {
13071 if((this instanceof DT) && this._sId) {
13072 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
13074 // Pass data through abstract method for any transformations
13075 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload),
13076 pag = this.get('paginator'),
13080 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
13082 if (this.get('dynamicData')) {
13083 if (oPayload && oPayload.pagination &&
13084 lang.isNumber(oPayload.pagination.recordOffset)) {
13085 index = oPayload.pagination.recordOffset;
13087 index = pag.getStartIndex();
13090 this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default
13093 this._oRecordSet.setRecords(oResponse.results, index | 0);
13096 this._handleDataReturnPayload(oRequest, oResponse, oPayload);
13102 else if(ok && oResponse.error) {
13103 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
13107 YAHOO.log("Instance destroyed before data returned.","info",this.toString());
13112 * Hook to update oPayload before consumption.
13114 * @method handleDataReturnPayload
13115 * @param oRequest {MIXED} Original generated request.
13116 * @param oResponse {Object} Response object.
13117 * @param oPayload {MIXED} State values.
13118 * @return oPayload {MIXED} State values.
13120 handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
13125 * Updates the DataTable with state data sent in an onDataReturn* payload.
13127 * @method handleDataReturnPayload
13128 * @param oRequest {MIXED} Original generated request.
13129 * @param oResponse {Object} Response object.
13130 * @param oPayload {MIXED} State values
13132 _handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
13133 oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload);
13135 // Update pagination
13136 var oPaginator = this.get('paginator');
13138 // Update totalRecords
13139 if(this.get("dynamicData")) {
13140 if (widget.Paginator.isNumeric(oPayload.totalRecords)) {
13141 oPaginator.set('totalRecords',oPayload.totalRecords);
13145 oPaginator.set('totalRecords',this._oRecordSet.getLength());
13147 // Update other paginator values
13148 if (lang.isObject(oPayload.pagination)) {
13149 oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage);
13150 oPaginator.set('recordOffset',oPayload.pagination.recordOffset);
13155 if (oPayload.sortedBy) {
13156 // Set the sorting values in preparation for refresh
13157 this.set('sortedBy', oPayload.sortedBy);
13159 // Backwards compatibility for sorting
13160 else if (oPayload.sorting) {
13161 // Set the sorting values in preparation for refresh
13162 this.set('sortedBy', oPayload.sorting);
13199 /////////////////////////////////////////////////////////////////////////////
13203 /////////////////////////////////////////////////////////////////////////////
13206 * Fired when the DataTable's rows are rendered from an initialized state.
13212 * Fired before the DataTable's DOM is rendered or modified.
13214 * @event beforeRenderEvent
13218 * Fired when the DataTable's DOM is rendered or modified.
13220 * @event renderEvent
13224 * Fired when the DataTable's post-render routine is complete, including
13225 * Column width validations.
13227 * @event postRenderEvent
13231 * Fired when the DataTable is disabled.
13233 * @event disableEvent
13237 * Fired when the DataTable is undisabled.
13239 * @event undisableEvent
13243 * Fired when data is returned from DataSource but before it is consumed by
13246 * @event dataReturnEvent
13247 * @param oArgs.request {String} Original request.
13248 * @param oArgs.response {Object} Response object.
13252 * Fired when the DataTable has a focus event.
13254 * @event tableFocusEvent
13258 * Fired when the DataTable THEAD element has a focus event.
13260 * @event theadFocusEvent
13264 * Fired when the DataTable TBODY element has a focus event.
13266 * @event tbodyFocusEvent
13270 * Fired when the DataTable has a blur event.
13272 * @event tableBlurEvent
13275 /*TODO implement theadBlurEvent
13276 * Fired when the DataTable THEAD element has a blur event.
13278 * @event theadBlurEvent
13281 /*TODO: implement tbodyBlurEvent
13282 * Fired when the DataTable TBODY element has a blur event.
13284 * @event tbodyBlurEvent
13288 * Fired when the DataTable has a key event.
13290 * @event tableKeyEvent
13291 * @param oArgs.event {HTMLEvent} The event object.
13292 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13296 * Fired when the DataTable THEAD element has a key event.
13298 * @event theadKeyEvent
13299 * @param oArgs.event {HTMLEvent} The event object.
13300 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13304 * Fired when the DataTable TBODY element has a key event.
13306 * @event tbodyKeyEvent
13307 * @param oArgs.event {HTMLEvent} The event object.
13308 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13312 * Fired when the DataTable has a mouseover.
13314 * @event tableMouseoverEvent
13315 * @param oArgs.event {HTMLEvent} The event object.
13316 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13321 * Fired when the DataTable has a mouseout.
13323 * @event tableMouseoutEvent
13324 * @param oArgs.event {HTMLEvent} The event object.
13325 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13330 * Fired when the DataTable has a mousedown.
13332 * @event tableMousedownEvent
13333 * @param oArgs.event {HTMLEvent} The event object.
13334 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13339 * Fired when the DataTable has a mouseup.
13341 * @event tableMouseupEvent
13342 * @param oArgs.event {HTMLEvent} The event object.
13343 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13348 * Fired when the DataTable has a click.
13350 * @event tableClickEvent
13351 * @param oArgs.event {HTMLEvent} The event object.
13352 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13357 * Fired when the DataTable has a dblclick.
13359 * @event tableDblclickEvent
13360 * @param oArgs.event {HTMLEvent} The event object.
13361 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13366 * Fired when a message is shown in the DataTable's message element.
13368 * @event tableMsgShowEvent
13369 * @param oArgs.html {String} The HTML displayed.
13370 * @param oArgs.className {String} The className assigned.
13375 * Fired when the DataTable's message element is hidden.
13377 * @event tableMsgHideEvent
13381 * Fired when a THEAD row has a mouseover.
13383 * @event theadRowMouseoverEvent
13384 * @param oArgs.event {HTMLEvent} The event object.
13385 * @param oArgs.target {HTMLElement} The TR element.
13389 * Fired when a THEAD row has a mouseout.
13391 * @event theadRowMouseoutEvent
13392 * @param oArgs.event {HTMLEvent} The event object.
13393 * @param oArgs.target {HTMLElement} The TR element.
13397 * Fired when a THEAD row has a mousedown.
13399 * @event theadRowMousedownEvent
13400 * @param oArgs.event {HTMLEvent} The event object.
13401 * @param oArgs.target {HTMLElement} The TR element.
13405 * Fired when a THEAD row has a mouseup.
13407 * @event theadRowMouseupEvent
13408 * @param oArgs.event {HTMLEvent} The event object.
13409 * @param oArgs.target {HTMLElement} The TR element.
13413 * Fired when a THEAD row has a click.
13415 * @event theadRowClickEvent
13416 * @param oArgs.event {HTMLEvent} The event object.
13417 * @param oArgs.target {HTMLElement} The TR element.
13421 * Fired when a THEAD row has a dblclick.
13423 * @event theadRowDblclickEvent
13424 * @param oArgs.event {HTMLEvent} The event object.
13425 * @param oArgs.target {HTMLElement} The TR element.
13429 * Fired when a THEAD cell has a mouseover.
13431 * @event theadCellMouseoverEvent
13432 * @param oArgs.event {HTMLEvent} The event object.
13433 * @param oArgs.target {HTMLElement} The TH element.
13438 * Fired when a THEAD cell has a mouseout.
13440 * @event theadCellMouseoutEvent
13441 * @param oArgs.event {HTMLEvent} The event object.
13442 * @param oArgs.target {HTMLElement} The TH element.
13447 * Fired when a THEAD cell has a mousedown.
13449 * @event theadCellMousedownEvent
13450 * @param oArgs.event {HTMLEvent} The event object.
13451 * @param oArgs.target {HTMLElement} The TH element.
13455 * Fired when a THEAD cell has a mouseup.
13457 * @event theadCellMouseupEvent
13458 * @param oArgs.event {HTMLEvent} The event object.
13459 * @param oArgs.target {HTMLElement} The TH element.
13463 * Fired when a THEAD cell has a click.
13465 * @event theadCellClickEvent
13466 * @param oArgs.event {HTMLEvent} The event object.
13467 * @param oArgs.target {HTMLElement} The TH element.
13471 * Fired when a THEAD cell has a dblclick.
13473 * @event theadCellDblclickEvent
13474 * @param oArgs.event {HTMLEvent} The event object.
13475 * @param oArgs.target {HTMLElement} The TH element.
13479 * Fired when a THEAD label has a mouseover.
13481 * @event theadLabelMouseoverEvent
13482 * @param oArgs.event {HTMLEvent} The event object.
13483 * @param oArgs.target {HTMLElement} The SPAN element.
13488 * Fired when a THEAD label has a mouseout.
13490 * @event theadLabelMouseoutEvent
13491 * @param oArgs.event {HTMLEvent} The event object.
13492 * @param oArgs.target {HTMLElement} The SPAN element.
13497 * Fired when a THEAD label has a mousedown.
13499 * @event theadLabelMousedownEvent
13500 * @param oArgs.event {HTMLEvent} The event object.
13501 * @param oArgs.target {HTMLElement} The SPAN element.
13505 * Fired when a THEAD label has a mouseup.
13507 * @event theadLabelMouseupEvent
13508 * @param oArgs.event {HTMLEvent} The event object.
13509 * @param oArgs.target {HTMLElement} The SPAN element.
13513 * Fired when a THEAD label has a click.
13515 * @event theadLabelClickEvent
13516 * @param oArgs.event {HTMLEvent} The event object.
13517 * @param oArgs.target {HTMLElement} The SPAN element.
13521 * Fired when a THEAD label has a dblclick.
13523 * @event theadLabelDblclickEvent
13524 * @param oArgs.event {HTMLEvent} The event object.
13525 * @param oArgs.target {HTMLElement} The SPAN element.
13529 * Fired when a column is sorted.
13531 * @event columnSortEvent
13532 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13533 * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC
13534 * or YAHOO.widget.DataTable.CLASS_DESC.
13538 * Fired when a column width is set.
13540 * @event columnSetWidthEvent
13541 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13542 * @param oArgs.width {Number} The width in pixels.
13546 * Fired when a column width is unset.
13548 * @event columnUnsetWidthEvent
13549 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13553 * Fired when a column is drag-resized.
13555 * @event columnResizeEvent
13556 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13557 * @param oArgs.target {HTMLElement} The TH element.
13558 * @param oArgs.width {Number} Width in pixels.
13562 * Fired when a Column is moved to a new index.
13564 * @event columnReorderEvent
13565 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13566 * @param oArgs.oldIndex {Number} The previous index position.
13570 * Fired when a column is hidden.
13572 * @event columnHideEvent
13573 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13577 * Fired when a column is shown.
13579 * @event columnShowEvent
13580 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13584 * Fired when a column is selected.
13586 * @event columnSelectEvent
13587 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13591 * Fired when a column is unselected.
13593 * @event columnUnselectEvent
13594 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13597 * Fired when a column is removed.
13599 * @event columnRemoveEvent
13600 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13604 * Fired when a column is inserted.
13606 * @event columnInsertEvent
13607 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13608 * @param oArgs.index {Number} The index position.
13612 * Fired when a column is highlighted.
13614 * @event columnHighlightEvent
13615 * @param oArgs.column {YAHOO.widget.Column} The highlighted Column.
13619 * Fired when a column is unhighlighted.
13621 * @event columnUnhighlightEvent
13622 * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column.
13627 * Fired when a row has a mouseover.
13629 * @event rowMouseoverEvent
13630 * @param oArgs.event {HTMLEvent} The event object.
13631 * @param oArgs.target {HTMLElement} The TR element.
13635 * Fired when a row has a mouseout.
13637 * @event rowMouseoutEvent
13638 * @param oArgs.event {HTMLEvent} The event object.
13639 * @param oArgs.target {HTMLElement} The TR element.
13643 * Fired when a row has a mousedown.
13645 * @event rowMousedownEvent
13646 * @param oArgs.event {HTMLEvent} The event object.
13647 * @param oArgs.target {HTMLElement} The TR element.
13651 * Fired when a row has a mouseup.
13653 * @event rowMouseupEvent
13654 * @param oArgs.event {HTMLEvent} The event object.
13655 * @param oArgs.target {HTMLElement} The TR element.
13659 * Fired when a row has a click.
13661 * @event rowClickEvent
13662 * @param oArgs.event {HTMLEvent} The event object.
13663 * @param oArgs.target {HTMLElement} The TR element.
13667 * Fired when a row has a dblclick.
13669 * @event rowDblclickEvent
13670 * @param oArgs.event {HTMLEvent} The event object.
13671 * @param oArgs.target {HTMLElement} The TR element.
13675 * Fired when a row is added.
13677 * @event rowAddEvent
13678 * @param oArgs.record {YAHOO.widget.Record} The added Record.
13682 * Fired when rows are added.
13684 * @event rowsAddEvent
13685 * @param oArgs.record {YAHOO.widget.Record[]} The added Records.
13689 * Fired when a row is updated.
13691 * @event rowUpdateEvent
13692 * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13693 * @param oArgs.oldData {Object} Object literal of the old data.
13697 * Fired when a row is deleted.
13699 * @event rowDeleteEvent
13700 * @param oArgs.oldData {Object} Object literal of the deleted data.
13701 * @param oArgs.recordIndex {Number} Index of the deleted Record.
13702 * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page.
13706 * Fired when rows are deleted.
13708 * @event rowsDeleteEvent
13709 * @param oArgs.oldData {Object[]} Array of object literals of the deleted data.
13710 * @param oArgs.recordIndex {Number} Index of the first deleted Record.
13711 * @param oArgs.count {Number} Number of deleted Records.
13715 * Fired when a row is selected.
13717 * @event rowSelectEvent
13718 * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
13719 * @param oArgs.record {YAHOO.widget.Record} The selected Record.
13723 * Fired when a row is unselected.
13725 * @event rowUnselectEvent
13726 * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
13727 * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
13731 * Fired when all row selections are cleared.
13733 * @event unselectAllRowsEvent
13737 * Fired when a row is highlighted.
13739 * @event rowHighlightEvent
13740 * @param oArgs.el {HTMLElement} The highlighted TR element.
13741 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13745 * Fired when a row is unhighlighted.
13747 * @event rowUnhighlightEvent
13748 * @param oArgs.el {HTMLElement} The highlighted TR element.
13749 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13753 * Fired when a cell is updated.
13755 * @event cellUpdateEvent
13756 * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13757 * @param oArgs.column {YAHOO.widget.Column} The updated Column.
13758 * @param oArgs.oldData {Object} Original data value of the updated cell.
13762 * Fired when a cell has a mouseover.
13764 * @event cellMouseoverEvent
13765 * @param oArgs.event {HTMLEvent} The event object.
13766 * @param oArgs.target {HTMLElement} The TD element.
13770 * Fired when a cell has a mouseout.
13772 * @event cellMouseoutEvent
13773 * @param oArgs.event {HTMLEvent} The event object.
13774 * @param oArgs.target {HTMLElement} The TD element.
13778 * Fired when a cell has a mousedown.
13780 * @event cellMousedownEvent
13781 * @param oArgs.event {HTMLEvent} The event object.
13782 * @param oArgs.target {HTMLElement} The TD element.
13786 * Fired when a cell has a mouseup.
13788 * @event cellMouseupEvent
13789 * @param oArgs.event {HTMLEvent} The event object.
13790 * @param oArgs.target {HTMLElement} The TD element.
13794 * Fired when a cell has a click.
13796 * @event cellClickEvent
13797 * @param oArgs.event {HTMLEvent} The event object.
13798 * @param oArgs.target {HTMLElement} The TD element.
13802 * Fired when a cell has a dblclick.
13804 * @event cellDblclickEvent
13805 * @param oArgs.event {HTMLEvent} The event object.
13806 * @param oArgs.target {HTMLElement} The TD element.
13810 * Fired when a cell is formatted.
13812 * @event cellFormatEvent
13813 * @param oArgs.el {HTMLElement} The formatted TD element.
13814 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13815 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13816 * @param oArgs.key {String} (deprecated) The key of the formatted cell.
13820 * Fired when a cell is selected.
13822 * @event cellSelectEvent
13823 * @param oArgs.el {HTMLElement} The selected TD element.
13824 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13825 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13826 * @param oArgs.key {String} (deprecated) The key of the selected cell.
13830 * Fired when a cell is unselected.
13832 * @event cellUnselectEvent
13833 * @param oArgs.el {HTMLElement} The unselected TD element.
13834 * @param oArgs.record {YAHOO.widget.Record} The associated Record.
13835 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13836 * @param oArgs.key {String} (deprecated) The key of the unselected cell.
13841 * Fired when a cell is highlighted.
13843 * @event cellHighlightEvent
13844 * @param oArgs.el {HTMLElement} The highlighted TD element.
13845 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13846 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13847 * @param oArgs.key {String} (deprecated) The key of the highlighted cell.
13852 * Fired when a cell is unhighlighted.
13854 * @event cellUnhighlightEvent
13855 * @param oArgs.el {HTMLElement} The unhighlighted TD element.
13856 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13857 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13858 * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell.
13863 * Fired when all cell selections are cleared.
13865 * @event unselectAllCellsEvent
13869 * Fired when a CellEditor is shown.
13871 * @event editorShowEvent
13872 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13876 * Fired when a CellEditor has a keydown.
13878 * @event editorKeydownEvent
13879 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13880 * @param oArgs.event {HTMLEvent} The event object.
13884 * Fired when a CellEditor input is reverted.
13886 * @event editorRevertEvent
13887 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13888 * @param oArgs.newData {Object} New data value from form input field.
13889 * @param oArgs.oldData {Object} Old data value.
13893 * Fired when a CellEditor input is saved.
13895 * @event editorSaveEvent
13896 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13897 * @param oArgs.newData {Object} New data value from form input field.
13898 * @param oArgs.oldData {Object} Old data value.
13902 * Fired when a CellEditor input is canceled.
13904 * @event editorCancelEvent
13905 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13909 * Fired when a CellEditor has a blur event.
13911 * @event editorBlurEvent
13912 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13916 * Fired when a CellEditor is blocked.
13918 * @event editorBlockEvent
13919 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13923 * Fired when a CellEditor is unblocked.
13925 * @event editorUnblockEvent
13926 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13934 * Fired when a link is clicked.
13936 * @event linkClickEvent
13937 * @param oArgs.event {HTMLEvent} The event object.
13938 * @param oArgs.target {HTMLElement} The A element.
13942 * Fired when a BUTTON element or INPUT element of type "button", "image",
13943 * "submit", "reset" is clicked.
13945 * @event buttonClickEvent
13946 * @param oArgs.event {HTMLEvent} The event object.
13947 * @param oArgs.target {HTMLElement} The BUTTON element.
13951 * Fired when a CHECKBOX element is clicked.
13953 * @event checkboxClickEvent
13954 * @param oArgs.event {HTMLEvent} The event object.
13955 * @param oArgs.target {HTMLElement} The CHECKBOX element.
13959 * Fired when a SELECT element is changed.
13961 * @event dropdownChangeEvent
13962 * @param oArgs.event {HTMLEvent} The event object.
13963 * @param oArgs.target {HTMLElement} The SELECT element.
13967 * Fired when a RADIO element is clicked.
13969 * @event radioClickEvent
13970 * @param oArgs.event {HTMLEvent} The event object.
13971 * @param oArgs.target {HTMLElement} The RADIO element.
13999 /////////////////////////////////////////////////////////////////////////////
14003 /////////////////////////////////////////////////////////////////////////////
14006 * @method showCellEditorBtns
14007 * @deprecated Use CellEditor.renderBtns()
14009 showCellEditorBtns : function(elContainer) {
14011 var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
14012 Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON);
14015 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
14016 Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT);
14017 elSaveBtn.innerHTML = "OK";
14018 Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) {
14019 oSelf.onEventSaveCellEditor(oArgs, oSelf);
14020 oSelf.focusTbodyEl();
14024 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
14025 elCancelBtn.innerHTML = "Cancel";
14026 Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) {
14027 oSelf.onEventCancelCellEditor(oArgs, oSelf);
14028 oSelf.focusTbodyEl();
14031 YAHOO.log("The method showCellEditorBtns() has been deprecated." +
14032 " Please use the CellEditor class.", "warn", this.toString());
14036 * @method resetCellEditor
14037 * @deprecated Use destroyCellEditor
14039 resetCellEditor : function() {
14040 var elContainer = this._oCellEditor.container;
14041 elContainer.style.display = "none";
14042 Ev.purgeElement(elContainer, true);
14043 elContainer.innerHTML = "";
14044 this._oCellEditor.value = null;
14045 this._oCellEditor.isActive = false;
14047 YAHOO.log("The method resetCellEditor() has been deprecated." +
14048 " Please use the CellEditor class.", "warn", this.toString());
14052 * @event editorUpdateEvent
14053 * @deprecated Use CellEditor class.
14058 * @deprecated Use getTbodyEl().
14060 getBody : function() {
14061 // Backward compatibility
14062 YAHOO.log("The method getBody() has been deprecated" +
14063 " in favor of getTbodyEl()", "warn", this.toString());
14064 return this.getTbodyEl();
14069 * @deprecated Use getTdEl().
14071 getCell : function(index) {
14072 // Backward compatibility
14073 YAHOO.log("The method getCell() has been deprecated" +
14074 " in favor of getTdEl()", "warn", this.toString());
14075 return this.getTdEl(index);
14080 * @deprecated Use getTrEl().
14082 getRow : function(index) {
14083 // Backward compatibility
14084 YAHOO.log("The method getRow() has been deprecated" +
14085 " in favor of getTrEl()", "warn", this.toString());
14086 return this.getTrEl(index);
14090 * @method refreshView
14091 * @deprecated Use render.
14093 refreshView : function() {
14094 // Backward compatibility
14095 YAHOO.log("The method refreshView() has been deprecated" +
14096 " in favor of render()", "warn", this.toString());
14102 * @deprecated Use selectRow.
14104 select : function(els) {
14105 // Backward compatibility
14106 YAHOO.log("The method select() has been deprecated" +
14107 " in favor of selectRow()", "warn", this.toString());
14108 if(!lang.isArray(els)) {
14111 for(var i=0; i<els.length; i++) {
14112 this.selectRow(els[i]);
14117 * @method onEventEditCell
14118 * @deprecated Use onEventShowCellEditor.
14120 onEventEditCell : function(oArgs) {
14121 // Backward compatibility
14122 YAHOO.log("The method onEventEditCell() has been deprecated" +
14123 " in favor of onEventShowCellEditor()", "warn", this.toString());
14124 this.onEventShowCellEditor(oArgs);
14128 * @method _syncColWidths
14129 * @deprecated Use validateColumnWidths.
14131 _syncColWidths : function() {
14132 // Backward compatibility
14133 YAHOO.log("The method _syncColWidths() has been deprecated" +
14134 " in favor of validateColumnWidths()", "warn", this.toString());
14135 this.validateColumnWidths();
14139 * @event headerRowMouseoverEvent
14140 * @deprecated Use theadRowMouseoverEvent.
14144 * @event headerRowMouseoutEvent
14145 * @deprecated Use theadRowMouseoutEvent.
14149 * @event headerRowMousedownEvent
14150 * @deprecated Use theadRowMousedownEvent.
14154 * @event headerRowClickEvent
14155 * @deprecated Use theadRowClickEvent.
14159 * @event headerRowDblclickEvent
14160 * @deprecated Use theadRowDblclickEvent.
14164 * @event headerCellMouseoverEvent
14165 * @deprecated Use theadCellMouseoverEvent.
14169 * @event headerCellMouseoutEvent
14170 * @deprecated Use theadCellMouseoutEvent.
14174 * @event headerCellMousedownEvent
14175 * @deprecated Use theadCellMousedownEvent.
14179 * @event headerCellClickEvent
14180 * @deprecated Use theadCellClickEvent.
14184 * @event headerCellDblclickEvent
14185 * @deprecated Use theadCellDblclickEvent.
14189 * @event headerLabelMouseoverEvent
14190 * @deprecated Use theadLabelMouseoverEvent.
14194 * @event headerLabelMouseoutEvent
14195 * @deprecated Use theadLabelMouseoutEvent.
14199 * @event headerLabelMousedownEvent
14200 * @deprecated Use theadLabelMousedownEvent.
14204 * @event headerLabelClickEvent
14205 * @deprecated Use theadLabelClickEvent.
14209 * @event headerLabelDbllickEvent
14210 * @deprecated Use theadLabelDblclickEvent.
14216 * Alias for onDataReturnSetRows for backward compatibility
14217 * @method onDataReturnSetRecords
14218 * @deprecated Use onDataReturnSetRows
14220 DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows;
14223 * Alias for onPaginatorChange for backward compatibility
14224 * @method onPaginatorChange
14225 * @deprecated Use onPaginatorChangeRequest
14227 DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest;
14229 /////////////////////////////////////////////////////////////////////////////
14231 // Deprecated static APIs
14233 /////////////////////////////////////////////////////////////////////////////
14235 * @method DataTable.formatTheadCell
14236 * @deprecated Use formatTheadCell.
14238 DT.formatTheadCell = function() {};
14241 * @method DataTable.editCheckbox
14242 * @deprecated Use YAHOO.widget.CheckboxCellEditor.
14244 DT.editCheckbox = function() {};
14247 * @method DataTable.editDate
14248 * @deprecated Use YAHOO.widget.DateCellEditor.
14250 DT.editDate = function() {};
14253 * @method DataTable.editDropdown
14254 * @deprecated Use YAHOO.widget.DropdownCellEditor.
14256 DT.editDropdown = function() {};
14259 * @method DataTable.editRadio
14260 * @deprecated Use YAHOO.widget.RadioCellEditor.
14262 DT.editRadio = function() {};
14265 * @method DataTable.editTextarea
14266 * @deprecated Use YAHOO.widget.TextareaCellEditor
14268 DT.editTextarea = function() {};
14271 * @method DataTable.editTextbox
14272 * @deprecated Use YAHOO.widget.TextboxCellEditor
14274 DT.editTextbox= function() {};
14280 var lang = YAHOO.lang,
14282 widget = YAHOO.widget,
14287 DS = util.DataSourceBase,
14288 DT = widget.DataTable,
14289 Pag = widget.Paginator;
14292 * The ScrollingDataTable class extends the DataTable class to provide
14293 * functionality for x-scrolling, y-scrolling, and xy-scrolling.
14295 * @namespace YAHOO.widget
14296 * @class ScrollingDataTable
14297 * @extends YAHOO.widget.DataTable
14299 * @param elContainer {HTMLElement} Container element for the TABLE.
14300 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
14301 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
14302 * @param oConfigs {object} (optional) Object literal of configuration values.
14304 widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
14305 oConfigs = oConfigs || {};
14307 // Prevent infinite loop
14308 if(oConfigs.scrollable) {
14309 oConfigs.scrollable = false;
14312 widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs);
14314 // Once per instance
14315 this.subscribe("columnShowEvent", this._onColumnChange);
14318 var SDT = widget.ScrollingDataTable;
14320 /////////////////////////////////////////////////////////////////////////////
14322 // Public constants
14324 /////////////////////////////////////////////////////////////////////////////
14325 lang.augmentObject(SDT, {
14328 * Class name assigned to inner DataTable header container.
14330 * @property DataTable.CLASS_HEADER
14334 * @default "yui-dt-hd"
14336 CLASS_HEADER : "yui-dt-hd",
14339 * Class name assigned to inner DataTable body container.
14341 * @property DataTable.CLASS_BODY
14345 * @default "yui-dt-bd"
14347 CLASS_BODY : "yui-dt-bd"
14350 lang.extend(SDT, DT, {
14353 * Container for fixed header TABLE element.
14355 * @property _elHdContainer
14356 * @type HTMLElement
14359 _elHdContainer : null,
14362 * Fixed header TABLE element.
14364 * @property _elHdTable
14365 * @type HTMLElement
14371 * Container for scrolling body TABLE element.
14373 * @property _elBdContainer
14374 * @type HTMLElement
14377 _elBdContainer : null,
14380 * Body THEAD element.
14382 * @property _elBdThead
14383 * @type HTMLElement
14389 * Offscreen container to temporarily clone SDT for auto-width calculation.
14391 * @property _elTmpContainer
14392 * @type HTMLElement
14395 _elTmpContainer : null,
14398 * Offscreen TABLE element for auto-width calculation.
14400 * @property _elTmpTable
14401 * @type HTMLElement
14404 _elTmpTable : null,
14407 * True if x-scrollbar is currently visible.
14408 * @property _bScrollbarX
14412 _bScrollbarX : null,
14428 /////////////////////////////////////////////////////////////////////////////
14430 // Superclass methods
14432 /////////////////////////////////////////////////////////////////////////////
14435 * Implementation of Element's abstract method. Sets up config values.
14437 * @method initAttributes
14438 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
14442 initAttributes : function(oConfigs) {
14443 oConfigs = oConfigs || {};
14444 SDT.superclass.initAttributes.call(this, oConfigs);
14448 * @description Table width for scrollable tables (e.g., "40em").
14451 this.setAttributeConfig("width", {
14453 validator: lang.isString,
14454 method: function(oParam) {
14455 if(this._elHdContainer && this._elBdContainer) {
14456 this._elHdContainer.style.width = oParam;
14457 this._elBdContainer.style.width = oParam;
14458 this._syncScrollX();
14459 this._syncScrollOverhang();
14465 * @attribute height
14466 * @description Table body height for scrollable tables, not including headers (e.g., "40em").
14469 this.setAttributeConfig("height", {
14471 validator: lang.isString,
14472 method: function(oParam) {
14473 if(this._elHdContainer && this._elBdContainer) {
14474 this._elBdContainer.style.height = oParam;
14475 this._syncScrollX();
14476 this._syncScrollY();
14477 this._syncScrollOverhang();
14483 * @attribute COLOR_COLUMNFILLER
14484 * @description CSS color value assigned to header filler on scrollable tables.
14486 * @default "#F2F2F2"
14488 this.setAttributeConfig("COLOR_COLUMNFILLER", {
14490 validator: lang.isString,
14491 method: function(oParam) {
14492 this._elHdContainer.style.backgroundColor = oParam;
14498 * Initializes DOM elements for a ScrollingDataTable, including creation of
14499 * two separate TABLE elements.
14501 * @method _initDomElements
14502 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
14503 * return {Boolean} False in case of error, otherwise true
14506 _initDomElements : function(elContainer) {
14507 // Outer and inner containers
14508 this._initContainerEl(elContainer);
14509 if(this._elContainer && this._elHdContainer && this._elBdContainer) {
14511 this._initTableEl();
14513 if(this._elHdTable && this._elTable) {
14515 ///this._initColgroupEl(this._elHdTable, this._elTable);
14516 this._initColgroupEl(this._elHdTable);
14519 this._initTheadEl(this._elHdTable, this._elTable);
14522 this._initTbodyEl(this._elTable);
14524 this._initMsgTbodyEl(this._elTable);
14527 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody ||
14528 !this._elHdTable || !this._elBdThead) {
14529 YAHOO.log("Could not instantiate DataTable due to an invalid DOM elements", "error", this.toString());
14538 * Destroy's the DataTable outer and inner container elements, if available.
14540 * @method _destroyContainerEl
14541 * @param elContainer {HTMLElement} Reference to the container element.
14544 _destroyContainerEl : function(elContainer) {
14545 Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE);
14546 SDT.superclass._destroyContainerEl.call(this, elContainer);
14547 this._elHdContainer = null;
14548 this._elBdContainer = null;
14552 * Initializes the DataTable outer container element and creates inner header
14553 * and body container elements.
14555 * @method _initContainerEl
14556 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
14559 _initContainerEl : function(elContainer) {
14560 SDT.superclass._initContainerEl.call(this, elContainer);
14562 if(this._elContainer) {
14563 elContainer = this._elContainer; // was constructor input, now is DOM ref
14564 Dom.addClass(elContainer, DT.CLASS_SCROLLABLE);
14566 // Container for header TABLE
14567 var elHdContainer = document.createElement("div");
14568 elHdContainer.style.width = this.get("width") || "";
14569 elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER");
14570 Dom.addClass(elHdContainer, SDT.CLASS_HEADER);
14571 this._elHdContainer = elHdContainer;
14572 elContainer.appendChild(elHdContainer);
14574 // Container for body TABLE
14575 var elBdContainer = document.createElement("div");
14576 elBdContainer.style.width = this.get("width") || "";
14577 elBdContainer.style.height = this.get("height") || "";
14578 Dom.addClass(elBdContainer, SDT.CLASS_BODY);
14579 Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers
14580 this._elBdContainer = elBdContainer;
14581 elContainer.appendChild(elBdContainer);
14586 * Creates HTML markup CAPTION element.
14588 * @method _initCaptionEl
14589 * @param sCaption {String} Text for caption.
14592 _initCaptionEl : function(sCaption) {
14593 // Not yet supported
14594 /*if(this._elHdTable && sCaption) {
14595 // Create CAPTION element
14596 if(!this._elCaption) {
14597 this._elCaption = this._elHdTable.createCaption();
14599 // Set CAPTION value
14600 this._elCaption.innerHTML = sCaption;
14602 else if(this._elCaption) {
14603 this._elCaption.parentNode.removeChild(this._elCaption);
14608 * Destroy's the DataTable head TABLE element, if available.
14610 * @method _destroyHdTableEl
14613 _destroyHdTableEl : function() {
14614 var elTable = this._elHdTable;
14616 Ev.purgeElement(elTable, true);
14617 elTable.parentNode.removeChild(elTable);
14619 // A little out of place, but where else can we null out these extra elements?
14620 ///this._elBdColgroup = null;
14621 this._elBdThead = null;
14626 * Initializes ScrollingDataTable TABLE elements into the two inner containers.
14628 * @method _initTableEl
14631 _initTableEl : function() {
14633 if(this._elHdContainer) {
14634 this._destroyHdTableEl();
14637 this._elHdTable = this._elHdContainer.appendChild(document.createElement("table"));
14640 SDT.superclass._initTableEl.call(this, this._elBdContainer);
14644 * Initializes ScrollingDataTable THEAD elements into the two inner containers.
14646 * @method _initTheadEl
14647 * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference.
14648 * @param elTable {HTMLElement} (optional) TABLE element reference.
14651 _initTheadEl : function(elHdTable, elTable) {
14652 elHdTable = elHdTable || this._elHdTable;
14653 elTable = elTable || this._elTable;
14655 // Scrolling body's THEAD
14656 this._initBdTheadEl(elTable);
14657 // Standard fixed head THEAD
14658 SDT.superclass._initTheadEl.call(this, elHdTable);
14662 * SDT changes ID so as not to duplicate the accessibility TH IDs.
14664 * @method _initThEl
14665 * @param elTh {HTMLElement} TH element reference.
14666 * @param oColumn {YAHOO.widget.Column} Column object.
14669 _initThEl : function(elTh, oColumn) {
14670 SDT.superclass._initThEl.call(this, elTh, oColumn);
14671 elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD
14675 * Destroy's the DataTable body THEAD element, if available.
14677 * @method _destroyBdTheadEl
14680 _destroyBdTheadEl : function() {
14681 var elBdThead = this._elBdThead;
14683 var elTable = elBdThead.parentNode;
14684 Ev.purgeElement(elBdThead, true);
14685 elTable.removeChild(elBdThead);
14686 this._elBdThead = null;
14688 this._destroyColumnHelpers();
14693 * Initializes body THEAD element.
14695 * @method _initBdTheadEl
14696 * @param elTable {HTMLElement} TABLE element into which to create THEAD.
14697 * @return {HTMLElement} Initialized THEAD element.
14700 _initBdTheadEl : function(elTable) {
14702 // Destroy previous
14703 this._destroyBdTheadEl();
14705 var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild);
14707 // Add TRs to the THEAD;
14708 var oColumnSet = this._oColumnSet,
14709 colTree = oColumnSet.tree,
14710 elTh, elTheadTr, oColumn, i, j, k, len;
14712 for(i=0, k=colTree.length; i<k; i++) {
14713 elTheadTr = elThead.appendChild(document.createElement("tr"));
14715 // ...and create TH cells
14716 for(j=0, len=colTree[i].length; j<len; j++) {
14717 oColumn = colTree[i][j];
14718 elTh = elTheadTr.appendChild(document.createElement("th"));
14719 this._initBdThEl(elTh,oColumn,i,j);
14722 this._elBdThead = elThead;
14723 YAHOO.log("Accessibility TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
14728 * Populates TH element for the body THEAD element.
14730 * @method _initBdThEl
14731 * @param elTh {HTMLElement} TH element reference.
14732 * @param oColumn {YAHOO.widget.Column} Column object.
14735 _initBdThEl : function(elTh, oColumn) {
14736 elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility
14737 elTh.rowSpan = oColumn.getRowspan();
14738 elTh.colSpan = oColumn.getColspan();
14739 // Assign abbr attribute
14741 elTh.abbr = oColumn.abbr;
14744 // TODO: strip links and form elements
14745 var sKey = oColumn.getKey();
14746 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
14747 elTh.innerHTML = sLabel;
14751 * Initializes ScrollingDataTable TBODY element for data
14753 * @method _initTbodyEl
14754 * @param elTable {HTMLElement} TABLE element into which to create TBODY .
14757 _initTbodyEl : function(elTable) {
14758 SDT.superclass._initTbodyEl.call(this, elTable);
14760 // Bug 2105534 - Safari 3 gap
14761 // Bug 2492591 - IE8 offsetTop
14762 elTable.style.marginTop = (this._elTbody.offsetTop > 0) ?
14763 "-"+this._elTbody.offsetTop+"px" : 0;
14795 * Sets focus on the given element.
14798 * @param el {HTMLElement} Element.
14801 _focusEl : function(el) {
14802 el = el || this._elTbody;
14804 this._storeScrollPositions();
14805 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
14806 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
14807 // strange unexpected things as the user clicks on buttons and other controls.
14809 // Bug 1921135: Wrap the whole thing in a setTimeout
14810 setTimeout(function() {
14811 setTimeout(function() {
14814 oSelf._restoreScrollPositions();
14841 * Internal wrapper calls run() on render Chain instance.
14843 * @method _runRenderChain
14846 _runRenderChain : function() {
14847 this._storeScrollPositions();
14848 this._oChainRender.run();
14852 * Stores scroll positions so they can be restored after a render.
14854 * @method _storeScrollPositions
14857 _storeScrollPositions : function() {
14858 this._nScrollTop = this._elBdContainer.scrollTop;
14859 this._nScrollLeft = this._elBdContainer.scrollLeft;
14863 * Clears stored scroll positions to interrupt the automatic restore mechanism.
14864 * Useful for setting scroll positions programmatically rather than as part of
14865 * the post-render cleanup process.
14867 * @method clearScrollPositions
14870 clearScrollPositions : function() {
14871 this._nScrollTop = 0;
14872 this._nScrollLeft = 0;
14876 * Restores scroll positions to stored value.
14878 * @method _retoreScrollPositions
14881 _restoreScrollPositions : function() {
14882 // Reset scroll positions
14883 if(this._nScrollTop) {
14884 this._elBdContainer.scrollTop = this._nScrollTop;
14885 this._nScrollTop = null;
14887 if(this._nScrollLeft) {
14888 this._elBdContainer.scrollLeft = this._nScrollLeft;
14889 this._nScrollLeft = null;
14894 * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable.
14896 * @method _validateColumnWidth
14897 * @param oColumn {YAHOO.widget.Column} Column instance.
14898 * @param elTd {HTMLElement} TD element to validate against.
14901 _validateColumnWidth : function(oColumn, elTd) {
14902 // Only Columns without widths that are not hidden
14903 if(!oColumn.width && !oColumn.hidden) {
14904 var elTh = oColumn.getThEl();
14905 // Unset a calculated auto-width
14906 if(oColumn._calculatedWidth) {
14907 this._setColumnWidth(oColumn, "auto", "visible");
14909 // Compare auto-widths
14910 if(elTh.offsetWidth !== elTd.offsetWidth) {
14911 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14912 oColumn.getThLinerEl() : elTd.firstChild;
14914 // Grab the wider liner width, unless the minWidth is wider
14915 var newWidth = Math.max(0,
14916 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14919 var sOverflow = 'visible';
14921 // Now validate against maxAutoWidth
14922 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14923 newWidth = oColumn.maxAutoWidth;
14924 sOverflow = "hidden";
14927 // Set to the wider auto-width
14928 this._elTbody.style.display = "none";
14929 this._setColumnWidth(oColumn, newWidth+'px', sOverflow);
14930 oColumn._calculatedWidth = newWidth;
14931 this._elTbody.style.display = "";
14937 * For one or all Columns of a ScrollingDataTable, when Column is not hidden,
14938 * and width is not set, syncs widths of header and body cells and
14939 * validates that width against minWidth and/or maxAutoWidth as necessary.
14941 * @method validateColumnWidths
14942 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
14944 validateColumnWidths : function(oColumn) {
14945 // Validate there is at least one TR with proper TDs
14946 var allKeys = this._oColumnSet.keys,
14947 allKeysLength = allKeys.length,
14948 elRow = this.getFirstTrEl();
14950 // Reset overhang for IE
14952 this._setOverhangValue(1);
14955 if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) {
14956 // Temporarily unsnap container since it causes inaccurate calculations
14957 var sWidth = this.get("width");
14959 this._elHdContainer.style.width = "";
14960 this._elBdContainer.style.width = "";
14962 this._elContainer.style.width = "";
14964 //Validate just one Column
14965 if(oColumn && lang.isNumber(oColumn.getKeyIndex())) {
14966 this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]);
14968 // Iterate through all Columns to unset calculated widths in one pass
14970 var elTd, todos = [], thisTodo, i, len;
14971 for(i=0; i<allKeysLength; i++) {
14972 oColumn = allKeys[i];
14973 // Only Columns without widths that are not hidden, unset a calculated auto-width
14974 if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) {
14975 todos[todos.length] = oColumn;
14979 this._elTbody.style.display = "none";
14980 for(i=0, len=todos.length; i<len; i++) {
14981 this._setColumnWidth(todos[i], "auto", "visible");
14983 this._elTbody.style.display = "";
14987 // Iterate through all Columns and make the store the adjustments to make in one pass
14988 for(i=0; i<allKeysLength; i++) {
14989 oColumn = allKeys[i];
14990 elTd = elRow.childNodes[i];
14991 // Only Columns without widths that are not hidden
14992 if(!oColumn.width && !oColumn.hidden) {
14993 var elTh = oColumn.getThEl();
14995 // Compare auto-widths
14996 if(elTh.offsetWidth !== elTd.offsetWidth) {
14997 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14998 oColumn.getThLinerEl() : elTd.firstChild;
15000 // Grab the wider liner width, unless the minWidth is wider
15001 var newWidth = Math.max(0,
15002 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
15005 var sOverflow = 'visible';
15007 // Now validate against maxAutoWidth
15008 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
15009 newWidth = oColumn.maxAutoWidth;
15010 sOverflow = "hidden";
15013 todos[todos.length] = [oColumn, newWidth, sOverflow];
15018 this._elTbody.style.display = "none";
15019 for(i=0, len=todos.length; i<len; i++) {
15020 thisTodo = todos[i];
15021 // Set to the wider auto-width
15022 this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]);
15023 thisTodo[0]._calculatedWidth = thisTodo[1];
15025 this._elTbody.style.display = "";
15028 // Resnap unsnapped containers
15030 this._elHdContainer.style.width = sWidth;
15031 this._elBdContainer.style.width = sWidth;
15035 this._syncScroll();
15036 this._restoreScrollPositions();
15040 * Syncs padding around scrollable tables, including Column header right-padding
15041 * and container width and height.
15043 * @method _syncScroll
15046 _syncScroll : function() {
15047 this._syncScrollX();
15048 this._syncScrollY();
15049 this._syncScrollOverhang();
15052 this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft;
15053 if(!this.get("width")) {
15055 document.body.style += '';
15061 * Snaps container width for y-scrolling tables.
15063 * @method _syncScrollY
15066 _syncScrollY : function() {
15067 var elTbody = this._elTbody,
15068 elBdContainer = this._elBdContainer;
15070 // X-scrolling not enabled
15071 if(!this.get("width")) {
15072 // Snap outer container width to content
15073 this._elContainer.style.width =
15074 (elBdContainer.scrollHeight > elBdContainer.clientHeight) ?
15075 // but account for y-scrollbar since it is visible
15076 (elTbody.parentNode.clientWidth + 19) + "px" :
15077 // no y-scrollbar, just borders
15078 (elTbody.parentNode.clientWidth + 2) + "px";
15083 * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
15085 * @method _syncScrollX
15088 _syncScrollX : function() {
15089 var elTbody = this._elTbody,
15090 elBdContainer = this._elBdContainer;
15092 // IE 6 and 7 only when y-scrolling not enabled
15093 if(!this.get("height") && (ua.ie)) {
15094 // Snap outer container height to content
15095 elBdContainer.style.height =
15096 // but account for x-scrollbar if it is visible
15097 (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ?
15098 (elTbody.parentNode.offsetHeight + 18) + "px" :
15099 elTbody.parentNode.offsetHeight + "px";
15102 // Sync message tbody
15103 if(this._elTbody.rows.length === 0) {
15104 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
15107 this._elMsgTbody.parentNode.style.width = "";
15112 * Adds/removes Column header overhang as necesary.
15114 * @method _syncScrollOverhang
15117 _syncScrollOverhang : function() {
15118 var elBdContainer = this._elBdContainer,
15119 // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table
15122 // Y-scrollbar is visible, which is when the overhang needs to jut out
15123 if((elBdContainer.scrollHeight > elBdContainer.clientHeight) &&
15124 // X-scrollbar is also visible, which means the right is jagged, not flush with the Column
15125 (elBdContainer.scrollWidth > elBdContainer.clientWidth)) {
15129 this._setOverhangValue(nPadding);
15134 * Sets Column header overhang to given width.
15136 * @method _setOverhangValue
15137 * @param nBorderWidth {Number} Value of new border for overhang.
15140 _setOverhangValue : function(nBorderWidth) {
15141 var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [],
15142 len = aLastHeaders.length,
15143 sPrefix = this._sId+"-fixedth-",
15144 sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER");
15146 this._elThead.style.display = "none";
15147 for(var i=0; i<len; i++) {
15148 Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue;
15150 this._elThead.style.display = "";
15191 * Returns DOM reference to the DataTable's fixed header container element.
15193 * @method getHdContainerEl
15194 * @return {HTMLElement} Reference to DIV element.
15196 getHdContainerEl : function() {
15197 return this._elHdContainer;
15201 * Returns DOM reference to the DataTable's scrolling body container element.
15203 * @method getBdContainerEl
15204 * @return {HTMLElement} Reference to DIV element.
15206 getBdContainerEl : function() {
15207 return this._elBdContainer;
15211 * Returns DOM reference to the DataTable's fixed header TABLE element.
15213 * @method getHdTableEl
15214 * @return {HTMLElement} Reference to TABLE element.
15216 getHdTableEl : function() {
15217 return this._elHdTable;
15221 * Returns DOM reference to the DataTable's scrolling body TABLE element.
15223 * @method getBdTableEl
15224 * @return {HTMLElement} Reference to TABLE element.
15226 getBdTableEl : function() {
15227 return this._elTable;
15231 * Disables ScrollingDataTable UI.
15235 disable : function() {
15236 var elMask = this._elMask;
15237 elMask.style.width = this._elBdContainer.offsetWidth + "px";
15238 elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px";
15239 elMask.style.display = "";
15240 this.fireEvent("disableEvent");
15244 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
15245 * non-nested Columns, and top-level parent Columns (which will remove all
15246 * children Columns).
15248 * @method removeColumn
15249 * @param oColumn {YAHOO.widget.Column} Column instance.
15250 * @return oColumn {YAHOO.widget.Column} Removed Column instance.
15252 removeColumn : function(oColumn) {
15253 // Store scroll pos
15254 var hdPos = this._elHdContainer.scrollLeft;
15255 var bdPos = this._elBdContainer.scrollLeft;
15257 // Call superclass method
15258 oColumn = SDT.superclass.removeColumn.call(this, oColumn);
15260 // Restore scroll pos
15261 this._elHdContainer.scrollLeft = hdPos;
15262 this._elBdContainer.scrollLeft = bdPos;
15268 * Inserts given Column at the index if given, otherwise at the end. NOTE: You
15269 * can only add non-nested Columns and top-level parent Columns. You cannot add
15270 * a nested Column to an existing parent.
15272 * @method insertColumn
15273 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
15274 * definition or a Column instance.
15275 * @param index {Number} (optional) New tree index.
15276 * @return oColumn {YAHOO.widget.Column} Inserted Column instance.
15278 insertColumn : function(oColumn, index) {
15279 // Store scroll pos
15280 var hdPos = this._elHdContainer.scrollLeft;
15281 var bdPos = this._elBdContainer.scrollLeft;
15283 // Call superclass method
15284 var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index);
15286 // Restore scroll pos
15287 this._elHdContainer.scrollLeft = hdPos;
15288 this._elBdContainer.scrollLeft = bdPos;
15294 * Removes given Column and inserts into given tree index. NOTE: You
15295 * can only reorder non-nested Columns and top-level parent Columns. You cannot
15296 * reorder a nested Column to an existing parent.
15298 * @method reorderColumn
15299 * @param oColumn {YAHOO.widget.Column} Column instance.
15300 * @param index {Number} New tree index.
15302 reorderColumn : function(oColumn, index) {
15303 // Store scroll pos
15304 var hdPos = this._elHdContainer.scrollLeft;
15305 var bdPos = this._elBdContainer.scrollLeft;
15307 // Call superclass method
15308 var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index);
15310 // Restore scroll pos
15311 this._elHdContainer.scrollLeft = hdPos;
15312 this._elBdContainer.scrollLeft = bdPos;
15318 * Sets given Column to given pixel width. If new width is less than minWidth
15319 * width, sets to minWidth. Updates oColumn.width value.
15321 * @method setColumnWidth
15322 * @param oColumn {YAHOO.widget.Column} Column instance.
15323 * @param nWidth {Number} New width in pixels.
15325 setColumnWidth : function(oColumn, nWidth) {
15326 oColumn = this.getColumn(oColumn);
15328 this._storeScrollPositions();
15330 // Validate new width against minWidth
15331 if(lang.isNumber(nWidth)) {
15332 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
15335 oColumn.width = nWidth;
15337 // Resize the DOM elements
15338 this._setColumnWidth(oColumn, nWidth+"px");
15339 this._syncScroll();
15341 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
15342 YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString());
15344 // Unsets a width to auto-size
15345 else if(nWidth === null) {
15347 oColumn.width = nWidth;
15349 // Resize the DOM elements
15350 this._setColumnWidth(oColumn, "auto");
15351 this.validateColumnWidths(oColumn);
15352 this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
15353 YAHOO.log("Column " + oColumn + " width unset", "info", this.toString());
15356 // Bug 2339454: resize then sort misaligment
15357 this._clearTrTemplateEl();
15360 YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString());
15365 * Scrolls to given row or cell
15368 * @param to {YAHOO.widget.Record | HTMLElement } Itme to scroll to.
15370 scrollTo : function(to) {
15371 var td = this.getTdEl(to);
15373 this.clearScrollPositions();
15374 this.getBdContainerEl().scrollLeft = td.offsetLeft;
15375 this.getBdContainerEl().scrollTop = td.parentNode.offsetTop;
15378 var tr = this.getTrEl(to);
15380 this.clearScrollPositions();
15381 this.getBdContainerEl().scrollTop = tr.offsetTop;
15387 * Displays message within secondary TBODY.
15389 * @method showTableMessage
15390 * @param sHTML {String} (optional) Value for innerHTMlang.
15391 * @param sClassName {String} (optional) Classname.
15393 showTableMessage : function(sHTML, sClassName) {
15394 var elCell = this._elMsgTd;
15395 if(lang.isString(sHTML)) {
15396 elCell.firstChild.innerHTML = sHTML;
15398 if(lang.isString(sClassName)) {
15399 Dom.addClass(elCell.firstChild, sClassName);
15402 // Needed for SDT only
15403 var elThead = this.getTheadEl();
15404 var elTable = elThead.parentNode;
15405 var newWidth = elTable.offsetWidth;
15406 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
15408 this._elMsgTbody.style.display = "";
15410 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
15411 YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
15426 /////////////////////////////////////////////////////////////////////////////
15428 // Private Custom Event Handlers
15430 /////////////////////////////////////////////////////////////////////////////
15433 * Handles Column mutations
15435 * @method onColumnChange
15436 * @param oArgs {Object} Custom Event data.
15438 _onColumnChange : function(oArg) {
15439 // Figure out which Column changed
15440 var oColumn = (oArg.column) ? oArg.column :
15441 (oArg.editor) ? oArg.editor.column : null;
15442 this._storeScrollPositions();
15443 this.validateColumnWidths(oColumn);
15460 /////////////////////////////////////////////////////////////////////////////
15462 // Private DOM Event Handlers
15464 /////////////////////////////////////////////////////////////////////////////
15467 * Syncs scrolltop and scrollleft of all TABLEs.
15469 * @method _onScroll
15470 * @param e {HTMLEvent} The scroll event.
15471 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15474 _onScroll : function(e, oSelf) {
15475 oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft;
15477 if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
15478 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
15479 oSelf.cancelCellEditor();
15482 var elTarget = Ev.getTarget(e);
15483 var elTag = elTarget.nodeName.toLowerCase();
15484 oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
15488 * Handles keydown events on the THEAD element.
15490 * @method _onTheadKeydown
15491 * @param e {HTMLEvent} The key event.
15492 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15495 _onTheadKeydown : function(e, oSelf) {
15496 // If tabbing to next TH label link causes THEAD to scroll,
15497 // need to sync scrollLeft with TBODY
15498 if(Ev.getCharCode(e) === 9) {
15499 setTimeout(function() {
15500 if((oSelf instanceof SDT) && oSelf._sId) {
15501 oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft;
15506 var elTarget = Ev.getTarget(e);
15507 var elTag = elTarget.nodeName.toLowerCase();
15508 var bKeepBubbling = true;
15509 while(elTarget && (elTag != "table")) {
15515 // TODO: implement textareaKeyEvent
15518 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
15523 if(bKeepBubbling === false) {
15527 elTarget = elTarget.parentNode;
15529 elTag = elTarget.nodeName.toLowerCase();
15533 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
15540 * Fired when a fixed scrolling DataTable has a scroll.
15542 * @event tableScrollEvent
15543 * @param oArgs.event {HTMLEvent} The event object.
15544 * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
15545 * or the DataTable's TBODY element (everyone else).
15558 var lang = YAHOO.lang,
15560 widget = YAHOO.widget,
15566 DT = widget.DataTable;
15567 /****************************************************************************/
15568 /****************************************************************************/
15569 /****************************************************************************/
15572 * The BaseCellEditor class provides base functionality common to all inline cell
15573 * editors for a DataTable widget.
15575 * @namespace YAHOO.widget
15576 * @class BaseCellEditor
15577 * @uses YAHOO.util.EventProvider
15579 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
15580 * @param oConfigs {Object} (Optional) Object literal of configs.
15582 widget.BaseCellEditor = function(sType, oConfigs) {
15583 this._sId = this._sId || "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++;
15584 this._sType = sType;
15587 this._initConfigs(oConfigs);
15589 // Create Custom Events
15590 this._initEvents();
15596 var BCE = widget.BaseCellEditor;
15598 /////////////////////////////////////////////////////////////////////////////
15602 /////////////////////////////////////////////////////////////////////////////
15603 lang.augmentObject(BCE, {
15606 * Global instance counter.
15608 * @property CellEditor._nCount
15617 * Class applied to CellEditor container.
15619 * @property CellEditor.CLASS_CELLEDITOR
15622 * @default "yui-ceditor"
15624 CLASS_CELLEDITOR : "yui-ceditor"
15629 /////////////////////////////////////////////////////////////////////////////
15633 /////////////////////////////////////////////////////////////////////////////
15635 * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique
15636 * DOM ID strings and log messages.
15654 * DataTable instance.
15656 * @property _oDataTable
15657 * @type YAHOO.widget.DataTable
15660 _oDataTable : null,
15665 * @property _oColumn
15666 * @type YAHOO.widget.Column
15675 * @property _oRecord
15676 * @type YAHOO.widget.Record
15686 * @type HTMLElement
15693 * Container for inline editor.
15695 * @property _elContainer
15696 * @type HTMLElement
15699 _elContainer : null,
15702 * Reference to Cancel button, if available.
15704 * @property _elCancelBtn
15705 * @type HTMLElement
15709 _elCancelBtn : null,
15712 * Reference to Save button, if available.
15714 * @property _elSaveBtn
15715 * @type HTMLElement
15728 /////////////////////////////////////////////////////////////////////////////
15732 /////////////////////////////////////////////////////////////////////////////
15735 * Initialize configs.
15737 * @method _initConfigs
15740 _initConfigs : function(oConfigs) {
15741 // Object literal defines CellEditor configs
15742 if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
15743 for(var sConfig in oConfigs) {
15745 this[sConfig] = oConfigs[sConfig];
15752 * Initialize Custom Events.
15754 * @method _initEvents
15757 _initEvents : function() {
15758 this.createEvent("showEvent");
15759 this.createEvent("keydownEvent");
15760 this.createEvent("invalidDataEvent");
15761 this.createEvent("revertEvent");
15762 this.createEvent("saveEvent");
15763 this.createEvent("cancelEvent");
15764 this.createEvent("blurEvent");
15765 this.createEvent("blockEvent");
15766 this.createEvent("unblockEvent");
15781 /////////////////////////////////////////////////////////////////////////////
15783 // Public properties
15785 /////////////////////////////////////////////////////////////////////////////
15787 * Implementer defined function that can submit the input value to a server. This
15788 * function must accept the arguments fnCallback and oNewValue. When the submission
15789 * is complete, the function must also call fnCallback(bSuccess, oNewValue) to
15790 * finish the save routine in the CellEditor. This function can also be used to
15791 * perform extra validation or input value manipulation.
15793 * @property asyncSubmitter
15794 * @type HTMLFunction
15796 asyncSubmitter : null,
15807 * Default value in case Record data is undefined. NB: Null values will not trigger
15808 * the default value.
15810 * @property defaultValue
15814 defaultValue : null,
15817 * Validator function for input data, called from the DataTable instance scope,
15818 * receives the arguments (inputValue, currentValue, editorInstance) and returns
15819 * either the validated (or type-converted) value or undefined.
15821 * @property validator
15822 * @type HTMLFunction
15828 * If validation is enabled, resets input field of invalid data.
15830 * @property resetInvalidData
15834 resetInvalidData : true,
15837 * True if currently active.
15839 * @property isActive
15845 * Text to display on Save button.
15847 * @property LABEL_SAVE
15851 LABEL_SAVE : "Save",
15854 * Text to display on Cancel button.
15856 * @property LABEL_CANCEL
15858 * @default "Cancel"
15860 LABEL_CANCEL : "Cancel",
15863 * True if Save/Cancel buttons should not be displayed in the CellEditor.
15865 * @property disableBtns
15869 disableBtns : false,
15877 /////////////////////////////////////////////////////////////////////////////
15881 /////////////////////////////////////////////////////////////////////////////
15883 * CellEditor instance name, for logging.
15886 * @return {String} Unique name of the CellEditor instance.
15889 toString : function() {
15890 return "CellEditor instance " + this._sId;
15894 * CellEditor unique ID.
15897 * @return {String} Unique ID of the CellEditor instance.
15900 getId : function() {
15905 * Returns reference to associated DataTable instance.
15907 * @method getDataTable
15908 * @return {YAHOO.widget.DataTable} DataTable instance.
15911 getDataTable : function() {
15912 return this._oDataTable;
15916 * Returns reference to associated Column instance.
15918 * @method getColumn
15919 * @return {YAHOO.widget.Column} Column instance.
15922 getColumn : function() {
15923 return this._oColumn;
15927 * Returns reference to associated Record instance.
15929 * @method getRecord
15930 * @return {YAHOO.widget.Record} Record instance.
15933 getRecord : function() {
15934 return this._oRecord;
15940 * Returns reference to associated TD element.
15943 * @return {HTMLElement} TD element.
15946 getTdEl : function() {
15951 * Returns container element.
15953 * @method getContainerEl
15954 * @return {HTMLElement} Reference to container element.
15957 getContainerEl : function() {
15958 return this._elContainer;
15962 * Nulls out the entire CellEditor instance and related objects, removes attached
15963 * event listeners, and clears out DOM elements inside the container, removes
15964 * container from the DOM.
15968 destroy : function() {
15969 this.unsubscribeAll();
15971 // Column is late-binding in attach()
15972 var oColumn = this.getColumn();
15974 oColumn.editor = null;
15977 var elContainer = this.getContainerEl();
15978 Ev.purgeElement(elContainer, true);
15979 elContainer.parentNode.removeChild(elContainer);
15983 * Renders DOM elements and attaches event listeners.
15987 render : function() {
15988 if(this._elContainer) {
15989 YAHOO.util.Event.purgeElement(this._elContainer, true);
15990 this._elContainer.innerHTML = "";
15993 // Render Cell Editor container element as first child of body
15994 var elContainer = document.createElement("div");
15995 elContainer.id = this.getId() + "-container"; // Needed for tracking blur event
15996 elContainer.style.display = "none";
15997 elContainer.tabIndex = 0;
15998 elContainer.className = DT.CLASS_EDITOR;
15999 document.body.insertBefore(elContainer, document.body.firstChild);
16000 this._elContainer = elContainer;
16003 Ev.addListener(elContainer, "keydown", function(e, oSelf) {
16004 // ESC cancels Cell Editor
16005 if((e.keyCode == 27)) {
16006 var target = Ev.getTarget(e);
16007 // workaround for Mac FF3 bug that disabled clicks when ESC hit when
16008 // select is open. [bug 2273056]
16009 if (target.nodeName && target.nodeName.toLowerCase() === 'select') {
16014 // Pass through event
16015 oSelf.fireEvent("keydownEvent", {editor:this, event:e});
16020 // Show Save/Cancel buttons
16021 if(!this.disableBtns) {
16025 this.doAfterRender();
16029 * Renders Save/Cancel buttons.
16031 * @method renderBtns
16033 renderBtns : function() {
16035 var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div"));
16036 elBtnsDiv.className = DT.CLASS_BUTTON;
16039 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
16040 elSaveBtn.className = DT.CLASS_DEFAULT;
16041 elSaveBtn.innerHTML = this.LABEL_SAVE;
16042 Ev.addListener(elSaveBtn, "click", function(oArgs) {
16045 this._elSaveBtn = elSaveBtn;
16048 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
16049 elCancelBtn.innerHTML = this.LABEL_CANCEL;
16050 Ev.addListener(elCancelBtn, "click", function(oArgs) {
16053 this._elCancelBtn = elCancelBtn;
16057 * Attach CellEditor for a new interaction.
16060 * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance.
16061 * @param elCell {HTMLElement} Cell to edit.
16063 attach : function(oDataTable, elCell) {
16065 if(oDataTable instanceof YAHOO.widget.DataTable) {
16066 this._oDataTable = oDataTable;
16069 elCell = oDataTable.getTdEl(elCell);
16071 this._elTd = elCell;
16074 var oColumn = oDataTable.getColumn(elCell);
16076 this._oColumn = oColumn;
16079 var oRecord = oDataTable.getRecord(elCell);
16081 this._oRecord = oRecord;
16082 var value = oRecord.getData(this.getColumn().getField());
16083 this.value = (value !== undefined) ? value : this.defaultValue;
16089 YAHOO.log("Could not attach CellEditor","error",this.toString());
16094 * Moves container into position for display.
16098 move : function() {
16100 var elContainer = this.getContainerEl(),
16101 elTd = this.getTdEl(),
16102 x = Dom.getX(elTd),
16103 y = Dom.getY(elTd);
16105 //TODO: remove scrolling logic
16106 // SF doesn't get xy for cells in scrolling table
16107 // when tbody display is set to block
16108 if(isNaN(x) || isNaN(y)) {
16109 var elTbody = this.getDataTable().getTbodyEl();
16110 x = elTd.offsetLeft + // cell pos relative to table
16111 Dom.getX(elTbody.parentNode) - // plus table pos relative to document
16112 elTbody.scrollLeft; // minus tbody scroll
16113 y = elTd.offsetTop + // cell pos relative to table
16114 Dom.getY(elTbody.parentNode) - // plus table pos relative to document
16115 elTbody.scrollTop + // minus tbody scroll
16116 this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells
16119 elContainer.style.left = x + "px";
16120 elContainer.style.top = y + "px";
16124 * Displays CellEditor UI in the correct position.
16128 show : function() {
16130 this.isActive = true;
16131 this.getContainerEl().style.display = "";
16133 this.fireEvent("showEvent", {editor:this});
16134 YAHOO.log("CellEditor shown", "info", this.toString());
16142 block : function() {
16143 this.fireEvent("blockEvent", {editor:this});
16144 YAHOO.log("CellEditor blocked", "info", this.toString());
16148 * Fires unblockEvent
16152 unblock : function() {
16153 this.fireEvent("unblockEvent", {editor:this});
16154 YAHOO.log("CellEditor unblocked", "info", this.toString());
16158 * Saves value of CellEditor and hides UI.
16162 save : function() {
16164 var inputValue = this.getInputValue();
16165 var validValue = inputValue;
16167 // Validate new value
16168 if(this.validator) {
16169 validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this);
16170 if(validValue === undefined ) {
16171 if(this.resetInvalidData) {
16174 this.fireEvent("invalidDataEvent",
16175 {editor:this, oldData:this.value, newData:inputValue});
16176 YAHOO.log("Could not save Cell Editor input due to invalid data " +
16177 lang.dump(inputValue), "warn", this.toString());
16183 var finishSave = function(bSuccess, oNewValue) {
16184 var oOrigValue = oSelf.value;
16186 // Update new value
16187 oSelf.value = oNewValue;
16188 oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue);
16191 oSelf.getContainerEl().style.display = "none";
16192 oSelf.isActive = false;
16193 oSelf.getDataTable()._oCellEditor = null;
16195 oSelf.fireEvent("saveEvent",
16196 {editor:oSelf, oldData:oOrigValue, newData:oSelf.value});
16197 YAHOO.log("Cell Editor input saved", "info", this.toString());
16201 oSelf.fireEvent("revertEvent",
16202 {editor:oSelf, oldData:oOrigValue, newData:oNewValue});
16203 YAHOO.log("Could not save Cell Editor input " +
16204 lang.dump(oNewValue), "warn", oSelf.toString());
16210 if(lang.isFunction(this.asyncSubmitter)) {
16211 this.asyncSubmitter.call(this, finishSave, validValue);
16214 finishSave(true, validValue);
16219 * Cancels CellEditor input and hides UI.
16223 cancel : function() {
16224 if(this.isActive) {
16225 this.getContainerEl().style.display = "none";
16226 this.isActive = false;
16227 this.getDataTable()._oCellEditor = null;
16228 this.fireEvent("cancelEvent", {editor:this});
16229 YAHOO.log("CellEditor canceled", "info", this.toString());
16232 YAHOO.log("Unable to cancel CellEditor", "warn", this.toString());
16237 * Renders form elements.
16239 * @method renderForm
16241 renderForm : function() {
16242 // To be implemented by subclass
16246 * Access to add additional event listeners.
16248 * @method doAfterRender
16250 doAfterRender : function() {
16251 // To be implemented by subclass
16256 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16257 * to save input without them.
16259 * @method handleDisabledBtns
16261 handleDisabledBtns : function() {
16262 // To be implemented by subclass
16266 * Resets CellEditor UI to initial state.
16268 * @method resetForm
16270 resetForm : function() {
16271 // To be implemented by subclass
16275 * Sets focus in CellEditor.
16279 focus : function() {
16280 // To be implemented by subclass
16284 * Retrieves input value from CellEditor.
16286 * @method getInputValue
16288 getInputValue : function() {
16289 // To be implemented by subclass
16294 lang.augmentProto(BCE, util.EventProvider);
16297 /////////////////////////////////////////////////////////////////////////////
16301 /////////////////////////////////////////////////////////////////////////////
16304 * Fired when a CellEditor is shown.
16307 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16311 * Fired when a CellEditor has a keydown.
16313 * @event keydownEvent
16314 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16315 * @param oArgs.event {HTMLEvent} The event object.
16319 * Fired when a CellEditor input is reverted due to invalid data.
16321 * @event invalidDataEvent
16322 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16323 * @param oArgs.newData {Object} New data value from form input field.
16324 * @param oArgs.oldData {Object} Old data value.
16328 * Fired when a CellEditor input is reverted due to asyncSubmitter failure.
16330 * @event revertEvent
16331 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16332 * @param oArgs.newData {Object} New data value from form input field.
16333 * @param oArgs.oldData {Object} Old data value.
16337 * Fired when a CellEditor input is saved.
16340 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16341 * @param oArgs.newData {Object} New data value from form input field.
16342 * @param oArgs.oldData {Object} Old data value.
16346 * Fired when a CellEditor input is canceled.
16348 * @event cancelEvent
16349 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16353 * Fired when a CellEditor has a blur event.
16356 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16372 /****************************************************************************/
16373 /****************************************************************************/
16374 /****************************************************************************/
16377 * The CheckboxCellEditor class provides functionality for inline editing
16378 * DataTable cell data with checkboxes.
16380 * @namespace YAHOO.widget
16381 * @class CheckboxCellEditor
16382 * @extends YAHOO.widget.BaseCellEditor
16384 * @param oConfigs {Object} (Optional) Object literal of configs.
16386 widget.CheckboxCellEditor = function(oConfigs) {
16387 this._sId = "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16388 widget.CheckboxCellEditor.superclass.constructor.call(this, "checkbox", oConfigs);
16391 // CheckboxCellEditor extends BaseCellEditor
16392 lang.extend(widget.CheckboxCellEditor, BCE, {
16394 /////////////////////////////////////////////////////////////////////////////
16396 // CheckboxCellEditor public properties
16398 /////////////////////////////////////////////////////////////////////////////
16400 * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"])
16401 * or a an array of objects (e.g., [{label:"red", value:"#FF0000"},
16402 * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]).
16404 * @property checkboxOptions
16405 * @type String[] | Object[]
16407 checkboxOptions : null,
16410 * Reference to the checkbox elements.
16412 * @property checkboxes
16413 * @type HTMLElement[]
16418 * Array of checked values
16425 /////////////////////////////////////////////////////////////////////////////
16427 // CheckboxCellEditor public methods
16429 /////////////////////////////////////////////////////////////////////////////
16432 * Render a form with input(s) type=checkbox.
16434 * @method renderForm
16436 renderForm : function() {
16437 if(lang.isArray(this.checkboxOptions)) {
16438 var checkboxOption, checkboxValue, checkboxId, elLabel, j, len;
16440 // Create the checkbox buttons in an IE-friendly way...
16441 for(j=0,len=this.checkboxOptions.length; j<len; j++) {
16442 checkboxOption = this.checkboxOptions[j];
16443 checkboxValue = lang.isValue(checkboxOption.value) ?
16444 checkboxOption.value : checkboxOption;
16446 checkboxId = this.getId() + "-chk" + j;
16447 this.getContainerEl().innerHTML += "<input type=\"checkbox\"" +
16448 " id=\"" + checkboxId + "\"" + // Needed for label
16449 " value=\"" + checkboxValue + "\" />";
16451 // Create the labels in an IE-friendly way
16452 elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16453 elLabel.htmlFor = checkboxId;
16454 elLabel.innerHTML = lang.isValue(checkboxOption.label) ?
16455 checkboxOption.label : checkboxOption;
16458 // Store the reference to the checkbox elements
16459 var allCheckboxes = [];
16460 for(j=0; j<len; j++) {
16461 allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2];
16463 this.checkboxes = allCheckboxes;
16465 if(this.disableBtns) {
16466 this.handleDisabledBtns();
16470 YAHOO.log("Could not find checkboxOptions", "error", this.toString());
16475 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16476 * to save input without them.
16478 * @method handleDisabledBtns
16480 handleDisabledBtns : function() {
16481 Ev.addListener(this.getContainerEl(), "click", function(v){
16482 if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16490 * Resets CheckboxCellEditor UI to initial state.
16492 * @method resetForm
16494 resetForm : function() {
16495 // Normalize to array
16496 var originalValues = lang.isArray(this.value) ? this.value : [this.value];
16498 // Match checks to value
16499 for(var i=0, j=this.checkboxes.length; i<j; i++) {
16500 this.checkboxes[i].checked = false;
16501 for(var k=0, len=originalValues.length; k<len; k++) {
16502 if(this.checkboxes[i].value === originalValues[k]) {
16503 this.checkboxes[i].checked = true;
16510 * Sets focus in CheckboxCellEditor.
16514 focus : function() {
16515 this.checkboxes[0].focus();
16519 * Retrieves input value from CheckboxCellEditor.
16521 * @method getInputValue
16523 getInputValue : function() {
16524 var checkedValues = [];
16525 for(var i=0, j=this.checkboxes.length; i<j; i++) {
16526 if(this.checkboxes[i].checked) {
16527 checkedValues[checkedValues.length] = this.checkboxes[i].value;
16530 return checkedValues;
16535 // Copy static members to CheckboxCellEditor class
16536 lang.augmentObject(widget.CheckboxCellEditor, BCE);
16545 /****************************************************************************/
16546 /****************************************************************************/
16547 /****************************************************************************/
16550 * The DataCellEditor class provides functionality for inline editing
16551 * DataTable cell data with a YUI Calendar.
16553 * @namespace YAHOO.widget
16554 * @class DateCellEditor
16555 * @extends YAHOO.widget.BaseCellEditor
16557 * @param oConfigs {Object} (Optional) Object literal of configs.
16559 widget.DateCellEditor = function(oConfigs) {
16560 this._sId = "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16561 widget.DateCellEditor.superclass.constructor.call(this, "date", oConfigs);
16564 // CheckboxCellEditor extends BaseCellEditor
16565 lang.extend(widget.DateCellEditor, BCE, {
16567 /////////////////////////////////////////////////////////////////////////////
16569 // DateCellEditor public properties
16571 /////////////////////////////////////////////////////////////////////////////
16573 * Reference to Calendar instance.
16575 * @property calendar
16576 * @type YAHOO.widget.Calendar
16581 * Configs for the calendar instance, to be passed to Calendar constructor.
16583 * @property calendarOptions
16586 calendarOptions : null,
16591 * @property defaultValue
16593 * @default new Date()
16595 defaultValue : new Date(),
16598 /////////////////////////////////////////////////////////////////////////////
16600 // DateCellEditor public methods
16602 /////////////////////////////////////////////////////////////////////////////
16605 * Render a Calendar.
16607 * @method renderForm
16609 renderForm : function() {
16611 if(YAHOO.widget.Calendar) {
16612 var calContainer = this.getContainerEl().appendChild(document.createElement("div"));
16613 calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor
16615 new YAHOO.widget.Calendar(this.getId() + "-date",
16616 calContainer.id, this.calendarOptions);
16618 calContainer.style.cssFloat = "none";
16621 var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div"));
16622 calFloatClearer.style.clear = "both";
16625 this.calendar = calendar;
16627 if(this.disableBtns) {
16628 this.handleDisabledBtns();
16632 YAHOO.log("Could not find YUI Calendar", "error", this.toString());
16638 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16639 * to save input without them.
16641 * @method handleDisabledBtns
16643 handleDisabledBtns : function() {
16644 this.calendar.selectEvent.subscribe(function(v){
16651 * Resets DateCellEditor UI to initial state.
16653 * @method resetForm
16655 resetForm : function() {
16656 var value = this.value;
16657 var selectedValue = (value.getMonth()+1)+"/"+value.getDate()+"/"+value.getFullYear();
16658 this.calendar.cfg.setProperty("selected",selectedValue,false);
16659 this.calendar.render();
16663 * Sets focus in DateCellEditor.
16667 focus : function() {
16668 // To be impmlemented by subclass
16672 * Retrieves input value from DateCellEditor.
16674 * @method getInputValue
16676 getInputValue : function() {
16677 return this.calendar.getSelectedDates()[0];
16682 // Copy static members to DateCellEditor class
16683 lang.augmentObject(widget.DateCellEditor, BCE);
16693 /****************************************************************************/
16694 /****************************************************************************/
16695 /****************************************************************************/
16698 * The DropdownCellEditor class provides functionality for inline editing
16699 * DataTable cell data a SELECT element.
16701 * @namespace YAHOO.widget
16702 * @class DropdownCellEditor
16703 * @extends YAHOO.widget.BaseCellEditor
16705 * @param oConfigs {Object} (Optional) Object literal of configs.
16707 widget.DropdownCellEditor = function(oConfigs) {
16708 this._sId = "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16709 widget.DropdownCellEditor.superclass.constructor.call(this, "dropdown", oConfigs);
16712 // DropdownCellEditor extends BaseCellEditor
16713 lang.extend(widget.DropdownCellEditor, BCE, {
16715 /////////////////////////////////////////////////////////////////////////////
16717 // DropdownCellEditor public properties
16719 /////////////////////////////////////////////////////////////////////////////
16721 * Array of dropdown values. Can either be a simple array (e.g.,
16722 * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g.,
16723 * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
16724 * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]).
16726 * @property dropdownOptions
16727 * @type String[] | Object[]
16729 dropdownOptions : null,
16732 * Reference to Dropdown element.
16734 * @property dropdown
16735 * @type HTMLElement
16740 * Enables multi-select.
16742 * @property multiple
16748 * Specifies number of visible options.
16755 /////////////////////////////////////////////////////////////////////////////
16757 // DropdownCellEditor public methods
16759 /////////////////////////////////////////////////////////////////////////////
16762 * Render a form with select element.
16764 * @method renderForm
16766 renderForm : function() {
16767 var elDropdown = this.getContainerEl().appendChild(document.createElement("select"));
16768 elDropdown.style.zoom = 1;
16769 if(this.multiple) {
16770 elDropdown.multiple = "multiple";
16772 if(lang.isNumber(this.size)) {
16773 elDropdown.size = this.size;
16775 this.dropdown = elDropdown;
16777 if(lang.isArray(this.dropdownOptions)) {
16778 var dropdownOption, elOption;
16779 for(var i=0, j=this.dropdownOptions.length; i<j; i++) {
16780 dropdownOption = this.dropdownOptions[i];
16781 elOption = document.createElement("option");
16782 elOption.value = (lang.isValue(dropdownOption.value)) ?
16783 dropdownOption.value : dropdownOption;
16784 elOption.innerHTML = (lang.isValue(dropdownOption.label)) ?
16785 dropdownOption.label : dropdownOption;
16786 elOption = elDropdown.appendChild(elOption);
16789 if(this.disableBtns) {
16790 this.handleDisabledBtns();
16796 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16797 * to save input without them.
16799 * @method handleDisabledBtns
16801 handleDisabledBtns : function() {
16802 // Save on blur for multi-select
16803 if(this.multiple) {
16804 Ev.addListener(this.dropdown, "blur", function(v){
16809 // Save on change for single-select
16811 Ev.addListener(this.dropdown, "change", function(v){
16819 * Resets DropdownCellEditor UI to initial state.
16821 * @method resetForm
16823 resetForm : function() {
16824 var allOptions = this.dropdown.options,
16825 i=0, j=allOptions.length;
16827 // Look for multi-select selections
16828 if(lang.isArray(this.value)) {
16829 var allValues = this.value,
16830 m=0, n=allValues.length,
16832 // Reset all selections and stash options in a value hash
16834 allOptions[i].selected = false;
16835 hash[allOptions[i].value] = allOptions[i];
16838 if(hash[allValues[m]]) {
16839 hash[allValues[m]].selected = true;
16843 // Only need to look for a single selection
16846 if(this.value === allOptions[i].value) {
16847 allOptions[i].selected = true;
16854 * Sets focus in DropdownCellEditor.
16858 focus : function() {
16859 this.getDataTable()._focusEl(this.dropdown);
16863 * Retrieves input value from DropdownCellEditor.
16865 * @method getInputValue
16867 getInputValue : function() {
16868 var allOptions = this.dropdown.options;
16870 // Look for multiple selections
16871 if(this.multiple) {
16873 i=0, j=allOptions.length;
16875 if(allOptions[i].selected) {
16876 values.push(allOptions[i].value);
16881 // Only need to look for single selection
16883 return allOptions[allOptions.selectedIndex].value;
16889 // Copy static members to DropdownCellEditor class
16890 lang.augmentObject(widget.DropdownCellEditor, BCE);
16897 /****************************************************************************/
16898 /****************************************************************************/
16899 /****************************************************************************/
16902 * The RadioCellEditor class provides functionality for inline editing
16903 * DataTable cell data with radio buttons.
16905 * @namespace YAHOO.widget
16906 * @class RadioCellEditor
16907 * @extends YAHOO.widget.BaseCellEditor
16909 * @param oConfigs {Object} (Optional) Object literal of configs.
16911 widget.RadioCellEditor = function(oConfigs) {
16912 this._sId = "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16913 widget.RadioCellEditor.superclass.constructor.call(this, "radio", oConfigs);
16916 // RadioCellEditor extends BaseCellEditor
16917 lang.extend(widget.RadioCellEditor, BCE, {
16919 /////////////////////////////////////////////////////////////////////////////
16921 // RadioCellEditor public properties
16923 /////////////////////////////////////////////////////////////////////////////
16925 * Reference to radio elements.
16928 * @type HTMLElement[]
16933 * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"])
16934 * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1},
16935 * {label:"maybe", value:0}]).
16937 * @property radioOptions
16938 * @type String[] | Object[]
16940 radioOptions : null,
16942 /////////////////////////////////////////////////////////////////////////////
16944 // RadioCellEditor public methods
16946 /////////////////////////////////////////////////////////////////////////////
16949 * Render a form with input(s) type=radio.
16951 * @method renderForm
16953 renderForm : function() {
16954 if(lang.isArray(this.radioOptions)) {
16955 var radioOption, radioValue, radioId, elLabel;
16957 // Create the radio buttons in an IE-friendly way
16958 for(var i=0, len=this.radioOptions.length; i<len; i++) {
16959 radioOption = this.radioOptions[i];
16960 radioValue = lang.isValue(radioOption.value) ?
16961 radioOption.value : radioOption;
16962 radioId = this.getId() + "-radio" + i;
16963 this.getContainerEl().innerHTML += "<input type=\"radio\"" +
16964 " name=\"" + this.getId() + "\"" +
16965 " value=\"" + radioValue + "\"" +
16966 " id=\"" + radioId + "\" />"; // Needed for label
16968 // Create the labels in an IE-friendly way
16969 elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16970 elLabel.htmlFor = radioId;
16971 elLabel.innerHTML = (lang.isValue(radioOption.label)) ?
16972 radioOption.label : radioOption;
16975 // Store the reference to the checkbox elements
16976 var allRadios = [],
16978 for(var j=0; j<len; j++) {
16979 elRadio = this.getContainerEl().childNodes[j*2];
16980 allRadios[allRadios.length] = elRadio;
16982 this.radios = allRadios;
16984 if(this.disableBtns) {
16985 this.handleDisabledBtns();
16989 YAHOO.log("Could not find radioOptions", "error", this.toString());
16994 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16995 * to save input without them.
16997 * @method handleDisabledBtns
16999 handleDisabledBtns : function() {
17000 Ev.addListener(this.getContainerEl(), "click", function(v){
17001 if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
17009 * Resets RadioCellEditor UI to initial state.
17011 * @method resetForm
17013 resetForm : function() {
17014 for(var i=0, j=this.radios.length; i<j; i++) {
17015 var elRadio = this.radios[i];
17016 if(this.value === elRadio.value) {
17017 elRadio.checked = true;
17024 * Sets focus in RadioCellEditor.
17028 focus : function() {
17029 for(var i=0, j=this.radios.length; i<j; i++) {
17030 if(this.radios[i].checked) {
17031 this.radios[i].focus();
17038 * Retrieves input value from RadioCellEditor.
17040 * @method getInputValue
17042 getInputValue : function() {
17043 for(var i=0, j=this.radios.length; i<j; i++) {
17044 if(this.radios[i].checked) {
17045 return this.radios[i].value;
17052 // Copy static members to RadioCellEditor class
17053 lang.augmentObject(widget.RadioCellEditor, BCE);
17060 /****************************************************************************/
17061 /****************************************************************************/
17062 /****************************************************************************/
17065 * The TextareaCellEditor class provides functionality for inline editing
17066 * DataTable cell data with a TEXTAREA element.
17068 * @namespace YAHOO.widget
17069 * @class TextareaCellEditor
17070 * @extends YAHOO.widget.BaseCellEditor
17072 * @param oConfigs {Object} (Optional) Object literal of configs.
17074 widget.TextareaCellEditor = function(oConfigs) {
17075 this._sId = "yui-textareaceditor" + YAHOO.widget.BaseCellEditor._nCount++;
17076 widget.TextareaCellEditor.superclass.constructor.call(this, "textarea", oConfigs);
17079 // TextareaCellEditor extends BaseCellEditor
17080 lang.extend(widget.TextareaCellEditor, BCE, {
17082 /////////////////////////////////////////////////////////////////////////////
17084 // TextareaCellEditor public properties
17086 /////////////////////////////////////////////////////////////////////////////
17088 * Reference to textarea element.
17090 * @property textarea
17091 * @type HTMLElement
17096 /////////////////////////////////////////////////////////////////////////////
17098 // TextareaCellEditor public methods
17100 /////////////////////////////////////////////////////////////////////////////
17103 * Render a form with textarea.
17105 * @method renderForm
17107 renderForm : function() {
17108 var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea"));
17109 this.textarea = elTextarea;
17111 if(this.disableBtns) {
17112 this.handleDisabledBtns();
17117 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
17118 * to save input without them.
17120 * @method handleDisabledBtns
17122 handleDisabledBtns : function() {
17123 Ev.addListener(this.textarea, "blur", function(v){
17130 * Moves TextareaCellEditor UI to a cell.
17134 move : function() {
17135 this.textarea.style.width = this.getTdEl().offsetWidth + "px";
17136 this.textarea.style.height = "3em";
17137 YAHOO.widget.TextareaCellEditor.superclass.move.call(this);
17141 * Resets TextareaCellEditor UI to initial state.
17143 * @method resetForm
17145 resetForm : function() {
17146 this.textarea.value = this.value;
17150 * Sets focus in TextareaCellEditor.
17154 focus : function() {
17155 // Bug 2303181, Bug 2263600
17156 this.getDataTable()._focusEl(this.textarea);
17157 this.textarea.select();
17161 * Retrieves input value from TextareaCellEditor.
17163 * @method getInputValue
17165 getInputValue : function() {
17166 return this.textarea.value;
17171 // Copy static members to TextareaCellEditor class
17172 lang.augmentObject(widget.TextareaCellEditor, BCE);
17182 /****************************************************************************/
17183 /****************************************************************************/
17184 /****************************************************************************/
17187 * The TextboxCellEditor class provides functionality for inline editing
17188 * DataTable cell data with an INPUT TYPE=TEXT element.
17190 * @namespace YAHOO.widget
17191 * @class TextboxCellEditor
17192 * @extends YAHOO.widget.BaseCellEditor
17194 * @param oConfigs {Object} (Optional) Object literal of configs.
17196 widget.TextboxCellEditor = function(oConfigs) {
17197 this._sId = "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
17198 widget.TextboxCellEditor.superclass.constructor.call(this, "textbox", oConfigs);
17201 // TextboxCellEditor extends BaseCellEditor
17202 lang.extend(widget.TextboxCellEditor, BCE, {
17204 /////////////////////////////////////////////////////////////////////////////
17206 // TextboxCellEditor public properties
17208 /////////////////////////////////////////////////////////////////////////////
17210 * Reference to the textbox element.
17212 * @property textbox
17216 /////////////////////////////////////////////////////////////////////////////
17218 // TextboxCellEditor public methods
17220 /////////////////////////////////////////////////////////////////////////////
17223 * Render a form with input type=text.
17225 * @method renderForm
17227 renderForm : function() {
17229 // Bug 1802582: SF3/Mac needs a form element wrapping the input
17230 if(ua.webkit>420) {
17231 elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));
17234 elTextbox = this.getContainerEl().appendChild(document.createElement("input"));
17236 elTextbox.type = "text";
17237 this.textbox = elTextbox;
17239 // Save on enter by default
17240 // Bug: 1802582 Set up a listener on each textbox to track on keypress
17241 // since SF/OP can't preventDefault on keydown
17242 Ev.addListener(elTextbox, "keypress", function(v){
17243 if((v.keyCode === 13)) {
17244 // Prevent form submit
17245 YAHOO.util.Event.preventDefault(v);
17250 if(this.disableBtns) {
17251 // By default this is no-op since enter saves by default
17252 this.handleDisabledBtns();
17257 * Moves TextboxCellEditor UI to a cell.
17261 move : function() {
17262 this.textbox.style.width = this.getTdEl().offsetWidth + "px";
17263 widget.TextboxCellEditor.superclass.move.call(this);
17267 * Resets TextboxCellEditor UI to initial state.
17269 * @method resetForm
17271 resetForm : function() {
17272 this.textbox.value = lang.isValue(this.value) ? this.value.toString() : "";
17276 * Sets focus in TextboxCellEditor.
17280 focus : function() {
17281 // Bug 2303181, Bug 2263600
17282 this.getDataTable()._focusEl(this.textbox);
17283 this.textbox.select();
17287 * Returns new value for TextboxCellEditor.
17289 * @method getInputValue
17291 getInputValue : function() {
17292 return this.textbox.value;
17297 // Copy static members to TextboxCellEditor class
17298 lang.augmentObject(widget.TextboxCellEditor, BCE);
17306 /////////////////////////////////////////////////////////////////////////////
17308 // DataTable extension
17310 /////////////////////////////////////////////////////////////////////////////
17313 * CellEditor subclasses.
17314 * @property DataTable.Editors
17319 checkbox : widget.CheckboxCellEditor,
17320 "date" : widget.DateCellEditor,
17321 dropdown : widget.DropdownCellEditor,
17322 radio : widget.RadioCellEditor,
17323 textarea : widget.TextareaCellEditor,
17324 textbox : widget.TextboxCellEditor
17327 /****************************************************************************/
17328 /****************************************************************************/
17329 /****************************************************************************/
17332 * Factory class for instantiating a BaseCellEditor subclass.
17334 * @namespace YAHOO.widget
17335 * @class CellEditor
17336 * @extends YAHOO.widget.BaseCellEditor
17338 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
17339 * @param oConfigs {Object} (Optional) Object literal of configs.
17341 widget.CellEditor = function(sType, oConfigs) {
17342 // Point to one of the subclasses
17343 if(sType && DT.Editors[sType]) {
17344 lang.augmentObject(BCE, DT.Editors[sType]);
17345 return new DT.Editors[sType](oConfigs);
17348 return new BCE(null, oConfigs);
17352 var CE = widget.CellEditor;
17354 // Copy static members to CellEditor class
17355 lang.augmentObject(CE, BCE);
17360 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.8.0r4", build: "2449"});