2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 * Mechanism to execute a series of callbacks in a non-blocking queue. Each callback is executed via setTimout unless configured with a negative timeout, in which case it is run in blocking mode in the same execution thread as the previous callback. Callbacks can be function references or object literals with the following keys:
10 * <li><code>method</code> - {Function} REQUIRED the callback function.</li>
11 * <li><code>scope</code> - {Object} the scope from which to execute the callback. Default is the global window scope.</li>
12 * <li><code>argument</code> - {Array} parameters to be passed to method as individual arguments.</li>
13 * <li><code>timeout</code> - {number} millisecond delay to wait after previous callback completion before executing this callback. Negative values cause immediate blocking execution. Default 0.</li>
14 * <li><code>until</code> - {Function} boolean function executed before each iteration. Return true to indicate completion and proceed to the next callback.</li>
15 * <li><code>iterations</code> - {Number} number of times to execute the callback before proceeding to the next callback in the chain. Incompatible with <code>until</code>.</li>
18 * @namespace YAHOO.util
21 * @param callback* {Function|Object} Any number of callbacks to initialize the queue
23 YAHOO.util.Chain = function () {
30 this.q = [].slice.call(arguments);
33 * Event fired when the callback queue is emptied via execution (not via
34 * a call to chain.stop().
37 this.createEvent('end');
40 YAHOO.util.Chain.prototype = {
42 * Timeout id used to pause or stop execution and indicate the execution state of the Chain. 0 indicates paused or stopped, -1 indicates blocking execution, and any positive number indicates non-blocking execution.
50 * Begin executing the chain, or resume execution from the last paused position.
52 * @return {Chain} the Chain instance
55 // Grab the first callback in the queue
59 // If there is no callback in the queue or the Chain is currently
60 // in an execution mode, return
62 this.fireEvent('end');
70 if (typeof fn === 'function') {
71 var o = c.scope || {},
72 args = c.argument || [],
76 if (!(args instanceof Array)) {
80 // Execute immediately if the callback timeout is negative.
85 // Execute the callback from scope, with argument
88 } else if (c.iterations) {
89 for (;c.iterations-- > 0;) {
99 // If the until condition is set, check if we're done
102 // Shift this callback from the queue and execute the next
107 // Otherwise if either iterations is not set or we're
108 // executing the last iteration, shift callback from the queue
109 } else if (!c.iterations || !--c.iterations) {
113 // Otherwise set to execute after the configured timeout
114 this.id = setTimeout(function () {
115 // Execute the callback from scope, with argument
117 // Check if the Chain was not paused from inside the callback
119 // Indicate ready to run state
121 // Start the fun all over again
132 * Add a callback to the end of the queue
134 * @param c {Function|Object} the callback function ref or object literal
135 * @return {Chain} the Chain instance
143 * Pause the execution of the Chain after the current execution of the
144 * current callback completes. If called interstitially, clears the
145 * timeout for the pending callback. Paused Chains can be restarted with
148 * @return {Chain} the Chain instance
151 clearTimeout(this.id);
157 * Stop and clear the Chain's queue after the current execution of the
158 * current callback completes.
160 * @return {Chain} the Chain instance
168 YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);
170 /****************************************************************************/
171 /****************************************************************************/
172 /****************************************************************************/
175 * The ColumnSet class defines and manages a DataTable's Columns,
176 * including nested hierarchies and access to individual Column instances.
178 * @namespace YAHOO.widget
180 * @uses YAHOO.util.EventProvider
182 * @param aDefinitions {Object[]} Array of object literals that define cells in
185 YAHOO.widget.ColumnSet = function(aDefinitions) {
186 this._sId = "yui-cs" + YAHOO.widget.ColumnSet._nCount;
188 // First clone the defs
189 aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions);
190 this._init(aDefinitions);
192 YAHOO.widget.ColumnSet._nCount++;
193 YAHOO.log("ColumnSet initialized", "info", this.toString());
196 /////////////////////////////////////////////////////////////////////////////
198 // Private member variables
200 /////////////////////////////////////////////////////////////////////////////
203 * Internal class variable to index multiple ColumnSet instances.
205 * @property ColumnSet._nCount
210 YAHOO.widget.ColumnSet._nCount = 0;
212 YAHOO.widget.ColumnSet.prototype = {
214 * Unique instance name.
223 * Array of object literal Column definitions passed to the constructor.
225 * @property _aDefinitions
229 _aDefinitions : null,
231 /////////////////////////////////////////////////////////////////////////////
233 // Public member variables
235 /////////////////////////////////////////////////////////////////////////////
238 * Top-down tree representation of Column hierarchy.
241 * @type YAHOO.widget.Column[]
246 * Flattened representation of all Columns.
249 * @type YAHOO.widget.Column[]
255 * Array of Columns that map one-to-one to a table column.
258 * @type YAHOO.widget.Column[]
264 * ID index of nested parent hierarchies for HEADERS accessibility attribute.
272 /////////////////////////////////////////////////////////////////////////////
276 /////////////////////////////////////////////////////////////////////////////
279 * Initializes ColumnSet instance with data from Column definitions.
282 * @param aDefinitions {Object[]} Array of object literals that define cells in
287 _init : function(aDefinitions) {
288 // DOM tree representation of all Columns
290 // Flat representation of all Columns
292 // Flat representation of only Columns that are meant to display data
294 // Array of HEADERS attribute values for all keys in the "keys" array
297 // Tracks current node list depth being tracked
300 // Internal recursive function to define Column instances
301 var parseColumns = function(nodeList, parent) {
305 // Create corresponding tree node if not already there for this depth
306 if(!tree[nodeDepth]) {
307 tree[nodeDepth] = [];
311 // Parse each node at this depth for attributes and any children
312 for(var j=0; j<nodeList.length; j++) {
313 var currentNode = nodeList[j];
315 // Instantiate a new Column for each node
316 var oColumn = new YAHOO.widget.Column(currentNode);
318 // Cross-reference Column ID back to the original object literal definition
319 currentNode.yuiColumnId = oColumn._sId;
321 // Add the new Column to the flat list
324 // Assign its parent as an attribute, if applicable
326 oColumn._oParent = parent;
329 // The Column has descendants
330 if(YAHOO.lang.isArray(currentNode.children)) {
331 oColumn.children = currentNode.children;
333 // Determine COLSPAN value for this Column
334 var terminalChildNodes = 0;
335 var countTerminalChildNodes = function(ancestor) {
336 var descendants = ancestor.children;
337 // Drill down each branch and count terminal nodes
338 for(var k=0; k<descendants.length; k++) {
339 // Keep drilling down
340 if(YAHOO.lang.isArray(descendants[k].children)) {
341 countTerminalChildNodes(descendants[k]);
343 // Reached branch terminus
345 terminalChildNodes++;
349 countTerminalChildNodes(currentNode);
350 oColumn._nColspan = terminalChildNodes;
352 // Cascade certain properties to children if not defined on their own
353 var currentChildren = currentNode.children;
354 for(var k=0; k<currentChildren.length; k++) {
355 var child = currentChildren[k];
356 if(oColumn.className && (child.className === undefined)) {
357 child.className = oColumn.className;
359 if(oColumn.editor && (child.editor === undefined)) {
360 child.editor = oColumn.editor;
363 if(oColumn.editorOptions && (child.editorOptions === undefined)) {
364 child.editorOptions = oColumn.editorOptions;
366 if(oColumn.formatter && (child.formatter === undefined)) {
367 child.formatter = oColumn.formatter;
369 if(oColumn.resizeable && (child.resizeable === undefined)) {
370 child.resizeable = oColumn.resizeable;
372 if(oColumn.sortable && (child.sortable === undefined)) {
373 child.sortable = oColumn.sortable;
378 if(oColumn.width && (child.width === undefined)) {
379 child.width = oColumn.width;
381 if(oColumn.minWidth && (child.minWidth === undefined)) {
382 child.minWidth = oColumn.minWidth;
384 if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) {
385 child.maxAutoWidth = oColumn.maxAutoWidth;
387 // Backward compatibility
388 if(oColumn.type && (child.type === undefined)) {
389 child.type = oColumn.type;
391 if(oColumn.type && !oColumn.formatter) {
392 YAHOO.log("The property type has been" +
393 " deprecated in favor of formatter", "warn", oColumn.toString());
394 oColumn.formatter = oColumn.type;
396 if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
397 YAHOO.log("The property text has been" +
398 " deprecated in favor of label", "warn", oColumn.toString());
399 oColumn.label = oColumn.text;
402 YAHOO.log("The property parser is no longer supported",
403 "warn", this.toString());
405 if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
406 (oColumn.sortOptions.descFunction))) {
407 YAHOO.log("The properties sortOptions.ascFunction and " +
408 " sortOptions.descFunction have been deprecated in favor " +
409 " of sortOptions.sortFunction", "warn", oColumn.toString());
413 // The children themselves must also be parsed for Column instances
414 if(!tree[nodeDepth+1]) {
415 tree[nodeDepth+1] = [];
417 parseColumns(currentChildren, oColumn);
419 // This Column does not have any children
421 oColumn._nKeyIndex = keys.length;
422 oColumn._nColspan = 1;
426 // Add the Column to the top-down tree
427 tree[nodeDepth].push(oColumn);
432 // Parse out Column instances from the array of object literals
433 if(YAHOO.lang.isArray(aDefinitions)) {
434 parseColumns(aDefinitions);
437 this._aDefinitions = aDefinitions;
440 YAHOO.log("Could not initialize ColumnSet due to invalid definitions","error");
446 // Determine ROWSPAN value for each Column in the tree
447 var parseTreeForRowspan = function(tree) {
452 // Calculate the max depth of descendants for this row
453 var countMaxRowDepth = function(row, tmpRowDepth) {
454 tmpRowDepth = tmpRowDepth || 1;
456 for(var n=0; n<row.length; n++) {
458 // Column has children, so keep counting
459 if(YAHOO.lang.isArray(col.children)) {
461 countMaxRowDepth(col.children, tmpRowDepth);
464 // No children, is it the max depth?
466 if(tmpRowDepth > maxRowDepth) {
467 maxRowDepth = tmpRowDepth;
474 // Count max row depth for each row
475 for(var m=0; m<tree.length; m++) {
476 currentRow = tree[m];
477 countMaxRowDepth(currentRow);
479 // Assign the right ROWSPAN values to each Column in the row
480 for(var p=0; p<currentRow.length; p++) {
481 currentColumn = currentRow[p];
482 if(!YAHOO.lang.isArray(currentColumn.children)) {
483 currentColumn._nRowspan = maxRowDepth;
486 currentColumn._nRowspan = 1;
490 // Reset counter for next row
494 parseTreeForRowspan(tree);
496 // Store tree index values
497 for(i=0; i<tree[0].length; i++) {
498 tree[0][i]._nTreeIndex = i;
501 // Store header relationships in an array for HEADERS attribute
502 var recurseAncestorsForHeaders = function(i, oColumn) {
503 headers[i].push(oColumn.getSanitizedKey());
504 if(oColumn._oParent) {
505 recurseAncestorsForHeaders(i, oColumn._oParent);
508 for(i=0; i<keys.length; i++) {
510 recurseAncestorsForHeaders(i, keys[i]);
511 headers[i] = headers[i].reverse();
514 // Save to the ColumnSet instance
518 this.headers = headers;
521 /////////////////////////////////////////////////////////////////////////////
525 /////////////////////////////////////////////////////////////////////////////
528 * Returns unique name of the ColumnSet instance.
531 * @return {String} Unique name of the ColumnSet instance.
539 * ColumnSet instance name, for logging.
542 * @return {String} Unique name of the ColumnSet instance.
545 toString : function() {
546 return "ColumnSet instance " + this._sId;
550 * Public accessor to the definitions array.
552 * @method getDefinitions
553 * @return {Object[]} Array of object literal Column definitions.
556 getDefinitions : function() {
557 var aDefinitions = this._aDefinitions;
559 // Internal recursive function to define Column instances
560 var parseColumns = function(nodeList, oSelf) {
561 // Parse each node at this depth for attributes and any children
562 for(var j=0; j<nodeList.length; j++) {
563 var currentNode = nodeList[j];
565 // Get the Column for each node
566 var oColumn = oSelf.getColumnById(currentNode.yuiColumnId);
569 // Update the current values
570 var oDefinition = oColumn.getDefinition();
571 for(var name in oDefinition) {
572 if(YAHOO.lang.hasOwnProperty(oDefinition, name)) {
573 currentNode[name] = oDefinition[name];
578 // The Column has descendants
579 if(YAHOO.lang.isArray(currentNode.children)) {
580 // The children themselves must also be parsed for Column instances
581 parseColumns(currentNode.children, oSelf);
586 parseColumns(aDefinitions, this);
587 this._aDefinitions = aDefinitions;
592 * Returns Column instance with given ID.
594 * @method getColumnById
595 * @param column {String} Column ID.
596 * @return {YAHOO.widget.Column} Column instance.
599 getColumnById : function(column) {
600 if(YAHOO.lang.isString(column)) {
601 var allColumns = this.flat;
602 for(var i=allColumns.length-1; i>-1; i--) {
603 if(allColumns[i]._sId === column) {
604 return allColumns[i];
612 * Returns Column instance with given key or ColumnSet key index.
615 * @param column {String | Number} Column key or ColumnSet key index.
616 * @return {YAHOO.widget.Column} Column instance.
619 getColumn : function(column) {
620 if(YAHOO.lang.isNumber(column) && this.keys[column]) {
621 return this.keys[column];
623 else if(YAHOO.lang.isString(column)) {
624 var allColumns = this.flat;
626 for(var i=0; i<allColumns.length; i++) {
627 if(allColumns[i].key === column) {
628 aColumns.push(allColumns[i]);
631 if(aColumns.length === 1) {
634 else if(aColumns.length > 1) {
642 * Public accessor returns array of given Column's desendants (if any), including itself.
644 * @method getDescendants
645 * @parem {YAHOO.widget.Column} Column instance.
646 * @return {Array} Array including the Column itself and all descendants (if any).
648 getDescendants : function(oColumn) {
650 var allDescendants = [];
653 // Recursive function to loop thru all children
654 var parse = function(oParent) {
655 allDescendants.push(oParent);
656 // This Column has children
657 if(oParent.children) {
658 for(i=0; i<oParent.children.length; i++) {
659 parse(oSelf.getColumn(oParent.children[i].key));
665 return allDescendants;
669 /****************************************************************************/
670 /****************************************************************************/
671 /****************************************************************************/
674 * The Column class defines and manages attributes of DataTable Columns
676 * @namespace YAHOO.widget
679 * @param oConfigs {Object} Object literal of definitions.
681 YAHOO.widget.Column = function(oConfigs) {
682 this._sId = "yui-col" + YAHOO.widget.Column._nCount;
684 // Object literal defines Column attributes
685 if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
686 for(var sConfig in oConfigs) {
688 this[sConfig] = oConfigs[sConfig];
693 // Assign a key if not found
694 if(!YAHOO.lang.isValue(this.key)) {
695 this.key = "yui-dt-col" + YAHOO.widget.Column._nCount;
698 // Assign a field if not found, defaults to key
699 if(!YAHOO.lang.isValue(this.field)) {
700 this.field = this.key;
704 YAHOO.widget.Column._nCount++;
706 // Backward compatibility
707 if(this.width && !YAHOO.lang.isNumber(this.width)) {
709 YAHOO.log("The Column property width must be a number", "warn", this.toString());
711 if(this.editor && YAHOO.lang.isString(this.editor)) {
712 this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions);
713 YAHOO.log("The Column property editor must be an instance of YAHOO.widget.CellEditor", "warn", this.toString());
717 /////////////////////////////////////////////////////////////////////////////
719 // Private member variables
721 /////////////////////////////////////////////////////////////////////////////
723 YAHOO.lang.augmentObject(YAHOO.widget.Column, {
725 * Internal class variable to index multiple Column instances.
727 * @property Column._nCount
734 formatCheckbox : function(elCell, oRecord, oColumn, oData) {
735 YAHOO.log("The method YAHOO.widget.Column.formatCheckbox() has been" +
736 " deprecated in favor of YAHOO.widget.DataTable.formatCheckbox()", "warn",
737 "YAHOO.widget.Column.formatCheckbox");
738 YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
741 formatCurrency : function(elCell, oRecord, oColumn, oData) {
742 YAHOO.log("The method YAHOO.widget.Column.formatCurrency() has been" +
743 " deprecated in favor of YAHOO.widget.DataTable.formatCurrency()", "warn",
744 "YAHOO.widget.Column.formatCurrency");
745 YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
748 formatDate : function(elCell, oRecord, oColumn, oData) {
749 YAHOO.log("The method YAHOO.widget.Column.formatDate() has been" +
750 " deprecated in favor of YAHOO.widget.DataTable.formatDate()", "warn",
751 "YAHOO.widget.Column.formatDate");
752 YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
755 formatEmail : function(elCell, oRecord, oColumn, oData) {
756 YAHOO.log("The method YAHOO.widget.Column.formatEmail() has been" +
757 " deprecated in favor of YAHOO.widget.DataTable.formatEmail()", "warn",
758 "YAHOO.widget.Column.formatEmail");
759 YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
762 formatLink : function(elCell, oRecord, oColumn, oData) {
763 YAHOO.log("The method YAHOO.widget.Column.formatLink() has been" +
764 " deprecated in favor of YAHOO.widget.DataTable.formatLink()", "warn",
765 "YAHOO.widget.Column.formatLink");
766 YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
769 formatNumber : function(elCell, oRecord, oColumn, oData) {
770 YAHOO.log("The method YAHOO.widget.Column.formatNumber() has been" +
771 " deprecated in favor of YAHOO.widget.DataTable.formatNumber()", "warn",
772 "YAHOO.widget.Column.formatNumber");
773 YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
776 formatSelect : function(elCell, oRecord, oColumn, oData) {
777 YAHOO.log("The method YAHOO.widget.Column.formatSelect() has been" +
778 " deprecated in favor of YAHOO.widget.DataTable.formatDropdown()", "warn",
779 "YAHOO.widget.Column.formatSelect");
780 YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
784 YAHOO.widget.Column.prototype = {
786 * Unique String identifier assigned at instantiation.
795 * Reference to Column's current position index within its ColumnSet's keys
796 * array, if applicable. This property only applies to non-nested and bottom-
797 * level child Columns.
799 * @property _nKeyIndex
806 * Reference to Column's current position index within its ColumnSet's tree
807 * array, if applicable. This property only applies to non-nested and top-
808 * level parent Columns.
810 * @property _nTreeIndex
817 * Number of table cells the Column spans.
819 * @property _nColspan
826 * Number of table rows the Column spans.
828 * @property _nRowspan
835 * Column's parent Column instance, or null.
838 * @type YAHOO.widget.Column
844 * The DOM reference to the associated TH element.
853 * The DOM reference to the associated TH element's liner DIV element.
855 * @property _elThLiner
862 * The DOM reference to the associated TH element's label SPAN element.
864 * @property _elThLabel
871 * The DOM reference to the associated resizerelement (if any).
873 * @property _elResizer
880 * Internal width tracker.
889 * For unreg() purposes, a reference to the Column's DragDrop instance.
892 * @type YAHOO.util.DragDrop
898 * For unreg() purposes, a reference to the Column resizer's DragDrop instance.
900 * @property _ddResizer
901 * @type YAHOO.util.DragDrop
906 /////////////////////////////////////////////////////////////////////////////
908 // Public member variables
910 /////////////////////////////////////////////////////////////////////////////
913 * Unique name, required.
921 * Associated database field, or null.
929 * Text or HTML for display as Column's label in the TH element.
937 * Column head cell ABBR for accessibility.
945 * Array of object literals that define children (nested headers) of a Column.
953 * Column width (in pixels).
961 * Minimum Column width (in pixels).
970 * When a width is not defined for a Column, maxAutoWidth defines an upper
971 * limit that the Column should be auto-sized to. If resizeable is enabled,
972 * users may still resize to a greater width. Most useful for Columns intended
973 * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very
974 * wide Columns from disrupting visual readability by inducing truncation.
976 * @property maxAutoWidth
983 * True if Column is in hidden state.
992 * True if Column is in selected state.
1001 * Custom CSS class or array of classes to be applied to every cell in the Column.
1003 * @property className
1004 * @type String || String[]
1009 * Defines a format function.
1011 * @property formatter
1012 * @type String || HTMLFunction
1017 * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
1019 * @property currencyOptions
1023 currencyOptions : null,
1026 * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
1028 * @property dateOptions
1035 * A CellEditor instance, otherwise Column is not editable.
1038 * @type YAHOO.widget.CellEditor
1043 * True if Column is resizeable, false otherwise. The Drag & Drop Utility is
1044 * required to enable this feature. Only bottom-level and non-nested Columns are
1047 * @property resizeable
1054 * True if Column is sortable, false otherwise.
1056 * @property sortable
1063 * @property sortOptions.defaultOrder
1064 * @deprecated Use sortOptions.defaultDir.
1067 * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
1069 * @property sortOptions.defaultDir
1074 * Custom field to sort on.
1076 * @property sortOptions.field
1081 * Custom sort handler.
1083 * @property sortOptions.sortFunction
1103 /////////////////////////////////////////////////////////////////////////////
1107 /////////////////////////////////////////////////////////////////////////////
1110 * Returns unique ID string.
1113 * @return {String} Unique ID string.
1115 getId : function() {
1120 * Column instance name, for logging.
1123 * @return {String} Column's unique name.
1125 toString : function() {
1126 return "Column instance " + this._sId;
1130 * Returns object literal definition.
1132 * @method getDefinition
1133 * @return {Object} Object literal definition.
1135 getDefinition : function() {
1136 var oDefinition = {};
1138 // Update the definition
1139 oDefinition.abbr = this.abbr;
1140 oDefinition.className = this.className;
1141 oDefinition.editor = this.editor;
1142 oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
1143 oDefinition.field = this.field;
1144 oDefinition.formatter = this.formatter;
1145 oDefinition.hidden = this.hidden;
1146 oDefinition.key = this.key;
1147 oDefinition.label = this.label;
1148 oDefinition.minWidth = this.minWidth;
1149 oDefinition.maxAutoWidth = this.maxAutoWidth;
1150 oDefinition.resizeable = this.resizeable;
1151 oDefinition.selected = this.selected;
1152 oDefinition.sortable = this.sortable;
1153 oDefinition.sortOptions = this.sortOptions;
1154 oDefinition.width = this.width;
1160 * Returns unique Column key.
1163 * @return {String} Column key.
1165 getKey : function() {
1173 * @return {String} Column field.
1175 getField : function() {
1180 * Returns Column key which has been sanitized for DOM (class and ID) usage
1181 * starts with letter, contains only letters, numbers, hyphen, or period.
1183 * @method getSanitizedKey
1184 * @return {String} Sanitized Column key.
1186 getSanitizedKey : function() {
1187 return this.getKey().replace(/[^\w\-]/g,"");
1191 * Public accessor returns Column's current position index within its
1192 * ColumnSet's keys array, if applicable. Only non-nested and bottom-level
1193 * child Columns will return a value.
1195 * @method getKeyIndex
1196 * @return {Number} Position index, or null.
1198 getKeyIndex : function() {
1199 return this._nKeyIndex;
1203 * Public accessor returns Column's current position index within its
1204 * ColumnSet's tree array, if applicable. Only non-nested and top-level parent
1205 * Columns will return a value;
1207 * @method getTreeIndex
1208 * @return {Number} Position index, or null.
1210 getTreeIndex : function() {
1211 return this._nTreeIndex;
1215 * Public accessor returns Column's parent instance if any, or null otherwise.
1218 * @return {YAHOO.widget.Column} Column's parent instance.
1220 getParent : function() {
1221 return this._oParent;
1225 * Public accessor returns Column's calculated COLSPAN value.
1227 * @method getColspan
1228 * @return {Number} Column's COLSPAN value.
1230 getColspan : function() {
1231 return this._nColspan;
1233 // Backward compatibility
1234 getColSpan : function() {
1235 YAHOO.log("The method getColSpan() has been" +
1236 " deprecated in favor of getColspan()", "warn", this.toString());
1237 return this.getColspan();
1241 * Public accessor returns Column's calculated ROWSPAN value.
1243 * @method getRowspan
1244 * @return {Number} Column's ROWSPAN value.
1246 getRowspan : function() {
1247 return this._nRowspan;
1251 * Returns DOM reference to the key TH element.
1254 * @return {HTMLElement} TH element.
1256 getThEl : function() {
1261 * Returns DOM reference to the TH's liner DIV element. Introduced since
1262 * resizeable Columns may have an extra resizer liner, making the DIV liner
1263 * not reliably the TH element's first child.
1265 * @method getThLInerEl
1266 * @return {HTMLElement} TH element.
1268 getThLinerEl : function() {
1269 return this._elThLiner;
1273 * Returns DOM reference to the resizer element, or null.
1275 * @method getResizerEl
1276 * @return {HTMLElement} DIV element.
1278 getResizerEl : function() {
1279 return this._elResizer;
1282 // Backward compatibility
1285 * @deprecated Use getThEl
1287 getColEl : function() {
1288 YAHOO.log("The method getColEl() has been" +
1289 " deprecated in favor of getThEl()", "warn",
1291 return this.getThEl();
1293 getIndex : function() {
1294 YAHOO.log("The method getIndex() has been" +
1295 " deprecated in favor of getKeyIndex()", "warn",
1297 return this.getKeyIndex();
1299 format : function() {
1300 YAHOO.log("The method format() has been deprecated in favor of the " +
1301 "DataTable method formatCell()", "error", this.toString());
1305 /****************************************************************************/
1306 /****************************************************************************/
1307 /****************************************************************************/
1310 * Sort static utility to support Column sorting.
1312 * @namespace YAHOO.util
1317 /////////////////////////////////////////////////////////////////////////////
1321 /////////////////////////////////////////////////////////////////////////////
1324 * Comparator function for simple case-insensitive string sorting.
1327 * @param a {Object} First sort argument.
1328 * @param b {Object} Second sort argument.
1329 * @param desc {Boolean} True if sort direction is descending, false if
1330 * sort direction is ascending.
1332 compare: function(a, b, desc) {
1333 if((a === null) || (typeof a == "undefined")) {
1334 if((b === null) || (typeof b == "undefined")) {
1341 else if((b === null) || (typeof b == "undefined")) {
1345 if(a.constructor == String) {
1346 a = a.toLowerCase();
1348 if(b.constructor == String) {
1349 b = b.toLowerCase();
1352 return (desc) ? 1 : -1;
1355 return (desc) ? -1 : 1;
1363 /****************************************************************************/
1364 /****************************************************************************/
1365 /****************************************************************************/
1368 * ColumnDD subclasses DragDrop to support rearrangeable Columns.
1370 * @namespace YAHOO.util
1372 * @extends YAHOO.util.DDProxy
1374 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1375 * @param oColumn {YAHOO.widget.Column} Column instance.
1376 * @param elTh {HTMLElement} TH element reference.
1377 * @param elTarget {HTMLElement} Drag target element.
1379 YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
1380 if(oDataTable && oColumn && elTh && elTarget) {
1381 this.datatable = oDataTable;
1382 this.table = oDataTable.getTableEl();
1383 this.column = oColumn;
1384 this.headCell = elTh;
1385 this.pointer = elTarget;
1386 this.newIndex = null;
1388 this.initFrame(); // Needed for DDProxy
1389 this.invalidHandleTypes = {};
1391 // Set top/bottom padding to account for children of nested columns
1392 this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
1394 YAHOO.util.Event.on(window, 'resize', function() {
1395 this.initConstraints();
1399 YAHOO.log("Column dragdrop could not be created","warn",oDataTable.toString());
1403 if(YAHOO.util.DDProxy) {
1404 YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
1405 initConstraints: function() {
1406 //Get the top, right, bottom and left positions
1407 var region = YAHOO.util.Dom.getRegion(this.table),
1408 //Get the element we are working on
1410 //Get the xy position of it
1411 xy = YAHOO.util.Dom.getXY(el),
1412 //Get the width and height
1413 width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
1414 height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
1415 //Set left to x minus left
1416 left = ((xy[0] - region.left) + 15), //Buffer of 15px
1417 //Set right to right minus x minus width
1418 right = ((region.right - xy[0] - width) + 15);
1420 //Set the constraints based on the above calculations
1421 this.setXConstraint(left, right);
1422 this.setYConstraint(10, 10);
1424 _resizeProxy: function() {
1425 this.constructor.superclass._resizeProxy.apply(this, arguments);
1426 var dragEl = this.getDragEl(),
1429 YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
1430 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
1431 var xy = YAHOO.util.Dom.getXY(el);
1432 YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
1434 YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
1435 YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
1436 YAHOO.util.Dom.setXY(this.dragEl, xy);
1438 onMouseDown: function() {
1439 this.initConstraints();
1440 this.resetConstraints();
1442 clickValidator: function(e) {
1443 if(!this.column.hidden) {
1444 var target = YAHOO.util.Event.getTarget(e);
1445 return ( this.isValidHandleChild(target) &&
1446 (this.id == this.handleElId ||
1447 this.DDM.handleWasClicked(target, this.id)) );
1450 onDragOver: function(ev, id) {
1451 // Validate target as a Column
1452 var target = this.datatable.getColumn(id);
1454 // Validate target as a top-level parent
1455 var targetIndex = target.getTreeIndex();
1456 while((targetIndex === null) && target.getParent()) {
1457 target = target.getParent();
1458 targetIndex = target.getTreeIndex();
1460 if(targetIndex !== null) {
1461 // Are we placing to left or right of target?
1462 var elTarget = target.getThEl();
1463 var newIndex = targetIndex;
1464 var mouseX = YAHOO.util.Event.getPageX(ev),
1465 targetX = YAHOO.util.Dom.getX(elTarget),
1466 midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
1467 currentIndex = this.column.getTreeIndex();
1469 if (mouseX < midX) {
1470 YAHOO.util.Dom.setX(this.pointer, targetX);
1472 var targetWidth = parseInt(elTarget.offsetWidth, 10);
1473 YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
1476 if (targetIndex > currentIndex) {
1482 else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
1483 newIndex = this.datatable.getColumnSet().tree[0].length;
1485 this.newIndex = newIndex;
1489 onDragDrop: function() {
1490 this.datatable.reorderColumn(this.column, this.newIndex);
1492 endDrag: function() {
1493 this.newIndex = null;
1494 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
1499 /****************************************************************************/
1500 /****************************************************************************/
1501 /****************************************************************************/
1504 * ColumnResizer subclasses DragDrop to support resizeable Columns.
1506 * @namespace YAHOO.util
1507 * @class ColumnResizer
1508 * @extends YAHOO.util.DDProxy
1510 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
1511 * @param oColumn {YAHOO.widget.Column} Column instance.
1512 * @param elTh {HTMLElement} TH element reference.
1513 * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
1514 * @param elProxy {HTMLElement} Resizer proxy element.
1516 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
1517 if(oDataTable && oColumn && elTh && sHandleId) {
1518 this.datatable = oDataTable;
1519 this.column = oColumn;
1520 this.headCell = elTh;
1521 this.headCellLiner = oColumn.getThLinerEl();
1522 this.resizerLiner = elTh.firstChild;
1523 this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
1524 this.initFrame(); // Needed for proxy
1525 this.resetResizerEl(); // Needed when rowspan > 0
1527 // Set right padding for bug 1858462
1528 this.setPadding(0, 1, 0, 0);
1531 YAHOO.log("Column resizer could not be created","warn",oDataTable.toString());
1536 YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
1537 /////////////////////////////////////////////////////////////////////////////
1541 /////////////////////////////////////////////////////////////////////////////
1543 * Resets resizer element.
1545 * @method resetResizerEl
1547 resetResizerEl : function() {
1548 var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
1549 resizerStyle.left = "auto";
1550 resizerStyle.right = 0;
1551 resizerStyle.top = "auto";
1552 resizerStyle.bottom = 0;
1553 resizerStyle.height = this.headCell.offsetHeight+"px";
1556 /////////////////////////////////////////////////////////////////////////////
1558 // Public DOM event handlers
1560 /////////////////////////////////////////////////////////////////////////////
1563 * Handles mouseup events on the Column resizer.
1566 * @param e {string} The mouseup event
1568 onMouseUp : function(e) {
1569 // Reset height of all resizer els in case TH's have changed height
1570 var allKeys = this.datatable.getColumnSet().keys,
1572 for(var i=0, len=allKeys.length; i<len; i++) {
1574 if(col._ddResizer) {
1575 col._ddResizer.resetResizerEl();
1578 this.resetResizerEl();
1580 var el = this.headCellLiner;
1581 var newWidth = el.offsetWidth -
1582 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) -
1583 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0);
1585 this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth});
1589 * Handles mousedown events on the Column resizer.
1591 * @method onMouseDown
1592 * @param e {string} The mousedown event
1594 onMouseDown : function(e) {
1595 this.startWidth = this.headCellLiner.offsetWidth;
1596 this.startX = YAHOO.util.Event.getXY(e)[0];
1597 this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) +
1598 (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
1602 * Custom clickValidator to ensure Column is not in hidden state.
1604 * @method clickValidator
1608 clickValidator : function(e) {
1609 if(!this.column.hidden) {
1610 var target = YAHOO.util.Event.getTarget(e);
1611 return ( this.isValidHandleChild(target) &&
1612 (this.id == this.handleElId ||
1613 this.DDM.handleWasClicked(target, this.id)) );
1618 * Handles start drag on the Column resizer.
1621 * @param e {string} The drag event
1623 startDrag : function() {
1624 // Shrinks height of all resizer els to not hold open TH els
1625 var allKeys = this.datatable.getColumnSet().keys,
1626 thisKey = this.column.getKeyIndex(),
1628 for(var i=0, len=allKeys.length; i<len; i++) {
1630 if(col._ddResizer) {
1631 YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em";
1637 * Handles drag events on the Column resizer.
1640 * @param e {string} The drag event
1642 onDrag : function(e) {
1643 var newX = YAHOO.util.Event.getXY(e)[0];
1644 if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) {
1645 var offsetX = newX - this.startX;
1646 var newWidth = this.startWidth + offsetX - this.nLinerPadding;
1648 this.datatable.setColumnWidth(this.column, newWidth);
1655 /////////////////////////////////////////////////////////////////////////////
1659 /////////////////////////////////////////////////////////////////////////////
1662 * @property editorOptions
1663 * @deprecated Pass configs directly to CellEditor constructor.
1669 var lang = YAHOO.lang,
1671 widget = YAHOO.widget,
1675 DT = widget.DataTable;
1677 /****************************************************************************/
1678 /****************************************************************************/
1679 /****************************************************************************/
1682 * A RecordSet defines and manages a set of Records.
1684 * @namespace YAHOO.widget
1686 * @param data {Object || Object[]} An object literal or an array of data.
1689 YAHOO.widget.RecordSet = function(data) {
1690 // Internal variables
1691 this._sId = "yui-rs" + widget.RecordSet._nCount;
1692 widget.RecordSet._nCount++;
1697 if(lang.isArray(data)) {
1698 this.addRecords(data);
1700 else if(lang.isObject(data)) {
1701 this.addRecord(data);
1705 YAHOO.log("RecordSet initialized", "info", this.toString());
1708 var RS = widget.RecordSet;
1711 * Internal class variable to name multiple Recordset instances.
1713 * @property RecordSet._nCount
1722 /////////////////////////////////////////////////////////////////////////////
1724 // Private member variables
1726 /////////////////////////////////////////////////////////////////////////////
1728 * Unique String identifier assigned at instantiation.
1737 * Internal counter of how many Records are in the RecordSet.
1742 * @deprecated No longer used
1746 /////////////////////////////////////////////////////////////////////////////
1750 /////////////////////////////////////////////////////////////////////////////
1753 * Adds one Record to the RecordSet at the given index. If index is null,
1754 * then adds the Record to the end of the RecordSet.
1756 * @method _addRecord
1757 * @param oData {Object} An object literal of data.
1758 * @param index {Number} (optional) Position index.
1759 * @return {YAHOO.widget.Record} A Record instance.
1762 _addRecord : function(oData, index) {
1763 var oRecord = new YAHOO.widget.Record(oData);
1765 if(YAHOO.lang.isNumber(index) && (index > -1)) {
1766 this._records.splice(index,0,oRecord);
1769 //index = this.getLength();
1770 //this._records[index] = oRecord;
1771 this._records[this._records.length] = oRecord;
1778 * Sets/replaces one Record to the RecordSet at the given index. Existing
1779 * Records with higher indexes are not shifted. If no index specified, the
1780 * Record is added to the end of the RecordSet.
1782 * @method _setRecord
1783 * @param oData {Object} An object literal of data.
1784 * @param index {Number} (optional) Position index.
1785 * @return {YAHOO.widget.Record} A Record instance.
1788 _setRecord : function(oData, index) {
1789 if (!lang.isNumber(index) || index < 0) {
1790 index = this._records.length;
1792 return (this._records[index] = new widget.Record(oData));
1794 if(lang.isNumber(index) && (index > -1)) {
1795 this._records[index] = oRecord;
1796 if((index+1) > this.getLength()) {
1797 this._length = index+1;
1801 this._records[this.getLength()] = oRecord;
1809 * Deletes Records from the RecordSet at the given index. If range is null,
1810 * then only one Record is deleted.
1812 * @method _deleteRecord
1813 * @param index {Number} Position index.
1814 * @param range {Number} (optional) How many Records to delete
1817 _deleteRecord : function(index, range) {
1818 if(!lang.isNumber(range) || (range < 0)) {
1821 this._records.splice(index, range);
1822 //this._length = this._length - range;
1825 /////////////////////////////////////////////////////////////////////////////
1829 /////////////////////////////////////////////////////////////////////////////
1832 * Returns unique name of the RecordSet instance.
1835 * @return {String} Unique name of the RecordSet instance.
1837 getId : function() {
1842 * Public accessor to the unique name of the RecordSet instance.
1845 * @return {String} Unique name of the RecordSet instance.
1847 toString : function() {
1848 return "RecordSet instance " + this._sId;
1852 * Returns the number of Records held in the RecordSet.
1855 * @return {Number} Number of records in the RecordSet.
1857 getLength : function() {
1858 //return this._length;
1859 return this._records.length;
1863 * Returns Record by ID or RecordSet position index.
1866 * @param record {YAHOO.widget.Record | Number | String} Record instance,
1867 * RecordSet position index, or Record ID.
1868 * @return {YAHOO.widget.Record} Record object.
1870 getRecord : function(record) {
1872 if(record instanceof widget.Record) {
1873 for(i=0; i<this._records.length; i++) {
1874 if(this._records[i] && (this._records[i]._sId === record._sId)) {
1879 else if(lang.isNumber(record)) {
1880 if((record > -1) && (record < this.getLength())) {
1881 return this._records[record];
1884 else if(lang.isString(record)) {
1885 for(i=0; i<this._records.length; i++) {
1886 if(this._records[i] && (this._records[i]._sId === record)) {
1887 return this._records[i];
1891 // Not a valid Record for this RecordSet
1897 * Returns an array of Records from the RecordSet.
1899 * @method getRecords
1900 * @param index {Number} (optional) Recordset position index of which Record to
1902 * @param range {Number} (optional) Number of Records to get.
1903 * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
1904 * length equal to given range. If index is not given, all Records are returned.
1906 getRecords : function(index, range) {
1907 if(!lang.isNumber(index)) {
1908 return this._records;
1910 if(!lang.isNumber(range)) {
1911 return this._records.slice(index);
1913 return this._records.slice(index, index+range);
1917 * Returns a boolean indicating whether Records exist in the RecordSet at the
1918 * specified index range. Returns true if and only if a Record exists at each
1919 * index in the range.
1920 * @method hasRecords
1923 * @return {Boolean} true if all indices are populated in the RecordSet
1925 hasRecords : function (index, range) {
1926 var recs = this.getRecords(index,range);
1927 for (var i = 0; i < range; ++i) {
1928 if (typeof recs[i] === 'undefined') {
1936 * Returns current position index for the given Record.
1938 * @method getRecordIndex
1939 * @param oRecord {YAHOO.widget.Record} Record instance.
1940 * @return {Number} Record's RecordSet position index.
1943 getRecordIndex : function(oRecord) {
1945 for(var i=this._records.length-1; i>-1; i--) {
1946 if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
1956 * Adds one Record to the RecordSet at the given index. If index is null,
1957 * then adds the Record to the end of the RecordSet.
1960 * @param oData {Object} An object literal of data.
1961 * @param index {Number} (optional) Position index.
1962 * @return {YAHOO.widget.Record} A Record instance.
1964 addRecord : function(oData, index) {
1965 if(lang.isObject(oData)) {
1966 var oRecord = this._addRecord(oData, index);
1967 this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
1968 YAHOO.log("Added Record at index " + index +
1969 " with data " + lang.dump(oData), "info", this.toString());
1973 YAHOO.log("Could not add Record with data" +
1974 lang.dump(oData), "info", this.toString());
1980 * Adds multiple Records at once to the RecordSet at the given index with the
1981 * given object literal data. If index is null, then the new Records are
1982 * added to the end of the RecordSet.
1984 * @method addRecords
1985 * @param aData {Object[]} An object literal data or an array of data object literals.
1986 * @param index {Number} (optional) Position index.
1987 * @return {YAHOO.widget.Record[]} An array of Record instances.
1989 addRecords : function(aData, index) {
1990 if(lang.isArray(aData)) {
1991 var newRecords = [],
1994 index = lang.isNumber(index) ? index : this._records.length;
1997 // Can't go backwards bc we need to preserve order
1998 for(i=0,len=aData.length; i<len; ++i) {
1999 if(lang.isObject(aData[i])) {
2000 var record = this._addRecord(aData[i], idx++);
2001 newRecords.push(record);
2004 this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
2005 YAHOO.log("Added " + newRecords.length + " Record(s) at index " + index +
2006 " with data " + lang.dump(aData), "info", this.toString());
2009 else if(lang.isObject(aData)) {
2010 var oRecord = this._addRecord(aData);
2011 this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
2012 YAHOO.log("Added 1 Record at index " + index +
2013 " with data " + lang.dump(aData), "info", this.toString());
2017 YAHOO.log("Could not add Records with data " +
2018 lang.dump(aData), "info", this.toString());
2024 * Sets or replaces one Record to the RecordSet at the given index. Unlike
2025 * addRecord, an existing Record at that index is not shifted to preserve it.
2026 * If no index is specified, it adds the Record to the end of the RecordSet.
2029 * @param oData {Object} An object literal of data.
2030 * @param index {Number} (optional) Position index.
2031 * @return {YAHOO.widget.Record} A Record instance.
2033 setRecord : function(oData, index) {
2034 if(lang.isObject(oData)) {
2035 var oRecord = this._setRecord(oData, index);
2036 this.fireEvent("recordSetEvent",{record:oRecord,data:oData});
2037 YAHOO.log("Set Record at index " + index +
2038 " with data " + lang.dump(oData), "info", this.toString());
2042 YAHOO.log("Could not set Record with data" +
2043 lang.dump(oData), "info", this.toString());
2049 * Sets or replaces multiple Records at once to the RecordSet with the given
2050 * data, starting at the given index. If index is not specified, then the new
2051 * Records are added to the end of the RecordSet.
2053 * @method setRecords
2054 * @param aData {Object[]} An array of object literal data.
2055 * @param index {Number} (optional) Position index.
2056 * @return {YAHOO.widget.Record[]} An array of Record instances.
2058 setRecords : function(aData, index) {
2059 var Rec = widget.Record,
2060 a = lang.isArray(aData) ? aData : [aData],
2062 i = 0, l = a.length, j = 0;
2064 index = parseInt(index,10)|0;
2067 if (typeof a[i] === 'object' && a[i]) {
2068 added[j++] = this._records[index + i] = new Rec(a[i]);
2072 this.fireEvent("recordsSetEvent",{records:added,data:aData});
2073 // Backward compatibility for bug 1918245
2074 this.fireEvent("recordsSet",{records:added,data:aData});
2075 YAHOO.log("Set "+j+" Record(s) at index "+index, "info",
2078 if (a.length && !added.length) {
2079 YAHOO.log("Could not set Records with data " +
2080 lang.dump(aData), "info", this.toString());
2083 return added.length > 1 ? added : added[0];
2087 * Updates given Record with given data.
2089 * @method updateRecord
2090 * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2091 * a RecordSet position index, or a Record ID.
2092 * @param oData {Object} Object literal of new data.
2093 * @return {YAHOO.widget.Record} Updated Record, or null.
2095 updateRecord : function(record, oData) {
2096 var oRecord = this.getRecord(record);
2097 if(oRecord && lang.isObject(oData)) {
2098 // Copy data from the Record for the event that gets fired later
2100 for(var key in oRecord._oData) {
2101 if(lang.hasOwnProperty(oRecord._oData, key)) {
2102 oldData[key] = oRecord._oData[key];
2105 oRecord._oData = oData;
2106 this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
2107 YAHOO.log("Record at index " + this.getRecordIndex(oRecord) +
2108 " updated with data " + lang.dump(oData), "info", this.toString());
2112 YAHOO.log("Could not update Record " + record, "error", this.toString());
2119 * @deprecated Use updateRecordValue
2121 updateKey : function(record, sKey, oData) {
2122 this.updateRecordValue(record, sKey, oData);
2125 * Sets given Record at given key to given data.
2127 * @method updateRecordValue
2128 * @param record {YAHOO.widget.Record | Number | String} A Record instance,
2129 * a RecordSet position index, or a Record ID.
2130 * @param sKey {String} Key name.
2131 * @param oData {Object} New data.
2133 updateRecordValue : function(record, sKey, oData) {
2134 var oRecord = this.getRecord(record);
2137 var keyValue = oRecord._oData[sKey];
2138 // Copy data from the Record for the event that gets fired later
2139 if(keyValue && lang.isObject(keyValue)) {
2141 for(var key in keyValue) {
2142 if(lang.hasOwnProperty(keyValue, key)) {
2143 oldData[key] = keyValue[key];
2152 oRecord._oData[sKey] = oData;
2153 this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2154 this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
2155 YAHOO.log("Key \"" + sKey +
2156 "\" for Record at index " + this.getRecordIndex(oRecord) +
2157 " updated to \"" + lang.dump(oData) + "\"", "info", this.toString());
2160 YAHOO.log("Could not update key " + sKey + " for Record " + record, "error", this.toString());
2165 * Replaces all Records in RecordSet with new object literal data.
2167 * @method replaceRecords
2168 * @param data {Object || Object[]} An object literal of data or an array of
2169 * data object literals.
2170 * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
2171 * an array of Records.
2173 replaceRecords : function(data) {
2175 return this.addRecords(data);
2179 * Sorts all Records by given function. Records keep their unique IDs but will
2180 * have new RecordSet position indexes.
2182 * @method sortRecords
2183 * @param fnSort {Function} Reference to a sort function.
2184 * @param desc {Boolean} True if sort direction is descending, false if sort
2185 * direction is ascending.
2186 * @return {YAHOO.widget.Record[]} Sorted array of Records.
2188 sortRecords : function(fnSort, desc) {
2189 return this._records.sort(function(a, b) {return fnSort(a, b, desc);});
2193 * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
2195 * @method reverseRecords
2196 * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
2198 reverseRecords : function() {
2199 return this._records.reverse();
2203 * Removes the Record at the given position index from the RecordSet. If a range
2204 * is also provided, removes that many Records, starting from the index. Length
2205 * of RecordSet is correspondingly shortened.
2207 * @method deleteRecord
2208 * @param index {Number} Record's RecordSet position index.
2209 * @param range {Number} (optional) How many Records to delete.
2210 * @return {Object} A copy of the data held by the deleted Record.
2212 deleteRecord : function(index) {
2213 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2214 // Copy data from the Record for the event that gets fired later
2215 var oData = widget.DataTable._cloneObject(this.getRecord(index).getData());
2217 this._deleteRecord(index);
2218 this.fireEvent("recordDeleteEvent",{data:oData,index:index});
2219 YAHOO.log("Record deleted at index " + index +
2220 " and containing data " + lang.dump(oData), "info", this.toString());
2224 YAHOO.log("Could not delete Record at index " + index, "error", this.toString());
2230 * Removes the Record at the given position index from the RecordSet. If a range
2231 * is also provided, removes that many Records, starting from the index. Length
2232 * of RecordSet is correspondingly shortened.
2234 * @method deleteRecords
2235 * @param index {Number} Record's RecordSet position index.
2236 * @param range {Number} (optional) How many Records to delete.
2237 * @return {Object[]} An array of copies of the data held by the deleted Records.
2239 deleteRecords : function(index, range) {
2240 if(!lang.isNumber(range)) {
2243 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
2244 var recordsToDelete = this.getRecords(index, range);
2245 // Copy data from each Record for the event that gets fired later
2246 var deletedData = [];
2248 for(var i=0; i<recordsToDelete.length; i++) {
2249 deletedData[deletedData.length] = widget.DataTable._cloneObject(recordsToDelete[i]);
2251 this._deleteRecord(index, range);
2253 this.fireEvent("recordsDeleteEvent",{data:deletedData,index:index});
2254 YAHOO.log(range + "Record(s) deleted at index " + index +
2255 " and containing data " + lang.dump(deletedData), "info", this.toString());
2260 YAHOO.log("Could not delete Records at index " + index, "error", this.toString());
2266 * Deletes all Records from the RecordSet.
2270 reset : function() {
2273 this.fireEvent("resetEvent");
2274 YAHOO.log("All Records deleted from RecordSet", "info", this.toString());
2278 /////////////////////////////////////////////////////////////////////////////
2282 /////////////////////////////////////////////////////////////////////////////
2284 // RecordSet uses EventProvider
2285 lang.augmentProto(RS, util.EventProvider);
2288 * Fired when a new Record is added to the RecordSet.
2290 * @event recordAddEvent
2291 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2292 * @param oArgs.data {Object} Data added.
2296 * Fired when multiple Records are added to the RecordSet at once.
2298 * @event recordsAddEvent
2299 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2300 * @param oArgs.data {Object[]} Data added.
2304 * Fired when a Record is set in the RecordSet.
2306 * @event recordSetEvent
2307 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2308 * @param oArgs.data {Object} Data added.
2312 * Fired when multiple Records are set in the RecordSet at once.
2314 * @event recordsSetEvent
2315 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
2316 * @param oArgs.data {Object[]} Data added.
2320 * Fired when a Record is updated with new data.
2322 * @event recordUpdateEvent
2323 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2324 * @param oArgs.newData {Object} New data.
2325 * @param oArgs.oldData {Object} Old data.
2329 * Fired when a Record is deleted from the RecordSet.
2331 * @event recordDeleteEvent
2332 * @param oArgs.data {Object} A copy of the data held by the Record,
2333 * or an array of data object literals if multiple Records were deleted at once.
2334 * @param oArgs.index {Object} Index of the deleted Record.
2338 * Fired when multiple Records are deleted from the RecordSet at once.
2340 * @event recordsDeleteEvent
2341 * @param oArgs.data {Object[]} An array of data object literals copied
2343 * @param oArgs.index {Object} Index of the first deleted Record.
2347 * Fired when all Records are deleted from the RecordSet at once.
2353 * @event keyUpdateEvent
2354 * @deprecated Use recordValueUpdateEvent
2358 * Fired when a Record value is updated with new data.
2360 * @event recordValueUpdateEvent
2361 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
2362 * @param oArgs.key {String} The updated key.
2363 * @param oArgs.newData {Object} New data.
2364 * @param oArgs.oldData {Object} Old data.
2369 /****************************************************************************/
2370 /****************************************************************************/
2371 /****************************************************************************/
2374 * The Record class defines a DataTable record.
2376 * @namespace YAHOO.widget
2379 * @param oConfigs {Object} (optional) Object literal of key/value pairs.
2381 YAHOO.widget.Record = function(oLiteral) {
2382 this._nCount = widget.Record._nCount;
2383 this._sId = "yui-rec" + this._nCount;
2384 widget.Record._nCount++;
2386 if(lang.isObject(oLiteral)) {
2387 for(var sKey in oLiteral) {
2388 if(lang.hasOwnProperty(oLiteral, sKey)) {
2389 this._oData[sKey] = oLiteral[sKey];
2395 /////////////////////////////////////////////////////////////////////////////
2397 // Private member variables
2399 /////////////////////////////////////////////////////////////////////////////
2402 * Internal class variable to give unique IDs to Record instances.
2404 * @property Record._nCount
2408 YAHOO.widget.Record._nCount = 0;
2410 YAHOO.widget.Record.prototype = {
2412 * Immutable unique count assigned at instantiation. Remains constant while a
2413 * Record's position index can change from sorting.
2422 * Immutable unique ID assigned at instantiation. Remains constant while a
2423 * Record's position index can change from sorting.
2432 * Holds data for the Record in an object literal.
2440 /////////////////////////////////////////////////////////////////////////////
2442 // Public member variables
2444 /////////////////////////////////////////////////////////////////////////////
2446 /////////////////////////////////////////////////////////////////////////////
2450 /////////////////////////////////////////////////////////////////////////////
2453 * Returns unique count assigned at instantiation.
2458 getCount : function() {
2459 return this._nCount;
2463 * Returns unique ID assigned at instantiation.
2468 getId : function() {
2473 * Returns data for the Record for a field if given, or the entire object
2474 * literal otherwise.
2477 * @param sField {String} (Optional) The field from which to retrieve data value.
2480 getData : function(sField) {
2481 if(lang.isString(sField)) {
2482 return this._oData[sField];
2490 * Sets given data at the given key. Use the RecordSet method setValue to trigger
2494 * @param sKey {String} The key of the new value.
2495 * @param oData {MIXED} The new value.
2497 setData : function(sKey, oData) {
2498 this._oData[sKey] = oData;
2506 var lang = YAHOO.lang,
2508 widget = YAHOO.widget,
2513 DS = util.DataSourceBase;
2516 * The DataTable widget provides a progressively enhanced DHTML control for
2517 * displaying tabular data across A-grade browsers.
2520 * @requires yahoo, dom, event, element, datasource
2521 * @optional dragdrop, dragdrop
2522 * @title DataTable Widget
2525 /****************************************************************************/
2526 /****************************************************************************/
2527 /****************************************************************************/
2530 * DataTable class for the YUI DataTable widget.
2532 * @namespace YAHOO.widget
2534 * @extends YAHOO.util.Element
2536 * @param elContainer {HTMLElement} Container element for the TABLE.
2537 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
2538 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
2539 * @param oConfigs {object} (optional) Object literal of configuration values.
2541 YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
2542 var DT = widget.DataTable;
2544 ////////////////////////////////////////////////////////////////////////////
2545 // Backward compatibility for SDT, but prevent infinite loops
2547 if(oConfigs && oConfigs.scrollable) {
2548 return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
2551 ////////////////////////////////////////////////////////////////////////////
2555 this._nIndex = DT._nCount;
2556 this._sId = "yui-dt"+this._nIndex;
2557 this._oChainRender = new YAHOO.util.Chain();
2558 this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
2560 // Initialize configs
2561 this._initConfigs(oConfigs);
2563 // Initialize DataSource
2564 this._initDataSource(oDataSource);
2565 if(!this._oDataSource) {
2566 YAHOO.log("Could not instantiate DataTable due to an invalid DataSource", "error", this.toString());
2570 // Initialize ColumnSet
2571 this._initColumnSet(aColumnDefs);
2572 if(!this._oColumnSet) {
2573 YAHOO.log("Could not instantiate DataTable due to an invalid ColumnSet", "error", this.toString());
2577 // Initialize RecordSet
2578 this._initRecordSet();
2579 if(!this._oRecordSet) {
2582 // Initialize Attributes
2583 DT.superclass.constructor.call(this, elContainer, this.configs);
2585 // Initialize DOM elements
2586 var okDom = this._initDomElements(elContainer);
2588 YAHOO.log("Could not instantiate DataTable due to an invalid DOM elements", "error", this.toString());
2592 // Show message as soon as config is available
2593 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
2595 ////////////////////////////////////////////////////////////////////////////
2596 // Once per instance
2600 DT._nCurrentCount++;
2602 ////////////////////////////////////////////////////////////////////////////
2605 // Send a simple initial request
2607 success : this.onDataReturnSetRows,
2608 failure : this.onDataReturnSetRows,
2610 argument: this.getState()
2613 var initialLoad = this.get("initialLoad");
2614 if(initialLoad === true) {
2615 this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
2617 // Do not send an initial request at all
2618 else if(initialLoad === false) {
2619 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
2621 // Send an initial request with a custom payload
2623 var oCustom = initialLoad || {};
2624 oCallback.argument = oCustom.argument || {};
2625 this._oDataSource.sendRequest(oCustom.request, oCallback);
2629 var DT = widget.DataTable;
2631 /////////////////////////////////////////////////////////////////////////////
2635 /////////////////////////////////////////////////////////////////////////////
2637 lang.augmentObject(DT, {
2640 * Class name assigned to outer DataTable container.
2642 * @property DataTable.CLASS_DATATABLE
2648 CLASS_DATATABLE : "yui-dt",
2651 * Class name assigned to liner DIV elements.
2653 * @property DataTable.CLASS_LINER
2657 * @default "yui-dt-liner"
2659 CLASS_LINER : "yui-dt-liner",
2662 * Class name assigned to display label elements.
2664 * @property DataTable.CLASS_LABEL
2668 * @default "yui-dt-label"
2670 CLASS_LABEL : "yui-dt-label",
2673 * Class name assigned to messaging elements.
2675 * @property DataTable.CLASS_MESSAGE
2679 * @default "yui-dt-message"
2681 CLASS_MESSAGE : "yui-dt-message",
2684 * Class name assigned to mask element when DataTable is disabled.
2686 * @property DataTable.CLASS_MASK
2690 * @default "yui-dt-mask"
2692 CLASS_MASK : "yui-dt-mask",
2695 * Class name assigned to data elements.
2697 * @property DataTable.CLASS_DATA
2701 * @default "yui-dt-data"
2703 CLASS_DATA : "yui-dt-data",
2706 * Class name assigned to Column drag target.
2708 * @property DataTable.CLASS_COLTARGET
2712 * @default "yui-dt-coltarget"
2714 CLASS_COLTARGET : "yui-dt-coltarget",
2717 * Class name assigned to resizer handle elements.
2719 * @property DataTable.CLASS_RESIZER
2723 * @default "yui-dt-resizer"
2725 CLASS_RESIZER : "yui-dt-resizer",
2728 * Class name assigned to resizer liner elements.
2730 * @property DataTable.CLASS_RESIZERLINER
2734 * @default "yui-dt-resizerliner"
2736 CLASS_RESIZERLINER : "yui-dt-resizerliner",
2739 * Class name assigned to resizer proxy elements.
2741 * @property DataTable.CLASS_RESIZERPROXY
2745 * @default "yui-dt-resizerproxy"
2747 CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
2750 * Class name assigned to CellEditor container elements.
2752 * @property DataTable.CLASS_EDITOR
2756 * @default "yui-dt-editor"
2758 CLASS_EDITOR : "yui-dt-editor",
2761 * Class name assigned to paginator container elements.
2763 * @property DataTable.CLASS_PAGINATOR
2767 * @default "yui-dt-paginator"
2769 CLASS_PAGINATOR : "yui-dt-paginator",
2772 * Class name assigned to page number indicators.
2774 * @property DataTable.CLASS_PAGE
2778 * @default "yui-dt-page"
2780 CLASS_PAGE : "yui-dt-page",
2783 * Class name assigned to default indicators.
2785 * @property DataTable.CLASS_DEFAULT
2789 * @default "yui-dt-default"
2791 CLASS_DEFAULT : "yui-dt-default",
2794 * Class name assigned to previous indicators.
2796 * @property DataTable.CLASS_PREVIOUS
2800 * @default "yui-dt-previous"
2802 CLASS_PREVIOUS : "yui-dt-previous",
2805 * Class name assigned next indicators.
2807 * @property DataTable.CLASS_NEXT
2811 * @default "yui-dt-next"
2813 CLASS_NEXT : "yui-dt-next",
2816 * Class name assigned to first elements.
2818 * @property DataTable.CLASS_FIRST
2822 * @default "yui-dt-first"
2824 CLASS_FIRST : "yui-dt-first",
2827 * Class name assigned to last elements.
2829 * @property DataTable.CLASS_LAST
2833 * @default "yui-dt-last"
2835 CLASS_LAST : "yui-dt-last",
2838 * Class name assigned to even elements.
2840 * @property DataTable.CLASS_EVEN
2844 * @default "yui-dt-even"
2846 CLASS_EVEN : "yui-dt-even",
2849 * Class name assigned to odd elements.
2851 * @property DataTable.CLASS_ODD
2855 * @default "yui-dt-odd"
2857 CLASS_ODD : "yui-dt-odd",
2860 * Class name assigned to selected elements.
2862 * @property DataTable.CLASS_SELECTED
2866 * @default "yui-dt-selected"
2868 CLASS_SELECTED : "yui-dt-selected",
2871 * Class name assigned to highlighted elements.
2873 * @property DataTable.CLASS_HIGHLIGHTED
2877 * @default "yui-dt-highlighted"
2879 CLASS_HIGHLIGHTED : "yui-dt-highlighted",
2882 * Class name assigned to hidden elements.
2884 * @property DataTable.CLASS_HIDDEN
2888 * @default "yui-dt-hidden"
2890 CLASS_HIDDEN : "yui-dt-hidden",
2893 * Class name assigned to disabled elements.
2895 * @property DataTable.CLASS_DISABLED
2899 * @default "yui-dt-disabled"
2901 CLASS_DISABLED : "yui-dt-disabled",
2904 * Class name assigned to empty indicators.
2906 * @property DataTable.CLASS_EMPTY
2910 * @default "yui-dt-empty"
2912 CLASS_EMPTY : "yui-dt-empty",
2915 * Class name assigned to loading indicatorx.
2917 * @property DataTable.CLASS_LOADING
2921 * @default "yui-dt-loading"
2923 CLASS_LOADING : "yui-dt-loading",
2926 * Class name assigned to error indicators.
2928 * @property DataTable.CLASS_ERROR
2932 * @default "yui-dt-error"
2934 CLASS_ERROR : "yui-dt-error",
2937 * Class name assigned to editable elements.
2939 * @property DataTable.CLASS_EDITABLE
2943 * @default "yui-dt-editable"
2945 CLASS_EDITABLE : "yui-dt-editable",
2948 * Class name assigned to draggable elements.
2950 * @property DataTable.CLASS_DRAGGABLE
2954 * @default "yui-dt-draggable"
2956 CLASS_DRAGGABLE : "yui-dt-draggable",
2959 * Class name assigned to resizeable elements.
2961 * @property DataTable.CLASS_RESIZEABLE
2965 * @default "yui-dt-resizeable"
2967 CLASS_RESIZEABLE : "yui-dt-resizeable",
2970 * Class name assigned to scrollable elements.
2972 * @property DataTable.CLASS_SCROLLABLE
2976 * @default "yui-dt-scrollable"
2978 CLASS_SCROLLABLE : "yui-dt-scrollable",
2981 * Class name assigned to sortable elements.
2983 * @property DataTable.CLASS_SORTABLE
2987 * @default "yui-dt-sortable"
2989 CLASS_SORTABLE : "yui-dt-sortable",
2992 * Class name assigned to ascending elements.
2994 * @property DataTable.CLASS_ASC
2998 * @default "yui-dt-asc"
3000 CLASS_ASC : "yui-dt-asc",
3003 * Class name assigned to descending elements.
3005 * @property DataTable.CLASS_DESC
3009 * @default "yui-dt-desc"
3011 CLASS_DESC : "yui-dt-desc",
3014 * Class name assigned to BUTTON elements and/or container elements.
3016 * @property DataTable.CLASS_BUTTON
3020 * @default "yui-dt-button"
3022 CLASS_BUTTON : "yui-dt-button",
3025 * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
3027 * @property DataTable.CLASS_CHECKBOX
3031 * @default "yui-dt-checkbox"
3033 CLASS_CHECKBOX : "yui-dt-checkbox",
3036 * Class name assigned to SELECT elements and/or container elements.
3038 * @property DataTable.CLASS_DROPDOWN
3042 * @default "yui-dt-dropdown"
3044 CLASS_DROPDOWN : "yui-dt-dropdown",
3047 * Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
3049 * @property DataTable.CLASS_RADIO
3053 * @default "yui-dt-radio"
3055 CLASS_RADIO : "yui-dt-radio",
3057 /////////////////////////////////////////////////////////////////////////
3059 // Private static properties
3061 /////////////////////////////////////////////////////////////////////////
3064 * Internal class variable for indexing multiple DataTable instances.
3066 * @property DataTable._nCount
3074 * Internal class variable tracking current number of DataTable instances,
3075 * so that certain class values can be reset when all instances are destroyed.
3077 * @property DataTable._nCurrentCount
3085 * Reference to the STYLE node that is dynamically created and updated
3086 * in order to manage Column widths.
3088 * @property DataTable._elDynStyleNode
3093 _elDynStyleNode : null,
3096 * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
3098 * @property DataTable._bDynStylesFallback
3103 _bDynStylesFallback : (ua.ie && (ua.ie<7)) ? true : false,
3106 * Object literal hash of Columns and their dynamically create style rules.
3108 * @property DataTable._oDynStyles
3116 * Element reference to shared Column drag target.
3118 * @property DataTable._elColumnDragTarget
3123 _elColumnDragTarget : null,
3126 * Element reference to shared Column resizer proxy.
3128 * @property DataTable._elColumnResizerProxy
3133 _elColumnResizerProxy : null,
3135 /////////////////////////////////////////////////////////////////////////
3137 // Private static methods
3139 /////////////////////////////////////////////////////////////////////////
3142 * Clones object literal or array of object literals.
3144 * @method DataTable._cloneObject
3145 * @param o {Object} Object.
3149 _cloneObject : function(o) {
3150 if(!lang.isValue(o)) {
3156 if(o instanceof YAHOO.widget.BaseCellEditor) {
3159 else if(lang.isFunction(o)) {
3162 else if(lang.isArray(o)) {
3164 for(var i=0,len=o.length;i<len;i++) {
3165 array[i] = DT._cloneObject(o[i]);
3169 else if(lang.isObject(o)) {
3171 if(lang.hasOwnProperty(o, x)) {
3172 if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
3173 copy[x] = DT._cloneObject(o[x]);
3189 * Destroys shared Column drag target.
3191 * @method DataTable._destroyColumnDragTargetEl
3195 _destroyColumnDragTargetEl : function() {
3196 if(DT._elColumnDragTarget) {
3197 var el = DT._elColumnDragTarget;
3198 YAHOO.util.Event.purgeElement(el);
3199 el.parentNode.removeChild(el);
3200 DT._elColumnDragTarget = null;
3206 * Creates HTML markup for shared Column drag target.
3208 * @method DataTable._initColumnDragTargetEl
3209 * @return {HTMLElement} Reference to Column drag target.
3213 _initColumnDragTargetEl : function() {
3214 if(!DT._elColumnDragTarget) {
3215 // Attach Column drag target element as first child of body
3216 var elColumnDragTarget = document.createElement('div');
3217 elColumnDragTarget.className = DT.CLASS_COLTARGET;
3218 elColumnDragTarget.style.display = "none";
3219 document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
3221 // Internal tracker of Column drag target
3222 DT._elColumnDragTarget = elColumnDragTarget;
3225 return DT._elColumnDragTarget;
3229 * Destroys shared Column resizer proxy.
3231 * @method DataTable._destroyColumnResizerProxyEl
3232 * @return {HTMLElement} Reference to Column resizer proxy.
3236 _destroyColumnResizerProxyEl : function() {
3237 if(DT._elColumnResizerProxy) {
3238 var el = DT._elColumnResizerProxy;
3239 YAHOO.util.Event.purgeElement(el);
3240 el.parentNode.removeChild(el);
3241 DT._elColumnResizerProxy = null;
3246 * Creates HTML markup for shared Column resizer proxy.
3248 * @method DataTable._initColumnResizerProxyEl
3249 * @return {HTMLElement} Reference to Column resizer proxy.
3253 _initColumnResizerProxyEl : function() {
3254 if(!DT._elColumnResizerProxy) {
3255 // Attach Column resizer element as first child of body
3256 var elColumnResizerProxy = document.createElement("div");
3257 elColumnResizerProxy.id = "yui-dt-colresizerproxy"; // Needed for ColumnResizer
3258 elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
3259 document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
3261 // Internal tracker of Column resizer proxy
3262 DT._elColumnResizerProxy = elColumnResizerProxy;
3264 return DT._elColumnResizerProxy;
3268 * Formats a BUTTON element.
3270 * @method DataTable.formatButton
3271 * @param el {HTMLElement} The element to format with markup.
3272 * @param oRecord {YAHOO.widget.Record} Record instance.
3273 * @param oColumn {YAHOO.widget.Column} Column instance.
3274 * @param oData {Object | Boolean} Data value for the cell. By default, the value
3275 * is what gets written to the BUTTON.
3278 formatButton : function(el, oRecord, oColumn, oData) {
3279 var sValue = lang.isValue(oData) ? oData : "Click";
3280 //TODO: support YAHOO.widget.Button
3281 //if(YAHOO.widget.Button) {
3285 el.innerHTML = "<button type=\"button\" class=\""+
3286 DT.CLASS_BUTTON + "\">" + sValue + "</button>";
3291 * Formats a CHECKBOX element.
3293 * @method DataTable.formatCheckbox
3294 * @param el {HTMLElement} The element to format with markup.
3295 * @param oRecord {YAHOO.widget.Record} Record instance.
3296 * @param oColumn {YAHOO.widget.Column} Column instance.
3297 * @param oData {Object | Boolean} Data value for the cell. Can be a simple
3298 * Boolean to indicate whether checkbox is checked or not. Can be object literal
3299 * {checked:bBoolean, label:sLabel}. Other forms of oData require a custom
3303 formatCheckbox : function(el, oRecord, oColumn, oData) {
3304 var bChecked = oData;
3305 bChecked = (bChecked) ? " checked=\"checked\"" : "";
3306 el.innerHTML = "<input type=\"checkbox\"" + bChecked +
3307 " class=\"" + DT.CLASS_CHECKBOX + "\" />";
3311 * Formats currency. Default unit is USD.
3313 * @method DataTable.formatCurrency
3314 * @param el {HTMLElement} The element to format with markup.
3315 * @param oRecord {YAHOO.widget.Record} Record instance.
3316 * @param oColumn {YAHOO.widget.Column} Column instance.
3317 * @param oData {Number} Data value for the cell.
3320 formatCurrency : function(el, oRecord, oColumn, oData) {
3321 el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || this.get("currencyOptions"));
3325 * Formats JavaScript Dates.
3327 * @method DataTable.formatDate
3328 * @param el {HTMLElement} The element to format with markup.
3329 * @param oRecord {YAHOO.widget.Record} Record instance.
3330 * @param oColumn {YAHOO.widget.Column} Column instance.
3331 * @param oData {Object} Data value for the cell, or null.
3334 formatDate : function(el, oRecord, oColumn, oData) {
3335 var oConfig = oColumn.dateOptions || this.get("dateOptions");
3336 el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
3340 * Formats SELECT elements.
3342 * @method DataTable.formatDropdown
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 formatDropdown : function(el, oRecord, oColumn, oData) {
3350 var selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
3351 options = (lang.isArray(oColumn.dropdownOptions)) ?
3352 oColumn.dropdownOptions : null,
3355 collection = el.getElementsByTagName("select");
3357 // Create the form element only once, so we can attach the onChange listener
3358 if(collection.length === 0) {
3359 // Create SELECT element
3360 selectEl = document.createElement("select");
3361 selectEl.className = DT.CLASS_DROPDOWN;
3362 selectEl = el.appendChild(selectEl);
3364 // Add event listener
3365 Ev.addListener(selectEl,"change",this._onDropdownChange,this);
3368 selectEl = collection[0];
3370 // Update the form element
3372 // Clear out previous options
3373 selectEl.innerHTML = "";
3375 // We have options to populate
3377 // Create OPTION elements
3378 for(var i=0; i<options.length; i++) {
3379 var option = options[i];
3380 var optionEl = document.createElement("option");
3381 optionEl.value = (lang.isValue(option.value)) ?
3382 option.value : option;
3383 // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
3384 optionEl.innerHTML = (lang.isValue(option.text)) ?
3385 option.text : (lang.isValue(option.label)) ? option.label : option;
3386 optionEl = selectEl.appendChild(optionEl);
3387 if (optionEl.value == selectedValue) {
3388 optionEl.selected = true;
3392 // Selected value is our only option
3394 selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
3398 el.innerHTML = lang.isValue(oData) ? oData : "";
3405 * @method DataTable.formatEmail
3406 * @param el {HTMLElement} The element to format with markup.
3407 * @param oRecord {YAHOO.widget.Record} Record instance.
3408 * @param oColumn {YAHOO.widget.Column} Column instance.
3409 * @param oData {Object} Data value for the cell, or null.
3412 formatEmail : function(el, oRecord, oColumn, oData) {
3413 if(lang.isString(oData)) {
3414 el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
3417 el.innerHTML = lang.isValue(oData) ? oData : "";
3424 * @method DataTable.formatLink
3425 * @param el {HTMLElement} The element to format with markup.
3426 * @param oRecord {YAHOO.widget.Record} Record instance.
3427 * @param oColumn {YAHOO.widget.Column} Column instance.
3428 * @param oData {Object} Data value for the cell, or null.
3431 formatLink : function(el, oRecord, oColumn, oData) {
3432 if(lang.isString(oData)) {
3433 el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
3436 el.innerHTML = lang.isValue(oData) ? oData : "";
3443 * @method DataTable.formatNumber
3444 * @param el {HTMLElement} The element to format with markup.
3445 * @param oRecord {YAHOO.widget.Record} Record instance.
3446 * @param oColumn {YAHOO.widget.Column} Column instance.
3447 * @param oData {Object} Data value for the cell, or null.
3450 formatNumber : function(el, oRecord, oColumn, oData) {
3451 el.innerHTML = util.Number.format(oData, oColumn.numberOptions || this.get("numberOptions"));
3455 * Formats INPUT TYPE=RADIO elements.
3457 * @method DataTable.formatRadio
3458 * @param el {HTMLElement} The element to format with markup.
3459 * @param oRecord {YAHOO.widget.Record} Record instance.
3460 * @param oColumn {YAHOO.widget.Column} Column instance.
3461 * @param oData {Object} (Optional) Data value for the cell.
3464 formatRadio : function(el, oRecord, oColumn, oData) {
3465 var bChecked = oData;
3466 bChecked = (bChecked) ? " checked=\"checked\"" : "";
3467 el.innerHTML = "<input type=\"radio\"" + bChecked +
3468 " name=\""+this.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
3469 " class=\"" + DT.CLASS_RADIO+ "\" />";
3473 * Formats text strings.
3475 * @method DataTable.formatText
3476 * @param el {HTMLElement} The element to format with markup.
3477 * @param oRecord {YAHOO.widget.Record} Record instance.
3478 * @param oColumn {YAHOO.widget.Column} Column instance.
3479 * @param oData {Object} (Optional) Data value for the cell.
3482 formatText : function(el, oRecord, oColumn, oData) {
3483 var value = (lang.isValue(oData)) ? oData : "";
3484 //TODO: move to util function
3485 el.innerHTML = value.toString().replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
3489 * Formats TEXTAREA elements.
3491 * @method DataTable.formatTextarea
3492 * @param el {HTMLElement} The element to format with markup.
3493 * @param oRecord {YAHOO.widget.Record} Record instance.
3494 * @param oColumn {YAHOO.widget.Column} Column instance.
3495 * @param oData {Object} (Optional) Data value for the cell.
3498 formatTextarea : function(el, oRecord, oColumn, oData) {
3499 var value = (lang.isValue(oData)) ? oData : "",
3500 markup = "<textarea>" + value + "</textarea>";
3501 el.innerHTML = markup;
3505 * Formats INPUT TYPE=TEXT elements.
3507 * @method DataTable.formatTextbox
3508 * @param el {HTMLElement} The element to format with markup.
3509 * @param oRecord {YAHOO.widget.Record} Record instance.
3510 * @param oColumn {YAHOO.widget.Column} Column instance.
3511 * @param oData {Object} (Optional) Data value for the cell.
3514 formatTextbox : function(el, oRecord, oColumn, oData) {
3515 var value = (lang.isValue(oData)) ? oData : "",
3516 markup = "<input type=\"text\" value=\"" + value + "\" />";
3517 el.innerHTML = markup;
3521 * Default cell formatter
3523 * @method DataTable.formatDefault
3524 * @param el {HTMLElement} The element to format with markup.
3525 * @param oRecord {YAHOO.widget.Record} Record instance.
3526 * @param oColumn {YAHOO.widget.Column} Column instance.
3527 * @param oData {Object} (Optional) Data value for the cell.
3530 formatDefault : function(el, oRecord, oColumn, oData) {
3531 el.innerHTML = oData === undefined ||
3533 (typeof oData === 'number' && isNaN(oData)) ?
3534 " " : oData.toString();
3538 * Validates data value to type Number, doing type conversion as
3539 * necessary. A valid Number value is return, else null is returned
3540 * if input value does not validate.
3543 * @method DataTable.validateNumber
3544 * @param oData {Object} Data to validate.
3547 validateNumber : function(oData) {
3549 var number = oData * 1;
3552 if(lang.isNumber(number)) {
3556 YAHOO.log("Could not validate data " + lang.dump(oData) + " to type Number", "warn", this.toString());
3562 // Done in separate step so referenced functions are defined.
3564 * Cell formatting functions.
3565 * @property DataTable.Formatter
3570 button : DT.formatButton,
3571 checkbox : DT.formatCheckbox,
3572 currency : DT.formatCurrency,
3573 "date" : DT.formatDate,
3574 dropdown : DT.formatDropdown,
3575 email : DT.formatEmail,
3576 link : DT.formatLink,
3577 "number" : DT.formatNumber,
3578 radio : DT.formatRadio,
3579 text : DT.formatText,
3580 textarea : DT.formatTextarea,
3581 textbox : DT.formatTextbox,
3583 defaultFormatter : DT.formatDefault
3586 lang.extend(DT, util.Element, {
3588 /////////////////////////////////////////////////////////////////////////////
3590 // Superclass methods
3592 /////////////////////////////////////////////////////////////////////////////
3595 * Implementation of Element's abstract method. Sets up config values.
3597 * @method initAttributes
3598 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
3602 initAttributes : function(oConfigs) {
3603 oConfigs = oConfigs || {};
3604 DT.superclass.initAttributes.call(this, oConfigs);
3607 * @attribute summary
3608 * @description Value for the SUMMARY attribute.
3612 this.setAttributeConfig("summary", {
3614 validator: lang.isString,
3615 method: function(sSummary) {
3617 this._elTable.summary = sSummary;
3623 * @attribute selectionMode
3624 * @description Specifies row or cell selection mode. Accepts the following strings:
3626 * <dt>"standard"</dt>
3627 * <dd>Standard row selection with support for modifier keys to enable
3628 * multiple selections.</dd>
3631 * <dd>Row selection with modifier keys disabled to not allow
3632 * multiple selections.</dd>
3634 * <dt>"singlecell"</dt>
3635 * <dd>Cell selection with modifier keys disabled to not allow
3636 * multiple selections.</dd>
3638 * <dt>"cellblock"</dt>
3639 * <dd>Cell selection with support for modifier keys to enable multiple
3640 * selections in a block-fashion, like a spreadsheet.</dd>
3642 * <dt>"cellrange"</dt>
3643 * <dd>Cell selection with support for modifier keys to enable multiple
3644 * selections in a range-fashion, like a calendar.</dd>
3647 * @default "standard"
3650 this.setAttributeConfig("selectionMode", {
3652 validator: lang.isString
3656 * @attribute sortedBy
3657 * @description Object literal provides metadata for initial sort values if
3658 * data will arrive pre-sorted:
3660 * <dt>sortedBy.key</dt>
3661 * <dd>{String} Key of sorted Column</dd>
3662 * <dt>sortedBy.dir</dt>
3663 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3665 * @type Object | null
3667 this.setAttributeConfig("sortedBy", {
3669 // TODO: accepted array for nested sorts
3670 validator: function(oNewSortedBy) {
3672 return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
3675 return (oNewSortedBy === null);
3678 method: function(oNewSortedBy) {
3679 // Stash the previous value
3680 var oOldSortedBy = this.get("sortedBy");
3682 // Workaround for bug 1827195
3683 this._configs.sortedBy.value = oNewSortedBy;
3685 // Remove ASC/DESC from TH
3692 if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
3693 oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
3694 nOldColumnKeyIndex = oOldColumn.getKeyIndex();
3696 // Remove previous UI from THEAD
3697 var elOldTh = oOldColumn.getThEl();
3698 Dom.removeClass(elOldTh, oOldSortedBy.dir);
3699 this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
3702 oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
3703 nNewColumnKeyIndex = oNewColumn.getKeyIndex();
3705 // Update THEAD with new UI
3706 var elNewTh = oNewColumn.getThEl();
3707 // Backward compatibility
3708 if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") || (oNewSortedBy.dir == "desc"))) {
3709 var newClass = (oNewSortedBy.dir == "desc") ?
3712 Dom.addClass(elNewTh, newClass);
3715 var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
3716 Dom.addClass(elNewTh, sortClass);
3718 this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
3724 this._elTbody.style.display = "none";
3725 var allRows = this._elTbody.rows,
3727 for(var i=allRows.length-1; i>-1; i--) {
3728 allCells = allRows[i].childNodes;
3729 if(allCells[nOldColumnKeyIndex]) {
3730 Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
3732 if(allCells[nNewColumnKeyIndex]) {
3733 Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
3736 this._elTbody.style.display = "";
3739 this._clearTrTemplateEl();
3744 * @attribute paginator
3745 * @description An instance of YAHOO.widget.Paginator.
3747 * @type {Object|YAHOO.widget.Paginator}
3749 this.setAttributeConfig("paginator", {
3751 validator : function (val) {
3752 return val === null || val instanceof widget.Paginator;
3754 method : function () { this._updatePaginator.apply(this,arguments); }
3758 * @attribute caption
3759 * @description Value for the CAPTION element. NB: Not supported in
3760 * ScrollingDataTable.
3763 this.setAttributeConfig("caption", {
3765 validator: lang.isString,
3766 method: function(sCaption) {
3767 this._initCaptionEl(sCaption);
3772 * @attribute draggableColumns
3773 * @description True if Columns are draggable to reorder, false otherwise.
3774 * The Drag & Drop Utility is required to enable this feature. Only top-level
3775 * and non-nested Columns are draggable. Write once.
3779 this.setAttributeConfig("draggableColumns", {
3781 validator: lang.isBoolean,
3782 method: function(oParam) {
3785 this._initDraggableColumns();
3788 this._destroyDraggableColumns();
3795 * @attribute renderLoopSize
3796 * @description A value greater than 0 enables DOM rendering of rows to be
3797 * executed from a non-blocking timeout queue and sets how many rows to be
3798 * rendered per timeout. Recommended for very large data sets.
3802 this.setAttributeConfig("renderLoopSize", {
3804 validator: lang.isNumber
3808 * @attribute formatRow
3809 * @description A function that accepts a TR element and its associated Record
3810 * for custom formatting. The function must return TRUE in order to automatically
3811 * continue formatting of child TD elements, else TD elements will not be
3812 * automatically formatted.
3816 this.setAttributeConfig("formatRow", {
3818 validator: lang.isFunction
3822 * @attribute generateRequest
3823 * @description A function that converts an object literal of desired DataTable
3824 * states into a request value which is then passed to the DataSource's
3825 * sendRequest method in order to retrieve data for those states. This
3826 * function is passed an object literal of state data and a reference to the
3827 * DataTable instance:
3830 * <dt>pagination<dt>
3832 * <dt>offsetRecord</dt>
3833 * <dd>{Number} Index of the first Record of the desired page</dd>
3834 * <dt>rowsPerPage</dt>
3835 * <dd>{Number} Number of rows per page</dd>
3840 * <dd>{String} Key of sorted Column</dd>
3842 * <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
3845 * <dd>The DataTable instance</dd>
3848 * and by default returns a String of syntax:
3849 * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
3851 * @default HTMLFunction
3853 this.setAttributeConfig("generateRequest", {
3854 value: function(oState, oSelf) {
3856 oState = oState || {pagination:null, sortedBy:null};
3857 var sort = (oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey();
3858 var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
3859 var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
3860 var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
3862 // Build the request
3863 return "sort=" + sort +
3865 "&startIndex=" + startIndex +
3866 ((results !== null) ? "&results=" + results : "");
3868 validator: lang.isFunction
3872 * @attribute initialRequest
3873 * @description Defines the initial request that gets sent to the DataSource
3874 * during initialization. Value is ignored if initialLoad is set to any value
3879 this.setAttributeConfig("initialRequest", {
3884 * @attribute initialLoad
3885 * @description Determines whether or not to load data at instantiation. By
3886 * default, will trigger a sendRequest() to the DataSource and pass in the
3887 * request defined by initialRequest. If set to false, data will not load
3888 * at instantiation. Alternatively, implementers who wish to work with a
3889 * custom payload may pass in an object literal with the following values:
3892 * <dt>request (MIXED)</dt>
3893 * <dd>Request value.</dd>
3895 * <dt>argument (MIXED)</dt>
3896 * <dd>Custom data that will be passed through to the callback function.</dd>
3900 * @type Boolean | Object
3903 this.setAttributeConfig("initialLoad", {
3908 * @attribute dynamicData
3909 * @description If true, sorting and pagination are relegated to the DataSource
3910 * for handling, using the request returned by the "generateRequest" function.
3911 * Each new DataSource response blows away all previous Records. False by default, so
3912 * sorting and pagination will be handled directly on the client side, without
3913 * causing any new requests for data from the DataSource.
3917 this.setAttributeConfig("dynamicData", {
3919 validator: lang.isBoolean
3923 * @attribute MSG_EMPTY
3924 * @description Message to display if DataTable has no data.
3926 * @default "No records found."
3928 this.setAttributeConfig("MSG_EMPTY", {
3929 value: "No records found.",
3930 validator: lang.isString
3934 * @attribute MSG_LOADING
3935 * @description Message to display while DataTable is loading data.
3937 * @default "Loading..."
3939 this.setAttributeConfig("MSG_LOADING", {
3940 value: "Loading...",
3941 validator: lang.isString
3945 * @attribute MSG_ERROR
3946 * @description Message to display while DataTable has data error.
3948 * @default "Data error."
3950 this.setAttributeConfig("MSG_ERROR", {
3951 value: "Data error.",
3952 validator: lang.isString
3956 * @attribute MSG_SORTASC
3957 * @description Message to display in tooltip to sort Column in ascending order.
3959 * @default "Click to sort ascending"
3961 this.setAttributeConfig("MSG_SORTASC", {
3962 value: "Click to sort ascending",
3963 validator: lang.isString,
3964 method: function(sParam) {
3966 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3967 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
3968 allKeys[i]._elThLabel.firstChild.title = sParam;
3976 * @attribute MSG_SORTDESC
3977 * @description Message to display in tooltip to sort Column in descending order.
3979 * @default "Click to sort descending"
3981 this.setAttributeConfig("MSG_SORTDESC", {
3982 value: "Click to sort descending",
3983 validator: lang.isString,
3984 method: function(sParam) {
3986 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
3987 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
3988 allKeys[i]._elThLabel.firstChild.title = sParam;
3996 * @attribute currencySymbol
3999 this.setAttributeConfig("currencySymbol", {
4001 validator: lang.isString
4005 * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
4006 * @attribute currencyOptions
4008 * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
4010 this.setAttributeConfig("currencyOptions", {
4012 prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
4014 decimalSeparator:".",
4015 thousandsSeparator:","
4020 * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
4021 * @attribute dateOptions
4023 * @default {format:"%m/%d/%Y", locale:"en"}
4025 this.setAttributeConfig("dateOptions", {
4026 value: {format:"%m/%d/%Y", locale:"en"}
4030 * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
4031 * @attribute numberOptions
4033 * @default {decimalPlaces:0, thousandsSeparator:","}
4035 this.setAttributeConfig("numberOptions", {
4038 thousandsSeparator:","
4044 /////////////////////////////////////////////////////////////////////////////
4046 // Private member variables
4048 /////////////////////////////////////////////////////////////////////////////
4051 * True if instance is initialized, so as to fire the initEvent after render.
4061 * Index assigned to instance.
4070 * Counter for IDs assigned to TR elements.
4072 * @property _nTrCount
4079 * Counter for IDs assigned to TD elements.
4081 * @property _nTdCount
4088 * Unique id assigned to instance "yui-dtN", useful prefix for generating unique
4089 * DOM ID strings and log messages.
4100 * @property _oChainRender
4101 * @type YAHOO.util.Chain
4104 _oChainRender : null,
4107 * DOM reference to the container element for the DataTable instance into which
4108 * all other elements get created.
4110 * @property _elContainer
4114 _elContainer : null,
4117 * DOM reference to the mask element for the DataTable instance which disables it.
4126 * DOM reference to the TABLE element for the DataTable instance.
4128 * @property _elTable
4135 * DOM reference to the CAPTION element for the DataTable instance.
4137 * @property _elCaption
4144 * DOM reference to the COLGROUP element for the DataTable instance.
4146 * @property _elColgroup
4153 * DOM reference to the THEAD element for the DataTable instance.
4155 * @property _elThead
4162 * DOM reference to the primary TBODY element for the DataTable instance.
4164 * @property _elTbody
4171 * DOM reference to the secondary TBODY element used to display DataTable messages.
4173 * @property _elMsgTbody
4180 * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
4182 * @property _elMsgTr
4189 * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
4191 * @property _elMsgTd
4198 * DataSource instance for the DataTable instance.
4200 * @property _oDataSource
4201 * @type YAHOO.util.DataSource
4204 _oDataSource : null,
4207 * ColumnSet instance for the DataTable instance.
4209 * @property _oColumnSet
4210 * @type YAHOO.widget.ColumnSet
4216 * RecordSet instance for the DataTable instance.
4218 * @property _oRecordSet
4219 * @type YAHOO.widget.RecordSet
4225 * The active CellEditor instance for the DataTable instance.
4227 * @property _oCellEditor
4228 * @type YAHOO.widget.CellEditor
4231 _oCellEditor : null,
4234 * ID string of first TR element of the current DataTable page.
4236 * @property _sFirstTrId
4243 * ID string of the last TR element of the current DataTable page.
4245 * @property _sLastTrId
4252 * Template row to create all new rows from.
4253 * @property _elTrTemplate
4254 * @type {HTMLElement}
4257 _elTrTemplate : null,
4260 * Sparse array of custom functions to set column widths for browsers that don't
4261 * support dynamic CSS rules. Functions are added at the index representing
4262 * the number of rows they update.
4264 * @property _aDynFunctions
4268 _aDynFunctions : [],
4298 /////////////////////////////////////////////////////////////////////////////
4302 /////////////////////////////////////////////////////////////////////////////
4305 * Clears browser text selection. Useful to call on rowSelectEvent or
4306 * cellSelectEvent to prevent clicks or dblclicks from selecting text in the
4309 * @method clearTextSelection
4311 clearTextSelection : function() {
4313 if(window.getSelection) {
4314 sel = window.getSelection();
4316 else if(document.getSelection) {
4317 sel = document.getSelection();
4319 else if(document.selection) {
4320 sel = document.selection;
4326 else if (sel.removeAllRanges) {
4327 sel.removeAllRanges();
4329 else if(sel.collapse) {
4336 * Sets focus on the given element.
4339 * @param el {HTMLElement} Element.
4342 _focusEl : function(el) {
4343 el = el || this._elTbody;
4344 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
4345 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
4346 // strange unexpected things as the user clicks on buttons and other controls.
4347 setTimeout(function() {
4357 * Forces Gecko repaint.
4359 * @method _repaintGecko
4360 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4363 _repaintGecko : (ua.gecko) ?
4365 el = el || this._elContainer;
4366 var parent = el.parentNode;
4367 var nextSibling = el.nextSibling;
4368 parent.insertBefore(parent.removeChild(el), nextSibling);
4372 * Forces Opera repaint.
4374 * @method _repaintOpera
4377 _repaintOpera : (ua.opera) ?
4380 document.documentElement.className += " ";
4381 document.documentElement.className.trim();
4386 * Forces Webkit repaint.
4388 * @method _repaintWebkit
4389 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
4392 _repaintWebkit : (ua.webkit) ?
4394 el = el || this._elContainer;
4395 var parent = el.parentNode;
4396 var nextSibling = el.nextSibling;
4397 parent.insertBefore(parent.removeChild(el), nextSibling);
4424 * Initializes object literal of config values.
4426 * @method _initConfigs
4427 * @param oConfig {Object} Object literal of config values.
4430 _initConfigs : function(oConfigs) {
4431 if(!oConfigs || !lang.isObject(oConfigs)) {
4434 this.configs = oConfigs;
4438 * Initializes ColumnSet.
4440 * @method _initColumnSet
4441 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
4444 _initColumnSet : function(aColumnDefs) {
4445 var oColumn, i, len;
4447 if(this._oColumnSet) {
4448 // First clear _oDynStyles for existing ColumnSet and
4449 // uregister CellEditor Custom Events
4450 for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
4451 oColumn = this._oColumnSet.keys[i];
4452 DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
4453 if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
4454 oColumn.editor.unsubscribeAll();
4458 this._oColumnSet = null;
4459 this._clearTrTemplateEl();
4462 if(lang.isArray(aColumnDefs)) {
4463 this._oColumnSet = new YAHOO.widget.ColumnSet(aColumnDefs);
4465 // Backward compatibility
4466 else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
4467 this._oColumnSet = aColumnDefs;
4468 YAHOO.log("DataTable's constructor now requires an array" +
4469 " of object literal Column definitions instead of a ColumnSet instance",
4470 "warn", this.toString());
4473 // Register CellEditor Custom Events
4474 var allKeys = this._oColumnSet.keys;
4475 for(i=0, len=allKeys.length; i<len; i++) {
4476 oColumn = allKeys[i];
4477 if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
4478 oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
4479 oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
4480 oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
4481 oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
4482 oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
4483 oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
4484 oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
4485 oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
4491 * Initializes DataSource.
4493 * @method _initDataSource
4494 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
4497 _initDataSource : function(oDataSource) {
4498 this._oDataSource = null;
4499 if(oDataSource && (oDataSource instanceof DS)) {
4500 this._oDataSource = oDataSource;
4502 // Backward compatibility
4504 var tmpTable = null;
4505 var tmpContainer = this._elContainer;
4507 //TODO: this will break if re-initing DS at runtime for SDT
4508 // Peek in container child nodes to see if TABLE already exists
4509 if(tmpContainer.hasChildNodes()) {
4510 var tmpChildren = tmpContainer.childNodes;
4511 for(i=0; i<tmpChildren.length; i++) {
4512 if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
4513 tmpTable = tmpChildren[i];
4518 var tmpFieldsArray = [];
4519 for(; i<this._oColumnSet.keys.length; i++) {
4520 tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
4523 this._oDataSource = new DS(tmpTable);
4524 this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
4525 this._oDataSource.responseSchema = {fields: tmpFieldsArray};
4526 YAHOO.log("Null DataSource for progressive enhancement from" +
4527 " markup has been deprecated", "warn", this.toString());
4534 * Initializes RecordSet.
4536 * @method _initRecordSet
4539 _initRecordSet : function() {
4540 if(this._oRecordSet) {
4541 this._oRecordSet.reset();
4544 this._oRecordSet = new YAHOO.widget.RecordSet();
4549 * Initializes DOM elements.
4551 * @method _initDomElements
4552 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
4553 * return {Boolean} False in case of error, otherwise true
4556 _initDomElements : function(elContainer) {
4558 this._initContainerEl(elContainer);
4560 this._initTableEl(this._elContainer);
4562 this._initColgroupEl(this._elTable);
4564 this._initTheadEl(this._elTable);
4567 this._initMsgTbodyEl(this._elTable);
4570 this._initTbodyEl(this._elTable);
4572 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody) {
4573 YAHOO.log("Could not instantiate DataTable due to an invalid DOM elements", "error", this.toString());
4582 * Destroy's the DataTable outer container element, if available.
4584 * @method _destroyContainerEl
4585 * @param elContainer {HTMLElement} Reference to the container element.
4588 _destroyContainerEl : function(elContainer) {
4589 Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
4590 Ev.purgeElement(elContainer, true);
4591 elContainer.innerHTML = "";
4593 this._elContainer = null;
4594 this._elColgroup = null;
4595 this._elThead = null;
4596 this._elTbody = null;
4600 * Initializes the DataTable outer container element, including a mask.
4602 * @method _initContainerEl
4603 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
4606 _initContainerEl : function(elContainer) {
4607 // Validate container
4608 elContainer = Dom.get(elContainer);
4610 if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
4612 this._destroyContainerEl(elContainer);
4614 Dom.addClass(elContainer, DT.CLASS_DATATABLE);
4615 Ev.addListener(elContainer, "focus", this._onTableFocus, this);
4616 Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
4617 this._elContainer = elContainer;
4619 var elMask = document.createElement("div");
4620 elMask.className = DT.CLASS_MASK;
4621 elMask.style.display = "none";
4622 this._elMask = elContainer.appendChild(elMask);
4627 * Destroy's the DataTable TABLE element, if available.
4629 * @method _destroyTableEl
4632 _destroyTableEl : function() {
4633 var elTable = this._elTable;
4635 Ev.purgeElement(elTable, true);
4636 elTable.parentNode.removeChild(elTable);
4637 this._elCaption = null;
4638 this._elColgroup = null;
4639 this._elThead = null;
4640 this._elTbody = null;
4645 * Creates HTML markup CAPTION element.
4647 * @method _initCaptionEl
4648 * @param sCaption {String} Text for caption.
4651 _initCaptionEl : function(sCaption) {
4652 if(this._elTable && sCaption) {
4653 // Create CAPTION element
4654 if(!this._elCaption) {
4655 this._elCaption = this._elTable.createCaption();
4657 // Set CAPTION value
4658 this._elCaption.innerHTML = sCaption;
4660 else if(this._elCaption) {
4661 this._elCaption.parentNode.removeChild(this._elCaption);
4666 * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
4667 * container element.
4669 * @method _initTableEl
4670 * @param elContainer {HTMLElement} Container element into which to create TABLE.
4673 _initTableEl : function(elContainer) {
4676 this._destroyTableEl();
4679 this._elTable = elContainer.appendChild(document.createElement("table"));
4681 // Set SUMMARY attribute
4682 this._elTable.summary = this.get("summary");
4684 // Create CAPTION element
4685 if(this.get("caption")) {
4686 this._initCaptionEl(this.get("caption"));
4692 * Destroy's the DataTable COLGROUP element, if available.
4694 * @method _destroyColgroupEl
4697 _destroyColgroupEl : function() {
4698 var elColgroup = this._elColgroup;
4700 var elTable = elColgroup.parentNode;
4701 Ev.purgeElement(elColgroup, true);
4702 elTable.removeChild(elColgroup);
4703 this._elColgroup = null;
4708 * Initializes COLGROUP and COL elements for managing minWidth.
4710 * @method _initColgroupEl
4711 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4714 _initColgroupEl : function(elTable) {
4717 this._destroyColgroupEl();
4719 // Add COLs to DOCUMENT FRAGMENT
4720 var allCols = this._aColIds || [],
4721 allKeys = this._oColumnSet.keys,
4722 i = 0, len = allCols.length,
4724 elFragment = document.createDocumentFragment(),
4725 elColTemplate = document.createElement("col");
4727 for(i=0,len=allKeys.length; i<len; i++) {
4728 oColumn = allKeys[i];
4729 elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
4733 var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
4734 elColgroup.appendChild(elFragment);
4735 this._elColgroup = elColgroup;
4740 * Adds a COL element to COLGROUP at given index.
4742 * @method _insertColgroupColEl
4743 * @param index {Number} Index of new COL element.
4746 _insertColgroupColEl : function(index) {
4747 if(lang.isNumber(index)&& this._elColgroup) {
4748 var nextSibling = this._elColgroup.childNodes[index] || null;
4749 this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
4754 * Removes a COL element to COLGROUP at given index.
4756 * @method _removeColgroupColEl
4757 * @param index {Number} Index of removed COL element.
4760 _removeColgroupColEl : function(index) {
4761 if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
4762 this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
4767 * Reorders a COL element from old index(es) to new index.
4769 * @method _reorderColgroupColEl
4770 * @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
4771 * @param newIndex {Number} New index.
4774 _reorderColgroupColEl : function(aKeyIndexes, newIndex) {
4775 if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) {
4779 for(i=aKeyIndexes.length-1; i>-1; i--) {
4780 tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
4783 var nextSibling = this._elColgroup.childNodes[newIndex] || null;
4784 for(i=tmpCols.length-1; i>-1; i--) {
4785 this._elColgroup.insertBefore(tmpCols[i], nextSibling);
4791 * Destroy's the DataTable THEAD element, if available.
4793 * @method _destroyTheadEl
4796 _destroyTheadEl : function() {
4797 var elThead = this._elThead;
4799 var elTable = elThead.parentNode;
4800 Ev.purgeElement(elThead, true);
4801 this._destroyColumnHelpers();
4802 elTable.removeChild(elThead);
4803 this._elThead = null;
4808 * Initializes THEAD element.
4810 * @method _initTheadEl
4811 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
4812 * @param {HTMLElement} Initialized THEAD element.
4815 _initTheadEl : function(elTable) {
4816 elTable = elTable || this._elTable;
4820 this._destroyTheadEl();
4822 //TODO: append to DOM later for performance
4823 var elThead = (this._elColgroup) ?
4824 elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
4825 elTable.appendChild(document.createElement("thead"));
4827 // Set up DOM events for THEAD
4828 Ev.addListener(elThead, "focus", this._onTheadFocus, this);
4829 Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
4830 Ev.addListener(elThead, "mouseover", this._onTableMouseover, this);
4831 Ev.addListener(elThead, "mouseout", this._onTableMouseout, this);
4832 Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
4833 Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
4834 Ev.addListener(elThead, "click", this._onTheadClick, this);
4836 // Since we can't listen for click and dblclick on the same element...
4837 // Attach separately to THEAD and TBODY
4838 ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
4840 var oColumnSet = this._oColumnSet,
4843 // Add TRs to the THEAD
4844 var colTree = oColumnSet.tree;
4846 for(i=0; i<colTree.length; i++) {
4847 var elTheadTr = elThead.appendChild(document.createElement("tr"));
4849 // ...and create TH cells
4850 for(j=0; j<colTree[i].length; j++) {
4851 oColumn = colTree[i][j];
4852 elTh = elTheadTr.appendChild(document.createElement("th"));
4853 this._initThEl(elTh,oColumn);
4856 // Set FIRST/LAST on THEAD rows
4858 Dom.addClass(elTheadTr, DT.CLASS_FIRST);
4860 if(i === (colTree.length-1)) {
4861 Dom.addClass(elTheadTr, DT.CLASS_LAST);
4866 // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
4867 var aFirstHeaders = oColumnSet.headers[0] || [];
4868 for(i=0; i<aFirstHeaders.length; i++) {
4869 Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
4871 var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
4872 for(i=0; i<aLastHeaders.length; i++) {
4873 Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
4876 YAHOO.log("TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
4878 ///TODO: try _repaintGecko(this._elContainer) instead
4880 if(ua.webkit && ua.webkit < 420) {
4882 setTimeout(function() {
4883 elThead.style.display = "";
4885 elThead.style.display = 'none';
4888 this._elThead = elThead;
4890 // Column helpers needs _elThead to exist
4891 this._initColumnHelpers();
4896 * Populates TH element as defined by Column.
4899 * @param elTh {HTMLElement} TH element reference.
4900 * @param oColumn {YAHOO.widget.Column} Column object.
4903 _initThEl : function(elTh, oColumn) {
4904 elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
4905 elTh.innerHTML = "";
4906 elTh.rowSpan = oColumn.getRowspan();
4907 elTh.colSpan = oColumn.getColspan();
4908 oColumn._elTh = elTh;
4910 var elThLiner = elTh.appendChild(document.createElement("div"));
4911 elThLiner.id = elTh.id + "-liner"; // Needed for resizer
4912 elThLiner.className = DT.CLASS_LINER;
4913 oColumn._elThLiner = elThLiner;
4915 var elThLabel = elThLiner.appendChild(document.createElement("span"));
4916 elThLabel.className = DT.CLASS_LABEL;
4918 // Assign abbr attribute
4920 elTh.abbr = oColumn.abbr;
4922 // Clear minWidth on hidden Columns
4923 if(oColumn.hidden) {
4924 this._clearMinWidth(oColumn);
4927 elTh.className = this._getColumnClassNames(oColumn);
4929 // Set Column width...
4931 // Validate minWidth
4932 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
4933 oColumn.minWidth : oColumn.width;
4934 // ...for fallback cases
4935 if(DT._bDynStylesFallback) {
4936 elTh.firstChild.style.overflow = 'hidden';
4937 elTh.firstChild.style.width = nWidth + 'px';
4939 // ...for non fallback cases
4941 this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
4945 this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
4946 oColumn._elThLabel = elThLabel;
4950 * Outputs markup into the given TH based on given Column.
4952 * @method DataTable.formatTheadCell
4953 * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
4954 * not the liner DIV element.
4955 * @param oColumn {YAHOO.widget.Column} Column instance.
4956 * @param oSortedBy {Object} Sort state object literal.
4958 formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
4959 var sKey = oColumn.getKey();
4960 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
4962 // Add accessibility link for sortable Columns
4963 if(oColumn.sortable) {
4964 // Calculate the direction
4965 var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
4966 var bDesc = (sSortClass === DT.CLASS_DESC);
4968 // This is the sorted Column
4969 if(oSortedBy && (oColumn.key === oSortedBy.key)) {
4970 bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
4973 // Generate a unique HREF for visited status
4974 var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
4976 // Generate a dynamic TITLE for sort status
4977 var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
4979 // Format the element
4980 elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
4982 // Just display the label for non-sortable Columns
4984 elCellLabel.innerHTML = sLabel;
4989 * Disables DD from top-level Column TH elements.
4991 * @method _destroyDraggableColumns
4994 _destroyDraggableColumns : function() {
4996 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
4997 oColumn = this._oColumnSet.tree[0][i];
4999 oColumn._dd = oColumn._dd.unreg();
5000 Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);
5006 * Initializes top-level Column TH elements into DD instances.
5008 * @method _initDraggableColumns
5011 _initDraggableColumns : function() {
5012 this._destroyDraggableColumns();
5014 var oColumn, elTh, elDragTarget;
5015 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
5016 oColumn = this._oColumnSet.tree[0][i];
5017 elTh = oColumn.getThEl();
5018 Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
5019 elDragTarget = DT._initColumnDragTargetEl();
5020 oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
5024 YAHOO.log("Could not find DragDrop for draggable Columns", "warn", this.toString());
5029 * Disables resizeability on key Column TH elements.
5031 * @method _destroyResizeableColumns
5034 _destroyResizeableColumns : function() {
5035 var aKeys = this._oColumnSet.keys;
5036 for(var i=0, len=aKeys.length; i<len; i++) {
5037 if(aKeys[i]._ddResizer) {
5038 aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
5039 Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
5045 * Initializes resizeability on key Column TH elements.
5047 * @method _initResizeableColumns
5050 _initResizeableColumns : function() {
5051 this._destroyResizeableColumns();
5053 var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
5054 for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
5055 oColumn = this._oColumnSet.keys[i];
5056 if(oColumn.resizeable) {
5057 elTh = oColumn.getThEl();
5058 Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
5059 elThLiner = oColumn.getThLinerEl();
5061 // Bug 1915349: So resizer is as tall as TH when rowspan > 1
5062 // Create a separate resizer liner with position:relative
5063 elThResizerLiner = elTh.appendChild(document.createElement("div"));
5064 elThResizerLiner.className = DT.CLASS_RESIZERLINER;
5066 // Move TH contents into the new resizer liner
5067 elThResizerLiner.appendChild(elThLiner);
5069 // Create the resizer
5070 elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
5071 elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
5072 elThResizer.className = DT.CLASS_RESIZER;
5073 oColumn._elResizer = elThResizer;
5075 // Create the resizer proxy, once globally
5076 elResizerProxy = DT._initColumnResizerProxyEl();
5077 oColumn._ddResizer = new YAHOO.util.ColumnResizer(
5078 this, oColumn, elTh, elThResizer, elResizerProxy);
5079 cancelClick = function(e) {
5080 Ev.stopPropagation(e);
5082 Ev.addListener(elThResizer,"click",cancelClick);
5087 YAHOO.log("Could not find DragDrop for resizeable Columns", "warn", this.toString());
5092 * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
5094 * @method _destroyColumnHelpers
5097 _destroyColumnHelpers : function() {
5098 this._destroyDraggableColumns();
5099 this._destroyResizeableColumns();
5103 * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
5105 * @method _initColumnHelpers
5108 _initColumnHelpers : function() {
5109 if(this.get("draggableColumns")) {
5110 this._initDraggableColumns();
5112 this._initResizeableColumns();
5116 * Destroy's the DataTable TBODY element, if available.
5118 * @method _destroyTbodyEl
5121 _destroyTbodyEl : function() {
5122 var elTbody = this._elTbody;
5124 var elTable = elTbody.parentNode;
5125 Ev.purgeElement(elTbody, true);
5126 elTable.removeChild(elTbody);
5127 this._elTbody = null;
5132 * Initializes TBODY element for data.
5134 * @method _initTbodyEl
5135 * @param elTable {HTMLElement} TABLE element into which to create TBODY .
5138 _initTbodyEl : function(elTable) {
5141 this._destroyTbodyEl();
5144 var elTbody = elTable.appendChild(document.createElement("tbody"));
5145 elTbody.tabIndex = 0;
5146 elTbody.className = DT.CLASS_DATA;
5148 // Set up DOM events for TBODY
5149 Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
5150 Ev.addListener(elTbody, "mouseover", this._onTableMouseover, this);
5151 Ev.addListener(elTbody, "mouseout", this._onTableMouseout, this);
5152 Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
5153 Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
5154 Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
5155 Ev.addListener(elTbody, "keypress", this._onTableKeypress, this);
5156 Ev.addListener(elTbody, "click", this._onTbodyClick, this);
5158 // Since we can't listen for click and dblclick on the same element...
5159 // Attach separately to THEAD and TBODY
5160 ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
5163 // IE puts focus outline in the wrong place
5165 elTbody.hideFocus=true;
5168 this._elTbody = elTbody;
5173 * Destroy's the DataTable message TBODY element, if available.
5175 * @method _destroyMsgTbodyEl
5178 _destroyMsgTbodyEl : function() {
5179 var elMsgTbody = this._elMsgTbody;
5181 var elTable = elMsgTbody.parentNode;
5182 Ev.purgeElement(elMsgTbody, true);
5183 elTable.removeChild(elMsgTbody);
5184 this._elTbody = null;
5189 * Initializes TBODY element for messaging.
5191 * @method _initMsgTbodyEl
5192 * @param elTable {HTMLElement} TABLE element into which to create TBODY
5195 _initMsgTbodyEl : function(elTable) {
5197 var elMsgTbody = document.createElement("tbody");
5198 elMsgTbody.className = DT.CLASS_MESSAGE;
5199 var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
5200 elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5201 this._elMsgTr = elMsgTr;
5202 var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
5203 elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
5204 elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
5205 this._elMsgTd = elMsgTd;
5206 elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
5207 var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
5208 elMsgLiner.className = DT.CLASS_LINER;
5209 this._elMsgTbody = elMsgTbody;
5214 * Initialize internal event listeners
5216 * @method _initEvents
5219 _initEvents : function () {
5220 // Initialize Column sort
5221 this._initColumnSort();
5223 // Add the document level click listener
5224 YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
5226 // Paginator integration
5227 this.subscribe("paginatorChange",function () {
5228 this._handlePaginatorChange.apply(this,arguments);
5231 this.subscribe("initEvent",function () {
5232 this.renderPaginator();
5235 // Initialize CellEditor integration
5236 this._initCellEditing();
5240 * Initializes Column sorting.
5242 * @method _initColumnSort
5245 _initColumnSort : function() {
5246 this.subscribe("theadCellClickEvent", this.onEventSortColumn);
5248 // Backward compatibility
5249 var oSortedBy = this.get("sortedBy");
5251 if(oSortedBy.dir == "desc") {
5252 this._configs.sortedBy.value.dir = DT.CLASS_DESC;
5254 else if(oSortedBy.dir == "asc") {
5255 this._configs.sortedBy.value.dir = DT.CLASS_ASC;
5261 * Initializes CellEditor integration.
5263 * @method _initCellEditing
5266 _initCellEditing : function() {
5267 this.subscribe("editorBlurEvent",function () {
5268 this.onEditorBlurEvent.apply(this,arguments);
5270 this.subscribe("editorBlockEvent",function () {
5271 this.onEditorBlockEvent.apply(this,arguments);
5273 this.subscribe("editorUnblockEvent",function () {
5274 this.onEditorUnblockEvent.apply(this,arguments);
5310 // DOM MUTATION FUNCTIONS
5313 * Retruns classnames to represent current Column states.
5314 * @method _getColumnClassnames
5315 * @param oColumn {YAHOO.widget.Column} Column instance.
5316 * @param aAddClasses {String[]} An array of additional classnames to add to the
5318 * @return {String} A String of classnames to be assigned to TH or TD elements
5322 _getColumnClassNames : function (oColumn, aAddClasses) {
5326 if(lang.isString(oColumn.className)) {
5327 // Single custom class
5328 allClasses = [oColumn.className];
5330 else if(lang.isArray(oColumn.className)) {
5331 // Array of custom classes
5332 allClasses = oColumn.className;
5335 // no custom classes
5339 // Hook for setting width with via dynamic style uses key since ID is too disposable
5340 allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
5342 // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
5343 allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
5345 var isSortedBy = this.get("sortedBy") || {};
5347 if(oColumn.key === isSortedBy.key) {
5348 allClasses[allClasses.length] = isSortedBy.dir || '';
5351 if(oColumn.hidden) {
5352 allClasses[allClasses.length] = DT.CLASS_HIDDEN;
5355 if(oColumn.selected) {
5356 allClasses[allClasses.length] = DT.CLASS_SELECTED;
5359 if(oColumn.sortable) {
5360 allClasses[allClasses.length] = DT.CLASS_SORTABLE;
5363 if(oColumn.resizeable) {
5364 allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
5367 if(oColumn.editor) {
5368 allClasses[allClasses.length] = DT.CLASS_EDITABLE;
5371 // Addtnl classes, including First/Last
5373 allClasses = allClasses.concat(aAddClasses);
5376 return allClasses.join(' ');
5380 * Clears TR element template in response to any Column state change.
5381 * @method _clearTrTemplateEl
5384 _clearTrTemplateEl : function () {
5385 this._elTrTemplate = null;
5389 * Returns a new TR element template with TD elements classed with current
5391 * @method _getTrTemplateEl
5392 * @return {HTMLElement} A TR element to be cloned and added to the DOM.
5395 _getTrTemplateEl : function (oRecord, index) {
5396 // Template is already available
5397 if(this._elTrTemplate) {
5398 return this._elTrTemplate;
5400 // Template needs to be created
5403 tr = d.createElement('tr'),
5404 td = d.createElement('td'),
5405 div = d.createElement('div');
5407 // Append the liner element
5408 td.appendChild(div);
5410 // Create TD elements into DOCUMENT FRAGMENT
5411 var df = document.createDocumentFragment(),
5412 allKeys = this._oColumnSet.keys,
5415 // Set state for each TD;
5417 for(var i=0, keysLen=allKeys.length; i<keysLen; i++) {
5418 // Clone the TD template
5419 elTd = td.cloneNode(true);
5421 // Format the base TD
5422 elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
5424 df.appendChild(elTd);
5427 this._elTrTemplate = tr;
5433 * Formats a basic TD element.
5434 * @method _formatTdEl
5435 * @param oColumn {YAHOO.widget.Column} Associated Column instance.
5436 * @param elTd {HTMLElement} An unformatted TD element.
5437 * @param index {Number} Column key index.
5438 * @param isLast {Boolean} True if Column is last key of the ColumnSet.
5439 * @return {HTMLElement} A formatted TD element.
5442 _formatTdEl : function (oColumn, elTd, index, isLast) {
5443 var oColumnSet = this._oColumnSet;
5445 // Set the TD's accessibility headers
5446 var allHeaders = oColumnSet.headers,
5447 allColHeaders = allHeaders[index],
5450 for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
5451 sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
5452 sTdHeaders += sHeader;
5454 elTd.headers = sTdHeaders;
5456 // Class the TD element
5457 var aAddClasses = [];
5459 aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
5462 aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
5464 elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
5466 // Class the liner element
5467 elTd.firstChild.className = DT.CLASS_LINER;
5469 // Set Column width for fallback cases
5470 if(oColumn.width && DT._bDynStylesFallback) {
5471 // Validate minWidth
5472 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
5473 oColumn.minWidth : oColumn.width;
5474 elTd.firstChild.style.overflow = 'hidden';
5475 elTd.firstChild.style.width = nWidth + 'px';
5483 * Create a new TR element for a given Record and appends it with the correct
5484 * number of Column-state-classed TD elements. Striping is the responsibility of
5485 * the calling function, which may decide to stripe the single row, a subset of
5486 * rows, or all the rows.
5487 * @method _createTrEl
5488 * @param oRecord {YAHOO.widget.Record} Record instance
5489 * @return {HTMLElement} The new TR element. This must be added to the DOM.
5492 _addTrEl : function (oRecord) {
5493 var elTrTemplate = this._getTrTemplateEl();
5495 // Clone the TR template.
5496 var elTr = elTrTemplate.cloneNode(true);
5499 return this._updateTrEl(elTr,oRecord);
5503 * Formats the contents of the given TR's TD elements with data from the given
5504 * Record. Only innerHTML should change, nothing structural.
5506 * @method _updateTrEl
5507 * @param elTr {HTMLElement} The TR element to update.
5508 * @param oRecord {YAHOO.widget.Record} The associated Record instance.
5509 * @return {HTMLElement} DOM reference to the new TR element.
5512 _updateTrEl : function(elTr, oRecord) {
5513 var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
5515 // Hide the row to prevent constant reflows
5516 elTr.style.display = 'none';
5518 // Update TD elements with new data
5519 var allTds = elTr.childNodes,
5521 for(var i=0,len=allTds.length; i<len; ++i) {
5524 // Set the cell content
5525 this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
5528 // Redisplay the row for reflow
5529 elTr.style.display = '';
5532 elTr.id = oRecord.getId(); // Needed for Record association and tracking of FIRST/LAST
5538 * Deletes TR element by DOM reference or by DataTable page row index.
5540 * @method _deleteTrEl
5541 * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
5542 * @return {Boolean} Returns true if successful, else returns false.
5545 _deleteTrEl : function(row) {
5548 // Get page row index for the element
5549 if(!lang.isNumber(row)) {
5550 rowIndex = Dom.get(row).sectionRowIndex;
5555 if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
5556 // Cannot use tbody.deleteRow due to IE6 instability
5557 //return this._elTbody.deleteRow(rowIndex);
5558 return this._elTbody.removeChild(this.getTrEl(row));
5591 // CSS/STATE FUNCTIONS
5597 * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
5598 * of the DataTable page and updates internal tracker.
5600 * @method _unsetFirstRow
5603 _unsetFirstRow : function() {
5605 if(this._sFirstTrId) {
5606 Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
5607 this._sFirstTrId = null;
5612 * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
5613 * of the DataTable page and updates internal tracker.
5615 * @method _setFirstRow
5618 _setFirstRow : function() {
5619 this._unsetFirstRow();
5620 var elTr = this.getFirstTrEl();
5623 Dom.addClass(elTr, DT.CLASS_FIRST);
5624 this._sFirstTrId = elTr.id;
5629 * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
5630 * of the DataTable page and updates internal tracker.
5632 * @method _unsetLastRow
5635 _unsetLastRow : function() {
5636 // Unassign previous class
5637 if(this._sLastTrId) {
5638 Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
5639 this._sLastTrId = null;
5644 * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
5645 * of the DataTable page and updates internal tracker.
5647 * @method _setLastRow
5650 _setLastRow : function() {
5651 this._unsetLastRow();
5652 var elTr = this.getLastTrEl();
5655 Dom.addClass(elTr, DT.CLASS_LAST);
5656 this._sLastTrId = elTr.id;
5661 * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
5663 * @method _setRowStripes
5664 * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
5665 * or string ID, or page row index of where to start striping.
5666 * @param range {Number} (optional) If given, how many rows to stripe, otherwise
5667 * stripe all the rows until the end.
5670 _setRowStripes : function(row, range) {
5671 // Default values stripe all rows
5672 var allRows = this._elTbody.rows,
5674 nEndIndex = allRows.length,
5675 aOdds = [], nOddIdx = 0,
5676 aEvens = [], nEvenIdx = 0;
5679 if((row !== null) && (row !== undefined)) {
5680 // Validate given start row
5681 var elStartRow = this.getTrEl(row);
5683 nStartIndex = elStartRow.sectionRowIndex;
5685 // Validate given range
5686 if(lang.isNumber(range) && (range > 1)) {
5687 nEndIndex = nStartIndex + range;
5692 for(var i=nStartIndex; i<nEndIndex; i++) {
5694 aOdds[nOddIdx++] = allRows[i];
5696 aEvens[nEvenIdx++] = allRows[i];
5701 Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
5704 if (aEvens.length) {
5705 Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
5710 * Assigns the class DT.CLASS_SELECTED to TR and TD elements.
5712 * @method _setSelections
5715 _setSelections : function() {
5716 // Keep track of selected rows
5717 var allSelectedRows = this.getSelectedRows();
5718 // Keep track of selected cells
5719 var allSelectedCells = this.getSelectedCells();
5720 // Anything to select?
5721 if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
5722 var oColumnSet = this._oColumnSet,
5724 // Loop over each row
5725 for(var i=0; i<allSelectedRows.length; i++) {
5726 el = Dom.get(allSelectedRows[i]);
5728 Dom.addClass(el, DT.CLASS_SELECTED);
5731 // Loop over each cell
5732 for(i=0; i<allSelectedCells.length; i++) {
5733 el = Dom.get(allSelectedCells[i].recordId);
5735 Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
5783 /////////////////////////////////////////////////////////////////////////////
5785 // Private DOM Event Handlers
5787 /////////////////////////////////////////////////////////////////////////////
5790 * Validates minWidths whenever the render chain ends.
5792 * @method _onRenderChainEnd
5795 _onRenderChainEnd : function() {
5796 // Hide loading message
5797 this.hideTableMessage();
5799 // Show empty message
5800 if(this._elTbody.rows.length === 0) {
5801 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
5804 // Execute in timeout thread to give implementers a chance
5805 // to subscribe after the constructor
5807 setTimeout(function() {
5808 if((oSelf instanceof DT) && oSelf._sId) {
5811 oSelf._bInit = false;
5812 oSelf.fireEvent("initEvent");
5816 oSelf.fireEvent("renderEvent");
5817 // Backward compatibility
5818 oSelf.fireEvent("refreshEvent");
5819 YAHOO.log("DataTable rendered", "info", oSelf.toString());
5821 // Post-render routine
5822 oSelf.validateColumnWidths();
5824 // Post-render event
5825 oSelf.fireEvent("postRenderEvent");
5827 /*if(YAHOO.example.Performance.trialStart) {
5828 YAHOO.log((new Date()).getTime() - YAHOO.example.Performance.trialStart.getTime() + " ms", "time");
5829 YAHOO.example.Performance.trialStart = null;
5832 YAHOO.log("Post-render routine executed", "info", oSelf.toString());
5838 * Handles click events on the DOCUMENT.
5840 * @method _onDocumentClick
5841 * @param e {HTMLEvent} The click event.
5842 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5845 _onDocumentClick : function(e, oSelf) {
5846 var elTarget = Ev.getTarget(e);
5847 var elTag = elTarget.nodeName.toLowerCase();
5849 if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
5850 oSelf.fireEvent("tableBlurEvent");
5852 // Fires editorBlurEvent when click is not within the TABLE.
5853 // For cases when click is within the TABLE, due to timing issues,
5854 // the editorBlurEvent needs to get fired by the lower-level DOM click
5855 // handlers below rather than by the TABLE click handler directly.
5856 if(oSelf._oCellEditor) {
5857 if(oSelf._oCellEditor.getContainerEl) {
5858 var elContainer = oSelf._oCellEditor.getContainerEl();
5859 // Only if the click was not within the CellEditor container
5860 if(!Dom.isAncestor(elContainer, elTarget) &&
5861 (elContainer.id !== elTarget.id)) {
5862 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
5865 // Backward Compatibility
5866 else if(oSelf._oCellEditor.isActive) {
5867 // Only if the click was not within the Cell Editor container
5868 if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
5869 (oSelf._oCellEditor.container.id !== elTarget.id)) {
5870 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
5878 * Handles focus events on the DataTable instance.
5880 * @method _onTableFocus
5881 * @param e {HTMLEvent} The focus event.
5882 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5885 _onTableFocus : function(e, oSelf) {
5886 oSelf.fireEvent("tableFocusEvent");
5890 * Handles focus events on the THEAD element.
5892 * @method _onTheadFocus
5893 * @param e {HTMLEvent} The focus event.
5894 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5897 _onTheadFocus : function(e, oSelf) {
5898 oSelf.fireEvent("theadFocusEvent");
5899 oSelf.fireEvent("tableFocusEvent");
5903 * Handles focus events on the TBODY element.
5905 * @method _onTbodyFocus
5906 * @param e {HTMLEvent} The focus event.
5907 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5910 _onTbodyFocus : function(e, oSelf) {
5911 oSelf.fireEvent("tbodyFocusEvent");
5912 oSelf.fireEvent("tableFocusEvent");
5916 * Handles mouseover events on the DataTable instance.
5918 * @method _onTableMouseover
5919 * @param e {HTMLEvent} The mouseover event.
5920 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5923 _onTableMouseover : function(e, oSelf) {
5924 var elTarget = Ev.getTarget(e);
5925 var elTag = elTarget.nodeName.toLowerCase();
5926 var bKeepBubbling = true;
5927 while(elTarget && (elTag != "table")) {
5934 bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
5937 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5938 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
5939 // Backward compatibility
5940 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
5944 bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
5945 // Backward compatibility
5946 bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
5949 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
5950 bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
5951 // Backward compatibility
5952 bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
5955 bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
5961 if(bKeepBubbling === false) {
5965 elTarget = elTarget.parentNode;
5967 elTag = elTarget.nodeName.toLowerCase();
5971 oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
5975 * Handles mouseout events on the DataTable instance.
5977 * @method _onTableMouseout
5978 * @param e {HTMLEvent} The mouseout event.
5979 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
5982 _onTableMouseout : function(e, oSelf) {
5983 var elTarget = Ev.getTarget(e);
5984 var elTag = elTarget.nodeName.toLowerCase();
5985 var bKeepBubbling = true;
5986 while(elTarget && (elTag != "table")) {
5993 bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
5996 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
5997 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
5998 // Backward compatibility
5999 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
6003 bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
6004 // Backward compatibility
6005 bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
6008 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6009 bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
6010 // Backward compatibility
6011 bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
6014 bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
6020 if(bKeepBubbling === false) {
6024 elTarget = elTarget.parentNode;
6026 elTag = elTarget.nodeName.toLowerCase();
6030 oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
6034 * Handles mousedown events on the DataTable instance.
6036 * @method _onTableMousedown
6037 * @param e {HTMLEvent} The mousedown event.
6038 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6041 _onTableMousedown : function(e, oSelf) {
6042 var elTarget = Ev.getTarget(e);
6043 var elTag = elTarget.nodeName.toLowerCase();
6044 var bKeepBubbling = true;
6045 while(elTarget && (elTag != "table")) {
6052 bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
6055 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6056 bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
6057 // Backward compatibility
6058 bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
6062 bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
6063 // Backward compatibility
6064 bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
6067 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6068 bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
6069 // Backward compatibility
6070 bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
6073 bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
6079 if(bKeepBubbling === false) {
6083 elTarget = elTarget.parentNode;
6085 elTag = elTarget.nodeName.toLowerCase();
6089 oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
6093 * Handles mouseup events on the DataTable instance.
6095 * @method _onTableMouseup
6096 * @param e {HTMLEvent} The mouseup event.
6097 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6100 _onTableMouseup : function(e, oSelf) {
6101 var elTarget = Ev.getTarget(e);
6102 var elTag = elTarget.nodeName.toLowerCase();
6103 var bKeepBubbling = true;
6104 while(elTarget && (elTag != "table")) {
6111 bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
6114 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6115 bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
6116 // Backward compatibility
6117 bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
6121 bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
6122 // Backward compatibility
6123 bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
6126 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6127 bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
6128 // Backward compatibility
6129 bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
6132 bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
6138 if(bKeepBubbling === false) {
6142 elTarget = elTarget.parentNode;
6144 elTag = elTarget.nodeName.toLowerCase();
6148 oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
6152 * Handles dblclick events on the DataTable instance.
6154 * @method _onTableDblclick
6155 * @param e {HTMLEvent} The dblclick event.
6156 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6159 _onTableDblclick : function(e, oSelf) {
6160 var elTarget = Ev.getTarget(e);
6161 var elTag = elTarget.nodeName.toLowerCase();
6162 var bKeepBubbling = true;
6163 while(elTarget && (elTag != "table")) {
6168 bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
6171 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6172 bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
6173 // Backward compatibility
6174 bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
6178 bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
6179 // Backward compatibility
6180 bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
6183 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
6184 bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
6185 // Backward compatibility
6186 bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
6189 bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
6195 if(bKeepBubbling === false) {
6199 elTarget = elTarget.parentNode;
6201 elTag = elTarget.nodeName.toLowerCase();
6205 oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6208 * Handles keydown events on the THEAD element.
6210 * @method _onTheadKeydown
6211 * @param e {HTMLEvent} The key event.
6212 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6215 _onTheadKeydown : function(e, oSelf) {
6216 var elTarget = Ev.getTarget(e);
6217 var elTag = elTarget.nodeName.toLowerCase();
6218 var bKeepBubbling = true;
6219 while(elTarget && (elTag != "table")) {
6225 // TODO: implement textareaKeyEvent
6228 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
6233 if(bKeepBubbling === false) {
6237 elTarget = elTarget.parentNode;
6239 elTag = elTarget.nodeName.toLowerCase();
6243 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6247 * Handles keydown events on the TBODY element. Handles selection behavior,
6248 * provides hooks for ENTER to edit functionality.
6250 * @method _onTbodyKeydown
6251 * @param e {HTMLEvent} The key event.
6252 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6255 _onTbodyKeydown : function(e, oSelf) {
6256 var sMode = oSelf.get("selectionMode");
6258 if(sMode == "standard") {
6259 oSelf._handleStandardSelectionByKey(e);
6261 else if(sMode == "single") {
6262 oSelf._handleSingleSelectionByKey(e);
6264 else if(sMode == "cellblock") {
6265 oSelf._handleCellBlockSelectionByKey(e);
6267 else if(sMode == "cellrange") {
6268 oSelf._handleCellRangeSelectionByKey(e);
6270 else if(sMode == "singlecell") {
6271 oSelf._handleSingleCellSelectionByKey(e);
6274 if(oSelf._oCellEditor) {
6275 if(oSelf._oCellEditor.fireEvent) {
6276 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6278 else if(oSelf._oCellEditor.isActive) {
6279 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6283 var elTarget = Ev.getTarget(e);
6284 var elTag = elTarget.nodeName.toLowerCase();
6285 var bKeepBubbling = true;
6286 while(elTarget && (elTag != "table")) {
6291 bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
6296 if(bKeepBubbling === false) {
6300 elTarget = elTarget.parentNode;
6302 elTag = elTarget.nodeName.toLowerCase();
6306 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
6310 * Handles keypress events on the TABLE. Mainly to support stopEvent on Mac.
6312 * @method _onTableKeypress
6313 * @param e {HTMLEvent} The key event.
6314 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6317 _onTableKeypress : function(e, oSelf) {
6318 if(ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") !== -1) && (ua.webkit < 420)) {
6319 var nKey = Ev.getCharCode(e);
6325 else if(nKey == 38) {
6332 * Handles click events on the THEAD element.
6334 * @method _onTheadClick
6335 * @param e {HTMLEvent} The click event.
6336 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6339 _onTheadClick : function(e, oSelf) {
6340 // This blurs the CellEditor
6341 if(oSelf._oCellEditor) {
6342 if(oSelf._oCellEditor.fireEvent) {
6343 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6345 // Backward compatibility
6346 else if(oSelf._oCellEditor.isActive) {
6347 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6351 var elTarget = Ev.getTarget(e),
6352 elTag = elTarget.nodeName.toLowerCase(),
6353 bKeepBubbling = true;
6354 while(elTarget && (elTag != "table")) {
6359 var sType = elTarget.type.toLowerCase();
6360 if(sType == "checkbox") {
6361 bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
6363 else if(sType == "radio") {
6364 bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
6366 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6367 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6371 bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
6374 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
6377 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
6378 bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
6379 // Backward compatibility
6380 bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
6384 bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
6385 // Backward compatibility
6386 bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
6389 bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
6390 // Backward compatibility
6391 bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
6396 if(bKeepBubbling === false) {
6400 elTarget = elTarget.parentNode;
6402 elTag = elTarget.nodeName.toLowerCase();
6406 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6410 * Handles click events on the primary TBODY element.
6412 * @method _onTbodyClick
6413 * @param e {HTMLEvent} The click event.
6414 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6417 _onTbodyClick : function(e, oSelf) {
6418 // This blurs the CellEditor
6419 if(oSelf._oCellEditor) {
6420 if(oSelf._oCellEditor.fireEvent) {
6421 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
6423 else if(oSelf._oCellEditor.isActive) {
6424 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
6428 // Fire Custom Events
6429 var elTarget = Ev.getTarget(e),
6430 elTag = elTarget.nodeName.toLowerCase(),
6431 bKeepBubbling = true;
6432 while(elTarget && (elTag != "table")) {
6437 var sType = elTarget.type.toLowerCase();
6438 if(sType == "checkbox") {
6439 bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
6441 else if(sType == "radio") {
6442 bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
6444 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
6445 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6449 bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
6452 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
6455 bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
6458 bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
6463 if(bKeepBubbling === false) {
6467 elTarget = elTarget.parentNode;
6469 elTag = elTarget.nodeName.toLowerCase();
6473 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
6477 * Handles change events on SELECT elements within DataTable.
6479 * @method _onDropdownChange
6480 * @param e {HTMLEvent} The change event.
6481 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
6484 _onDropdownChange : function(e, oSelf) {
6485 var elTarget = Ev.getTarget(e);
6486 oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
6520 /////////////////////////////////////////////////////////////////////////////
6522 // Public member variables
6524 /////////////////////////////////////////////////////////////////////////////
6526 * Returns object literal of initial configs.
6535 /////////////////////////////////////////////////////////////////////////////
6539 /////////////////////////////////////////////////////////////////////////////
6542 * Returns unique id assigned to instance, which is a useful prefix for
6543 * generating unique DOM ID strings.
6546 * @return {String} Unique ID of the DataSource instance.
6548 getId : function() {
6553 * DataSource instance name, for logging.
6556 * @return {String} Unique name of the DataSource instance.
6559 toString : function() {
6560 return "DataTable instance " + this._sId;
6564 * Returns the DataTable instance's DataSource instance.
6566 * @method getDataSource
6567 * @return {YAHOO.util.DataSource} DataSource instance.
6569 getDataSource : function() {
6570 return this._oDataSource;
6574 * Returns the DataTable instance's ColumnSet instance.
6576 * @method getColumnSet
6577 * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
6579 getColumnSet : function() {
6580 return this._oColumnSet;
6584 * Returns the DataTable instance's RecordSet instance.
6586 * @method getRecordSet
6587 * @return {YAHOO.widget.RecordSet} RecordSet instance.
6589 getRecordSet : function() {
6590 return this._oRecordSet;
6594 * Returns on object literal representing the DataTable instance's current
6595 * state with the following properties:
6597 * <dt>pagination</dt>
6598 * <dd>Instance of YAHOO.widget.Paginator</dd>
6603 * <dt>sortedBy.key</dt>
6604 * <dd>{String} Key of sorted Column</dd>
6605 * <dt>sortedBy.dir</dt>
6606 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
6610 * <dt>selectedRows</dt>
6611 * <dd>Array of selected rows by Record ID.</dd>
6613 * <dt>selectedCells</dt>
6614 * <dd>Selected cells as an array of object literals:
6615 * {recordId:sRecordId, columnKey:sColumnKey}</dd>
6619 * @return {Object} DataTable instance state object literal values.
6621 getState : function() {
6623 totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
6624 pagination: this.get("paginator") ? this.get("paginator").getState() : null,
6625 sortedBy: this.get("sortedBy"),
6626 selectedRows: this.getSelectedRows(),
6627 selectedCells: this.getSelectedCells()
6676 * Returns DOM reference to the DataTable's container element.
6678 * @method getContainerEl
6679 * @return {HTMLElement} Reference to DIV element.
6681 getContainerEl : function() {
6682 return this._elContainer;
6686 * Returns DOM reference to the DataTable's TABLE element.
6688 * @method getTableEl
6689 * @return {HTMLElement} Reference to TABLE element.
6691 getTableEl : function() {
6692 return this._elTable;
6696 * Returns DOM reference to the DataTable's THEAD element.
6698 * @method getTheadEl
6699 * @return {HTMLElement} Reference to THEAD element.
6701 getTheadEl : function() {
6702 return this._elThead;
6706 * Returns DOM reference to the DataTable's primary TBODY element.
6708 * @method getTbodyEl
6709 * @return {HTMLElement} Reference to TBODY element.
6711 getTbodyEl : function() {
6712 return this._elTbody;
6716 * Returns DOM reference to the DataTable's secondary TBODY element that is
6717 * used to display messages.
6719 * @method getMsgTbodyEl
6720 * @return {HTMLElement} Reference to TBODY element.
6722 getMsgTbodyEl : function() {
6723 return this._elMsgTbody;
6727 * Returns DOM reference to the TD element within the secondary TBODY that is
6728 * used to display messages.
6730 * @method getMsgTdEl
6731 * @return {HTMLElement} Reference to TD element.
6733 getMsgTdEl : function() {
6734 return this._elMsgTd;
6738 * Returns the corresponding TR reference for a given DOM element, ID string or
6739 * directly page row index. If the given identifier is a child of a TR element,
6740 * then DOM tree is traversed until a parent TR element is returned, otherwise
6744 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
6745 * get: by element reference, ID string, page row index, or Record.
6746 * @return {HTMLElement} Reference to TR element, or null.
6748 getTrEl : function(row) {
6750 if(row instanceof YAHOO.widget.Record) {
6751 return document.getElementById(row.getId());
6753 // By page row index
6754 else if(lang.isNumber(row)) {
6755 var allRows = this._elTbody.rows;
6756 return ((row > -1) && (row < allRows.length)) ? allRows[row] : null;
6758 // By ID string or element reference
6760 var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
6762 // Validate HTML element
6763 if(elRow && (elRow.ownerDocument == document)) {
6764 // Validate TR element
6765 if(elRow.nodeName.toLowerCase() != "tr") {
6766 // Traverse up the DOM to find the corresponding TR element
6767 elRow = Dom.getAncestorByTagName(elRow,"tr");
6778 * Returns DOM reference to the first TR element in the DataTable page, or null.
6780 * @method getFirstTrEl
6781 * @return {HTMLElement} Reference to TR element.
6783 getFirstTrEl : function() {
6784 return this._elTbody.rows[0] || null;
6788 * Returns DOM reference to the last TR element in the DataTable page, or null.
6790 * @method getLastTrEl
6791 * @return {HTMLElement} Reference to last TR element.
6793 getLastTrEl : function() {
6794 var allRows = this._elTbody.rows;
6795 if(allRows.length > 0) {
6796 return allRows[allRows.length-1] || null;
6801 * Returns DOM reference to the next TR element from the given TR element, or null.
6803 * @method getNextTrEl
6804 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6805 * reference, ID string, page row index, or Record from which to get next TR element.
6806 * @return {HTMLElement} Reference to next TR element.
6808 getNextTrEl : function(row) {
6809 var nThisTrIndex = this.getTrIndex(row);
6810 if(nThisTrIndex !== null) {
6811 var allRows = this._elTbody.rows;
6812 if(nThisTrIndex < allRows.length-1) {
6813 return allRows[nThisTrIndex+1];
6817 YAHOO.log("Could not get next TR element for row " + row, "info", this.toString());
6822 * Returns DOM reference to the previous TR element from the given TR element, or null.
6824 * @method getPreviousTrEl
6825 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
6826 * reference, ID string, page row index, or Record from which to get previous TR element.
6827 * @return {HTMLElement} Reference to previous TR element.
6829 getPreviousTrEl : function(row) {
6830 var nThisTrIndex = this.getTrIndex(row);
6831 if(nThisTrIndex !== null) {
6832 var allRows = this._elTbody.rows;
6833 if(nThisTrIndex > 0) {
6834 return allRows[nThisTrIndex-1];
6838 YAHOO.log("Could not get previous TR element for row " + row, "info", this.toString());
6843 * Returns DOM reference to a TD liner element.
6845 * @method getTdLinerEl
6846 * @param cell {HTMLElement | Object} TD element or child of a TD element, or
6847 * object literal of syntax {record:oRecord, column:oColumn}.
6848 * @return {HTMLElement} Reference to TD liner element.
6850 getTdLinerEl : function(cell) {
6851 var elCell = this.getTdEl(cell);
6852 return elCell.firstChild || null;
6856 * Returns DOM reference to a TD element.
6859 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
6860 * object literal of syntax {record:oRecord, column:oColumn}.
6861 * @return {HTMLElement} Reference to TD element.
6863 getTdEl : function(cell) {
6865 var el = Dom.get(cell);
6867 // Validate HTML element
6868 if(el && (el.ownerDocument == document)) {
6869 // Validate TD element
6870 if(el.nodeName.toLowerCase() != "td") {
6871 // Traverse up the DOM to find the corresponding TR element
6872 elCell = Dom.getAncestorByTagName(el, "td");
6881 var oRecord, nColKeyIndex;
6883 if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
6884 oRecord = this.getRecord(cell.recordId);
6885 var oColumn = this.getColumn(cell.columnKey);
6887 nColKeyIndex = oColumn.getKeyIndex();
6891 if(cell.record && cell.column && cell.column.getKeyIndex) {
6892 oRecord = cell.record;
6893 nColKeyIndex = cell.column.getKeyIndex();
6895 var elRow = this.getTrEl(oRecord);
6896 if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
6897 return elRow.cells[nColKeyIndex] || null;
6905 * Returns DOM reference to the first TD element in the DataTable page (by default),
6906 * the first TD element of the optionally given row, or null.
6908 * @method getFirstTdEl
6909 * @param row {HTMLElement} (optional) row from which to get first TD
6910 * @return {HTMLElement} Reference to TD element.
6912 getFirstTdEl : function(row) {
6913 var elRow = this.getTrEl(row) || this.getFirstTrEl();
6914 if(elRow && (elRow.cells.length > 0)) {
6915 return elRow.cells[0];
6917 YAHOO.log("Could not get first TD element for row " + elRow, "info", this.toString());
6922 * Returns DOM reference to the last TD element in the DataTable page (by default),
6923 * the first TD element of the optionally given row, or null.
6925 * @method getLastTdEl
6926 * @return {HTMLElement} Reference to last TD element.
6928 getLastTdEl : function(row) {
6929 var elRow = this.getTrEl(row) || this.getLastTrEl();
6930 if(elRow && (elRow.cells.length > 0)) {
6931 return elRow.cells[elRow.cells.length-1];
6933 YAHOO.log("Could not get last TD element for row " + elRow, "info", this.toString());
6938 * Returns DOM reference to the next TD element from the given cell, or null.
6940 * @method getNextTdEl
6941 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6942 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6943 * @return {HTMLElement} Reference to next TD element, or null.
6945 getNextTdEl : function(cell) {
6946 var elCell = this.getTdEl(cell);
6948 var nThisTdIndex = elCell.cellIndex;
6949 var elRow = this.getTrEl(elCell);
6950 if(nThisTdIndex < elRow.cells.length-1) {
6951 return elRow.cells[nThisTdIndex+1];
6954 var elNextRow = this.getNextTrEl(elRow);
6956 return elNextRow.cells[0];
6960 YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
6965 * Returns DOM reference to the previous TD element from the given cell, or null.
6967 * @method getPreviousTdEl
6968 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6969 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
6970 * @return {HTMLElement} Reference to previous TD element, or null.
6972 getPreviousTdEl : function(cell) {
6973 var elCell = this.getTdEl(cell);
6975 var nThisTdIndex = elCell.cellIndex;
6976 var elRow = this.getTrEl(elCell);
6977 if(nThisTdIndex > 0) {
6978 return elRow.cells[nThisTdIndex-1];
6981 var elPreviousRow = this.getPreviousTrEl(elRow);
6983 return this.getLastTdEl(elPreviousRow);
6987 YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString());
6992 * Returns DOM reference to the above TD element from the given cell, or null.
6994 * @method getAboveTdEl
6995 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
6996 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
6997 * @return {HTMLElement} Reference to next TD element, or null.
6999 getAboveTdEl : function(cell) {
7000 var elCell = this.getTdEl(cell);
7002 var elPreviousRow = this.getPreviousTrEl(elCell);
7004 return elPreviousRow.cells[elCell.cellIndex];
7007 YAHOO.log("Could not get above TD element for cell " + cell, "info", this.toString());
7012 * Returns DOM reference to the below TD element from the given cell, or null.
7014 * @method getBelowTdEl
7015 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
7016 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
7017 * @return {HTMLElement} Reference to previous TD element, or null.
7019 getBelowTdEl : function(cell) {
7020 var elCell = this.getTdEl(cell);
7022 var elNextRow = this.getNextTrEl(elCell);
7024 return elNextRow.cells[elCell.cellIndex];
7027 YAHOO.log("Could not get below TD element for cell " + cell, "info", this.toString());
7032 * Returns DOM reference to a TH liner element. Needed to normalize for resizeable
7033 * Columns, which have an additional resizer liner DIV element between the TH
7034 * element and the liner DIV element.
7036 * @method getThLinerEl
7037 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
7038 * DOM element reference, or string ID.
7039 * @return {HTMLElement} Reference to TH liner element.
7041 getThLinerEl : function(theadCell) {
7042 var oColumn = this.getColumn(theadCell);
7043 return (oColumn) ? oColumn.getThLinerEl() : null;
7047 * Returns DOM reference to a TH element.
7050 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
7051 * DOM element reference, or string ID.
7052 * @return {HTMLElement} Reference to TH element.
7054 getThEl : function(theadCell) {
7057 // Validate Column instance
7058 if(theadCell instanceof YAHOO.widget.Column) {
7059 var oColumn = theadCell;
7060 elTh = oColumn.getThEl();
7065 // Validate HTML element
7067 var el = Dom.get(theadCell);
7069 if(el && (el.ownerDocument == document)) {
7070 // Validate TH element
7071 if(el.nodeName.toLowerCase() != "th") {
7072 // Traverse up the DOM to find the corresponding TR element
7073 elTh = Dom.getAncestorByTagName(el,"th");
7087 * Returns the page row index of given row. Returns null if the row is not on the
7088 * current DataTable page.
7090 * @method getTrIndex
7091 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
7092 * string reference to an element within the DataTable page, a Record instance,
7093 * or a Record's RecordSet index.
7094 * @return {Number} Page row index, or null if row does not exist or is not on current page.
7096 getTrIndex : function(row) {
7100 if(row instanceof YAHOO.widget.Record) {
7101 nRecordIndex = this._oRecordSet.getRecordIndex(row);
7102 if(nRecordIndex === null) {
7103 // Not a valid Record
7107 // Calculate page row index from Record index
7108 else if(lang.isNumber(row)) {
7111 if(lang.isNumber(nRecordIndex)) {
7112 // Validate the number
7113 if((nRecordIndex > -1) && (nRecordIndex < this._oRecordSet.getLength())) {
7114 // DataTable is paginated
7115 var oPaginator = this.get('paginator');
7117 // Check the record index is within the indices of the
7119 var rng = oPaginator.getPageRecords();
7120 if (rng && nRecordIndex >= rng[0] && nRecordIndex <= rng[1]) {
7121 // This Record is on current page
7122 return nRecordIndex - rng[0];
7124 // This Record is not on current page
7129 // Not paginated, just return the Record index
7131 return nRecordIndex;
7134 // RecordSet index is out of range
7139 // By element reference or ID string
7141 // Validate TR element
7142 var elRow = this.getTrEl(row);
7143 if(elRow && (elRow.ownerDocument == document) &&
7144 (elRow.parentNode == this._elTbody)) {
7145 return elRow.sectionRowIndex;
7149 YAHOO.log("Could not get page row index for row " + row, "info", this.toString());
7201 * Resets a RecordSet with the given data and populates the page view
7202 * with the new data. Any previous data, and selection and sort states are
7203 * cleared. New data should be added as a separate step.
7205 * @method initializeTable
7207 initializeTable : function() {
7211 // Clear the RecordSet
7212 this._oRecordSet.reset();
7214 // Clear the Paginator's totalRecords if paginating
7215 var pag = this.get('paginator');
7217 pag.set('totalRecords',0);
7221 this._unselectAllTrEls();
7222 this._unselectAllTdEls();
7223 this._aSelections = null;
7224 this._oAnchorRecord = null;
7225 this._oAnchorCell = null;
7228 this.set("sortedBy", null);
7232 * Internal wrapper calls run() on render Chain instance.
7234 * @method _runRenderChain
7237 _runRenderChain : function() {
7238 this._oChainRender.run();
7242 * Renders the view with existing Records from the RecordSet while
7243 * maintaining sort, pagination, and selection states. For performance, reuses
7244 * existing DOM elements when possible while deleting extraneous elements.
7248 render : function() {
7249 //YAHOO.example.Performance.trialStart = new Date();
7251 this._oChainRender.stop();
7252 YAHOO.log("DataTable rendering...", "info", this.toString());
7254 var i, j, k, len, allRecords;
7256 var oPaginator = this.get('paginator');
7257 // Paginator is enabled, show a subset of Records and update Paginator UI
7259 allRecords = this._oRecordSet.getRecords(
7260 oPaginator.getStartIndex(),
7261 oPaginator.getRowsPerPage());
7263 // Not paginated, show all records
7265 allRecords = this._oRecordSet.getRecords();
7268 // From the top, update in-place existing rows, so as to reuse DOM elements
7269 var elTbody = this._elTbody,
7270 loopN = this.get("renderLoopSize"),
7271 nRecordsLength = allRecords.length;
7274 if(nRecordsLength > 0) {
7275 elTbody.style.display = "none";
7276 while(elTbody.lastChild) {
7277 elTbody.removeChild(elTbody.lastChild);
7279 elTbody.style.display = "";
7281 // Set up the loop Chain to render rows
7282 this._oChainRender.add({
7283 method: function(oArg) {
7284 if((this instanceof DT) && this._sId) {
7285 var i = oArg.nCurrentRecord,
7286 endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
7287 nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
7290 elTbody.style.display = "none";
7292 for(; i<endRecordIndex; i++) {
7293 elRow = Dom.get(allRecords[i].getId());
7294 elRow = elRow || this._addTrEl(allRecords[i]);
7295 nextSibling = elTbody.childNodes[i] || null;
7296 elTbody.insertBefore(elRow, nextSibling);
7298 elTbody.style.display = "";
7300 // Set up for the next loop
7301 oArg.nCurrentRecord = i;
7305 iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
7307 nCurrentRecord: 0,//nRecordsLength-1, // Start at first Record
7308 nLoopLength: (loopN > 0) ? loopN : nRecordsLength
7310 timeout: (loopN > 0) ? 0 : -1
7313 // Post-render tasks
7314 this._oChainRender.add({
7315 method: function(oArg) {
7316 if((this instanceof DT) && this._sId) {
7317 while(elTbody.rows.length > nRecordsLength) {
7318 elTbody.removeChild(elTbody.lastChild);
7320 this._setFirstRow();
7322 this._setRowStripes();
7323 this._setSelections();
7327 timeout: (loopN > 0) ? 0 : -1
7331 // Table has no rows
7333 // Set up the loop Chain to delete rows
7334 var nTotal = elTbody.rows.length;
7336 this._oChainRender.add({
7337 method: function(oArg) {
7338 if((this instanceof DT) && this._sId) {
7339 var i = oArg.nCurrent,
7340 loopN = oArg.nLoopLength,
7341 nIterEnd = (i - loopN < 0) ? -1 : i - loopN;
7343 elTbody.style.display = "none";
7345 for(; i>nIterEnd; i--) {
7346 elTbody.deleteRow(-1);
7348 elTbody.style.display = "";
7350 // Set up for the next loop
7355 iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
7358 nLoopLength: (loopN > 0) ? loopN : nTotal
7360 timeout: (loopN > 0) ? 0 : -1
7364 this._runRenderChain();
7368 * Disables DataTable UI.
7372 disable : function() {
7373 var elTable = this._elTable;
7374 var elMask = this._elMask;
7375 elMask.style.width = elTable.offsetWidth + "px";
7376 elMask.style.height = elTable.offsetHeight + "px";
7377 elMask.style.display = "";
7378 this.fireEvent("disableEvent");
7382 * Undisables DataTable UI.
7386 undisable : function() {
7387 this._elMask.style.display = "none";
7388 this.fireEvent("undisableEvent");
7392 * Nulls out the entire DataTable instance and related objects, removes attached
7393 * event listeners, and clears out DOM elements inside the container. After
7394 * calling this method, the instance reference should be expliclitly nulled by
7395 * implementer, as in myDataTable = null. Use with caution!
7399 destroy : function() {
7401 var instanceName = this.toString();
7403 this._oChainRender.stop();
7405 // Destroy static resizer proxy and column proxy
7406 DT._destroyColumnDragTargetEl();
7407 DT._destroyColumnResizerProxyEl();
7409 // Destroy ColumnDD and ColumnResizers
7410 this._destroyColumnHelpers();
7412 // Destroy all CellEditors
7414 for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
7415 oCellEditor = this._oColumnSet.flat[i].editor;
7416 if(oCellEditor && oCellEditor.destroy) {
7417 oCellEditor.destroy();
7418 this._oColumnSet.flat[i].editor = null;
7422 // Unhook custom events
7423 this._oRecordSet.unsubscribeAll();
7424 this.unsubscribeAll();
7426 // Unhook DOM events
7427 Ev.removeListener(document, "click", this._onDocumentClick);
7429 // Clear out the container
7430 this._destroyContainerEl(this._elContainer);
7433 for(var param in this) {
7434 if(lang.hasOwnProperty(this, param)) {
7439 // Clean up static values
7440 DT._nCurrentCount--;
7442 if(DT._nCurrentCount < 1) {
7443 if(DT._elDynStyleNode) {
7444 document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
7445 DT._elDynStyleNode = null;
7449 YAHOO.log("DataTable instance destroyed: " + instanceName);
7453 * Displays message within secondary TBODY.
7455 * @method showTableMessage
7456 * @param sHTML {String} (optional) Value for innerHTMlang.
7457 * @param sClassName {String} (optional) Classname.
7459 showTableMessage : function(sHTML, sClassName) {
7460 var elCell = this._elMsgTd;
7461 if(lang.isString(sHTML)) {
7462 elCell.firstChild.innerHTML = sHTML;
7464 if(lang.isString(sClassName)) {
7465 elCell.className = sClassName;
7468 this._elMsgTbody.style.display = "";
7470 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
7471 YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
7475 * Hides secondary TBODY.
7477 * @method hideTableMessage
7479 hideTableMessage : function() {
7480 if(this._elMsgTbody.style.display != "none") {
7481 this._elMsgTbody.style.display = "none";
7482 this._elMsgTbody.parentNode.style.width = "";
7483 this.fireEvent("tableMsgHideEvent");
7484 YAHOO.log("DataTable message hidden", "info", this.toString());
7489 * Brings focus to the TBODY element. Alias to focusTbodyEl.
7493 focus : function() {
7494 this.focusTbodyEl();
7498 * Brings focus to the THEAD element.
7500 * @method focusTheadEl
7502 focusTheadEl : function() {
7503 this._focusEl(this._elThead);
7507 * Brings focus to the TBODY element.
7509 * @method focusTbodyEl
7511 focusTbodyEl : function() {
7512 this._focusEl(this._elTbody);
7516 * Setting display:none on DataTable or any parent may impact width validations.
7517 * After setting display back to "", implementers should call this method to
7518 * manually perform those validations.
7522 onShow : function() {
7523 this.validateColumnWidths();
7525 for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
7527 if(col._ddResizer) {
7528 col._ddResizer.resetResizerEl();
7599 // RECORDSET FUNCTIONS
7602 * Returns Record index for given TR element or page row index.
7604 * @method getRecordIndex
7605 * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
7606 * element reference or page row index.
7607 * @return {Number} Record's RecordSet index, or null.
7609 getRecordIndex : function(row) {
7612 if(!lang.isNumber(row)) {
7614 if(row instanceof YAHOO.widget.Record) {
7615 return this._oRecordSet.getRecordIndex(row);
7617 // By element reference
7619 // Find the TR element
7620 var el = this.getTrEl(row);
7622 nTrIndex = el.sectionRowIndex;
7626 // By page row index
7631 if(lang.isNumber(nTrIndex)) {
7632 var oPaginator = this.get("paginator");
7634 return oPaginator.get('recordOffset') + nTrIndex;
7641 YAHOO.log("Could not get Record index for row " + row, "info", this.toString());
7646 * For the given identifier, returns the associated Record instance.
7649 * @param row {HTMLElement | Number | String} DOM reference to a TR element (or
7650 * child of a TR element), RecordSet position index, or Record ID.
7651 * @return {YAHOO.widget.Record} Record instance.
7653 getRecord : function(row) {
7654 var oRecord = this._oRecordSet.getRecord(row);
7657 // Validate TR element
7658 var elRow = this.getTrEl(row);
7660 oRecord = this._oRecordSet.getRecord(this.getRecordIndex(elRow.sectionRowIndex));
7664 if(oRecord instanceof YAHOO.widget.Record) {
7665 return this._oRecordSet.getRecord(oRecord);
7668 YAHOO.log("Could not get Record for row at " + row, "info", this.toString());
7721 * For the given identifier, returns the associated Column instance. Note: For
7722 * getting Columns by Column ID string, please use the method getColumnById().
7725 * @param column {HTMLElement | String | Number} TH/TD element (or child of a
7726 * TH/TD element), a Column key, or a ColumnSet key index.
7727 * @return {YAHOO.widget.Column} Column instance.
7729 getColumn : function(column) {
7730 var oColumn = this._oColumnSet.getColumn(column);
7733 // Validate TD element
7734 var elCell = this.getTdEl(column);
7736 oColumn = this._oColumnSet.getColumn(elCell.cellIndex);
7738 // Validate TH element
7740 elCell = this.getThEl(column);
7743 var allColumns = this._oColumnSet.flat;
7744 for(var i=0, len=allColumns.length; i<len; i++) {
7745 if(allColumns[i].getThEl().id === elCell.id) {
7746 oColumn = allColumns[i];
7753 YAHOO.log("Could not get Column for column at " + column, "info", this.toString());
7759 * For the given Column ID, returns the associated Column instance. Note: For
7760 * getting Columns by key, please use the method getColumn().
7762 * @method getColumnById
7763 * @param column {String} Column ID string.
7764 * @return {YAHOO.widget.Column} Column instance.
7766 getColumnById : function(column) {
7767 return this._oColumnSet.getColumnById(column);
7771 * For the given Column instance, returns next direction to sort.
7773 * @method getColumnSortDir
7774 * @param oColumn {YAHOO.widget.Column} Column instance.
7775 * @param oSortedBy {Object} (optional) Specify the state, or use current state.
7776 * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
7778 getColumnSortDir : function(oColumn, oSortedBy) {
7779 // Backward compatibility
7780 if(oColumn.sortOptions && oColumn.sortOptions.defaultOrder) {
7781 if(oColumn.sortOptions.defaultOrder == "asc") {
7782 oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
7784 else if (oColumn.sortOptions.defaultOrder == "desc") {
7785 oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
7789 // What is the Column's default sort direction?
7790 var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
7792 // Is the Column currently sorted?
7793 var bSorted = false;
7794 oSortedBy = oSortedBy || this.get("sortedBy");
7795 if(oSortedBy && (oSortedBy.key === oColumn.key)) {
7798 sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7801 sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
7808 * Overridable method gives implementers a hook to show loading message before
7811 * @method doBeforeSortColumn
7812 * @param oColumn {YAHOO.widget.Column} Column instance.
7813 * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
7814 * YAHOO.widget.DataTable.CLASS_DESC.
7815 * @return {Boolean} Return true to continue sorting Column.
7817 doBeforeSortColumn : function(oColumn, sSortDir) {
7818 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
7823 * Sorts given Column. If "dynamicData" is true, current selections are purged before
7824 * a request is sent to the DataSource for data for the new state (using the
7825 * request returned by "generateRequest()").
7827 * @method sortColumn
7828 * @param oColumn {YAHOO.widget.Column} Column instance.
7829 * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
7830 * YAHOO.widget.DataTable.CLASS_DESC
7832 sortColumn : function(oColumn, sDir) {
7833 if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
7834 if(!oColumn.sortable) {
7835 Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
7838 // Validate given direction
7839 if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
7844 var sSortDir = sDir || this.getColumnSortDir(oColumn);
7846 // Is the Column currently sorted?
7847 var oSortedBy = this.get("sortedBy") || {};
7848 var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
7850 var ok = this.doBeforeSortColumn(oColumn, sSortDir);
7853 if(this.get("dynamicData")) {
7854 // Get current state
7855 var oState = this.getState();
7857 // Reset record offset, if paginated
7858 if(oState.pagination) {
7859 oState.pagination.recordOffset = 0;
7862 // Update sortedBy to new values
7868 // Get the request for the new state
7869 var request = this.get("generateRequest")(oState, this);
7872 this.unselectAllRows();
7873 this.unselectAllCells();
7875 // Send request for new data
7877 success : this.onDataReturnSetRows,
7878 failure : this.onDataReturnSetRows,
7879 argument : oState, // Pass along the new state to the callback
7882 this._oDataSource.sendRequest(request, callback);
7886 // Is there a custom sort handler function defined?
7887 var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
7888 // Custom sort function
7889 oColumn.sortOptions.sortFunction : null;
7892 if(!bSorted || sDir || sortFnc) {
7893 // Get the field to sort
7894 var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
7896 // Default sort function if necessary
7897 sortFnc = sortFnc ||
7898 function(a, b, desc) {
7899 var sorted = YAHOO.util.Sort.compare(a.getData(sField),b.getData(sField), desc);
7901 return YAHOO.util.Sort.compare(a.getCount(),b.getCount(), desc); // Bug 1932978
7908 this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false));
7910 // Just reverse the Records
7912 this._oRecordSet.reverseRecords();
7915 // Reset to first page if paginated
7916 var oPaginator = this.get('paginator');
7918 // Set page silently, so as not to fire change event.
7919 oPaginator.setPage(1,true);
7922 // Update UI via sortedBy
7924 this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn});
7927 this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
7928 YAHOO.log("Column \"" + oColumn.key + "\" sorted \"" + sSortDir + "\"", "info", this.toString());
7932 YAHOO.log("Could not sort Column \"" + oColumn.key + "\"", "warn", this.toString());
7936 * Sets given Column to given pixel width. If new width is less than minimum
7937 * width, sets to minimum width. Updates oColumn.width value.
7939 * @method setColumnWidth
7940 * @param oColumn {YAHOO.widget.Column} Column instance.
7941 * @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
7942 * subject to minWidth and maxAutoWidth validations.
7944 setColumnWidth : function(oColumn, nWidth) {
7945 if(!(oColumn instanceof YAHOO.widget.Column)) {
7946 oColumn = this.getColumn(oColumn);
7949 // Validate new width against minimum width
7950 if(lang.isNumber(nWidth)) {
7951 // This is why we must require a Number... :-|
7952 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
7955 oColumn.width = nWidth;
7957 // Resize the DOM elements
7958 this._setColumnWidth(oColumn, nWidth+"px");
7960 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
7961 YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString());
7963 // Unsets a width to auto-size
7964 else if(nWidth === null) {
7966 oColumn.width = nWidth;
7968 // Resize the DOM elements
7969 this._setColumnWidth(oColumn, "auto");
7970 this.validateColumnWidths(oColumn);
7971 this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
7972 YAHOO.log("Column " + oColumn + " width unset", "info", this.toString());
7975 // Bug 2339454: resize then sort misaligment
7976 this._clearTrTemplateEl();
7979 YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString());
7984 * Sets liner DIV elements of given Column to given width. When value should be
7985 * auto-calculated to fit content overflow is set to visible, otherwise overflow
7986 * is set to hidden. No validations against minimum width and no updating
7987 * Column.width value.
7989 * @method _setColumnWidth
7990 * @param oColumn {YAHOO.widget.Column} Column instance.
7991 * @param sWidth {String} New width value.
7992 * @param sOverflow {String} Should be "hidden" when Column width is explicitly
7993 * being set to a value, but should be "visible" when Column is meant to auto-fit content.
7996 _setColumnWidth : function(oColumn, sWidth, sOverflow) {
7997 if(oColumn && (oColumn.getKeyIndex() !== null)) {
7998 sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
8000 // Dynamic style algorithm
8001 if(!DT._bDynStylesFallback) {
8002 this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
8004 // Dynamic function algorithm
8006 this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
8010 YAHOO.log("Could not set width of unknown Column " + oColumn + " to " + sWidth, "warn", this.toString());
8015 * Updates width of a Column's liner DIV elements by dynamically creating a
8016 * STYLE node and writing and updating CSS style rules to it. If this fails during
8017 * runtime, the fallback method _setColumnWidthDynFunction() will be called.
8018 * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
8019 * nested within another TABLE element. For these cases, it is recommended to
8020 * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
8022 * @method _setColumnWidthDynStyles
8023 * @param oColumn {YAHOO.widget.Column} Column instance.
8024 * @param sWidth {String} New width value.
8027 _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
8028 var s = DT._elDynStyleNode,
8031 // Create a new STYLE node
8033 s = document.createElement('style');
8034 s.type = 'text/css';
8035 s = document.getElementsByTagName('head').item(0).appendChild(s);
8036 DT._elDynStyleNode = s;
8039 // We have a STYLE node to update
8041 // Use unique classname for this Column instance as a hook for resizing
8042 var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
8044 // Hide for performance
8046 this._elTbody.style.display = 'none';
8049 rule = DT._oDynStyles[sClassname];
8051 // The Column does not yet have a rule
8053 if(s.styleSheet && s.styleSheet.addRule) {
8054 s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
8055 s.styleSheet.addRule(sClassname,'width:'+sWidth);
8056 rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
8057 DT._oDynStyles[sClassname] = rule;
8059 else if(s.sheet && s.sheet.insertRule) {
8060 s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
8061 rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
8062 DT._oDynStyles[sClassname] = rule;
8065 // We have a rule to update
8067 rule.style.overflow = sOverflow;
8068 rule.style.width = sWidth;
8073 this._elTbody.style.display = '';
8077 // That was not a success, we must call the fallback routine
8079 DT._bDynStylesFallback = true;
8080 this._setColumnWidthDynFunction(oColumn, sWidth);
8085 * Updates width of a Column's liner DIV elements by dynamically creating a
8086 * function to update all element style properties in one pass. Note: This
8087 * technique is not supported in sandboxed environments that prohibit EVALs.
8089 * @method _setColumnWidthDynFunction
8090 * @param oColumn {YAHOO.widget.Column} Column instance.
8091 * @param sWidth {String} New width value.
8094 _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
8095 // TODO: why is this here?
8096 if(sWidth == 'auto') {
8100 // Create one function for each value of rows.length
8101 var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
8103 // Dynamically create the function
8104 if (!this._aDynFunctions[rowslen]) {
8106 //Compile a custom function to do all the liner div width
8107 //assignments at the same time. A unique function is required
8108 //for each unique number of rows in _elTbody. This will
8109 //result in a function declaration like:
8110 //function (oColumn,sWidth,sOverflow) {
8111 // var colIdx = oColumn.getKeyIndex();
8112 // oColumn.getThLinerEl().style.overflow =
8113 // this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
8114 // this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
8115 // ... (for all row indices in this._elTbody.rows.length - 1)
8116 // this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
8118 // oColumn.getThLinerEl().style.width =
8119 // this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
8120 // this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
8121 // ... (for all row indices in this._elTbody.rows.length - 1)
8122 // this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
8128 'var colIdx=oColumn.getKeyIndex();',
8129 'oColumn.getThLinerEl().style.overflow='
8131 for (i=rowslen-1, j=2; i >= 0; --i) {
8132 resizerDef[j++] = 'this._elTbody.rows[';
8133 resizerDef[j++] = i;
8134 resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
8136 resizerDef[j] = 'sOverflow;';
8137 resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
8138 for (i=rowslen-1, k=j+2; i >= 0; --i) {
8139 resizerDef[k++] = 'this._elTbody.rows[';
8140 resizerDef[k++] = i;
8141 resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
8143 resizerDef[k] = 'sWidth;';
8144 this._aDynFunctions[rowslen] =
8145 new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
8148 // Get the function to execute
8149 var resizerFn = this._aDynFunctions[rowslen];
8151 // TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
8153 resizerFn.call(this,oColumn,sWidth,sOverflow);
8158 * For one or all Columns, when Column is not hidden, width is not set, and minWidth
8159 * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
8161 * @method validateColumnWidths
8162 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
8164 validateColumnWidths : function(oColumn) {
8165 var elColgroup = this._elColgroup;
8166 var elColgroupClone = elColgroup.cloneNode(true);
8167 var bNeedsValidation = false;
8168 var allKeys = this._oColumnSet.keys;
8170 // Validate just one Column's minWidth and/or maxAutoWidth
8171 if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
8172 elThLiner = oColumn.getThLinerEl();
8173 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8174 elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width =
8176 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8177 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8178 bNeedsValidation = true;
8180 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8181 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8184 // Validate all Columns
8186 for(var i=0, len=allKeys.length; i<len; i++) {
8187 oColumn = allKeys[i];
8188 if(!oColumn.hidden && !oColumn.width) {
8189 elThLiner = oColumn.getThLinerEl();
8190 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
8191 elColgroupClone.childNodes[i].style.width =
8193 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
8194 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
8195 bNeedsValidation = true;
8197 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
8198 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
8203 if(bNeedsValidation) {
8204 elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
8205 this._elColgroup = elColgroupClone;
8212 * @method _clearMinWidth
8213 * @param oColumn {YAHOO.widget.Column} Which Column.
8216 _clearMinWidth : function(oColumn) {
8217 if(oColumn.getKeyIndex() !== null) {
8218 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
8223 * Restores minWidth.
8225 * @method _restoreMinWidth
8226 * @param oColumn {YAHOO.widget.Column} Which Column.
8229 _restoreMinWidth : function(oColumn) {
8230 if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
8231 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
8236 * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
8237 * hide/show non-nested Columns, and top-level parent Columns (which will
8238 * hide/show all children Columns).
8240 * @method hideColumn
8241 * @param oColumn {YAHOO.widget.Column} Column instance.
8243 hideColumn : function(oColumn) {
8244 if(!(oColumn instanceof YAHOO.widget.Column)) {
8245 oColumn = this.getColumn(oColumn);
8247 // Only top-level Columns can get hidden due to issues in FF2 and SF3
8248 if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
8250 var allrows = this.getTbodyEl().rows;
8251 var l = allrows.length;
8252 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8254 // Hide each nested Column
8255 for(var i=0; i<allDescendants.length; i++) {
8256 var thisColumn = allDescendants[i];
8257 thisColumn.hidden = true;
8259 // Style the head cell
8260 Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8262 // Does this Column have body cells?
8263 var thisKeyIndex = thisColumn.getKeyIndex();
8264 if(thisKeyIndex !== null) {
8266 this._clearMinWidth(oColumn);
8268 // Style the body cells
8269 for(var j=0;j<l;j++) {
8270 Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8274 this.fireEvent("columnHideEvent",{column:thisColumn});
8275 YAHOO.log("Column \"" + oColumn.key + "\" hidden", "info", this.toString());
8278 this._repaintOpera();
8279 this._clearTrTemplateEl();
8282 YAHOO.log("Could not hide Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be hidden", "warn", this.toString());
8287 * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
8288 * hide/show non-nested Columns, and top-level parent Columns (which will
8289 * hide/show all children Columns).
8291 * @method showColumn
8292 * @param oColumn {YAHOO.widget.Column} Column instance.
8294 showColumn : function(oColumn) {
8295 if(!(oColumn instanceof YAHOO.widget.Column)) {
8296 oColumn = this.getColumn(oColumn);
8298 // Only top-level Columns can get hidden
8299 if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
8300 var allrows = this.getTbodyEl().rows;
8301 var l = allrows.length;
8302 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8304 // Show each nested Column
8305 for(var i=0; i<allDescendants.length; i++) {
8306 var thisColumn = allDescendants[i];
8307 thisColumn.hidden = false;
8309 // Unstyle the head cell
8310 Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
8312 // Does this Column have body cells?
8313 var thisKeyIndex = thisColumn.getKeyIndex();
8314 if(thisKeyIndex !== null) {
8316 this._restoreMinWidth(oColumn);
8319 // Unstyle the body cells
8320 for(var j=0;j<l;j++) {
8321 Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
8325 this.fireEvent("columnShowEvent",{column:thisColumn});
8326 YAHOO.log("Column \"" + oColumn.key + "\" shown", "info", this.toString());
8328 this._clearTrTemplateEl();
8331 YAHOO.log("Could not show Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be shown", "warn", this.toString());
8336 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
8337 * non-nested Columns, and top-level parent Columns (which will remove all
8338 * children Columns).
8340 * @method removeColumn
8341 * @param oColumn {YAHOO.widget.Column} Column instance.
8342 * @return oColumn {YAHOO.widget.Column} Removed Column instance.
8344 removeColumn : function(oColumn) {
8346 if(!(oColumn instanceof YAHOO.widget.Column)) {
8347 oColumn = this.getColumn(oColumn);
8350 var nColTreeIndex = oColumn.getTreeIndex();
8351 if(nColTreeIndex !== null) {
8352 // Which key index(es)
8354 aKeyIndexes = oColumn.getKeyIndex();
8355 // Must be a parent Column
8356 if(aKeyIndexes === null) {
8357 var descKeyIndexes = [];
8358 var allDescendants = this._oColumnSet.getDescendants(oColumn);
8359 for(i=0, len=allDescendants.length; i<len; i++) {
8360 // Is this descendant a key Column?
8361 var thisKey = allDescendants[i].getKeyIndex();
8362 if(thisKey !== null) {
8363 descKeyIndexes[descKeyIndexes.length] = thisKey;
8366 if(descKeyIndexes.length > 0) {
8367 aKeyIndexes = descKeyIndexes;
8370 // Must be a key Column
8372 aKeyIndexes = [aKeyIndexes];
8375 if(aKeyIndexes !== null) {
8376 // Sort the indexes so we can remove from the right
8377 aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8379 // Destroy previous THEAD
8380 this._destroyTheadEl();
8383 var aOrigColumnDefs = this._oColumnSet.getDefinitions();
8384 oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
8385 this._initColumnSet(aOrigColumnDefs);
8386 this._initTheadEl();
8389 for(i=aKeyIndexes.length-1; i>-1; i--) {
8390 this._removeColgroupColEl(aKeyIndexes[i]);
8394 var allRows = this._elTbody.rows;
8395 if(allRows.length > 0) {
8396 var loopN = this.get("renderLoopSize"),
8397 loopEnd = allRows.length;
8398 this._oChainRender.add({
8399 method: function(oArg) {
8400 if((this instanceof DT) && this._sId) {
8401 var i = oArg.nCurrentRow,
8402 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8403 aIndexes = oArg.aIndexes,
8405 for(; i < len; ++i) {
8406 for(j = aIndexes.length-1; j>-1; j--) {
8407 allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
8410 oArg.nCurrentRow = i;
8413 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8414 argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
8416 timeout: (loopN > 0) ? 0 : -1
8418 this._runRenderChain();
8421 this.fireEvent("columnRemoveEvent",{column:oColumn});
8422 YAHOO.log("Column \"" + oColumn.key + "\" removed", "info", this.toString());
8427 YAHOO.log("Could not remove Column \"" + oColumn.key + "\". Only non-nested Columns can be removed", "warn", this.toString());
8431 * Inserts given Column at the index if given, otherwise at the end. NOTE: You
8432 * can only add non-nested Columns and top-level parent Columns. You cannot add
8433 * a nested Column to an existing parent.
8435 * @method insertColumn
8436 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
8437 * definition or a Column instance.
8438 * @param index {Number} (optional) New tree index.
8439 * @return oColumn {YAHOO.widget.Column} Inserted Column instance.
8441 insertColumn : function(oColumn, index) {
8443 if(oColumn instanceof YAHOO.widget.Column) {
8444 oColumn = oColumn.getDefinition();
8446 else if(oColumn.constructor !== Object) {
8447 YAHOO.log("Could not insert Column \"" + oColumn + "\" due to invalid argument", "warn", this.toString());
8451 // Validate index or append new Column to the end of the ColumnSet
8452 var oColumnSet = this._oColumnSet;
8453 if(!lang.isValue(index) || !lang.isNumber(index)) {
8454 index = oColumnSet.tree[0].length;
8457 // Destroy previous THEAD
8458 this._destroyTheadEl();
8461 var aNewColumnDefs = this._oColumnSet.getDefinitions();
8462 aNewColumnDefs.splice(index, 0, oColumn);
8463 this._initColumnSet(aNewColumnDefs);
8464 this._initTheadEl();
8466 // Need to refresh the reference
8467 oColumnSet = this._oColumnSet;
8468 var oNewColumn = oColumnSet.tree[0][index];
8470 // Get key index(es) for new Column
8472 descKeyIndexes = [];
8473 var allDescendants = oColumnSet.getDescendants(oNewColumn);
8474 for(i=0, len=allDescendants.length; i<len; i++) {
8475 // Is this descendant a key Column?
8476 var thisKey = allDescendants[i].getKeyIndex();
8477 if(thisKey !== null) {
8478 descKeyIndexes[descKeyIndexes.length] = thisKey;
8482 if(descKeyIndexes.length > 0) {
8484 var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8487 for(i=descKeyIndexes.length-1; i>-1; i--) {
8488 this._insertColgroupColEl(descKeyIndexes[i]);
8492 var allRows = this._elTbody.rows;
8493 if(allRows.length > 0) {
8494 var loopN = this.get("renderLoopSize"),
8495 loopEnd = allRows.length;
8497 // Get templates for each new TD
8498 var aTdTemplates = [],
8500 for(i=0, len=descKeyIndexes.length; i<len; i++) {
8501 var thisKeyIndex = descKeyIndexes[i];
8502 elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
8503 elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
8504 aTdTemplates[thisKeyIndex] = elTdTemplate;
8507 this._oChainRender.add({
8508 method: function(oArg) {
8509 if((this instanceof DT) && this._sId) {
8510 var i = oArg.nCurrentRow, j,
8511 descKeyIndexes = oArg.descKeyIndexes,
8512 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8514 for(; i < len; ++i) {
8515 nextSibling = allRows[i].childNodes[newIndex] || null;
8516 for(j=descKeyIndexes.length-1; j>-1; j--) {
8517 allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
8520 oArg.nCurrentRow = i;
8523 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8524 argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
8526 timeout: (loopN > 0) ? 0 : -1
8528 this._runRenderChain();
8531 this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
8532 YAHOO.log("Column \"" + oColumn.key + "\" inserted into index " + index, "info", this.toString());
8538 * Removes given Column and inserts into given tree index. NOTE: You
8539 * can only reorder non-nested Columns and top-level parent Columns. You cannot
8540 * reorder a nested Column to an existing parent.
8542 * @method reorderColumn
8543 * @param oColumn {YAHOO.widget.Column} Column instance.
8544 * @param index {Number} New tree index.
8545 * @return oColumn {YAHOO.widget.Column} Reordered Column instance.
8547 reorderColumn : function(oColumn, index) {
8548 // Validate Column and new index
8549 if(!(oColumn instanceof YAHOO.widget.Column)) {
8550 oColumn = this.getColumn(oColumn);
8552 if(oColumn && YAHOO.lang.isNumber(index)) {
8553 var nOrigTreeIndex = oColumn.getTreeIndex();
8554 if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
8555 // Which key index(es)
8557 aOrigKeyIndexes = oColumn.getKeyIndex(),
8559 descKeyIndexes = [],
8561 // Must be a parent Column...
8562 if(aOrigKeyIndexes === null) {
8563 allDescendants = this._oColumnSet.getDescendants(oColumn);
8564 for(i=0, len=allDescendants.length; i<len; i++) {
8565 // Is this descendant a key Column?
8566 thisKey = allDescendants[i].getKeyIndex();
8567 if(thisKey !== null) {
8568 descKeyIndexes[descKeyIndexes.length] = thisKey;
8571 if(descKeyIndexes.length > 0) {
8572 aOrigKeyIndexes = descKeyIndexes;
8575 // ...or else must be a key Column
8577 aOrigKeyIndexes = [aOrigKeyIndexes];
8580 if(aOrigKeyIndexes !== null) {
8582 aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
8584 // Destroy previous THEAD
8585 this._destroyTheadEl();
8588 var aColumnDefs = this._oColumnSet.getDefinitions();
8589 var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
8590 aColumnDefs.splice(index, 0, oColumnDef);
8591 this._initColumnSet(aColumnDefs);
8592 this._initTheadEl();
8594 // Need to refresh the reference
8595 var oNewColumn = this._oColumnSet.tree[0][index];
8597 // What are new key index(es)
8598 var aNewKeyIndexes = oNewColumn.getKeyIndex();
8599 // Must be a parent Column
8600 if(aNewKeyIndexes === null) {
8601 descKeyIndexes = [];
8602 allDescendants = this._oColumnSet.getDescendants(oNewColumn);
8603 for(i=0, len=allDescendants.length; i<len; i++) {
8604 // Is this descendant a key Column?
8605 thisKey = allDescendants[i].getKeyIndex();
8606 if(thisKey !== null) {
8607 descKeyIndexes[descKeyIndexes.length] = thisKey;
8610 if(descKeyIndexes.length > 0) {
8611 aNewKeyIndexes = descKeyIndexes;
8614 // Must be a key Column
8616 aNewKeyIndexes = [aNewKeyIndexes];
8619 // Sort the new indexes and grab the first one for the new location
8620 var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
8623 this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
8626 var allRows = this._elTbody.rows;
8627 if(allRows.length > 0) {
8628 var loopN = this.get("renderLoopSize"),
8629 loopEnd = allRows.length;
8630 this._oChainRender.add({
8631 method: function(oArg) {
8632 if((this instanceof DT) && this._sId) {
8633 var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
8634 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
8635 aIndexes = oArg.aIndexes, thisTr;
8637 for(; i < len; ++i) {
8639 thisTr = allRows[i];
8642 for(j=aIndexes.length-1; j>-1; j--) {
8643 tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
8647 nextSibling = thisTr.childNodes[newIndex] || null;
8648 for(j=tmpTds.length-1; j>-1; j--) {
8649 thisTr.insertBefore(tmpTds[j], nextSibling);
8652 oArg.nCurrentRow = i;
8655 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
8656 argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
8658 timeout: (loopN > 0) ? 0 : -1
8660 this._runRenderChain();
8663 this.fireEvent("columnReorderEvent",{column:oNewColumn});
8664 YAHOO.log("Column \"" + oNewColumn.key + "\" reordered", "info", this.toString());
8669 YAHOO.log("Could not reorder Column \"" + oColumn.key + "\". Only non-nested Columns can be reordered", "warn", this.toString());
8673 * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8674 * select/unselect non-nested Columns, and bottom-level key Columns.
8676 * @method selectColumn
8677 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8678 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8680 selectColumn : function(oColumn) {
8681 oColumn = this.getColumn(oColumn);
8682 if(oColumn && !oColumn.selected) {
8683 // Only bottom-level Columns can get hidden
8684 if(oColumn.getKeyIndex() !== null) {
8685 oColumn.selected = true;
8688 var elTh = oColumn.getThEl();
8689 Dom.addClass(elTh,DT.CLASS_SELECTED);
8691 // Update body cells
8692 var allRows = this.getTbodyEl().rows;
8693 var oChainRender = this._oChainRender;
8695 method: function(oArg) {
8696 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8697 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);
8702 iterations: allRows.length,
8703 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8706 this._clearTrTemplateEl();
8708 this._elTbody.style.display = "none";
8709 this._runRenderChain();
8710 this._elTbody.style.display = "";
8712 this.fireEvent("columnSelectEvent",{column:oColumn});
8713 YAHOO.log("Column \"" + oColumn.key + "\" selected", "info", this.toString());
8716 YAHOO.log("Could not select Column \"" + oColumn.key + "\". Only non-nested Columns can be selected", "warn", this.toString());
8722 * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
8723 * select/unselect non-nested Columns, and bottom-level key Columns.
8725 * @method unselectColumn
8726 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8727 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8729 unselectColumn : function(oColumn) {
8730 oColumn = this.getColumn(oColumn);
8731 if(oColumn && oColumn.selected) {
8732 // Only bottom-level Columns can get hidden
8733 if(oColumn.getKeyIndex() !== null) {
8734 oColumn.selected = false;
8737 var elTh = oColumn.getThEl();
8738 Dom.removeClass(elTh,DT.CLASS_SELECTED);
8740 // Update body cells
8741 var allRows = this.getTbodyEl().rows;
8742 var oChainRender = this._oChainRender;
8744 method: function(oArg) {
8745 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8746 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);
8751 iterations:allRows.length,
8752 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
8755 this._clearTrTemplateEl();
8757 this._elTbody.style.display = "none";
8758 this._runRenderChain();
8759 this._elTbody.style.display = "";
8761 this.fireEvent("columnUnselectEvent",{column:oColumn});
8762 YAHOO.log("Column \"" + oColumn.key + "\" unselected", "info", this.toString());
8765 YAHOO.log("Could not unselect Column \"" + oColumn.key + "\". Only non-nested Columns can be unselected", "warn", this.toString());
8771 * Returns an array selected Column instances.
8773 * @method getSelectedColumns
8774 * @return {YAHOO.widget.Column[]} Array of Column instances.
8776 getSelectedColumns : function(oColumn) {
8777 var selectedColumns = [];
8778 var aKeys = this._oColumnSet.keys;
8779 for(var i=0,len=aKeys.length; i<len; i++) {
8780 if(aKeys[i].selected) {
8781 selectedColumns[selectedColumns.length] = aKeys[i];
8784 return selectedColumns;
8788 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8789 * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8790 * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8792 * @method highlightColumn
8793 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8794 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8796 highlightColumn : function(column) {
8797 var oColumn = this.getColumn(column);
8798 // Only bottom-level Columns can get highlighted
8799 if(oColumn && (oColumn.getKeyIndex() !== null)) {
8801 var elTh = oColumn.getThEl();
8802 Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
8804 // Update body cells
8805 var allRows = this.getTbodyEl().rows;
8806 var oChainRender = this._oChainRender;
8808 method: function(oArg) {
8809 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8810 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
8815 iterations:allRows.length,
8816 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8819 this._elTbody.style.display = "none";
8820 this._runRenderChain();
8821 this._elTbody.style.display = "";
8823 this.fireEvent("columnHighlightEvent",{column:oColumn});
8824 YAHOO.log("Column \"" + oColumn.key + "\" highlighed", "info", this.toString());
8827 YAHOO.log("Could not highlight Column \"" + oColumn.key + "\". Only non-nested Columns can be highlighted", "warn", this.toString());
8832 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
8833 * NOTE: You cannot highlight/unhighlight nested Columns. You can only
8834 * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
8836 * @method unhighlightColumn
8837 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
8838 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
8840 unhighlightColumn : function(column) {
8841 var oColumn = this.getColumn(column);
8842 // Only bottom-level Columns can get highlighted
8843 if(oColumn && (oColumn.getKeyIndex() !== null)) {
8845 var elTh = oColumn.getThEl();
8846 Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
8848 // Update body cells
8849 var allRows = this.getTbodyEl().rows;
8850 var oChainRender = this._oChainRender;
8852 method: function(oArg) {
8853 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
8854 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
8859 iterations:allRows.length,
8860 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
8863 this._elTbody.style.display = "none";
8864 this._runRenderChain();
8865 this._elTbody.style.display = "";
8867 this.fireEvent("columnUnhighlightEvent",{column:oColumn});
8868 YAHOO.log("Column \"" + oColumn.key + "\" unhighlighted", "info", this.toString());
8871 YAHOO.log("Could not unhighlight Column \"" + oColumn.key + "\". Only non-nested Columns can be unhighlighted", "warn", this.toString());
8921 * Adds one new Record of data into the RecordSet at the index if given,
8922 * otherwise at the end. If the new Record is in page view, the
8923 * corresponding DOM elements are also updated.
8926 * @param oData {Object} Object literal of data for the row.
8927 * @param index {Number} (optional) RecordSet position index at which to add data.
8929 addRow : function(oData, index) {
8930 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
8931 YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
8935 if(oData && lang.isObject(oData)) {
8936 var oRecord = this._oRecordSet.addRecord(oData, index);
8939 var oPaginator = this.get('paginator');
8943 // Update the paginator's totalRecords
8944 var totalRecords = oPaginator.get('totalRecords');
8945 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
8946 oPaginator.set('totalRecords',totalRecords + 1);
8949 recIndex = this.getRecordIndex(oRecord);
8950 var endRecIndex = (oPaginator.getPageRecords())[1];
8952 // New record affects the view
8953 if (recIndex <= endRecIndex) {
8954 // Defer UI updates to the render method
8958 this.fireEvent("rowAddEvent", {record:oRecord});
8959 YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString());
8964 recIndex = this.getTrIndex(oRecord);
8965 if(lang.isNumber(recIndex)) {
8966 // Add the TR element
8967 this._oChainRender.add({
8968 method: function(oArg) {
8969 if((this instanceof DT) && this._sId) {
8970 var oRecord = oArg.record;
8971 var recIndex = oArg.recIndex;
8972 var elNewTr = this._addTrEl(oRecord);
8974 var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
8975 this._elTbody.insertBefore(elNewTr, elNext);
8978 if(recIndex === 0) {
8979 this._setFirstRow();
8981 if(elNext === null) {
8985 this._setRowStripes();
8987 this.hideTableMessage();
8989 this.fireEvent("rowAddEvent", {record:oRecord});
8990 YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString());
8994 argument: {record: oRecord, recIndex: recIndex},
8996 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
8998 this._runRenderChain();
9004 YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString());
9008 * Convenience method to add multiple rows.
9011 * @param aData {Object[]} Array of object literal data for the rows.
9012 * @param index {Number} (optional) RecordSet position index at which to add data.
9014 addRows : function(aData, index) {
9015 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
9016 YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());
9020 if(lang.isArray(aData)) {
9021 var aRecords = this._oRecordSet.addRecords(aData, index);
9023 var recIndex = this.getRecordIndex(aRecords[0]);
9026 var oPaginator = this.get('paginator');
9028 // Update the paginator's totalRecords
9029 var totalRecords = oPaginator.get('totalRecords');
9030 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9031 oPaginator.set('totalRecords',totalRecords + aRecords.length);
9034 var endRecIndex = (oPaginator.getPageRecords())[1];
9036 // At least one of the new records affects the view
9037 if (recIndex <= endRecIndex) {
9041 this.fireEvent("rowsAddEvent", {records:aRecords});
9042 YAHOO.log("Added " + aRecords.length +
9043 " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
9044 " with data " + lang.dump(aData), "info", this.toString());
9049 // Add the TR elements
9050 var loopN = this.get("renderLoopSize");
9051 var loopEnd = recIndex + aData.length;
9052 var nRowsNeeded = (loopEnd - recIndex); // how many needed
9053 var isLast = (recIndex >= this._elTbody.rows.length);
9054 this._oChainRender.add({
9055 method: function(oArg) {
9056 if((this instanceof DT) && this._sId) {
9057 var aRecords = oArg.aRecords,
9058 i = oArg.nCurrentRow,
9059 j = oArg.nCurrentRecord,
9060 len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
9061 df = document.createDocumentFragment(),
9062 elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
9063 for(; i < len; i++, j++) {
9064 df.appendChild(this._addTrEl(aRecords[j]));
9066 this._elTbody.insertBefore(df, elNext);
9067 oArg.nCurrentRow = i;
9068 oArg.nCurrentRecord = j;
9071 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
9072 argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
9074 timeout: (loopN > 0) ? 0 : -1
9076 this._oChainRender.add({
9077 method: function(oArg) {
9078 var recIndex = oArg.recIndex;
9080 if(recIndex === 0) {
9081 this._setFirstRow();
9087 this._setRowStripes();
9089 this.fireEvent("rowsAddEvent", {records:aRecords});
9090 YAHOO.log("Added " + aRecords.length +
9091 " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) +
9092 " with data " + lang.dump(aData), "info", this.toString());
9094 argument: {recIndex: recIndex, isLast: isLast},
9096 timeout: -1 // Needs to run immediately after the DOM insertions above
9098 this._runRenderChain();
9099 this.hideTableMessage();
9104 YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString());
9108 * For the given row, updates the associated Record with the given data. If the
9109 * row is on current page, the corresponding DOM elements are also updated.
9112 * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
9113 * Which row to update: By Record instance, by Record's RecordSet
9114 * position index, by HTMLElement reference to the TR element, or by ID string
9115 * of the TR element.
9116 * @param oData {Object} Object literal of data for the row.
9118 updateRow : function(row, oData) {
9120 if (!lang.isNumber(index)) {
9121 index = this.getRecordIndex(row);
9124 // Update the Record
9125 if(lang.isNumber(index) && (index >= 0)) {
9126 var oRecordSet = this._oRecordSet,
9127 oldRecord = oRecordSet.getRecord(index);
9131 var updatedRecord = this._oRecordSet.setRecord(oData, index),
9132 elRow = this.getTrEl(oldRecord),
9133 // Copy data from the Record for the event that gets fired later
9134 oldData = oldRecord ? oldRecord.getData() : null;
9137 // Update selected rows as necessary
9138 var tracker = this._aSelections || [],
9140 oldId = oldRecord.getId(),
9141 newId = updatedRecord.getId();
9142 for(; i<tracker.length; i++) {
9143 if((tracker[i] === oldId)) {
9146 else if(tracker[i].recordId === oldId) {
9147 tracker[i].recordId = newId;
9151 // Update the TR only if row is on current page
9152 this._oChainRender.add({
9153 method: function() {
9154 if((this instanceof DT) && this._sId) {
9156 var oPaginator = this.get('paginator');
9158 var pageStartIndex = (oPaginator.getPageRecords())[0],
9159 pageLastIndex = (oPaginator.getPageRecords())[1];
9161 // At least one of the new records affects the view
9162 if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
9168 this._updateTrEl(elRow, updatedRecord);
9171 this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
9174 this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
9175 YAHOO.log("DataTable row updated: Record ID = " + updatedRecord.getId() +
9176 ", Record index = " + this.getRecordIndex(updatedRecord) +
9177 ", page row index = " + this.getTrIndex(updatedRecord), "info", this.toString());
9181 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9183 this._runRenderChain();
9188 YAHOO.log("Could not update row " + row + " with the data : " + lang.dump(oData), "warn", this.toString());
9193 * Starting with the given row, updates associated Records with the given data.
9194 * The number of rows to update are determined by the array of data provided.
9195 * Undefined data (i.e., not an object literal) causes a row to be skipped. If
9196 * any of the rows are on current page, the corresponding DOM elements are also
9199 * @method updateRows
9200 * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
9201 * Starting row to update: By Record instance, by Record's RecordSet
9202 * position index, by HTMLElement reference to the TR element, or by ID string
9203 * of the TR element.
9204 * @param aData {Object[]} Array of object literal of data for the rows.
9206 updateRows : function(startrow, aData) {
9207 if(lang.isArray(aData)) {
9208 var startIndex = startrow,
9209 oRecordSet = this._oRecordSet;
9211 if (!lang.isNumber(startrow)) {
9212 startIndex = this.getRecordIndex(startrow);
9215 if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
9216 var lastIndex = startIndex + aData.length,
9217 aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
9218 aNewRecords = oRecordSet.setRecords(aData, startIndex);
9220 // Update selected rows as necessary
9221 var tracker = this._aSelections || [],
9222 i=0, j, newId, oldId;
9223 for(; i<tracker.length; i++) {
9224 for(j=0; j<aOldRecords.length; j++) {
9225 oldId = aOldRecords[j].getId();
9226 if((tracker[i] === oldId)) {
9227 tracker[i] = aNewRecords[j].getId();
9229 else if(tracker[i].recordId === oldId) {
9230 tracker[i].recordId = aNewRecords[j].getId();
9236 var oPaginator = this.get('paginator');
9238 var pageStartIndex = (oPaginator.getPageRecords())[0],
9239 pageLastIndex = (oPaginator.getPageRecords())[1];
9241 // At least one of the new records affects the view
9242 if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
9246 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9247 YAHOO.log("Added " + aNewRecords.length +
9248 " rows starting at index " + startIndex +
9249 " with data " + lang.dump(aData), "info", this.toString());
9254 // Update the TR elements
9255 var loopN = this.get("renderLoopSize"),
9256 rowCount = aData.length, // how many needed
9257 lastRowIndex = this._elTbody.rows.length,
9258 isLast = (lastIndex >= lastRowIndex),
9259 isAdding = (lastIndex > lastRowIndex);
9261 this._oChainRender.add({
9262 method: function(oArg) {
9263 if((this instanceof DT) && this._sId) {
9264 var aRecords = oArg.aRecords,
9265 i = oArg.nCurrentRow,
9266 j = oArg.nDataPointer,
9267 len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
9269 for(; i < len; i++,j++) {
9270 if(isAdding && (i>=lastRowIndex)) {
9271 this._elTbody.appendChild(this._addTrEl(aRecords[j]));
9274 this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
9277 oArg.nCurrentRow = i;
9278 oArg.nDataPointer = j;
9281 iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
9282 argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
9284 timeout: (loopN > 0) ? 0 : -1
9286 this._oChainRender.add({
9287 method: function(oArg) {
9288 var recIndex = oArg.recIndex;
9290 if(recIndex === 0) {
9291 this._setFirstRow();
9297 this._setRowStripes();
9299 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
9300 YAHOO.log("Added " + aNewRecords.length +
9301 " rows starting at index " + startIndex +
9302 " with data " + lang.dump(aData), "info", this.toString());
9304 argument: {recIndex: startIndex, isLast: isLast},
9306 timeout: -1 // Needs to run immediately after the DOM insertions above
9308 this._runRenderChain();
9309 this.hideTableMessage();
9315 YAHOO.log("Could not update rows at " + startrow + " with " + lang.dump(aData), "warn", this.toString());
9319 * Deletes the given row's Record from the RecordSet. If the row is on current page,
9320 * the corresponding DOM elements are also deleted.
9323 * @param row {HTMLElement | String | Number} DOM element reference or ID string
9324 * to DataTable page element or RecordSet index.
9326 deleteRow : function(row) {
9327 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9328 if(lang.isNumber(nRecordIndex)) {
9329 var oRecord = this.getRecord(nRecordIndex);
9331 var nTrIndex = this.getTrIndex(nRecordIndex);
9333 // Remove from selection tracker if there
9334 var sRecordId = oRecord.getId();
9335 var tracker = this._aSelections || [];
9336 for(var j=tracker.length-1; j>-1; j--) {
9337 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9338 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9339 tracker.splice(j,1);
9343 // Delete Record from RecordSet
9344 var oData = this._oRecordSet.deleteRecord(nRecordIndex);
9348 // If paginated and the deleted row was on this or a prior page, just
9350 var oPaginator = this.get('paginator');
9352 // Update the paginator's totalRecords
9353 var totalRecords = oPaginator.get('totalRecords'),
9354 // must capture before the totalRecords change because
9355 // Paginator shifts to previous page automatically
9356 rng = oPaginator.getPageRecords();
9358 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9359 oPaginator.set('totalRecords',totalRecords - 1);
9362 // The deleted record was on this or a prior page, re-render
9363 if (!rng || nRecordIndex <= rng[1]) {
9367 this._oChainRender.add({
9368 method: function() {
9369 if((this instanceof DT) && this._sId) {
9370 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
9371 YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());
9375 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9377 this._runRenderChain();
9381 if(lang.isNumber(nTrIndex)) {
9382 this._oChainRender.add({
9383 method: function() {
9384 if((this instanceof DT) && this._sId) {
9385 var isLast = (nTrIndex == this.getLastTrEl().sectionRowIndex);
9386 this._deleteTrEl(nTrIndex);
9388 // Post-delete tasks
9389 if(this._elTbody.rows.length > 0) {
9391 if(nTrIndex === 0) {
9392 this._setFirstRow();
9398 if(nTrIndex != this._elTbody.rows.length) {
9399 this._setRowStripes(nTrIndex);
9403 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
9404 YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString());
9408 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9410 this._runRenderChain();
9417 YAHOO.log("Could not delete row: " + row, "warn", this.toString());
9422 * Convenience method to delete multiple rows.
9424 * @method deleteRows
9425 * @param row {HTMLElement | String | Number} DOM element reference or ID string
9426 * to DataTable page element or RecordSet index.
9427 * @param count {Number} (optional) How many rows to delete. A negative value
9428 * will delete towards the beginning.
9430 deleteRows : function(row, count) {
9431 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
9432 if(lang.isNumber(nRecordIndex)) {
9433 var oRecord = this.getRecord(nRecordIndex);
9435 var nTrIndex = this.getTrIndex(nRecordIndex);
9437 // Remove from selection tracker if there
9438 var sRecordId = oRecord.getId();
9439 var tracker = this._aSelections || [];
9440 for(var j=tracker.length-1; j>-1; j--) {
9441 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
9442 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
9443 tracker.splice(j,1);
9447 // Delete Record from RecordSet
9448 var highIndex = nRecordIndex;
9449 var lowIndex = nRecordIndex;
9451 // Validate count and account for negative value
9452 if(count && lang.isNumber(count)) {
9453 highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
9454 lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
9455 count = (count > 0) ? count : count*-1;
9458 count = highIndex - lowIndex + 1;
9465 var aData = this._oRecordSet.deleteRecords(lowIndex, count);
9469 var oPaginator = this.get('paginator'),
9470 loopN = this.get("renderLoopSize");
9471 // If paginated and the deleted row was on this or a prior page, just
9474 // Update the paginator's totalRecords
9475 var totalRecords = oPaginator.get('totalRecords'),
9476 // must capture before the totalRecords change because
9477 // Paginator shifts to previous page automatically
9478 rng = oPaginator.getPageRecords();
9480 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
9481 oPaginator.set('totalRecords',totalRecords - aData.length);
9484 // The records were on this or a prior page, re-render
9485 if (!rng || lowIndex <= rng[1]) {
9489 this._oChainRender.add({
9490 method: function(oArg) {
9491 if((this instanceof DT) && this._sId) {
9492 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9493 YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
9497 timeout: (loopN > 0) ? 0 : -1
9499 this._runRenderChain();
9504 if(lang.isNumber(nTrIndex)) {
9505 // Delete the TR elements starting with highest index
9506 var loopEnd = lowIndex;
9507 var nRowsNeeded = count; // how many needed
9508 this._oChainRender.add({
9509 method: function(oArg) {
9510 if((this instanceof DT) && this._sId) {
9511 var i = oArg.nCurrentRow,
9512 len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
9514 this._deleteTrEl(i);
9516 oArg.nCurrentRow = i;
9519 iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
9520 argument: {nCurrentRow:highIndex},
9522 timeout: (loopN > 0) ? 0 : -1
9524 this._oChainRender.add({
9525 method: function() {
9526 // Post-delete tasks
9527 if(this._elTbody.rows.length > 0) {
9528 this._setFirstRow();
9530 this._setRowStripes();
9533 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
9534 YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString());
9537 timeout: -1 // Needs to run immediately after the DOM deletions above
9539 this._runRenderChain();
9546 YAHOO.log("Could not delete " + count + " rows at row " + row, "warn", this.toString());
9598 * Outputs markup into the given TD based on given Record.
9600 * @method formatCell
9601 * @param elCell {HTMLElement} The liner DIV element within the TD.
9602 * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
9603 * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
9605 formatCell : function(elCell, oRecord, oColumn) {
9607 oRecord = this.getRecord(elCell);
9610 oColumn = this.getColumn(elCell.parentNode.cellIndex);
9613 if(oRecord && oColumn) {
9614 var sField = oColumn.field;
9615 var oData = oRecord.getData(sField);
9617 var fnFormatter = typeof oColumn.formatter === 'function' ?
9619 DT.Formatter[oColumn.formatter+''] ||
9620 DT.Formatter.defaultFormatter;
9622 // Apply special formatter
9624 fnFormatter.call(this, elCell, oRecord, oColumn, oData);
9627 elCell.innerHTML = oData;
9630 this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elCell});
9633 YAHOO.log("Could not format cell " + elCell, "error", this.toString());
9638 * For the given row and column, updates the Record with the given data. If the
9639 * cell is on current page, the corresponding DOM elements are also updated.
9641 * @method updateCell
9642 * @param oRecord {YAHOO.widget.Record} Record instance.
9643 * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
9644 * @param oData {Object} New data value for the cell.
9646 updateCell : function(oRecord, oColumn, oData) {
9647 // Validate Column and Record
9648 oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
9649 if(oColumn && oColumn.getKey() && (oRecord instanceof YAHOO.widget.Record)) {
9650 var sKey = oColumn.getKey(),
9652 // Copy data from the Record for the event that gets fired later
9653 //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
9654 oldData = oRecord.getData(sKey);
9656 // Update Record with new data
9657 this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
9659 // Update the TD only if row is on current page
9660 var elTd = this.getTdEl({record: oRecord, column: oColumn});
9662 this._oChainRender.add({
9663 method: function() {
9664 if((this instanceof DT) && this._sId) {
9665 this.formatCell(elTd.firstChild);
9666 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9667 YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
9668 ", Record index = " + this.getRecordIndex(oRecord) +
9669 ", page row index = " + this.getTrIndex(oRecord) +
9670 ", Column key = " + oColumn.getKey(), "info", this.toString());
9674 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
9676 this._runRenderChain();
9679 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
9680 YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() +
9681 ", Record index = " + this.getRecordIndex(oRecord) +
9682 ", page row index = " + this.getTrIndex(oRecord) +
9683 ", Column key = " + oColumn.getKey(), "info", this.toString());
9740 * Method executed during set() operation for the "paginator" attribute.
9741 * Adds and/or severs event listeners between DataTable and Paginator
9743 * @method _updatePaginator
9744 * @param newPag {Paginator} Paginator instance (or null) for DataTable to use
9747 _updatePaginator : function (newPag) {
9748 var oldPag = this.get('paginator');
9749 if (oldPag && newPag !== oldPag) {
9750 oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9753 newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
9758 * Update the UI infrastructure in response to a "paginator" attribute change.
9760 * @method _handlePaginatorChange
9761 * @param e {Object} Change event object containing keys 'type','newValue',
9765 _handlePaginatorChange : function (e) {
9766 if (e.prevValue === e.newValue) { return; }
9768 var newPag = e.newValue,
9769 oldPag = e.prevValue,
9770 containers = this._defaultPaginatorContainers();
9773 if (oldPag.getContainerNodes()[0] == containers[0]) {
9774 oldPag.set('containers',[]);
9778 // Convenience: share the default containers if possible.
9779 // Otherwise, remove the default containers from the DOM.
9780 if (containers[0]) {
9781 if (newPag && !newPag.getContainerNodes().length) {
9782 newPag.set('containers',containers);
9784 // No new Paginator to use existing containers, OR new
9785 // Paginator has configured containers.
9786 for (var i = containers.length - 1; i >= 0; --i) {
9787 if (containers[i]) {
9788 containers[i].parentNode.removeChild(containers[i]);
9801 this.renderPaginator();
9807 * Returns the default containers used for Paginators. If create param is
9808 * passed, the containers will be created and added to the DataTable container.
9810 * @method _defaultPaginatorContainers
9811 * @param create {boolean} Create the default containers if not found
9814 _defaultPaginatorContainers : function (create) {
9815 var above_id = this._sId + '-paginator0',
9816 below_id = this._sId + '-paginator1',
9817 above = Dom.get(above_id),
9818 below = Dom.get(below_id);
9820 if (create && (!above || !below)) {
9821 // One above and one below the table
9823 above = document.createElement('div');
9824 above.id = above_id;
9825 Dom.addClass(above, DT.CLASS_PAGINATOR);
9827 this._elContainer.insertBefore(above,this._elContainer.firstChild);
9831 below = document.createElement('div');
9832 below.id = below_id;
9833 Dom.addClass(below, DT.CLASS_PAGINATOR);
9835 this._elContainer.appendChild(below);
9839 return [above,below];
9843 * Renders the Paginator to the DataTable UI
9845 * @method renderPaginator
9847 renderPaginator : function () {
9848 var pag = this.get("paginator");
9849 if (!pag) { return; }
9851 // Add the containers if the Paginator is not configured with containers
9852 if (!pag.getContainerNodes().length) {
9853 pag.set('containers',this._defaultPaginatorContainers(true));
9860 * Overridable method gives implementers a hook to show loading message before
9861 * changing Paginator value.
9863 * @method doBeforePaginatorChange
9864 * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9865 * @return {Boolean} Return true to continue changing Paginator value.
9867 doBeforePaginatorChange : function(oPaginatorState) {
9868 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
9873 * Responds to new Pagination states. By default, updates the UI to reflect the
9874 * new state. If "dynamicData" is true, current selections are purged before
9875 * a request is sent to the DataSource for data for the new state (using the
9876 * request returned by "generateRequest()").
9878 * @method onPaginatorChangeRequest
9879 * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
9881 onPaginatorChangeRequest : function (oPaginatorState) {
9882 var ok = this.doBeforePaginatorChange(oPaginatorState);
9884 // Server-side pagination
9885 if(this.get("dynamicData")) {
9886 // Get the current state
9887 var oState = this.getState();
9889 // Update pagination values
9890 oState.pagination = oPaginatorState;
9892 // Get the request for the new state
9893 var request = this.get("generateRequest")(oState, this);
9896 this.unselectAllRows();
9897 this.unselectAllCells();
9899 // Get the new data from the server
9901 success : this.onDataReturnSetRows,
9902 failure : this.onDataReturnSetRows,
9903 argument : oState, // Pass along the new state to the callback
9906 this._oDataSource.sendRequest(request, callback);
9908 // Client-side pagination
9910 // Set the core pagination values silently (the second param)
9911 // to avoid looping back through the changeRequest mechanism
9912 oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
9913 oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
9920 YAHOO.log("Could not change Paginator value \"" + oPaginatorState + "\"", "warn", this.toString());
9973 // SELECTION/HIGHLIGHTING
9976 * Reference to last highlighted cell element
9978 * @property _elLastHighlightedTd
9982 _elLastHighlightedTd : null,
9985 * ID string of last highlighted row element
9987 * @property _sLastHighlightedTrElId
9991 //_sLastHighlightedTrElId : null,
9994 * Array to track row selections (by sRecordId) and/or cell selections
9995 * (by {recordId:sRecordId, columnKey:sColumnKey})
9997 * @property _aSelections
10001 _aSelections : null,
10004 * Record instance of the row selection anchor.
10006 * @property _oAnchorRecord
10007 * @type YAHOO.widget.Record
10010 _oAnchorRecord : null,
10013 * Object literal representing cell selection anchor:
10014 * {recordId:sRecordId, columnKey:sColumnKey}.
10016 * @property _oAnchorCell
10020 _oAnchorCell : null,
10023 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
10024 * from all TR elements on the page.
10026 * @method _unselectAllTrEls
10029 _unselectAllTrEls : function() {
10030 var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
10031 Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
10035 * Returns object literal of values that represent the selection trigger. Used
10036 * to determine selection behavior resulting from a key event.
10038 * @method _getSelectionTrigger
10041 _getSelectionTrigger : function() {
10042 var sMode = this.get("selectionMode");
10044 var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
10047 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
10048 oTriggerCell = this.getLastSelectedCell();
10049 // No selected cells found
10050 if(!oTriggerCell) {
10054 oTriggerRecord = this.getRecord(oTriggerCell.recordId);
10055 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
10056 elTriggerRow = this.getTrEl(oTriggerRecord);
10057 nTriggerTrIndex = this.getTrIndex(elTriggerRow);
10059 // Selected cell not found on this page
10060 if(nTriggerTrIndex === null) {
10064 oTrigger.record = oTriggerRecord;
10065 oTrigger.recordIndex = nTriggerRecordIndex;
10066 oTrigger.el = this.getTdEl(oTriggerCell);
10067 oTrigger.trIndex = nTriggerTrIndex;
10068 oTrigger.column = this.getColumn(oTriggerCell.columnKey);
10069 oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
10070 oTrigger.cell = oTriggerCell;
10077 oTriggerRecord = this.getLastSelectedRecord();
10078 // No selected rows found
10079 if(!oTriggerRecord) {
10083 // Selected row found, but is it on current page?
10084 oTriggerRecord = this.getRecord(oTriggerRecord);
10085 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
10086 elTriggerRow = this.getTrEl(oTriggerRecord);
10087 nTriggerTrIndex = this.getTrIndex(elTriggerRow);
10089 // Selected row not found on this page
10090 if(nTriggerTrIndex === null) {
10094 oTrigger.record = oTriggerRecord;
10095 oTrigger.recordIndex = nTriggerRecordIndex;
10096 oTrigger.el = elTriggerRow;
10097 oTrigger.trIndex = nTriggerTrIndex;
10105 * Returns object literal of values that represent the selection anchor. Used
10106 * to determine selection behavior resulting from a user event.
10108 * @method _getSelectionAnchor
10109 * @param oTrigger {Object} (Optional) Object literal of selection trigger values
10110 * (for key events).
10113 _getSelectionAnchor : function(oTrigger) {
10114 var sMode = this.get("selectionMode");
10116 var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
10119 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
10120 // Validate anchor cell
10121 var oAnchorCell = this._oAnchorCell;
10124 oAnchorCell = this._oAnchorCell = oTrigger.cell;
10130 oAnchorRecord = this._oAnchorCell.record;
10131 nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
10132 nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
10133 // If anchor cell is not on this page...
10134 if(nAnchorTrIndex === null) {
10135 // ...set TR index equal to top TR
10136 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
10137 nAnchorTrIndex = 0;
10139 // ...set TR index equal to bottom TR
10141 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10145 oAnchor.record = oAnchorRecord;
10146 oAnchor.recordIndex = nAnchorRecordIndex;
10147 oAnchor.trIndex = nAnchorTrIndex;
10148 oAnchor.column = this._oAnchorCell.column;
10149 oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
10150 oAnchor.cell = oAnchorCell;
10155 oAnchorRecord = this._oAnchorRecord;
10156 if(!oAnchorRecord) {
10158 oAnchorRecord = this._oAnchorRecord = oTrigger.record;
10165 nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
10166 nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
10167 // If anchor row is not on this page...
10168 if(nAnchorTrIndex === null) {
10169 // ...set TR index equal to top TR
10170 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
10171 nAnchorTrIndex = 0;
10173 // ...set TR index equal to bottom TR
10175 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
10179 oAnchor.record = oAnchorRecord;
10180 oAnchor.recordIndex = nAnchorRecordIndex;
10181 oAnchor.trIndex = nAnchorTrIndex;
10187 * Determines selection behavior resulting from a mouse event when selection mode
10188 * is set to "standard".
10190 * @method _handleStandardSelectionByMouse
10191 * @param oArgs.event {HTMLEvent} Event object.
10192 * @param oArgs.target {HTMLElement} Target element.
10195 _handleStandardSelectionByMouse : function(oArgs) {
10196 var elTarget = oArgs.target;
10198 // Validate target row
10199 var elTargetRow = this.getTrEl(elTarget);
10201 var e = oArgs.event;
10202 var bSHIFT = e.shiftKey;
10203 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10205 var oTargetRecord = this.getRecord(elTargetRow);
10206 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10208 var oAnchor = this._getSelectionAnchor();
10212 // Both SHIFT and CTRL
10213 if(bSHIFT && bCTRL) {
10216 if(this.isSelected(oAnchor.record)) {
10217 // Select all rows between anchor row and target row, including target row
10218 if(oAnchor.recordIndex < nTargetRecordIndex) {
10219 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
10220 if(!this.isSelected(i)) {
10225 // Select all rows between target row and anchor row, including target row
10227 for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
10228 if(!this.isSelected(i)) {
10235 // Unselect all rows between anchor row and target row
10236 if(oAnchor.recordIndex < nTargetRecordIndex) {
10237 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
10238 if(this.isSelected(i)) {
10239 this.unselectRow(i);
10243 // Unselect all rows between target row and anchor row
10245 for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
10246 if(this.isSelected(i)) {
10247 this.unselectRow(i);
10251 // Select the target row
10252 this.selectRow(oTargetRecord);
10258 this._oAnchorRecord = oTargetRecord;
10260 // Toggle selection of target
10261 if(this.isSelected(oTargetRecord)) {
10262 this.unselectRow(oTargetRecord);
10265 this.selectRow(oTargetRecord);
10271 this.unselectAllRows();
10275 // Select all rows between anchor row and target row,
10276 // including the anchor row and target row
10277 if(oAnchor.recordIndex < nTargetRecordIndex) {
10278 for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
10282 // Select all rows between target row and anchor row,
10283 // including the target row and anchor row
10285 for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
10293 this._oAnchorRecord = oTargetRecord;
10295 // Select target row only
10296 this.selectRow(oTargetRecord);
10302 this._oAnchorRecord = oTargetRecord;
10304 // Toggle selection of target
10305 if(this.isSelected(oTargetRecord)) {
10306 this.unselectRow(oTargetRecord);
10309 this.selectRow(oTargetRecord);
10312 // Neither SHIFT nor CTRL
10314 this._handleSingleSelectionByMouse(oArgs);
10321 * Determines selection behavior resulting from a key event when selection mode
10322 * is set to "standard".
10324 * @method _handleStandardSelectionByKey
10325 * @param e {HTMLEvent} Event object.
10328 _handleStandardSelectionByKey : function(e) {
10329 var nKey = Ev.getCharCode(e);
10331 if((nKey == 38) || (nKey == 40)) {
10332 var bSHIFT = e.shiftKey;
10334 // Validate trigger
10335 var oTrigger = this._getSelectionTrigger();
10336 // Arrow selection only works if last selected row is on current page
10344 var oAnchor = this._getSelectionAnchor(oTrigger);
10346 // Determine which direction we're going to
10348 // Selecting down away from anchor row
10349 if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
10350 this.selectRow(this.getNextTrEl(oTrigger.el));
10352 // Selecting up away from anchor row
10353 else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
10354 this.selectRow(this.getPreviousTrEl(oTrigger.el));
10356 // Unselect trigger
10358 this.unselectRow(oTrigger.el);
10362 this._handleSingleSelectionByKey(e);
10368 * Determines selection behavior resulting from a mouse event when selection mode
10369 * is set to "single".
10371 * @method _handleSingleSelectionByMouse
10372 * @param oArgs.event {HTMLEvent} Event object.
10373 * @param oArgs.target {HTMLElement} Target element.
10376 _handleSingleSelectionByMouse : function(oArgs) {
10377 var elTarget = oArgs.target;
10379 // Validate target row
10380 var elTargetRow = this.getTrEl(elTarget);
10382 var oTargetRecord = this.getRecord(elTargetRow);
10385 this._oAnchorRecord = oTargetRecord;
10387 // Select only target
10388 this.unselectAllRows();
10389 this.selectRow(oTargetRecord);
10394 * Determines selection behavior resulting from a key event when selection mode
10395 * is set to "single".
10397 * @method _handleSingleSelectionByKey
10398 * @param e {HTMLEvent} Event object.
10401 _handleSingleSelectionByKey : function(e) {
10402 var nKey = Ev.getCharCode(e);
10404 if((nKey == 38) || (nKey == 40)) {
10405 // Validate trigger
10406 var oTrigger = this._getSelectionTrigger();
10407 // Arrow selection only works if last selected row is on current page
10414 // Determine the new row to select
10416 if(nKey == 38) { // arrow up
10417 elNew = this.getPreviousTrEl(oTrigger.el);
10419 // Validate new row
10420 if(elNew === null) {
10421 //TODO: wrap around to last tr on current page
10422 //elNew = this.getLastTrEl();
10424 //TODO: wrap back to last tr of previous page
10426 // Top row selection is sticky
10427 elNew = this.getFirstTrEl();
10430 else if(nKey == 40) { // arrow down
10431 elNew = this.getNextTrEl(oTrigger.el);
10433 // Validate new row
10434 if(elNew === null) {
10435 //TODO: wrap around to first tr on current page
10436 //elNew = this.getFirstTrEl();
10438 //TODO: wrap forward to first tr of previous page
10440 // Bottom row selection is sticky
10441 elNew = this.getLastTrEl();
10445 // Unselect all rows
10446 this.unselectAllRows();
10448 // Select the new row
10449 this.selectRow(elNew);
10452 this._oAnchorRecord = this.getRecord(elNew);
10457 * Determines selection behavior resulting from a mouse event when selection mode
10458 * is set to "cellblock".
10460 * @method _handleCellBlockSelectionByMouse
10461 * @param oArgs.event {HTMLEvent} Event object.
10462 * @param oArgs.target {HTMLElement} Target element.
10465 _handleCellBlockSelectionByMouse : function(oArgs) {
10466 var elTarget = oArgs.target;
10468 // Validate target cell
10469 var elTargetCell = this.getTdEl(elTarget);
10471 var e = oArgs.event;
10472 var bSHIFT = e.shiftKey;
10473 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10475 var elTargetRow = this.getTrEl(elTargetCell);
10476 var nTargetTrIndex = this.getTrIndex(elTargetRow);
10477 var oTargetColumn = this.getColumn(elTargetCell);
10478 var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10479 var oTargetRecord = this.getRecord(elTargetRow);
10480 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10481 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10483 var oAnchor = this._getSelectionAnchor();
10485 var allRows = this.getTbodyEl().rows;
10486 var startIndex, endIndex, currentRow, i, j;
10488 // Both SHIFT and CTRL
10489 if(bSHIFT && bCTRL) {
10493 // Anchor is selected
10494 if(this.isSelected(oAnchor.cell)) {
10495 // All cells are on the same row
10496 if(oAnchor.recordIndex === nTargetRecordIndex) {
10497 // Select all cells between anchor cell and target cell, including target cell
10498 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10499 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10500 this.selectCell(elTargetRow.cells[i]);
10503 // Select all cells between target cell and anchor cell, including target cell
10504 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10505 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10506 this.selectCell(elTargetRow.cells[i]);
10510 // Anchor row is above target row
10511 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10512 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10513 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10515 // Select all cells from startIndex to endIndex on rows between anchor row and target row
10516 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10517 for(j=startIndex; j<=endIndex; j++) {
10518 this.selectCell(allRows[i].cells[j]);
10522 // Anchor row is below target row
10524 startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
10525 endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
10527 // Select all cells from startIndex to endIndex on rows between target row and anchor row
10528 for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
10529 for(j=endIndex; j>=startIndex; j--) {
10530 this.selectCell(allRows[i].cells[j]);
10535 // Anchor cell is unselected
10537 // All cells are on the same row
10538 if(oAnchor.recordIndex === nTargetRecordIndex) {
10539 // Unselect all cells between anchor cell and target cell
10540 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10541 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10542 this.unselectCell(elTargetRow.cells[i]);
10545 // Select all cells between target cell and anchor cell
10546 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10547 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10548 this.unselectCell(elTargetRow.cells[i]);
10552 // Anchor row is above target row
10553 if(oAnchor.recordIndex < nTargetRecordIndex) {
10554 // Unselect all cells from anchor cell to target cell
10555 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10556 currentRow = allRows[i];
10557 for(j=0; j<currentRow.cells.length; j++) {
10558 // This is the anchor row, only unselect cells after the anchor cell
10559 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
10560 if(j>oAnchor.colKeyIndex) {
10561 this.unselectCell(currentRow.cells[j]);
10564 // This is the target row, only unelect cells before the target cell
10565 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
10566 if(j<nTargetColKeyIndex) {
10567 this.unselectCell(currentRow.cells[j]);
10570 // Unselect all cells on this row
10572 this.unselectCell(currentRow.cells[j]);
10577 // Anchor row is below target row
10579 // Unselect all cells from target cell to anchor cell
10580 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10581 currentRow = allRows[i];
10582 for(j=0; j<currentRow.cells.length; j++) {
10583 // This is the target row, only unselect cells after the target cell
10584 if(currentRow.sectionRowIndex == nTargetTrIndex) {
10585 if(j>nTargetColKeyIndex) {
10586 this.unselectCell(currentRow.cells[j]);
10589 // This is the anchor row, only unselect cells before the anchor cell
10590 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
10591 if(j<oAnchor.colKeyIndex) {
10592 this.unselectCell(currentRow.cells[j]);
10595 // Unselect all cells on this row
10597 this.unselectCell(currentRow.cells[j]);
10603 // Select the target cell
10604 this.selectCell(elTargetCell);
10610 this._oAnchorCell = oTargetCell;
10612 // Toggle selection of target
10613 if(this.isSelected(oTargetCell)) {
10614 this.unselectCell(oTargetCell);
10617 this.selectCell(oTargetCell);
10624 this.unselectAllCells();
10628 // All cells are on the same row
10629 if(oAnchor.recordIndex === nTargetRecordIndex) {
10630 // Select all cells between anchor cell and target cell,
10631 // including the anchor cell and target cell
10632 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10633 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
10634 this.selectCell(elTargetRow.cells[i]);
10637 // Select all cells between target cell and anchor cell
10638 // including the target cell and anchor cell
10639 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10640 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
10641 this.selectCell(elTargetRow.cells[i]);
10645 // Anchor row is above target row
10646 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10647 // Select the cellblock from anchor cell to target cell
10648 // including the anchor cell and the target cell
10649 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10650 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10652 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10653 for(j=startIndex; j<=endIndex; j++) {
10654 this.selectCell(allRows[i].cells[j]);
10658 // Anchor row is below target row
10660 // Select the cellblock from target cell to anchor cell
10661 // including the target cell and the anchor cell
10662 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
10663 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
10665 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
10666 for(j=startIndex; j<=endIndex; j++) {
10667 this.selectCell(allRows[i].cells[j]);
10675 this._oAnchorCell = oTargetCell;
10677 // Select target only
10678 this.selectCell(oTargetCell);
10685 this._oAnchorCell = oTargetCell;
10687 // Toggle selection of target
10688 if(this.isSelected(oTargetCell)) {
10689 this.unselectCell(oTargetCell);
10692 this.selectCell(oTargetCell);
10696 // Neither SHIFT nor CTRL
10698 this._handleSingleCellSelectionByMouse(oArgs);
10704 * Determines selection behavior resulting from a key event when selection mode
10705 * is set to "cellblock".
10707 * @method _handleCellBlockSelectionByKey
10708 * @param e {HTMLEvent} Event object.
10711 _handleCellBlockSelectionByKey : function(e) {
10712 var nKey = Ev.getCharCode(e);
10713 var bSHIFT = e.shiftKey;
10714 if((nKey == 9) || !bSHIFT) {
10715 this._handleSingleCellSelectionByKey(e);
10719 if((nKey > 36) && (nKey < 41)) {
10720 // Validate trigger
10721 var oTrigger = this._getSelectionTrigger();
10722 // Arrow selection only works if last selected row is on current page
10730 var oAnchor = this._getSelectionAnchor(oTrigger);
10732 var i, startIndex, endIndex, elNew, elNewRow;
10733 var allRows = this.getTbodyEl().rows;
10734 var elThisRow = oTrigger.el.parentNode;
10736 // Determine which direction we're going to
10738 if(nKey == 40) { // arrow down
10739 // Selecting away from anchor cell
10740 if(oAnchor.recordIndex <= oTrigger.recordIndex) {
10741 // Select the horiz block on the next row...
10742 // ...making sure there is room below the trigger row
10743 elNewRow = this.getNextTrEl(oTrigger.el);
10745 startIndex = oAnchor.colKeyIndex;
10746 endIndex = oTrigger.colKeyIndex;
10748 if(startIndex > endIndex) {
10749 for(i=startIndex; i>=endIndex; i--) {
10750 elNew = elNewRow.cells[i];
10751 this.selectCell(elNew);
10756 for(i=startIndex; i<=endIndex; i++) {
10757 elNew = elNewRow.cells[i];
10758 this.selectCell(elNew);
10763 // Unselecting towards anchor cell
10765 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10766 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10767 // Unselect the horiz block on this row towards the next row
10768 for(i=startIndex; i<=endIndex; i++) {
10769 this.unselectCell(elThisRow.cells[i]);
10774 else if(nKey == 38) {
10775 // Selecting away from anchor cell
10776 if(oAnchor.recordIndex >= oTrigger.recordIndex) {
10777 // Select the horiz block on the previous row...
10778 // ...making sure there is room
10779 elNewRow = this.getPreviousTrEl(oTrigger.el);
10781 // Select in order from anchor to trigger...
10782 startIndex = oAnchor.colKeyIndex;
10783 endIndex = oTrigger.colKeyIndex;
10785 if(startIndex > endIndex) {
10786 for(i=startIndex; i>=endIndex; i--) {
10787 elNew = elNewRow.cells[i];
10788 this.selectCell(elNew);
10793 for(i=startIndex; i<=endIndex; i++) {
10794 elNew = elNewRow.cells[i];
10795 this.selectCell(elNew);
10800 // Unselecting towards anchor cell
10802 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10803 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
10804 // Unselect the horiz block on this row towards the previous row
10805 for(i=startIndex; i<=endIndex; i++) {
10806 this.unselectCell(elThisRow.cells[i]);
10811 else if(nKey == 39) {
10812 // Selecting away from anchor cell
10813 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
10814 // Select the next vert block to the right...
10815 // ...making sure there is room
10816 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
10817 // Select in order from anchor to trigger...
10818 startIndex = oAnchor.trIndex;
10819 endIndex = oTrigger.trIndex;
10821 if(startIndex > endIndex) {
10822 for(i=startIndex; i>=endIndex; i--) {
10823 elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10824 this.selectCell(elNew);
10829 for(i=startIndex; i<=endIndex; i++) {
10830 elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
10831 this.selectCell(elNew);
10836 // Unselecting towards anchor cell
10838 // Unselect the vert block on this column towards the right
10839 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10840 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10841 for(i=startIndex; i<=endIndex; i++) {
10842 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10847 else if(nKey == 37) {
10848 // Selecting away from anchor cell
10849 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
10850 //Select the previous vert block to the left
10851 if(oTrigger.colKeyIndex > 0) {
10852 // Select in order from anchor to trigger...
10853 startIndex = oAnchor.trIndex;
10854 endIndex = oTrigger.trIndex;
10856 if(startIndex > endIndex) {
10857 for(i=startIndex; i>=endIndex; i--) {
10858 elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10859 this.selectCell(elNew);
10864 for(i=startIndex; i<=endIndex; i++) {
10865 elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
10866 this.selectCell(elNew);
10871 // Unselecting towards anchor cell
10873 // Unselect the vert block on this column towards the left
10874 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
10875 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
10876 for(i=startIndex; i<=endIndex; i++) {
10877 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
10885 * Determines selection behavior resulting from a mouse event when selection mode
10886 * is set to "cellrange".
10888 * @method _handleCellRangeSelectionByMouse
10889 * @param oArgs.event {HTMLEvent} Event object.
10890 * @param oArgs.target {HTMLElement} Target element.
10893 _handleCellRangeSelectionByMouse : function(oArgs) {
10894 var elTarget = oArgs.target;
10896 // Validate target cell
10897 var elTargetCell = this.getTdEl(elTarget);
10899 var e = oArgs.event;
10900 var bSHIFT = e.shiftKey;
10901 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
10903 var elTargetRow = this.getTrEl(elTargetCell);
10904 var nTargetTrIndex = this.getTrIndex(elTargetRow);
10905 var oTargetColumn = this.getColumn(elTargetCell);
10906 var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
10907 var oTargetRecord = this.getRecord(elTargetRow);
10908 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
10909 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
10911 var oAnchor = this._getSelectionAnchor();
10913 var allRows = this.getTbodyEl().rows;
10914 var currentRow, i, j;
10916 // Both SHIFT and CTRL
10917 if(bSHIFT && bCTRL) {
10921 // Anchor is selected
10922 if(this.isSelected(oAnchor.cell)) {
10923 // All cells are on the same row
10924 if(oAnchor.recordIndex === nTargetRecordIndex) {
10925 // Select all cells between anchor cell and target cell, including target cell
10926 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10927 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
10928 this.selectCell(elTargetRow.cells[i]);
10931 // Select all cells between target cell and anchor cell, including target cell
10932 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10933 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
10934 this.selectCell(elTargetRow.cells[i]);
10938 // Anchor row is above target row
10939 else if(oAnchor.recordIndex < nTargetRecordIndex) {
10940 // Select all cells on anchor row from anchor cell to the end of the row
10941 for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
10942 this.selectCell(elTargetRow.cells[i]);
10945 // Select all cells on all rows between anchor row and target row
10946 for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
10947 for(j=0; j<allRows[i].cells.length; j++){
10948 this.selectCell(allRows[i].cells[j]);
10952 // Select all cells on target row from first cell to the target cell
10953 for(i=0; i<=nTargetColKeyIndex; i++) {
10954 this.selectCell(elTargetRow.cells[i]);
10957 // Anchor row is below target row
10959 // Select all cells on target row from target cell to the end of the row
10960 for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
10961 this.selectCell(elTargetRow.cells[i]);
10964 // Select all cells on all rows between target row and anchor row
10965 for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
10966 for(j=0; j<allRows[i].cells.length; j++){
10967 this.selectCell(allRows[i].cells[j]);
10971 // Select all cells on anchor row from first cell to the anchor cell
10972 for(i=0; i<oAnchor.colKeyIndex; i++) {
10973 this.selectCell(elTargetRow.cells[i]);
10977 // Anchor cell is unselected
10979 // All cells are on the same row
10980 if(oAnchor.recordIndex === nTargetRecordIndex) {
10981 // Unselect all cells between anchor cell and target cell
10982 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
10983 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
10984 this.unselectCell(elTargetRow.cells[i]);
10987 // Select all cells between target cell and anchor cell
10988 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
10989 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
10990 this.unselectCell(elTargetRow.cells[i]);
10994 // Anchor row is above target row
10995 if(oAnchor.recordIndex < nTargetRecordIndex) {
10996 // Unselect all cells from anchor cell to target cell
10997 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
10998 currentRow = allRows[i];
10999 for(j=0; j<currentRow.cells.length; j++) {
11000 // This is the anchor row, only unselect cells after the anchor cell
11001 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
11002 if(j>oAnchor.colKeyIndex) {
11003 this.unselectCell(currentRow.cells[j]);
11006 // This is the target row, only unelect cells before the target cell
11007 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
11008 if(j<nTargetColKeyIndex) {
11009 this.unselectCell(currentRow.cells[j]);
11012 // Unselect all cells on this row
11014 this.unselectCell(currentRow.cells[j]);
11019 // Anchor row is below target row
11021 // Unselect all cells from target cell to anchor cell
11022 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
11023 currentRow = allRows[i];
11024 for(j=0; j<currentRow.cells.length; j++) {
11025 // This is the target row, only unselect cells after the target cell
11026 if(currentRow.sectionRowIndex == nTargetTrIndex) {
11027 if(j>nTargetColKeyIndex) {
11028 this.unselectCell(currentRow.cells[j]);
11031 // This is the anchor row, only unselect cells before the anchor cell
11032 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11033 if(j<oAnchor.colKeyIndex) {
11034 this.unselectCell(currentRow.cells[j]);
11037 // Unselect all cells on this row
11039 this.unselectCell(currentRow.cells[j]);
11045 // Select the target cell
11046 this.selectCell(elTargetCell);
11052 this._oAnchorCell = oTargetCell;
11054 // Toggle selection of target
11055 if(this.isSelected(oTargetCell)) {
11056 this.unselectCell(oTargetCell);
11059 this.selectCell(oTargetCell);
11066 this.unselectAllCells();
11070 // All cells are on the same row
11071 if(oAnchor.recordIndex === nTargetRecordIndex) {
11072 // Select all cells between anchor cell and target cell,
11073 // including the anchor cell and target cell
11074 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
11075 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
11076 this.selectCell(elTargetRow.cells[i]);
11079 // Select all cells between target cell and anchor cell
11080 // including the target cell and anchor cell
11081 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
11082 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
11083 this.selectCell(elTargetRow.cells[i]);
11087 // Anchor row is above target row
11088 else if(oAnchor.recordIndex < nTargetRecordIndex) {
11089 // Select all cells from anchor cell to target cell
11090 // including the anchor cell and target cell
11091 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
11092 currentRow = allRows[i];
11093 for(j=0; j<currentRow.cells.length; j++) {
11094 // This is the anchor row, only select the anchor cell and after
11095 if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11096 if(j>=oAnchor.colKeyIndex) {
11097 this.selectCell(currentRow.cells[j]);
11100 // This is the target row, only select the target cell and before
11101 else if(currentRow.sectionRowIndex == nTargetTrIndex) {
11102 if(j<=nTargetColKeyIndex) {
11103 this.selectCell(currentRow.cells[j]);
11106 // Select all cells on this row
11108 this.selectCell(currentRow.cells[j]);
11113 // Anchor row is below target row
11115 // Select all cells from target cell to anchor cell,
11116 // including the target cell and anchor cell
11117 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
11118 currentRow = allRows[i];
11119 for(j=0; j<currentRow.cells.length; j++) {
11120 // This is the target row, only select the target cell and after
11121 if(currentRow.sectionRowIndex == nTargetTrIndex) {
11122 if(j>=nTargetColKeyIndex) {
11123 this.selectCell(currentRow.cells[j]);
11126 // This is the anchor row, only select the anchor cell and before
11127 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
11128 if(j<=oAnchor.colKeyIndex) {
11129 this.selectCell(currentRow.cells[j]);
11132 // Select all cells on this row
11134 this.selectCell(currentRow.cells[j]);
11143 this._oAnchorCell = oTargetCell;
11145 // Select target only
11146 this.selectCell(oTargetCell);
11155 this._oAnchorCell = oTargetCell;
11157 // Toggle selection of target
11158 if(this.isSelected(oTargetCell)) {
11159 this.unselectCell(oTargetCell);
11162 this.selectCell(oTargetCell);
11166 // Neither SHIFT nor CTRL
11168 this._handleSingleCellSelectionByMouse(oArgs);
11174 * Determines selection behavior resulting from a key event when selection mode
11175 * is set to "cellrange".
11177 * @method _handleCellRangeSelectionByKey
11178 * @param e {HTMLEvent} Event object.
11181 _handleCellRangeSelectionByKey : function(e) {
11182 var nKey = Ev.getCharCode(e);
11183 var bSHIFT = e.shiftKey;
11184 if((nKey == 9) || !bSHIFT) {
11185 this._handleSingleCellSelectionByKey(e);
11189 if((nKey > 36) && (nKey < 41)) {
11190 // Validate trigger
11191 var oTrigger = this._getSelectionTrigger();
11192 // Arrow selection only works if last selected row is on current page
11200 var oAnchor = this._getSelectionAnchor(oTrigger);
11202 var i, elNewRow, elNew;
11203 var allRows = this.getTbodyEl().rows;
11204 var elThisRow = oTrigger.el.parentNode;
11208 elNewRow = this.getNextTrEl(oTrigger.el);
11210 // Selecting away from anchor cell
11211 if(oAnchor.recordIndex <= oTrigger.recordIndex) {
11212 // Select all cells to the end of this row
11213 for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){
11214 elNew = elThisRow.cells[i];
11215 this.selectCell(elNew);
11218 // Select some of the cells on the next row down
11220 for(i=0; i<=oTrigger.colKeyIndex; i++){
11221 elNew = elNewRow.cells[i];
11222 this.selectCell(elNew);
11226 // Unselecting towards anchor cell
11228 // Unselect all cells to the end of this row
11229 for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){
11230 this.unselectCell(elThisRow.cells[i]);
11233 // Unselect some of the cells on the next row down
11235 for(i=0; i<oTrigger.colKeyIndex; i++){
11236 this.unselectCell(elNewRow.cells[i]);
11242 else if(nKey == 38) {
11243 elNewRow = this.getPreviousTrEl(oTrigger.el);
11245 // Selecting away from anchor cell
11246 if(oAnchor.recordIndex >= oTrigger.recordIndex) {
11247 // Select all the cells to the beginning of this row
11248 for(i=oTrigger.colKeyIndex-1; i>-1; i--){
11249 elNew = elThisRow.cells[i];
11250 this.selectCell(elNew);
11253 // Select some of the cells from the end of the previous row
11255 for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){
11256 elNew = elNewRow.cells[i];
11257 this.selectCell(elNew);
11261 // Unselecting towards anchor cell
11263 // Unselect all the cells to the beginning of this row
11264 for(i=oTrigger.colKeyIndex; i>-1; i--){
11265 this.unselectCell(elThisRow.cells[i]);
11268 // Unselect some of the cells from the end of the previous row
11270 for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){
11271 this.unselectCell(elNewRow.cells[i]);
11277 else if(nKey == 39) {
11278 elNewRow = this.getNextTrEl(oTrigger.el);
11280 // Selecting away from anchor cell
11281 if(oAnchor.recordIndex < oTrigger.recordIndex) {
11282 // Select the next cell to the right
11283 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11284 elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11285 this.selectCell(elNew);
11287 // Select the first cell of the next row
11288 else if(elNewRow) {
11289 elNew = elNewRow.cells[0];
11290 this.selectCell(elNew);
11293 // Unselecting towards anchor cell
11294 else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11295 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11297 // Unselect this cell towards the right
11298 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11300 // Unselect this cells towards the first cell of the next row
11304 // Anchor is on this row
11306 // Selecting away from anchor
11307 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
11308 // Select the next cell to the right
11309 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
11310 elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
11311 this.selectCell(elNew);
11313 // Select the first cell on the next row
11314 else if(oTrigger.trIndex < allRows.length-1){
11315 elNew = elNewRow.cells[0];
11316 this.selectCell(elNew);
11319 // Unselecting towards anchor
11321 // Unselect this cell towards the right
11322 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11327 else if(nKey == 37) {
11328 elNewRow = this.getPreviousTrEl(oTrigger.el);
11330 // Unselecting towards the anchor
11331 if(oAnchor.recordIndex < oTrigger.recordIndex) {
11332 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11334 // Unselect this cell towards the left
11335 if(oTrigger.colKeyIndex > 0) {
11337 // Unselect this cell towards the last cell of the previous row
11341 // Selecting towards the anchor
11342 else if(oAnchor.recordIndex > oTrigger.recordIndex) {
11343 // Select the next cell to the left
11344 if(oTrigger.colKeyIndex > 0) {
11345 elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11346 this.selectCell(elNew);
11348 // Select the last cell of the previous row
11349 else if(oTrigger.trIndex > 0){
11350 elNew = elNewRow.cells[elNewRow.cells.length-1];
11351 this.selectCell(elNew);
11354 // Anchor is on this row
11356 // Selecting away from anchor cell
11357 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
11358 // Select the next cell to the left
11359 if(oTrigger.colKeyIndex > 0) {
11360 elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
11361 this.selectCell(elNew);
11363 // Select the last cell of the previous row
11364 else if(oTrigger.trIndex > 0){
11365 elNew = elNewRow.cells[elNewRow.cells.length-1];
11366 this.selectCell(elNew);
11369 // Unselecting towards anchor cell
11371 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
11373 // Unselect this cell towards the left
11374 if(oTrigger.colKeyIndex > 0) {
11376 // Unselect this cell towards the last cell of the previous row
11386 * Determines selection behavior resulting from a mouse event when selection mode
11387 * is set to "singlecell".
11389 * @method _handleSingleCellSelectionByMouse
11390 * @param oArgs.event {HTMLEvent} Event object.
11391 * @param oArgs.target {HTMLElement} Target element.
11394 _handleSingleCellSelectionByMouse : function(oArgs) {
11395 var elTarget = oArgs.target;
11397 // Validate target cell
11398 var elTargetCell = this.getTdEl(elTarget);
11400 var elTargetRow = this.getTrEl(elTargetCell);
11401 var oTargetRecord = this.getRecord(elTargetRow);
11402 var oTargetColumn = this.getColumn(elTargetCell);
11403 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
11406 this._oAnchorCell = oTargetCell;
11408 // Select only target
11409 this.unselectAllCells();
11410 this.selectCell(oTargetCell);
11415 * Determines selection behavior resulting from a key event when selection mode
11416 * is set to "singlecell".
11418 * @method _handleSingleCellSelectionByKey
11419 * @param e {HTMLEvent} Event object.
11422 _handleSingleCellSelectionByKey : function(e) {
11423 var nKey = Ev.getCharCode(e);
11424 if((nKey == 9) || ((nKey > 36) && (nKey < 41))) {
11425 var bSHIFT = e.shiftKey;
11427 // Validate trigger
11428 var oTrigger = this._getSelectionTrigger();
11429 // Arrow selection only works if last selected row is on current page
11434 // Determine the new cell to select
11436 if(nKey == 40) { // Arrow down
11437 elNew = this.getBelowTdEl(oTrigger.el);
11439 // Validate new cell
11440 if(elNew === null) {
11441 //TODO: wrap around to first tr on current page
11443 //TODO: wrap forward to first tr of next page
11445 // Bottom selection is sticky
11446 elNew = oTrigger.el;
11449 else if(nKey == 38) { // Arrow up
11450 elNew = this.getAboveTdEl(oTrigger.el);
11452 // Validate new cell
11453 if(elNew === null) {
11454 //TODO: wrap around to last tr on current page
11456 //TODO: wrap back to last tr of previous page
11458 // Top selection is sticky
11459 elNew = oTrigger.el;
11462 else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab
11463 elNew = this.getNextTdEl(oTrigger.el);
11465 // Validate new cell
11466 if(elNew === null) {
11467 //TODO: wrap around to first td on current page
11469 //TODO: wrap forward to first td of next page
11471 // Top-left selection is sticky, and release TAB focus
11472 //elNew = oTrigger.el;
11476 else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab
11477 elNew = this.getPreviousTdEl(oTrigger.el);
11479 // Validate new cell
11480 if(elNew === null) {
11481 //TODO: wrap around to last td on current page
11483 //TODO: wrap back to last td of previous page
11485 // Bottom-right selection is sticky, and release TAB focus
11486 //elNew = oTrigger.el;
11493 // Unselect all cells
11494 this.unselectAllCells();
11496 // Select the new cell
11497 this.selectCell(elNew);
11500 this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)};
11505 * Returns array of selected TR elements on the page.
11507 * @method getSelectedTrEls
11508 * @return {HTMLElement[]} Array of selected TR elements.
11510 getSelectedTrEls : function() {
11511 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
11515 * Sets given row to the selected state.
11517 * @method selectRow
11518 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11519 * reference or ID string, Record instance, or RecordSet position index.
11521 selectRow : function(row) {
11522 var oRecord, elRow;
11524 if(row instanceof YAHOO.widget.Record) {
11525 oRecord = this._oRecordSet.getRecord(row);
11526 elRow = this.getTrEl(oRecord);
11528 else if(lang.isNumber(row)) {
11529 oRecord = this.getRecord(row);
11530 elRow = this.getTrEl(oRecord);
11533 elRow = this.getTrEl(row);
11534 oRecord = this.getRecord(elRow);
11538 // Update selection trackers
11539 var tracker = this._aSelections || [];
11540 var sRecordId = oRecord.getId();
11543 // Remove if already there:
11544 // Use Array.indexOf if available...
11545 /*if(tracker.indexOf && (tracker.indexOf(sRecordId) > -1)) {
11546 tracker.splice(tracker.indexOf(sRecordId),1);
11548 if(tracker.indexOf) {
11549 index = tracker.indexOf(sRecordId);
11552 // ...or do it the old-fashioned way
11554 for(var j=tracker.length-1; j>-1; j--) {
11555 if(tracker[j] === sRecordId){
11562 tracker.splice(index,1);
11566 tracker.push(sRecordId);
11567 this._aSelections = tracker;
11570 if(!this._oAnchorRecord) {
11571 this._oAnchorRecord = oRecord;
11576 Dom.addClass(elRow, DT.CLASS_SELECTED);
11579 this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
11580 YAHOO.log("Selected " + elRow, "info", this.toString());
11583 YAHOO.log("Could not select row " + row, "warn", this.toString());
11588 * Sets given row to the unselected state.
11590 * @method unselectRow
11591 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
11592 * reference or ID string, Record instance, or RecordSet position index.
11594 unselectRow : function(row) {
11595 var elRow = this.getTrEl(row);
11598 if(row instanceof YAHOO.widget.Record) {
11599 oRecord = this._oRecordSet.getRecord(row);
11601 else if(lang.isNumber(row)) {
11602 oRecord = this.getRecord(row);
11605 oRecord = this.getRecord(elRow);
11609 // Update selection trackers
11610 var tracker = this._aSelections || [];
11611 var sRecordId = oRecord.getId();
11614 // Use Array.indexOf if available...
11615 if(tracker.indexOf) {
11616 index = tracker.indexOf(sRecordId);
11618 // ...or do it the old-fashioned way
11620 for(var j=tracker.length-1; j>-1; j--) {
11621 if(tracker[j] === sRecordId){
11629 tracker.splice(index,1);
11630 this._aSelections = tracker;
11633 Dom.removeClass(elRow, DT.CLASS_SELECTED);
11635 this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
11636 YAHOO.log("Unselected " + elRow, "info", this.toString());
11641 YAHOO.log("Could not unselect row " + row, "warn", this.toString());
11645 * Clears out all row selections.
11647 * @method unselectAllRows
11649 unselectAllRows : function() {
11650 // Remove all rows from tracker
11651 var tracker = this._aSelections || [],
11654 for(var j=tracker.length-1; j>-1; j--) {
11655 if(lang.isString(tracker[j])){
11656 recId = tracker.splice(j,1);
11657 removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId);
11662 this._aSelections = tracker;
11665 this._unselectAllTrEls();
11667 this.fireEvent("unselectAllRowsEvent", {records: removed});
11668 YAHOO.log("Unselected all rows", "info", this.toString());
11672 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
11673 * from all TD elements in the internal tracker.
11675 * @method _unselectAllTdEls
11678 _unselectAllTdEls : function() {
11679 var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11680 Dom.removeClass(selectedCells, DT.CLASS_SELECTED);
11684 * Returns array of selected TD elements on the page.
11686 * @method getSelectedTdEls
11687 * @return {HTMLElement[]} Array of selected TD elements.
11689 getSelectedTdEls : function() {
11690 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
11694 * Sets given cell to the selected state.
11696 * @method selectCell
11697 * @param cell {HTMLElement | String} DOM element reference or ID string
11698 * to DataTable page element or RecordSet index.
11700 selectCell : function(cell) {
11701 //TODO: accept {record} in selectCell()
11702 var elCell = this.getTdEl(cell);
11705 var oRecord = this.getRecord(elCell);
11706 var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11708 if(oRecord && sColumnKey) {
11710 var tracker = this._aSelections || [];
11711 var sRecordId = oRecord.getId();
11714 for(var j=tracker.length-1; j>-1; j--) {
11715 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11716 tracker.splice(j,1);
11722 tracker.push({recordId:sRecordId, columnKey:sColumnKey});
11725 this._aSelections = tracker;
11726 if(!this._oAnchorCell) {
11727 this._oAnchorCell = {record:oRecord, column:this.getColumn(sColumnKey)};
11731 Dom.addClass(elCell, DT.CLASS_SELECTED);
11733 this.fireEvent("cellSelectEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key: this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11734 YAHOO.log("Selected " + elCell, "info", this.toString());
11738 YAHOO.log("Could not select cell " + cell, "warn", this.toString());
11742 * Sets given cell to the unselected state.
11744 * @method unselectCell
11745 * @param cell {HTMLElement | String} DOM element reference or ID string
11746 * to DataTable page element or RecordSet index.
11748 unselectCell : function(cell) {
11749 var elCell = this.getTdEl(cell);
11752 var oRecord = this.getRecord(elCell);
11753 var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11755 if(oRecord && sColumnKey) {
11757 var tracker = this._aSelections || [];
11758 var id = oRecord.getId();
11761 for(var j=tracker.length-1; j>-1; j--) {
11762 if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){
11763 // Remove from tracker
11764 tracker.splice(j,1);
11767 this._aSelections = tracker;
11770 Dom.removeClass(elCell, DT.CLASS_SELECTED);
11772 this.fireEvent("cellUnselectEvent", {record:oRecord, column: this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
11773 YAHOO.log("Unselected " + elCell, "info", this.toString());
11779 YAHOO.log("Could not unselect cell " + cell, "warn", this.toString());
11783 * Clears out all cell selections.
11785 * @method unselectAllCells
11787 unselectAllCells : function() {
11788 // Remove all cells from tracker
11789 var tracker = this._aSelections || [];
11790 for(var j=tracker.length-1; j>-1; j--) {
11791 if(lang.isObject(tracker[j])){
11792 tracker.splice(j,1);
11797 this._aSelections = tracker;
11800 this._unselectAllTdEls();
11802 //TODO: send data to unselectAllCellsEvent handler
11803 this.fireEvent("unselectAllCellsEvent");
11804 YAHOO.log("Unselected all cells", "info", this.toString());
11808 * Returns true if given item is selected, false otherwise.
11810 * @method isSelected
11811 * @param o {String | HTMLElement | YAHOO.widget.Record | Number
11812 * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by
11813 * reference or ID string, a Record instance, a RecordSet position index,
11814 * or an object literal representation
11816 * @return {Boolean} True if item is selected.
11818 isSelected : function(o) {
11819 if(o && (o.ownerDocument == document)) {
11820 return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED));
11823 var oRecord, sRecordId, j;
11824 var tracker = this._aSelections;
11825 if(tracker && tracker.length > 0) {
11826 // Looking for a Record?
11827 if(o instanceof YAHOO.widget.Record) {
11830 else if(lang.isNumber(o)) {
11831 oRecord = this.getRecord(o);
11834 sRecordId = oRecord.getId();
11837 // Use Array.indexOf if available...
11838 if(tracker.indexOf) {
11839 if(tracker.indexOf(sRecordId) > -1) {
11843 // ...or do it the old-fashioned way
11845 for(j=tracker.length-1; j>-1; j--) {
11846 if(tracker[j] === sRecordId){
11852 // Looking for a cell
11853 else if(o.record && o.column){
11854 sRecordId = o.record.getId();
11855 var sColumnKey = o.column.getKey();
11857 for(j=tracker.length-1; j>-1; j--) {
11858 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
11869 * Returns selected rows as an array of Record IDs.
11871 * @method getSelectedRows
11872 * @return {String[]} Array of selected rows by Record ID.
11874 getSelectedRows : function() {
11875 var aSelectedRows = [];
11876 var tracker = this._aSelections || [];
11877 for(var j=0; j<tracker.length; j++) {
11878 if(lang.isString(tracker[j])){
11879 aSelectedRows.push(tracker[j]);
11882 return aSelectedRows;
11886 * Returns selected cells as an array of object literals:
11887 * {recordId:sRecordId, columnKey:sColumnKey}.
11889 * @method getSelectedCells
11890 * @return {Object[]} Array of selected cells by Record ID and Column ID.
11892 getSelectedCells : function() {
11893 var aSelectedCells = [];
11894 var tracker = this._aSelections || [];
11895 for(var j=0; j<tracker.length; j++) {
11896 if(tracker[j] && lang.isObject(tracker[j])){
11897 aSelectedCells.push(tracker[j]);
11900 return aSelectedCells;
11904 * Returns last selected Record ID.
11906 * @method getLastSelectedRecord
11907 * @return {String} Record ID of last selected row.
11909 getLastSelectedRecord : function() {
11910 var tracker = this._aSelections;
11911 if(tracker && tracker.length > 0) {
11912 for(var i=tracker.length-1; i>-1; i--) {
11913 if(lang.isString(tracker[i])){
11921 * Returns last selected cell as an object literal:
11922 * {recordId:sRecordId, columnKey:sColumnKey}.
11924 * @method getLastSelectedCell
11925 * @return {Object} Object literal representation of a cell.
11927 getLastSelectedCell : function() {
11928 var tracker = this._aSelections;
11929 if(tracker && tracker.length > 0) {
11930 for(var i=tracker.length-1; i>-1; i--) {
11931 if(tracker[i].recordId && tracker[i].columnKey){
11939 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
11941 * @method highlightRow
11942 * @param row {HTMLElement | String} DOM element reference or ID string.
11944 highlightRow : function(row) {
11945 var elRow = this.getTrEl(row);
11948 // Make sure previous row is unhighlighted
11949 /* if(this._sLastHighlightedTrElId) {
11950 Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED);
11952 var oRecord = this.getRecord(elRow);
11953 Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED);
11954 //this._sLastHighlightedTrElId = elRow.id;
11955 this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
11956 YAHOO.log("Highlighted " + elRow, "info", this.toString());
11959 YAHOO.log("Could not highlight row " + row, "warn", this.toString());
11963 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
11965 * @method unhighlightRow
11966 * @param row {HTMLElement | String} DOM element reference or ID string.
11968 unhighlightRow : function(row) {
11969 var elRow = this.getTrEl(row);
11972 var oRecord = this.getRecord(elRow);
11973 Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED);
11974 this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
11975 YAHOO.log("Unhighlighted " + elRow, "info", this.toString());
11978 YAHOO.log("Could not unhighlight row " + row, "warn", this.toString());
11982 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
11984 * @method highlightCell
11985 * @param cell {HTMLElement | String} DOM element reference or ID string.
11987 highlightCell : function(cell) {
11988 var elCell = this.getTdEl(cell);
11991 // Make sure previous cell is unhighlighted
11992 if(this._elLastHighlightedTd) {
11993 this.unhighlightCell(this._elLastHighlightedTd);
11996 var oRecord = this.getRecord(elCell);
11997 var sColumnKey = this.getColumn(elCell.cellIndex).getKey();
11998 Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED);
11999 this._elLastHighlightedTd = elCell;
12000 this.fireEvent("cellHighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
12001 YAHOO.log("Highlighted " + elCell, "info", this.toString());
12004 YAHOO.log("Could not highlight cell " + cell, "warn", this.toString());
12008 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
12010 * @method unhighlightCell
12011 * @param cell {HTMLElement | String} DOM element reference or ID string.
12013 unhighlightCell : function(cell) {
12014 var elCell = this.getTdEl(cell);
12017 var oRecord = this.getRecord(elCell);
12018 Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED);
12019 this._elLastHighlightedTd = null;
12020 this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(elCell.cellIndex), key:this.getColumn(elCell.cellIndex).getKey(), el:elCell});
12021 YAHOO.log("Unhighlighted " + elCell, "info", this.toString());
12024 YAHOO.log("Could not unhighlight cell " + cell, "warn", this.toString());
12074 * Returns current CellEditor instance, or null.
12075 * @method getCellEditor
12076 * @return {YAHOO.widget.CellEditor} CellEditor instance.
12078 getCellEditor : function() {
12079 return this._oCellEditor;
12084 * Activates and shows CellEditor instance for the given cell while deactivating and
12085 * canceling previous CellEditor. It is baked into DataTable that only one CellEditor
12086 * can be active at any given time.
12088 * @method showCellEditor
12089 * @param elCell {HTMLElement | String} Cell to edit.
12091 showCellEditor : function(elCell, oRecord, oColumn) {
12092 // Get a particular CellEditor
12093 elCell = this.getTdEl(elCell);
12095 oColumn = this.getColumn(elCell);
12096 if(oColumn && oColumn.editor) {
12097 var oCellEditor = this._oCellEditor;
12098 // Clean up active CellEditor
12100 if(this._oCellEditor.cancel) {
12101 this._oCellEditor.cancel();
12103 else if(oCellEditor.isActive) {
12104 this.cancelCellEditor();
12108 if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) {
12110 oCellEditor = oColumn.editor;
12111 var ok = oCellEditor.attach(this, elCell);
12113 oCellEditor.move();
12114 ok = this.doBeforeShowCellEditor(oCellEditor);
12116 oCellEditor.show();
12117 this._oCellEditor = oCellEditor;
12121 // Backward compatibility
12123 if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
12124 oRecord = this.getRecord(elCell);
12126 if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
12127 oColumn = this.getColumn(elCell);
12129 if(oRecord && oColumn) {
12130 if(!this._oCellEditor || this._oCellEditor.container) {
12131 this._initCellEditorEl();
12134 // Update Editor values
12135 oCellEditor = this._oCellEditor;
12136 oCellEditor.cell = elCell;
12137 oCellEditor.record = oRecord;
12138 oCellEditor.column = oColumn;
12139 oCellEditor.validator = (oColumn.editorOptions &&
12140 lang.isFunction(oColumn.editorOptions.validator)) ?
12141 oColumn.editorOptions.validator : null;
12142 oCellEditor.value = oRecord.getData(oColumn.key);
12143 oCellEditor.defaultValue = null;
12146 var elContainer = oCellEditor.container;
12147 var x = Dom.getX(elCell);
12148 var y = Dom.getY(elCell);
12150 // SF doesn't get xy for cells in scrolling table
12151 // when tbody display is set to block
12152 if(isNaN(x) || isNaN(y)) {
12153 x = elCell.offsetLeft + // cell pos relative to table
12154 Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document
12155 this._elTbody.scrollLeft; // minus tbody scroll
12156 y = elCell.offsetTop + // cell pos relative to table
12157 Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document
12158 this._elTbody.scrollTop + // minus tbody scroll
12159 this._elThead.offsetHeight; // account for fixed THEAD cells
12162 elContainer.style.left = x + "px";
12163 elContainer.style.top = y + "px";
12165 // Hook to customize the UI
12166 this.doBeforeShowCellEditor(this._oCellEditor);
12168 //TODO: This is temporarily up here due so elements can be focused
12170 elContainer.style.display = "";
12173 Ev.addListener(elContainer, "keydown", function(e, oSelf) {
12174 // ESC hides Cell Editor
12175 if((e.keyCode == 27)) {
12176 oSelf.cancelCellEditor();
12177 oSelf.focusTbodyEl();
12180 oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
12184 // Render Editor markup
12186 if(lang.isString(oColumn.editor)) {
12187 switch(oColumn.editor) {
12189 fnEditor = DT.editCheckbox;
12192 fnEditor = DT.editDate;
12195 fnEditor = DT.editDropdown;
12198 fnEditor = DT.editRadio;
12201 fnEditor = DT.editTextarea;
12204 fnEditor = DT.editTextbox;
12210 else if(lang.isFunction(oColumn.editor)) {
12211 fnEditor = oColumn.editor;
12215 // Create DOM input elements
12216 fnEditor(this._oCellEditor, this);
12218 // Show Save/Cancel buttons
12219 if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
12220 this.showCellEditorBtns(elContainer);
12223 oCellEditor.isActive = true;
12225 //TODO: verify which args to pass
12226 this.fireEvent("editorShowEvent", {editor:oCellEditor});
12227 YAHOO.log("Cell Editor shown for " + elCell, "info", this.toString());
12241 * Backward compatibility.
12243 * @method _initCellEditorEl
12247 _initCellEditorEl : function() {
12248 // Attach Cell Editor container element as first child of body
12249 var elCellEditor = document.createElement("div");
12250 elCellEditor.id = this._sId + "-celleditor";
12251 elCellEditor.style.display = "none";
12252 elCellEditor.tabIndex = 0;
12253 Dom.addClass(elCellEditor, DT.CLASS_EDITOR);
12254 var elFirstChild = Dom.getFirstChild(document.body);
12256 elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild);
12259 elCellEditor = document.body.appendChild(elCellEditor);
12262 // Internal tracker of Cell Editor values
12263 var oCellEditor = {};
12264 oCellEditor.container = elCellEditor;
12265 oCellEditor.value = null;
12266 oCellEditor.isActive = false;
12267 this._oCellEditor = oCellEditor;
12271 * Overridable abstract method to customize CellEditor before showing.
12273 * @method doBeforeShowCellEditor
12274 * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance.
12275 * @return {Boolean} Return true to continue showing CellEditor.
12277 doBeforeShowCellEditor : function(oCellEditor) {
12282 * Saves active CellEditor input to Record and upates DOM UI.
12284 * @method saveCellEditor
12286 saveCellEditor : function() {
12287 if(this._oCellEditor) {
12288 if(this._oCellEditor.save) {
12289 this._oCellEditor.save();
12291 // Backward compatibility
12292 else if(this._oCellEditor.isActive) {
12293 var newData = this._oCellEditor.value;
12294 // Copy the data to pass to the event
12295 //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key));
12296 var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
12298 // Validate input data
12299 if(this._oCellEditor.validator) {
12300 newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor);
12301 if(newData === null ) {
12302 this.resetCellEditor();
12303 this.fireEvent("editorRevertEvent",
12304 {editor:this._oCellEditor, oldData:oldData, newData:newData});
12305 YAHOO.log("Could not save Cell Editor input due to invalid data " +
12306 lang.dump(newData), "warn", this.toString());
12310 // Update the Record
12311 this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
12313 this.formatCell(this._oCellEditor.cell.firstChild);
12316 this._oChainRender.add({
12317 method: function() {
12318 this.validateColumnWidths();
12322 this._oChainRender.run();
12323 // Clear out the Cell Editor
12324 this.resetCellEditor();
12326 this.fireEvent("editorSaveEvent",
12327 {editor:this._oCellEditor, oldData:oldData, newData:newData});
12328 YAHOO.log("Cell Editor input saved", "info", this.toString());
12334 * Cancels active CellEditor.
12336 * @method cancelCellEditor
12338 cancelCellEditor : function() {
12339 if(this._oCellEditor) {
12340 if(this._oCellEditor.cancel) {
12341 this._oCellEditor.cancel();
12343 // Backward compatibility
12344 else if(this._oCellEditor.isActive) {
12345 this.resetCellEditor();
12346 //TODO: preserve values for the event?
12347 this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
12348 YAHOO.log("Cell Editor input canceled", "info", this.toString());
12351 YAHOO.log("CellEditor input canceled", "info", this.toString());
12356 * Destroys active CellEditor instance and UI.
12358 * @method destroyCellEditor
12360 destroyCellEditor : function() {
12361 if(this._oCellEditor) {
12362 this._oCellEditor.destroy();
12363 this._oCellEditor = null;
12368 * Passes through showEvent of the active CellEditor.
12370 * @method _onEditorShowEvent
12371 * @param oArgs {Object} Custom Event args.
12374 _onEditorShowEvent : function(oArgs) {
12375 this.fireEvent("editorShowEvent", oArgs);
12379 * Passes through keydownEvent of the active CellEditor.
12380 * @param oArgs {Object} Custom Event args.
12382 * @method _onEditorKeydownEvent
12385 _onEditorKeydownEvent : function(oArgs) {
12386 this.fireEvent("editorKeydownEvent", oArgs);
12390 * Passes through revertEvent of the active CellEditor.
12392 * @method _onEditorRevertEvent
12393 * @param oArgs {Object} Custom Event args.
12396 _onEditorRevertEvent : function(oArgs) {
12397 this.fireEvent("editorRevertEvent", oArgs);
12401 * Passes through saveEvent of the active CellEditor.
12403 * @method _onEditorSaveEvent
12404 * @param oArgs {Object} Custom Event args.
12407 _onEditorSaveEvent : function(oArgs) {
12408 this.fireEvent("editorSaveEvent", oArgs);
12412 * Passes through cancelEvent of the active CellEditor.
12414 * @method _onEditorCancelEvent
12415 * @param oArgs {Object} Custom Event args.
12418 _onEditorCancelEvent : function(oArgs) {
12419 this.fireEvent("editorCancelEvent", oArgs);
12423 * Passes through blurEvent of the active CellEditor.
12425 * @method _onEditorBlurEvent
12426 * @param oArgs {Object} Custom Event args.
12429 _onEditorBlurEvent : function(oArgs) {
12430 this.fireEvent("editorBlurEvent", oArgs);
12434 * Passes through blockEvent of the active CellEditor.
12436 * @method _onEditorBlockEvent
12437 * @param oArgs {Object} Custom Event args.
12440 _onEditorBlockEvent : function(oArgs) {
12441 this.fireEvent("editorBlockEvent", oArgs);
12445 * Passes through unblockEvent of the active CellEditor.
12447 * @method _onEditorUnblockEvent
12448 * @param oArgs {Object} Custom Event args.
12451 _onEditorUnblockEvent : function(oArgs) {
12452 this.fireEvent("editorUnblockEvent", oArgs);
12456 * Public handler of the editorBlurEvent. By default, saves on blur if
12457 * disableBtns is true, otherwise cancels on blur.
12459 * @method onEditorBlurEvent
12460 * @param oArgs {Object} Custom Event args.
12462 onEditorBlurEvent : function(oArgs) {
12463 if(oArgs.editor.disableBtns) {
12465 if(oArgs.editor.save) { // Backward incompatible
12466 oArgs.editor.save();
12469 else if(oArgs.editor.cancel) { // Backward incompatible
12471 oArgs.editor.cancel();
12476 * Public handler of the editorBlockEvent. By default, disables DataTable UI.
12478 * @method onEditorBlockEvent
12479 * @param oArgs {Object} Custom Event args.
12481 onEditorBlockEvent : function(oArgs) {
12486 * Public handler of the editorUnblockEvent. By default, undisables DataTable UI.
12488 * @method onEditorUnblockEvent
12489 * @param oArgs {Object} Custom Event args.
12491 onEditorUnblockEvent : function(oArgs) {
12532 // ABSTRACT METHODS
12535 * Overridable method gives implementers a hook to access data before
12536 * it gets added to RecordSet and rendered to the TBODY.
12538 * @method doBeforeLoadData
12539 * @param sRequest {String} Original request.
12540 * @param oResponse {Object} Response object.
12541 * @param oPayload {MIXED} additional arguments
12542 * @return {Boolean} Return true to continue loading data into RecordSet and
12543 * updating DataTable with new Records, false to cancel.
12545 doBeforeLoadData : function(sRequest, oResponse, oPayload) {
12611 /////////////////////////////////////////////////////////////////////////////
12613 // Public Custom Event Handlers
12615 /////////////////////////////////////////////////////////////////////////////
12618 * Overridable custom event handler to sort Column.
12620 * @method onEventSortColumn
12621 * @param oArgs.event {HTMLEvent} Event object.
12622 * @param oArgs.target {HTMLElement} Target element.
12624 onEventSortColumn : function(oArgs) {
12625 //TODO: support form elements in sortable columns
12626 var evt = oArgs.event;
12627 var target = oArgs.target;
12629 var el = this.getThEl(target) || this.getTdEl(target);
12631 var oColumn = this.getColumn(el);
12632 if(oColumn.sortable) {
12634 this.sortColumn(oColumn);
12638 YAHOO.log("Could not find Column for " + target, "warn", this.toString());
12643 * Overridable custom event handler to select Column.
12645 * @method onEventSelectColumn
12646 * @param oArgs.event {HTMLEvent} Event object.
12647 * @param oArgs.target {HTMLElement} Target element.
12649 onEventSelectColumn : function(oArgs) {
12650 this.selectColumn(oArgs.target);
12654 * Overridable custom event handler to highlight Column. Accounts for spurious
12655 * caused-by-child events.
12657 * @method onEventHighlightColumn
12658 * @param oArgs.event {HTMLEvent} Event object.
12659 * @param oArgs.target {HTMLElement} Target element.
12661 onEventHighlightColumn : function(oArgs) {
12662 //TODO: filter for all spurious events at a lower level
12663 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12664 this.highlightColumn(oArgs.target);
12669 * Overridable custom event handler to unhighlight Column. Accounts for spurious
12670 * caused-by-child events.
12672 * @method onEventUnhighlightColumn
12673 * @param oArgs.event {HTMLEvent} Event object.
12674 * @param oArgs.target {HTMLElement} Target element.
12676 onEventUnhighlightColumn : function(oArgs) {
12677 //TODO: filter for all spurious events at a lower level
12678 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12679 this.unhighlightColumn(oArgs.target);
12684 * Overridable custom event handler to manage selection according to desktop paradigm.
12686 * @method onEventSelectRow
12687 * @param oArgs.event {HTMLEvent} Event object.
12688 * @param oArgs.target {HTMLElement} Target element.
12690 onEventSelectRow : function(oArgs) {
12691 var sMode = this.get("selectionMode");
12692 if(sMode == "single") {
12693 this._handleSingleSelectionByMouse(oArgs);
12696 this._handleStandardSelectionByMouse(oArgs);
12701 * Overridable custom event handler to select cell.
12703 * @method onEventSelectCell
12704 * @param oArgs.event {HTMLEvent} Event object.
12705 * @param oArgs.target {HTMLElement} Target element.
12707 onEventSelectCell : function(oArgs) {
12708 var sMode = this.get("selectionMode");
12709 if(sMode == "cellblock") {
12710 this._handleCellBlockSelectionByMouse(oArgs);
12712 else if(sMode == "cellrange") {
12713 this._handleCellRangeSelectionByMouse(oArgs);
12716 this._handleSingleCellSelectionByMouse(oArgs);
12721 * Overridable custom event handler to highlight row. Accounts for spurious
12722 * caused-by-child events.
12724 * @method onEventHighlightRow
12725 * @param oArgs.event {HTMLEvent} Event object.
12726 * @param oArgs.target {HTMLElement} Target element.
12728 onEventHighlightRow : function(oArgs) {
12729 //TODO: filter for all spurious events at a lower level
12730 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12731 this.highlightRow(oArgs.target);
12736 * Overridable custom event handler to unhighlight row. Accounts for spurious
12737 * caused-by-child events.
12739 * @method onEventUnhighlightRow
12740 * @param oArgs.event {HTMLEvent} Event object.
12741 * @param oArgs.target {HTMLElement} Target element.
12743 onEventUnhighlightRow : function(oArgs) {
12744 //TODO: filter for all spurious events at a lower level
12745 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12746 this.unhighlightRow(oArgs.target);
12751 * Overridable custom event handler to highlight cell. Accounts for spurious
12752 * caused-by-child events.
12754 * @method onEventHighlightCell
12755 * @param oArgs.event {HTMLEvent} Event object.
12756 * @param oArgs.target {HTMLElement} Target element.
12758 onEventHighlightCell : function(oArgs) {
12759 //TODO: filter for all spurious events at a lower level
12760 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12761 this.highlightCell(oArgs.target);
12766 * Overridable custom event handler to unhighlight cell. Accounts for spurious
12767 * caused-by-child events.
12769 * @method onEventUnhighlightCell
12770 * @param oArgs.event {HTMLEvent} Event object.
12771 * @param oArgs.target {HTMLElement} Target element.
12773 onEventUnhighlightCell : function(oArgs) {
12774 //TODO: filter for all spurious events at a lower level
12775 if(!Dom.isAncestor(oArgs.target,Ev.getRelatedTarget(oArgs.event))) {
12776 this.unhighlightCell(oArgs.target);
12781 * Overridable custom event handler to format cell.
12783 * @method onEventFormatCell
12784 * @param oArgs.event {HTMLEvent} Event object.
12785 * @param oArgs.target {HTMLElement} Target element.
12787 onEventFormatCell : function(oArgs) {
12788 var target = oArgs.target;
12790 var elCell = this.getTdEl(target);
12792 var oColumn = this.getColumn(elCell.cellIndex);
12793 this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn);
12796 YAHOO.log("Could not format cell " + target, "warn", this.toString());
12801 * Overridable custom event handler to edit cell.
12803 * @method onEventShowCellEditor
12804 * @param oArgs.event {HTMLEvent} Event object.
12805 * @param oArgs.target {HTMLElement} Target element.
12807 onEventShowCellEditor : function(oArgs) {
12808 this.showCellEditor(oArgs.target);
12812 * Overridable custom event handler to save active CellEditor input.
12814 * @method onEventSaveCellEditor
12816 onEventSaveCellEditor : function(oArgs) {
12817 if(this._oCellEditor) {
12818 if(this._oCellEditor.save) {
12819 this._oCellEditor.save();
12821 // Backward compatibility
12823 this.saveCellEditor();
12829 * Overridable custom event handler to cancel active CellEditor.
12831 * @method onEventCancelCellEditor
12833 onEventCancelCellEditor : function(oArgs) {
12834 if(this._oCellEditor) {
12835 if(this._oCellEditor.cancel) {
12836 this._oCellEditor.cancel();
12838 // Backward compatibility
12840 this.cancelCellEditor();
12846 * Callback function receives data from DataSource and populates an entire
12847 * DataTable with Records and TR elements, clearing previous Records, if any.
12849 * @method onDataReturnInitializeTable
12850 * @param sRequest {String} Original request.
12851 * @param oResponse {Object} Response object.
12852 * @param oPayload {MIXED} (optional) Additional argument(s)
12854 onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) {
12855 if((this instanceof DT) && this._sId) {
12856 this.initializeTable();
12858 this.onDataReturnSetRows(sRequest,oResponse,oPayload);
12863 * Callback function receives reponse from DataSource, replaces all existing
12864 * Records in RecordSet, updates TR elements with new data, and updates state
12865 * UI for pagination and sorting from payload data, if necessary.
12867 * @method onDataReturnReplaceRows
12868 * @param oRequest {MIXED} Original generated request.
12869 * @param oResponse {Object} Response object.
12870 * @param oPayload {MIXED} (optional) Additional argument(s)
12872 onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) {
12873 if((this instanceof DT) && this._sId) {
12874 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
12876 // Pass data through abstract method for any transformations
12877 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload),
12878 pag = this.get('paginator'),
12882 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12884 this._oRecordSet.reset();
12886 if (this.get('dynamicData')) {
12887 if (oPayload && oPayload.pagination &&
12888 lang.isNumber(oPayload.pagination.recordOffset)) {
12889 index = oPayload.pagination.recordOffset;
12891 index = pag.getStartIndex();
12895 this._oRecordSet.setRecords(oResponse.results, index | 0);
12898 this._handleDataReturnPayload(oRequest, oResponse, oPayload);
12904 else if(ok && oResponse.error) {
12905 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12911 * Callback function receives data from DataSource and appends to an existing
12912 * DataTable new Records and, if applicable, creates or updates
12913 * corresponding TR elements.
12915 * @method onDataReturnAppendRows
12916 * @param sRequest {String} Original request.
12917 * @param oResponse {Object} Response object.
12918 * @param oPayload {MIXED} (optional) Additional argument(s)
12920 onDataReturnAppendRows : function(sRequest, oResponse, oPayload) {
12921 if((this instanceof DT) && this._sId) {
12922 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12924 // Pass data through abstract method for any transformations
12925 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12927 // Data ok to append
12928 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12930 this.addRows(oResponse.results);
12933 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12936 else if(ok && oResponse.error) {
12937 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12943 * Callback function receives data from DataSource and inserts new records
12944 * starting at the index specified in oPayload.insertIndex. The value for
12945 * oPayload.insertIndex can be populated when sending the request to the DataSource,
12946 * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime.
12947 * If applicable, creates or updates corresponding TR elements.
12949 * @method onDataReturnInsertRows
12950 * @param sRequest {String} Original request.
12951 * @param oResponse {Object} Response object.
12952 * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex.
12954 onDataReturnInsertRows : function(sRequest, oResponse, oPayload) {
12955 if((this instanceof DT) && this._sId) {
12956 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12958 // Pass data through abstract method for any transformations
12959 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12961 // Data ok to append
12962 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12964 this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0));
12967 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
12970 else if(ok && oResponse.error) {
12971 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
12977 * Callback function receives data from DataSource and incrementally updates Records
12978 * starting at the index specified in oPayload.updateIndex. The value for
12979 * oPayload.updateIndex can be populated when sending the request to the DataSource,
12980 * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime.
12981 * If applicable, creates or updates corresponding TR elements.
12983 * @method onDataReturnUpdateRows
12984 * @param sRequest {String} Original request.
12985 * @param oResponse {Object} Response object.
12986 * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex.
12988 onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) {
12989 if((this instanceof DT) && this._sId) {
12990 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
12992 // Pass data through abstract method for any transformations
12993 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
12995 // Data ok to append
12996 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
12998 this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results);
13001 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
13004 else if(ok && oResponse.error) {
13005 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
13011 * Callback function receives reponse from DataSource and populates the
13012 * RecordSet with the results.
13014 * @method onDataReturnSetRows
13015 * @param oRequest {MIXED} Original generated request.
13016 * @param oResponse {Object} Response object.
13017 * @param oPayload {MIXED} (optional) Additional argument(s)
13019 onDataReturnSetRows : function(oRequest, oResponse, oPayload) {
13020 if((this instanceof DT) && this._sId) {
13021 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
13023 // Pass data through abstract method for any transformations
13024 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload),
13025 pag = this.get('paginator'),
13029 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
13031 if (this.get('dynamicData')) {
13032 if (oPayload && oPayload.pagination &&
13033 lang.isNumber(oPayload.pagination.recordOffset)) {
13034 index = oPayload.pagination.recordOffset;
13036 index = pag.getStartIndex();
13039 this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default
13042 this._oRecordSet.setRecords(oResponse.results, index | 0);
13045 this._handleDataReturnPayload(oRequest, oResponse, oPayload);
13051 else if(ok && oResponse.error) {
13052 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
13056 YAHOO.log("Instance destroyed before data returned.","info",this.toString());
13061 * Hook to update oPayload before consumption.
13063 * @method handleDataReturnPayload
13064 * @param oRequest {MIXED} Original generated request.
13065 * @param oResponse {Object} Response object.
13066 * @param oPayload {MIXED} State values.
13067 * @return oPayload {MIXED} State values.
13069 handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
13074 * Updates the DataTable with state data sent in an onDataReturn* payload.
13076 * @method handleDataReturnPayload
13077 * @param oRequest {MIXED} Original generated request.
13078 * @param oResponse {Object} Response object.
13079 * @param oPayload {MIXED} State values
13081 _handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
13082 oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload);
13084 // Update pagination
13085 var oPaginator = this.get('paginator');
13087 // Update totalRecords
13088 if(this.get("dynamicData")) {
13089 if (widget.Paginator.isNumeric(oPayload.totalRecords)) {
13090 oPaginator.set('totalRecords',oPayload.totalRecords);
13094 oPaginator.set('totalRecords',this._oRecordSet.getLength());
13096 // Update other paginator values
13097 if (lang.isObject(oPayload.pagination)) {
13098 oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage);
13099 oPaginator.set('recordOffset',oPayload.pagination.recordOffset);
13104 if (oPayload.sortedBy) {
13105 // Set the sorting values in preparation for refresh
13106 this.set('sortedBy', oPayload.sortedBy);
13108 // Backwards compatibility for sorting
13109 else if (oPayload.sorting) {
13110 // Set the sorting values in preparation for refresh
13111 this.set('sortedBy', oPayload.sorting);
13148 /////////////////////////////////////////////////////////////////////////////
13152 /////////////////////////////////////////////////////////////////////////////
13155 * Fired when the DataTable's rows are rendered from an initialized state.
13161 * Fired when the DataTable's DOM is rendered or modified.
13163 * @event renderEvent
13167 * Fired when the DataTable's post-render routine is complete, including
13168 * Column width validations.
13170 * @event postRenderEvent
13174 * Fired when the DataTable is disabled.
13176 * @event disableEvent
13180 * Fired when the DataTable is undisabled.
13182 * @event undisableEvent
13186 * Fired when data is returned from DataSource but before it is consumed by
13189 * @event dataReturnEvent
13190 * @param oArgs.request {String} Original request.
13191 * @param oArgs.response {Object} Response object.
13195 * Fired when the DataTable has a focus event.
13197 * @event tableFocusEvent
13201 * Fired when the DataTable THEAD element has a focus event.
13203 * @event theadFocusEvent
13207 * Fired when the DataTable TBODY element has a focus event.
13209 * @event tbodyFocusEvent
13213 * Fired when the DataTable has a blur event.
13215 * @event tableBlurEvent
13218 /*TODO implement theadBlurEvent
13219 * Fired when the DataTable THEAD element has a blur event.
13221 * @event theadBlurEvent
13224 /*TODO: implement tbodyBlurEvent
13225 * Fired when the DataTable TBODY element has a blur event.
13227 * @event tbodyBlurEvent
13231 * Fired when the DataTable has a key event.
13233 * @event tableKeyEvent
13234 * @param oArgs.event {HTMLEvent} The event object.
13235 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13239 * Fired when the DataTable THEAD element has a key event.
13241 * @event theadKeyEvent
13242 * @param oArgs.event {HTMLEvent} The event object.
13243 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13247 * Fired when the DataTable TBODY element has a key event.
13249 * @event tbodyKeyEvent
13250 * @param oArgs.event {HTMLEvent} The event object.
13251 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13255 * Fired when the DataTable has a mouseover.
13257 * @event tableMouseoverEvent
13258 * @param oArgs.event {HTMLEvent} The event object.
13259 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13264 * Fired when the DataTable has a mouseout.
13266 * @event tableMouseoutEvent
13267 * @param oArgs.event {HTMLEvent} The event object.
13268 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13273 * Fired when the DataTable has a mousedown.
13275 * @event tableMousedownEvent
13276 * @param oArgs.event {HTMLEvent} The event object.
13277 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13282 * Fired when the DataTable has a mouseup.
13284 * @event tableMouseupEvent
13285 * @param oArgs.event {HTMLEvent} The event object.
13286 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13291 * Fired when the DataTable has a click.
13293 * @event tableClickEvent
13294 * @param oArgs.event {HTMLEvent} The event object.
13295 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13300 * Fired when the DataTable has a dblclick.
13302 * @event tableDblclickEvent
13303 * @param oArgs.event {HTMLEvent} The event object.
13304 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
13309 * Fired when a message is shown in the DataTable's message element.
13311 * @event tableMsgShowEvent
13312 * @param oArgs.html {String} The HTML displayed.
13313 * @param oArgs.className {String} The className assigned.
13318 * Fired when the DataTable's message element is hidden.
13320 * @event tableMsgHideEvent
13324 * Fired when a THEAD row has a mouseover.
13326 * @event theadRowMouseoverEvent
13327 * @param oArgs.event {HTMLEvent} The event object.
13328 * @param oArgs.target {HTMLElement} The TR element.
13332 * Fired when a THEAD row has a mouseout.
13334 * @event theadRowMouseoutEvent
13335 * @param oArgs.event {HTMLEvent} The event object.
13336 * @param oArgs.target {HTMLElement} The TR element.
13340 * Fired when a THEAD row has a mousedown.
13342 * @event theadRowMousedownEvent
13343 * @param oArgs.event {HTMLEvent} The event object.
13344 * @param oArgs.target {HTMLElement} The TR element.
13348 * Fired when a THEAD row has a mouseup.
13350 * @event theadRowMouseupEvent
13351 * @param oArgs.event {HTMLEvent} The event object.
13352 * @param oArgs.target {HTMLElement} The TR element.
13356 * Fired when a THEAD row has a click.
13358 * @event theadRowClickEvent
13359 * @param oArgs.event {HTMLEvent} The event object.
13360 * @param oArgs.target {HTMLElement} The TR element.
13364 * Fired when a THEAD row has a dblclick.
13366 * @event theadRowDblclickEvent
13367 * @param oArgs.event {HTMLEvent} The event object.
13368 * @param oArgs.target {HTMLElement} The TR element.
13372 * Fired when a THEAD cell has a mouseover.
13374 * @event theadCellMouseoverEvent
13375 * @param oArgs.event {HTMLEvent} The event object.
13376 * @param oArgs.target {HTMLElement} The TH element.
13381 * Fired when a THEAD cell has a mouseout.
13383 * @event theadCellMouseoutEvent
13384 * @param oArgs.event {HTMLEvent} The event object.
13385 * @param oArgs.target {HTMLElement} The TH element.
13390 * Fired when a THEAD cell has a mousedown.
13392 * @event theadCellMousedownEvent
13393 * @param oArgs.event {HTMLEvent} The event object.
13394 * @param oArgs.target {HTMLElement} The TH element.
13398 * Fired when a THEAD cell has a mouseup.
13400 * @event theadCellMouseupEvent
13401 * @param oArgs.event {HTMLEvent} The event object.
13402 * @param oArgs.target {HTMLElement} The TH element.
13406 * Fired when a THEAD cell has a click.
13408 * @event theadCellClickEvent
13409 * @param oArgs.event {HTMLEvent} The event object.
13410 * @param oArgs.target {HTMLElement} The TH element.
13414 * Fired when a THEAD cell has a dblclick.
13416 * @event theadCellDblclickEvent
13417 * @param oArgs.event {HTMLEvent} The event object.
13418 * @param oArgs.target {HTMLElement} The TH element.
13422 * Fired when a THEAD label has a mouseover.
13424 * @event theadLabelMouseoverEvent
13425 * @param oArgs.event {HTMLEvent} The event object.
13426 * @param oArgs.target {HTMLElement} The SPAN element.
13431 * Fired when a THEAD label has a mouseout.
13433 * @event theadLabelMouseoutEvent
13434 * @param oArgs.event {HTMLEvent} The event object.
13435 * @param oArgs.target {HTMLElement} The SPAN element.
13440 * Fired when a THEAD label has a mousedown.
13442 * @event theadLabelMousedownEvent
13443 * @param oArgs.event {HTMLEvent} The event object.
13444 * @param oArgs.target {HTMLElement} The SPAN element.
13448 * Fired when a THEAD label has a mouseup.
13450 * @event theadLabelMouseupEvent
13451 * @param oArgs.event {HTMLEvent} The event object.
13452 * @param oArgs.target {HTMLElement} The SPAN element.
13456 * Fired when a THEAD label has a click.
13458 * @event theadLabelClickEvent
13459 * @param oArgs.event {HTMLEvent} The event object.
13460 * @param oArgs.target {HTMLElement} The SPAN element.
13464 * Fired when a THEAD label has a dblclick.
13466 * @event theadLabelDblclickEvent
13467 * @param oArgs.event {HTMLEvent} The event object.
13468 * @param oArgs.target {HTMLElement} The SPAN element.
13472 * Fired when a column is sorted.
13474 * @event columnSortEvent
13475 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13476 * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC
13477 * or YAHOO.widget.DataTable.CLASS_DESC.
13481 * Fired when a column width is set.
13483 * @event columnSetWidthEvent
13484 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13485 * @param oArgs.width {Number} The width in pixels.
13489 * Fired when a column width is unset.
13491 * @event columnUnsetWidthEvent
13492 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13496 * Fired when a column is drag-resized.
13498 * @event columnResizeEvent
13499 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13500 * @param oArgs.target {HTMLElement} The TH element.
13501 * @param oArgs.width {Number} Width in pixels.
13505 * Fired when a Column is moved to a new index.
13507 * @event columnReorderEvent
13508 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13509 * @param oArgs.oldIndex {Number} The previous index position.
13513 * Fired when a column is hidden.
13515 * @event columnHideEvent
13516 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13520 * Fired when a column is shown.
13522 * @event columnShowEvent
13523 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13527 * Fired when a column is selected.
13529 * @event columnSelectEvent
13530 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13534 * Fired when a column is unselected.
13536 * @event columnUnselectEvent
13537 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13540 * Fired when a column is removed.
13542 * @event columnRemoveEvent
13543 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13547 * Fired when a column is inserted.
13549 * @event columnInsertEvent
13550 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
13551 * @param oArgs.index {Number} The index position.
13555 * Fired when a column is highlighted.
13557 * @event columnHighlightEvent
13558 * @param oArgs.column {YAHOO.widget.Column} The highlighted Column.
13562 * Fired when a column is unhighlighted.
13564 * @event columnUnhighlightEvent
13565 * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column.
13570 * Fired when a row has a mouseover.
13572 * @event rowMouseoverEvent
13573 * @param oArgs.event {HTMLEvent} The event object.
13574 * @param oArgs.target {HTMLElement} The TR element.
13578 * Fired when a row has a mouseout.
13580 * @event rowMouseoutEvent
13581 * @param oArgs.event {HTMLEvent} The event object.
13582 * @param oArgs.target {HTMLElement} The TR element.
13586 * Fired when a row has a mousedown.
13588 * @event rowMousedownEvent
13589 * @param oArgs.event {HTMLEvent} The event object.
13590 * @param oArgs.target {HTMLElement} The TR element.
13594 * Fired when a row has a mouseup.
13596 * @event rowMouseupEvent
13597 * @param oArgs.event {HTMLEvent} The event object.
13598 * @param oArgs.target {HTMLElement} The TR element.
13602 * Fired when a row has a click.
13604 * @event rowClickEvent
13605 * @param oArgs.event {HTMLEvent} The event object.
13606 * @param oArgs.target {HTMLElement} The TR element.
13610 * Fired when a row has a dblclick.
13612 * @event rowDblclickEvent
13613 * @param oArgs.event {HTMLEvent} The event object.
13614 * @param oArgs.target {HTMLElement} The TR element.
13618 * Fired when a row is added.
13620 * @event rowAddEvent
13621 * @param oArgs.record {YAHOO.widget.Record} The added Record.
13625 * Fired when rows are added.
13627 * @event rowsAddEvent
13628 * @param oArgs.record {YAHOO.widget.Record[]} The added Records.
13632 * Fired when a row is updated.
13634 * @event rowUpdateEvent
13635 * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13636 * @param oArgs.oldData {Object} Object literal of the old data.
13640 * Fired when a row is deleted.
13642 * @event rowDeleteEvent
13643 * @param oArgs.oldData {Object} Object literal of the deleted data.
13644 * @param oArgs.recordIndex {Number} Index of the deleted Record.
13645 * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page.
13649 * Fired when rows are deleted.
13651 * @event rowsDeleteEvent
13652 * @param oArgs.oldData {Object[]} Array of object literals of the deleted data.
13653 * @param oArgs.recordIndex {Number} Index of the first deleted Record.
13654 * @param oArgs.count {Number} Number of deleted Records.
13658 * Fired when a row is selected.
13660 * @event rowSelectEvent
13661 * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
13662 * @param oArgs.record {YAHOO.widget.Record} The selected Record.
13666 * Fired when a row is unselected.
13668 * @event rowUnselectEvent
13669 * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
13670 * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
13674 * Fired when all row selections are cleared.
13676 * @event unselectAllRowsEvent
13680 * Fired when a row is highlighted.
13682 * @event rowHighlightEvent
13683 * @param oArgs.el {HTMLElement} The highlighted TR element.
13684 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13688 * Fired when a row is unhighlighted.
13690 * @event rowUnhighlightEvent
13691 * @param oArgs.el {HTMLElement} The highlighted TR element.
13692 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
13696 * Fired when a cell is updated.
13698 * @event cellUpdateEvent
13699 * @param oArgs.record {YAHOO.widget.Record} The updated Record.
13700 * @param oArgs.column {YAHOO.widget.Column} The updated Column.
13701 * @param oArgs.oldData {Object} Original data value of the updated cell.
13705 * Fired when a cell has a mouseover.
13707 * @event cellMouseoverEvent
13708 * @param oArgs.event {HTMLEvent} The event object.
13709 * @param oArgs.target {HTMLElement} The TD element.
13713 * Fired when a cell has a mouseout.
13715 * @event cellMouseoutEvent
13716 * @param oArgs.event {HTMLEvent} The event object.
13717 * @param oArgs.target {HTMLElement} The TD element.
13721 * Fired when a cell has a mousedown.
13723 * @event cellMousedownEvent
13724 * @param oArgs.event {HTMLEvent} The event object.
13725 * @param oArgs.target {HTMLElement} The TD element.
13729 * Fired when a cell has a mouseup.
13731 * @event cellMouseupEvent
13732 * @param oArgs.event {HTMLEvent} The event object.
13733 * @param oArgs.target {HTMLElement} The TD element.
13737 * Fired when a cell has a click.
13739 * @event cellClickEvent
13740 * @param oArgs.event {HTMLEvent} The event object.
13741 * @param oArgs.target {HTMLElement} The TD element.
13745 * Fired when a cell has a dblclick.
13747 * @event cellDblclickEvent
13748 * @param oArgs.event {HTMLEvent} The event object.
13749 * @param oArgs.target {HTMLElement} The TD element.
13753 * Fired when a cell is formatted.
13755 * @event cellFormatEvent
13756 * @param oArgs.el {HTMLElement} The formatted TD element.
13757 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13758 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13759 * @param oArgs.key {String} (deprecated) The key of the formatted cell.
13763 * Fired when a cell is selected.
13765 * @event cellSelectEvent
13766 * @param oArgs.el {HTMLElement} The selected TD element.
13767 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13768 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13769 * @param oArgs.key {String} (deprecated) The key of the selected cell.
13773 * Fired when a cell is unselected.
13775 * @event cellUnselectEvent
13776 * @param oArgs.el {HTMLElement} The unselected TD element.
13777 * @param oArgs.record {YAHOO.widget.Record} The associated Record.
13778 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13779 * @param oArgs.key {String} (deprecated) The key of the unselected cell.
13784 * Fired when a cell is highlighted.
13786 * @event cellHighlightEvent
13787 * @param oArgs.el {HTMLElement} The highlighted TD element.
13788 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13789 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13790 * @param oArgs.key {String} (deprecated) The key of the highlighted cell.
13795 * Fired when a cell is unhighlighted.
13797 * @event cellUnhighlightEvent
13798 * @param oArgs.el {HTMLElement} The unhighlighted TD element.
13799 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
13800 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
13801 * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell.
13806 * Fired when all cell selections are cleared.
13808 * @event unselectAllCellsEvent
13812 * Fired when a CellEditor is shown.
13814 * @event editorShowEvent
13815 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13819 * Fired when a CellEditor has a keydown.
13821 * @event editorKeydownEvent
13822 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13823 * @param oArgs.event {HTMLEvent} The event object.
13827 * Fired when a CellEditor input is reverted.
13829 * @event editorRevertEvent
13830 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13831 * @param oArgs.newData {Object} New data value from form input field.
13832 * @param oArgs.oldData {Object} Old data value.
13836 * Fired when a CellEditor input is saved.
13838 * @event editorSaveEvent
13839 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13840 * @param oArgs.newData {Object} New data value from form input field.
13841 * @param oArgs.oldData {Object} Old data value.
13845 * Fired when a CellEditor input is canceled.
13847 * @event editorCancelEvent
13848 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13852 * Fired when a CellEditor has a blur event.
13854 * @event editorBlurEvent
13855 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13859 * Fired when a CellEditor is blocked.
13861 * @event editorBlockEvent
13862 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13866 * Fired when a CellEditor is unblocked.
13868 * @event editorUnblockEvent
13869 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
13877 * Fired when a link is clicked.
13879 * @event linkClickEvent
13880 * @param oArgs.event {HTMLEvent} The event object.
13881 * @param oArgs.target {HTMLElement} The A element.
13885 * Fired when a BUTTON element or INPUT element of type "button", "image",
13886 * "submit", "reset" is clicked.
13888 * @event buttonClickEvent
13889 * @param oArgs.event {HTMLEvent} The event object.
13890 * @param oArgs.target {HTMLElement} The BUTTON element.
13894 * Fired when a CHECKBOX element is clicked.
13896 * @event checkboxClickEvent
13897 * @param oArgs.event {HTMLEvent} The event object.
13898 * @param oArgs.target {HTMLElement} The CHECKBOX element.
13902 * Fired when a SELECT element is changed.
13904 * @event dropdownChangeEvent
13905 * @param oArgs.event {HTMLEvent} The event object.
13906 * @param oArgs.target {HTMLElement} The SELECT element.
13910 * Fired when a RADIO element is clicked.
13912 * @event radioClickEvent
13913 * @param oArgs.event {HTMLEvent} The event object.
13914 * @param oArgs.target {HTMLElement} The RADIO element.
13942 /////////////////////////////////////////////////////////////////////////////
13946 /////////////////////////////////////////////////////////////////////////////
13949 * @method showCellEditorBtns
13950 * @deprecated Use CellEditor.renderBtns()
13952 showCellEditorBtns : function(elContainer) {
13954 var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
13955 Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON);
13958 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
13959 Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT);
13960 elSaveBtn.innerHTML = "OK";
13961 Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) {
13962 oSelf.onEventSaveCellEditor(oArgs, oSelf);
13963 oSelf.focusTbodyEl();
13967 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
13968 elCancelBtn.innerHTML = "Cancel";
13969 Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) {
13970 oSelf.onEventCancelCellEditor(oArgs, oSelf);
13971 oSelf.focusTbodyEl();
13974 YAHOO.log("The method showCellEditorBtns() has been deprecated." +
13975 " Please use the CellEditor class.", "warn", this.toString());
13979 * @method resetCellEditor
13980 * @deprecated Use destroyCellEditor
13982 resetCellEditor : function() {
13983 var elContainer = this._oCellEditor.container;
13984 elContainer.style.display = "none";
13985 Ev.purgeElement(elContainer, true);
13986 elContainer.innerHTML = "";
13987 this._oCellEditor.value = null;
13988 this._oCellEditor.isActive = false;
13990 YAHOO.log("The method resetCellEditor() has been deprecated." +
13991 " Please use the CellEditor class.", "warn", this.toString());
13995 * @event editorUpdateEvent
13996 * @deprecated Use CellEditor class.
14001 * @deprecated Use getTbodyEl().
14003 getBody : function() {
14004 // Backward compatibility
14005 YAHOO.log("The method getBody() has been deprecated" +
14006 " in favor of getTbodyEl()", "warn", this.toString());
14007 return this.getTbodyEl();
14012 * @deprecated Use getTdEl().
14014 getCell : function(index) {
14015 // Backward compatibility
14016 YAHOO.log("The method getCell() has been deprecated" +
14017 " in favor of getTdEl()", "warn", this.toString());
14018 return this.getTdEl(index);
14023 * @deprecated Use getTrEl().
14025 getRow : function(index) {
14026 // Backward compatibility
14027 YAHOO.log("The method getRow() has been deprecated" +
14028 " in favor of getTrEl()", "warn", this.toString());
14029 return this.getTrEl(index);
14033 * @method refreshView
14034 * @deprecated Use render.
14036 refreshView : function() {
14037 // Backward compatibility
14038 YAHOO.log("The method refreshView() has been deprecated" +
14039 " in favor of render()", "warn", this.toString());
14045 * @deprecated Use selectRow.
14047 select : function(els) {
14048 // Backward compatibility
14049 YAHOO.log("The method select() has been deprecated" +
14050 " in favor of selectRow()", "warn", this.toString());
14051 if(!lang.isArray(els)) {
14054 for(var i=0; i<els.length; i++) {
14055 this.selectRow(els[i]);
14060 * @method onEventEditCell
14061 * @deprecated Use onEventShowCellEditor.
14063 onEventEditCell : function(oArgs) {
14064 // Backward compatibility
14065 YAHOO.log("The method onEventEditCell() has been deprecated" +
14066 " in favor of onEventShowCellEditor()", "warn", this.toString());
14067 this.onEventShowCellEditor(oArgs);
14071 * @method _syncColWidths
14072 * @deprecated Use validateColumnWidths.
14074 _syncColWidths : function() {
14075 // Backward compatibility
14076 YAHOO.log("The method _syncColWidths() has been deprecated" +
14077 " in favor of validateColumnWidths()", "warn", this.toString());
14078 this.validateColumnWidths();
14082 * @event headerRowMouseoverEvent
14083 * @deprecated Use theadRowMouseoverEvent.
14087 * @event headerRowMouseoutEvent
14088 * @deprecated Use theadRowMouseoutEvent.
14092 * @event headerRowMousedownEvent
14093 * @deprecated Use theadRowMousedownEvent.
14097 * @event headerRowClickEvent
14098 * @deprecated Use theadRowClickEvent.
14102 * @event headerRowDblclickEvent
14103 * @deprecated Use theadRowDblclickEvent.
14107 * @event headerCellMouseoverEvent
14108 * @deprecated Use theadCellMouseoverEvent.
14112 * @event headerCellMouseoutEvent
14113 * @deprecated Use theadCellMouseoutEvent.
14117 * @event headerCellMousedownEvent
14118 * @deprecated Use theadCellMousedownEvent.
14122 * @event headerCellClickEvent
14123 * @deprecated Use theadCellClickEvent.
14127 * @event headerCellDblclickEvent
14128 * @deprecated Use theadCellDblclickEvent.
14132 * @event headerLabelMouseoverEvent
14133 * @deprecated Use theadLabelMouseoverEvent.
14137 * @event headerLabelMouseoutEvent
14138 * @deprecated Use theadLabelMouseoutEvent.
14142 * @event headerLabelMousedownEvent
14143 * @deprecated Use theadLabelMousedownEvent.
14147 * @event headerLabelClickEvent
14148 * @deprecated Use theadLabelClickEvent.
14152 * @event headerLabelDbllickEvent
14153 * @deprecated Use theadLabelDblclickEvent.
14159 * Alias for onDataReturnSetRows for backward compatibility
14160 * @method onDataReturnSetRecords
14161 * @deprecated Use onDataReturnSetRows
14163 DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows;
14166 * Alias for onPaginatorChange for backward compatibility
14167 * @method onPaginatorChange
14168 * @deprecated Use onPaginatorChangeRequest
14170 DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest;
14172 /////////////////////////////////////////////////////////////////////////////
14174 // Deprecated static APIs
14176 /////////////////////////////////////////////////////////////////////////////
14178 * @method DataTable.formatTheadCell
14179 * @deprecated Use formatTheadCell.
14181 DT.formatTheadCell = function() {};
14184 * @method DataTable.editCheckbox
14185 * @deprecated Use YAHOO.widget.CheckboxCellEditor.
14187 DT.editCheckbox = function() {};
14190 * @method DataTable.editDate
14191 * @deprecated Use YAHOO.widget.DateCellEditor.
14193 DT.editDate = function() {};
14196 * @method DataTable.editDropdown
14197 * @deprecated Use YAHOO.widget.DropdownCellEditor.
14199 DT.editDropdown = function() {};
14202 * @method DataTable.editRadio
14203 * @deprecated Use YAHOO.widget.RadioCellEditor.
14205 DT.editRadio = function() {};
14208 * @method DataTable.editTextarea
14209 * @deprecated Use YAHOO.widget.TextareaCellEditor
14211 DT.editTextarea = function() {};
14214 * @method DataTable.editTextbox
14215 * @deprecated Use YAHOO.widget.TextboxCellEditor
14217 DT.editTextbox= function() {};
14223 var lang = YAHOO.lang,
14225 widget = YAHOO.widget,
14230 DS = util.DataSourceBase,
14231 DT = widget.DataTable,
14232 Pag = widget.Paginator;
14235 * The ScrollingDataTable class extends the DataTable class to provide
14236 * functionality for x-scrolling, y-scrolling, and xy-scrolling.
14238 * @namespace YAHOO.widget
14239 * @class ScrollingDataTable
14240 * @extends YAHOO.widget.DataTable
14242 * @param elContainer {HTMLElement} Container element for the TABLE.
14243 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
14244 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
14245 * @param oConfigs {object} (optional) Object literal of configuration values.
14247 widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
14248 oConfigs = oConfigs || {};
14250 // Prevent infinite loop
14251 if(oConfigs.scrollable) {
14252 oConfigs.scrollable = false;
14255 widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs);
14257 // Once per instance
14258 this.subscribe("columnShowEvent", this._onColumnChange);
14261 var SDT = widget.ScrollingDataTable;
14263 /////////////////////////////////////////////////////////////////////////////
14265 // Public constants
14267 /////////////////////////////////////////////////////////////////////////////
14268 lang.augmentObject(SDT, {
14271 * Class name assigned to inner DataTable header container.
14273 * @property DataTable.CLASS_HEADER
14277 * @default "yui-dt-hd"
14279 CLASS_HEADER : "yui-dt-hd",
14282 * Class name assigned to inner DataTable body container.
14284 * @property DataTable.CLASS_BODY
14288 * @default "yui-dt-bd"
14290 CLASS_BODY : "yui-dt-bd"
14293 lang.extend(SDT, DT, {
14296 * Container for fixed header TABLE element.
14298 * @property _elHdContainer
14299 * @type HTMLElement
14302 _elHdContainer : null,
14305 * Fixed header TABLE element.
14307 * @property _elHdTable
14308 * @type HTMLElement
14314 * Container for scrolling body TABLE element.
14316 * @property _elBdContainer
14317 * @type HTMLElement
14320 _elBdContainer : null,
14323 * Body THEAD element.
14325 * @property _elBdThead
14326 * @type HTMLElement
14332 * Offscreen container to temporarily clone SDT for auto-width calculation.
14334 * @property _elTmpContainer
14335 * @type HTMLElement
14338 _elTmpContainer : null,
14341 * Offscreen TABLE element for auto-width calculation.
14343 * @property _elTmpTable
14344 * @type HTMLElement
14347 _elTmpTable : null,
14350 * True if x-scrollbar is currently visible.
14351 * @property _bScrollbarX
14355 _bScrollbarX : null,
14371 /////////////////////////////////////////////////////////////////////////////
14373 // Superclass methods
14375 /////////////////////////////////////////////////////////////////////////////
14378 * Implementation of Element's abstract method. Sets up config values.
14380 * @method initAttributes
14381 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
14385 initAttributes : function(oConfigs) {
14386 oConfigs = oConfigs || {};
14387 SDT.superclass.initAttributes.call(this, oConfigs);
14391 * @description Table width for scrollable tables (e.g., "40em").
14394 this.setAttributeConfig("width", {
14396 validator: lang.isString,
14397 method: function(oParam) {
14398 if(this._elHdContainer && this._elBdContainer) {
14399 this._elHdContainer.style.width = oParam;
14400 this._elBdContainer.style.width = oParam;
14401 this._syncScrollX();
14402 this._syncScrollOverhang();
14408 * @attribute height
14409 * @description Table body height for scrollable tables, not including headers (e.g., "40em").
14412 this.setAttributeConfig("height", {
14414 validator: lang.isString,
14415 method: function(oParam) {
14416 if(this._elHdContainer && this._elBdContainer) {
14417 this._elBdContainer.style.height = oParam;
14418 this._syncScrollX();
14419 this._syncScrollY();
14420 this._syncScrollOverhang();
14426 * @attribute COLOR_COLUMNFILLER
14427 * @description CSS color value assigned to header filler on scrollable tables.
14429 * @default "#F2F2F2"
14431 this.setAttributeConfig("COLOR_COLUMNFILLER", {
14433 validator: lang.isString,
14434 method: function(oParam) {
14435 this._elHdContainer.style.backgroundColor = oParam;
14441 * Initializes DOM elements for a ScrollingDataTable, including creation of
14442 * two separate TABLE elements.
14444 * @method _initDomElements
14445 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
14446 * return {Boolean} False in case of error, otherwise true
14449 _initDomElements : function(elContainer) {
14450 // Outer and inner containers
14451 this._initContainerEl(elContainer);
14452 if(this._elContainer && this._elHdContainer && this._elBdContainer) {
14454 this._initTableEl();
14456 if(this._elHdTable && this._elTable) {
14458 ///this._initColgroupEl(this._elHdTable, this._elTable);
14459 this._initColgroupEl(this._elHdTable);
14462 this._initTheadEl(this._elHdTable, this._elTable);
14465 this._initTbodyEl(this._elTable);
14467 this._initMsgTbodyEl(this._elTable);
14470 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody ||
14471 !this._elHdTable || !this._elBdThead) {
14472 YAHOO.log("Could not instantiate DataTable due to an invalid DOM elements", "error", this.toString());
14481 * Destroy's the DataTable outer and inner container elements, if available.
14483 * @method _destroyContainerEl
14484 * @param elContainer {HTMLElement} Reference to the container element.
14487 _destroyContainerEl : function(elContainer) {
14488 Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE);
14489 SDT.superclass._destroyContainerEl.call(this, elContainer);
14490 this._elHdContainer = null;
14491 this._elBdContainer = null;
14495 * Initializes the DataTable outer container element and creates inner header
14496 * and body container elements.
14498 * @method _initContainerEl
14499 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
14502 _initContainerEl : function(elContainer) {
14503 SDT.superclass._initContainerEl.call(this, elContainer);
14505 if(this._elContainer) {
14506 elContainer = this._elContainer; // was constructor input, now is DOM ref
14507 Dom.addClass(elContainer, DT.CLASS_SCROLLABLE);
14509 // Container for header TABLE
14510 var elHdContainer = document.createElement("div");
14511 elHdContainer.style.width = this.get("width") || "";
14512 elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER");
14513 Dom.addClass(elHdContainer, SDT.CLASS_HEADER);
14514 this._elHdContainer = elHdContainer;
14515 elContainer.appendChild(elHdContainer);
14517 // Container for body TABLE
14518 var elBdContainer = document.createElement("div");
14519 elBdContainer.style.width = this.get("width") || "";
14520 elBdContainer.style.height = this.get("height") || "";
14521 Dom.addClass(elBdContainer, SDT.CLASS_BODY);
14522 Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers
14523 this._elBdContainer = elBdContainer;
14524 elContainer.appendChild(elBdContainer);
14529 * Creates HTML markup CAPTION element.
14531 * @method _initCaptionEl
14532 * @param sCaption {String} Text for caption.
14535 _initCaptionEl : function(sCaption) {
14536 // Not yet supported
14537 /*if(this._elHdTable && sCaption) {
14538 // Create CAPTION element
14539 if(!this._elCaption) {
14540 this._elCaption = this._elHdTable.createCaption();
14542 // Set CAPTION value
14543 this._elCaption.innerHTML = sCaption;
14545 else if(this._elCaption) {
14546 this._elCaption.parentNode.removeChild(this._elCaption);
14551 * Destroy's the DataTable head TABLE element, if available.
14553 * @method _destroyHdTableEl
14556 _destroyHdTableEl : function() {
14557 var elTable = this._elHdTable;
14559 Ev.purgeElement(elTable, true);
14560 elTable.parentNode.removeChild(elTable);
14562 // A little out of place, but where else can we null out these extra elements?
14563 ///this._elBdColgroup = null;
14564 this._elBdThead = null;
14569 * Initializes ScrollingDataTable TABLE elements into the two inner containers.
14571 * @method _initTableEl
14574 _initTableEl : function() {
14576 if(this._elHdContainer) {
14577 this._destroyHdTableEl();
14580 this._elHdTable = this._elHdContainer.appendChild(document.createElement("table"));
14583 SDT.superclass._initTableEl.call(this, this._elBdContainer);
14587 * Initializes ScrollingDataTable THEAD elements into the two inner containers.
14589 * @method _initTheadEl
14590 * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference.
14591 * @param elTable {HTMLElement} (optional) TABLE element reference.
14594 _initTheadEl : function(elHdTable, elTable) {
14595 elHdTable = elHdTable || this._elHdTable;
14596 elTable = elTable || this._elTable;
14598 // Scrolling body's THEAD
14599 this._initBdTheadEl(elTable);
14600 // Standard fixed head THEAD
14601 SDT.superclass._initTheadEl.call(this, elHdTable);
14605 * SDT changes ID so as not to duplicate the accessibility TH IDs.
14607 * @method _initThEl
14608 * @param elTh {HTMLElement} TH element reference.
14609 * @param oColumn {YAHOO.widget.Column} Column object.
14612 _initThEl : function(elTh, oColumn) {
14613 SDT.superclass._initThEl.call(this, elTh, oColumn);
14614 elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD
14618 * Destroy's the DataTable body THEAD element, if available.
14620 * @method _destroyBdTheadEl
14623 _destroyBdTheadEl : function() {
14624 var elBdThead = this._elBdThead;
14626 var elTable = elBdThead.parentNode;
14627 Ev.purgeElement(elBdThead, true);
14628 elTable.removeChild(elBdThead);
14629 this._elBdThead = null;
14631 this._destroyColumnHelpers();
14636 * Initializes body THEAD element.
14638 * @method _initBdTheadEl
14639 * @param elTable {HTMLElement} TABLE element into which to create THEAD.
14640 * @return {HTMLElement} Initialized THEAD element.
14643 _initBdTheadEl : function(elTable) {
14645 // Destroy previous
14646 this._destroyBdTheadEl();
14648 var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild);
14650 // Add TRs to the THEAD;
14651 var oColumnSet = this._oColumnSet,
14652 colTree = oColumnSet.tree,
14653 elTh, elTheadTr, oColumn, i, j, k, len;
14655 for(i=0, k=colTree.length; i<k; i++) {
14656 elTheadTr = elThead.appendChild(document.createElement("tr"));
14658 // ...and create TH cells
14659 for(j=0, len=colTree[i].length; j<len; j++) {
14660 oColumn = colTree[i][j];
14661 elTh = elTheadTr.appendChild(document.createElement("th"));
14662 this._initBdThEl(elTh,oColumn,i,j);
14665 this._elBdThead = elThead;
14666 YAHOO.log("Accessibility TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString());
14671 * Populates TH element for the body THEAD element.
14673 * @method _initBdThEl
14674 * @param elTh {HTMLElement} TH element reference.
14675 * @param oColumn {YAHOO.widget.Column} Column object.
14678 _initBdThEl : function(elTh, oColumn) {
14679 elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility
14680 elTh.rowSpan = oColumn.getRowspan();
14681 elTh.colSpan = oColumn.getColspan();
14682 // Assign abbr attribute
14684 elTh.abbr = oColumn.abbr;
14687 // TODO: strip links and form elements
14688 var sKey = oColumn.getKey();
14689 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
14690 elTh.innerHTML = sLabel;
14694 * Initializes ScrollingDataTable TBODY element for data
14696 * @method _initTbodyEl
14697 * @param elTable {HTMLElement} TABLE element into which to create TBODY .
14700 _initTbodyEl : function(elTable) {
14701 SDT.superclass._initTbodyEl.call(this, elTable);
14703 // Bug 2105534 - Safari 3 gap
14704 // Bug 2492591 - IE8 offsetTop
14705 elTable.style.marginTop = (this._elTbody.offsetTop > 0) ?
14706 "-"+this._elTbody.offsetTop+"px" : 0;
14738 * Sets focus on the given element.
14741 * @param el {HTMLElement} Element.
14744 _focusEl : function(el) {
14745 el = el || this._elTbody;
14747 this._storeScrollPositions();
14748 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
14749 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
14750 // strange unexpected things as the user clicks on buttons and other controls.
14752 // Bug 1921135: Wrap the whole thing in a setTimeout
14753 setTimeout(function() {
14754 setTimeout(function() {
14757 oSelf._restoreScrollPositions();
14784 * Internal wrapper calls run() on render Chain instance.
14786 * @method _runRenderChain
14789 _runRenderChain : function() {
14790 this._storeScrollPositions();
14791 this._oChainRender.run();
14795 * Stores scroll positions so they can be restored after a render.
14797 * @method _storeScrollPositions
14800 _storeScrollPositions : function() {
14801 this._nScrollTop = this._elBdContainer.scrollTop;
14802 this._nScrollLeft = this._elBdContainer.scrollLeft;
14806 * Restores scroll positions to stored value.
14808 * @method _retoreScrollPositions
14811 _restoreScrollPositions : function() {
14812 // Reset scroll positions
14813 if(this._nScrollTop) {
14814 this._elBdContainer.scrollTop = this._nScrollTop;
14815 this._nScrollTop = null;
14817 if(this._nScrollLeft) {
14818 this._elBdContainer.scrollLeft = this._nScrollLeft;
14819 this._nScrollLeft = null;
14824 * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable.
14826 * @method _validateColumnWidth
14827 * @param oColumn {YAHOO.widget.Column} Column instance.
14828 * @param elTd {HTMLElement} TD element to validate against.
14831 _validateColumnWidth : function(oColumn, elTd) {
14832 // Only Columns without widths that are not hidden
14833 if(!oColumn.width && !oColumn.hidden) {
14834 var elTh = oColumn.getThEl();
14835 // Unset a calculated auto-width
14836 if(oColumn._calculatedWidth) {
14837 this._setColumnWidth(oColumn, "auto", "visible");
14839 // Compare auto-widths
14840 if(elTh.offsetWidth !== elTd.offsetWidth) {
14841 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14842 oColumn.getThLinerEl() : elTd.firstChild;
14844 // Grab the wider liner width, unless the minWidth is wider
14845 var newWidth = Math.max(0,
14846 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14849 var sOverflow = 'visible';
14851 // Now validate against maxAutoWidth
14852 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14853 newWidth = oColumn.maxAutoWidth;
14854 sOverflow = "hidden";
14857 // Set to the wider auto-width
14858 this._elTbody.style.display = "none";
14859 this._setColumnWidth(oColumn, newWidth+'px', sOverflow);
14860 oColumn._calculatedWidth = newWidth;
14861 this._elTbody.style.display = "";
14867 * For one or all Columns of a ScrollingDataTable, when Column is not hidden,
14868 * and width is not set, syncs widths of header and body cells and
14869 * validates that width against minWidth and/or maxAutoWidth as necessary.
14871 * @method validateColumnWidths
14872 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
14874 validateColumnWidths : function(oColumn) {
14875 // Validate there is at least one TR with proper TDs
14876 var allKeys = this._oColumnSet.keys,
14877 allKeysLength = allKeys.length,
14878 elRow = this.getFirstTrEl();
14880 // Reset overhang for IE
14882 this._setOverhangValue(1);
14885 if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) {
14886 // Temporarily unsnap container since it causes inaccurate calculations
14887 var sWidth = this.get("width");
14889 this._elHdContainer.style.width = "";
14890 this._elBdContainer.style.width = "";
14892 this._elContainer.style.width = "";
14894 //Validate just one Column
14895 if(oColumn && lang.isNumber(oColumn.getKeyIndex())) {
14896 this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]);
14898 // Iterate through all Columns to unset calculated widths in one pass
14900 var elTd, todos = [], thisTodo, i, len;
14901 for(i=0; i<allKeysLength; i++) {
14902 oColumn = allKeys[i];
14903 // Only Columns without widths that are not hidden, unset a calculated auto-width
14904 if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) {
14905 todos[todos.length] = oColumn;
14909 this._elTbody.style.display = "none";
14910 for(i=0, len=todos.length; i<len; i++) {
14911 this._setColumnWidth(todos[i], "auto", "visible");
14913 this._elTbody.style.display = "";
14917 // Iterate through all Columns and make the store the adjustments to make in one pass
14918 for(i=0; i<allKeysLength; i++) {
14919 oColumn = allKeys[i];
14920 elTd = elRow.childNodes[i];
14921 // Only Columns without widths that are not hidden
14922 if(!oColumn.width && !oColumn.hidden) {
14923 var elTh = oColumn.getThEl();
14925 // Compare auto-widths
14926 if(elTh.offsetWidth !== elTd.offsetWidth) {
14927 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
14928 oColumn.getThLinerEl() : elTd.firstChild;
14930 // Grab the wider liner width, unless the minWidth is wider
14931 var newWidth = Math.max(0,
14932 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
14935 var sOverflow = 'visible';
14937 // Now validate against maxAutoWidth
14938 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
14939 newWidth = oColumn.maxAutoWidth;
14940 sOverflow = "hidden";
14943 todos[todos.length] = [oColumn, newWidth, sOverflow];
14948 this._elTbody.style.display = "none";
14949 for(i=0, len=todos.length; i<len; i++) {
14950 thisTodo = todos[i];
14951 // Set to the wider auto-width
14952 this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]);
14953 thisTodo[0]._calculatedWidth = thisTodo[1];
14955 this._elTbody.style.display = "";
14958 // Resnap unsnapped containers
14960 this._elHdContainer.style.width = sWidth;
14961 this._elBdContainer.style.width = sWidth;
14965 this._syncScroll();
14966 this._restoreScrollPositions();
14970 * Syncs padding around scrollable tables, including Column header right-padding
14971 * and container width and height.
14973 * @method _syncScroll
14976 _syncScroll : function() {
14977 this._syncScrollX();
14978 this._syncScrollY();
14979 this._syncScrollOverhang();
14982 this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft;
14983 if(!this.get("width")) {
14985 document.body.style += '';
14991 * Snaps container width for y-scrolling tables.
14993 * @method _syncScrollY
14996 _syncScrollY : function() {
14997 var elTbody = this._elTbody,
14998 elBdContainer = this._elBdContainer;
15000 // X-scrolling not enabled
15001 if(!this.get("width")) {
15002 // Snap outer container width to content
15003 this._elContainer.style.width =
15004 (elBdContainer.scrollHeight > elBdContainer.clientHeight) ?
15005 // but account for y-scrollbar since it is visible
15006 (elTbody.parentNode.clientWidth + 19) + "px" :
15007 // no y-scrollbar, just borders
15008 (elTbody.parentNode.clientWidth + 2) + "px";
15013 * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
15015 * @method _syncScrollX
15018 _syncScrollX : function() {
15019 var elTbody = this._elTbody,
15020 elBdContainer = this._elBdContainer;
15022 // IE 6 and 7 only when y-scrolling not enabled
15023 if(!this.get("height") && (ua.ie)) {
15024 // Snap outer container height to content
15025 elBdContainer.style.height =
15026 // but account for x-scrollbar if it is visible
15027 (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ?
15028 (elTbody.parentNode.offsetHeight + 18) + "px" :
15029 elTbody.parentNode.offsetHeight + "px";
15032 // Sync message tbody
15033 if(this._elTbody.rows.length === 0) {
15034 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
15037 this._elMsgTbody.parentNode.style.width = "";
15042 * Adds/removes Column header overhang as necesary.
15044 * @method _syncScrollOverhang
15047 _syncScrollOverhang : function() {
15048 var elBdContainer = this._elBdContainer,
15049 // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table
15052 // Y-scrollbar is visible, which is when the overhang needs to jut out
15053 if((elBdContainer.scrollHeight > elBdContainer.clientHeight) &&
15054 // X-scrollbar is also visible, which means the right is jagged, not flush with the Column
15055 (elBdContainer.scrollWidth > elBdContainer.clientWidth)) {
15059 this._setOverhangValue(nPadding);
15064 * Sets Column header overhang to given width.
15066 * @method _setOverhangValue
15067 * @param nBorderWidth {Number} Value of new border for overhang.
15070 _setOverhangValue : function(nBorderWidth) {
15071 var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [],
15072 len = aLastHeaders.length,
15073 sPrefix = this._sId+"-fixedth-",
15074 sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER");
15076 this._elThead.style.display = "none";
15077 for(var i=0; i<len; i++) {
15078 Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue;
15080 this._elThead.style.display = "";
15121 * Returns DOM reference to the DataTable's fixed header container element.
15123 * @method getHdContainerEl
15124 * @return {HTMLElement} Reference to DIV element.
15126 getHdContainerEl : function() {
15127 return this._elHdContainer;
15131 * Returns DOM reference to the DataTable's scrolling body container element.
15133 * @method getBdContainerEl
15134 * @return {HTMLElement} Reference to DIV element.
15136 getBdContainerEl : function() {
15137 return this._elBdContainer;
15141 * Returns DOM reference to the DataTable's fixed header TABLE element.
15143 * @method getHdTableEl
15144 * @return {HTMLElement} Reference to TABLE element.
15146 getHdTableEl : function() {
15147 return this._elHdTable;
15151 * Returns DOM reference to the DataTable's scrolling body TABLE element.
15153 * @method getBdTableEl
15154 * @return {HTMLElement} Reference to TABLE element.
15156 getBdTableEl : function() {
15157 return this._elTable;
15161 * Disables ScrollingDataTable UI.
15165 disable : function() {
15166 var elMask = this._elMask;
15167 elMask.style.width = this._elBdContainer.offsetWidth + "px";
15168 elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px";
15169 elMask.style.display = "";
15170 this.fireEvent("disableEvent");
15174 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
15175 * non-nested Columns, and top-level parent Columns (which will remove all
15176 * children Columns).
15178 * @method removeColumn
15179 * @param oColumn {YAHOO.widget.Column} Column instance.
15180 * @return oColumn {YAHOO.widget.Column} Removed Column instance.
15182 removeColumn : function(oColumn) {
15183 // Store scroll pos
15184 var hdPos = this._elHdContainer.scrollLeft;
15185 var bdPos = this._elBdContainer.scrollLeft;
15187 // Call superclass method
15188 oColumn = SDT.superclass.removeColumn.call(this, oColumn);
15190 // Restore scroll pos
15191 this._elHdContainer.scrollLeft = hdPos;
15192 this._elBdContainer.scrollLeft = bdPos;
15198 * Inserts given Column at the index if given, otherwise at the end. NOTE: You
15199 * can only add non-nested Columns and top-level parent Columns. You cannot add
15200 * a nested Column to an existing parent.
15202 * @method insertColumn
15203 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
15204 * definition or a Column instance.
15205 * @param index {Number} (optional) New tree index.
15206 * @return oColumn {YAHOO.widget.Column} Inserted Column instance.
15208 insertColumn : function(oColumn, index) {
15209 // Store scroll pos
15210 var hdPos = this._elHdContainer.scrollLeft;
15211 var bdPos = this._elBdContainer.scrollLeft;
15213 // Call superclass method
15214 var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index);
15216 // Restore scroll pos
15217 this._elHdContainer.scrollLeft = hdPos;
15218 this._elBdContainer.scrollLeft = bdPos;
15224 * Removes given Column and inserts into given tree index. NOTE: You
15225 * can only reorder non-nested Columns and top-level parent Columns. You cannot
15226 * reorder a nested Column to an existing parent.
15228 * @method reorderColumn
15229 * @param oColumn {YAHOO.widget.Column} Column instance.
15230 * @param index {Number} New tree index.
15232 reorderColumn : function(oColumn, index) {
15233 // Store scroll pos
15234 var hdPos = this._elHdContainer.scrollLeft;
15235 var bdPos = this._elBdContainer.scrollLeft;
15237 // Call superclass method
15238 var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index);
15240 // Restore scroll pos
15241 this._elHdContainer.scrollLeft = hdPos;
15242 this._elBdContainer.scrollLeft = bdPos;
15248 * Sets given Column to given pixel width. If new width is less than minWidth
15249 * width, sets to minWidth. Updates oColumn.width value.
15251 * @method setColumnWidth
15252 * @param oColumn {YAHOO.widget.Column} Column instance.
15253 * @param nWidth {Number} New width in pixels.
15255 setColumnWidth : function(oColumn, nWidth) {
15256 oColumn = this.getColumn(oColumn);
15258 // Validate new width against minWidth
15259 if(lang.isNumber(nWidth)) {
15260 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
15263 oColumn.width = nWidth;
15265 // Resize the DOM elements
15266 this._setColumnWidth(oColumn, nWidth+"px");
15267 this._syncScroll();
15269 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
15270 YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString());
15272 // Unsets a width to auto-size
15273 else if(nWidth === null) {
15275 oColumn.width = nWidth;
15277 // Resize the DOM elements
15278 this._setColumnWidth(oColumn, "auto");
15279 this.validateColumnWidths(oColumn);
15280 this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
15281 YAHOO.log("Column " + oColumn + " width unset", "info", this.toString());
15284 // Bug 2339454: resize then sort misaligment
15285 this._clearTrTemplateEl();
15288 YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString());
15293 * Displays message within secondary TBODY.
15295 * @method showTableMessage
15296 * @param sHTML {String} (optional) Value for innerHTMlang.
15297 * @param sClassName {String} (optional) Classname.
15299 showTableMessage : function(sHTML, sClassName) {
15300 var elCell = this._elMsgTd;
15301 if(lang.isString(sHTML)) {
15302 elCell.firstChild.innerHTML = sHTML;
15304 if(lang.isString(sClassName)) {
15305 Dom.addClass(elCell.firstChild, sClassName);
15308 // Needed for SDT only
15309 var elThead = this.getTheadEl();
15310 var elTable = elThead.parentNode;
15311 var newWidth = elTable.offsetWidth;
15312 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
15314 this._elMsgTbody.style.display = "";
15316 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
15317 YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString());
15332 /////////////////////////////////////////////////////////////////////////////
15334 // Private Custom Event Handlers
15336 /////////////////////////////////////////////////////////////////////////////
15339 * Handles Column mutations
15341 * @method onColumnChange
15342 * @param oArgs {Object} Custom Event data.
15344 _onColumnChange : function(oArg) {
15345 // Figure out which Column changed
15346 var oColumn = (oArg.column) ? oArg.column :
15347 (oArg.editor) ? oArg.editor.column : null;
15348 this._storeScrollPositions();
15349 this.validateColumnWidths(oColumn);
15366 /////////////////////////////////////////////////////////////////////////////
15368 // Private DOM Event Handlers
15370 /////////////////////////////////////////////////////////////////////////////
15373 * Syncs scrolltop and scrollleft of all TABLEs.
15375 * @method _onScroll
15376 * @param e {HTMLEvent} The scroll event.
15377 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15380 _onScroll : function(e, oSelf) {
15381 oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft;
15383 if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
15384 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
15385 oSelf.cancelCellEditor();
15388 var elTarget = Ev.getTarget(e);
15389 var elTag = elTarget.nodeName.toLowerCase();
15390 oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
15394 * Handles keydown events on the THEAD element.
15396 * @method _onTheadKeydown
15397 * @param e {HTMLEvent} The key event.
15398 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
15401 _onTheadKeydown : function(e, oSelf) {
15402 // If tabbing to next TH label link causes THEAD to scroll,
15403 // need to sync scrollLeft with TBODY
15404 if(Ev.getCharCode(e) === 9) {
15405 setTimeout(function() {
15406 if((oSelf instanceof SDT) && oSelf._sId) {
15407 oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft;
15412 var elTarget = Ev.getTarget(e);
15413 var elTag = elTarget.nodeName.toLowerCase();
15414 var bKeepBubbling = true;
15415 while(elTarget && (elTag != "table")) {
15421 // TODO: implement textareaKeyEvent
15424 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
15429 if(bKeepBubbling === false) {
15433 elTarget = elTarget.parentNode;
15435 elTag = elTarget.nodeName.toLowerCase();
15439 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
15446 * Fired when a fixed scrolling DataTable has a scroll.
15448 * @event tableScrollEvent
15449 * @param oArgs.event {HTMLEvent} The event object.
15450 * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
15451 * or the DataTable's TBODY element (everyone else).
15464 var lang = YAHOO.lang,
15466 widget = YAHOO.widget,
15472 DT = widget.DataTable;
15473 /****************************************************************************/
15474 /****************************************************************************/
15475 /****************************************************************************/
15478 * The BaseCellEditor class provides base functionality common to all inline cell
15479 * editors for a DataTable widget.
15481 * @namespace YAHOO.widget
15482 * @class BaseCellEditor
15483 * @uses YAHOO.util.EventProvider
15485 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
15486 * @param oConfigs {Object} (Optional) Object literal of configs.
15488 widget.BaseCellEditor = function(sType, oConfigs) {
15489 this._sId = this._sId || "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++;
15490 this._sType = sType;
15493 this._initConfigs(oConfigs);
15495 // Create Custom Events
15496 this._initEvents();
15502 var BCE = widget.BaseCellEditor;
15504 /////////////////////////////////////////////////////////////////////////////
15508 /////////////////////////////////////////////////////////////////////////////
15509 lang.augmentObject(BCE, {
15512 * Global instance counter.
15514 * @property CellEditor._nCount
15523 * Class applied to CellEditor container.
15525 * @property CellEditor.CLASS_CELLEDITOR
15528 * @default "yui-ceditor"
15530 CLASS_CELLEDITOR : "yui-ceditor"
15535 /////////////////////////////////////////////////////////////////////////////
15539 /////////////////////////////////////////////////////////////////////////////
15541 * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique
15542 * DOM ID strings and log messages.
15560 * DataTable instance.
15562 * @property _oDataTable
15563 * @type YAHOO.widget.DataTable
15566 _oDataTable : null,
15571 * @property _oColumn
15572 * @type YAHOO.widget.Column
15580 * @property _oRecord
15581 * @type YAHOO.widget.Record
15591 * @type HTMLElement
15598 * Container for inline editor.
15600 * @property _elContainer
15601 * @type HTMLElement
15604 _elContainer : null,
15607 * Reference to Cancel button, if available.
15609 * @property _elCancelBtn
15610 * @type HTMLElement
15614 _elCancelBtn : null,
15617 * Reference to Save button, if available.
15619 * @property _elSaveBtn
15620 * @type HTMLElement
15633 /////////////////////////////////////////////////////////////////////////////
15637 /////////////////////////////////////////////////////////////////////////////
15640 * Initialize configs.
15642 * @method _initConfigs
15645 _initConfigs : function(oConfigs) {
15646 // Object literal defines CellEditor configs
15647 if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
15648 for(var sConfig in oConfigs) {
15650 this[sConfig] = oConfigs[sConfig];
15657 * Initialize Custom Events.
15659 * @method _initEvents
15662 _initEvents : function() {
15663 this.createEvent("showEvent");
15664 this.createEvent("keydownEvent");
15665 this.createEvent("invalidDataEvent");
15666 this.createEvent("revertEvent");
15667 this.createEvent("saveEvent");
15668 this.createEvent("cancelEvent");
15669 this.createEvent("blurEvent");
15670 this.createEvent("blockEvent");
15671 this.createEvent("unblockEvent");
15686 /////////////////////////////////////////////////////////////////////////////
15688 // Public properties
15690 /////////////////////////////////////////////////////////////////////////////
15692 * Implementer defined function that can submit the input value to a server. This
15693 * function must accept the arguments fnCallback and oNewValue. When the submission
15694 * is complete, the function must also call fnCallback(bSuccess, oNewValue) to
15695 * finish the save routine in the CellEditor. This function can also be used to
15696 * perform extra validation or input value manipulation.
15698 * @property asyncSubmitter
15699 * @type HTMLFunction
15701 asyncSubmitter : null,
15712 * Default value in case Record data is undefined. NB: Null values will not trigger
15713 * the default value.
15715 * @property defaultValue
15719 defaultValue : null,
15722 * Validator function for input data, called from the DataTable instance scope,
15723 * receives the arguments (inputValue, currentValue, editorInstance) and returns
15724 * either the validated (or type-converted) value or undefined.
15726 * @property validator
15727 * @type HTMLFunction
15733 * If validation is enabled, resets input field of invalid data.
15735 * @property resetInvalidData
15739 resetInvalidData : true,
15742 * True if currently active.
15744 * @property isActive
15750 * Text to display on Save button.
15752 * @property LABEL_SAVE
15756 LABEL_SAVE : "Save",
15759 * Text to display on Cancel button.
15761 * @property LABEL_CANCEL
15763 * @default "Cancel"
15765 LABEL_CANCEL : "Cancel",
15768 * True if Save/Cancel buttons should not be displayed in the CellEditor.
15770 * @property disableBtns
15774 disableBtns : false,
15782 /////////////////////////////////////////////////////////////////////////////
15786 /////////////////////////////////////////////////////////////////////////////
15788 * CellEditor instance name, for logging.
15791 * @return {String} Unique name of the CellEditor instance.
15794 toString : function() {
15795 return "CellEditor instance " + this._sId;
15799 * CellEditor unique ID.
15802 * @return {String} Unique ID of the CellEditor instance.
15805 getId : function() {
15810 * Returns reference to associated DataTable instance.
15812 * @method getDataTable
15813 * @return {YAHOO.widget.DataTable} DataTable instance.
15816 getDataTable : function() {
15817 return this._oDataTable;
15821 * Returns reference to associated Column instance.
15823 * @method getColumn
15824 * @return {YAHOO.widget.Column} Column instance.
15827 getColumn : function() {
15828 return this._oColumn;
15832 * Returns reference to associated Record instance.
15834 * @method getRecord
15835 * @return {YAHOO.widget.Record} Record instance.
15838 getRecord : function() {
15839 return this._oRecord;
15845 * Returns reference to associated TD element.
15848 * @return {HTMLElement} TD element.
15851 getTdEl : function() {
15856 * Returns container element.
15858 * @method getContainerEl
15859 * @return {HTMLElement} Reference to container element.
15862 getContainerEl : function() {
15863 return this._elContainer;
15867 * Nulls out the entire CellEditor instance and related objects, removes attached
15868 * event listeners, and clears out DOM elements inside the container, removes
15869 * container from the DOM.
15873 destroy : function() {
15874 this.unsubscribeAll();
15876 // Column is late-binding in attach()
15877 var oColumn = this.getColumn();
15879 oColumn.editor = null;
15882 var elContainer = this.getContainerEl();
15883 Ev.purgeElement(elContainer, true);
15884 elContainer.parentNode.removeChild(elContainer);
15888 * Renders DOM elements and attaches event listeners.
15892 render : function() {
15893 if(this._elContainer) {
15894 YAHOO.util.Event.purgeElement(this._elContainer, true);
15895 this._elContainer.innerHTML = "";
15898 // Render Cell Editor container element as first child of body
15899 var elContainer = document.createElement("div");
15900 elContainer.id = this.getId() + "-container"; // Needed for tracking blur event
15901 elContainer.style.display = "none";
15902 elContainer.tabIndex = 0;
15903 elContainer.className = DT.CLASS_EDITOR;
15904 document.body.insertBefore(elContainer, document.body.firstChild);
15905 this._elContainer = elContainer;
15908 Ev.addListener(elContainer, "keydown", function(e, oSelf) {
15909 // ESC cancels Cell Editor
15910 if((e.keyCode == 27)) {
15911 var target = Ev.getTarget(e);
15912 // workaround for Mac FF3 bug that disabled clicks when ESC hit when
15913 // select is open. [bug 2273056]
15914 if (target.nodeName && target.nodeName.toLowerCase() === 'select') {
15919 // Pass through event
15920 oSelf.fireEvent("keydownEvent", {editor:this, event:e});
15925 // Show Save/Cancel buttons
15926 if(!this.disableBtns) {
15930 this.doAfterRender();
15934 * Renders Save/Cancel buttons.
15936 * @method renderBtns
15938 renderBtns : function() {
15940 var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div"));
15941 elBtnsDiv.className = DT.CLASS_BUTTON;
15944 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
15945 elSaveBtn.className = DT.CLASS_DEFAULT;
15946 elSaveBtn.innerHTML = this.LABEL_SAVE;
15947 Ev.addListener(elSaveBtn, "click", function(oArgs) {
15950 this._elSaveBtn = elSaveBtn;
15953 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
15954 elCancelBtn.innerHTML = this.LABEL_CANCEL;
15955 Ev.addListener(elCancelBtn, "click", function(oArgs) {
15958 this._elCancelBtn = elCancelBtn;
15962 * Attach CellEditor for a new interaction.
15965 * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance.
15966 * @param elCell {HTMLElement} Cell to edit.
15968 attach : function(oDataTable, elCell) {
15970 if(oDataTable instanceof YAHOO.widget.DataTable) {
15971 this._oDataTable = oDataTable;
15974 elCell = oDataTable.getTdEl(elCell);
15976 this._elTd = elCell;
15979 var oColumn = oDataTable.getColumn(elCell);
15981 this._oColumn = oColumn;
15984 var oRecord = oDataTable.getRecord(elCell);
15986 this._oRecord = oRecord;
15987 var value = oRecord.getData(this.getColumn().getKey());
15988 this.value = (value !== undefined) ? value : this.defaultValue;
15994 YAHOO.log("Could not attach CellEditor","error",this.toString());
15999 * Moves container into position for display.
16003 move : function() {
16005 var elContainer = this.getContainerEl(),
16006 elTd = this.getTdEl(),
16007 x = Dom.getX(elTd),
16008 y = Dom.getY(elTd);
16010 //TODO: remove scrolling logic
16011 // SF doesn't get xy for cells in scrolling table
16012 // when tbody display is set to block
16013 if(isNaN(x) || isNaN(y)) {
16014 var elTbody = this.getDataTable().getTbodyEl();
16015 x = elTd.offsetLeft + // cell pos relative to table
16016 Dom.getX(elTbody.parentNode) - // plus table pos relative to document
16017 elTbody.scrollLeft; // minus tbody scroll
16018 y = elTd.offsetTop + // cell pos relative to table
16019 Dom.getY(elTbody.parentNode) - // plus table pos relative to document
16020 elTbody.scrollTop + // minus tbody scroll
16021 this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells
16024 elContainer.style.left = x + "px";
16025 elContainer.style.top = y + "px";
16029 * Displays CellEditor UI in the correct position.
16033 show : function() {
16035 this.isActive = true;
16036 this.getContainerEl().style.display = "";
16038 this.fireEvent("showEvent", {editor:this});
16039 YAHOO.log("CellEditor shown", "info", this.toString());
16047 block : function() {
16048 this.fireEvent("blockEvent", {editor:this});
16049 YAHOO.log("CellEditor blocked", "info", this.toString());
16053 * Fires unblockEvent
16057 unblock : function() {
16058 this.fireEvent("unblockEvent", {editor:this});
16059 YAHOO.log("CellEditor unblocked", "info", this.toString());
16063 * Saves value of CellEditor and hides UI.
16067 save : function() {
16069 var inputValue = this.getInputValue();
16070 var validValue = inputValue;
16072 // Validate new value
16073 if(this.validator) {
16074 validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this);
16075 if(validValue === undefined ) {
16076 if(this.resetInvalidData) {
16079 this.fireEvent("invalidDataEvent",
16080 {editor:this, oldData:this.value, newData:inputValue});
16081 YAHOO.log("Could not save Cell Editor input due to invalid data " +
16082 lang.dump(inputValue), "warn", this.toString());
16088 var finishSave = function(bSuccess, oNewValue) {
16089 var oOrigValue = oSelf.value;
16091 // Update new value
16092 oSelf.value = oNewValue;
16093 oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue);
16096 oSelf.getContainerEl().style.display = "none";
16097 oSelf.isActive = false;
16098 oSelf.getDataTable()._oCellEditor = null;
16100 oSelf.fireEvent("saveEvent",
16101 {editor:oSelf, oldData:oOrigValue, newData:oSelf.value});
16102 YAHOO.log("Cell Editor input saved", "info", this.toString());
16106 oSelf.fireEvent("revertEvent",
16107 {editor:oSelf, oldData:oOrigValue, newData:oNewValue});
16108 YAHOO.log("Could not save Cell Editor input " +
16109 lang.dump(oNewValue), "warn", oSelf.toString());
16115 if(lang.isFunction(this.asyncSubmitter)) {
16116 this.asyncSubmitter.call(this, finishSave, validValue);
16119 finishSave(true, validValue);
16124 * Cancels CellEditor input and hides UI.
16128 cancel : function() {
16129 if(this.isActive) {
16130 this.getContainerEl().style.display = "none";
16131 this.isActive = false;
16132 this.getDataTable()._oCellEditor = null;
16133 this.fireEvent("cancelEvent", {editor:this});
16134 YAHOO.log("CellEditor canceled", "info", this.toString());
16137 YAHOO.log("Unable to cancel CellEditor", "warn", this.toString());
16142 * Renders form elements.
16144 * @method renderForm
16146 renderForm : function() {
16147 // To be implemented by subclass
16151 * Access to add additional event listeners.
16153 * @method doAfterRender
16155 doAfterRender : function() {
16156 // To be implemented by subclass
16161 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16162 * to save input without them.
16164 * @method handleDisabledBtns
16166 handleDisabledBtns : function() {
16167 // To be implemented by subclass
16171 * Resets CellEditor UI to initial state.
16173 * @method resetForm
16175 resetForm : function() {
16176 // To be implemented by subclass
16180 * Sets focus in CellEditor.
16184 focus : function() {
16185 // To be implemented by subclass
16189 * Retrieves input value from CellEditor.
16191 * @method getInputValue
16193 getInputValue : function() {
16194 // To be implemented by subclass
16199 lang.augmentProto(BCE, util.EventProvider);
16202 /////////////////////////////////////////////////////////////////////////////
16206 /////////////////////////////////////////////////////////////////////////////
16209 * Fired when a CellEditor is shown.
16212 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16216 * Fired when a CellEditor has a keydown.
16218 * @event keydownEvent
16219 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16220 * @param oArgs.event {HTMLEvent} The event object.
16224 * Fired when a CellEditor input is reverted due to invalid data.
16226 * @event invalidDataEvent
16227 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16228 * @param oArgs.newData {Object} New data value from form input field.
16229 * @param oArgs.oldData {Object} Old data value.
16233 * Fired when a CellEditor input is reverted due to asyncSubmitter failure.
16235 * @event revertEvent
16236 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16237 * @param oArgs.newData {Object} New data value from form input field.
16238 * @param oArgs.oldData {Object} Old data value.
16242 * Fired when a CellEditor input is saved.
16245 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16246 * @param oArgs.newData {Object} New data value from form input field.
16247 * @param oArgs.oldData {Object} Old data value.
16251 * Fired when a CellEditor input is canceled.
16253 * @event cancelEvent
16254 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16258 * Fired when a CellEditor has a blur event.
16261 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
16277 /****************************************************************************/
16278 /****************************************************************************/
16279 /****************************************************************************/
16282 * The CheckboxCellEditor class provides functionality for inline editing
16283 * DataTable cell data with checkboxes.
16285 * @namespace YAHOO.widget
16286 * @class CheckboxCellEditor
16287 * @extends YAHOO.widget.BaseCellEditor
16289 * @param oConfigs {Object} (Optional) Object literal of configs.
16291 widget.CheckboxCellEditor = function(oConfigs) {
16292 this._sId = "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16293 widget.CheckboxCellEditor.superclass.constructor.call(this, "checkbox", oConfigs);
16296 // CheckboxCellEditor extends BaseCellEditor
16297 lang.extend(widget.CheckboxCellEditor, BCE, {
16299 /////////////////////////////////////////////////////////////////////////////
16301 // CheckboxCellEditor public properties
16303 /////////////////////////////////////////////////////////////////////////////
16305 * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"])
16306 * or a an array of objects (e.g., [{label:"red", value:"#FF0000"},
16307 * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]).
16309 * @property checkboxOptions
16310 * @type String[] | Object[]
16312 checkboxOptions : null,
16315 * Reference to the checkbox elements.
16317 * @property checkboxes
16318 * @type HTMLElement[]
16323 * Array of checked values
16330 /////////////////////////////////////////////////////////////////////////////
16332 // CheckboxCellEditor public methods
16334 /////////////////////////////////////////////////////////////////////////////
16337 * Render a form with input(s) type=checkbox.
16339 * @method renderForm
16341 renderForm : function() {
16342 if(lang.isArray(this.checkboxOptions)) {
16343 var checkboxOption, checkboxValue, checkboxId, elLabel, j, len;
16345 // Create the checkbox buttons in an IE-friendly way...
16346 for(j=0,len=this.checkboxOptions.length; j<len; j++) {
16347 checkboxOption = this.checkboxOptions[j];
16348 checkboxValue = lang.isValue(checkboxOption.value) ?
16349 checkboxOption.value : checkboxOption;
16351 checkboxId = this.getId() + "-chk" + j;
16352 this.getContainerEl().innerHTML += "<input type=\"checkbox\"" +
16353 " id=\"" + checkboxId + "\"" + // Needed for label
16354 " value=\"" + checkboxValue + "\" />";
16356 // Create the labels in an IE-friendly way
16357 elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16358 elLabel.htmlFor = checkboxId;
16359 elLabel.innerHTML = lang.isValue(checkboxOption.label) ?
16360 checkboxOption.label : checkboxOption;
16363 // Store the reference to the checkbox elements
16364 var allCheckboxes = [];
16365 for(j=0; j<len; j++) {
16366 allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2];
16368 this.checkboxes = allCheckboxes;
16370 if(this.disableBtns) {
16371 this.handleDisabledBtns();
16375 YAHOO.log("Could not find checkboxOptions", "error", this.toString());
16380 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16381 * to save input without them.
16383 * @method handleDisabledBtns
16385 handleDisabledBtns : function() {
16386 Ev.addListener(this.getContainerEl(), "click", function(v){
16387 if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16395 * Resets CheckboxCellEditor UI to initial state.
16397 * @method resetForm
16399 resetForm : function() {
16400 // Normalize to array
16401 var originalValues = lang.isArray(this.value) ? this.value : [this.value];
16403 // Match checks to value
16404 for(var i=0, j=this.checkboxes.length; i<j; i++) {
16405 this.checkboxes[i].checked = false;
16406 for(var k=0, len=originalValues.length; k<len; k++) {
16407 if(this.checkboxes[i].value === originalValues[k]) {
16408 this.checkboxes[i].checked = true;
16415 * Sets focus in CheckboxCellEditor.
16419 focus : function() {
16420 this.checkboxes[0].focus();
16424 * Retrieves input value from CheckboxCellEditor.
16426 * @method getInputValue
16428 getInputValue : function() {
16429 var checkedValues = [];
16430 for(var i=0, j=this.checkboxes.length; i<j; i++) {
16431 if(this.checkboxes[i].checked) {
16432 checkedValues[checkedValues.length] = this.checkboxes[i].value;
16435 return checkedValues;
16440 // Copy static members to CheckboxCellEditor class
16441 lang.augmentObject(widget.CheckboxCellEditor, BCE);
16450 /****************************************************************************/
16451 /****************************************************************************/
16452 /****************************************************************************/
16455 * The DataCellEditor class provides functionality for inline editing
16456 * DataTable cell data with a YUI Calendar.
16458 * @namespace YAHOO.widget
16459 * @class DateCellEditor
16460 * @extends YAHOO.widget.BaseCellEditor
16462 * @param oConfigs {Object} (Optional) Object literal of configs.
16464 widget.DateCellEditor = function(oConfigs) {
16465 this._sId = "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16466 widget.DateCellEditor.superclass.constructor.call(this, "date", oConfigs);
16469 // CheckboxCellEditor extends BaseCellEditor
16470 lang.extend(widget.DateCellEditor, BCE, {
16472 /////////////////////////////////////////////////////////////////////////////
16474 // DateCellEditor public properties
16476 /////////////////////////////////////////////////////////////////////////////
16478 * Reference to Calendar instance.
16480 * @property calendar
16481 * @type YAHOO.widget.Calendar
16486 * Configs for the calendar instance, to be passed to Calendar constructor.
16488 * @property calendarOptions
16491 calendarOptions : null,
16496 * @property defaultValue
16498 * @default new Date()
16500 defaultValue : new Date(),
16503 /////////////////////////////////////////////////////////////////////////////
16505 // DateCellEditor public methods
16507 /////////////////////////////////////////////////////////////////////////////
16510 * Render a Calendar.
16512 * @method renderForm
16514 renderForm : function() {
16516 if(YAHOO.widget.Calendar) {
16517 var calContainer = this.getContainerEl().appendChild(document.createElement("div"));
16518 calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor
16520 new YAHOO.widget.Calendar(this.getId() + "-date",
16521 calContainer.id, this.calendarOptions);
16523 calContainer.style.cssFloat = "none";
16526 var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div"));
16527 calFloatClearer.style.clear = "both";
16530 this.calendar = calendar;
16532 if(this.disableBtns) {
16533 this.handleDisabledBtns();
16537 YAHOO.log("Could not find YUI Calendar", "error", this.toString());
16543 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16544 * to save input without them.
16546 * @method handleDisabledBtns
16548 handleDisabledBtns : function() {
16549 this.calendar.selectEvent.subscribe(function(v){
16556 * Resets DateCellEditor UI to initial state.
16558 * @method resetForm
16560 resetForm : function() {
16561 var value = this.value;
16562 var selectedValue = (value.getMonth()+1)+"/"+value.getDate()+"/"+value.getFullYear();
16563 this.calendar.cfg.setProperty("selected",selectedValue,false);
16564 this.calendar.render();
16568 * Sets focus in DateCellEditor.
16572 focus : function() {
16573 // To be impmlemented by subclass
16577 * Retrieves input value from DateCellEditor.
16579 * @method getInputValue
16581 getInputValue : function() {
16582 return this.calendar.getSelectedDates()[0];
16587 // Copy static members to DateCellEditor class
16588 lang.augmentObject(widget.DateCellEditor, BCE);
16598 /****************************************************************************/
16599 /****************************************************************************/
16600 /****************************************************************************/
16603 * The DropdownCellEditor class provides functionality for inline editing
16604 * DataTable cell data a SELECT element.
16606 * @namespace YAHOO.widget
16607 * @class DropdownCellEditor
16608 * @extends YAHOO.widget.BaseCellEditor
16610 * @param oConfigs {Object} (Optional) Object literal of configs.
16612 widget.DropdownCellEditor = function(oConfigs) {
16613 this._sId = "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16614 widget.DropdownCellEditor.superclass.constructor.call(this, "dropdown", oConfigs);
16617 // DropdownCellEditor extends BaseCellEditor
16618 lang.extend(widget.DropdownCellEditor, BCE, {
16620 /////////////////////////////////////////////////////////////////////////////
16622 // DropdownCellEditor public properties
16624 /////////////////////////////////////////////////////////////////////////////
16626 * Array of dropdown values. Can either be a simple array (e.g.,
16627 * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g.,
16628 * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
16629 * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]).
16631 * @property dropdownOptions
16632 * @type String[] | Object[]
16634 dropdownOptions : null,
16637 * Reference to Dropdown element.
16639 * @property dropdown
16640 * @type HTMLElement
16645 /////////////////////////////////////////////////////////////////////////////
16647 // DropdownCellEditor public methods
16649 /////////////////////////////////////////////////////////////////////////////
16652 * Render a form with select element.
16654 * @method renderForm
16656 renderForm : function() {
16657 var elDropdown = this.getContainerEl().appendChild(document.createElement("select"));
16658 elDropdown.style.zoom = 1;
16659 this.dropdown = elDropdown;
16661 if(lang.isArray(this.dropdownOptions)) {
16662 var dropdownOption, elOption;
16663 for(var i=0, j=this.dropdownOptions.length; i<j; i++) {
16664 dropdownOption = this.dropdownOptions[i];
16665 elOption = document.createElement("option");
16666 elOption.value = (lang.isValue(dropdownOption.value)) ?
16667 dropdownOption.value : dropdownOption;
16668 elOption.innerHTML = (lang.isValue(dropdownOption.label)) ?
16669 dropdownOption.label : dropdownOption;
16670 elOption = elDropdown.appendChild(elOption);
16673 if(this.disableBtns) {
16674 this.handleDisabledBtns();
16680 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16681 * to save input without them.
16683 * @method handleDisabledBtns
16685 handleDisabledBtns : function() {
16686 Ev.addListener(this.dropdown, "change", function(v){
16693 * Resets DropdownCellEditor UI to initial state.
16695 * @method resetForm
16697 resetForm : function() {
16698 for(var i=0, j=this.dropdown.options.length; i<j; i++) {
16699 if(this.value === this.dropdown.options[i].value) {
16700 this.dropdown.options[i].selected = true;
16706 * Sets focus in DropdownCellEditor.
16710 focus : function() {
16711 this.getDataTable()._focusEl(this.dropdown);
16715 * Retrieves input value from DropdownCellEditor.
16717 * @method getInputValue
16719 getInputValue : function() {
16720 return this.dropdown.options[this.dropdown.options.selectedIndex].value;
16725 // Copy static members to DropdownCellEditor class
16726 lang.augmentObject(widget.DropdownCellEditor, BCE);
16733 /****************************************************************************/
16734 /****************************************************************************/
16735 /****************************************************************************/
16738 * The RadioCellEditor class provides functionality for inline editing
16739 * DataTable cell data with radio buttons.
16741 * @namespace YAHOO.widget
16742 * @class RadioCellEditor
16743 * @extends YAHOO.widget.BaseCellEditor
16745 * @param oConfigs {Object} (Optional) Object literal of configs.
16747 widget.RadioCellEditor = function(oConfigs) {
16748 this._sId = "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16749 widget.RadioCellEditor.superclass.constructor.call(this, "radio", oConfigs);
16752 // RadioCellEditor extends BaseCellEditor
16753 lang.extend(widget.RadioCellEditor, BCE, {
16755 /////////////////////////////////////////////////////////////////////////////
16757 // RadioCellEditor public properties
16759 /////////////////////////////////////////////////////////////////////////////
16761 * Reference to radio elements.
16764 * @type HTMLElement[]
16769 * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"])
16770 * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1},
16771 * {label:"maybe", value:0}]).
16773 * @property radioOptions
16774 * @type String[] | Object[]
16776 radioOptions : null,
16778 /////////////////////////////////////////////////////////////////////////////
16780 // RadioCellEditor public methods
16782 /////////////////////////////////////////////////////////////////////////////
16785 * Render a form with input(s) type=radio.
16787 * @method renderForm
16789 renderForm : function() {
16790 if(lang.isArray(this.radioOptions)) {
16791 var radioOption, radioValue, radioId, elLabel;
16793 // Create the radio buttons in an IE-friendly way
16794 for(var i=0, len=this.radioOptions.length; i<len; i++) {
16795 radioOption = this.radioOptions[i];
16796 radioValue = lang.isValue(radioOption.value) ?
16797 radioOption.value : radioOption;
16798 radioId = this.getId() + "-radio" + i;
16799 this.getContainerEl().innerHTML += "<input type=\"radio\"" +
16800 " name=\"" + this.getId() + "\"" +
16801 " value=\"" + radioValue + "\"" +
16802 " id=\"" + radioId + "\" />"; // Needed for label
16804 // Create the labels in an IE-friendly way
16805 elLabel = this.getContainerEl().appendChild(document.createElement("label"));
16806 elLabel.htmlFor = radioId;
16807 elLabel.innerHTML = (lang.isValue(radioOption.label)) ?
16808 radioOption.label : radioOption;
16811 // Store the reference to the checkbox elements
16812 var allRadios = [],
16814 for(var j=0; j<len; j++) {
16815 elRadio = this.getContainerEl().childNodes[j*2];
16816 allRadios[allRadios.length] = elRadio;
16818 this.radios = allRadios;
16820 if(this.disableBtns) {
16821 this.handleDisabledBtns();
16825 YAHOO.log("Could not find radioOptions", "error", this.toString());
16830 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16831 * to save input without them.
16833 * @method handleDisabledBtns
16835 handleDisabledBtns : function() {
16836 Ev.addListener(this.getContainerEl(), "click", function(v){
16837 if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
16845 * Resets RadioCellEditor UI to initial state.
16847 * @method resetForm
16849 resetForm : function() {
16850 for(var i=0, j=this.radios.length; i<j; i++) {
16851 var elRadio = this.radios[i];
16852 if(this.value === elRadio.value) {
16853 elRadio.checked = true;
16860 * Sets focus in RadioCellEditor.
16864 focus : function() {
16865 for(var i=0, j=this.radios.length; i<j; i++) {
16866 if(this.radios[i].checked) {
16867 this.radios[i].focus();
16874 * Retrieves input value from RadioCellEditor.
16876 * @method getInputValue
16878 getInputValue : function() {
16879 for(var i=0, j=this.radios.length; i<j; i++) {
16880 if(this.radios[i].checked) {
16881 return this.radios[i].value;
16888 // Copy static members to RadioCellEditor class
16889 lang.augmentObject(widget.RadioCellEditor, BCE);
16896 /****************************************************************************/
16897 /****************************************************************************/
16898 /****************************************************************************/
16901 * The TextareaCellEditor class provides functionality for inline editing
16902 * DataTable cell data with a TEXTAREA element.
16904 * @namespace YAHOO.widget
16905 * @class TextareaCellEditor
16906 * @extends YAHOO.widget.BaseCellEditor
16908 * @param oConfigs {Object} (Optional) Object literal of configs.
16910 widget.TextareaCellEditor = function(oConfigs) {
16911 this._sId = "yui-textareaceditor" + YAHOO.widget.BaseCellEditor._nCount++;
16912 widget.TextareaCellEditor.superclass.constructor.call(this, "textarea", oConfigs);
16915 // TextareaCellEditor extends BaseCellEditor
16916 lang.extend(widget.TextareaCellEditor, BCE, {
16918 /////////////////////////////////////////////////////////////////////////////
16920 // TextareaCellEditor public properties
16922 /////////////////////////////////////////////////////////////////////////////
16924 * Reference to textarea element.
16926 * @property textarea
16927 * @type HTMLElement
16932 /////////////////////////////////////////////////////////////////////////////
16934 // TextareaCellEditor public methods
16936 /////////////////////////////////////////////////////////////////////////////
16939 * Render a form with textarea.
16941 * @method renderForm
16943 renderForm : function() {
16944 var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea"));
16945 this.textarea = elTextarea;
16947 if(this.disableBtns) {
16948 this.handleDisabledBtns();
16953 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
16954 * to save input without them.
16956 * @method handleDisabledBtns
16958 handleDisabledBtns : function() {
16959 Ev.addListener(this.textarea, "blur", function(v){
16966 * Moves TextareaCellEditor UI to a cell.
16970 move : function() {
16971 this.textarea.style.width = this.getTdEl().offsetWidth + "px";
16972 this.textarea.style.height = "3em";
16973 YAHOO.widget.TextareaCellEditor.superclass.move.call(this);
16977 * Resets TextareaCellEditor UI to initial state.
16979 * @method resetForm
16981 resetForm : function() {
16982 this.textarea.value = this.value;
16986 * Sets focus in TextareaCellEditor.
16990 focus : function() {
16991 // Bug 2303181, Bug 2263600
16992 this.getDataTable()._focusEl(this.textarea);
16993 this.textarea.select();
16997 * Retrieves input value from TextareaCellEditor.
16999 * @method getInputValue
17001 getInputValue : function() {
17002 return this.textarea.value;
17007 // Copy static members to TextareaCellEditor class
17008 lang.augmentObject(widget.TextareaCellEditor, BCE);
17018 /****************************************************************************/
17019 /****************************************************************************/
17020 /****************************************************************************/
17023 * The TextboxCellEditor class provides functionality for inline editing
17024 * DataTable cell data with an INPUT TYPE=TEXT element.
17026 * @namespace YAHOO.widget
17027 * @class TextboxCellEditor
17028 * @extends YAHOO.widget.BaseCellEditor
17030 * @param oConfigs {Object} (Optional) Object literal of configs.
17032 widget.TextboxCellEditor = function(oConfigs) {
17033 this._sId = "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
17034 widget.TextboxCellEditor.superclass.constructor.call(this, "textbox", oConfigs);
17037 // TextboxCellEditor extends BaseCellEditor
17038 lang.extend(widget.TextboxCellEditor, BCE, {
17040 /////////////////////////////////////////////////////////////////////////////
17042 // TextboxCellEditor public properties
17044 /////////////////////////////////////////////////////////////////////////////
17046 * Reference to the textbox element.
17048 * @property textbox
17052 /////////////////////////////////////////////////////////////////////////////
17054 // TextboxCellEditor public methods
17056 /////////////////////////////////////////////////////////////////////////////
17059 * Render a form with input type=text.
17061 * @method renderForm
17063 renderForm : function() {
17065 // Bug 1802582: SF3/Mac needs a form element wrapping the input
17066 if(ua.webkit>420) {
17067 elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));
17070 elTextbox = this.getContainerEl().appendChild(document.createElement("input"));
17072 elTextbox.type = "text";
17073 this.textbox = elTextbox;
17075 // Save on enter by default
17076 // Bug: 1802582 Set up a listener on each textbox to track on keypress
17077 // since SF/OP can't preventDefault on keydown
17078 Ev.addListener(elTextbox, "keypress", function(v){
17079 if((v.keyCode === 13)) {
17080 // Prevent form submit
17081 YAHOO.util.Event.preventDefault(v);
17086 if(this.disableBtns) {
17087 // By default this is no-op since enter saves by default
17088 this.handleDisabledBtns();
17093 * Moves TextboxCellEditor UI to a cell.
17097 move : function() {
17098 this.textbox.style.width = this.getTdEl().offsetWidth + "px";
17099 widget.TextboxCellEditor.superclass.move.call(this);
17103 * Resets TextboxCellEditor UI to initial state.
17105 * @method resetForm
17107 resetForm : function() {
17108 this.textbox.value = lang.isValue(this.value) ? this.value.toString() : "";
17112 * Sets focus in TextboxCellEditor.
17116 focus : function() {
17117 // Bug 2303181, Bug 2263600
17118 this.getDataTable()._focusEl(this.textbox);
17119 this.textbox.select();
17123 * Returns new value for TextboxCellEditor.
17125 * @method getInputValue
17127 getInputValue : function() {
17128 return this.textbox.value;
17133 // Copy static members to TextboxCellEditor class
17134 lang.augmentObject(widget.TextboxCellEditor, BCE);
17142 /////////////////////////////////////////////////////////////////////////////
17144 // DataTable extension
17146 /////////////////////////////////////////////////////////////////////////////
17149 * CellEditor subclasses.
17150 * @property DataTable.Editors
17155 checkbox : widget.CheckboxCellEditor,
17156 "date" : widget.DateCellEditor,
17157 dropdown : widget.DropdownCellEditor,
17158 radio : widget.RadioCellEditor,
17159 textarea : widget.TextareaCellEditor,
17160 textbox : widget.TextboxCellEditor
17163 /****************************************************************************/
17164 /****************************************************************************/
17165 /****************************************************************************/
17168 * Factory class for instantiating a BaseCellEditor subclass.
17170 * @namespace YAHOO.widget
17171 * @class CellEditor
17172 * @extends YAHOO.widget.BaseCellEditor
17174 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
17175 * @param oConfigs {Object} (Optional) Object literal of configs.
17177 widget.CellEditor = function(sType, oConfigs) {
17178 // Point to one of the subclasses
17179 if(sType && DT.Editors[sType]) {
17180 lang.augmentObject(BCE, DT.Editors[sType]);
17181 return new DT.Editors[sType](oConfigs);
17184 return new BCE(null, oConfigs);
17188 var CE = widget.CellEditor;
17190 // Copy static members to CellEditor class
17191 lang.augmentObject(CE, BCE);
17196 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.7.0", build: "1799"});