[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 YUI.add('yui2-datatable', function(Y) { 2 var YAHOO = Y.YUI2; 3 /* 4 Copyright (c) 2011, Yahoo! Inc. All rights reserved. 5 Code licensed under the BSD License: 6 http://developer.yahoo.com/yui/license.html 7 version: 2.9.0 8 */ 9 /** 10 * 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: 11 * <ul> 12 * <li><code>method</code> - {Function} REQUIRED the callback function.</li> 13 * <li><code>scope</code> - {Object} the scope from which to execute the callback. Default is the global window scope.</li> 14 * <li><code>argument</code> - {Array} parameters to be passed to method as individual arguments.</li> 15 * <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> 16 * <li><code>until</code> - {Function} boolean function executed before each iteration. Return true to indicate completion and proceed to the next callback.</li> 17 * <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 * </ul> 19 * 20 * @namespace YAHOO.util 21 * @class Chain 22 * @constructor 23 * @param callback* {Function|Object} Any number of callbacks to initialize the queue 24 */ 25 YAHOO.util.Chain = function () { 26 /** 27 * The callback queue 28 * @property q 29 * @type {Array} 30 * @private 31 */ 32 this.q = [].slice.call(arguments); 33 34 /** 35 * Event fired when the callback queue is emptied via execution (not via 36 * a call to chain.stop(). 37 * @event end 38 */ 39 this.createEvent('end'); 40 }; 41 42 YAHOO.util.Chain.prototype = { 43 /** 44 * 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. 45 * @property id 46 * @type {number} 47 * @private 48 */ 49 id : 0, 50 51 /** 52 * Begin executing the chain, or resume execution from the last paused position. 53 * @method run 54 * @return {Chain} the Chain instance 55 */ 56 run : function () { 57 // Grab the first callback in the queue 58 var c = this.q[0], 59 fn; 60 61 // If there is no callback in the queue or the Chain is currently 62 // in an execution mode, return 63 if (!c) { 64 this.fireEvent('end'); 65 return this; 66 } else if (this.id) { 67 return this; 68 } 69 70 fn = c.method || c; 71 72 if (typeof fn === 'function') { 73 var o = c.scope || {}, 74 args = c.argument || [], 75 ms = c.timeout || 0, 76 me = this; 77 78 if (!(args instanceof Array)) { 79 args = [args]; 80 } 81 82 // Execute immediately if the callback timeout is negative. 83 if (ms < 0) { 84 this.id = ms; 85 if (c.until) { 86 for (;!c.until();) { 87 // Execute the callback from scope, with argument 88 fn.apply(o,args); 89 } 90 } else if (c.iterations) { 91 for (;c.iterations-- > 0;) { 92 fn.apply(o,args); 93 } 94 } else { 95 fn.apply(o,args); 96 } 97 this.q.shift(); 98 this.id = 0; 99 return this.run(); 100 } else { 101 // If the until condition is set, check if we're done 102 if (c.until) { 103 if (c.until()) { 104 // Shift this callback from the queue and execute the next 105 // callback 106 this.q.shift(); 107 return this.run(); 108 } 109 // Otherwise if either iterations is not set or we're 110 // executing the last iteration, shift callback from the queue 111 } else if (!c.iterations || !--c.iterations) { 112 this.q.shift(); 113 } 114 115 // Otherwise set to execute after the configured timeout 116 this.id = setTimeout(function () { 117 // Execute the callback from scope, with argument 118 fn.apply(o,args); 119 // Check if the Chain was not paused from inside the callback 120 if (me.id) { 121 // Indicate ready to run state 122 me.id = 0; 123 // Start the fun all over again 124 me.run(); 125 } 126 },ms); 127 } 128 } 129 130 return this; 131 }, 132 133 /** 134 * Add a callback to the end of the queue 135 * @method add 136 * @param c {Function|Object} the callback function ref or object literal 137 * @return {Chain} the Chain instance 138 */ 139 add : function (c) { 140 this.q.push(c); 141 return this; 142 }, 143 144 /** 145 * Pause the execution of the Chain after the current execution of the 146 * current callback completes. If called interstitially, clears the 147 * timeout for the pending callback. Paused Chains can be restarted with 148 * chain.run() 149 * @method pause 150 * @return {Chain} the Chain instance 151 */ 152 pause: function () { 153 // Conditional added for Caja compatibility 154 if (this.id > 0) { 155 clearTimeout(this.id); 156 } 157 this.id = 0; 158 return this; 159 }, 160 161 /** 162 * Stop and clear the Chain's queue after the current execution of the 163 * current callback completes. 164 * @method stop 165 * @return {Chain} the Chain instance 166 */ 167 stop : function () { 168 this.pause(); 169 this.q = []; 170 return this; 171 } 172 }; 173 YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider); 174 175 /** 176 * Augments the Event Utility with a <code>delegate</code> method that 177 * facilitates easy creation of delegated event listeners. (Note: Using CSS 178 * selectors as the filtering criteria for delegated event listeners requires 179 * inclusion of the Selector Utility.) 180 * 181 * @module event-delegate 182 * @title Event Utility Event Delegation Module 183 * @namespace YAHOO.util 184 * @requires event 185 */ 186 187 (function () { 188 189 var Event = YAHOO.util.Event, 190 Lang = YAHOO.lang, 191 delegates = [], 192 193 194 getMatch = function(el, selector, container) { 195 196 var returnVal; 197 198 if (!el || el === container) { 199 returnVal = false; 200 } 201 else { 202 returnVal = YAHOO.util.Selector.test(el, selector) ? el: getMatch(el.parentNode, selector, container); 203 } 204 205 return returnVal; 206 207 }; 208 209 210 Lang.augmentObject(Event, { 211 212 /** 213 * Creates a delegate function used to call event listeners specified 214 * via the <code>YAHOO.util.Event.delegate</code> method. 215 * 216 * @method _createDelegate 217 * 218 * @param {Function} fn The method (event listener) to call. 219 * @param {Function|string} filter Function or CSS selector used to 220 * determine for what element(s) the event listener should be called. 221 * @param {Object} obj An arbitrary object that will be 222 * passed as a parameter to the listener. 223 * @param {Boolean|object} overrideContext If true, the value of the 224 * obj parameter becomes the execution context 225 * of the listener. If an object, this object 226 * becomes the execution context. 227 * @return {Function} Function that will call the event listener 228 * specified by the <code>YAHOO.util.Event.delegate</code> method. 229 * @private 230 * @for Event 231 * @static 232 */ 233 _createDelegate: function (fn, filter, obj, overrideContext) { 234 235 return function (event) { 236 237 var container = this, 238 target = Event.getTarget(event), 239 selector = filter, 240 241 // The user might have specified the document object 242 // as the delegation container, in which case it is not 243 // nessary to scope the provided CSS selector(s) to the 244 // delegation container 245 bDocument = (container.nodeType === 9), 246 247 matchedEl, 248 context, 249 sID, 250 sIDSelector; 251 252 253 if (Lang.isFunction(filter)) { 254 matchedEl = filter(target); 255 } 256 else if (Lang.isString(filter)) { 257 258 if (!bDocument) { 259 260 sID = container.id; 261 262 if (!sID) { 263 sID = Event.generateId(container); 264 } 265 266 // Scope all selectors to the container 267 sIDSelector = ("#" + sID + " "); 268 selector = (sIDSelector + filter).replace(/,/gi, ("," + sIDSelector)); 269 270 } 271 272 273 if (YAHOO.util.Selector.test(target, selector)) { 274 matchedEl = target; 275 } 276 else if (YAHOO.util.Selector.test(target, ((selector.replace(/,/gi, " *,")) + " *"))) { 277 278 // The target is a descendant of an element matching 279 // the selector, so crawl up to find the ancestor that 280 // matches the selector 281 282 matchedEl = getMatch(target, selector, container); 283 284 } 285 286 } 287 288 289 if (matchedEl) { 290 291 // The default context for delegated listeners is the 292 // element that matched the filter. 293 294 context = matchedEl; 295 296 if (overrideContext) { 297 if (overrideContext === true) { 298 context = obj; 299 } else { 300 context = overrideContext; 301 } 302 } 303 304 // Call the listener passing in the container and the 305 // element that matched the filter in case the user 306 // needs those. 307 308 return fn.call(context, event, matchedEl, container, obj); 309 310 } 311 312 }; 313 314 }, 315 316 317 /** 318 * Appends a delegated event listener. Delegated event listeners 319 * receive three arguments by default: the DOM event, the element 320 * specified by the filtering function or CSS selector, and the 321 * container element (the element to which the event listener is 322 * bound). (Note: Using the delegate method requires the event-delegate 323 * module. Using CSS selectors as the filtering criteria for delegated 324 * event listeners requires inclusion of the Selector Utility.) 325 * 326 * @method delegate 327 * 328 * @param {String|HTMLElement|Array|NodeList} container An id, an element 329 * reference, or a collection of ids and/or elements to assign the 330 * listener to. 331 * @param {String} type The type of event listener to append 332 * @param {Function} fn The method the event invokes 333 * @param {Function|string} filter Function or CSS selector used to 334 * determine for what element(s) the event listener should be called. 335 * When a function is specified, the function should return an 336 * HTML element. Using a CSS Selector requires the inclusion of the 337 * CSS Selector Utility. 338 * @param {Object} obj An arbitrary object that will be 339 * passed as a parameter to the listener 340 * @param {Boolean|object} overrideContext If true, the value of the obj parameter becomes 341 * the execution context of the listener. If an 342 * object, this object becomes the execution 343 * context. 344 * @return {Boolean} Returns true if the action was successful or defered, 345 * false if one or more of the elements 346 * could not have the listener attached, 347 * or if the operation throws an exception. 348 * @static 349 * @for Event 350 */ 351 delegate: function (container, type, fn, filter, obj, overrideContext) { 352 353 var sType = type, 354 fnMouseDelegate, 355 fnDelegate; 356 357 358 if (Lang.isString(filter) && !YAHOO.util.Selector) { 359 YAHOO.log("Using a CSS selector to define the filtering criteria for a delegated listener requires the Selector Utility.", "error", "Event"); 360 return false; 361 } 362 363 364 if (type == "mouseenter" || type == "mouseleave") { 365 366 if (!Event._createMouseDelegate) { 367 YAHOO.log("Delegating a " + type + " event requires the event-mouseenter module.", "error", "Event"); 368 return false; 369 } 370 371 // Look up the real event--either mouseover or mouseout 372 sType = Event._getType(type); 373 374 fnMouseDelegate = Event._createMouseDelegate(fn, obj, overrideContext); 375 376 fnDelegate = Event._createDelegate(function (event, matchedEl, container) { 377 378 return fnMouseDelegate.call(matchedEl, event, container); 379 380 }, filter, obj, overrideContext); 381 382 } 383 else { 384 385 fnDelegate = Event._createDelegate(fn, filter, obj, overrideContext); 386 387 } 388 389 delegates.push([container, sType, fn, fnDelegate]); 390 391 return Event.on(container, sType, fnDelegate); 392 393 }, 394 395 396 /** 397 * Removes a delegated event listener. 398 * 399 * @method removeDelegate 400 * 401 * @param {String|HTMLElement|Array|NodeList} container An id, an element 402 * reference, or a collection of ids and/or elements to remove 403 * the listener from. 404 * @param {String} type The type of event to remove. 405 * @param {Function} fn The method the event invokes. If fn is 406 * undefined, then all event listeners for the type of event are 407 * removed. 408 * @return {boolean} Returns true if the unbind was successful, false 409 * otherwise. 410 * @static 411 * @for Event 412 */ 413 removeDelegate: function (container, type, fn) { 414 415 var sType = type, 416 returnVal = false, 417 index, 418 cacheItem; 419 420 // Look up the real event--either mouseover or mouseout 421 if (type == "mouseenter" || type == "mouseleave") { 422 sType = Event._getType(type); 423 } 424 425 index = Event._getCacheIndex(delegates, container, sType, fn); 426 427 if (index >= 0) { 428 cacheItem = delegates[index]; 429 } 430 431 432 if (container && cacheItem) { 433 434 returnVal = Event.removeListener(cacheItem[0], cacheItem[1], cacheItem[3]); 435 436 if (returnVal) { 437 delete delegates[index][2]; 438 delete delegates[index][3]; 439 delegates.splice(index, 1); 440 } 441 442 } 443 444 return returnVal; 445 446 } 447 448 }); 449 450 }()); 451 452 453 /** 454 * Augments the Event Utility with support for the mouseenter and mouseleave 455 * events: A mouseenter event fires the first time the mouse enters an 456 * element; a mouseleave event first the first time the mouse leaves an 457 * element. 458 * 459 * @module event-mouseenter 460 * @title Event Utility mouseenter and mouseout Module 461 * @namespace YAHOO.util 462 * @requires event 463 */ 464 465 (function () { 466 467 var Event = YAHOO.util.Event, 468 Lang = YAHOO.lang, 469 470 addListener = Event.addListener, 471 removeListener = Event.removeListener, 472 getListeners = Event.getListeners, 473 474 delegates = [], 475 476 specialTypes = { 477 mouseenter: "mouseover", 478 mouseleave: "mouseout" 479 }, 480 481 remove = function(el, type, fn) { 482 483 var index = Event._getCacheIndex(delegates, el, type, fn), 484 cacheItem, 485 returnVal; 486 487 if (index >= 0) { 488 cacheItem = delegates[index]; 489 } 490 491 if (el && cacheItem) { 492 493 // removeListener will translate the value of type 494 returnVal = removeListener.call(Event, cacheItem[0], type, cacheItem[3]); 495 496 if (returnVal) { 497 delete delegates[index][2]; 498 delete delegates[index][3]; 499 delegates.splice(index, 1); 500 } 501 502 } 503 504 return returnVal; 505 506 }; 507 508 509 Lang.augmentObject(Event._specialTypes, specialTypes); 510 511 Lang.augmentObject(Event, { 512 513 /** 514 * Creates a delegate function used to call mouseover and mouseleave 515 * event listeners specified via the 516 * <code>YAHOO.util.Event.addListener</code> 517 * or <code>YAHOO.util.Event.on</code> method. 518 * 519 * @method _createMouseDelegate 520 * 521 * @param {Function} fn The method (event listener) to call 522 * @param {Object} obj An arbitrary object that will be 523 * passed as a parameter to the listener 524 * @param {Boolean|object} overrideContext If true, the value of the 525 * obj parameter becomes the execution context 526 * of the listener. If an object, this object 527 * becomes the execution context. 528 * @return {Function} Function that will call the event listener 529 * specified by either the <code>YAHOO.util.Event.addListener</code> 530 * or <code>YAHOO.util.Event.on</code> method. 531 * @private 532 * @static 533 * @for Event 534 */ 535 _createMouseDelegate: function (fn, obj, overrideContext) { 536 537 return function (event, container) { 538 539 var el = this, 540 relatedTarget = Event.getRelatedTarget(event), 541 context, 542 args; 543 544 if (el != relatedTarget && !YAHOO.util.Dom.isAncestor(el, relatedTarget)) { 545 546 context = el; 547 548 if (overrideContext) { 549 if (overrideContext === true) { 550 context = obj; 551 } else { 552 context = overrideContext; 553 } 554 } 555 556 // The default args passed back to a mouseenter or 557 // mouseleave listener are: the event, and any object 558 // the user passed when subscribing 559 560 args = [event, obj]; 561 562 // Add the element and delegation container as arguments 563 // when delegating mouseenter and mouseleave 564 565 if (container) { 566 args.splice(1, 0, el, container); 567 } 568 569 return fn.apply(context, args); 570 571 } 572 573 }; 574 575 }, 576 577 addListener: function (el, type, fn, obj, overrideContext) { 578 579 var fnDelegate, 580 returnVal; 581 582 if (specialTypes[type]) { 583 584 fnDelegate = Event._createMouseDelegate(fn, obj, overrideContext); 585 586 fnDelegate.mouseDelegate = true; 587 588 delegates.push([el, type, fn, fnDelegate]); 589 590 // addListener will translate the value of type 591 returnVal = addListener.call(Event, el, type, fnDelegate); 592 593 } 594 else { 595 returnVal = addListener.apply(Event, arguments); 596 } 597 598 return returnVal; 599 600 }, 601 602 removeListener: function (el, type, fn) { 603 604 var returnVal; 605 606 if (specialTypes[type]) { 607 returnVal = remove.apply(Event, arguments); 608 } 609 else { 610 returnVal = removeListener.apply(Event, arguments); 611 } 612 613 return returnVal; 614 615 }, 616 617 getListeners: function (el, type) { 618 619 // If the user specified the type as mouseover or mouseout, 620 // need to filter out those used by mouseenter and mouseleave. 621 // If the user specified the type as mouseenter or mouseleave, 622 // need to filter out the true mouseover and mouseout listeners. 623 624 var listeners = [], 625 elListeners, 626 bMouseOverOrOut = (type === "mouseover" || type === "mouseout"), 627 bMouseDelegate, 628 i, 629 l; 630 631 if (type && (bMouseOverOrOut || specialTypes[type])) { 632 633 elListeners = getListeners.call(Event, el, this._getType(type)); 634 635 if (elListeners) { 636 637 for (i=elListeners.length-1; i>-1; i--) { 638 639 l = elListeners[i]; 640 bMouseDelegate = l.fn.mouseDelegate; 641 642 if ((specialTypes[type] && bMouseDelegate) || (bMouseOverOrOut && !bMouseDelegate)) { 643 listeners.push(l); 644 } 645 646 } 647 648 } 649 650 } 651 else { 652 listeners = getListeners.apply(Event, arguments); 653 } 654 655 return (listeners && listeners.length) ? listeners : null; 656 657 } 658 659 }, true); 660 661 Event.on = Event.addListener; 662 663 }()); 664 YAHOO.register("event-mouseenter", YAHOO.util.Event, {version: "2.9.0", build: "2800"}); 665 666 var Y = YAHOO, 667 Y_DOM = YAHOO.util.Dom, 668 EMPTY_ARRAY = [], 669 Y_UA = Y.env.ua, 670 Y_Lang = Y.lang, 671 Y_DOC = document, 672 Y_DOCUMENT_ELEMENT = Y_DOC.documentElement, 673 674 Y_DOM_inDoc = Y_DOM.inDocument, 675 Y_mix = Y_Lang.augmentObject, 676 Y_guid = Y_DOM.generateId, 677 678 Y_getDoc = function(element) { 679 var doc = Y_DOC; 680 if (element) { 681 doc = (element.nodeType === 9) ? element : // element === document 682 element.ownerDocument || // element === DOM node 683 element.document || // element === window 684 Y_DOC; // default 685 } 686 687 return doc; 688 }, 689 690 Y_Array = function(o, startIdx) { 691 var l, a, start = startIdx || 0; 692 693 // IE errors when trying to slice HTMLElement collections 694 try { 695 return Array.prototype.slice.call(o, start); 696 } catch (e) { 697 a = []; 698 l = o.length; 699 for (; start < l; start++) { 700 a.push(o[start]); 701 } 702 return a; 703 } 704 }, 705 706 Y_DOM_allById = function(id, root) { 707 root = root || Y_DOC; 708 var nodes = [], 709 ret = [], 710 i, 711 node; 712 713 if (root.querySelectorAll) { 714 ret = root.querySelectorAll('[id="' + id + '"]'); 715 } else if (root.all) { 716 nodes = root.all(id); 717 718 if (nodes) { 719 // root.all may return HTMLElement or HTMLCollection. 720 // some elements are also HTMLCollection (FORM, SELECT). 721 if (nodes.nodeName) { 722 if (nodes.id === id) { // avoid false positive on name 723 ret.push(nodes); 724 nodes = EMPTY_ARRAY; // done, no need to filter 725 } else { // prep for filtering 726 nodes = [nodes]; 727 } 728 } 729 730 if (nodes.length) { 731 // filter out matches on node.name 732 // and element.id as reference to element with id === 'id' 733 for (i = 0; node = nodes[i++];) { 734 if (node.id === id || 735 (node.attributes && node.attributes.id && 736 node.attributes.id.value === id)) { 737 ret.push(node); 738 } 739 } 740 } 741 } 742 } else { 743 ret = [Y_getDoc(root).getElementById(id)]; 744 } 745 746 return ret; 747 }; 748 749 /** 750 * The selector-native module provides support for native querySelector 751 * @module dom 752 * @submodule selector-native 753 * @for Selector 754 */ 755 756 /** 757 * Provides support for using CSS selectors to query the DOM 758 * @class Selector 759 * @static 760 * @for Selector 761 */ 762 763 var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition', 764 OWNER_DOCUMENT = 'ownerDocument', 765 766 Selector = { 767 _foundCache: [], 768 769 useNative: true, 770 771 _compare: ('sourceIndex' in Y_DOCUMENT_ELEMENT) ? 772 function(nodeA, nodeB) { 773 var a = nodeA.sourceIndex, 774 b = nodeB.sourceIndex; 775 776 if (a === b) { 777 return 0; 778 } else if (a > b) { 779 return 1; 780 } 781 782 return -1; 783 784 } : (Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION] ? 785 function(nodeA, nodeB) { 786 if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) { 787 return -1; 788 } else { 789 return 1; 790 } 791 } : 792 function(nodeA, nodeB) { 793 var rangeA, rangeB, compare; 794 if (nodeA && nodeB) { 795 rangeA = nodeA[OWNER_DOCUMENT].createRange(); 796 rangeA.setStart(nodeA, 0); 797 rangeB = nodeB[OWNER_DOCUMENT].createRange(); 798 rangeB.setStart(nodeB, 0); 799 compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END 800 } 801 802 return compare; 803 804 }), 805 806 _sort: function(nodes) { 807 if (nodes) { 808 nodes = Y_Array(nodes, 0, true); 809 if (nodes.sort) { 810 nodes.sort(Selector._compare); 811 } 812 } 813 814 return nodes; 815 }, 816 817 _deDupe: function(nodes) { 818 var ret = [], 819 i, node; 820 821 for (i = 0; (node = nodes[i++]);) { 822 if (!node._found) { 823 ret[ret.length] = node; 824 node._found = true; 825 } 826 } 827 828 for (i = 0; (node = ret[i++]);) { 829 node._found = null; 830 node.removeAttribute('_found'); 831 } 832 833 return ret; 834 }, 835 836 /** 837 * Retrieves a set of nodes based on a given CSS selector. 838 * @method query 839 * 840 * @param {string} selector The CSS Selector to test the node against. 841 * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc 842 * @param {Boolean} firstOnly optional Whether or not to return only the first match. 843 * @return {Array} An array of nodes that match the given selector. 844 * @static 845 */ 846 query: function(selector, root, firstOnly, skipNative) { 847 if (typeof root == 'string') { 848 root = Y_DOM.get(root); 849 if (!root) { 850 return (firstOnly) ? null : []; 851 } 852 } else { 853 root = root || Y_DOC; 854 } 855 856 var ret = [], 857 useNative = (Selector.useNative && Y_DOC.querySelector && !skipNative), 858 queries = [[selector, root]], 859 query, 860 result, 861 i, 862 fn = (useNative) ? Selector._nativeQuery : Selector._bruteQuery; 863 864 if (selector && fn) { 865 // split group into seperate queries 866 if (!skipNative && // already done if skipping 867 (!useNative || root.tagName)) { // split native when element scoping is needed 868 queries = Selector._splitQueries(selector, root); 869 } 870 871 for (i = 0; (query = queries[i++]);) { 872 result = fn(query[0], query[1], firstOnly); 873 if (!firstOnly) { // coerce DOM Collection to Array 874 result = Y_Array(result, 0, true); 875 } 876 if (result) { 877 ret = ret.concat(result); 878 } 879 } 880 881 if (queries.length > 1) { // remove dupes and sort by doc order 882 ret = Selector._sort(Selector._deDupe(ret)); 883 } 884 } 885 886 Y.log('query: ' + selector + ' returning: ' + ret.length, 'info', 'Selector'); 887 return (firstOnly) ? (ret[0] || null) : ret; 888 889 }, 890 891 // allows element scoped queries to begin with combinator 892 // e.g. query('> p', document.body) === query('body > p') 893 _splitQueries: function(selector, node) { 894 var groups = selector.split(','), 895 queries = [], 896 prefix = '', 897 i, len; 898 899 if (node) { 900 // enforce for element scoping 901 if (node.tagName) { 902 node.id = node.id || Y_guid(); 903 prefix = '[id="' + node.id + '"] '; 904 } 905 906 for (i = 0, len = groups.length; i < len; ++i) { 907 selector = prefix + groups[i]; 908 queries.push([selector, node]); 909 } 910 } 911 912 return queries; 913 }, 914 915 _nativeQuery: function(selector, root, one) { 916 if (Y_UA.webkit && selector.indexOf(':checked') > -1 && 917 (Selector.pseudos && Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected" 918 return Selector.query(selector, root, one, true); // redo with skipNative true to try brute query 919 } 920 try { 921 //Y.log('trying native query with: ' + selector, 'info', 'selector-native'); 922 return root['querySelector' + (one ? '' : 'All')](selector); 923 } catch(e) { // fallback to brute if available 924 //Y.log('native query error; reverting to brute query with: ' + selector, 'info', 'selector-native'); 925 return Selector.query(selector, root, one, true); // redo with skipNative true 926 } 927 }, 928 929 filter: function(nodes, selector) { 930 var ret = [], 931 i, node; 932 933 if (nodes && selector) { 934 for (i = 0; (node = nodes[i++]);) { 935 if (Selector.test(node, selector)) { 936 ret[ret.length] = node; 937 } 938 } 939 } else { 940 Y.log('invalid filter input (nodes: ' + nodes + 941 ', selector: ' + selector + ')', 'warn', 'Selector'); 942 } 943 944 return ret; 945 }, 946 947 test: function(node, selector, root) { 948 var ret = false, 949 groups = selector.split(','), 950 useFrag = false, 951 parent, 952 item, 953 items, 954 frag, 955 i, j, group; 956 957 if (node && node.tagName) { // only test HTMLElements 958 959 // we need a root if off-doc 960 if (!root && !Y_DOM_inDoc(node)) { 961 parent = node.parentNode; 962 if (parent) { 963 root = parent; 964 } else { // only use frag when no parent to query 965 frag = node[OWNER_DOCUMENT].createDocumentFragment(); 966 frag.appendChild(node); 967 root = frag; 968 useFrag = true; 969 } 970 } 971 root = root || node[OWNER_DOCUMENT]; 972 973 if (!node.id) { 974 node.id = Y_guid(); 975 } 976 for (i = 0; (group = groups[i++]);) { // TODO: off-dom test 977 group += '[id="' + node.id + '"]'; 978 items = Selector.query(group, root); 979 980 for (j = 0; item = items[j++];) { 981 if (item === node) { 982 ret = true; 983 break; 984 } 985 } 986 if (ret) { 987 break; 988 } 989 } 990 991 if (useFrag) { // cleanup 992 frag.removeChild(node); 993 } 994 } 995 996 return ret; 997 } 998 999 }; 1000 1001 YAHOO.util.Selector = Selector; 1002 /** 1003 * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements. 1004 * @module dom 1005 * @submodule selector-css2 1006 * @for Selector 1007 */ 1008 1009 /** 1010 * Provides helper methods for collecting and filtering DOM elements. 1011 */ 1012 1013 var PARENT_NODE = 'parentNode', 1014 TAG_NAME = 'tagName', 1015 ATTRIBUTES = 'attributes', 1016 COMBINATOR = 'combinator', 1017 PSEUDOS = 'pseudos', 1018 1019 SelectorCSS2 = { 1020 _reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move? 1021 SORT_RESULTS: true, 1022 _children: function(node, tag) { 1023 var ret = node.children, 1024 i, 1025 children = [], 1026 childNodes, 1027 child; 1028 1029 if (node.children && tag && node.children.tags) { 1030 children = node.children.tags(tag); 1031 } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children 1032 childNodes = ret || node.childNodes; 1033 ret = []; 1034 for (i = 0; (child = childNodes[i++]);) { 1035 if (child.tagName) { 1036 if (!tag || tag === child.tagName) { 1037 ret.push(child); 1038 } 1039 } 1040 } 1041 } 1042 1043 return ret || []; 1044 }, 1045 1046 _re: { 1047 //attr: /(\[.*\])/g, 1048 attr: /(\[[^\]]*\])/g, 1049 //esc: /\\[:\[][\w\d\]]*/gi, 1050 esc: /\\[:\[\]\(\)#\.\'\>+~"]/gi, 1051 //pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\))*)/i 1052 pseudos: /(\([^\)]*\))/g 1053 }, 1054 1055 /** 1056 * Mapping of shorthand tokens to corresponding attribute selector 1057 * @property shorthand 1058 * @type object 1059 */ 1060 shorthand: { 1061 //'\\#([^\\s\\\\(\\[:]*)': '[id=$1]', 1062 '\\#(-?[_a-z]+[-\\w\\uE000]*)': '[id=$1]', 1063 //'\\#([^\\s\\\.:\\[\\]]*)': '[id=$1]', 1064 //'\\.([^\\s\\\\(\\[:]*)': '[className=$1]' 1065 '\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]' 1066 }, 1067 1068 /** 1069 * List of operators and corresponding boolean functions. 1070 * These functions are passed the attribute and the current node's value of the attribute. 1071 * @property operators 1072 * @type object 1073 */ 1074 operators: { 1075 '': function(node, attr) { return !!node.getAttribute(attr); }, // Just test for existence of attribute 1076 //'': '.+', 1077 //'=': '^{val}$', // equality 1078 '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited 1079 '|=': '^{val}(?:-|$)' // optional hyphen-delimited 1080 }, 1081 1082 pseudos: { 1083 'first-child': function(node) { 1084 return Selector._children(node[PARENT_NODE])[0] === node; 1085 } 1086 }, 1087 1088 _bruteQuery: function(selector, root, firstOnly) { 1089 var ret = [], 1090 nodes = [], 1091 tokens = Selector._tokenize(selector), 1092 token = tokens[tokens.length - 1], 1093 rootDoc = Y_getDoc(root), 1094 child, 1095 id, 1096 className, 1097 tagName; 1098 1099 1100 // if we have an initial ID, set to root when in document 1101 /* 1102 if (tokens[0] && rootDoc === root && 1103 (id = tokens[0].id) && 1104 rootDoc.getElementById(id)) { 1105 root = rootDoc.getElementById(id); 1106 } 1107 */ 1108 1109 if (token) { 1110 // prefilter nodes 1111 id = token.id; 1112 className = token.className; 1113 tagName = token.tagName || '*'; 1114 1115 if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags 1116 // try ID first, unless no root.all && root not in document 1117 // (root.all works off document, but not getElementById) 1118 // TODO: move to allById? 1119 if (id && (root.all || (root.nodeType === 9 || Y_DOM_inDoc(root)))) { 1120 nodes = Y_DOM_allById(id, root); 1121 // try className 1122 } else if (className) { 1123 nodes = root.getElementsByClassName(className); 1124 } else { // default to tagName 1125 nodes = root.getElementsByTagName(tagName); 1126 } 1127 1128 } else { // brute getElementsByTagName('*') 1129 child = root.firstChild; 1130 while (child) { 1131 if (child.tagName) { // only collect HTMLElements 1132 nodes.push(child); 1133 } 1134 child = child.nextSilbing || child.firstChild; 1135 } 1136 } 1137 if (nodes.length) { 1138 ret = Selector._filterNodes(nodes, tokens, firstOnly); 1139 } 1140 } 1141 1142 return ret; 1143 }, 1144 1145 _filterNodes: function(nodes, tokens, firstOnly) { 1146 var i = 0, 1147 j, 1148 len = tokens.length, 1149 n = len - 1, 1150 result = [], 1151 node = nodes[0], 1152 tmpNode = node, 1153 getters = Selector.getters, 1154 operator, 1155 combinator, 1156 token, 1157 path, 1158 pass, 1159 //FUNCTION = 'function', 1160 value, 1161 tests, 1162 test; 1163 1164 //do { 1165 for (i = 0; (tmpNode = node = nodes[i++]);) { 1166 n = len - 1; 1167 path = null; 1168 1169 testLoop: 1170 while (tmpNode && tmpNode.tagName) { 1171 token = tokens[n]; 1172 tests = token.tests; 1173 j = tests.length; 1174 if (j && !pass) { 1175 while ((test = tests[--j])) { 1176 operator = test[1]; 1177 if (getters[test[0]]) { 1178 value = getters[test[0]](tmpNode, test[0]); 1179 } else { 1180 value = tmpNode[test[0]]; 1181 // use getAttribute for non-standard attributes 1182 if (value === undefined && tmpNode.getAttribute) { 1183 value = tmpNode.getAttribute(test[0]); 1184 } 1185 } 1186 1187 if ((operator === '=' && value !== test[2]) || // fast path for equality 1188 (typeof operator !== 'string' && // protect against String.test monkey-patch (Moo) 1189 operator.test && !operator.test(value)) || // regex test 1190 (!operator.test && // protect against RegExp as function (webkit) 1191 typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test 1192 1193 // skip non element nodes or non-matching tags 1194 if ((tmpNode = tmpNode[path])) { 1195 while (tmpNode && 1196 (!tmpNode.tagName || 1197 (token.tagName && token.tagName !== tmpNode.tagName)) 1198 ) { 1199 tmpNode = tmpNode[path]; 1200 } 1201 } 1202 continue testLoop; 1203 } 1204 } 1205 } 1206 1207 n--; // move to next token 1208 // now that we've passed the test, move up the tree by combinator 1209 if (!pass && (combinator = token.combinator)) { 1210 path = combinator.axis; 1211 tmpNode = tmpNode[path]; 1212 1213 // skip non element nodes 1214 while (tmpNode && !tmpNode.tagName) { 1215 tmpNode = tmpNode[path]; 1216 } 1217 1218 if (combinator.direct) { // one pass only 1219 path = null; 1220 } 1221 1222 } else { // success if we made it this far 1223 result.push(node); 1224 if (firstOnly) { 1225 return result; 1226 } 1227 break; 1228 } 1229 } 1230 }// while (tmpNode = node = nodes[++i]); 1231 node = tmpNode = null; 1232 return result; 1233 }, 1234 1235 combinators: { 1236 ' ': { 1237 axis: 'parentNode' 1238 }, 1239 1240 '>': { 1241 axis: 'parentNode', 1242 direct: true 1243 }, 1244 1245 1246 '+': { 1247 axis: 'previousSibling', 1248 direct: true 1249 } 1250 }, 1251 1252 _parsers: [ 1253 { 1254 name: ATTRIBUTES, 1255 //re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i, 1256 re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i, 1257 fn: function(match, token) { 1258 var operator = match[2] || '', 1259 operators = Selector.operators, 1260 escVal = (match[3]) ? match[3].replace(/\\/g, '') : '', 1261 test; 1262 1263 // add prefiltering for ID and CLASS 1264 if ((match[1] === 'id' && operator === '=') || 1265 (match[1] === 'className' && 1266 Y_DOCUMENT_ELEMENT.getElementsByClassName && 1267 (operator === '~=' || operator === '='))) { 1268 token.prefilter = match[1]; 1269 1270 1271 match[3] = escVal; 1272 1273 // escape all but ID for prefilter, which may run through QSA (via Dom.allById) 1274 token[match[1]] = (match[1] === 'id') ? match[3] : escVal; 1275 1276 } 1277 1278 // add tests 1279 if (operator in operators) { 1280 test = operators[operator]; 1281 if (typeof test === 'string') { 1282 match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1'); 1283 test = new RegExp(test.replace('{val}', match[3])); 1284 } 1285 match[2] = test; 1286 } 1287 if (!token.last || token.prefilter !== match[1]) { 1288 return match.slice(1); 1289 } 1290 } 1291 1292 }, 1293 { 1294 name: TAG_NAME, 1295 re: /^((?:-?[_a-z]+[\w-]*)|\*)/i, 1296 fn: function(match, token) { 1297 var tag = match[1].toUpperCase(); 1298 token.tagName = tag; 1299 1300 if (tag !== '*' && (!token.last || token.prefilter)) { 1301 return [TAG_NAME, '=', tag]; 1302 } 1303 if (!token.prefilter) { 1304 token.prefilter = 'tagName'; 1305 } 1306 } 1307 }, 1308 { 1309 name: COMBINATOR, 1310 re: /^\s*([>+~]|\s)\s*/, 1311 fn: function(match, token) { 1312 } 1313 }, 1314 { 1315 name: PSEUDOS, 1316 re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i, 1317 fn: function(match, token) { 1318 var test = Selector[PSEUDOS][match[1]]; 1319 if (test) { // reorder match array and unescape special chars for tests 1320 if (match[2]) { 1321 match[2] = match[2].replace(/\\/g, ''); 1322 } 1323 return [match[2], test]; 1324 } else { // selector token not supported (possibly missing CSS3 module) 1325 return false; 1326 } 1327 } 1328 } 1329 ], 1330 1331 _getToken: function(token) { 1332 return { 1333 tagName: null, 1334 id: null, 1335 className: null, 1336 attributes: {}, 1337 combinator: null, 1338 tests: [] 1339 }; 1340 }, 1341 1342 /** 1343 Break selector into token units per simple selector. 1344 Combinator is attached to the previous token. 1345 */ 1346 _tokenize: function(selector) { 1347 selector = selector || ''; 1348 selector = Selector._replaceShorthand(Y_Lang.trim(selector)); 1349 var token = Selector._getToken(), // one token per simple selector (left selector holds combinator) 1350 query = selector, // original query for debug report 1351 tokens = [], // array of tokens 1352 found = false, // whether or not any matches were found this pass 1353 match, // the regex match 1354 test, 1355 i, parser; 1356 1357 /* 1358 Search for selector patterns, store, and strip them from the selector string 1359 until no patterns match (invalid selector) or we run out of chars. 1360 1361 Multiple attributes and pseudos are allowed, in any order. 1362 for example: 1363 'form:first-child[type=button]:not(button)[lang|=en]' 1364 */ 1365 1366 outer: 1367 do { 1368 found = false; // reset after full pass 1369 1370 for (i = 0; (parser = Selector._parsers[i++]);) { 1371 if ( (match = parser.re.exec(selector)) ) { // note assignment 1372 if (parser.name !== COMBINATOR ) { 1373 token.selector = selector; 1374 } 1375 selector = selector.replace(match[0], ''); // strip current match from selector 1376 if (!selector.length) { 1377 token.last = true; 1378 } 1379 1380 if (Selector._attrFilters[match[1]]) { // convert class to className, etc. 1381 match[1] = Selector._attrFilters[match[1]]; 1382 } 1383 1384 test = parser.fn(match, token); 1385 if (test === false) { // selector not supported 1386 found = false; 1387 break outer; 1388 } else if (test) { 1389 token.tests.push(test); 1390 } 1391 1392 if (!selector.length || parser.name === COMBINATOR) { 1393 tokens.push(token); 1394 token = Selector._getToken(token); 1395 if (parser.name === COMBINATOR) { 1396 token.combinator = Selector.combinators[match[1]]; 1397 } 1398 } 1399 found = true; 1400 1401 1402 } 1403 } 1404 } while (found && selector.length); 1405 1406 if (!found || selector.length) { // not fully parsed 1407 Y.log('query: ' + query + ' contains unsupported token in: ' + selector, 'warn', 'Selector'); 1408 tokens = []; 1409 } 1410 return tokens; 1411 }, 1412 1413 _replaceShorthand: function(selector) { 1414 var shorthand = Selector.shorthand, 1415 esc = selector.match(Selector._re.esc), // pull escaped colon, brackets, etc. 1416 attrs, 1417 pseudos, 1418 re, i, len; 1419 1420 if (esc) { 1421 selector = selector.replace(Selector._re.esc, '\uE000'); 1422 } 1423 1424 attrs = selector.match(Selector._re.attr); 1425 pseudos = selector.match(Selector._re.pseudos); 1426 1427 if (attrs) { 1428 selector = selector.replace(Selector._re.attr, '\uE001'); 1429 } 1430 1431 if (pseudos) { 1432 selector = selector.replace(Selector._re.pseudos, '\uE002'); 1433 } 1434 1435 1436 for (re in shorthand) { 1437 if (shorthand.hasOwnProperty(re)) { 1438 selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]); 1439 } 1440 } 1441 1442 if (attrs) { 1443 for (i = 0, len = attrs.length; i < len; ++i) { 1444 selector = selector.replace(/\uE001/, attrs[i]); 1445 } 1446 } 1447 1448 if (pseudos) { 1449 for (i = 0, len = pseudos.length; i < len; ++i) { 1450 selector = selector.replace(/\uE002/, pseudos[i]); 1451 } 1452 } 1453 1454 selector = selector.replace(/\[/g, '\uE003'); 1455 selector = selector.replace(/\]/g, '\uE004'); 1456 1457 selector = selector.replace(/\(/g, '\uE005'); 1458 selector = selector.replace(/\)/g, '\uE006'); 1459 1460 if (esc) { 1461 for (i = 0, len = esc.length; i < len; ++i) { 1462 selector = selector.replace('\uE000', esc[i]); 1463 } 1464 } 1465 1466 return selector; 1467 }, 1468 1469 _attrFilters: { 1470 'class': 'className', 1471 'for': 'htmlFor' 1472 }, 1473 1474 getters: { 1475 href: function(node, attr) { 1476 return Y_DOM.getAttribute(node, attr); 1477 } 1478 } 1479 }; 1480 1481 Y_mix(Selector, SelectorCSS2, true); 1482 Selector.getters.src = Selector.getters.rel = Selector.getters.href; 1483 1484 // IE wants class with native queries 1485 if (Selector.useNative && Y_DOC.querySelector) { 1486 Selector.shorthand['\\.([^\\s\\\\(\\[:]*)'] = '[class~=$1]'; 1487 } 1488 1489 /** 1490 * The selector css3 module provides support for css3 selectors. 1491 * @module dom 1492 * @submodule selector-css3 1493 * @for Selector 1494 */ 1495 1496 /* 1497 an+b = get every _a_th node starting at the _b_th 1498 0n+b = no repeat ("0" and "n" may both be omitted (together) , e.g. "0n+1" or "1", not "0+1"), return only the _b_th element 1499 1n+b = get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n") 1500 an+0 = get every _a_th element, "0" may be omitted 1501 */ 1502 1503 Selector._reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/; 1504 1505 Selector._getNth = function(node, expr, tag, reverse) { 1506 Selector._reNth.test(expr); 1507 var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_) 1508 n = RegExp.$2, // "n" 1509 oddeven = RegExp.$3, // "odd" or "even" 1510 b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_ 1511 result = [], 1512 siblings = Selector._children(node.parentNode, tag), 1513 op; 1514 1515 if (oddeven) { 1516 a = 2; // always every other 1517 op = '+'; 1518 n = 'n'; 1519 b = (oddeven === 'odd') ? 1 : 0; 1520 } else if ( isNaN(a) ) { 1521 a = (n) ? 1 : 0; // start from the first or no repeat 1522 } 1523 1524 if (a === 0) { // just the first 1525 if (reverse) { 1526 b = siblings.length - b + 1; 1527 } 1528 1529 if (siblings[b - 1] === node) { 1530 return true; 1531 } else { 1532 return false; 1533 } 1534 1535 } else if (a < 0) { 1536 reverse = !!reverse; 1537 a = Math.abs(a); 1538 } 1539 1540 if (!reverse) { 1541 for (var i = b - 1, len = siblings.length; i < len; i += a) { 1542 if ( i >= 0 && siblings[i] === node ) { 1543 return true; 1544 } 1545 } 1546 } else { 1547 for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) { 1548 if ( i < len && siblings[i] === node ) { 1549 return true; 1550 } 1551 } 1552 } 1553 return false; 1554 }; 1555 1556 Y_mix(Selector.pseudos, { 1557 'root': function(node) { 1558 return node === node.ownerDocument.documentElement; 1559 }, 1560 1561 'nth-child': function(node, expr) { 1562 return Selector._getNth(node, expr); 1563 }, 1564 1565 'nth-last-child': function(node, expr) { 1566 return Selector._getNth(node, expr, null, true); 1567 }, 1568 1569 'nth-of-type': function(node, expr) { 1570 return Selector._getNth(node, expr, node.tagName); 1571 }, 1572 1573 'nth-last-of-type': function(node, expr) { 1574 return Selector._getNth(node, expr, node.tagName, true); 1575 }, 1576 1577 'last-child': function(node) { 1578 var children = Selector._children(node.parentNode); 1579 return children[children.length - 1] === node; 1580 }, 1581 1582 'first-of-type': function(node) { 1583 return Selector._children(node.parentNode, node.tagName)[0] === node; 1584 }, 1585 1586 'last-of-type': function(node) { 1587 var children = Selector._children(node.parentNode, node.tagName); 1588 return children[children.length - 1] === node; 1589 }, 1590 1591 'only-child': function(node) { 1592 var children = Selector._children(node.parentNode); 1593 return children.length === 1 && children[0] === node; 1594 }, 1595 1596 'only-of-type': function(node) { 1597 var children = Selector._children(node.parentNode, node.tagName); 1598 return children.length === 1 && children[0] === node; 1599 }, 1600 1601 'empty': function(node) { 1602 return node.childNodes.length === 0; 1603 }, 1604 1605 'not': function(node, expr) { 1606 return !Selector.test(node, expr); 1607 }, 1608 1609 'contains': function(node, expr) { 1610 var text = node.innerText || node.textContent || ''; 1611 return text.indexOf(expr) > -1; 1612 }, 1613 1614 'checked': function(node) { 1615 return (node.checked === true || node.selected === true); 1616 }, 1617 1618 enabled: function(node) { 1619 return (node.disabled !== undefined && !node.disabled); 1620 }, 1621 1622 disabled: function(node) { 1623 return (node.disabled); 1624 } 1625 }); 1626 1627 Y_mix(Selector.operators, { 1628 '^=': '^{val}', // Match starts with value 1629 '!=': function(node, attr, val) { return node[attr] !== val; }, // Match starts with value 1630 '$=': '{val}$', // Match ends with value 1631 '*=': '{val}' // Match contains value as substring 1632 }); 1633 1634 Selector.combinators['~'] = { 1635 axis: 'previousSibling' 1636 }; 1637 YAHOO.register("selector", YAHOO.util.Selector, {version: "2.9.0", build: "2800"}); 1638 1639 1640 1641 /****************************************************************************/ 1642 /****************************************************************************/ 1643 /****************************************************************************/ 1644 1645 var Dom = YAHOO.util.Dom; 1646 1647 /** 1648 * The ColumnSet class defines and manages a DataTable's Columns, 1649 * including nested hierarchies and access to individual Column instances. 1650 * 1651 * @namespace YAHOO.widget 1652 * @class ColumnSet 1653 * @uses YAHOO.util.EventProvider 1654 * @constructor 1655 * @param aDefinitions {Object[]} Array of object literals that define cells in 1656 * the THEAD. 1657 */ 1658 YAHOO.widget.ColumnSet = function(aDefinitions) { 1659 this._sId = Dom.generateId(null, "yui-cs"); // "yui-cs" + YAHOO.widget.ColumnSet._nCount; 1660 1661 // First clone the defs 1662 aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions); 1663 this._init(aDefinitions); 1664 1665 YAHOO.widget.ColumnSet._nCount++; 1666 YAHOO.log("ColumnSet initialized", "info", this.toString()); 1667 }; 1668 1669 ///////////////////////////////////////////////////////////////////////////// 1670 // 1671 // Private member variables 1672 // 1673 ///////////////////////////////////////////////////////////////////////////// 1674 1675 /** 1676 * Internal class variable to index multiple ColumnSet instances. 1677 * 1678 * @property ColumnSet._nCount 1679 * @type Number 1680 * @private 1681 * @static 1682 */ 1683 YAHOO.widget.ColumnSet._nCount = 0; 1684 1685 YAHOO.widget.ColumnSet.prototype = { 1686 /** 1687 * Unique instance name. 1688 * 1689 * @property _sId 1690 * @type String 1691 * @private 1692 */ 1693 _sId : null, 1694 1695 /** 1696 * Array of object literal Column definitions passed to the constructor. 1697 * 1698 * @property _aDefinitions 1699 * @type Object[] 1700 * @private 1701 */ 1702 _aDefinitions : null, 1703 1704 ///////////////////////////////////////////////////////////////////////////// 1705 // 1706 // Public member variables 1707 // 1708 ///////////////////////////////////////////////////////////////////////////// 1709 1710 /** 1711 * Top-down tree representation of Column hierarchy. 1712 * 1713 * @property tree 1714 * @type YAHOO.widget.Column[] 1715 */ 1716 tree : null, 1717 1718 /** 1719 * Flattened representation of all Columns. 1720 * 1721 * @property flat 1722 * @type YAHOO.widget.Column[] 1723 * @default [] 1724 */ 1725 flat : null, 1726 1727 /** 1728 * Array of Columns that map one-to-one to a table column. 1729 * 1730 * @property keys 1731 * @type YAHOO.widget.Column[] 1732 * @default [] 1733 */ 1734 keys : null, 1735 1736 /** 1737 * ID index of nested parent hierarchies for HEADERS accessibility attribute. 1738 * 1739 * @property headers 1740 * @type String[] 1741 * @default [] 1742 */ 1743 headers : null, 1744 1745 ///////////////////////////////////////////////////////////////////////////// 1746 // 1747 // Private methods 1748 // 1749 ///////////////////////////////////////////////////////////////////////////// 1750 1751 /** 1752 * Initializes ColumnSet instance with data from Column definitions. 1753 * 1754 * @method _init 1755 * @param aDefinitions {Object[]} Array of object literals that define cells in 1756 * the THEAD . 1757 * @private 1758 */ 1759 1760 _init : function(aDefinitions) { 1761 // DOM tree representation of all Columns 1762 var tree = []; 1763 // Flat representation of all Columns 1764 var flat = []; 1765 // Flat representation of only Columns that are meant to display data 1766 var keys = []; 1767 // Array of HEADERS attribute values for all keys in the "keys" array 1768 var headers = []; 1769 1770 // Tracks current node list depth being tracked 1771 var nodeDepth = -1; 1772 1773 // Internal recursive function to define Column instances 1774 var parseColumns = function(nodeList, parent) { 1775 // One level down 1776 nodeDepth++; 1777 1778 // Create corresponding tree node if not already there for this depth 1779 if(!tree[nodeDepth]) { 1780 tree[nodeDepth] = []; 1781 } 1782 1783 1784 // Parse each node at this depth for attributes and any children 1785 for(var j=0; j<nodeList.length; j++) { 1786 var currentNode = nodeList[j]; 1787 1788 // Instantiate a new Column for each node 1789 var oColumn = new YAHOO.widget.Column(currentNode); 1790 1791 // Cross-reference Column ID back to the original object literal definition 1792 currentNode.yuiColumnId = oColumn._sId; 1793 1794 // Add the new Column to the flat list 1795 flat.push(oColumn); 1796 1797 // Assign its parent as an attribute, if applicable 1798 if(parent) { 1799 oColumn._oParent = parent; 1800 } 1801 1802 // The Column has descendants 1803 if(YAHOO.lang.isArray(currentNode.children)) { 1804 oColumn.children = currentNode.children; 1805 1806 // Determine COLSPAN value for this Column 1807 var terminalChildNodes = 0; 1808 var countTerminalChildNodes = function(ancestor) { 1809 var descendants = ancestor.children; 1810 // Drill down each branch and count terminal nodes 1811 for(var k=0; k<descendants.length; k++) { 1812 // Keep drilling down 1813 if(YAHOO.lang.isArray(descendants[k].children)) { 1814 countTerminalChildNodes(descendants[k]); 1815 } 1816 // Reached branch terminus 1817 else { 1818 terminalChildNodes++; 1819 } 1820 } 1821 }; 1822 countTerminalChildNodes(currentNode); 1823 oColumn._nColspan = terminalChildNodes; 1824 1825 // Cascade certain properties to children if not defined on their own 1826 var currentChildren = currentNode.children; 1827 for(var k=0; k<currentChildren.length; k++) { 1828 var child = currentChildren[k]; 1829 if(oColumn.className && (child.className === undefined)) { 1830 child.className = oColumn.className; 1831 } 1832 if(oColumn.editor && (child.editor === undefined)) { 1833 child.editor = oColumn.editor; 1834 } 1835 //TODO: Deprecated 1836 if(oColumn.editorOptions && (child.editorOptions === undefined)) { 1837 child.editorOptions = oColumn.editorOptions; 1838 } 1839 if(oColumn.formatter && (child.formatter === undefined)) { 1840 child.formatter = oColumn.formatter; 1841 } 1842 if(oColumn.resizeable && (child.resizeable === undefined)) { 1843 child.resizeable = oColumn.resizeable; 1844 } 1845 if(oColumn.sortable && (child.sortable === undefined)) { 1846 child.sortable = oColumn.sortable; 1847 } 1848 if(oColumn.hidden) { 1849 child.hidden = true; 1850 } 1851 if(oColumn.width && (child.width === undefined)) { 1852 child.width = oColumn.width; 1853 } 1854 if(oColumn.minWidth && (child.minWidth === undefined)) { 1855 child.minWidth = oColumn.minWidth; 1856 } 1857 if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) { 1858 child.maxAutoWidth = oColumn.maxAutoWidth; 1859 } 1860 // Backward compatibility 1861 if(oColumn.type && (child.type === undefined)) { 1862 child.type = oColumn.type; 1863 } 1864 if(oColumn.type && !oColumn.formatter) { 1865 YAHOO.log("The property type has been" + 1866 " deprecated in favor of formatter", "warn", oColumn.toString()); 1867 oColumn.formatter = oColumn.type; 1868 } 1869 if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) { 1870 YAHOO.log("The property text has been" + 1871 " deprecated in favor of label", "warn", oColumn.toString()); 1872 oColumn.label = oColumn.text; 1873 } 1874 if(oColumn.parser) { 1875 YAHOO.log("The property parser is no longer supported", 1876 "warn", this.toString()); 1877 } 1878 if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) || 1879 (oColumn.sortOptions.descFunction))) { 1880 YAHOO.log("The properties sortOptions.ascFunction and " + 1881 " sortOptions.descFunction have been deprecated in favor " + 1882 " of sortOptions.sortFunction", "warn", oColumn.toString()); 1883 } 1884 } 1885 1886 // The children themselves must also be parsed for Column instances 1887 if(!tree[nodeDepth+1]) { 1888 tree[nodeDepth+1] = []; 1889 } 1890 parseColumns(currentChildren, oColumn); 1891 } 1892 // This Column does not have any children 1893 else { 1894 oColumn._nKeyIndex = keys.length; 1895 oColumn._nColspan = 1; 1896 keys.push(oColumn); 1897 } 1898 1899 // Add the Column to the top-down tree 1900 tree[nodeDepth].push(oColumn); 1901 } 1902 nodeDepth--; 1903 }; 1904 1905 // Parse out Column instances from the array of object literals 1906 if(YAHOO.lang.isArray(aDefinitions)) { 1907 parseColumns(aDefinitions); 1908 1909 // Store the array 1910 this._aDefinitions = aDefinitions; 1911 } 1912 else { 1913 YAHOO.log("Could not initialize ColumnSet due to invalid definitions","error"); 1914 return null; 1915 } 1916 1917 var i; 1918 1919 // Determine ROWSPAN value for each Column in the tree 1920 var parseTreeForRowspan = function(tree) { 1921 var maxRowDepth = 1; 1922 var currentRow; 1923 var currentColumn; 1924 1925 // Calculate the max depth of descendants for this row 1926 var countMaxRowDepth = function(row, tmpRowDepth) { 1927 tmpRowDepth = tmpRowDepth || 1; 1928 1929 for(var n=0; n<row.length; n++) { 1930 var col = row[n]; 1931 // Column has children, so keep counting 1932 if(YAHOO.lang.isArray(col.children)) { 1933 tmpRowDepth++; 1934 countMaxRowDepth(col.children, tmpRowDepth); 1935 tmpRowDepth--; 1936 } 1937 // No children, is it the max depth? 1938 else { 1939 if(tmpRowDepth > maxRowDepth) { 1940 maxRowDepth = tmpRowDepth; 1941 } 1942 } 1943 1944 } 1945 }; 1946 1947 // Count max row depth for each row 1948 for(var m=0; m<tree.length; m++) { 1949 currentRow = tree[m]; 1950 countMaxRowDepth(currentRow); 1951 1952 // Assign the right ROWSPAN values to each Column in the row 1953 for(var p=0; p<currentRow.length; p++) { 1954 currentColumn = currentRow[p]; 1955 if(!YAHOO.lang.isArray(currentColumn.children)) { 1956 currentColumn._nRowspan = maxRowDepth; 1957 } 1958 else { 1959 currentColumn._nRowspan = 1; 1960 } 1961 } 1962 1963 // Reset counter for next row 1964 maxRowDepth = 1; 1965 } 1966 }; 1967 parseTreeForRowspan(tree); 1968 1969 // Store tree index values 1970 for(i=0; i<tree[0].length; i++) { 1971 tree[0][i]._nTreeIndex = i; 1972 } 1973 1974 // Store header relationships in an array for HEADERS attribute 1975 var recurseAncestorsForHeaders = function(i, oColumn) { 1976 headers[i].push(oColumn.getSanitizedKey()); 1977 if(oColumn._oParent) { 1978 recurseAncestorsForHeaders(i, oColumn._oParent); 1979 } 1980 }; 1981 for(i=0; i<keys.length; i++) { 1982 headers[i] = []; 1983 recurseAncestorsForHeaders(i, keys[i]); 1984 headers[i] = headers[i].reverse(); 1985 } 1986 1987 // Save to the ColumnSet instance 1988 this.tree = tree; 1989 this.flat = flat; 1990 this.keys = keys; 1991 this.headers = headers; 1992 }, 1993 1994 ///////////////////////////////////////////////////////////////////////////// 1995 // 1996 // Public methods 1997 // 1998 ///////////////////////////////////////////////////////////////////////////// 1999 2000 /** 2001 * Returns unique name of the ColumnSet instance. 2002 * 2003 * @method getId 2004 * @return {String} Unique name of the ColumnSet instance. 2005 */ 2006 2007 getId : function() { 2008 return this._sId; 2009 }, 2010 2011 /** 2012 * ColumnSet instance name, for logging. 2013 * 2014 * @method toString 2015 * @return {String} Unique name of the ColumnSet instance. 2016 */ 2017 2018 toString : function() { 2019 return "ColumnSet instance " + this._sId; 2020 }, 2021 2022 /** 2023 * Public accessor to the definitions array. 2024 * 2025 * @method getDefinitions 2026 * @return {Object[]} Array of object literal Column definitions. 2027 */ 2028 2029 getDefinitions : function() { 2030 var aDefinitions = this._aDefinitions; 2031 2032 // Internal recursive function to define Column instances 2033 var parseColumns = function(nodeList, oSelf) { 2034 // Parse each node at this depth for attributes and any children 2035 for(var j=0; j<nodeList.length; j++) { 2036 var currentNode = nodeList[j]; 2037 2038 // Get the Column for each node 2039 var oColumn = oSelf.getColumnById(currentNode.yuiColumnId); 2040 2041 if(oColumn) { 2042 // Update the current values 2043 var oDefinition = oColumn.getDefinition(); 2044 for(var name in oDefinition) { 2045 if(YAHOO.lang.hasOwnProperty(oDefinition, name)) { 2046 currentNode[name] = oDefinition[name]; 2047 } 2048 } 2049 } 2050 2051 // The Column has descendants 2052 if(YAHOO.lang.isArray(currentNode.children)) { 2053 // The children themselves must also be parsed for Column instances 2054 parseColumns(currentNode.children, oSelf); 2055 } 2056 } 2057 }; 2058 2059 parseColumns(aDefinitions, this); 2060 this._aDefinitions = aDefinitions; 2061 return aDefinitions; 2062 }, 2063 2064 /** 2065 * Returns Column instance with given ID. 2066 * 2067 * @method getColumnById 2068 * @param column {String} Column ID. 2069 * @return {YAHOO.widget.Column} Column instance. 2070 */ 2071 2072 getColumnById : function(column) { 2073 if(YAHOO.lang.isString(column)) { 2074 var allColumns = this.flat; 2075 for(var i=allColumns.length-1; i>-1; i--) { 2076 if(allColumns[i]._sId === column) { 2077 return allColumns[i]; 2078 } 2079 } 2080 } 2081 return null; 2082 }, 2083 2084 /** 2085 * Returns Column instance with given key or ColumnSet key index. 2086 * 2087 * @method getColumn 2088 * @param column {String | Number} Column key or ColumnSet key index. 2089 * @return {YAHOO.widget.Column} Column instance. 2090 */ 2091 2092 getColumn : function(column) { 2093 if(YAHOO.lang.isNumber(column) && this.keys[column]) { 2094 return this.keys[column]; 2095 } 2096 else if(YAHOO.lang.isString(column)) { 2097 var allColumns = this.flat; 2098 var aColumns = []; 2099 for(var i=0; i<allColumns.length; i++) { 2100 if(allColumns[i].key === column) { 2101 aColumns.push(allColumns[i]); 2102 } 2103 } 2104 if(aColumns.length === 1) { 2105 return aColumns[0]; 2106 } 2107 else if(aColumns.length > 1) { 2108 return aColumns; 2109 } 2110 } 2111 return null; 2112 }, 2113 2114 /** 2115 * Public accessor returns array of given Column's desendants (if any), including itself. 2116 * 2117 * @method getDescendants 2118 * @parem {YAHOO.widget.Column} Column instance. 2119 * @return {Array} Array including the Column itself and all descendants (if any). 2120 */ 2121 getDescendants : function(oColumn) { 2122 var oSelf = this; 2123 var allDescendants = []; 2124 var i; 2125 2126 // Recursive function to loop thru all children 2127 var parse = function(oParent) { 2128 allDescendants.push(oParent); 2129 // This Column has children 2130 if(oParent.children) { 2131 for(i=0; i<oParent.children.length; i++) { 2132 parse(oSelf.getColumn(oParent.children[i].key)); 2133 } 2134 } 2135 }; 2136 parse(oColumn); 2137 2138 return allDescendants; 2139 } 2140 }; 2141 2142 /****************************************************************************/ 2143 /****************************************************************************/ 2144 /****************************************************************************/ 2145 2146 /** 2147 * The Column class defines and manages attributes of DataTable Columns 2148 * 2149 * @namespace YAHOO.widget 2150 * @class Column 2151 * @constructor 2152 * @param oConfigs {Object} Object literal of definitions. 2153 */ 2154 YAHOO.widget.Column = function(oConfigs) { 2155 this._sId = Dom.generateId(null, "yui-col"); // "yui-col" + YAHOO.widget.Column._nCount; 2156 2157 // Object literal defines Column attributes 2158 if(oConfigs && YAHOO.lang.isObject(oConfigs)) { 2159 for(var sConfig in oConfigs) { 2160 if(sConfig) { 2161 this[sConfig] = oConfigs[sConfig]; 2162 } 2163 } 2164 } 2165 2166 // Assign a key if not found 2167 if(!YAHOO.lang.isValue(this.key)) { 2168 this.key = Dom.generateId(null, "yui-dt-col"); //"yui-dt-col" + YAHOO.widget.Column._nCount; 2169 } 2170 2171 // Assign a field if not found, defaults to key 2172 if(!YAHOO.lang.isValue(this.field)) { 2173 this.field = this.key; 2174 } 2175 2176 // Increment counter 2177 YAHOO.widget.Column._nCount++; 2178 2179 // Backward compatibility 2180 if(this.width && !YAHOO.lang.isNumber(this.width)) { 2181 this.width = null; 2182 YAHOO.log("The Column property width must be a number", "warn", this.toString()); 2183 } 2184 if(this.editor && YAHOO.lang.isString(this.editor)) { 2185 this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions); 2186 YAHOO.log("The Column property editor must be an instance of YAHOO.widget.CellEditor", "warn", this.toString()); 2187 } 2188 }; 2189 2190 ///////////////////////////////////////////////////////////////////////////// 2191 // 2192 // Private member variables 2193 // 2194 ///////////////////////////////////////////////////////////////////////////// 2195 2196 YAHOO.lang.augmentObject(YAHOO.widget.Column, { 2197 /** 2198 * Internal class variable to index multiple Column instances. 2199 * 2200 * @property Column._nCount 2201 * @type Number 2202 * @private 2203 * @static 2204 */ 2205 _nCount : 0, 2206 2207 formatCheckbox : function(elCell, oRecord, oColumn, oData) { 2208 YAHOO.log("The method YAHOO.widget.Column.formatCheckbox() has been" + 2209 " deprecated in favor of YAHOO.widget.DataTable.formatCheckbox()", "warn", 2210 "YAHOO.widget.Column.formatCheckbox"); 2211 YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData); 2212 }, 2213 2214 formatCurrency : function(elCell, oRecord, oColumn, oData) { 2215 YAHOO.log("The method YAHOO.widget.Column.formatCurrency() has been" + 2216 " deprecated in favor of YAHOO.widget.DataTable.formatCurrency()", "warn", 2217 "YAHOO.widget.Column.formatCurrency"); 2218 YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData); 2219 }, 2220 2221 formatDate : function(elCell, oRecord, oColumn, oData) { 2222 YAHOO.log("The method YAHOO.widget.Column.formatDate() has been" + 2223 " deprecated in favor of YAHOO.widget.DataTable.formatDate()", "warn", 2224 "YAHOO.widget.Column.formatDate"); 2225 YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData); 2226 }, 2227 2228 formatEmail : function(elCell, oRecord, oColumn, oData) { 2229 YAHOO.log("The method YAHOO.widget.Column.formatEmail() has been" + 2230 " deprecated in favor of YAHOO.widget.DataTable.formatEmail()", "warn", 2231 "YAHOO.widget.Column.formatEmail"); 2232 YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData); 2233 }, 2234 2235 formatLink : function(elCell, oRecord, oColumn, oData) { 2236 YAHOO.log("The method YAHOO.widget.Column.formatLink() has been" + 2237 " deprecated in favor of YAHOO.widget.DataTable.formatLink()", "warn", 2238 "YAHOO.widget.Column.formatLink"); 2239 YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData); 2240 }, 2241 2242 formatNumber : function(elCell, oRecord, oColumn, oData) { 2243 YAHOO.log("The method YAHOO.widget.Column.formatNumber() has been" + 2244 " deprecated in favor of YAHOO.widget.DataTable.formatNumber()", "warn", 2245 "YAHOO.widget.Column.formatNumber"); 2246 YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData); 2247 }, 2248 2249 formatSelect : function(elCell, oRecord, oColumn, oData) { 2250 YAHOO.log("The method YAHOO.widget.Column.formatSelect() has been" + 2251 " deprecated in favor of YAHOO.widget.DataTable.formatDropdown()", "warn", 2252 "YAHOO.widget.Column.formatSelect"); 2253 YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData); 2254 } 2255 }); 2256 2257 YAHOO.widget.Column.prototype = { 2258 /** 2259 * Unique String identifier assigned at instantiation. 2260 * 2261 * @property _sId 2262 * @type String 2263 * @private 2264 */ 2265 _sId : null, 2266 2267 /** 2268 * Reference to Column's current position index within its ColumnSet's keys 2269 * array, if applicable. This property only applies to non-nested and bottom- 2270 * level child Columns. 2271 * 2272 * @property _nKeyIndex 2273 * @type Number 2274 * @private 2275 */ 2276 _nKeyIndex : null, 2277 2278 /** 2279 * Reference to Column's current position index within its ColumnSet's tree 2280 * array, if applicable. This property only applies to non-nested and top- 2281 * level parent Columns. 2282 * 2283 * @property _nTreeIndex 2284 * @type Number 2285 * @private 2286 */ 2287 _nTreeIndex : null, 2288 2289 /** 2290 * Number of table cells the Column spans. 2291 * 2292 * @property _nColspan 2293 * @type Number 2294 * @private 2295 */ 2296 _nColspan : 1, 2297 2298 /** 2299 * Number of table rows the Column spans. 2300 * 2301 * @property _nRowspan 2302 * @type Number 2303 * @private 2304 */ 2305 _nRowspan : 1, 2306 2307 /** 2308 * Column's parent Column instance, or null. 2309 * 2310 * @property _oParent 2311 * @type YAHOO.widget.Column 2312 * @private 2313 */ 2314 _oParent : null, 2315 2316 /** 2317 * The DOM reference to the associated TH element. 2318 * 2319 * @property _elTh 2320 * @type HTMLElement 2321 * @private 2322 */ 2323 _elTh : null, 2324 2325 /** 2326 * The DOM reference to the associated TH element's liner DIV element. 2327 * 2328 * @property _elThLiner 2329 * @type HTMLElement 2330 * @private 2331 */ 2332 _elThLiner : null, 2333 2334 /** 2335 * The DOM reference to the associated TH element's label SPAN element. 2336 * 2337 * @property _elThLabel 2338 * @type HTMLElement 2339 * @private 2340 */ 2341 _elThLabel : null, 2342 2343 /** 2344 * The DOM reference to the associated resizerelement (if any). 2345 * 2346 * @property _elResizer 2347 * @type HTMLElement 2348 * @private 2349 */ 2350 _elResizer : null, 2351 2352 /** 2353 * Internal width tracker. 2354 * 2355 * @property _nWidth 2356 * @type Number 2357 * @private 2358 */ 2359 _nWidth : null, 2360 2361 /** 2362 * For unreg() purposes, a reference to the Column's DragDrop instance. 2363 * 2364 * @property _dd 2365 * @type YAHOO.util.DragDrop 2366 * @private 2367 */ 2368 _dd : null, 2369 2370 /** 2371 * For unreg() purposes, a reference to the Column resizer's DragDrop instance. 2372 * 2373 * @property _ddResizer 2374 * @type YAHOO.util.DragDrop 2375 * @private 2376 */ 2377 _ddResizer : null, 2378 2379 ///////////////////////////////////////////////////////////////////////////// 2380 // 2381 // Public member variables 2382 // 2383 ///////////////////////////////////////////////////////////////////////////// 2384 2385 /** 2386 * Unique name, required. If "label" property is not provided, the "key" 2387 * value will be treated as markup and inserted into the DOM as innerHTML. 2388 * 2389 * @property key 2390 * @type String|HTML 2391 */ 2392 key : null, 2393 2394 /** 2395 * Associated database field, or null. 2396 * 2397 * @property field 2398 * @type String 2399 */ 2400 field : null, 2401 2402 /** 2403 * Value displayed as Column header in the TH element. String value is 2404 * treated as markup and inserted into the DOM as innerHTML. 2405 * 2406 * @property label 2407 * @type HTML 2408 */ 2409 label : null, 2410 2411 /** 2412 * Column head cell ABBR for accessibility. 2413 * 2414 * @property abbr 2415 * @type String 2416 */ 2417 abbr : null, 2418 2419 /** 2420 * Array of object literals that define children (nested headers) of a Column. 2421 * 2422 * @property children 2423 * @type Object[] 2424 */ 2425 children : null, 2426 2427 /** 2428 * Column width (in pixels). 2429 * 2430 * @property width 2431 * @type Number 2432 */ 2433 width : null, 2434 2435 /** 2436 * Minimum Column width (in pixels). 2437 * 2438 * @property minWidth 2439 * @type Number 2440 * @default null 2441 */ 2442 minWidth : null, 2443 2444 /** 2445 * When a width is not defined for a Column, maxAutoWidth defines an upper 2446 * limit that the Column should be auto-sized to. If resizeable is enabled, 2447 * users may still resize to a greater width. Most useful for Columns intended 2448 * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very 2449 * wide Columns from disrupting visual readability by inducing truncation. 2450 * 2451 * @property maxAutoWidth 2452 * @type Number 2453 * @default null 2454 */ 2455 maxAutoWidth : null, 2456 2457 /** 2458 * True if Column is in hidden state. 2459 * 2460 * @property hidden 2461 * @type Boolean 2462 * @default false 2463 */ 2464 hidden : false, 2465 2466 /** 2467 * True if Column is in selected state. 2468 * 2469 * @property selected 2470 * @type Boolean 2471 * @default false 2472 */ 2473 selected : false, 2474 2475 /** 2476 * Custom CSS class or array of classes to be applied to every cell in the Column. 2477 * 2478 * @property className 2479 * @type String || String[] 2480 */ 2481 className : null, 2482 2483 /** 2484 * Cell formatter function, or a shortcut pointer to a function in the 2485 * DataTable.Formatter object. The function, called from the DataTable's 2486 * formatCell method, renders markup into the cell liner 2487 * element and accepts the following arguments: 2488 * <dl> 2489 * <dt>elLiner</dt> 2490 * <dd>The element to write innerHTML to.</dd> 2491 * <dt>oRecord</dt> 2492 * <dd>The associated Record for the row.</dd> 2493 * <dt>oColumn</dt> 2494 * <dd>The Column instance for the cell.</dd> 2495 * <dt>oData</dt> 2496 * <dd>The data value for the cell.</dd> 2497 * </dl> 2498 * 2499 * @property formatter 2500 * @type String || HTMLFunction 2501 */ 2502 formatter : null, 2503 2504 /** 2505 * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter. 2506 * 2507 * @property currencyOptions 2508 * @type Object 2509 * @default null 2510 */ 2511 currencyOptions : null, 2512 2513 /** 2514 * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter. 2515 * 2516 * @property dateOptions 2517 * @type Object 2518 * @default null 2519 */ 2520 dateOptions : null, 2521 2522 /** 2523 * Array of dropdown values for formatter:"dropdown" cases. Can either be a 2524 * simple array (e.g., ["Alabama","Alaska","Arizona","Arkansas"]) or a an 2525 * array of objects (e.g., [{label:"Alabama", value:"AL"}, 2526 * {label:"Alaska", value:"AK"}, {label:"Arizona", value:"AZ"}, 2527 * {label:"Arkansas", value:"AR"}]). String values are treated as markup and 2528 * inserted into the DOM as innerHTML. 2529 * 2530 * @property dropdownOptions 2531 * @type HTML[] | Object[] 2532 */ 2533 dropdownOptions : null, 2534 2535 /** 2536 * A CellEditor instance, otherwise Column is not editable. 2537 * 2538 * @property editor 2539 * @type YAHOO.widget.CellEditor 2540 */ 2541 editor : null, 2542 2543 /** 2544 * True if Column is resizeable, false otherwise. The Drag & Drop Utility is 2545 * required to enable this feature. Only bottom-level and non-nested Columns are 2546 * resizeble. 2547 * 2548 * @property resizeable 2549 * @type Boolean 2550 * @default false 2551 */ 2552 resizeable : false, 2553 2554 /** 2555 * True if Column is sortable, false otherwise. 2556 * 2557 * @property sortable 2558 * @type Boolean 2559 * @default false 2560 */ 2561 sortable : false, 2562 2563 /** 2564 * @property sortOptions.defaultOrder 2565 * @deprecated Use sortOptions.defaultDir. 2566 */ 2567 /** 2568 * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC. 2569 * 2570 * @property sortOptions.defaultDir 2571 * @type String 2572 * @default null 2573 */ 2574 /** 2575 * Custom field to sort on. 2576 * 2577 * @property sortOptions.field 2578 * @type String 2579 * @default null 2580 */ 2581 /** 2582 * Custom sort handler. Signature: sortFunction(a, b, desc, field) where field is the sortOptions.field value 2583 * 2584 * @property sortOptions.sortFunction 2585 * @type Function 2586 * @default null 2587 */ 2588 sortOptions : null, 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 ///////////////////////////////////////////////////////////////////////////// 2605 // 2606 // Public methods 2607 // 2608 ///////////////////////////////////////////////////////////////////////////// 2609 2610 /** 2611 * Returns unique ID string. 2612 * 2613 * @method getId 2614 * @return {String} Unique ID string. 2615 */ 2616 getId : function() { 2617 return this._sId; 2618 }, 2619 2620 /** 2621 * Column instance name, for logging. 2622 * 2623 * @method toString 2624 * @return {String} Column's unique name. 2625 */ 2626 toString : function() { 2627 return "Column instance " + this._sId; 2628 }, 2629 2630 /** 2631 * Returns object literal definition. 2632 * 2633 * @method getDefinition 2634 * @return {Object} Object literal definition. 2635 */ 2636 getDefinition : function() { 2637 var oDefinition = {}; 2638 2639 // Update the definition 2640 oDefinition.abbr = this.abbr; 2641 oDefinition.className = this.className; 2642 oDefinition.editor = this.editor; 2643 oDefinition.editorOptions = this.editorOptions; //TODO: deprecated 2644 oDefinition.field = this.field; 2645 oDefinition.formatter = this.formatter; 2646 oDefinition.hidden = this.hidden; 2647 oDefinition.key = this.key; 2648 oDefinition.label = this.label; 2649 oDefinition.minWidth = this.minWidth; 2650 oDefinition.maxAutoWidth = this.maxAutoWidth; 2651 oDefinition.resizeable = this.resizeable; 2652 oDefinition.selected = this.selected; 2653 oDefinition.sortable = this.sortable; 2654 oDefinition.sortOptions = this.sortOptions; 2655 oDefinition.width = this.width; 2656 2657 // Bug 2529147 2658 oDefinition._calculatedWidth = this._calculatedWidth; 2659 2660 return oDefinition; 2661 }, 2662 2663 /** 2664 * Returns unique Column key. 2665 * 2666 * @method getKey 2667 * @return {String} Column key. 2668 */ 2669 getKey : function() { 2670 return this.key; 2671 }, 2672 2673 /** 2674 * Returns field. 2675 * 2676 * @method getField 2677 * @return {String} Column field. 2678 */ 2679 getField : function() { 2680 return this.field; 2681 }, 2682 2683 /** 2684 * Returns Column key which has been sanitized for DOM (class and ID) usage 2685 * starts with letter, contains only letters, numbers, hyphen, or period. 2686 * 2687 * @method getSanitizedKey 2688 * @return {String} Sanitized Column key. 2689 */ 2690 getSanitizedKey : function() { 2691 return this.getKey().replace(/[^\w\-]/g,""); 2692 }, 2693 2694 /** 2695 * Public accessor returns Column's current position index within its 2696 * ColumnSet's keys array, if applicable. Only non-nested and bottom-level 2697 * child Columns will return a value. 2698 * 2699 * @method getKeyIndex 2700 * @return {Number} Position index, or null. 2701 */ 2702 getKeyIndex : function() { 2703 return this._nKeyIndex; 2704 }, 2705 2706 /** 2707 * Public accessor returns Column's current position index within its 2708 * ColumnSet's tree array, if applicable. Only non-nested and top-level parent 2709 * Columns will return a value; 2710 * 2711 * @method getTreeIndex 2712 * @return {Number} Position index, or null. 2713 */ 2714 getTreeIndex : function() { 2715 return this._nTreeIndex; 2716 }, 2717 2718 /** 2719 * Public accessor returns Column's parent instance if any, or null otherwise. 2720 * 2721 * @method getParent 2722 * @return {YAHOO.widget.Column} Column's parent instance. 2723 */ 2724 getParent : function() { 2725 return this._oParent; 2726 }, 2727 2728 /** 2729 * Public accessor returns Column's calculated COLSPAN value. 2730 * 2731 * @method getColspan 2732 * @return {Number} Column's COLSPAN value. 2733 */ 2734 getColspan : function() { 2735 return this._nColspan; 2736 }, 2737 // Backward compatibility 2738 getColSpan : function() { 2739 YAHOO.log("The method getColSpan() has been" + 2740 " deprecated in favor of getColspan()", "warn", this.toString()); 2741 return this.getColspan(); 2742 }, 2743 2744 /** 2745 * Public accessor returns Column's calculated ROWSPAN value. 2746 * 2747 * @method getRowspan 2748 * @return {Number} Column's ROWSPAN value. 2749 */ 2750 getRowspan : function() { 2751 return this._nRowspan; 2752 }, 2753 2754 /** 2755 * Returns DOM reference to the key TH element. 2756 * 2757 * @method getThEl 2758 * @return {HTMLElement} TH element. 2759 */ 2760 getThEl : function() { 2761 return this._elTh; 2762 }, 2763 2764 /** 2765 * Returns DOM reference to the TH's liner DIV element. Introduced since 2766 * resizeable Columns may have an extra resizer liner, making the DIV liner 2767 * not reliably the TH element's first child. 2768 * 2769 * @method getThLInerEl 2770 * @return {HTMLElement} TH element. 2771 */ 2772 getThLinerEl : function() { 2773 return this._elThLiner; 2774 }, 2775 2776 /** 2777 * Returns DOM reference to the resizer element, or null. 2778 * 2779 * @method getResizerEl 2780 * @return {HTMLElement} DIV element. 2781 */ 2782 getResizerEl : function() { 2783 return this._elResizer; 2784 }, 2785 2786 // Backward compatibility 2787 /** 2788 * @method getColEl 2789 * @deprecated Use getThEl 2790 */ 2791 getColEl : function() { 2792 YAHOO.log("The method getColEl() has been" + 2793 " deprecated in favor of getThEl()", "warn", 2794 this.toString()); 2795 return this.getThEl(); 2796 }, 2797 getIndex : function() { 2798 YAHOO.log("The method getIndex() has been" + 2799 " deprecated in favor of getKeyIndex()", "warn", 2800 this.toString()); 2801 return this.getKeyIndex(); 2802 }, 2803 format : function() { 2804 YAHOO.log("The method format() has been deprecated in favor of the " + 2805 "DataTable method formatCell()", "error", this.toString()); 2806 } 2807 }; 2808 2809 /****************************************************************************/ 2810 /****************************************************************************/ 2811 /****************************************************************************/ 2812 2813 /** 2814 * Sort static utility to support Column sorting. 2815 * 2816 * @namespace YAHOO.util 2817 * @class Sort 2818 * @static 2819 */ 2820 YAHOO.util.Sort = { 2821 ///////////////////////////////////////////////////////////////////////////// 2822 // 2823 // Public methods 2824 // 2825 ///////////////////////////////////////////////////////////////////////////// 2826 2827 /** 2828 * Comparator function for simple case-insensitive string sorting. 2829 * 2830 * @method compare 2831 * @param a {Object} First sort argument. 2832 * @param b {Object} Second sort argument. 2833 * @param desc {Boolean} True if sort direction is descending, false if 2834 * sort direction is ascending. 2835 * @return {Boolean} Return -1 when a < b. Return 0 when a = b. 2836 * Return 1 when a > b. 2837 */ 2838 compare: function(a, b, desc) { 2839 if((a === null) || (typeof a == "undefined")) { 2840 if((b === null) || (typeof b == "undefined")) { 2841 return 0; 2842 } 2843 else { 2844 return 1; 2845 } 2846 } 2847 else if((b === null) || (typeof b == "undefined")) { 2848 return -1; 2849 } 2850 2851 if(a.constructor == String) { 2852 a = a.toLowerCase(); 2853 } 2854 if(b.constructor == String) { 2855 b = b.toLowerCase(); 2856 } 2857 if(a < b) { 2858 return (desc) ? 1 : -1; 2859 } 2860 else if (a > b) { 2861 return (desc) ? -1 : 1; 2862 } 2863 else { 2864 return 0; 2865 } 2866 } 2867 }; 2868 2869 /****************************************************************************/ 2870 /****************************************************************************/ 2871 /****************************************************************************/ 2872 2873 /** 2874 * ColumnDD subclasses DragDrop to support rearrangeable Columns. 2875 * 2876 * @namespace YAHOO.util 2877 * @class ColumnDD 2878 * @extends YAHOO.util.DDProxy 2879 * @constructor 2880 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 2881 * @param oColumn {YAHOO.widget.Column} Column instance. 2882 * @param elTh {HTMLElement} TH element reference. 2883 * @param elTarget {HTMLElement} Drag target element. 2884 */ 2885 YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) { 2886 if(oDataTable && oColumn && elTh && elTarget) { 2887 this.datatable = oDataTable; 2888 this.table = oDataTable.getTableEl(); 2889 this.column = oColumn; 2890 this.headCell = elTh; 2891 this.pointer = elTarget; 2892 this.newIndex = null; 2893 this.init(elTh); 2894 this.initFrame(); // Needed for DDProxy 2895 this.invalidHandleTypes = {}; 2896 2897 // Set top/bottom padding to account for children of nested columns 2898 this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0); 2899 2900 YAHOO.util.Event.on(window, 'resize', function() { 2901 this.initConstraints(); 2902 }, this, true); 2903 } 2904 else { 2905 YAHOO.log("Column dragdrop could not be created","warn",oDataTable.toString()); 2906 } 2907 }; 2908 2909 if(YAHOO.util.DDProxy) { 2910 YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, { 2911 initConstraints: function() { 2912 //Get the top, right, bottom and left positions 2913 var region = YAHOO.util.Dom.getRegion(this.table), 2914 //Get the element we are working on 2915 el = this.getEl(), 2916 //Get the xy position of it 2917 xy = YAHOO.util.Dom.getXY(el), 2918 //Get the width and height 2919 width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10), 2920 height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10), 2921 //Set left to x minus left 2922 left = ((xy[0] - region.left) + 15), //Buffer of 15px 2923 //Set right to right minus x minus width 2924 right = ((region.right - xy[0] - width) + 15); 2925 2926 //Set the constraints based on the above calculations 2927 this.setXConstraint(left, right); 2928 this.setYConstraint(10, 10); 2929 }, 2930 _resizeProxy: function() { 2931 YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this, arguments); 2932 var dragEl = this.getDragEl(), 2933 el = this.getEl(); 2934 2935 YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px'); 2936 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block'); 2937 var xy = YAHOO.util.Dom.getXY(el); 2938 YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]); 2939 2940 YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px"); 2941 YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px'); 2942 YAHOO.util.Dom.setXY(this.dragEl, xy); 2943 }, 2944 onMouseDown: function() { 2945 this.initConstraints(); 2946 this.resetConstraints(); 2947 }, 2948 clickValidator: function(e) { 2949 if(!this.column.hidden) { 2950 var target = YAHOO.util.Event.getTarget(e); 2951 return ( this.isValidHandleChild(target) && 2952 (this.id == this.handleElId || 2953 this.DDM.handleWasClicked(target, this.id)) ); 2954 } 2955 }, 2956 onDragOver: function(ev, id) { 2957 // Validate target as a Column 2958 var target = this.datatable.getColumn(id); 2959 if(target) { 2960 // Validate target as a top-level parent 2961 var targetIndex = target.getTreeIndex(); 2962 while((targetIndex === null) && target.getParent()) { 2963 target = target.getParent(); 2964 targetIndex = target.getTreeIndex(); 2965 } 2966 if(targetIndex !== null) { 2967 // Are we placing to left or right of target? 2968 var elTarget = target.getThEl(); 2969 var newIndex = targetIndex; 2970 var mouseX = YAHOO.util.Event.getPageX(ev), 2971 targetX = YAHOO.util.Dom.getX(elTarget), 2972 midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2), 2973 currentIndex = this.column.getTreeIndex(); 2974 2975 if (mouseX < midX) { 2976 YAHOO.util.Dom.setX(this.pointer, targetX); 2977 } else { 2978 var targetWidth = parseInt(elTarget.offsetWidth, 10); 2979 YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth)); 2980 newIndex++; 2981 } 2982 if (targetIndex > currentIndex) { 2983 newIndex--; 2984 } 2985 if(newIndex < 0) { 2986 newIndex = 0; 2987 } 2988 else if(newIndex > this.datatable.getColumnSet().tree[0].length) { 2989 newIndex = this.datatable.getColumnSet().tree[0].length; 2990 } 2991 this.newIndex = newIndex; 2992 } 2993 } 2994 }, 2995 onDragDrop: function() { 2996 this.datatable.reorderColumn(this.column, this.newIndex); 2997 }, 2998 endDrag: function() { 2999 this.newIndex = null; 3000 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none'); 3001 } 3002 }); 3003 } 3004 3005 /****************************************************************************/ 3006 /****************************************************************************/ 3007 /****************************************************************************/ 3008 3009 /** 3010 * ColumnResizer subclasses DragDrop to support resizeable Columns. 3011 * 3012 * @namespace YAHOO.util 3013 * @class ColumnResizer 3014 * @extends YAHOO.util.DDProxy 3015 * @constructor 3016 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 3017 * @param oColumn {YAHOO.widget.Column} Column instance. 3018 * @param elTh {HTMLElement} TH element reference. 3019 * @param sHandleElId {String} DOM ID of the handle element that causes the resize. 3020 * @param elProxy {HTMLElement} Resizer proxy element. 3021 */ 3022 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) { 3023 if(oDataTable && oColumn && elTh && sHandleId) { 3024 this.datatable = oDataTable; 3025 this.column = oColumn; 3026 this.headCell = elTh; 3027 this.headCellLiner = oColumn.getThLinerEl(); 3028 this.resizerLiner = elTh.firstChild; 3029 this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id}); 3030 this.initFrame(); // Needed for proxy 3031 this.resetResizerEl(); // Needed when rowspan > 0 3032 3033 // Set right padding for bug 1858462 3034 this.setPadding(0, 1, 0, 0); 3035 } 3036 else { 3037 YAHOO.log("Column resizer could not be created","warn",oDataTable.toString()); 3038 } 3039 }; 3040 3041 if(YAHOO.util.DD) { 3042 YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, { 3043 ///////////////////////////////////////////////////////////////////////////// 3044 // 3045 // Public methods 3046 // 3047 ///////////////////////////////////////////////////////////////////////////// 3048 /** 3049 * Resets resizer element. 3050 * 3051 * @method resetResizerEl 3052 */ 3053 resetResizerEl : function() { 3054 var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style; 3055 resizerStyle.left = "auto"; 3056 resizerStyle.right = 0; 3057 resizerStyle.top = "auto"; 3058 resizerStyle.bottom = 0; 3059 resizerStyle.height = this.headCell.offsetHeight+"px"; 3060 }, 3061 3062 ///////////////////////////////////////////////////////////////////////////// 3063 // 3064 // Public DOM event handlers 3065 // 3066 ///////////////////////////////////////////////////////////////////////////// 3067 3068 /** 3069 * Handles mouseup events on the Column resizer. 3070 * 3071 * @method onMouseUp 3072 * @param e {string} The mouseup event 3073 */ 3074 onMouseUp : function(e) { 3075 // Reset height of all resizer els in case TH's have changed height 3076 var allKeys = this.datatable.getColumnSet().keys, 3077 col; 3078 for(var i=0, len=allKeys.length; i<len; i++) { 3079 col = allKeys[i]; 3080 if(col._ddResizer) { 3081 col._ddResizer.resetResizerEl(); 3082 } 3083 } 3084 this.resetResizerEl(); 3085 3086 var el = this.headCellLiner; 3087 var newWidth = el.offsetWidth - 3088 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) - 3089 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0); 3090 3091 this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth}); 3092 }, 3093 3094 /** 3095 * Handles mousedown events on the Column resizer. 3096 * 3097 * @method onMouseDown 3098 * @param e {string} The mousedown event 3099 */ 3100 onMouseDown : function(e) { 3101 this.startWidth = this.headCellLiner.offsetWidth; 3102 this.startX = YAHOO.util.Event.getXY(e)[0]; 3103 this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) + 3104 (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0); 3105 }, 3106 3107 /** 3108 * Custom clickValidator to ensure Column is not in hidden state. 3109 * 3110 * @method clickValidator 3111 * @param {Event} e 3112 * @private 3113 */ 3114 clickValidator : function(e) { 3115 if(!this.column.hidden) { 3116 var target = YAHOO.util.Event.getTarget(e); 3117 return ( this.isValidHandleChild(target) && 3118 (this.id == this.handleElId || 3119 this.DDM.handleWasClicked(target, this.id)) ); 3120 } 3121 }, 3122 3123 /** 3124 * Handles start drag on the Column resizer. 3125 * 3126 * @method startDrag 3127 * @param e {string} The drag event 3128 */ 3129 startDrag : function() { 3130 // Shrinks height of all resizer els to not hold open TH els 3131 var allKeys = this.datatable.getColumnSet().keys, 3132 thisKey = this.column.getKeyIndex(), 3133 col; 3134 for(var i=0, len=allKeys.length; i<len; i++) { 3135 col = allKeys[i]; 3136 if(col._ddResizer) { 3137 YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em"; 3138 } 3139 } 3140 }, 3141 3142 /** 3143 * Handles drag events on the Column resizer. 3144 * 3145 * @method onDrag 3146 * @param e {string} The drag event 3147 */ 3148 onDrag : function(e) { 3149 var newX = YAHOO.util.Event.getXY(e)[0]; 3150 if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) { 3151 var offsetX = newX - this.startX; 3152 var newWidth = this.startWidth + offsetX - this.nLinerPadding; 3153 if(newWidth > 0) { 3154 this.datatable.setColumnWidth(this.column, newWidth); 3155 } 3156 } 3157 } 3158 }); 3159 } 3160 3161 ///////////////////////////////////////////////////////////////////////////// 3162 // 3163 // Deprecated 3164 // 3165 ///////////////////////////////////////////////////////////////////////////// 3166 3167 /** 3168 * @property editorOptions 3169 * @deprecated Pass configs directly to CellEditor constructor. 3170 */ 3171 3172 3173 (function () { 3174 3175 var lang = YAHOO.lang, 3176 util = YAHOO.util, 3177 widget = YAHOO.widget, 3178 3179 Dom = util.Dom, 3180 Ev = util.Event, 3181 DT = widget.DataTable; 3182 3183 /****************************************************************************/ 3184 /****************************************************************************/ 3185 /****************************************************************************/ 3186 3187 /** 3188 * A RecordSet defines and manages a set of Records. 3189 * 3190 * @namespace YAHOO.widget 3191 * @class RecordSet 3192 * @param data {Object || Object[]} An object literal or an array of data. 3193 * @constructor 3194 */ 3195 YAHOO.widget.RecordSet = function(data) { 3196 this._init(data); 3197 }; 3198 3199 var RS = widget.RecordSet; 3200 3201 /** 3202 * Internal class variable to name multiple Recordset instances. 3203 * 3204 * @property RecordSet._nCount 3205 * @type Number 3206 * @private 3207 * @static 3208 */ 3209 RS._nCount = 0; 3210 3211 RS.prototype = { 3212 3213 ///////////////////////////////////////////////////////////////////////////// 3214 // 3215 // Private member variables 3216 // 3217 ///////////////////////////////////////////////////////////////////////////// 3218 /** 3219 * Unique String identifier assigned at instantiation. 3220 * 3221 * @property _sId 3222 * @type String 3223 * @private 3224 */ 3225 _sId : null, 3226 3227 /** 3228 * Internal counter of how many Records are in the RecordSet. 3229 * 3230 * @property _length 3231 * @type Number 3232 * @private 3233 * @deprecated No longer used 3234 */ 3235 //_length : null, 3236 3237 ///////////////////////////////////////////////////////////////////////////// 3238 // 3239 // Private methods 3240 // 3241 ///////////////////////////////////////////////////////////////////////////// 3242 3243 /** 3244 * Initializer. 3245 * 3246 * @method _init 3247 * @param data {Object || Object[]} An object literal or an array of data. 3248 * @private 3249 */ 3250 _init : function(data) { 3251 // Internal variables 3252 this._sId = Dom.generateId(null, "yui-rs");// "yui-rs" + widget.RecordSet._nCount; 3253 widget.RecordSet._nCount++; 3254 this._records = []; 3255 //this._length = 0; 3256 3257 this._initEvents(); 3258 3259 if(data) { 3260 if(lang.isArray(data)) { 3261 this.addRecords(data); 3262 } 3263 else if(lang.isObject(data)) { 3264 this.addRecord(data); 3265 } 3266 } 3267 3268 YAHOO.log("RecordSet initialized", "info", this.toString()); 3269 }, 3270 3271 /** 3272 * Initializes custom events. 3273 * 3274 * @method _initEvents 3275 * @private 3276 */ 3277 _initEvents : function() { 3278 this.createEvent("recordAddEvent"); 3279 this.createEvent("recordsAddEvent"); 3280 this.createEvent("recordSetEvent"); 3281 this.createEvent("recordsSetEvent"); 3282 this.createEvent("recordUpdateEvent"); 3283 this.createEvent("recordDeleteEvent"); 3284 this.createEvent("recordsDeleteEvent"); 3285 this.createEvent("resetEvent"); 3286 this.createEvent("recordValueUpdateEvent"); 3287 }, 3288 3289 /** 3290 * Adds one Record to the RecordSet at the given index. If index is null, 3291 * then adds the Record to the end of the RecordSet. 3292 * 3293 * @method _addRecord 3294 * @param oData {Object} An object literal of data. 3295 * @param index {Number} (optional) Position index. 3296 * @return {YAHOO.widget.Record} A Record instance. 3297 * @private 3298 */ 3299 _addRecord : function(oData, index) { 3300 var oRecord = new YAHOO.widget.Record(oData); 3301 3302 if(YAHOO.lang.isNumber(index) && (index > -1)) { 3303 this._records.splice(index,0,oRecord); 3304 } 3305 else { 3306 //index = this.getLength(); 3307 //this._records[index] = oRecord; 3308 this._records[this._records.length] = oRecord; 3309 } 3310 //this._length++; 3311 return oRecord; 3312 }, 3313 3314 /** 3315 * Sets/replaces one Record to the RecordSet at the given index. Existing 3316 * Records with higher indexes are not shifted. If no index specified, the 3317 * Record is added to the end of the RecordSet. 3318 * 3319 * @method _setRecord 3320 * @param oData {Object} An object literal of data. 3321 * @param index {Number} (optional) Position index. 3322 * @return {YAHOO.widget.Record} A Record instance. 3323 * @private 3324 */ 3325 _setRecord : function(oData, index) { 3326 if (!lang.isNumber(index) || index < 0) { 3327 index = this._records.length; 3328 } 3329 return (this._records[index] = new widget.Record(oData)); 3330 /* 3331 if(lang.isNumber(index) && (index > -1)) { 3332 this._records[index] = oRecord; 3333 if((index+1) > this.getLength()) { 3334 this._length = index+1; 3335 } 3336 } 3337 else { 3338 this._records[this.getLength()] = oRecord; 3339 this._length++; 3340 } 3341 return oRecord; 3342 */ 3343 }, 3344 3345 /** 3346 * Deletes Records from the RecordSet at the given index. If range is null, 3347 * then only one Record is deleted. 3348 * 3349 * @method _deleteRecord 3350 * @param index {Number} Position index. 3351 * @param range {Number} (optional) How many Records to delete 3352 * @private 3353 */ 3354 _deleteRecord : function(index, range) { 3355 if(!lang.isNumber(range) || (range < 0)) { 3356 range = 1; 3357 } 3358 this._records.splice(index, range); 3359 //this._length = this._length - range; 3360 }, 3361 3362 ///////////////////////////////////////////////////////////////////////////// 3363 // 3364 // Public methods 3365 // 3366 ///////////////////////////////////////////////////////////////////////////// 3367 3368 /** 3369 * Returns unique name of the RecordSet instance. 3370 * 3371 * @method getId 3372 * @return {String} Unique name of the RecordSet instance. 3373 */ 3374 getId : function() { 3375 return this._sId; 3376 }, 3377 3378 /** 3379 * Public accessor to the unique name of the RecordSet instance. 3380 * 3381 * @method toString 3382 * @return {String} Unique name of the RecordSet instance. 3383 */ 3384 toString : function() { 3385 return "RecordSet instance " + this._sId; 3386 }, 3387 3388 /** 3389 * Returns the number of Records held in the RecordSet. 3390 * 3391 * @method getLength 3392 * @return {Number} Number of records in the RecordSet. 3393 */ 3394 getLength : function() { 3395 //return this._length; 3396 return this._records.length; 3397 }, 3398 3399 /** 3400 * Returns Record by ID or RecordSet position index. 3401 * 3402 * @method getRecord 3403 * @param record {YAHOO.widget.Record | Number | String} Record instance, 3404 * RecordSet position index, or Record ID. 3405 * @return {YAHOO.widget.Record} Record object. 3406 */ 3407 getRecord : function(record) { 3408 var i; 3409 if(record instanceof widget.Record) { 3410 for(i=0; i<this._records.length; i++) { 3411 if(this._records[i] && (this._records[i]._sId === record._sId)) { 3412 return record; 3413 } 3414 } 3415 } 3416 else if(lang.isNumber(record)) { 3417 if((record > -1) && (record < this.getLength())) { 3418 return this._records[record]; 3419 } 3420 } 3421 else if(lang.isString(record)) { 3422 for(i=0; i<this._records.length; i++) { 3423 if(this._records[i] && (this._records[i]._sId === record)) { 3424 return this._records[i]; 3425 } 3426 } 3427 } 3428 // Not a valid Record for this RecordSet 3429 return null; 3430 3431 }, 3432 3433 /** 3434 * Returns an array of Records from the RecordSet. 3435 * 3436 * @method getRecords 3437 * @param index {Number} (optional) Recordset position index of which Record to 3438 * start at. 3439 * @param range {Number} (optional) Number of Records to get. 3440 * @return {YAHOO.widget.Record[]} Array of Records starting at given index and 3441 * length equal to given range. If index is not given, all Records are returned. 3442 */ 3443 getRecords : function(index, range) { 3444 if(!lang.isNumber(index)) { 3445 return this._records; 3446 } 3447 if(!lang.isNumber(range)) { 3448 return this._records.slice(index); 3449 } 3450 return this._records.slice(index, index+range); 3451 }, 3452 3453 /** 3454 * Returns a boolean indicating whether Records exist in the RecordSet at the 3455 * specified index range. Returns true if and only if a Record exists at each 3456 * index in the range. 3457 * @method hasRecords 3458 * @param index 3459 * @param range 3460 * @return {Boolean} true if all indices are populated in the RecordSet 3461 */ 3462 hasRecords : function (index, range) { 3463 var recs = this.getRecords(index,range); 3464 for (var i = 0; i < range; ++i) { 3465 if (typeof recs[i] === 'undefined') { 3466 return false; 3467 } 3468 } 3469 return true; 3470 }, 3471 3472 /** 3473 * Returns current position index for the given Record. 3474 * 3475 * @method getRecordIndex 3476 * @param oRecord {YAHOO.widget.Record} Record instance. 3477 * @return {Number} Record's RecordSet position index. 3478 */ 3479 3480 getRecordIndex : function(oRecord) { 3481 if(oRecord) { 3482 for(var i=this._records.length-1; i>-1; i--) { 3483 if(this._records[i] && oRecord.getId() === this._records[i].getId()) { 3484 return i; 3485 } 3486 } 3487 } 3488 return null; 3489 3490 }, 3491 3492 /** 3493 * Adds one Record to the RecordSet at the given index. If index is null, 3494 * then adds the Record to the end of the RecordSet. 3495 * 3496 * @method addRecord 3497 * @param oData {Object} An object literal of data. 3498 * @param index {Number} (optional) Position index. 3499 * @return {YAHOO.widget.Record} A Record instance. 3500 */ 3501 addRecord : function(oData, index) { 3502 if(lang.isObject(oData)) { 3503 var oRecord = this._addRecord(oData, index); 3504 this.fireEvent("recordAddEvent",{record:oRecord,data:oData}); 3505 YAHOO.log("Added Record at index " + index + 3506 " with data " + lang.dump(oData), "info", this.toString()); 3507 return oRecord; 3508 } 3509 else { 3510 YAHOO.log("Could not add Record with data" + 3511 lang.dump(oData), "info", this.toString()); 3512 return null; 3513 } 3514 }, 3515 3516 /** 3517 * Adds multiple Records at once to the RecordSet at the given index with the 3518 * given object literal data. If index is null, then the new Records are 3519 * added to the end of the RecordSet. 3520 * 3521 * @method addRecords 3522 * @param aData {Object[]} An object literal data or an array of data object literals. 3523 * @param index {Number} (optional) Position index. 3524 * @return {YAHOO.widget.Record[]} An array of Record instances. 3525 */ 3526 addRecords : function(aData, index) { 3527 if(lang.isArray(aData)) { 3528 var newRecords = [], 3529 idx,i,len; 3530 3531 index = lang.isNumber(index) ? index : this._records.length; 3532 idx = index; 3533 3534 // Can't go backwards bc we need to preserve order 3535 for(i=0,len=aData.length; i<len; ++i) { 3536 if(lang.isObject(aData[i])) { 3537 var record = this._addRecord(aData[i], idx++); 3538 newRecords.push(record); 3539 } 3540 } 3541 this.fireEvent("recordsAddEvent",{records:newRecords,data:aData}); 3542 YAHOO.log("Added " + newRecords.length + " Record(s) at index " + index + 3543 " with data " + lang.dump(aData), "info", this.toString()); 3544 return newRecords; 3545 } 3546 else if(lang.isObject(aData)) { 3547 var oRecord = this._addRecord(aData); 3548 this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData}); 3549 YAHOO.log("Added 1 Record at index " + index + 3550 " with data " + lang.dump(aData), "info", this.toString()); 3551 return oRecord; 3552 } 3553 else { 3554 YAHOO.log("Could not add Records with data " + 3555 lang.dump(aData), "info", this.toString()); 3556 return null; 3557 } 3558 }, 3559 3560 /** 3561 * Sets or replaces one Record to the RecordSet at the given index. Unlike 3562 * addRecord, an existing Record at that index is not shifted to preserve it. 3563 * If no index is specified, it adds the Record to the end of the RecordSet. 3564 * 3565 * @method setRecord 3566 * @param oData {Object} An object literal of data. 3567 * @param index {Number} (optional) Position index. 3568 * @return {YAHOO.widget.Record} A Record instance. 3569 */ 3570 setRecord : function(oData, index) { 3571 if(lang.isObject(oData)) { 3572 var oRecord = this._setRecord(oData, index); 3573 this.fireEvent("recordSetEvent",{record:oRecord,data:oData}); 3574 YAHOO.log("Set Record at index " + index + 3575 " with data " + lang.dump(oData), "info", this.toString()); 3576 return oRecord; 3577 } 3578 else { 3579 YAHOO.log("Could not set Record with data" + 3580 lang.dump(oData), "info", this.toString()); 3581 return null; 3582 } 3583 }, 3584 3585 /** 3586 * Sets or replaces multiple Records at once to the RecordSet with the given 3587 * data, starting at the given index. If index is not specified, then the new 3588 * Records are added to the end of the RecordSet. 3589 * 3590 * @method setRecords 3591 * @param aData {Object[]} An array of object literal data. 3592 * @param index {Number} (optional) Position index. 3593 * @return {YAHOO.widget.Record[]} An array of Record instances. 3594 */ 3595 setRecords : function(aData, index) { 3596 var Rec = widget.Record, 3597 a = lang.isArray(aData) ? aData : [aData], 3598 added = [], 3599 i = 0, l = a.length, j = 0; 3600 3601 index = parseInt(index,10)|0; 3602 3603 for(; i < l; ++i) { 3604 if (typeof a[i] === 'object' && a[i]) { 3605 added[j++] = this._records[index + i] = new Rec(a[i]); 3606 } 3607 } 3608 3609 this.fireEvent("recordsSetEvent",{records:added,data:aData}); 3610 // Backward compatibility for bug 1918245 3611 this.fireEvent("recordsSet",{records:added,data:aData}); 3612 YAHOO.log("Set "+j+" Record(s) at index "+index, "info", 3613 this.toString()); 3614 3615 if (a.length && !added.length) { 3616 YAHOO.log("Could not set Records with data " + 3617 lang.dump(aData), "info", this.toString()); 3618 } 3619 3620 return added; 3621 }, 3622 3623 /** 3624 * Updates given Record with given data. 3625 * 3626 * @method updateRecord 3627 * @param record {YAHOO.widget.Record | Number | String} A Record instance, 3628 * a RecordSet position index, or a Record ID. 3629 * @param oData {Object} Object literal of new data. 3630 * @return {YAHOO.widget.Record} Updated Record, or null. 3631 */ 3632 updateRecord : function(record, oData) { 3633 var oRecord = this.getRecord(record); 3634 if(oRecord && lang.isObject(oData)) { 3635 // Copy data from the Record for the event that gets fired later 3636 var oldData = {}; 3637 for(var key in oRecord._oData) { 3638 if(lang.hasOwnProperty(oRecord._oData, key)) { 3639 oldData[key] = oRecord._oData[key]; 3640 } 3641 } 3642 oRecord._oData = oData; 3643 this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData}); 3644 YAHOO.log("Record at index " + this.getRecordIndex(oRecord) + 3645 " updated with data " + lang.dump(oData), "info", this.toString()); 3646 return oRecord; 3647 } 3648 else { 3649 YAHOO.log("Could not update Record " + record, "error", this.toString()); 3650 return null; 3651 } 3652 }, 3653 3654 /** 3655 * @method updateKey 3656 * @deprecated Use updateRecordValue 3657 */ 3658 updateKey : function(record, sKey, oData) { 3659 this.updateRecordValue(record, sKey, oData); 3660 }, 3661 /** 3662 * Sets given Record at given key to given data. 3663 * 3664 * @method updateRecordValue 3665 * @param record {YAHOO.widget.Record | Number | String} A Record instance, 3666 * a RecordSet position index, or a Record ID. 3667 * @param sKey {String} Key name. 3668 * @param oData {Object} New data. 3669 */ 3670 updateRecordValue : function(record, sKey, oData) { 3671 var oRecord = this.getRecord(record); 3672 if(oRecord) { 3673 var oldData = null; 3674 var keyValue = oRecord._oData[sKey]; 3675 // Copy data from the Record for the event that gets fired later 3676 if(keyValue && lang.isObject(keyValue)) { 3677 oldData = {}; 3678 for(var key in keyValue) { 3679 if(lang.hasOwnProperty(keyValue, key)) { 3680 oldData[key] = keyValue[key]; 3681 } 3682 } 3683 } 3684 // Copy by value 3685 else { 3686 oldData = keyValue; 3687 } 3688 3689 oRecord._oData[sKey] = oData; 3690 this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData}); 3691 this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData}); 3692 YAHOO.log("Key \"" + sKey + 3693 "\" for Record at index " + this.getRecordIndex(oRecord) + 3694 " updated to \"" + lang.dump(oData) + "\"", "info", this.toString()); 3695 } 3696 else { 3697 YAHOO.log("Could not update key " + sKey + " for Record " + record, "error", this.toString()); 3698 } 3699 }, 3700 3701 /** 3702 * Replaces all Records in RecordSet with new object literal data. 3703 * 3704 * @method replaceRecords 3705 * @param data {Object || Object[]} An object literal of data or an array of 3706 * data object literals. 3707 * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or 3708 * an array of Records. 3709 */ 3710 replaceRecords : function(data) { 3711 this.reset(); 3712 return this.addRecords(data); 3713 }, 3714 3715 /** 3716 * Sorts all Records by given function. Records keep their unique IDs but will 3717 * have new RecordSet position indexes. 3718 * 3719 * @method sortRecords 3720 * @param fnSort {Function} Reference to a sort function. 3721 * @param desc {Boolean} True if sort direction is descending, false if sort 3722 * direction is ascending. 3723 * @param field {String} The field to sort by, from sortOptions.field 3724 * @return {YAHOO.widget.Record[]} Sorted array of Records. 3725 */ 3726 sortRecords : function(fnSort, desc, field) { 3727 return this._records.sort(function(a, b) {return fnSort(a, b, desc, field);}); 3728 }, 3729 3730 /** 3731 * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"]. 3732 * 3733 * @method reverseRecords 3734 * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records. 3735 */ 3736 reverseRecords : function() { 3737 return this._records.reverse(); 3738 }, 3739 3740 /** 3741 * Removes the Record at the given position index from the RecordSet. If a range 3742 * is also provided, removes that many Records, starting from the index. Length 3743 * of RecordSet is correspondingly shortened. 3744 * 3745 * @method deleteRecord 3746 * @param index {Number} Record's RecordSet position index. 3747 * @return {Object} A copy of the data held by the deleted Record. 3748 */ 3749 deleteRecord : function(index) { 3750 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) { 3751 var oData = this.getRecord(index).getData(); 3752 3753 this._deleteRecord(index); 3754 this.fireEvent("recordDeleteEvent",{data:oData,index:index}); 3755 YAHOO.log("Record deleted at index " + index + 3756 " and containing data " + lang.dump(oData), "info", this.toString()); 3757 return oData; 3758 } 3759 else { 3760 YAHOO.log("Could not delete Record at index " + index, "error", this.toString()); 3761 return null; 3762 } 3763 }, 3764 3765 /** 3766 * Removes the Record at the given position index from the RecordSet. If a range 3767 * is also provided, removes that many Records, starting from the index. Length 3768 * of RecordSet is correspondingly shortened. 3769 * 3770 * @method deleteRecords 3771 * @param index {Number} Record's RecordSet position index. 3772 * @param range {Number} (optional) How many Records to delete. 3773 * @return {Object[]} An array of copies of the data held by the deleted Records. 3774 */ 3775 deleteRecords : function(index, range) { 3776 if(!lang.isNumber(range)) { 3777 range = 1; 3778 } 3779 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) { 3780 var recordsToDelete = this.getRecords(index, range); 3781 var deletedData = [], // this mistakenly held Records, not data 3782 deletedObjects = []; // this passes data only 3783 3784 for(var i=0; i<recordsToDelete.length; i++) { 3785 deletedData[deletedData.length] = recordsToDelete[i]; // backward compatibility 3786 deletedObjects[deletedObjects.length] = recordsToDelete[i].getData(); 3787 } 3788 this._deleteRecord(index, range); 3789 3790 this.fireEvent("recordsDeleteEvent",{data:deletedData,deletedData:deletedObjects,index:index}); 3791 YAHOO.log(range + "Record(s) deleted at index " + index + 3792 " and containing data " + lang.dump(deletedObjects), "info", this.toString()); 3793 3794 return deletedData; 3795 } 3796 else { 3797 YAHOO.log("Could not delete Records at index " + index, "error", this.toString()); 3798 return null; 3799 } 3800 }, 3801 3802 /** 3803 * Deletes all Records from the RecordSet. 3804 * 3805 * @method reset 3806 */ 3807 reset : function() { 3808 this._records = []; 3809 //this._length = 0; 3810 this.fireEvent("resetEvent"); 3811 YAHOO.log("All Records deleted from RecordSet", "info", this.toString()); 3812 } 3813 }; 3814 3815 ///////////////////////////////////////////////////////////////////////////// 3816 // 3817 // Custom Events 3818 // 3819 ///////////////////////////////////////////////////////////////////////////// 3820 3821 // RecordSet uses EventProvider 3822 lang.augmentProto(RS, util.EventProvider); 3823 3824 /** 3825 * Fired when a new Record is added to the RecordSet. 3826 * 3827 * @event recordAddEvent 3828 * @param oArgs.record {YAHOO.widget.Record} The Record instance. 3829 * @param oArgs.data {Object} Data added. 3830 */ 3831 3832 /** 3833 * Fired when multiple Records are added to the RecordSet at once. 3834 * 3835 * @event recordsAddEvent 3836 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances. 3837 * @param oArgs.data {Object[]} Data added. 3838 */ 3839 3840 /** 3841 * Fired when a Record is set in the RecordSet. 3842 * 3843 * @event recordSetEvent 3844 * @param oArgs.record {YAHOO.widget.Record} The Record instance. 3845 * @param oArgs.data {Object} Data added. 3846 */ 3847 3848 /** 3849 * Fired when multiple Records are set in the RecordSet at once. 3850 * 3851 * @event recordsSetEvent 3852 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances. 3853 * @param oArgs.data {Object[]} Data added. 3854 */ 3855 3856 /** 3857 * Fired when a Record is updated with new data. 3858 * 3859 * @event recordUpdateEvent 3860 * @param oArgs.record {YAHOO.widget.Record} The Record instance. 3861 * @param oArgs.newData {Object} New data. 3862 * @param oArgs.oldData {Object} Old data. 3863 */ 3864 3865 /** 3866 * Fired when a Record is deleted from the RecordSet. 3867 * 3868 * @event recordDeleteEvent 3869 * @param oArgs.data {Object} The data held by the deleted Record, 3870 * or an array of data object literals if multiple Records were deleted at once. 3871 * @param oArgs.index {Object} Index of the deleted Record. 3872 */ 3873 3874 /** 3875 * Fired when multiple Records are deleted from the RecordSet at once. 3876 * 3877 * @event recordsDeleteEvent 3878 * @param oArgs.data {Object[]} An array of deleted Records. 3879 * @param oArgs.deletedData {Object[]} An array of deleted data. 3880 * @param oArgs.index {Object} Index of the first deleted Record. 3881 */ 3882 3883 /** 3884 * Fired when all Records are deleted from the RecordSet at once. 3885 * 3886 * @event resetEvent 3887 */ 3888 3889 /** 3890 * @event keyUpdateEvent 3891 * @deprecated Use recordValueUpdateEvent 3892 */ 3893 3894 /** 3895 * Fired when a Record value is updated with new data. 3896 * 3897 * @event recordValueUpdateEvent 3898 * @param oArgs.record {YAHOO.widget.Record} The Record instance. 3899 * @param oArgs.key {String} The updated key. 3900 * @param oArgs.newData {Object} New data. 3901 * @param oArgs.oldData {Object} Old data. 3902 * 3903 */ 3904 3905 3906 /****************************************************************************/ 3907 /****************************************************************************/ 3908 /****************************************************************************/ 3909 3910 /** 3911 * The Record class defines a DataTable record. 3912 * 3913 * @namespace YAHOO.widget 3914 * @class Record 3915 * @constructor 3916 * @param oConfigs {Object} (optional) Object literal of key/value pairs. 3917 */ 3918 YAHOO.widget.Record = function(oLiteral) { 3919 this._nCount = widget.Record._nCount; 3920 this._sId = Dom.generateId(null, "yui-rec");//"yui-rec" + this._nCount; 3921 widget.Record._nCount++; 3922 this._oData = {}; 3923 if(lang.isObject(oLiteral)) { 3924 for(var sKey in oLiteral) { 3925 if(lang.hasOwnProperty(oLiteral, sKey)) { 3926 this._oData[sKey] = oLiteral[sKey]; 3927 } 3928 } 3929 } 3930 }; 3931 3932 ///////////////////////////////////////////////////////////////////////////// 3933 // 3934 // Private member variables 3935 // 3936 ///////////////////////////////////////////////////////////////////////////// 3937 3938 /** 3939 * Internal class variable to give unique IDs to Record instances. 3940 * 3941 * @property Record._nCount 3942 * @type Number 3943 * @private 3944 */ 3945 YAHOO.widget.Record._nCount = 0; 3946 3947 YAHOO.widget.Record.prototype = { 3948 /** 3949 * Immutable unique count assigned at instantiation. Remains constant while a 3950 * Record's position index can change from sorting. 3951 * 3952 * @property _nCount 3953 * @type Number 3954 * @private 3955 */ 3956 _nCount : null, 3957 3958 /** 3959 * Immutable unique ID assigned at instantiation. Remains constant while a 3960 * Record's position index can change from sorting. 3961 * 3962 * @property _sId 3963 * @type String 3964 * @private 3965 */ 3966 _sId : null, 3967 3968 /** 3969 * Holds data for the Record in an object literal. 3970 * 3971 * @property _oData 3972 * @type Object 3973 * @private 3974 */ 3975 _oData : null, 3976 3977 ///////////////////////////////////////////////////////////////////////////// 3978 // 3979 // Public member variables 3980 // 3981 ///////////////////////////////////////////////////////////////////////////// 3982 3983 ///////////////////////////////////////////////////////////////////////////// 3984 // 3985 // Public methods 3986 // 3987 ///////////////////////////////////////////////////////////////////////////// 3988 3989 /** 3990 * Returns unique count assigned at instantiation. 3991 * 3992 * @method getCount 3993 * @return Number 3994 */ 3995 getCount : function() { 3996 return this._nCount; 3997 }, 3998 3999 /** 4000 * Returns unique ID assigned at instantiation. 4001 * 4002 * @method getId 4003 * @return String 4004 */ 4005 getId : function() { 4006 return this._sId; 4007 }, 4008 4009 /** 4010 * Returns data for the Record for a field if given, or the entire object 4011 * literal otherwise. 4012 * 4013 * @method getData 4014 * @param sField {String} (Optional) The field from which to retrieve data value. 4015 * @return Object 4016 */ 4017 getData : function(sField) { 4018 if(lang.isString(sField)) { 4019 return this._oData[sField]; 4020 } 4021 else { 4022 return this._oData; 4023 } 4024 }, 4025 4026 /** 4027 * Sets given data at the given key. Use the RecordSet method updateRecordValue to trigger 4028 * events. 4029 * 4030 * @method setData 4031 * @param sKey {String} The key of the new value. 4032 * @param oData {MIXED} The new value. 4033 */ 4034 setData : function(sKey, oData) { 4035 this._oData[sKey] = oData; 4036 } 4037 }; 4038 4039 })(); 4040 4041 (function () { 4042 4043 var lang = YAHOO.lang, 4044 util = YAHOO.util, 4045 widget = YAHOO.widget, 4046 ua = YAHOO.env.ua, 4047 4048 Dom = util.Dom, 4049 Ev = util.Event, 4050 DS = util.DataSourceBase; 4051 4052 /** 4053 * The DataTable widget provides a progressively enhanced DHTML control for 4054 * displaying tabular data across A-grade browsers. 4055 * 4056 * @module datatable 4057 * @requires yahoo, dom, event, element, datasource 4058 * @optional dragdrop, dragdrop 4059 * @title DataTable Widget 4060 */ 4061 4062 /****************************************************************************/ 4063 /****************************************************************************/ 4064 /****************************************************************************/ 4065 4066 /** 4067 * DataTable class for the YUI DataTable widget. 4068 * 4069 * @namespace YAHOO.widget 4070 * @class DataTable 4071 * @extends YAHOO.util.Element 4072 * @constructor 4073 * @param elContainer {HTMLElement} Container element for the TABLE. 4074 * @param aColumnDefs {Object[]} Array of object literal Column definitions. 4075 * @param oDataSource {YAHOO.util.DataSource} DataSource instance. 4076 * @param oConfigs {object} (optional) Object literal of configuration values. 4077 */ 4078 YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) { 4079 var DT = widget.DataTable; 4080 4081 //////////////////////////////////////////////////////////////////////////// 4082 // Backward compatibility for SDT, but prevent infinite loops 4083 4084 if(oConfigs && oConfigs.scrollable) { 4085 return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs); 4086 } 4087 4088 //////////////////////////////////////////////////////////////////////////// 4089 // Initialization 4090 4091 // Internal vars 4092 this._nIndex = DT._nCount; 4093 this._sId = Dom.generateId(null, "yui-dt");// "yui-dt"+this._nIndex; 4094 this._oChainRender = new YAHOO.util.Chain(); 4095 this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true); 4096 4097 // Initialize configs 4098 this._initConfigs(oConfigs); 4099 4100 // Initialize DataSource 4101 this._initDataSource(oDataSource); 4102 if(!this._oDataSource) { 4103 YAHOO.log("Could not instantiate DataTable due to an invalid DataSource", "error", this.toString()); 4104 return; 4105 } 4106 4107 // Initialize ColumnSet 4108 this._initColumnSet(aColumnDefs); 4109 if(!this._oColumnSet) { 4110 YAHOO.log("Could not instantiate DataTable due to an invalid ColumnSet", "error", this.toString()); 4111 return; 4112 } 4113 4114 // Initialize RecordSet 4115 this._initRecordSet(); 4116 if(!this._oRecordSet) { 4117 } 4118 4119 // Initialize Attributes 4120 DT.superclass.constructor.call(this, elContainer, this.configs); 4121 4122 // Initialize DOM elements 4123 var okDom = this._initDomElements(elContainer); 4124 if(!okDom) { 4125 YAHOO.log("Could not instantiate DataTable due to an invalid DOM element", "error", this.toString()); 4126 return; 4127 } 4128 4129 // Show message as soon as config is available 4130 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING); 4131 4132 //////////////////////////////////////////////////////////////////////////// 4133 // Once per instance 4134 this._initEvents(); 4135 4136 DT._nCount++; 4137 DT._nCurrentCount++; 4138 4139 //////////////////////////////////////////////////////////////////////////// 4140 // Data integration 4141 4142 // Send a simple initial request 4143 var oCallback = { 4144 success : this.onDataReturnSetRows, 4145 failure : this.onDataReturnSetRows, 4146 scope : this, 4147 argument: this.getState() 4148 }; 4149 4150 var initialLoad = this.get("initialLoad"); 4151 if(initialLoad === true) { 4152 this._oDataSource.sendRequest(this.get("initialRequest"), oCallback); 4153 } 4154 // Do not send an initial request at all 4155 else if(initialLoad === false) { 4156 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY); 4157 } 4158 // Send an initial request with a custom payload 4159 else { 4160 var oCustom = initialLoad || {}; 4161 oCallback.argument = oCustom.argument || {}; 4162 this._oDataSource.sendRequest(oCustom.request, oCallback); 4163 } 4164 }; 4165 4166 var DT = widget.DataTable; 4167 4168 ///////////////////////////////////////////////////////////////////////////// 4169 // 4170 // Public constants 4171 // 4172 ///////////////////////////////////////////////////////////////////////////// 4173 4174 lang.augmentObject(DT, { 4175 4176 /** 4177 * Class name assigned to outer DataTable container. 4178 * 4179 * @property DataTable.CLASS_DATATABLE 4180 * @type String 4181 * @static 4182 * @final 4183 * @default "yui-dt" 4184 */ 4185 CLASS_DATATABLE : "yui-dt", 4186 4187 /** 4188 * Class name assigned to liner DIV elements. 4189 * 4190 * @property DataTable.CLASS_LINER 4191 * @type String 4192 * @static 4193 * @final 4194 * @default "yui-dt-liner" 4195 */ 4196 CLASS_LINER : "yui-dt-liner", 4197 4198 /** 4199 * Class name assigned to display label elements. 4200 * 4201 * @property DataTable.CLASS_LABEL 4202 * @type String 4203 * @static 4204 * @final 4205 * @default "yui-dt-label" 4206 */ 4207 CLASS_LABEL : "yui-dt-label", 4208 4209 /** 4210 * Class name assigned to messaging elements. 4211 * 4212 * @property DataTable.CLASS_MESSAGE 4213 * @type String 4214 * @static 4215 * @final 4216 * @default "yui-dt-message" 4217 */ 4218 CLASS_MESSAGE : "yui-dt-message", 4219 4220 /** 4221 * Class name assigned to mask element when DataTable is disabled. 4222 * 4223 * @property DataTable.CLASS_MASK 4224 * @type String 4225 * @static 4226 * @final 4227 * @default "yui-dt-mask" 4228 */ 4229 CLASS_MASK : "yui-dt-mask", 4230 4231 /** 4232 * Class name assigned to data elements. 4233 * 4234 * @property DataTable.CLASS_DATA 4235 * @type String 4236 * @static 4237 * @final 4238 * @default "yui-dt-data" 4239 */ 4240 CLASS_DATA : "yui-dt-data", 4241 4242 /** 4243 * Class name assigned to Column drag target. 4244 * 4245 * @property DataTable.CLASS_COLTARGET 4246 * @type String 4247 * @static 4248 * @final 4249 * @default "yui-dt-coltarget" 4250 */ 4251 CLASS_COLTARGET : "yui-dt-coltarget", 4252 4253 /** 4254 * Class name assigned to resizer handle elements. 4255 * 4256 * @property DataTable.CLASS_RESIZER 4257 * @type String 4258 * @static 4259 * @final 4260 * @default "yui-dt-resizer" 4261 */ 4262 CLASS_RESIZER : "yui-dt-resizer", 4263 4264 /** 4265 * Class name assigned to resizer liner elements. 4266 * 4267 * @property DataTable.CLASS_RESIZERLINER 4268 * @type String 4269 * @static 4270 * @final 4271 * @default "yui-dt-resizerliner" 4272 */ 4273 CLASS_RESIZERLINER : "yui-dt-resizerliner", 4274 4275 /** 4276 * Class name assigned to resizer proxy elements. 4277 * 4278 * @property DataTable.CLASS_RESIZERPROXY 4279 * @type String 4280 * @static 4281 * @final 4282 * @default "yui-dt-resizerproxy" 4283 */ 4284 CLASS_RESIZERPROXY : "yui-dt-resizerproxy", 4285 4286 /** 4287 * Class name assigned to CellEditor container elements. 4288 * 4289 * @property DataTable.CLASS_EDITOR 4290 * @type String 4291 * @static 4292 * @final 4293 * @default "yui-dt-editor" 4294 */ 4295 CLASS_EDITOR : "yui-dt-editor", 4296 4297 /** 4298 * Class name assigned to CellEditor container shim. 4299 * 4300 * @property DataTable.CLASS_EDITOR_SHIM 4301 * @type String 4302 * @static 4303 * @final 4304 * @default "yui-dt-editor-shim" 4305 */ 4306 CLASS_EDITOR_SHIM : "yui-dt-editor-shim", 4307 4308 /** 4309 * Class name assigned to paginator container elements. 4310 * 4311 * @property DataTable.CLASS_PAGINATOR 4312 * @type String 4313 * @static 4314 * @final 4315 * @default "yui-dt-paginator" 4316 */ 4317 CLASS_PAGINATOR : "yui-dt-paginator", 4318 4319 /** 4320 * Class name assigned to page number indicators. 4321 * 4322 * @property DataTable.CLASS_PAGE 4323 * @type String 4324 * @static 4325 * @final 4326 * @default "yui-dt-page" 4327 */ 4328 CLASS_PAGE : "yui-dt-page", 4329 4330 /** 4331 * Class name assigned to default indicators. 4332 * 4333 * @property DataTable.CLASS_DEFAULT 4334 * @type String 4335 * @static 4336 * @final 4337 * @default "yui-dt-default" 4338 */ 4339 CLASS_DEFAULT : "yui-dt-default", 4340 4341 /** 4342 * Class name assigned to previous indicators. 4343 * 4344 * @property DataTable.CLASS_PREVIOUS 4345 * @type String 4346 * @static 4347 * @final 4348 * @default "yui-dt-previous" 4349 */ 4350 CLASS_PREVIOUS : "yui-dt-previous", 4351 4352 /** 4353 * Class name assigned next indicators. 4354 * 4355 * @property DataTable.CLASS_NEXT 4356 * @type String 4357 * @static 4358 * @final 4359 * @default "yui-dt-next" 4360 */ 4361 CLASS_NEXT : "yui-dt-next", 4362 4363 /** 4364 * Class name assigned to first elements. 4365 * 4366 * @property DataTable.CLASS_FIRST 4367 * @type String 4368 * @static 4369 * @final 4370 * @default "yui-dt-first" 4371 */ 4372 CLASS_FIRST : "yui-dt-first", 4373 4374 /** 4375 * Class name assigned to last elements. 4376 * 4377 * @property DataTable.CLASS_LAST 4378 * @type String 4379 * @static 4380 * @final 4381 * @default "yui-dt-last" 4382 */ 4383 CLASS_LAST : "yui-dt-last", 4384 4385 /** 4386 * Class name assigned to Record elements. 4387 * 4388 * @property DataTable.CLASS_REC 4389 * @type String 4390 * @static 4391 * @final 4392 * @default "yui-dt-rec" 4393 */ 4394 CLASS_REC : "yui-dt-rec", 4395 4396 /** 4397 * Class name assigned to even elements. 4398 * 4399 * @property DataTable.CLASS_EVEN 4400 * @type String 4401 * @static 4402 * @final 4403 * @default "yui-dt-even" 4404 */ 4405 CLASS_EVEN : "yui-dt-even", 4406 4407 /** 4408 * Class name assigned to odd elements. 4409 * 4410 * @property DataTable.CLASS_ODD 4411 * @type String 4412 * @static 4413 * @final 4414 * @default "yui-dt-odd" 4415 */ 4416 CLASS_ODD : "yui-dt-odd", 4417 4418 /** 4419 * Class name assigned to selected elements. 4420 * 4421 * @property DataTable.CLASS_SELECTED 4422 * @type String 4423 * @static 4424 * @final 4425 * @default "yui-dt-selected" 4426 */ 4427 CLASS_SELECTED : "yui-dt-selected", 4428 4429 /** 4430 * Class name assigned to highlighted elements. 4431 * 4432 * @property DataTable.CLASS_HIGHLIGHTED 4433 * @type String 4434 * @static 4435 * @final 4436 * @default "yui-dt-highlighted" 4437 */ 4438 CLASS_HIGHLIGHTED : "yui-dt-highlighted", 4439 4440 /** 4441 * Class name assigned to hidden elements. 4442 * 4443 * @property DataTable.CLASS_HIDDEN 4444 * @type String 4445 * @static 4446 * @final 4447 * @default "yui-dt-hidden" 4448 */ 4449 CLASS_HIDDEN : "yui-dt-hidden", 4450 4451 /** 4452 * Class name assigned to disabled elements. 4453 * 4454 * @property DataTable.CLASS_DISABLED 4455 * @type String 4456 * @static 4457 * @final 4458 * @default "yui-dt-disabled" 4459 */ 4460 CLASS_DISABLED : "yui-dt-disabled", 4461 4462 /** 4463 * Class name assigned to empty indicators. 4464 * 4465 * @property DataTable.CLASS_EMPTY 4466 * @type String 4467 * @static 4468 * @final 4469 * @default "yui-dt-empty" 4470 */ 4471 CLASS_EMPTY : "yui-dt-empty", 4472 4473 /** 4474 * Class name assigned to loading indicatorx. 4475 * 4476 * @property DataTable.CLASS_LOADING 4477 * @type String 4478 * @static 4479 * @final 4480 * @default "yui-dt-loading" 4481 */ 4482 CLASS_LOADING : "yui-dt-loading", 4483 4484 /** 4485 * Class name assigned to error indicators. 4486 * 4487 * @property DataTable.CLASS_ERROR 4488 * @type String 4489 * @static 4490 * @final 4491 * @default "yui-dt-error" 4492 */ 4493 CLASS_ERROR : "yui-dt-error", 4494 4495 /** 4496 * Class name assigned to editable elements. 4497 * 4498 * @property DataTable.CLASS_EDITABLE 4499 * @type String 4500 * @static 4501 * @final 4502 * @default "yui-dt-editable" 4503 */ 4504 CLASS_EDITABLE : "yui-dt-editable", 4505 4506 /** 4507 * Class name assigned to draggable elements. 4508 * 4509 * @property DataTable.CLASS_DRAGGABLE 4510 * @type String 4511 * @static 4512 * @final 4513 * @default "yui-dt-draggable" 4514 */ 4515 CLASS_DRAGGABLE : "yui-dt-draggable", 4516 4517 /** 4518 * Class name assigned to resizeable elements. 4519 * 4520 * @property DataTable.CLASS_RESIZEABLE 4521 * @type String 4522 * @static 4523 * @final 4524 * @default "yui-dt-resizeable" 4525 */ 4526 CLASS_RESIZEABLE : "yui-dt-resizeable", 4527 4528 /** 4529 * Class name assigned to scrollable elements. 4530 * 4531 * @property DataTable.CLASS_SCROLLABLE 4532 * @type String 4533 * @static 4534 * @final 4535 * @default "yui-dt-scrollable" 4536 */ 4537 CLASS_SCROLLABLE : "yui-dt-scrollable", 4538 4539 /** 4540 * Class name assigned to sortable elements. 4541 * 4542 * @property DataTable.CLASS_SORTABLE 4543 * @type String 4544 * @static 4545 * @final 4546 * @default "yui-dt-sortable" 4547 */ 4548 CLASS_SORTABLE : "yui-dt-sortable", 4549 4550 /** 4551 * Class name assigned to ascending elements. 4552 * 4553 * @property DataTable.CLASS_ASC 4554 * @type String 4555 * @static 4556 * @final 4557 * @default "yui-dt-asc" 4558 */ 4559 CLASS_ASC : "yui-dt-asc", 4560 4561 /** 4562 * Class name assigned to descending elements. 4563 * 4564 * @property DataTable.CLASS_DESC 4565 * @type String 4566 * @static 4567 * @final 4568 * @default "yui-dt-desc" 4569 */ 4570 CLASS_DESC : "yui-dt-desc", 4571 4572 /** 4573 * Class name assigned to BUTTON elements and/or container elements. 4574 * 4575 * @property DataTable.CLASS_BUTTON 4576 * @type String 4577 * @static 4578 * @final 4579 * @default "yui-dt-button" 4580 */ 4581 CLASS_BUTTON : "yui-dt-button", 4582 4583 /** 4584 * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements. 4585 * 4586 * @property DataTable.CLASS_CHECKBOX 4587 * @type String 4588 * @static 4589 * @final 4590 * @default "yui-dt-checkbox" 4591 */ 4592 CLASS_CHECKBOX : "yui-dt-checkbox", 4593 4594 /** 4595 * Class name assigned to SELECT elements and/or container elements. 4596 * 4597 * @property DataTable.CLASS_DROPDOWN 4598 * @type String 4599 * @static 4600 * @final 4601 * @default "yui-dt-dropdown" 4602 */ 4603 CLASS_DROPDOWN : "yui-dt-dropdown", 4604 4605 /** 4606 * Class name assigned to INPUT TYPE=RADIO elements and/or container elements. 4607 * 4608 * @property DataTable.CLASS_RADIO 4609 * @type String 4610 * @static 4611 * @final 4612 * @default "yui-dt-radio" 4613 */ 4614 CLASS_RADIO : "yui-dt-radio", 4615 4616 ///////////////////////////////////////////////////////////////////////// 4617 // 4618 // Private static properties 4619 // 4620 ///////////////////////////////////////////////////////////////////////// 4621 4622 /** 4623 * Internal class variable for indexing multiple DataTable instances. 4624 * 4625 * @property DataTable._nCount 4626 * @type Number 4627 * @private 4628 * @static 4629 */ 4630 _nCount : 0, 4631 4632 /** 4633 * Internal class variable tracking current number of DataTable instances, 4634 * so that certain class values can be reset when all instances are destroyed. 4635 * 4636 * @property DataTable._nCurrentCount 4637 * @type Number 4638 * @private 4639 * @static 4640 */ 4641 _nCurrentCount : 0, 4642 4643 /** 4644 * Reference to the STYLE node that is dynamically created and updated 4645 * in order to manage Column widths. 4646 * 4647 * @property DataTable._elDynStyleNode 4648 * @type HTMLElement 4649 * @private 4650 * @static 4651 */ 4652 _elDynStyleNode : null, 4653 4654 /** 4655 * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility. 4656 * 4657 * @property DataTable._bDynStylesFallback 4658 * @type boolean 4659 * @private 4660 * @static 4661 */ 4662 _bDynStylesFallback : (ua.ie) ? true : false, 4663 4664 /** 4665 * Object literal hash of Columns and their dynamically create style rules. 4666 * 4667 * @property DataTable._oDynStyles 4668 * @type Object 4669 * @private 4670 * @static 4671 */ 4672 _oDynStyles : {}, 4673 4674 ///////////////////////////////////////////////////////////////////////// 4675 // 4676 // Private static methods 4677 // 4678 ///////////////////////////////////////////////////////////////////////// 4679 4680 /** 4681 * Clones object literal or array of object literals. 4682 * 4683 * @method DataTable._cloneObject 4684 * @param o {Object} Object. 4685 * @private 4686 * @static 4687 */ 4688 _cloneObject: function(o) { 4689 if(!lang.isValue(o)) { 4690 return o; 4691 } 4692 4693 var copy = {}; 4694 4695 if(o instanceof YAHOO.widget.BaseCellEditor) { 4696 copy = o; 4697 } 4698 else if(Object.prototype.toString.apply(o) === "[object RegExp]") { 4699 copy = o; 4700 } 4701 else if(lang.isFunction(o)) { 4702 copy = o; 4703 } 4704 else if(lang.isArray(o)) { 4705 var array = []; 4706 for(var i=0,len=o.length;i<len;i++) { 4707 array[i] = DT._cloneObject(o[i]); 4708 } 4709 copy = array; 4710 } 4711 else if(lang.isObject(o)) { 4712 for (var x in o){ 4713 if(lang.hasOwnProperty(o, x)) { 4714 if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) { 4715 copy[x] = DT._cloneObject(o[x]); 4716 } 4717 else { 4718 copy[x] = o[x]; 4719 } 4720 } 4721 } 4722 } 4723 else { 4724 copy = o; 4725 } 4726 4727 return copy; 4728 }, 4729 4730 /** 4731 * Formats a BUTTON element. 4732 * 4733 * @method DataTable.formatButton 4734 * @param el {HTMLElement} The element to format with markup. 4735 * @param oRecord {YAHOO.widget.Record} Record instance. 4736 * @param oColumn {YAHOO.widget.Column} Column instance. 4737 * @param oData {HTML} Data value for the cell. By default, the value 4738 * is what gets written to the BUTTON. String values are treated as markup 4739 * and inserted into the DOM with innerHTML. 4740 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4741 * @static 4742 */ 4743 formatButton : function(el, oRecord, oColumn, oData, oDataTable) { 4744 var sValue = lang.isValue(oData) ? oData : "Click"; 4745 //TODO: support YAHOO.widget.Button 4746 //if(YAHOO.widget.Button) { 4747 4748 //} 4749 //else { 4750 el.innerHTML = "<button type=\"button\" class=\""+ 4751 DT.CLASS_BUTTON + "\">" + sValue + "</button>"; 4752 //} 4753 }, 4754 4755 /** 4756 * Formats a CHECKBOX element. 4757 * 4758 * @method DataTable.formatCheckbox 4759 * @param el {HTMLElement} The element to format with markup. 4760 * @param oRecord {YAHOO.widget.Record} Record instance. 4761 * @param oColumn {YAHOO.widget.Column} Column instance. 4762 * @param oData {Object | Boolean | HTML} Data value for the cell. Can be a simple 4763 * Boolean to indicate whether checkbox is checked or not. Can be object literal 4764 * {checked:bBoolean, label:sLabel}. String values are treated as markup 4765 * and inserted into the DOM with innerHTML. 4766 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4767 * @static 4768 */ 4769 formatCheckbox : function(el, oRecord, oColumn, oData, oDataTable) { 4770 var bChecked = oData; 4771 bChecked = (bChecked) ? " checked=\"checked\"" : ""; 4772 el.innerHTML = "<input type=\"checkbox\"" + bChecked + 4773 " class=\"" + DT.CLASS_CHECKBOX + "\" />"; 4774 }, 4775 4776 /** 4777 * Formats currency. Default unit is USD. 4778 * 4779 * @method DataTable.formatCurrency 4780 * @param el {HTMLElement} The element to format with markup. 4781 * @param oRecord {YAHOO.widget.Record} Record instance. 4782 * @param oColumn {YAHOO.widget.Column} Column instance. 4783 * @param oData {Number} Data value for the cell. 4784 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4785 * @static 4786 */ 4787 formatCurrency : function(el, oRecord, oColumn, oData, oDataTable) { 4788 var oDT = oDataTable || this; 4789 el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || oDT.get("currencyOptions")); 4790 }, 4791 4792 /** 4793 * Formats JavaScript Dates. 4794 * 4795 * @method DataTable.formatDate 4796 * @param el {HTMLElement} The element to format with markup. 4797 * @param oRecord {YAHOO.widget.Record} Record instance. 4798 * @param oColumn {YAHOO.widget.Column} Column instance. 4799 * @param oData {Object} Data value for the cell, or null. String values are 4800 * treated as markup and inserted into the DOM with innerHTML. 4801 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4802 * @static 4803 */ 4804 formatDate : function(el, oRecord, oColumn, oData, oDataTable) { 4805 var oDT = oDataTable || this, 4806 oConfig = oColumn.dateOptions || oDT.get("dateOptions"); 4807 el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale); 4808 }, 4809 4810 /** 4811 * Formats SELECT elements. 4812 * 4813 * @method DataTable.formatDropdown 4814 * @param el {HTMLElement} The element to format with markup. 4815 * @param oRecord {YAHOO.widget.Record} Record instance. 4816 * @param oColumn {YAHOO.widget.Column} Column instance. 4817 * @param oData {Object} Data value for the cell, or null. String values may 4818 * be treated as markup and inserted into the DOM with innerHTML as element 4819 * label. 4820 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4821 * @static 4822 */ 4823 formatDropdown : function(el, oRecord, oColumn, oData, oDataTable) { 4824 var oDT = oDataTable || this, 4825 selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field), 4826 options = (lang.isArray(oColumn.dropdownOptions)) ? 4827 oColumn.dropdownOptions : null, 4828 4829 selectEl, 4830 collection = el.getElementsByTagName("select"); 4831 4832 // Create the form element only once, so we can attach the onChange listener 4833 if(collection.length === 0) { 4834 // Create SELECT element 4835 selectEl = document.createElement("select"); 4836 selectEl.className = DT.CLASS_DROPDOWN; 4837 selectEl = el.appendChild(selectEl); 4838 4839 // Add event listener 4840 Ev.addListener(selectEl,"change",oDT._onDropdownChange,oDT); 4841 } 4842 4843 selectEl = collection[0]; 4844 4845 // Update the form element 4846 if(selectEl) { 4847 // Clear out previous options 4848 selectEl.innerHTML = ""; 4849 4850 // We have options to populate 4851 if(options) { 4852 // Create OPTION elements 4853 for(var i=0; i<options.length; i++) { 4854 var option = options[i]; 4855 var optionEl = document.createElement("option"); 4856 optionEl.value = (lang.isValue(option.value)) ? 4857 option.value : option; 4858 // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor 4859 optionEl.innerHTML = (lang.isValue(option.text)) ? 4860 option.text : (lang.isValue(option.label)) ? option.label : option; 4861 optionEl = selectEl.appendChild(optionEl); 4862 if (optionEl.value == selectedValue) { 4863 optionEl.selected = true; 4864 } 4865 } 4866 } 4867 // Selected value is our only option 4868 else { 4869 selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>"; 4870 } 4871 } 4872 else { 4873 el.innerHTML = lang.isValue(oData) ? oData : ""; 4874 } 4875 }, 4876 4877 /** 4878 * Formats emails. 4879 * 4880 * @method DataTable.formatEmail 4881 * @param el {HTMLElement} The element to format with markup. 4882 * @param oRecord {YAHOO.widget.Record} Record instance. 4883 * @param oColumn {YAHOO.widget.Column} Column instance. 4884 * @param oData {String} Data value for the cell, or null. Values are 4885 * HTML-escaped. 4886 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4887 * @static 4888 */ 4889 formatEmail : function(el, oRecord, oColumn, oData, oDataTable) { 4890 if(lang.isString(oData)) { 4891 oData = lang.escapeHTML(oData); 4892 el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>"; 4893 } 4894 else { 4895 el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : ""; 4896 } 4897 }, 4898 4899 /** 4900 * Formats links. 4901 * 4902 * @method DataTable.formatLink 4903 * @param el {HTMLElement} The element to format with markup. 4904 * @param oRecord {YAHOO.widget.Record} Record instance. 4905 * @param oColumn {YAHOO.widget.Column} Column instance. 4906 * @param oData {String} Data value for the cell, or null. Values are 4907 * HTML-escaped 4908 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4909 * @static 4910 */ 4911 formatLink : function(el, oRecord, oColumn, oData, oDataTable) { 4912 if(lang.isString(oData)) { 4913 oData = lang.escapeHTML(oData); 4914 el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>"; 4915 } 4916 else { 4917 el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : ""; 4918 } 4919 }, 4920 4921 /** 4922 * Formats numbers. 4923 * 4924 * @method DataTable.formatNumber 4925 * @param el {HTMLElement} The element to format with markup. 4926 * @param oRecord {YAHOO.widget.Record} Record instance. 4927 * @param oColumn {YAHOO.widget.Column} Column instance. 4928 * @param oData {Object} Data value for the cell, or null. 4929 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4930 * @static 4931 */ 4932 formatNumber : function(el, oRecord, oColumn, oData, oDataTable) { 4933 var oDT = oDataTable || this; 4934 el.innerHTML = util.Number.format(oData, oColumn.numberOptions || oDT.get("numberOptions")); 4935 }, 4936 4937 /** 4938 * Formats INPUT TYPE=RADIO elements. 4939 * 4940 * @method DataTable.formatRadio 4941 * @param el {HTMLElement} The element to format with markup. 4942 * @param oRecord {YAHOO.widget.Record} Record instance. 4943 * @param oColumn {YAHOO.widget.Column} Column instance. 4944 * @param oData {Object} (Optional) Data value for the cell. 4945 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4946 * @static 4947 */ 4948 formatRadio : function(el, oRecord, oColumn, oData, oDataTable) { 4949 var oDT = oDataTable || this, 4950 bChecked = oData; 4951 bChecked = (bChecked) ? " checked=\"checked\"" : ""; 4952 el.innerHTML = "<input type=\"radio\"" + bChecked + 4953 " name=\""+oDT.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" + 4954 " class=\"" + DT.CLASS_RADIO+ "\" />"; 4955 }, 4956 4957 /** 4958 * Formats text strings. 4959 * 4960 * @method DataTable.formatText 4961 * @param el {HTMLElement} The element to format with markup. 4962 * @param oRecord {YAHOO.widget.Record} Record instance. 4963 * @param oColumn {YAHOO.widget.Column} Column instance. 4964 * @param oData {String} (Optional) Data value for the cell. Values are 4965 * HTML-escaped. 4966 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4967 * @static 4968 */ 4969 formatText : function(el, oRecord, oColumn, oData, oDataTable) { 4970 var value = (lang.isValue(oData)) ? oData : ""; 4971 el.innerHTML = lang.escapeHTML(value.toString()); 4972 }, 4973 4974 /** 4975 * Formats TEXTAREA elements. 4976 * 4977 * @method DataTable.formatTextarea 4978 * @param el {HTMLElement} The element to format with markup. 4979 * @param oRecord {YAHOO.widget.Record} Record instance. 4980 * @param oColumn {YAHOO.widget.Column} Column instance. 4981 * @param oData {Object} (Optional) Data value for the cell. Values are 4982 * HTML-escaped. 4983 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4984 * @static 4985 */ 4986 formatTextarea : function(el, oRecord, oColumn, oData, oDataTable) { 4987 var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "", 4988 markup = "<textarea>" + value + "</textarea>"; 4989 el.innerHTML = markup; 4990 }, 4991 4992 /** 4993 * Formats INPUT TYPE=TEXT elements. 4994 * 4995 * @method DataTable.formatTextbox 4996 * @param el {HTMLElement} The element to format with markup. 4997 * @param oRecord {YAHOO.widget.Record} Record instance. 4998 * @param oColumn {YAHOO.widget.Column} Column instance. 4999 * @param oData {Object} (Optional) Data value for the cell. Values are 5000 * HTML-escaped. 5001 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 5002 * @static 5003 */ 5004 formatTextbox : function(el, oRecord, oColumn, oData, oDataTable) { 5005 var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "", 5006 markup = "<input type=\"text\" value=\"" + value + "\" />"; 5007 el.innerHTML = markup; 5008 }, 5009 5010 /** 5011 * Default cell formatter 5012 * 5013 * @method DataTable.formatDefault 5014 * @param el {HTMLElement} The element to format with markup. 5015 * @param oRecord {YAHOO.widget.Record} Record instance. 5016 * @param oColumn {YAHOO.widget.Column} Column instance. 5017 * @param oData {HTML} (Optional) Data value for the cell. String values are 5018 * treated as markup and inserted into the DOM with innerHTML. 5019 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 5020 * @static 5021 */ 5022 formatDefault : function(el, oRecord, oColumn, oData, oDataTable) { 5023 el.innerHTML = (lang.isValue(oData) && oData !== "") ? oData.toString() : " "; 5024 }, 5025 5026 /** 5027 * Validates data value to type Number, doing type conversion as 5028 * necessary. A valid Number value is return, else null is returned 5029 * if input value does not validate. 5030 * 5031 * 5032 * @method DataTable.validateNumber 5033 * @param oData {Object} Data to validate. 5034 * @static 5035 */ 5036 validateNumber : function(oData) { 5037 //Convert to number 5038 var number = oData * 1; 5039 5040 // Validate 5041 if(lang.isNumber(number)) { 5042 return number; 5043 } 5044 else { 5045 YAHOO.log("Could not validate data " + lang.dump(oData) + " to type Number", "warn", this.toString()); 5046 return undefined; 5047 } 5048 } 5049 }); 5050 5051 // Done in separate step so referenced functions are defined. 5052 /** 5053 * Registry of cell formatting functions, enables shortcut pointers in Column 5054 * definition formatter value (i.e., {key:"myColumn", formatter:"date"}). 5055 * @property DataTable.Formatter 5056 * @type Object 5057 * @static 5058 */ 5059 DT.Formatter = { 5060 button : DT.formatButton, 5061 checkbox : DT.formatCheckbox, 5062 currency : DT.formatCurrency, 5063 "date" : DT.formatDate, 5064 dropdown : DT.formatDropdown, 5065 email : DT.formatEmail, 5066 link : DT.formatLink, 5067 "number" : DT.formatNumber, 5068 radio : DT.formatRadio, 5069 text : DT.formatText, 5070 textarea : DT.formatTextarea, 5071 textbox : DT.formatTextbox, 5072 5073 defaultFormatter : DT.formatDefault 5074 }; 5075 5076 lang.extend(DT, util.Element, { 5077 5078 ///////////////////////////////////////////////////////////////////////////// 5079 // 5080 // Superclass methods 5081 // 5082 ///////////////////////////////////////////////////////////////////////////// 5083 5084 /** 5085 * Implementation of Element's abstract method. Sets up config values. 5086 * 5087 * @method initAttributes 5088 * @param oConfigs {Object} (Optional) Object literal definition of configuration values. 5089 * @private 5090 */ 5091 5092 initAttributes : function(oConfigs) { 5093 oConfigs = oConfigs || {}; 5094 DT.superclass.initAttributes.call(this, oConfigs); 5095 5096 /** 5097 * @attribute summary 5098 * @description String value for the SUMMARY attribute. 5099 * @type String 5100 * @default "" 5101 */ 5102 this.setAttributeConfig("summary", { 5103 value: "", 5104 validator: lang.isString, 5105 method: function(sSummary) { 5106 if(this._elTable) { 5107 this._elTable.summary = sSummary; 5108 } 5109 } 5110 }); 5111 5112 /** 5113 * @attribute selectionMode 5114 * @description Specifies row or cell selection mode. Accepts the following strings: 5115 * <dl> 5116 * <dt>"standard"</dt> 5117 * <dd>Standard row selection with support for modifier keys to enable 5118 * multiple selections.</dd> 5119 * 5120 * <dt>"single"</dt> 5121 * <dd>Row selection with modifier keys disabled to not allow 5122 * multiple selections.</dd> 5123 * 5124 * <dt>"singlecell"</dt> 5125 * <dd>Cell selection with modifier keys disabled to not allow 5126 * multiple selections.</dd> 5127 * 5128 * <dt>"cellblock"</dt> 5129 * <dd>Cell selection with support for modifier keys to enable multiple 5130 * selections in a block-fashion, like a spreadsheet.</dd> 5131 * 5132 * <dt>"cellrange"</dt> 5133 * <dd>Cell selection with support for modifier keys to enable multiple 5134 * selections in a range-fashion, like a calendar.</dd> 5135 * </dl> 5136 * 5137 * @default "standard" 5138 * @type String 5139 */ 5140 this.setAttributeConfig("selectionMode", { 5141 value: "standard", 5142 validator: lang.isString 5143 }); 5144 5145 /** 5146 * @attribute sortedBy 5147 * @description Object literal provides metadata for initial sort values if 5148 * data will arrive pre-sorted: 5149 * <dl> 5150 * <dt>sortedBy.key</dt> 5151 * <dd>{String} Key of sorted Column</dd> 5152 * <dt>sortedBy.dir</dt> 5153 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd> 5154 * </dl> 5155 * @type Object | null 5156 */ 5157 this.setAttributeConfig("sortedBy", { 5158 value: null, 5159 // TODO: accepted array for nested sorts 5160 validator: function(oNewSortedBy) { 5161 if(oNewSortedBy) { 5162 return (lang.isObject(oNewSortedBy) && oNewSortedBy.key); 5163 } 5164 else { 5165 return (oNewSortedBy === null); 5166 } 5167 }, 5168 method: function(oNewSortedBy) { 5169 // Stash the previous value 5170 var oOldSortedBy = this.get("sortedBy"); 5171 5172 // Workaround for bug 1827195 5173 this._configs.sortedBy.value = oNewSortedBy; 5174 5175 // Remove ASC/DESC from TH 5176 var oOldColumn, 5177 nOldColumnKeyIndex, 5178 oNewColumn, 5179 nNewColumnKeyIndex; 5180 5181 if(this._elThead) { 5182 if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) { 5183 oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key); 5184 nOldColumnKeyIndex = oOldColumn.getKeyIndex(); 5185 5186 // Remove previous UI from THEAD 5187 var elOldTh = oOldColumn.getThEl(); 5188 Dom.removeClass(elOldTh, oOldSortedBy.dir); 5189 this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy); 5190 } 5191 if(oNewSortedBy) { 5192 oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key); 5193 nNewColumnKeyIndex = oNewColumn.getKeyIndex(); 5194 5195 // Update THEAD with new UI 5196 var elNewTh = oNewColumn.getThEl(); 5197 // Backward compatibility 5198 if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") || (oNewSortedBy.dir == "desc"))) { 5199 var newClass = (oNewSortedBy.dir == "desc") ? 5200 DT.CLASS_DESC : 5201 DT.CLASS_ASC; 5202 Dom.addClass(elNewTh, newClass); 5203 } 5204 else { 5205 var sortClass = oNewSortedBy.dir || DT.CLASS_ASC; 5206 Dom.addClass(elNewTh, sortClass); 5207 } 5208 this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy); 5209 } 5210 } 5211 5212 if(this._elTbody) { 5213 // Update TBODY UI 5214 this._elTbody.style.display = "none"; 5215 var allRows = this._elTbody.rows, 5216 allCells; 5217 for(var i=allRows.length-1; i>-1; i--) { 5218 allCells = allRows[i].childNodes; 5219 if(allCells[nOldColumnKeyIndex]) { 5220 Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir); 5221 } 5222 if(allCells[nNewColumnKeyIndex]) { 5223 Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir); 5224 } 5225 } 5226 this._elTbody.style.display = ""; 5227 } 5228 5229 this._clearTrTemplateEl(); 5230 } 5231 }); 5232 5233 /** 5234 * @attribute paginator 5235 * @description An instance of YAHOO.widget.Paginator. 5236 * @default null 5237 * @type {Object|YAHOO.widget.Paginator} 5238 */ 5239 this.setAttributeConfig("paginator", { 5240 value : null, 5241 validator : function (val) { 5242 return val === null || val instanceof widget.Paginator; 5243 }, 5244 method : function () { this._updatePaginator.apply(this,arguments); } 5245 }); 5246 5247 /** 5248 * @attribute caption 5249 * @description Value for the CAPTION element. String values are treated as 5250 * markup and inserted into the DOM with innerHTML. NB: Not supported in 5251 * ScrollingDataTable. 5252 * @type HTML 5253 */ 5254 this.setAttributeConfig("caption", { 5255 value: null, 5256 validator: lang.isString, 5257 method: function(sCaption) { 5258 this._initCaptionEl(sCaption); 5259 } 5260 }); 5261 5262 /** 5263 * @attribute draggableColumns 5264 * @description True if Columns are draggable to reorder, false otherwise. 5265 * The Drag & Drop Utility is required to enable this feature. Only top-level 5266 * and non-nested Columns are draggable. Write once. 5267 * @default false 5268 * @type Boolean 5269 */ 5270 this.setAttributeConfig("draggableColumns", { 5271 value: false, 5272 validator: lang.isBoolean, 5273 method: function(oParam) { 5274 if(this._elThead) { 5275 if(oParam) { 5276 this._initDraggableColumns(); 5277 } 5278 else { 5279 this._destroyDraggableColumns(); 5280 } 5281 } 5282 } 5283 }); 5284 5285 /** 5286 * @attribute renderLoopSize 5287 * @description A value greater than 0 enables DOM rendering of rows to be 5288 * executed from a non-blocking timeout queue and sets how many rows to be 5289 * rendered per timeout. Recommended for very large data sets. 5290 * @type Number 5291 * @default 0 5292 */ 5293 this.setAttributeConfig("renderLoopSize", { 5294 value: 0, 5295 validator: lang.isNumber 5296 }); 5297 5298 /** 5299 * @attribute sortFunction 5300 * @description Default Column sort function, receives the following args: 5301 * <dl> 5302 * <dt>a {Object}</dt> 5303 * <dd>First sort argument.</dd> 5304 * <dt>b {Object}</dt> 5305 * <dd>Second sort argument.</dd> 5306 5307 * <dt>desc {Boolean}</dt> 5308 * <dd>True if sort direction is descending, false if 5309 * sort direction is ascending.</dd> 5310 * <dt>field {String}</dt> 5311 * <dd>The field to sort by, from sortOptions.field</dd> 5312 * </dl> 5313 * @type function 5314 */ 5315 this.setAttributeConfig("sortFunction", { 5316 value: function(a, b, desc, field) { 5317 var compare = YAHOO.util.Sort.compare, 5318 sorted = compare(a.getData(field),b.getData(field), desc); 5319 if(sorted === 0) { 5320 return compare(a.getCount(),b.getCount(), desc); // Bug 1932978 5321 } 5322 else { 5323 return sorted; 5324 } 5325 } 5326 }); 5327 5328 /** 5329 * @attribute formatRow 5330 * @description A function that accepts a TR element and its associated Record 5331 * for custom formatting. The function must return TRUE in order to automatically 5332 * continue formatting of child TD elements, else TD elements will not be 5333 * automatically formatted. 5334 * @type function 5335 * @default null 5336 */ 5337 this.setAttributeConfig("formatRow", { 5338 value: null, 5339 validator: lang.isFunction 5340 }); 5341 5342 /** 5343 * @attribute generateRequest 5344 * @description A function that converts an object literal of desired DataTable 5345 * states into a request value which is then passed to the DataSource's 5346 * sendRequest method in order to retrieve data for those states. This 5347 * function is passed an object literal of state data and a reference to the 5348 * DataTable instance: 5349 * 5350 * <dl> 5351 * <dt>pagination<dt> 5352 * <dd> 5353 * <dt>offsetRecord</dt> 5354 * <dd>{Number} Index of the first Record of the desired page</dd> 5355 * <dt>rowsPerPage</dt> 5356 * <dd>{Number} Number of rows per page</dd> 5357 * </dd> 5358 * <dt>sortedBy</dt> 5359 * <dd> 5360 * <dt>key</dt> 5361 * <dd>{String} Key of sorted Column</dd> 5362 * <dt>dir</dt> 5363 * <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd> 5364 * </dd> 5365 * <dt>self</dt> 5366 * <dd>The DataTable instance</dd> 5367 * </dl> 5368 * 5369 * and by default returns a String of syntax: 5370 * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}" 5371 * @type function 5372 * @default HTMLFunction 5373 */ 5374 this.setAttributeConfig("generateRequest", { 5375 value: function(oState, oSelf) { 5376 // Set defaults 5377 oState = oState || {pagination:null, sortedBy:null}; 5378 var sort = encodeURIComponent((oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey()); 5379 var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc"; 5380 var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0; 5381 var results = (oState.pagination) ? oState.pagination.rowsPerPage : null; 5382 5383 // Build the request 5384 return "sort=" + sort + 5385 "&dir=" + dir + 5386 "&startIndex=" + startIndex + 5387 ((results !== null) ? "&results=" + results : ""); 5388 }, 5389 validator: lang.isFunction 5390 }); 5391 5392 /** 5393 * @attribute initialRequest 5394 * @description Defines the initial request that gets sent to the DataSource 5395 * during initialization. Value is ignored if initialLoad is set to any value 5396 * other than true. 5397 * @type MIXED 5398 * @default null 5399 */ 5400 this.setAttributeConfig("initialRequest", { 5401 value: null 5402 }); 5403 5404 /** 5405 * @attribute initialLoad 5406 * @description Determines whether or not to load data at instantiation. By 5407 * default, will trigger a sendRequest() to the DataSource and pass in the 5408 * request defined by initialRequest. If set to false, data will not load 5409 * at instantiation. Alternatively, implementers who wish to work with a 5410 * custom payload may pass in an object literal with the following values: 5411 * 5412 * <dl> 5413 * <dt>request (MIXED)</dt> 5414 * <dd>Request value.</dd> 5415 * 5416 * <dt>argument (MIXED)</dt> 5417 * <dd>Custom data that will be passed through to the callback function.</dd> 5418 * </dl> 5419 * 5420 * 5421 * @type Boolean | Object 5422 * @default true 5423 */ 5424 this.setAttributeConfig("initialLoad", { 5425 value: true 5426 }); 5427 5428 /** 5429 * @attribute dynamicData 5430 * @description If true, sorting and pagination are relegated to the DataSource 5431 * for handling, using the request returned by the "generateRequest" function. 5432 * Each new DataSource response blows away all previous Records. False by default, so 5433 * sorting and pagination will be handled directly on the client side, without 5434 * causing any new requests for data from the DataSource. 5435 * @type Boolean 5436 * @default false 5437 */ 5438 this.setAttributeConfig("dynamicData", { 5439 value: false, 5440 validator: lang.isBoolean 5441 }); 5442 5443 /** 5444 * @attribute MSG_EMPTY 5445 * @description Message to display if DataTable has no data. String 5446 * values are treated as markup and inserted into the DOM with innerHTML. 5447 * @type HTML 5448 * @default "No records found." 5449 */ 5450 this.setAttributeConfig("MSG_EMPTY", { 5451 value: "No records found.", 5452 validator: lang.isString 5453 }); 5454 5455 /** 5456 * @attribute MSG_LOADING 5457 * @description Message to display while DataTable is loading data. String 5458 * values are treated as markup and inserted into the DOM with innerHTML. 5459 * @type HTML 5460 * @default "Loading..." 5461 */ 5462 this.setAttributeConfig("MSG_LOADING", { 5463 value: "Loading...", 5464 validator: lang.isString 5465 }); 5466 5467 /** 5468 * @attribute MSG_ERROR 5469 * @description Message to display while DataTable has data error. String 5470 * values are treated as markup and inserted into the DOM with innerHTML. 5471 * @type HTML 5472 * @default "Data error." 5473 */ 5474 this.setAttributeConfig("MSG_ERROR", { 5475 value: "Data error.", 5476 validator: lang.isString 5477 }); 5478 5479 /** 5480 * @attribute MSG_SORTASC 5481 * @description Message to display in tooltip to sort Column in ascending 5482 * order. String values are treated as markup and inserted into the DOM as 5483 * innerHTML. 5484 * @type HTML 5485 * @default "Click to sort ascending" 5486 */ 5487 this.setAttributeConfig("MSG_SORTASC", { 5488 value: "Click to sort ascending", 5489 validator: lang.isString, 5490 method: function(sParam) { 5491 if(this._elThead) { 5492 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) { 5493 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) { 5494 allKeys[i]._elThLabel.firstChild.title = sParam; 5495 } 5496 } 5497 } 5498 } 5499 }); 5500 5501 /** 5502 * @attribute MSG_SORTDESC 5503 * @description Message to display in tooltip to sort Column in descending 5504 * order. String values are treated as markup and inserted into the DOM as 5505 * innerHTML. 5506 * @type HTML 5507 * @default "Click to sort descending" 5508 */ 5509 this.setAttributeConfig("MSG_SORTDESC", { 5510 value: "Click to sort descending", 5511 validator: lang.isString, 5512 method: function(sParam) { 5513 if(this._elThead) { 5514 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) { 5515 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) { 5516 allKeys[i]._elThLabel.firstChild.title = sParam; 5517 } 5518 } 5519 } 5520 } 5521 }); 5522 5523 /** 5524 * @attribute currencySymbol 5525 * @deprecated Use currencyOptions. 5526 */ 5527 this.setAttributeConfig("currencySymbol", { 5528 value: "$", 5529 validator: lang.isString 5530 }); 5531 5532 /** 5533 * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter. 5534 * @attribute currencyOptions 5535 * @type Object 5536 * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","} 5537 */ 5538 this.setAttributeConfig("currencyOptions", { 5539 value: { 5540 prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol 5541 decimalPlaces:2, 5542 decimalSeparator:".", 5543 thousandsSeparator:"," 5544 } 5545 }); 5546 5547 /** 5548 * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter. 5549 * @attribute dateOptions 5550 * @type Object 5551 * @default {format:"%m/%d/%Y", locale:"en"} 5552 */ 5553 this.setAttributeConfig("dateOptions", { 5554 value: {format:"%m/%d/%Y", locale:"en"} 5555 }); 5556 5557 /** 5558 * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter. 5559 * @attribute numberOptions 5560 * @type Object 5561 * @default {decimalPlaces:0, thousandsSeparator:","} 5562 */ 5563 this.setAttributeConfig("numberOptions", { 5564 value: { 5565 decimalPlaces:0, 5566 thousandsSeparator:"," 5567 } 5568 }); 5569 5570 }, 5571 5572 ///////////////////////////////////////////////////////////////////////////// 5573 // 5574 // Private member variables 5575 // 5576 ///////////////////////////////////////////////////////////////////////////// 5577 5578 /** 5579 * True if instance is initialized, so as to fire the initEvent after render. 5580 * 5581 * @property _bInit 5582 * @type Boolean 5583 * @default true 5584 * @private 5585 */ 5586 _bInit : true, 5587 5588 /** 5589 * Index assigned to instance. 5590 * 5591 * @property _nIndex 5592 * @type Number 5593 * @private 5594 */ 5595 _nIndex : null, 5596 5597 /** 5598 * Counter for IDs assigned to TR elements. 5599 * 5600 * @property _nTrCount 5601 * @type Number 5602 * @private 5603 */ 5604 _nTrCount : 0, 5605 5606 /** 5607 * Counter for IDs assigned to TD elements. 5608 * 5609 * @property _nTdCount 5610 * @type Number 5611 * @private 5612 */ 5613 _nTdCount : 0, 5614 5615 /** 5616 * Unique id assigned to instance "yui-dtN", useful prefix for generating unique 5617 * DOM ID strings and log messages. 5618 * 5619 * @property _sId 5620 * @type String 5621 * @private 5622 */ 5623 _sId : null, 5624 5625 /** 5626 * Render chain. 5627 * 5628 * @property _oChainRender 5629 * @type YAHOO.util.Chain 5630 * @private 5631 */ 5632 _oChainRender : null, 5633 5634 /** 5635 * DOM reference to the container element for the DataTable instance into which 5636 * all other elements get created. 5637 * 5638 * @property _elContainer 5639 * @type HTMLElement 5640 * @private 5641 */ 5642 _elContainer : null, 5643 5644 /** 5645 * DOM reference to the mask element for the DataTable instance which disables it. 5646 * 5647 * @property _elMask 5648 * @type HTMLElement 5649 * @private 5650 */ 5651 _elMask : null, 5652 5653 /** 5654 * DOM reference to the TABLE element for the DataTable instance. 5655 * 5656 * @property _elTable 5657 * @type HTMLElement 5658 * @private 5659 */ 5660 _elTable : null, 5661 5662 /** 5663 * DOM reference to the CAPTION element for the DataTable instance. 5664 * 5665 * @property _elCaption 5666 * @type HTMLElement 5667 * @private 5668 */ 5669 _elCaption : null, 5670 5671 /** 5672 * DOM reference to the COLGROUP element for the DataTable instance. 5673 * 5674 * @property _elColgroup 5675 * @type HTMLElement 5676 * @private 5677 */ 5678 _elColgroup : null, 5679 5680 /** 5681 * DOM reference to the THEAD element for the DataTable instance. 5682 * 5683 * @property _elThead 5684 * @type HTMLElement 5685 * @private 5686 */ 5687 _elThead : null, 5688 5689 /** 5690 * DOM reference to the primary TBODY element for the DataTable instance. 5691 * 5692 * @property _elTbody 5693 * @type HTMLElement 5694 * @private 5695 */ 5696 _elTbody : null, 5697 5698 /** 5699 * DOM reference to the secondary TBODY element used to display DataTable messages. 5700 * 5701 * @property _elMsgTbody 5702 * @type HTMLElement 5703 * @private 5704 */ 5705 _elMsgTbody : null, 5706 5707 /** 5708 * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages. 5709 * 5710 * @property _elMsgTr 5711 * @type HTMLElement 5712 * @private 5713 */ 5714 _elMsgTr : null, 5715 5716 /** 5717 * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages. 5718 * 5719 * @property _elMsgTd 5720 * @type HTMLElement 5721 * @private 5722 */ 5723 _elMsgTd : null, 5724 5725 /** 5726 * Element reference to shared Column drag target. 5727 * 5728 * @property _elColumnDragTarget 5729 * @type HTMLElement 5730 * @private 5731 */ 5732 _elColumnDragTarget : null, 5733 5734 /** 5735 * Element reference to shared Column resizer proxy. 5736 * 5737 * @property _elColumnResizerProxy 5738 * @type HTMLElement 5739 * @private 5740 */ 5741 _elColumnResizerProxy : null, 5742 5743 /** 5744 * DataSource instance for the DataTable instance. 5745 * 5746 * @property _oDataSource 5747 * @type YAHOO.util.DataSource 5748 * @private 5749 */ 5750 _oDataSource : null, 5751 5752 /** 5753 * ColumnSet instance for the DataTable instance. 5754 * 5755 * @property _oColumnSet 5756 * @type YAHOO.widget.ColumnSet 5757 * @private 5758 */ 5759 _oColumnSet : null, 5760 5761 /** 5762 * RecordSet instance for the DataTable instance. 5763 * 5764 * @property _oRecordSet 5765 * @type YAHOO.widget.RecordSet 5766 * @private 5767 */ 5768 _oRecordSet : null, 5769 5770 /** 5771 * The active CellEditor instance for the DataTable instance. 5772 * 5773 * @property _oCellEditor 5774 * @type YAHOO.widget.CellEditor 5775 * @private 5776 */ 5777 _oCellEditor : null, 5778 5779 /** 5780 * ID string of first TR element of the current DataTable page. 5781 * 5782 * @property _sFirstTrId 5783 * @type String 5784 * @private 5785 */ 5786 _sFirstTrId : null, 5787 5788 /** 5789 * ID string of the last TR element of the current DataTable page. 5790 * 5791 * @property _sLastTrId 5792 * @type String 5793 * @private 5794 */ 5795 _sLastTrId : null, 5796 5797 /** 5798 * Template row to create all new rows from. 5799 * @property _elTrTemplate 5800 * @type {HTMLElement} 5801 * @private 5802 */ 5803 _elTrTemplate : null, 5804 5805 /** 5806 * Sparse array of custom functions to set column widths for browsers that don't 5807 * support dynamic CSS rules. Functions are added at the index representing 5808 * the number of rows they update. 5809 * 5810 * @property _aDynFunctions 5811 * @type Array 5812 * @private 5813 */ 5814 _aDynFunctions : [], 5815 5816 /** 5817 * Disabled state. 5818 * 5819 * @property _disabled 5820 * @type Boolean 5821 * @private 5822 */ 5823 _disabled : false, 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 ///////////////////////////////////////////////////////////////////////////// 5853 // 5854 // Private methods 5855 // 5856 ///////////////////////////////////////////////////////////////////////////// 5857 5858 /** 5859 * Clears browser text selection. Useful to call on rowSelectEvent or 5860 * cellSelectEvent to prevent clicks or dblclicks from selecting text in the 5861 * browser. 5862 * 5863 * @method clearTextSelection 5864 */ 5865 clearTextSelection : function() { 5866 var sel; 5867 if(window.getSelection) { 5868 sel = window.getSelection(); 5869 } 5870 else if(document.getSelection) { 5871 sel = document.getSelection(); 5872 } 5873 else if(document.selection) { 5874 sel = document.selection; 5875 } 5876 if(sel) { 5877 if(sel.empty) { 5878 sel.empty(); 5879 } 5880 else if (sel.removeAllRanges) { 5881 sel.removeAllRanges(); 5882 } 5883 else if(sel.collapse) { 5884 sel.collapse(); 5885 } 5886 } 5887 }, 5888 5889 /** 5890 * Sets focus on the given element. 5891 * 5892 * @method _focusEl 5893 * @param el {HTMLElement} Element. 5894 * @private 5895 */ 5896 _focusEl : function(el) { 5897 el = el || this._elTbody; 5898 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets 5899 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing 5900 // strange unexpected things as the user clicks on buttons and other controls. 5901 setTimeout(function() { 5902 try { 5903 el.focus(); 5904 } 5905 catch(e) { 5906 } 5907 },0); 5908 }, 5909 5910 /** 5911 * Forces Gecko repaint. 5912 * 5913 * @method _repaintGecko 5914 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body. 5915 * @private 5916 */ 5917 _repaintGecko : (ua.gecko) ? 5918 function(el) { 5919 el = el || this._elContainer; 5920 var parent = el.parentNode; 5921 var nextSibling = el.nextSibling; 5922 parent.insertBefore(parent.removeChild(el), nextSibling); 5923 } : function() {}, 5924 5925 /** 5926 * Forces Opera repaint. 5927 * 5928 * @method _repaintOpera 5929 * @private 5930 */ 5931 _repaintOpera : (ua.opera) ? 5932 function() { 5933 if(ua.opera) { 5934 document.documentElement.className += " "; 5935 document.documentElement.className = YAHOO.lang.trim(document.documentElement.className); 5936 } 5937 } : function() {} , 5938 5939 /** 5940 * Forces Webkit repaint. 5941 * 5942 * @method _repaintWebkit 5943 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body. 5944 * @private 5945 */ 5946 _repaintWebkit : (ua.webkit) ? 5947 function(el) { 5948 el = el || this._elContainer; 5949 var parent = el.parentNode; 5950 var nextSibling = el.nextSibling; 5951 parent.insertBefore(parent.removeChild(el), nextSibling); 5952 } : function() {}, 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 // INIT FUNCTIONS 5976 5977 /** 5978 * Initializes object literal of config values. 5979 * 5980 * @method _initConfigs 5981 * @param oConfig {Object} Object literal of config values. 5982 * @private 5983 */ 5984 _initConfigs : function(oConfigs) { 5985 if(!oConfigs || !lang.isObject(oConfigs)) { 5986 oConfigs = {}; 5987 } 5988 this.configs = oConfigs; 5989 }, 5990 5991 /** 5992 * Initializes ColumnSet. 5993 * 5994 * @method _initColumnSet 5995 * @param aColumnDefs {Object[]} Array of object literal Column definitions. 5996 * @private 5997 */ 5998 _initColumnSet : function(aColumnDefs) { 5999 var oColumn, i, len; 6000 6001 if(this._oColumnSet) { 6002 // First clear _oDynStyles for existing ColumnSet and 6003 // uregister CellEditor Custom Events 6004 for(i=0, len=this._oColumnSet.keys.length; i<len; i++) { 6005 oColumn = this._oColumnSet.keys[i]; 6006 DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined; 6007 if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility 6008 oColumn.editor.unsubscribeAll(); 6009 } 6010 } 6011 6012 this._oColumnSet = null; 6013 this._clearTrTemplateEl(); 6014 } 6015 6016 if(lang.isArray(aColumnDefs)) { 6017 this._oColumnSet = new YAHOO.widget.ColumnSet(aColumnDefs); 6018 } 6019 // Backward compatibility 6020 else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) { 6021 this._oColumnSet = aColumnDefs; 6022 YAHOO.log("DataTable's constructor now requires an array" + 6023 " of object literal Column definitions instead of a ColumnSet instance", 6024 "warn", this.toString()); 6025 } 6026 6027 // Register CellEditor Custom Events 6028 var allKeys = this._oColumnSet.keys; 6029 for(i=0, len=allKeys.length; i<len; i++) { 6030 oColumn = allKeys[i]; 6031 if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility 6032 oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true); 6033 oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true); 6034 oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true); 6035 oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true); 6036 oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true); 6037 oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true); 6038 oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true); 6039 oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true); 6040 } 6041 } 6042 }, 6043 6044 /** 6045 * Initializes DataSource. 6046 * 6047 * @method _initDataSource 6048 * @param oDataSource {YAHOO.util.DataSource} DataSource instance. 6049 * @private 6050 */ 6051 _initDataSource : function(oDataSource) { 6052 this._oDataSource = null; 6053 if(oDataSource && (lang.isFunction(oDataSource.sendRequest))) { 6054 this._oDataSource = oDataSource; 6055 } 6056 // Backward compatibility 6057 else { 6058 var tmpTable = null; 6059 var tmpContainer = this._elContainer; 6060 var i=0; 6061 //TODO: this will break if re-initing DS at runtime for SDT 6062 // Peek in container child nodes to see if TABLE already exists 6063 if(tmpContainer.hasChildNodes()) { 6064 var tmpChildren = tmpContainer.childNodes; 6065 for(i=0; i<tmpChildren.length; i++) { 6066 if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") { 6067 tmpTable = tmpChildren[i]; 6068 break; 6069 } 6070 } 6071 if(tmpTable) { 6072 var tmpFieldsArray = []; 6073 for(; i<this._oColumnSet.keys.length; i++) { 6074 tmpFieldsArray.push({key:this._oColumnSet.keys[i].key}); 6075 } 6076 6077 this._oDataSource = new DS(tmpTable); 6078 this._oDataSource.responseType = DS.TYPE_HTMLTABLE; 6079 this._oDataSource.responseSchema = {fields: tmpFieldsArray}; 6080 YAHOO.log("Null DataSource for progressive enhancement from" + 6081 " markup has been deprecated", "warn", this.toString()); 6082 } 6083 } 6084 } 6085 }, 6086 6087 /** 6088 * Initializes RecordSet. 6089 * 6090 * @method _initRecordSet 6091 * @private 6092 */ 6093 _initRecordSet : function() { 6094 if(this._oRecordSet) { 6095 this._oRecordSet.reset(); 6096 } 6097 else { 6098 this._oRecordSet = new YAHOO.widget.RecordSet(); 6099 } 6100 }, 6101 6102 /** 6103 * Initializes DOM elements. 6104 * 6105 * @method _initDomElements 6106 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 6107 * return {Boolean} False in case of error, otherwise true 6108 * @private 6109 */ 6110 _initDomElements : function(elContainer) { 6111 // Outer container 6112 this._initContainerEl(elContainer); 6113 // TABLE 6114 this._initTableEl(this._elContainer); 6115 // COLGROUP 6116 this._initColgroupEl(this._elTable); 6117 // THEAD 6118 this._initTheadEl(this._elTable); 6119 6120 // Message TBODY 6121 this._initMsgTbodyEl(this._elTable); 6122 6123 // Primary TBODY 6124 this._initTbodyEl(this._elTable); 6125 6126 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody) { 6127 return false; 6128 } 6129 else { 6130 return true; 6131 } 6132 }, 6133 6134 /** 6135 * Destroy's the DataTable outer container element, if available. 6136 * 6137 * @method _destroyContainerEl 6138 * @param elContainer {HTMLElement} Reference to the container element. 6139 * @private 6140 */ 6141 _destroyContainerEl : function(elContainer) { 6142 var columns = this._oColumnSet.keys, 6143 elements, i; 6144 6145 Dom.removeClass(elContainer, DT.CLASS_DATATABLE); 6146 6147 // Bug 2528783 6148 Ev.purgeElement( elContainer ); 6149 Ev.purgeElement( this._elThead, true ); // recursive to get resize handles 6150 Ev.purgeElement( this._elTbody ); 6151 Ev.purgeElement( this._elMsgTbody ); 6152 6153 // because change doesn't bubble, each select (via formatDropdown) gets 6154 // its own subscription 6155 elements = elContainer.getElementsByTagName( 'select' ); 6156 6157 if ( elements.length ) { 6158 Ev.detachListener( elements, 'change' ); 6159 } 6160 6161 for ( i = columns.length - 1; i >= 0; --i ) { 6162 if ( columns[i].editor ) { 6163 Ev.purgeElement( columns[i].editor._elContainer ); 6164 } 6165 } 6166 6167 elContainer.innerHTML = ""; 6168 6169 this._elContainer = null; 6170 this._elColgroup = null; 6171 this._elThead = null; 6172 this._elTbody = null; 6173 }, 6174 6175 /** 6176 * Initializes the DataTable outer container element, including a mask. 6177 * 6178 * @method _initContainerEl 6179 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 6180 * @private 6181 */ 6182 _initContainerEl : function(elContainer) { 6183 // Validate container 6184 elContainer = Dom.get(elContainer); 6185 6186 if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) { 6187 // Destroy previous 6188 this._destroyContainerEl(elContainer); 6189 6190 Dom.addClass(elContainer, DT.CLASS_DATATABLE); 6191 Ev.addListener(elContainer, "focus", this._onTableFocus, this); 6192 Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this); 6193 this._elContainer = elContainer; 6194 6195 var elMask = document.createElement("div"); 6196 elMask.className = DT.CLASS_MASK; 6197 elMask.style.display = "none"; 6198 this._elMask = elContainer.appendChild(elMask); 6199 } 6200 }, 6201 6202 /** 6203 * Destroy's the DataTable TABLE element, if available. 6204 * 6205 * @method _destroyTableEl 6206 * @private 6207 */ 6208 _destroyTableEl : function() { 6209 var elTable = this._elTable; 6210 if(elTable) { 6211 Ev.purgeElement(elTable, true); 6212 elTable.parentNode.removeChild(elTable); 6213 this._elCaption = null; 6214 this._elColgroup = null; 6215 this._elThead = null; 6216 this._elTbody = null; 6217 } 6218 }, 6219 6220 /** 6221 * Creates HTML markup CAPTION element. 6222 * 6223 * @method _initCaptionEl 6224 * @param sCaption {HTML} Caption value. String values are treated as markup and 6225 * inserted into the DOM with innerHTML. 6226 * @private 6227 */ 6228 _initCaptionEl : function(sCaption) { 6229 if(this._elTable && sCaption) { 6230 // Create CAPTION element 6231 if(!this._elCaption) { 6232 this._elCaption = this._elTable.createCaption(); 6233 } 6234 // Set CAPTION value 6235 this._elCaption.innerHTML = sCaption; 6236 } 6237 else if(this._elCaption) { 6238 this._elCaption.parentNode.removeChild(this._elCaption); 6239 } 6240 }, 6241 6242 /** 6243 * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer 6244 * container element. 6245 * 6246 * @method _initTableEl 6247 * @param elContainer {HTMLElement} Container element into which to create TABLE. 6248 * @private 6249 */ 6250 _initTableEl : function(elContainer) { 6251 if(elContainer) { 6252 // Destroy previous 6253 this._destroyTableEl(); 6254 6255 // Create TABLE 6256 this._elTable = elContainer.appendChild(document.createElement("table")); 6257 6258 // Set SUMMARY attribute 6259 this._elTable.summary = this.get("summary"); 6260 6261 // Create CAPTION element 6262 if(this.get("caption")) { 6263 this._initCaptionEl(this.get("caption")); 6264 } 6265 6266 // Set up mouseover/mouseout events via mouseenter/mouseleave delegation 6267 Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this); 6268 Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this); 6269 Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-data>tr>td", this); 6270 Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-data>tr>td", this); 6271 Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-message>tr>td", this); 6272 Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-message>tr>td", this); 6273 } 6274 }, 6275 6276 /** 6277 * Destroy's the DataTable COLGROUP element, if available. 6278 * 6279 * @method _destroyColgroupEl 6280 * @private 6281 */ 6282 _destroyColgroupEl : function() { 6283 var elColgroup = this._elColgroup; 6284 if(elColgroup) { 6285 var elTable = elColgroup.parentNode; 6286 Ev.purgeElement(elColgroup, true); 6287 elTable.removeChild(elColgroup); 6288 this._elColgroup = null; 6289 } 6290 }, 6291 6292 /** 6293 * Initializes COLGROUP and COL elements for managing minWidth. 6294 * 6295 * @method _initColgroupEl 6296 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP. 6297 * @private 6298 */ 6299 _initColgroupEl : function(elTable) { 6300 if(elTable) { 6301 // Destroy previous 6302 this._destroyColgroupEl(); 6303 6304 // Add COLs to DOCUMENT FRAGMENT 6305 var allCols = this._aColIds || [], 6306 allKeys = this._oColumnSet.keys, 6307 i = 0, len = allCols.length, 6308 elCol, oColumn, 6309 elFragment = document.createDocumentFragment(), 6310 elColTemplate = document.createElement("col"); 6311 6312 for(i=0,len=allKeys.length; i<len; i++) { 6313 oColumn = allKeys[i]; 6314 elCol = elFragment.appendChild(elColTemplate.cloneNode(false)); 6315 } 6316 6317 // Create COLGROUP 6318 var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild); 6319 elColgroup.appendChild(elFragment); 6320 this._elColgroup = elColgroup; 6321 } 6322 }, 6323 6324 /** 6325 * Adds a COL element to COLGROUP at given index. 6326 * 6327 * @method _insertColgroupColEl 6328 * @param index {Number} Index of new COL element. 6329 * @private 6330 */ 6331 _insertColgroupColEl : function(index) { 6332 if(lang.isNumber(index)&& this._elColgroup) { 6333 var nextSibling = this._elColgroup.childNodes[index] || null; 6334 this._elColgroup.insertBefore(document.createElement("col"), nextSibling); 6335 } 6336 }, 6337 6338 /** 6339 * Removes a COL element to COLGROUP at given index. 6340 * 6341 * @method _removeColgroupColEl 6342 * @param index {Number} Index of removed COL element. 6343 * @private 6344 */ 6345 _removeColgroupColEl : function(index) { 6346 if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) { 6347 this._elColgroup.removeChild(this._elColgroup.childNodes[index]); 6348 } 6349 }, 6350 6351 /** 6352 * Reorders a COL element from old index(es) to new index. 6353 * 6354 * @method _reorderColgroupColEl 6355 * @param aKeyIndexes {Number[]} Array of indexes of removed COL element. 6356 * @param newIndex {Number} New index. 6357 * @private 6358 */ 6359 _reorderColgroupColEl : function(aKeyIndexes, newIndex) { 6360 if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) { 6361 var i, 6362 tmpCols = []; 6363 // Remove COL 6364 for(i=aKeyIndexes.length-1; i>-1; i--) { 6365 tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]])); 6366 } 6367 // Insert COL 6368 var nextSibling = this._elColgroup.childNodes[newIndex] || null; 6369 for(i=tmpCols.length-1; i>-1; i--) { 6370 this._elColgroup.insertBefore(tmpCols[i], nextSibling); 6371 } 6372 } 6373 }, 6374 6375 /** 6376 * Destroy's the DataTable THEAD element, if available. 6377 * 6378 * @method _destroyTheadEl 6379 * @private 6380 */ 6381 _destroyTheadEl : function() { 6382 var elThead = this._elThead; 6383 if(elThead) { 6384 var elTable = elThead.parentNode; 6385 Ev.purgeElement(elThead, true); 6386 this._destroyColumnHelpers(); 6387 elTable.removeChild(elThead); 6388 this._elThead = null; 6389 } 6390 }, 6391 6392 /** 6393 * Initializes THEAD element. 6394 * 6395 * @method _initTheadEl 6396 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP. 6397 * @param {HTMLElement} Initialized THEAD element. 6398 * @private 6399 */ 6400 _initTheadEl : function(elTable) { 6401 elTable = elTable || this._elTable; 6402 6403 if(elTable) { 6404 // Destroy previous 6405 this._destroyTheadEl(); 6406 6407 //TODO: append to DOM later for performance 6408 var elThead = (this._elColgroup) ? 6409 elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) : 6410 elTable.appendChild(document.createElement("thead")); 6411 6412 // Set up DOM events for THEAD 6413 Ev.addListener(elThead, "focus", this._onTheadFocus, this); 6414 Ev.addListener(elThead, "keydown", this._onTheadKeydown, this); 6415 Ev.addListener(elThead, "mousedown", this._onTableMousedown, this); 6416 Ev.addListener(elThead, "mouseup", this._onTableMouseup, this); 6417 Ev.addListener(elThead, "click", this._onTheadClick, this); 6418 6419 // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave 6420 // delegation at the TABLE level 6421 6422 // Since we can't listen for click and dblclick on the same element... 6423 // Attach separately to THEAD and TBODY 6424 ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this); 6425 6426 var oColumnSet = this._oColumnSet, 6427 oColumn, i,j, l; 6428 6429 // Add TRs to the THEAD 6430 var colTree = oColumnSet.tree; 6431 var elTh; 6432 for(i=0; i<colTree.length; i++) { 6433 var elTheadTr = elThead.appendChild(document.createElement("tr")); 6434 6435 // ...and create TH cells 6436 for(j=0; j<colTree[i].length; j++) { 6437 oColumn = colTree[i][j]; 6438 elTh = elTheadTr.appendChild(document.createElement("th")); 6439 this._initThEl(elTh,oColumn); 6440 } 6441 6442 // Set FIRST/LAST on THEAD rows 6443 if(i === 0) { 6444 Dom.addClass(elTheadTr, DT.CLASS_FIRST); 6445 } 6446 if(i === (colTree.length-1)) { 6447 Dom.addClass(elTheadTr, DT.CLASS_LAST); 6448 } 6449 6450 } 6451 6452 // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array 6453 var aFirstHeaders = oColumnSet.headers[0] || []; 6454 for(i=0; i<aFirstHeaders.length; i++) { 6455 Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST); 6456 } 6457 var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || []; 6458 for(i=0; i<aLastHeaders.length; i++) { 6459 Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST); 6460 } 6461 6462 YAHOO.log("TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString()); 6463 6464 ///TODO: try _repaintGecko(this._elContainer) instead 6465 // Bug 1806891 6466 if(ua.webkit && ua.webkit < 420) { 6467 var oSelf = this; 6468 setTimeout(function() { 6469 elThead.style.display = ""; 6470 },0); 6471 elThead.style.display = 'none'; 6472 } 6473 6474 this._elThead = elThead; 6475 6476 // Column helpers needs _elThead to exist 6477 this._initColumnHelpers(); 6478 } 6479 }, 6480 6481 /** 6482 * Populates TH element as defined by Column. 6483 * 6484 * @method _initThEl 6485 * @param elTh {HTMLElement} TH element reference. 6486 * @param oColumn {YAHOO.widget.Column} Column object. 6487 * @private 6488 */ 6489 _initThEl : function(elTh, oColumn) { 6490 elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD 6491 elTh.innerHTML = ""; 6492 elTh.rowSpan = oColumn.getRowspan(); 6493 elTh.colSpan = oColumn.getColspan(); 6494 oColumn._elTh = elTh; 6495 6496 var elThLiner = elTh.appendChild(document.createElement("div")); 6497 elThLiner.id = elTh.id + "-liner"; // Needed for resizer 6498 elThLiner.className = DT.CLASS_LINER; 6499 oColumn._elThLiner = elThLiner; 6500 6501 var elThLabel = elThLiner.appendChild(document.createElement("span")); 6502 elThLabel.className = DT.CLASS_LABEL; 6503 6504 // Assign abbr attribute 6505 if(oColumn.abbr) { 6506 elTh.abbr = oColumn.abbr; 6507 } 6508 // Clear minWidth on hidden Columns 6509 if(oColumn.hidden) { 6510 this._clearMinWidth(oColumn); 6511 } 6512 6513 elTh.className = this._getColumnClassNames(oColumn); 6514 6515 // Set Column width... 6516 if(oColumn.width) { 6517 // Validate minWidth 6518 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ? 6519 oColumn.minWidth : oColumn.width; 6520 // ...for fallback cases 6521 if(DT._bDynStylesFallback) { 6522 elTh.firstChild.style.overflow = 'hidden'; 6523 elTh.firstChild.style.width = nWidth + 'px'; 6524 } 6525 // ...for non fallback cases 6526 else { 6527 this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden'); 6528 } 6529 } 6530 6531 this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy")); 6532 oColumn._elThLabel = elThLabel; 6533 }, 6534 6535 /** 6536 * Outputs markup into the given TH based on given Column. 6537 * 6538 * @method formatTheadCell 6539 * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner, 6540 * not the liner DIV element. 6541 * @param oColumn {YAHOO.widget.Column} Column instance. 6542 * @param oSortedBy {Object} Sort state object literal. 6543 */ 6544 formatTheadCell : function(elCellLabel, oColumn, oSortedBy) { 6545 var sKey = oColumn.getKey(); 6546 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey; 6547 6548 // Add accessibility link for sortable Columns 6549 if(oColumn.sortable) { 6550 // Calculate the direction 6551 var sSortClass = this.getColumnSortDir(oColumn, oSortedBy); 6552 var bDesc = (sSortClass === DT.CLASS_DESC); 6553 6554 // This is the sorted Column 6555 if(oSortedBy && (oColumn.key === oSortedBy.key)) { 6556 bDesc = !(oSortedBy.dir === DT.CLASS_DESC); 6557 } 6558 6559 // Generate a unique HREF for visited status 6560 var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey(); 6561 6562 // Generate a dynamic TITLE for sort status 6563 var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC"); 6564 6565 // Format the element 6566 elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>"; 6567 } 6568 // Just display the label for non-sortable Columns 6569 else { 6570 elCellLabel.innerHTML = sLabel; 6571 } 6572 }, 6573 6574 /** 6575 * Disables DD from top-level Column TH elements. 6576 * 6577 * @method _destroyDraggableColumns 6578 * @private 6579 */ 6580 _destroyDraggableColumns : function() { 6581 var oColumn, elTh; 6582 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) { 6583 oColumn = this._oColumnSet.tree[0][i]; 6584 if(oColumn._dd) { 6585 oColumn._dd = oColumn._dd.unreg(); 6586 Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE); 6587 } 6588 } 6589 6590 // Destroy column drag proxy 6591 this._destroyColumnDragTargetEl(); 6592 }, 6593 6594 /** 6595 * Initializes top-level Column TH elements into DD instances. 6596 * 6597 * @method _initDraggableColumns 6598 * @private 6599 */ 6600 _initDraggableColumns : function() { 6601 this._destroyDraggableColumns(); 6602 if(util.DD) { 6603 var oColumn, elTh, elDragTarget; 6604 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) { 6605 oColumn = this._oColumnSet.tree[0][i]; 6606 elTh = oColumn.getThEl(); 6607 Dom.addClass(elTh, DT.CLASS_DRAGGABLE); 6608 elDragTarget = this._initColumnDragTargetEl(); 6609 oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget); 6610 } 6611 } 6612 else { 6613 YAHOO.log("Could not find DragDrop for draggable Columns", "warn", this.toString()); 6614 } 6615 }, 6616 6617 /** 6618 * Destroys shared Column drag target. 6619 * 6620 * @method _destroyColumnDragTargetEl 6621 * @private 6622 */ 6623 _destroyColumnDragTargetEl : function() { 6624 if(this._elColumnDragTarget) { 6625 var el = this._elColumnDragTarget; 6626 YAHOO.util.Event.purgeElement(el); 6627 el.parentNode.removeChild(el); 6628 this._elColumnDragTarget = null; 6629 } 6630 }, 6631 6632 /** 6633 * Creates HTML markup for shared Column drag target. 6634 * 6635 * @method _initColumnDragTargetEl 6636 * @return {HTMLElement} Reference to Column drag target. 6637 * @private 6638 */ 6639 _initColumnDragTargetEl : function() { 6640 if(!this._elColumnDragTarget) { 6641 // Attach Column drag target element as first child of body 6642 var elColumnDragTarget = document.createElement('div'); 6643 elColumnDragTarget.id = this.getId() + "-coltarget"; 6644 elColumnDragTarget.className = DT.CLASS_COLTARGET; 6645 elColumnDragTarget.style.display = "none"; 6646 document.body.insertBefore(elColumnDragTarget, document.body.firstChild); 6647 6648 // Internal tracker of Column drag target 6649 this._elColumnDragTarget = elColumnDragTarget; 6650 6651 } 6652 return this._elColumnDragTarget; 6653 }, 6654 6655 /** 6656 * Disables resizeability on key Column TH elements. 6657 * 6658 * @method _destroyResizeableColumns 6659 * @private 6660 */ 6661 _destroyResizeableColumns : function() { 6662 var aKeys = this._oColumnSet.keys; 6663 for(var i=0, len=aKeys.length; i<len; i++) { 6664 if(aKeys[i]._ddResizer) { 6665 aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg(); 6666 Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE); 6667 } 6668 } 6669 6670 // Destroy resizer proxy 6671 this._destroyColumnResizerProxyEl(); 6672 }, 6673 6674 /** 6675 * Initializes resizeability on key Column TH elements. 6676 * 6677 * @method _initResizeableColumns 6678 * @private 6679 */ 6680 _initResizeableColumns : function() { 6681 this._destroyResizeableColumns(); 6682 if(util.DD) { 6683 var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick; 6684 for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) { 6685 oColumn = this._oColumnSet.keys[i]; 6686 if(oColumn.resizeable) { 6687 elTh = oColumn.getThEl(); 6688 Dom.addClass(elTh, DT.CLASS_RESIZEABLE); 6689 elThLiner = oColumn.getThLinerEl(); 6690 6691 // Bug 1915349: So resizer is as tall as TH when rowspan > 1 6692 // Create a separate resizer liner with position:relative 6693 elThResizerLiner = elTh.appendChild(document.createElement("div")); 6694 elThResizerLiner.className = DT.CLASS_RESIZERLINER; 6695 6696 // Move TH contents into the new resizer liner 6697 elThResizerLiner.appendChild(elThLiner); 6698 6699 // Create the resizer 6700 elThResizer = elThResizerLiner.appendChild(document.createElement("div")); 6701 elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer 6702 elThResizer.className = DT.CLASS_RESIZER; 6703 oColumn._elResizer = elThResizer; 6704 6705 // Create the resizer proxy, once per instance 6706 elResizerProxy = this._initColumnResizerProxyEl(); 6707 oColumn._ddResizer = new YAHOO.util.ColumnResizer( 6708 this, oColumn, elTh, elThResizer, elResizerProxy); 6709 cancelClick = function(e) { 6710 Ev.stopPropagation(e); 6711 }; 6712 Ev.addListener(elThResizer,"click",cancelClick); 6713 } 6714 } 6715 } 6716 else { 6717 YAHOO.log("Could not find DragDrop for resizeable Columns", "warn", this.toString()); 6718 } 6719 }, 6720 6721 /** 6722 * Destroys shared Column resizer proxy. 6723 * 6724 * @method _destroyColumnResizerProxyEl 6725 * @return {HTMLElement} Reference to Column resizer proxy. 6726 * @private 6727 */ 6728 _destroyColumnResizerProxyEl : function() { 6729 if(this._elColumnResizerProxy) { 6730 var el = this._elColumnResizerProxy; 6731 YAHOO.util.Event.purgeElement(el); 6732 el.parentNode.removeChild(el); 6733 this._elColumnResizerProxy = null; 6734 } 6735 }, 6736 6737 /** 6738 * Creates HTML markup for shared Column resizer proxy. 6739 * 6740 * @method _initColumnResizerProxyEl 6741 * @return {HTMLElement} Reference to Column resizer proxy. 6742 * @private 6743 */ 6744 _initColumnResizerProxyEl : function() { 6745 if(!this._elColumnResizerProxy) { 6746 // Attach Column resizer element as first child of body 6747 var elColumnResizerProxy = document.createElement("div"); 6748 elColumnResizerProxy.id = this.getId() + "-colresizerproxy"; // Needed for ColumnResizer 6749 elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY; 6750 document.body.insertBefore(elColumnResizerProxy, document.body.firstChild); 6751 6752 // Internal tracker of Column resizer proxy 6753 this._elColumnResizerProxy = elColumnResizerProxy; 6754 } 6755 return this._elColumnResizerProxy; 6756 }, 6757 6758 /** 6759 * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers. 6760 * 6761 * @method _destroyColumnHelpers 6762 * @private 6763 */ 6764 _destroyColumnHelpers : function() { 6765 this._destroyDraggableColumns(); 6766 this._destroyResizeableColumns(); 6767 }, 6768 6769 /** 6770 * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers. 6771 * 6772 * @method _initColumnHelpers 6773 * @private 6774 */ 6775 _initColumnHelpers : function() { 6776 if(this.get("draggableColumns")) { 6777 this._initDraggableColumns(); 6778 } 6779 this._initResizeableColumns(); 6780 }, 6781 6782 /** 6783 * Destroy's the DataTable TBODY element, if available. 6784 * 6785 * @method _destroyTbodyEl 6786 * @private 6787 */ 6788 _destroyTbodyEl : function() { 6789 var elTbody = this._elTbody; 6790 if(elTbody) { 6791 var elTable = elTbody.parentNode; 6792 Ev.purgeElement(elTbody, true); 6793 elTable.removeChild(elTbody); 6794 this._elTbody = null; 6795 } 6796 }, 6797 6798 /** 6799 * Initializes TBODY element for data. 6800 * 6801 * @method _initTbodyEl 6802 * @param elTable {HTMLElement} TABLE element into which to create TBODY . 6803 * @private 6804 */ 6805 _initTbodyEl : function(elTable) { 6806 if(elTable) { 6807 // Destroy previous 6808 this._destroyTbodyEl(); 6809 6810 // Create TBODY 6811 var elTbody = elTable.appendChild(document.createElement("tbody")); 6812 elTbody.tabIndex = 0; 6813 elTbody.className = DT.CLASS_DATA; 6814 6815 // Set up DOM events for TBODY 6816 Ev.addListener(elTbody, "focus", this._onTbodyFocus, this); 6817 Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this); 6818 Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this); 6819 Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this); 6820 Ev.addListener(elTbody, "click", this._onTbodyClick, this); 6821 6822 // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave 6823 // delegation at the TABLE level 6824 6825 // Since we can't listen for click and dblclick on the same element... 6826 // Attach separately to THEAD and TBODY 6827 ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this); 6828 6829 6830 // IE puts focus outline in the wrong place 6831 if(ua.ie) { 6832 elTbody.hideFocus=true; 6833 } 6834 6835 this._elTbody = elTbody; 6836 } 6837 }, 6838 6839 /** 6840 * Destroy's the DataTable message TBODY element, if available. 6841 * 6842 * @method _destroyMsgTbodyEl 6843 * @private 6844 */ 6845 _destroyMsgTbodyEl : function() { 6846 var elMsgTbody = this._elMsgTbody; 6847 if(elMsgTbody) { 6848 var elTable = elMsgTbody.parentNode; 6849 Ev.purgeElement(elMsgTbody, true); 6850 elTable.removeChild(elMsgTbody); 6851 this._elTbody = null; 6852 } 6853 }, 6854 6855 /** 6856 * Initializes TBODY element for messaging. 6857 * 6858 * @method _initMsgTbodyEl 6859 * @param elTable {HTMLElement} TABLE element into which to create TBODY 6860 * @private 6861 */ 6862 _initMsgTbodyEl : function(elTable) { 6863 if(elTable) { 6864 var elMsgTbody = document.createElement("tbody"); 6865 elMsgTbody.className = DT.CLASS_MESSAGE; 6866 var elMsgTr = elMsgTbody.appendChild(document.createElement("tr")); 6867 elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST; 6868 this._elMsgTr = elMsgTr; 6869 var elMsgTd = elMsgTr.appendChild(document.createElement("td")); 6870 elMsgTd.colSpan = this._oColumnSet.keys.length || 1; 6871 elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST; 6872 this._elMsgTd = elMsgTd; 6873 elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody); 6874 var elMsgLiner = elMsgTd.appendChild(document.createElement("div")); 6875 elMsgLiner.className = DT.CLASS_LINER; 6876 this._elMsgTbody = elMsgTbody; 6877 6878 // Set up DOM events for TBODY 6879 Ev.addListener(elMsgTbody, "focus", this._onTbodyFocus, this); 6880 Ev.addListener(elMsgTbody, "mousedown", this._onTableMousedown, this); 6881 Ev.addListener(elMsgTbody, "mouseup", this._onTableMouseup, this); 6882 Ev.addListener(elMsgTbody, "keydown", this._onTbodyKeydown, this); 6883 Ev.addListener(elMsgTbody, "click", this._onTbodyClick, this); 6884 6885 // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave 6886 // delegation at the TABLE level 6887 } 6888 }, 6889 6890 /** 6891 * Initialize internal event listeners 6892 * 6893 * @method _initEvents 6894 * @private 6895 */ 6896 _initEvents : function () { 6897 // Initialize Column sort 6898 this._initColumnSort(); 6899 6900 // Add the document level click listener 6901 YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this); 6902 6903 // Paginator integration 6904 this.subscribe("paginatorChange",function () { 6905 this._handlePaginatorChange.apply(this,arguments); 6906 }); 6907 6908 this.subscribe("initEvent",function () { 6909 this.renderPaginator(); 6910 }); 6911 6912 // Initialize CellEditor integration 6913 this._initCellEditing(); 6914 }, 6915 6916 /** 6917 * Initializes Column sorting. 6918 * 6919 * @method _initColumnSort 6920 * @private 6921 */ 6922 _initColumnSort : function() { 6923 this.subscribe("theadCellClickEvent", this.onEventSortColumn); 6924 6925 // Backward compatibility 6926 var oSortedBy = this.get("sortedBy"); 6927 if(oSortedBy) { 6928 if(oSortedBy.dir == "desc") { 6929 this._configs.sortedBy.value.dir = DT.CLASS_DESC; 6930 } 6931 else if(oSortedBy.dir == "asc") { 6932 this._configs.sortedBy.value.dir = DT.CLASS_ASC; 6933 } 6934 } 6935 }, 6936 6937 /** 6938 * Initializes CellEditor integration. 6939 * 6940 * @method _initCellEditing 6941 * @private 6942 */ 6943 _initCellEditing : function() { 6944 this.subscribe("editorBlurEvent",function () { 6945 this.onEditorBlurEvent.apply(this,arguments); 6946 }); 6947 this.subscribe("editorBlockEvent",function () { 6948 this.onEditorBlockEvent.apply(this,arguments); 6949 }); 6950 this.subscribe("editorUnblockEvent",function () { 6951 this.onEditorUnblockEvent.apply(this,arguments); 6952 }); 6953 }, 6954 6955 6956 6957 6958 6959 6960 6961 6962 6963 6964 6965 6966 6967 6968 6969 6970 6971 6972 6973 6974 6975 6976 6977 6978 6979 6980 6981 6982 6983 6984 6985 6986 6987 // DOM MUTATION FUNCTIONS 6988 6989 /** 6990 * Retruns classnames to represent current Column states. 6991 * @method _getColumnClassnames 6992 * @param oColumn {YAHOO.widget.Column} Column instance. 6993 * @param aAddClasses {String[]} An array of additional classnames to add to the 6994 * return value. 6995 * @return {String} A String of classnames to be assigned to TH or TD elements 6996 * for given Column. 6997 * @private 6998 */ 6999 _getColumnClassNames : function (oColumn, aAddClasses) { 7000 var allClasses; 7001 7002 // Add CSS classes 7003 if(lang.isString(oColumn.className)) { 7004 // Single custom class 7005 allClasses = [oColumn.className]; 7006 } 7007 else if(lang.isArray(oColumn.className)) { 7008 // Array of custom classes 7009 allClasses = oColumn.className; 7010 } 7011 else { 7012 // no custom classes 7013 allClasses = []; 7014 } 7015 7016 // Hook for setting width with via dynamic style uses key since ID is too disposable 7017 allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey(); 7018 7019 // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":" 7020 allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey(); 7021 7022 var isSortedBy = this.get("sortedBy") || {}; 7023 // Sorted 7024 if(oColumn.key === isSortedBy.key) { 7025 allClasses[allClasses.length] = isSortedBy.dir || ''; 7026 } 7027 // Hidden 7028 if(oColumn.hidden) { 7029 allClasses[allClasses.length] = DT.CLASS_HIDDEN; 7030 } 7031 // Selected 7032 if(oColumn.selected) { 7033 allClasses[allClasses.length] = DT.CLASS_SELECTED; 7034 } 7035 // Sortable 7036 if(oColumn.sortable) { 7037 allClasses[allClasses.length] = DT.CLASS_SORTABLE; 7038 } 7039 // Resizeable 7040 if(oColumn.resizeable) { 7041 allClasses[allClasses.length] = DT.CLASS_RESIZEABLE; 7042 } 7043 // Editable 7044 if(oColumn.editor) { 7045 allClasses[allClasses.length] = DT.CLASS_EDITABLE; 7046 } 7047 7048 // Addtnl classes, including First/Last 7049 if(aAddClasses) { 7050 allClasses = allClasses.concat(aAddClasses); 7051 } 7052 7053 return allClasses.join(' '); 7054 }, 7055 7056 /** 7057 * Clears TR element template in response to any Column state change. 7058 * @method _clearTrTemplateEl 7059 * @private 7060 */ 7061 _clearTrTemplateEl : function () { 7062 this._elTrTemplate = null; 7063 }, 7064 7065 /** 7066 * Returns a new TR element template with TD elements classed with current 7067 * Column states. 7068 * @method _getTrTemplateEl 7069 * @return {HTMLElement} A TR element to be cloned and added to the DOM. 7070 * @private 7071 */ 7072 _getTrTemplateEl : function (oRecord, index) { 7073 // Template is already available 7074 if(this._elTrTemplate) { 7075 return this._elTrTemplate; 7076 } 7077 // Template needs to be created 7078 else { 7079 var d = document, 7080 tr = d.createElement('tr'), 7081 td = d.createElement('td'), 7082 div = d.createElement('div'); 7083 7084 // Append the liner element 7085 td.appendChild(div); 7086 7087 // Create TD elements into DOCUMENT FRAGMENT 7088 var df = document.createDocumentFragment(), 7089 allKeys = this._oColumnSet.keys, 7090 elTd; 7091 7092 // Set state for each TD; 7093 var aAddClasses; 7094 for(var i=0, keysLen=allKeys.length; i<keysLen; i++) { 7095 // Clone the TD template 7096 elTd = td.cloneNode(true); 7097 7098 // Format the base TD 7099 elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1)); 7100 7101 df.appendChild(elTd); 7102 } 7103 tr.appendChild(df); 7104 tr.className = DT.CLASS_REC; 7105 this._elTrTemplate = tr; 7106 return tr; 7107 } 7108 }, 7109 7110 /** 7111 * Formats a basic TD element. 7112 * @method _formatTdEl 7113 * @param oColumn {YAHOO.widget.Column} Associated Column instance. 7114 * @param elTd {HTMLElement} An unformatted TD element. 7115 * @param index {Number} Column key index. 7116 * @param isLast {Boolean} True if Column is last key of the ColumnSet. 7117 * @return {HTMLElement} A formatted TD element. 7118 * @private 7119 */ 7120 _formatTdEl : function (oColumn, elTd, index, isLast) { 7121 var oColumnSet = this._oColumnSet; 7122 7123 // Set the TD's accessibility headers 7124 var allHeaders = oColumnSet.headers, 7125 allColHeaders = allHeaders[index], 7126 sTdHeaders = "", 7127 sHeader; 7128 for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) { 7129 sHeader = this._sId + "-th-" + allColHeaders[j] + ' '; 7130 sTdHeaders += sHeader; 7131 } 7132 elTd.headers = sTdHeaders; 7133 7134 // Class the TD element 7135 var aAddClasses = []; 7136 if(index === 0) { 7137 aAddClasses[aAddClasses.length] = DT.CLASS_FIRST; 7138 } 7139 if(isLast) { 7140 aAddClasses[aAddClasses.length] = DT.CLASS_LAST; 7141 } 7142 elTd.className = this._getColumnClassNames(oColumn, aAddClasses); 7143 7144 // Class the liner element 7145 elTd.firstChild.className = DT.CLASS_LINER; 7146 7147 // Set Column width for fallback cases 7148 if(oColumn.width && DT._bDynStylesFallback) { 7149 // Validate minWidth 7150 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ? 7151 oColumn.minWidth : oColumn.width; 7152 elTd.firstChild.style.overflow = 'hidden'; 7153 elTd.firstChild.style.width = nWidth + 'px'; 7154 } 7155 7156 return elTd; 7157 }, 7158 7159 7160 /** 7161 * Create a new TR element for a given Record and appends it with the correct 7162 * number of Column-state-classed TD elements. Striping is the responsibility of 7163 * the calling function, which may decide to stripe the single row, a subset of 7164 * rows, or all the rows. 7165 * @method _createTrEl 7166 * @param oRecord {YAHOO.widget.Record} Record instance 7167 * @return {HTMLElement} The new TR element. This must be added to the DOM. 7168 * @private 7169 */ 7170 _addTrEl : function (oRecord) { 7171 var elTrTemplate = this._getTrTemplateEl(); 7172 7173 // Clone the TR template. 7174 var elTr = elTrTemplate.cloneNode(true); 7175 7176 // Populate content 7177 return this._updateTrEl(elTr,oRecord); 7178 }, 7179 7180 /** 7181 * Formats the contents of the given TR's TD elements with data from the given 7182 * Record. Only innerHTML should change, nothing structural. 7183 * 7184 * @method _updateTrEl 7185 * @param elTr {HTMLElement} The TR element to update. 7186 * @param oRecord {YAHOO.widget.Record} The associated Record instance. 7187 * @return {HTMLElement} DOM reference to the new TR element. 7188 * @private 7189 */ 7190 _updateTrEl : function(elTr, oRecord) { 7191 var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true; 7192 if(ok) { 7193 // Hide the row to prevent constant reflows 7194 elTr.style.display = 'none'; 7195 7196 // Update TD elements with new data 7197 var allTds = elTr.childNodes, 7198 elTd; 7199 for(var i=0,len=allTds.length; i<len; ++i) { 7200 elTd = allTds[i]; 7201 7202 // Set the cell content 7203 this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]); 7204 } 7205 7206 // Redisplay the row for reflow 7207 elTr.style.display = ''; 7208 } 7209 7210 // Record-to-TR association and tracking of FIRST/LAST 7211 var oldId = elTr.id, 7212 newId = oRecord.getId(); 7213 if(this._sFirstTrId === oldId) { 7214 this._sFirstTrId = newId; 7215 } 7216 if(this._sLastTrId === oldId) { 7217 this._sLastTrId = newId; 7218 } 7219 elTr.id = newId; 7220 return elTr; 7221 }, 7222 7223 7224 /** 7225 * Deletes TR element by DOM reference or by DataTable page row index. 7226 * 7227 * @method _deleteTrEl 7228 * @param row {HTMLElement | Number} TR element reference or Datatable page row index. 7229 * @return {Boolean} Returns true if successful, else returns false. 7230 * @private 7231 */ 7232 _deleteTrEl : function(row) { 7233 var rowIndex; 7234 7235 // Get page row index for the element 7236 if(!lang.isNumber(row)) { 7237 rowIndex = Dom.get(row).sectionRowIndex; 7238 } 7239 else { 7240 rowIndex = row; 7241 } 7242 if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) { 7243 // Cannot use tbody.deleteRow due to IE6 instability 7244 //return this._elTbody.deleteRow(rowIndex); 7245 return this._elTbody.removeChild(this._elTbody.rows[row]); 7246 } 7247 else { 7248 return null; 7249 } 7250 }, 7251 7252 7253 7254 7255 7256 7257 7258 7259 7260 7261 7262 7263 7264 7265 7266 7267 7268 7269 7270 7271 7272 7273 7274 7275 7276 7277 7278 // CSS/STATE FUNCTIONS 7279 7280 7281 7282 7283 /** 7284 * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element 7285 * of the DataTable page and updates internal tracker. 7286 * 7287 * @method _unsetFirstRow 7288 * @private 7289 */ 7290 _unsetFirstRow : function() { 7291 // Remove FIRST 7292 if(this._sFirstTrId) { 7293 Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST); 7294 this._sFirstTrId = null; 7295 } 7296 }, 7297 7298 /** 7299 * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element 7300 * of the DataTable page and updates internal tracker. 7301 * 7302 * @method _setFirstRow 7303 * @private 7304 */ 7305 _setFirstRow : function() { 7306 this._unsetFirstRow(); 7307 var elTr = this.getFirstTrEl(); 7308 if(elTr) { 7309 // Set FIRST 7310 Dom.addClass(elTr, DT.CLASS_FIRST); 7311 this._sFirstTrId = elTr.id; 7312 } 7313 }, 7314 7315 /** 7316 * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element 7317 * of the DataTable page and updates internal tracker. 7318 * 7319 * @method _unsetLastRow 7320 * @private 7321 */ 7322 _unsetLastRow : function() { 7323 // Unassign previous class 7324 if(this._sLastTrId) { 7325 Dom.removeClass(this._sLastTrId, DT.CLASS_LAST); 7326 this._sLastTrId = null; 7327 } 7328 }, 7329 7330 /** 7331 * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element 7332 * of the DataTable page and updates internal tracker. 7333 * 7334 * @method _setLastRow 7335 * @private 7336 */ 7337 _setLastRow : function() { 7338 this._unsetLastRow(); 7339 var elTr = this.getLastTrEl(); 7340 if(elTr) { 7341 // Assign class 7342 Dom.addClass(elTr, DT.CLASS_LAST); 7343 this._sLastTrId = elTr.id; 7344 } 7345 }, 7346 7347 /** 7348 * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements. 7349 * 7350 * @method _setRowStripes 7351 * @param row {HTMLElement | String | Number} (optional) HTML TR element reference 7352 * or string ID, or page row index of where to start striping. 7353 * @param range {Number} (optional) If given, how many rows to stripe, otherwise 7354 * stripe all the rows until the end. 7355 * @private 7356 */ 7357 _setRowStripes : function(row, range) { 7358 // Default values stripe all rows 7359 var allRows = this._elTbody.rows, 7360 nStartIndex = 0, 7361 nEndIndex = allRows.length, 7362 aOdds = [], nOddIdx = 0, 7363 aEvens = [], nEvenIdx = 0; 7364 7365 // Stripe a subset 7366 if((row !== null) && (row !== undefined)) { 7367 // Validate given start row 7368 var elStartRow = this.getTrEl(row); 7369 if(elStartRow) { 7370 nStartIndex = elStartRow.sectionRowIndex; 7371 7372 // Validate given range 7373 if(lang.isNumber(range) && (range > 1)) { 7374 nEndIndex = nStartIndex + range; 7375 } 7376 } 7377 } 7378 7379 for(var i=nStartIndex; i<nEndIndex; i++) { 7380 if(i%2) { 7381 aOdds[nOddIdx++] = allRows[i]; 7382 } else { 7383 aEvens[nEvenIdx++] = allRows[i]; 7384 } 7385 } 7386 7387 if (aOdds.length) { 7388 Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD); 7389 } 7390 7391 if (aEvens.length) { 7392 Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN); 7393 } 7394 }, 7395 7396 /** 7397 * Assigns the class DT.CLASS_SELECTED to TR and TD elements. 7398 * 7399 * @method _setSelections 7400 * @private 7401 */ 7402 _setSelections : function() { 7403 // Keep track of selected rows 7404 var allSelectedRows = this.getSelectedRows(); 7405 // Keep track of selected cells 7406 var allSelectedCells = this.getSelectedCells(); 7407 // Anything to select? 7408 if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) { 7409 var oColumnSet = this._oColumnSet, 7410 el; 7411 // Loop over each row 7412 for(var i=0; i<allSelectedRows.length; i++) { 7413 el = Dom.get(allSelectedRows[i]); 7414 if(el) { 7415 Dom.addClass(el, DT.CLASS_SELECTED); 7416 } 7417 } 7418 // Loop over each cell 7419 for(i=0; i<allSelectedCells.length; i++) { 7420 el = Dom.get(allSelectedCells[i].recordId); 7421 if(el) { 7422 Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED); 7423 } 7424 } 7425 } 7426 }, 7427 7428 7429 7430 7431 7432 7433 7434 7435 7436 7437 7438 7439 7440 7441 7442 7443 7444 7445 7446 7447 7448 7449 7450 7451 7452 7453 7454 7455 7456 7457 7458 7459 7460 7461 7462 7463 7464 7465 7466 7467 7468 7469 7470 ///////////////////////////////////////////////////////////////////////////// 7471 // 7472 // Private DOM Event Handlers 7473 // 7474 ///////////////////////////////////////////////////////////////////////////// 7475 7476 /** 7477 * Validates minWidths whenever the render chain ends. 7478 * 7479 * @method _onRenderChainEnd 7480 * @private 7481 */ 7482 _onRenderChainEnd : function() { 7483 // Hide loading message 7484 this.hideTableMessage(); 7485 7486 // Show empty message 7487 if(this._elTbody.rows.length === 0) { 7488 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY); 7489 } 7490 7491 // Execute in timeout thread to give implementers a chance 7492 // to subscribe after the constructor 7493 var oSelf = this; 7494 setTimeout(function() { 7495 if((oSelf instanceof DT) && oSelf._sId) { 7496 // Init event 7497 if(oSelf._bInit) { 7498 oSelf._bInit = false; 7499 oSelf.fireEvent("initEvent"); 7500 } 7501 7502 // Render event 7503 oSelf.fireEvent("renderEvent"); 7504 // Backward compatibility 7505 oSelf.fireEvent("refreshEvent"); 7506 YAHOO.log("DataTable rendered", "info", oSelf.toString()); 7507 7508 // Post-render routine 7509 oSelf.validateColumnWidths(); 7510 7511 // Post-render event 7512 oSelf.fireEvent("postRenderEvent"); 7513 7514 /*if(YAHOO.example.Performance.trialStart) { 7515 YAHOO.log((new Date()).getTime() - YAHOO.example.Performance.trialStart.getTime() + " ms", "time"); 7516 YAHOO.example.Performance.trialStart = null; 7517 }*/ 7518 7519 YAHOO.log("Post-render routine executed", "info", oSelf.toString()); 7520 } 7521 }, 0); 7522 }, 7523 7524 /** 7525 * Handles click events on the DOCUMENT. 7526 * 7527 * @method _onDocumentClick 7528 * @param e {HTMLEvent} The click event. 7529 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7530 * @private 7531 */ 7532 _onDocumentClick : function(e, oSelf) { 7533 var elTarget = Ev.getTarget(e); 7534 var elTag = elTarget.nodeName.toLowerCase(); 7535 7536 if(!Dom.isAncestor(oSelf._elContainer, elTarget)) { 7537 oSelf.fireEvent("tableBlurEvent"); 7538 7539 // Fires editorBlurEvent when click is not within the TABLE. 7540 // For cases when click is within the TABLE, due to timing issues, 7541 // the editorBlurEvent needs to get fired by the lower-level DOM click 7542 // handlers below rather than by the TABLE click handler directly. 7543 if(oSelf._oCellEditor) { 7544 if(oSelf._oCellEditor.getContainerEl) { 7545 var elContainer = oSelf._oCellEditor.getContainerEl(); 7546 // Only if the click was not within the CellEditor container 7547 if(!Dom.isAncestor(elContainer, elTarget) && 7548 (elContainer.id !== elTarget.id)) { 7549 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor}); 7550 } 7551 } 7552 // Backward Compatibility 7553 else if(oSelf._oCellEditor.isActive) { 7554 // Only if the click was not within the Cell Editor container 7555 if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) && 7556 (oSelf._oCellEditor.container.id !== elTarget.id)) { 7557 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor}); 7558 } 7559 } 7560 } 7561 } 7562 }, 7563 7564 /** 7565 * Handles focus events on the DataTable instance. 7566 * 7567 * @method _onTableFocus 7568 * @param e {HTMLEvent} The focus event. 7569 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7570 * @private 7571 */ 7572 _onTableFocus : function(e, oSelf) { 7573 oSelf.fireEvent("tableFocusEvent"); 7574 }, 7575 7576 /** 7577 * Handles focus events on the THEAD element. 7578 * 7579 * @method _onTheadFocus 7580 * @param e {HTMLEvent} The focus event. 7581 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7582 * @private 7583 */ 7584 _onTheadFocus : function(e, oSelf) { 7585 oSelf.fireEvent("theadFocusEvent"); 7586 oSelf.fireEvent("tableFocusEvent"); 7587 }, 7588 7589 /** 7590 * Handles focus events on the TBODY element. 7591 * 7592 * @method _onTbodyFocus 7593 * @param e {HTMLEvent} The focus event. 7594 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7595 * @private 7596 */ 7597 _onTbodyFocus : function(e, oSelf) { 7598 oSelf.fireEvent("tbodyFocusEvent"); 7599 oSelf.fireEvent("tableFocusEvent"); 7600 }, 7601 7602 /** 7603 * Handles mouseover events on the DataTable instance. 7604 * 7605 * @method _onTableMouseover 7606 * @param e {HTMLEvent} The mouseover event. 7607 * @param origTarget {HTMLElement} The mouseenter delegated element. 7608 * @param container {HTMLElement} The mouseenter delegation container. 7609 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7610 * @private 7611 */ 7612 _onTableMouseover : function(e, origTarget, container, oSelf) { 7613 var elTarget = origTarget; 7614 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7615 var bKeepBubbling = true; 7616 while(elTarget && (elTag != "table")) { 7617 switch(elTag) { 7618 case "body": 7619 return; 7620 case "a": 7621 break; 7622 case "td": 7623 bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e}); 7624 break; 7625 case "span": 7626 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) { 7627 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e}); 7628 // Backward compatibility 7629 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e}); 7630 } 7631 break; 7632 case "th": 7633 bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e}); 7634 // Backward compatibility 7635 bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e}); 7636 break; 7637 case "tr": 7638 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") { 7639 bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e}); 7640 // Backward compatibility 7641 bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e}); 7642 } 7643 else { 7644 bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e}); 7645 } 7646 break; 7647 default: 7648 break; 7649 } 7650 if(bKeepBubbling === false) { 7651 return; 7652 } 7653 else { 7654 elTarget = elTarget.parentNode; 7655 if(elTarget) { 7656 elTag = elTarget.nodeName.toLowerCase(); 7657 } 7658 } 7659 } 7660 oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7661 }, 7662 7663 /** 7664 * Handles mouseout events on the DataTable instance. 7665 * 7666 * @method _onTableMouseout 7667 * @param e {HTMLEvent} The mouseout event. 7668 * @param origTarget {HTMLElement} The mouseleave delegated element. 7669 * @param container {HTMLElement} The mouseleave delegation container. 7670 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7671 * @private 7672 */ 7673 _onTableMouseout : function(e, origTarget, container, oSelf) { 7674 var elTarget = origTarget; 7675 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7676 var bKeepBubbling = true; 7677 while(elTarget && (elTag != "table")) { 7678 switch(elTag) { 7679 case "body": 7680 return; 7681 case "a": 7682 break; 7683 case "td": 7684 bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e}); 7685 break; 7686 case "span": 7687 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) { 7688 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e}); 7689 // Backward compatibility 7690 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e}); 7691 } 7692 break; 7693 case "th": 7694 bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e}); 7695 // Backward compatibility 7696 bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e}); 7697 break; 7698 case "tr": 7699 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") { 7700 bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e}); 7701 // Backward compatibility 7702 bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e}); 7703 } 7704 else { 7705 bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e}); 7706 } 7707 break; 7708 default: 7709 break; 7710 } 7711 if(bKeepBubbling === false) { 7712 return; 7713 } 7714 else { 7715 elTarget = elTarget.parentNode; 7716 if(elTarget) { 7717 elTag = elTarget.nodeName.toLowerCase(); 7718 } 7719 } 7720 } 7721 oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7722 }, 7723 7724 /** 7725 * Handles mousedown events on the DataTable instance. 7726 * 7727 * @method _onTableMousedown 7728 * @param e {HTMLEvent} The mousedown event. 7729 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7730 * @private 7731 */ 7732 _onTableMousedown : function(e, oSelf) { 7733 var elTarget = Ev.getTarget(e); 7734 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7735 var bKeepBubbling = true; 7736 while(elTarget && (elTag != "table")) { 7737 switch(elTag) { 7738 case "body": 7739 return; 7740 case "a": 7741 break; 7742 case "td": 7743 bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e}); 7744 break; 7745 case "span": 7746 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) { 7747 bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e}); 7748 // Backward compatibility 7749 bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e}); 7750 } 7751 break; 7752 case "th": 7753 bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e}); 7754 // Backward compatibility 7755 bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e}); 7756 break; 7757 case "tr": 7758 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") { 7759 bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e}); 7760 // Backward compatibility 7761 bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e}); 7762 } 7763 else { 7764 bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e}); 7765 } 7766 break; 7767 default: 7768 break; 7769 } 7770 if(bKeepBubbling === false) { 7771 return; 7772 } 7773 else { 7774 elTarget = elTarget.parentNode; 7775 if(elTarget) { 7776 elTag = elTarget.nodeName.toLowerCase(); 7777 } 7778 } 7779 } 7780 oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7781 }, 7782 7783 /** 7784 * Handles mouseup events on the DataTable instance. 7785 * 7786 * @method _onTableMouseup 7787 * @param e {HTMLEvent} The mouseup event. 7788 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7789 * @private 7790 */ 7791 _onTableMouseup : function(e, oSelf) { 7792 var elTarget = Ev.getTarget(e); 7793 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7794 var bKeepBubbling = true; 7795 while(elTarget && (elTag != "table")) { 7796 switch(elTag) { 7797 case "body": 7798 return; 7799 case "a": 7800 break; 7801 case "td": 7802 bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e}); 7803 break; 7804 case "span": 7805 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) { 7806 bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e}); 7807 // Backward compatibility 7808 bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e}); 7809 } 7810 break; 7811 case "th": 7812 bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e}); 7813 // Backward compatibility 7814 bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e}); 7815 break; 7816 case "tr": 7817 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") { 7818 bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e}); 7819 // Backward compatibility 7820 bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e}); 7821 } 7822 else { 7823 bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e}); 7824 } 7825 break; 7826 default: 7827 break; 7828 } 7829 if(bKeepBubbling === false) { 7830 return; 7831 } 7832 else { 7833 elTarget = elTarget.parentNode; 7834 if(elTarget) { 7835 elTag = elTarget.nodeName.toLowerCase(); 7836 } 7837 } 7838 } 7839 oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7840 }, 7841 7842 /** 7843 * Handles dblclick events on the DataTable instance. 7844 * 7845 * @method _onTableDblclick 7846 * @param e {HTMLEvent} The dblclick event. 7847 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7848 * @private 7849 */ 7850 _onTableDblclick : function(e, oSelf) { 7851 var elTarget = Ev.getTarget(e); 7852 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7853 var bKeepBubbling = true; 7854 while(elTarget && (elTag != "table")) { 7855 switch(elTag) { 7856 case "body": 7857 return; 7858 case "td": 7859 bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e}); 7860 break; 7861 case "span": 7862 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) { 7863 bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e}); 7864 // Backward compatibility 7865 bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e}); 7866 } 7867 break; 7868 case "th": 7869 bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e}); 7870 // Backward compatibility 7871 bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e}); 7872 break; 7873 case "tr": 7874 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") { 7875 bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e}); 7876 // Backward compatibility 7877 bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e}); 7878 } 7879 else { 7880 bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e}); 7881 } 7882 break; 7883 default: 7884 break; 7885 } 7886 if(bKeepBubbling === false) { 7887 return; 7888 } 7889 else { 7890 elTarget = elTarget.parentNode; 7891 if(elTarget) { 7892 elTag = elTarget.nodeName.toLowerCase(); 7893 } 7894 } 7895 } 7896 oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7897 }, 7898 /** 7899 * Handles keydown events on the THEAD element. 7900 * 7901 * @method _onTheadKeydown 7902 * @param e {HTMLEvent} The key event. 7903 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7904 * @private 7905 */ 7906 _onTheadKeydown : function(e, oSelf) { 7907 var elTarget = Ev.getTarget(e); 7908 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7909 var bKeepBubbling = true; 7910 while(elTarget && (elTag != "table")) { 7911 switch(elTag) { 7912 case "body": 7913 return; 7914 case "input": 7915 case "textarea": 7916 // TODO: implement textareaKeyEvent 7917 break; 7918 case "thead": 7919 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e}); 7920 break; 7921 default: 7922 break; 7923 } 7924 if(bKeepBubbling === false) { 7925 return; 7926 } 7927 else { 7928 elTarget = elTarget.parentNode; 7929 if(elTarget) { 7930 elTag = elTarget.nodeName.toLowerCase(); 7931 } 7932 } 7933 } 7934 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7935 }, 7936 7937 /** 7938 * Handles keydown events on the TBODY element. Handles selection behavior, 7939 * provides hooks for ENTER to edit functionality. 7940 * 7941 * @method _onTbodyKeydown 7942 * @param e {HTMLEvent} The key event. 7943 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7944 * @private 7945 */ 7946 _onTbodyKeydown : function(e, oSelf) { 7947 var sMode = oSelf.get("selectionMode"); 7948 7949 if(sMode == "standard") { 7950 oSelf._handleStandardSelectionByKey(e); 7951 } 7952 else if(sMode == "single") { 7953 oSelf._handleSingleSelectionByKey(e); 7954 } 7955 else if(sMode == "cellblock") { 7956 oSelf._handleCellBlockSelectionByKey(e); 7957 } 7958 else if(sMode == "cellrange") { 7959 oSelf._handleCellRangeSelectionByKey(e); 7960 } 7961 else if(sMode == "singlecell") { 7962 oSelf._handleSingleCellSelectionByKey(e); 7963 } 7964 7965 if(oSelf._oCellEditor) { 7966 if(oSelf._oCellEditor.fireEvent) { 7967 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor}); 7968 } 7969 else if(oSelf._oCellEditor.isActive) { 7970 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor}); 7971 } 7972 } 7973 7974 var elTarget = Ev.getTarget(e); 7975 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7976 var bKeepBubbling = true; 7977 while(elTarget && (elTag != "table")) { 7978 switch(elTag) { 7979 case "body": 7980 return; 7981 case "tbody": 7982 bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e}); 7983 break; 7984 default: 7985 break; 7986 } 7987 if(bKeepBubbling === false) { 7988 return; 7989 } 7990 else { 7991 elTarget = elTarget.parentNode; 7992 if(elTarget) { 7993 elTag = elTarget.nodeName.toLowerCase(); 7994 } 7995 } 7996 } 7997 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7998 }, 7999 8000 /** 8001 * Handles click events on the THEAD element. 8002 * 8003 * @method _onTheadClick 8004 * @param e {HTMLEvent} The click event. 8005 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 8006 * @private 8007 */ 8008 _onTheadClick : function(e, oSelf) { 8009 // This blurs the CellEditor 8010 if(oSelf._oCellEditor) { 8011 if(oSelf._oCellEditor.fireEvent) { 8012 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor}); 8013 } 8014 // Backward compatibility 8015 else if(oSelf._oCellEditor.isActive) { 8016 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor}); 8017 } 8018 } 8019 8020 var elTarget = Ev.getTarget(e), 8021 elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(), 8022 bKeepBubbling = true; 8023 while(elTarget && (elTag != "table")) { 8024 switch(elTag) { 8025 case "body": 8026 return; 8027 case "input": 8028 var sType = elTarget.type.toLowerCase(); 8029 if(sType == "checkbox") { 8030 bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e}); 8031 } 8032 else if(sType == "radio") { 8033 bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e}); 8034 } 8035 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) { 8036 if(!elTarget.disabled) { 8037 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e}); 8038 } 8039 else { 8040 bKeepBubbling = false; 8041 } 8042 } 8043 else if (elTarget.disabled){ 8044 bKeepBubbling = false; 8045 } 8046 break; 8047 case "a": 8048 bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e}); 8049 break; 8050 case "button": 8051 if(!elTarget.disabled) { 8052 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e}); 8053 } 8054 else { 8055 bKeepBubbling = false; 8056 } 8057 break; 8058 case "span": 8059 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) { 8060 bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e}); 8061 // Backward compatibility 8062 bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e}); 8063 } 8064 break; 8065 case "th": 8066 bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e}); 8067 // Backward compatibility 8068 bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e}); 8069 break; 8070 case "tr": 8071 bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e}); 8072 // Backward compatibility 8073 bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e}); 8074 break; 8075 default: 8076 break; 8077 } 8078 if(bKeepBubbling === false) { 8079 return; 8080 } 8081 else { 8082 elTarget = elTarget.parentNode; 8083 if(elTarget) { 8084 elTag = elTarget.nodeName.toLowerCase(); 8085 } 8086 } 8087 } 8088 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e}); 8089 }, 8090 8091 /** 8092 * Handles click events on the primary TBODY element. 8093 * 8094 * @method _onTbodyClick 8095 * @param e {HTMLEvent} The click event. 8096 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 8097 * @private 8098 */ 8099 _onTbodyClick : function(e, oSelf) { 8100 // This blurs the CellEditor 8101 if(oSelf._oCellEditor) { 8102 if(oSelf._oCellEditor.fireEvent) { 8103 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor}); 8104 } 8105 else if(oSelf._oCellEditor.isActive) { 8106 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor}); 8107 } 8108 } 8109 8110 // Fire Custom Events 8111 var elTarget = Ev.getTarget(e), 8112 elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(), 8113 bKeepBubbling = true; 8114 while(elTarget && (elTag != "table")) { 8115 switch(elTag) { 8116 case "body": 8117 return; 8118 case "input": 8119 var sType = elTarget.type.toLowerCase(); 8120 if(sType == "checkbox") { 8121 bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e}); 8122 } 8123 else if(sType == "radio") { 8124 bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e}); 8125 } 8126 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) { 8127 if(!elTarget.disabled) { 8128 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e}); 8129 } 8130 else { 8131 bKeepBubbling = false; 8132 } 8133 } 8134 else if (elTarget.disabled){ 8135 bKeepBubbling = false; 8136 } 8137 break; 8138 case "a": 8139 bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e}); 8140 break; 8141 case "button": 8142 if(!elTarget.disabled) { 8143 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e}); 8144 } 8145 else { 8146 bKeepBubbling = false; 8147 } 8148 break; 8149 case "td": 8150 bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e}); 8151 break; 8152 case "tr": 8153 bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e}); 8154 break; 8155 default: 8156 break; 8157 } 8158 if(bKeepBubbling === false) { 8159 return; 8160 } 8161 else { 8162 elTarget = elTarget.parentNode; 8163 if(elTarget) { 8164 elTag = elTarget.nodeName.toLowerCase(); 8165 } 8166 } 8167 } 8168 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e}); 8169 }, 8170 8171 /** 8172 * Handles change events on SELECT elements within DataTable. 8173 * 8174 * @method _onDropdownChange 8175 * @param e {HTMLEvent} The change event. 8176 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 8177 * @private 8178 */ 8179 _onDropdownChange : function(e, oSelf) { 8180 var elTarget = Ev.getTarget(e); 8181 oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget}); 8182 }, 8183 8184 8185 8186 8187 8188 8189 8190 8191 8192 8193 8194 8195 8196 8197 8198 8199 8200 8201 8202 8203 8204 8205 8206 8207 8208 8209 8210 8211 8212 8213 8214 8215 ///////////////////////////////////////////////////////////////////////////// 8216 // 8217 // Public member variables 8218 // 8219 ///////////////////////////////////////////////////////////////////////////// 8220 /** 8221 * Returns object literal of initial configs. 8222 * 8223 * @property configs 8224 * @type Object 8225 * @default {} 8226 */ 8227 configs: null, 8228 8229 8230 ///////////////////////////////////////////////////////////////////////////// 8231 // 8232 // Public methods 8233 // 8234 ///////////////////////////////////////////////////////////////////////////// 8235 8236 /** 8237 * Returns unique id assigned to instance, which is a useful prefix for 8238 * generating unique DOM ID strings. 8239 * 8240 * @method getId 8241 * @return {String} Unique ID of the DataSource instance. 8242 */ 8243 getId : function() { 8244 return this._sId; 8245 }, 8246 8247 /** 8248 * DataSource instance name, for logging. 8249 * 8250 * @method toString 8251 * @return {String} Unique name of the DataSource instance. 8252 */ 8253 8254 toString : function() { 8255 return "DataTable instance " + this._sId; 8256 }, 8257 8258 /** 8259 * Returns the DataTable instance's DataSource instance. 8260 * 8261 * @method getDataSource 8262 * @return {YAHOO.util.DataSource} DataSource instance. 8263 */ 8264 getDataSource : function() { 8265 return this._oDataSource; 8266 }, 8267 8268 /** 8269 * Returns the DataTable instance's ColumnSet instance. 8270 * 8271 * @method getColumnSet 8272 * @return {YAHOO.widget.ColumnSet} ColumnSet instance. 8273 */ 8274 getColumnSet : function() { 8275 return this._oColumnSet; 8276 }, 8277 8278 /** 8279 * Returns the DataTable instance's RecordSet instance. 8280 * 8281 * @method getRecordSet 8282 * @return {YAHOO.widget.RecordSet} RecordSet instance. 8283 */ 8284 getRecordSet : function() { 8285 return this._oRecordSet; 8286 }, 8287 8288 /** 8289 * Returns on object literal representing the DataTable instance's current 8290 * state with the following properties: 8291 * <dl> 8292 * <dt>pagination</dt> 8293 * <dd>Instance of YAHOO.widget.Paginator</dd> 8294 * 8295 * <dt>sortedBy</dt> 8296 * <dd> 8297 * <dl> 8298 * <dt>sortedBy.key</dt> 8299 * <dd>{String} Key of sorted Column</dd> 8300 * <dt>sortedBy.dir</dt> 8301 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd> 8302 * </dl> 8303 * </dd> 8304 * 8305 * <dt>selectedRows</dt> 8306 * <dd>Array of selected rows by Record ID.</dd> 8307 * 8308 * <dt>selectedCells</dt> 8309 * <dd>Selected cells as an array of object literals: 8310 * {recordId:sRecordId, columnKey:sColumnKey}</dd> 8311 * </dl> 8312 * 8313 * @method getState 8314 * @return {Object} DataTable instance state object literal values. 8315 */ 8316 getState : function() { 8317 return { 8318 totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(), 8319 pagination: this.get("paginator") ? this.get("paginator").getState() : null, 8320 sortedBy: this.get("sortedBy"), 8321 selectedRows: this.getSelectedRows(), 8322 selectedCells: this.getSelectedCells() 8323 }; 8324 }, 8325 8326 8327 8328 8329 8330 8331 8332 8333 8334 8335 8336 8337 8338 8339 8340 8341 8342 8343 8344 8345 8346 8347 8348 8349 8350 8351 8352 8353 8354 8355 8356 8357 8358 8359 8360 8361 8362 8363 8364 8365 8366 8367 8368 // DOM ACCESSORS 8369 8370 /** 8371 * Returns DOM reference to the DataTable's container element. 8372 * 8373 * @method getContainerEl 8374 * @return {HTMLElement} Reference to DIV element. 8375 */ 8376 getContainerEl : function() { 8377 return this._elContainer; 8378 }, 8379 8380 /** 8381 * Returns DOM reference to the DataTable's TABLE element. 8382 * 8383 * @method getTableEl 8384 * @return {HTMLElement} Reference to TABLE element. 8385 */ 8386 getTableEl : function() { 8387 return this._elTable; 8388 }, 8389 8390 /** 8391 * Returns DOM reference to the DataTable's THEAD element. 8392 * 8393 * @method getTheadEl 8394 * @return {HTMLElement} Reference to THEAD element. 8395 */ 8396 getTheadEl : function() { 8397 return this._elThead; 8398 }, 8399 8400 /** 8401 * Returns DOM reference to the DataTable's primary TBODY element. 8402 * 8403 * @method getTbodyEl 8404 * @return {HTMLElement} Reference to TBODY element. 8405 */ 8406 getTbodyEl : function() { 8407 return this._elTbody; 8408 }, 8409 8410 /** 8411 * Returns DOM reference to the DataTable's secondary TBODY element that is 8412 * used to display messages. 8413 * 8414 * @method getMsgTbodyEl 8415 * @return {HTMLElement} Reference to TBODY element. 8416 */ 8417 getMsgTbodyEl : function() { 8418 return this._elMsgTbody; 8419 }, 8420 8421 /** 8422 * Returns DOM reference to the TD element within the secondary TBODY that is 8423 * used to display messages. 8424 * 8425 * @method getMsgTdEl 8426 * @return {HTMLElement} Reference to TD element. 8427 */ 8428 getMsgTdEl : function() { 8429 return this._elMsgTd; 8430 }, 8431 8432 /** 8433 * Returns the corresponding TR reference for a given DOM element, ID string or 8434 * page row index. If the given identifier is a child of a TR element, 8435 * then DOM tree is traversed until a parent TR element is returned, otherwise 8436 * null. Returns null if the row is not considered a primary row (i.e., row 8437 * extensions). 8438 * 8439 * @method getTrEl 8440 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to 8441 * get: by element reference, ID string, page row index, or Record. 8442 * @return {HTMLElement} Reference to TR element, or null. 8443 */ 8444 getTrEl : function(row) { 8445 // By Record 8446 if(row instanceof YAHOO.widget.Record) { 8447 return document.getElementById(row.getId()); 8448 } 8449 // By page row index 8450 else if(lang.isNumber(row)) { 8451 var dataRows = Dom.getElementsByClassName(DT.CLASS_REC, "tr", this._elTbody); 8452 return dataRows && dataRows[row] ? dataRows[row] : null; 8453 } 8454 // By ID string or element reference 8455 else if(row) { 8456 var elRow = (lang.isString(row)) ? document.getElementById(row) : row; 8457 8458 // Validate HTML element 8459 if(elRow && elRow.ownerDocument == document) { 8460 // Validate TR element 8461 if(elRow.nodeName.toLowerCase() != "tr") { 8462 // Traverse up the DOM to find the corresponding TR element 8463 elRow = Dom.getAncestorByTagName(elRow,"tr"); 8464 } 8465 8466 return elRow; 8467 } 8468 } 8469 8470 return null; 8471 }, 8472 8473 /** 8474 * Returns DOM reference to the first primary TR element in the DataTable page, or null. 8475 * 8476 * @method getFirstTrEl 8477 * @return {HTMLElement} Reference to TR element. 8478 */ 8479 getFirstTrEl : function() { 8480 var allRows = this._elTbody.rows, 8481 i=0; 8482 while(allRows[i]) { 8483 if(this.getRecord(allRows[i])) { 8484 return allRows[i]; 8485 } 8486 i++; 8487 } 8488 return null; 8489 8490 }, 8491 8492 /** 8493 * Returns DOM reference to the last primary TR element in the DataTable page, or null. 8494 * 8495 * @method getLastTrEl 8496 * @return {HTMLElement} Reference to last TR element. 8497 */ 8498 getLastTrEl : function() { 8499 var allRows = this._elTbody.rows, 8500 i=allRows.length-1; 8501 while(i>-1) { 8502 if(this.getRecord(allRows[i])) { 8503 return allRows[i]; 8504 } 8505 i--; 8506 } 8507 return null; 8508 }, 8509 8510 /** 8511 * Returns DOM reference to the next TR element from the given primary TR element, or null. 8512 * 8513 * @method getNextTrEl 8514 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element 8515 * reference, ID string, page row index, or Record from which to get next TR element. 8516 * @param forcePrimary {Boolean} (optional) If true, will only return TR elements 8517 * that correspond to Records. Non-primary rows (such as row expansions) 8518 * will be skipped. 8519 * @return {HTMLElement} Reference to next TR element. 8520 */ 8521 getNextTrEl : function(row, forcePrimary) { 8522 var nThisTrIndex = this.getTrIndex(row); 8523 if(nThisTrIndex !== null) { 8524 var allRows = this._elTbody.rows; 8525 if(forcePrimary) { 8526 while(nThisTrIndex < allRows.length-1) { 8527 row = allRows[nThisTrIndex+1]; 8528 if(this.getRecord(row)) { 8529 return row; 8530 } 8531 nThisTrIndex++; 8532 } 8533 } 8534 else { 8535 if(nThisTrIndex < allRows.length-1) { 8536 return allRows[nThisTrIndex+1]; 8537 } 8538 } 8539 } 8540 8541 YAHOO.log("Could not get next TR element for row " + row, "info", this.toString()); 8542 return null; 8543 }, 8544 8545 /** 8546 * Returns DOM reference to the previous TR element from the given primary TR element, or null. 8547 * 8548 * @method getPreviousTrEl 8549 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element 8550 * reference, ID string, page row index, or Record from which to get previous TR element. 8551 * @param forcePrimary {Boolean} (optional) If true, will only return TR elements 8552 * from rothat correspond to Records. Non-primary rows (such as row expansions) 8553 * will be skipped. 8554 * @return {HTMLElement} Reference to previous TR element. 8555 */ 8556 getPreviousTrEl : function(row, forcePrimary) { 8557 var nThisTrIndex = this.getTrIndex(row); 8558 if(nThisTrIndex !== null) { 8559 var allRows = this._elTbody.rows; 8560 8561 if(forcePrimary) { 8562 while(nThisTrIndex > 0) { 8563 row = allRows[nThisTrIndex-1]; 8564 if(this.getRecord(row)) { 8565 return row; 8566 } 8567 nThisTrIndex--; 8568 } 8569 } 8570 else { 8571 if(nThisTrIndex > 0) { 8572 return allRows[nThisTrIndex-1]; 8573 } 8574 } 8575 } 8576 8577 YAHOO.log("Could not get previous TR element for row " + row, "info", this.toString()); 8578 return null; 8579 }, 8580 8581 8582 /** 8583 * Workaround for IE bug where hidden or not-in-dom elements cause cellIndex 8584 * value to be incorrect. 8585 * 8586 * @method getCellIndex 8587 * @param cell {HTMLElement | Object} TD element or child of a TD element, or 8588 * object literal of syntax {record:oRecord, column:oColumn}. 8589 * @return {Number} TD.cellIndex value. 8590 */ 8591 getCellIndex : function(cell) { 8592 cell = this.getTdEl(cell); 8593 if(cell) { 8594 if(ua.ie > 0) { 8595 var i=0, 8596 tr = cell.parentNode, 8597 allCells = tr.childNodes, 8598 len = allCells.length; 8599 for(; i<len; i++) { 8600 if(allCells[i] == cell) { 8601 return i; 8602 } 8603 } 8604 } 8605 else { 8606 return cell.cellIndex; 8607 } 8608 } 8609 }, 8610 8611 /** 8612 * Returns DOM reference to a TD liner element. 8613 * 8614 * @method getTdLinerEl 8615 * @param cell {HTMLElement | Object} TD element or child of a TD element, or 8616 * object literal of syntax {record:oRecord, column:oColumn}. 8617 * @return {HTMLElement} Reference to TD liner element. 8618 */ 8619 getTdLinerEl : function(cell) { 8620 var elCell = this.getTdEl(cell); 8621 return elCell.firstChild || null; 8622 }, 8623 8624 /** 8625 * Returns DOM reference to a TD element. Returns null if the row is not 8626 * considered a primary row (i.e., row extensions). 8627 * 8628 * @method getTdEl 8629 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or 8630 * object literal of syntax {record:oRecord, column:oColumn}. 8631 * @return {HTMLElement} Reference to TD element. 8632 */ 8633 getTdEl : function(cell) { 8634 var elCell; 8635 var el = Dom.get(cell); 8636 8637 // Validate HTML element 8638 if(el && (el.ownerDocument == document)) { 8639 // Validate TD element 8640 if(el.nodeName.toLowerCase() != "td") { 8641 // Traverse up the DOM to find the corresponding TR element 8642 elCell = Dom.getAncestorByTagName(el, "td"); 8643 } 8644 else { 8645 elCell = el; 8646 } 8647 8648 // Make sure the TD is in this TBODY or is not in DOM 8649 // Bug 2527707 and bug 2263558 8650 if(elCell && ((elCell.parentNode.parentNode == this._elTbody) || 8651 (elCell.parentNode.parentNode === null) || 8652 (elCell.parentNode.parentNode.nodeType === 11))) { 8653 // Now we can return the TD element 8654 return elCell; 8655 } 8656 } 8657 else if(cell) { 8658 var oRecord, nColKeyIndex; 8659 8660 if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) { 8661 oRecord = this.getRecord(cell.recordId); 8662 var oColumn = this.getColumn(cell.columnKey); 8663 if(oColumn) { 8664 nColKeyIndex = oColumn.getKeyIndex(); 8665 } 8666 8667 } 8668 if(cell.record && cell.column && cell.column.getKeyIndex) { 8669 oRecord = cell.record; 8670 nColKeyIndex = cell.column.getKeyIndex(); 8671 } 8672 var elRow = this.getTrEl(oRecord); 8673 if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) { 8674 return elRow.cells[nColKeyIndex] || null; 8675 } 8676 } 8677 8678 return null; 8679 }, 8680 8681 /** 8682 * Returns DOM reference to the first primary TD element in the DataTable page (by default), 8683 * the first TD element of the optionally given row, or null. 8684 * 8685 * @method getFirstTdEl 8686 * @param row {HTMLElement} (optional) row from which to get first TD 8687 * @return {HTMLElement} Reference to TD element. 8688 */ 8689 getFirstTdEl : function(row) { 8690 var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getFirstTrEl(); 8691 if(elRow) { 8692 if(elRow.cells && elRow.cells.length > 0) { 8693 return elRow.cells[0]; 8694 } 8695 else if(elRow.childNodes && elRow.childNodes.length > 0) { 8696 return elRow.childNodes[0]; 8697 } 8698 } 8699 YAHOO.log("Could not get first TD element for row " + elRow, "info", this.toString()); 8700 return null; 8701 }, 8702 8703 /** 8704 * Returns DOM reference to the last primary TD element in the DataTable page (by default), 8705 * the first TD element of the optionally given row, or null. 8706 * 8707 * @method getLastTdEl 8708 * @param row {HTMLElement} (optional) row from which to get first TD 8709 * @return {HTMLElement} Reference to last TD element. 8710 */ 8711 getLastTdEl : function(row) { 8712 var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getLastTrEl(); 8713 if(elRow) { 8714 if(elRow.cells && elRow.cells.length > 0) { 8715 return elRow.cells[elRow.cells.length-1]; 8716 } 8717 else if(elRow.childNodes && elRow.childNodes.length > 0) { 8718 return elRow.childNodes[elRow.childNodes.length-1]; 8719 } 8720 } 8721 YAHOO.log("Could not get last TD element for row " + elRow, "info", this.toString()); 8722 return null; 8723 }, 8724 8725 /** 8726 * Returns DOM reference to the next TD element from the given cell, or null. 8727 * 8728 * @method getNextTdEl 8729 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or 8730 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element. 8731 * @return {HTMLElement} Reference to next TD element, or null. 8732 */ 8733 getNextTdEl : function(cell) { 8734 var elCell = this.getTdEl(cell); 8735 if(elCell) { 8736 var nThisTdIndex = this.getCellIndex(elCell); 8737 var elRow = this.getTrEl(elCell); 8738 if(elRow.cells && (elRow.cells.length) > 0 && (nThisTdIndex < elRow.cells.length-1)) { 8739 return elRow.cells[nThisTdIndex+1]; 8740 } 8741 else if(elRow.childNodes && (elRow.childNodes.length) > 0 && (nThisTdIndex < elRow.childNodes.length-1)) { 8742 return elRow.childNodes[nThisTdIndex+1]; 8743 } 8744 else { 8745 var elNextRow = this.getNextTrEl(elRow); 8746 if(elNextRow) { 8747 return elNextRow.cells[0]; 8748 } 8749 } 8750 } 8751 YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString()); 8752 return null; 8753 }, 8754 8755 /** 8756 * Returns DOM reference to the previous TD element from the given cell, or null. 8757 * 8758 * @method getPreviousTdEl 8759 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or 8760 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element. 8761 * @return {HTMLElement} Reference to previous TD element, or null. 8762 */ 8763 getPreviousTdEl : function(cell) { 8764 var elCell = this.getTdEl(cell); 8765 if(elCell) { 8766 var nThisTdIndex = this.getCellIndex(elCell); 8767 var elRow = this.getTrEl(elCell); 8768 if(nThisTdIndex > 0) { 8769 if(elRow.cells && elRow.cells.length > 0) { 8770 return elRow.cells[nThisTdIndex-1]; 8771 } 8772 else if(elRow.childNodes && elRow.childNodes.length > 0) { 8773 return elRow.childNodes[nThisTdIndex-1]; 8774 } 8775 } 8776 else { 8777 var elPreviousRow = this.getPreviousTrEl(elRow); 8778 if(elPreviousRow) { 8779 return this.getLastTdEl(elPreviousRow); 8780 } 8781 } 8782 } 8783 YAHOO.log("Could not get next TD element for cell " + cell, "info", this.toString()); 8784 return null; 8785 }, 8786 8787 /** 8788 * Returns DOM reference to the above TD element from the given cell, or null. 8789 * 8790 * @method getAboveTdEl 8791 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or 8792 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element. 8793 * @param forcePrimary {Boolean} (optional) If true, will only return TD elements 8794 * from rows that correspond to Records. Non-primary rows (such as row expansions) 8795 * will be skipped. 8796 * @return {HTMLElement} Reference to above TD element, or null. 8797 */ 8798 getAboveTdEl : function(cell, forcePrimary) { 8799 var elCell = this.getTdEl(cell); 8800 if(elCell) { 8801 var elPreviousRow = this.getPreviousTrEl(elCell, forcePrimary); 8802 if(elPreviousRow ) { 8803 var cellIndex = this.getCellIndex(elCell); 8804 if(elPreviousRow.cells && elPreviousRow.cells.length > 0) { 8805 return elPreviousRow.cells[cellIndex] ? elPreviousRow.cells[cellIndex] : null; 8806 } 8807 else if(elPreviousRow.childNodes && elPreviousRow.childNodes.length > 0) { 8808 return elPreviousRow.childNodes[cellIndex] ? elPreviousRow.childNodes[cellIndex] : null; 8809 } 8810 } 8811 } 8812 YAHOO.log("Could not get above TD element for cell " + cell, "info", this.toString()); 8813 return null; 8814 }, 8815 8816 /** 8817 * Returns DOM reference to the below TD element from the given cell, or null. 8818 * 8819 * @method getBelowTdEl 8820 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or 8821 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element. 8822 * @param forcePrimary {Boolean} (optional) If true, will only return TD elements 8823 * from rows that correspond to Records. Non-primary rows (such as row expansions) 8824 * will be skipped. 8825 * @return {HTMLElement} Reference to below TD element, or null. 8826 */ 8827 getBelowTdEl : function(cell, forcePrimary) { 8828 var elCell = this.getTdEl(cell); 8829 if(elCell) { 8830 var elNextRow = this.getNextTrEl(elCell, forcePrimary); 8831 if(elNextRow) { 8832 var cellIndex = this.getCellIndex(elCell); 8833 if(elNextRow.cells && elNextRow.cells.length > 0) { 8834 return elNextRow.cells[cellIndex] ? elNextRow.cells[cellIndex] : null; 8835 } 8836 else if(elNextRow.childNodes && elNextRow.childNodes.length > 0) { 8837 return elNextRow.childNodes[cellIndex] ? elNextRow.childNodes[cellIndex] : null; 8838 } 8839 } 8840 } 8841 YAHOO.log("Could not get below TD element for cell " + cell, "info", this.toString()); 8842 return null; 8843 }, 8844 8845 /** 8846 * Returns DOM reference to a TH liner element. Needed to normalize for resizeable 8847 * Columns, which have an additional resizer liner DIV element between the TH 8848 * element and the liner DIV element. 8849 * 8850 * @method getThLinerEl 8851 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance, 8852 * DOM element reference, or string ID. 8853 * @return {HTMLElement} Reference to TH liner element. 8854 */ 8855 getThLinerEl : function(theadCell) { 8856 var oColumn = this.getColumn(theadCell); 8857 return (oColumn) ? oColumn.getThLinerEl() : null; 8858 }, 8859 8860 /** 8861 * Returns DOM reference to a TH element. 8862 * 8863 * @method getThEl 8864 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance, 8865 * DOM element reference, or string ID. 8866 * @return {HTMLElement} Reference to TH element. 8867 */ 8868 getThEl : function(theadCell) { 8869 var elTh; 8870 8871 // Validate Column instance 8872 if(theadCell instanceof YAHOO.widget.Column) { 8873 var oColumn = theadCell; 8874 elTh = oColumn.getThEl(); 8875 if(elTh) { 8876 return elTh; 8877 } 8878 } 8879 // Validate HTML element 8880 else { 8881 var el = Dom.get(theadCell); 8882 8883 if(el && (el.ownerDocument == document)) { 8884 // Validate TH element 8885 if(el.nodeName.toLowerCase() != "th") { 8886 // Traverse up the DOM to find the corresponding TR element 8887 elTh = Dom.getAncestorByTagName(el,"th"); 8888 } 8889 else { 8890 elTh = el; 8891 } 8892 8893 return elTh; 8894 } 8895 } 8896 8897 return null; 8898 }, 8899 8900 /** 8901 * Returns the page row index of given primary row. Returns null if the row is not on the 8902 * current DataTable page, or if row is not considered a primary row (i.e., row 8903 * extensions). 8904 * 8905 * @method getTrIndex 8906 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID 8907 * string reference to an element within the DataTable page, a Record instance, 8908 * or a Record's RecordSet index. 8909 * @return {Number} Page row index, or null if data row does not exist or is not on current page. 8910 */ 8911 getTrIndex : function(row) { 8912 var record = this.getRecord(row), 8913 index = this.getRecordIndex(record), 8914 tr; 8915 if(record) { 8916 tr = this.getTrEl(record); 8917 if(tr) { 8918 return tr.sectionRowIndex; 8919 } 8920 else { 8921 var oPaginator = this.get("paginator"); 8922 if(oPaginator) { 8923 return oPaginator.get('recordOffset') + index; 8924 } 8925 else { 8926 return index; 8927 } 8928 } 8929 } 8930 YAHOO.log("Could not get page row index for row " + row, "info", this.toString()); 8931 return null; 8932 }, 8933 8934 8935 8936 8937 8938 8939 8940 8941 8942 8943 8944 8945 8946 8947 8948 8949 8950 8951 8952 8953 8954 8955 8956 8957 8958 8959 8960 8961 8962 8963 8964 8965 8966 8967 8968 8969 8970 8971 8972 8973 8974 8975 8976 8977 8978 8979 // TABLE FUNCTIONS 8980 8981 /** 8982 * Loads new data. Convenience method that calls DataSource's sendRequest() 8983 * method under the hood. 8984 * 8985 * @method load 8986 * @param oConfig {object} Optional configuration parameters: 8987 * 8988 * <dl> 8989 * <dt>request</dt><dd>Pass in a new request, or initialRequest is used.</dd> 8990 * <dt>callback</dt><dd>Pass in DataSource sendRequest() callback object, or the following is used: 8991 * <dl> 8992 * <dt>success</dt><dd>datatable.onDataReturnInitializeTable</dd> 8993 * <dt>failure</dt><dd>datatable.onDataReturnInitializeTable</dd> 8994 * <dt>scope</dt><dd>datatable</dd> 8995 * <dt>argument</dt><dd>datatable.getState()</dd> 8996 * </dl> 8997 * </dd> 8998 * <dt>datasource</dt><dd>Pass in a new DataSource instance to override the current DataSource for this transaction.</dd> 8999 * </dl> 9000 */ 9001 load : function(oConfig) { 9002 oConfig = oConfig || {}; 9003 9004 (oConfig.datasource || this._oDataSource).sendRequest(oConfig.request || this.get("initialRequest"), oConfig.callback || { 9005 success: this.onDataReturnInitializeTable, 9006 failure: this.onDataReturnInitializeTable, 9007 scope: this, 9008 argument: this.getState() 9009 }); 9010 }, 9011 9012 /** 9013 * Resets a RecordSet with the given data and populates the page view 9014 * with the new data. Any previous data, and selection and sort states are 9015 * cleared. New data should be added as a separate step. 9016 * 9017 * @method initializeTable 9018 */ 9019 initializeTable : function() { 9020 // Reset init flag 9021 this._bInit = true; 9022 9023 // Clear the RecordSet 9024 this._oRecordSet.reset(); 9025 9026 // Clear the Paginator's totalRecords if paginating 9027 var pag = this.get('paginator'); 9028 if (pag) { 9029 pag.set('totalRecords',0); 9030 } 9031 9032 // Clear selections 9033 this._unselectAllTrEls(); 9034 this._unselectAllTdEls(); 9035 this._aSelections = null; 9036 this._oAnchorRecord = null; 9037 this._oAnchorCell = null; 9038 9039 // Clear sort 9040 this.set("sortedBy", null); 9041 }, 9042 9043 /** 9044 * Internal wrapper calls run() on render Chain instance. 9045 * 9046 * @method _runRenderChain 9047 * @private 9048 */ 9049 _runRenderChain : function() { 9050 this._oChainRender.run(); 9051 }, 9052 9053 /** 9054 * Returns array of Records for current view. For example, if paginated, it 9055 * returns the subset of Records for current page. 9056 * 9057 * @method _getViewRecords 9058 * @protected 9059 * @return {Array} Array of Records to display in current view. 9060 */ 9061 _getViewRecords : function() { 9062 // Paginator is enabled, show a subset of Records 9063 var oPaginator = this.get('paginator'); 9064 if(oPaginator) { 9065 return this._oRecordSet.getRecords( 9066 oPaginator.getStartIndex(), 9067 oPaginator.getRowsPerPage()); 9068 } 9069 // Not paginated, show all records 9070 else { 9071 return this._oRecordSet.getRecords(); 9072 } 9073 9074 }, 9075 9076 /** 9077 * Renders the view with existing Records from the RecordSet while 9078 * maintaining sort, pagination, and selection states. For performance, reuses 9079 * existing DOM elements when possible while deleting extraneous elements. 9080 * 9081 * @method render 9082 */ 9083 render : function() { 9084 //YAHOO.example.Performance.trialStart = new Date(); 9085 9086 this._oChainRender.stop(); 9087 9088 this.fireEvent("beforeRenderEvent"); 9089 YAHOO.log("DataTable rendering...", "info", this.toString()); 9090 9091 var i, j, k, len, 9092 allRecords = this._getViewRecords(); 9093 9094 9095 // From the top, update in-place existing rows, so as to reuse DOM elements 9096 var elTbody = this._elTbody, 9097 loopN = this.get("renderLoopSize"), 9098 nRecordsLength = allRecords.length; 9099 9100 // Table has rows 9101 if(nRecordsLength > 0) { 9102 elTbody.style.display = "none"; 9103 while(elTbody.lastChild) { 9104 elTbody.removeChild(elTbody.lastChild); 9105 } 9106 elTbody.style.display = ""; 9107 9108 // Set up the loop Chain to render rows 9109 this._oChainRender.add({ 9110 method: function(oArg) { 9111 if((this instanceof DT) && this._sId) { 9112 var i = oArg.nCurrentRecord, 9113 endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ? 9114 nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength), 9115 elRow, nextSibling; 9116 9117 elTbody.style.display = "none"; 9118 9119 for(; i<endRecordIndex; i++) { 9120 elRow = Dom.get(allRecords[i].getId()); 9121 elRow = elRow || this._addTrEl(allRecords[i]); 9122 nextSibling = elTbody.childNodes[i] || null; 9123 elTbody.insertBefore(elRow, nextSibling); 9124 } 9125 elTbody.style.display = ""; 9126 9127 // Set up for the next loop 9128 oArg.nCurrentRecord = i; 9129 } 9130 }, 9131 scope: this, 9132 iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1, 9133 argument: { 9134 nCurrentRecord: 0,//nRecordsLength-1, // Start at first Record 9135 nLoopLength: (loopN > 0) ? loopN : nRecordsLength 9136 }, 9137 timeout: (loopN > 0) ? 0 : -1 9138 }); 9139 9140 // Post-render tasks 9141 this._oChainRender.add({ 9142 method: function(oArg) { 9143 if((this instanceof DT) && this._sId) { 9144 while(elTbody.rows.length > nRecordsLength) { 9145 elTbody.removeChild(elTbody.lastChild); 9146 } 9147 this._setFirstRow(); 9148 this._setLastRow(); 9149 this._setRowStripes(); 9150 this._setSelections(); 9151 } 9152 }, 9153 scope: this, 9154 timeout: (loopN > 0) ? 0 : -1 9155 }); 9156 9157 } 9158 // Table has no rows 9159 else { 9160 // Set up the loop Chain to delete rows 9161 var nTotal = elTbody.rows.length; 9162 if(nTotal > 0) { 9163 this._oChainRender.add({ 9164 method: function(oArg) { 9165 if((this instanceof DT) && this._sId) { 9166 var i = oArg.nCurrent, 9167 loopN = oArg.nLoopLength, 9168 nIterEnd = (i - loopN < 0) ? 0 : i - loopN; 9169 9170 elTbody.style.display = "none"; 9171 9172 for(; i>nIterEnd; i--) { 9173 elTbody.deleteRow(-1); 9174 } 9175 elTbody.style.display = ""; 9176 9177 // Set up for the next loop 9178 oArg.nCurrent = i; 9179 } 9180 }, 9181 scope: this, 9182 iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1, 9183 argument: { 9184 nCurrent: nTotal, 9185 nLoopLength: (loopN > 0) ? loopN : nTotal 9186 }, 9187 timeout: (loopN > 0) ? 0 : -1 9188 }); 9189 } 9190 } 9191 this._runRenderChain(); 9192 }, 9193 9194 /** 9195 * Disables DataTable UI. 9196 * 9197 * @method disable 9198 */ 9199 disable : function() { 9200 this._disabled = true; 9201 var elTable = this._elTable; 9202 var elMask = this._elMask; 9203 elMask.style.width = elTable.offsetWidth + "px"; 9204 elMask.style.height = elTable.offsetHeight + "px"; 9205 elMask.style.left = elTable.offsetLeft + "px"; 9206 elMask.style.display = ""; 9207 this.fireEvent("disableEvent"); 9208 }, 9209 9210 /** 9211 * Undisables DataTable UI. 9212 * 9213 * @method undisable 9214 */ 9215 undisable : function() { 9216 this._disabled = false; 9217 this._elMask.style.display = "none"; 9218 this.fireEvent("undisableEvent"); 9219 }, 9220 9221 /** 9222 * Returns disabled state. 9223 * 9224 * @method isDisabled 9225 * @return {Boolean} True if UI is disabled, otherwise false 9226 */ 9227 isDisabled : function() { 9228 return this._disabled; 9229 }, 9230 9231 /** 9232 * Nulls out the entire DataTable instance and related objects, removes attached 9233 * event listeners, and clears out DOM elements inside the container. After 9234 * calling this method, the instance reference should be expliclitly nulled by 9235 * implementer, as in myDataTable = null. Use with caution! 9236 * 9237 * @method destroy 9238 */ 9239 destroy : function() { 9240 // Store for later 9241 var instanceName = this.toString(); 9242 9243 this._oChainRender.stop(); 9244 9245 // Destroy ColumnDD and ColumnResizers 9246 this._destroyColumnHelpers(); 9247 9248 // Destroy all CellEditors 9249 var oCellEditor; 9250 for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) { 9251 oCellEditor = this._oColumnSet.flat[i].editor; 9252 if(oCellEditor && oCellEditor.destroy) { 9253 oCellEditor.destroy(); 9254 this._oColumnSet.flat[i].editor = null; 9255 } 9256 } 9257 9258 // Destroy Paginator 9259 this._destroyPaginator(); 9260 9261 // Unhook custom events 9262 this._oRecordSet.unsubscribeAll(); 9263 this.unsubscribeAll(); 9264 9265 // Unhook DOM events 9266 Ev.removeListener(document, "click", this._onDocumentClick); 9267 9268 // Clear out the container 9269 this._destroyContainerEl(this._elContainer); 9270 9271 // Null out objects 9272 for(var param in this) { 9273 if(lang.hasOwnProperty(this, param)) { 9274 this[param] = null; 9275 } 9276 } 9277 9278 // Clean up static values 9279 DT._nCurrentCount--; 9280 9281 if(DT._nCurrentCount < 1) { 9282 if(DT._elDynStyleNode) { 9283 document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode); 9284 DT._elDynStyleNode = null; 9285 } 9286 } 9287 9288 YAHOO.log("DataTable instance destroyed: " + instanceName); 9289 }, 9290 9291 /** 9292 * Displays message within secondary TBODY. 9293 * 9294 * @method showTableMessage 9295 * @param sHTML {HTML} (optional) Value for innerHTML. 9296 * @param sClassName {String} (optional) Classname. 9297 */ 9298 showTableMessage : function(sHTML, sClassName) { 9299 var elCell = this._elMsgTd; 9300 if(lang.isString(sHTML)) { 9301 elCell.firstChild.innerHTML = sHTML; 9302 } 9303 if(lang.isString(sClassName)) { 9304 elCell.className = sClassName; 9305 } 9306 9307 this._elMsgTbody.style.display = ""; 9308 9309 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName}); 9310 YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString()); 9311 }, 9312 9313 /** 9314 * Hides secondary TBODY. 9315 * 9316 * @method hideTableMessage 9317 */ 9318 hideTableMessage : function() { 9319 if(this._elMsgTbody.style.display != "none") { 9320 this._elMsgTbody.style.display = "none"; 9321 this._elMsgTbody.parentNode.style.width = ""; 9322 this.fireEvent("tableMsgHideEvent"); 9323 YAHOO.log("DataTable message hidden", "info", this.toString()); 9324 } 9325 }, 9326 9327 /** 9328 * Brings focus to the TBODY element. Alias to focusTbodyEl. 9329 * 9330 * @method focus 9331 */ 9332 focus : function() { 9333 this.focusTbodyEl(); 9334 }, 9335 9336 /** 9337 * Brings focus to the THEAD element. 9338 * 9339 * @method focusTheadEl 9340 */ 9341 focusTheadEl : function() { 9342 this._focusEl(this._elThead); 9343 }, 9344 9345 /** 9346 * Brings focus to the TBODY element. 9347 * 9348 * @method focusTbodyEl 9349 */ 9350 focusTbodyEl : function() { 9351 this._focusEl(this._elTbody); 9352 }, 9353 9354 /** 9355 * Setting display:none on DataTable or any parent may impact width validations. 9356 * After setting display back to "", implementers should call this method to 9357 * manually perform those validations. 9358 * 9359 * @method onShow 9360 */ 9361 onShow : function() { 9362 this.validateColumnWidths(); 9363 9364 for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) { 9365 col = allKeys[i]; 9366 if(col._ddResizer) { 9367 col._ddResizer.resetResizerEl(); 9368 } 9369 } 9370 }, 9371 9372 9373 9374 9375 9376 9377 9378 9379 9380 9381 9382 9383 9384 9385 9386 9387 9388 9389 9390 9391 9392 9393 9394 9395 9396 9397 9398 9399 9400 9401 9402 9403 9404 9405 9406 9407 9408 9409 9410 9411 9412 9413 9414 9415 9416 9417 9418 9419 9420 9421 9422 9423 9424 9425 9426 9427 9428 9429 9430 9431 9432 9433 9434 9435 9436 9437 9438 // RECORDSET FUNCTIONS 9439 9440 /** 9441 * Returns Record index for given TR element or page row index. 9442 * 9443 * @method getRecordIndex 9444 * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR 9445 * element reference or page row index. 9446 * @return {Number} Record's RecordSet index, or null. 9447 */ 9448 getRecordIndex : function(row) { 9449 var nTrIndex; 9450 9451 if(!lang.isNumber(row)) { 9452 // By Record 9453 if(row instanceof YAHOO.widget.Record) { 9454 return this._oRecordSet.getRecordIndex(row); 9455 } 9456 // By element reference 9457 else { 9458 // Find the TR element 9459 var el = this.getTrEl(row); 9460 if(el) { 9461 nTrIndex = el.sectionRowIndex; 9462 } 9463 } 9464 } 9465 // By page row index 9466 else { 9467 nTrIndex = row; 9468 } 9469 9470 if(lang.isNumber(nTrIndex)) { 9471 var oPaginator = this.get("paginator"); 9472 if(oPaginator) { 9473 return oPaginator.get('recordOffset') + nTrIndex; 9474 } 9475 else { 9476 return nTrIndex; 9477 } 9478 } 9479 9480 YAHOO.log("Could not get Record index for row " + row, "info", this.toString()); 9481 return null; 9482 }, 9483 9484 /** 9485 * For the given identifier, returns the associated Record instance. 9486 * 9487 * @method getRecord 9488 * @param row {HTMLElement | Number | String} DOM reference to a TR element (or 9489 * child of a TR element), RecordSet position index, or Record ID. 9490 * @return {YAHOO.widget.Record} Record instance. 9491 */ 9492 getRecord : function(row) { 9493 var oRecord = this._oRecordSet.getRecord(row); 9494 9495 if(!oRecord) { 9496 // Validate TR element 9497 var elRow = this.getTrEl(row); 9498 if(elRow) { 9499 oRecord = this._oRecordSet.getRecord(elRow.id); 9500 } 9501 } 9502 9503 if(oRecord instanceof YAHOO.widget.Record) { 9504 return this._oRecordSet.getRecord(oRecord); 9505 } 9506 else { 9507 YAHOO.log("Could not get Record for row at " + row, "info", this.toString()); 9508 return null; 9509 } 9510 }, 9511 9512 9513 9514 9515 9516 9517 9518 9519 9520 9521 9522 9523 9524 9525 9526 9527 9528 9529 9530 9531 9532 9533 9534 9535 9536 9537 9538 9539 9540 9541 9542 9543 9544 9545 9546 9547 9548 9549 9550 9551 9552 9553 9554 9555 9556 9557 // COLUMN FUNCTIONS 9558 9559 /** 9560 * For the given identifier, returns the associated Column instance. Note: For 9561 * getting Columns by Column ID string, please use the method getColumnById(). 9562 * 9563 * @method getColumn 9564 * @param column {HTMLElement | String | Number} TH/TD element (or child of a 9565 * TH/TD element), a Column key, or a ColumnSet key index. 9566 * @return {YAHOO.widget.Column} Column instance. 9567 */ 9568 getColumn : function(column) { 9569 var oColumn = this._oColumnSet.getColumn(column); 9570 9571 if(!oColumn) { 9572 // Validate TD element 9573 var elCell = this.getTdEl(column); 9574 if(elCell) { 9575 oColumn = this._oColumnSet.getColumn(this.getCellIndex(elCell)); 9576 } 9577 // Validate TH element 9578 else { 9579 elCell = this.getThEl(column); 9580 if(elCell) { 9581 // Find by TH el ID 9582 var allColumns = this._oColumnSet.flat; 9583 for(var i=0, len=allColumns.length; i<len; i++) { 9584 if(allColumns[i].getThEl().id === elCell.id) { 9585 oColumn = allColumns[i]; 9586 } 9587 } 9588 } 9589 } 9590 } 9591 if(!oColumn) { 9592 YAHOO.log("Could not get Column for column at " + column, "info", this.toString()); 9593 } 9594 return oColumn; 9595 }, 9596 9597 /** 9598 * For the given Column ID, returns the associated Column instance. Note: For 9599 * getting Columns by key, please use the method getColumn(). 9600 * 9601 * @method getColumnById 9602 * @param column {String} Column ID string. 9603 * @return {YAHOO.widget.Column} Column instance. 9604 */ 9605 getColumnById : function(column) { 9606 return this._oColumnSet.getColumnById(column); 9607 }, 9608 9609 /** 9610 * For the given Column instance, returns next direction to sort. 9611 * 9612 * @method getColumnSortDir 9613 * @param oColumn {YAHOO.widget.Column} Column instance. 9614 * @param oSortedBy {Object} (optional) Specify the state, or use current state. 9615 * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC. 9616 */ 9617 getColumnSortDir : function(oColumn, oSortedBy) { 9618 // Backward compatibility 9619 if(oColumn.sortOptions && oColumn.sortOptions.defaultDir) { 9620 if(oColumn.sortOptions.defaultDir == "asc") { 9621 oColumn.sortOptions.defaultDir = DT.CLASS_ASC; 9622 } 9623 else if (oColumn.sortOptions.defaultDir == "desc") { 9624 oColumn.sortOptions.defaultDir = DT.CLASS_DESC; 9625 } 9626 } 9627 9628 // What is the Column's default sort direction? 9629 var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC; 9630 9631 // Is the Column currently sorted? 9632 var bSorted = false; 9633 oSortedBy = oSortedBy || this.get("sortedBy"); 9634 if(oSortedBy && (oSortedBy.key === oColumn.key)) { 9635 bSorted = true; 9636 if(oSortedBy.dir) { 9637 sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC; 9638 } 9639 else { 9640 sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC; 9641 } 9642 } 9643 return sortDir; 9644 }, 9645 9646 /** 9647 * Overridable method gives implementers a hook to show loading message before 9648 * sorting Column. 9649 * 9650 * @method doBeforeSortColumn 9651 * @param oColumn {YAHOO.widget.Column} Column instance. 9652 * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or 9653 * YAHOO.widget.DataTable.CLASS_DESC. 9654 * @return {Boolean} Return true to continue sorting Column. 9655 */ 9656 doBeforeSortColumn : function(oColumn, sSortDir) { 9657 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING); 9658 return true; 9659 }, 9660 9661 /** 9662 * Sorts given Column. If "dynamicData" is true, current selections are purged before 9663 * a request is sent to the DataSource for data for the new state (using the 9664 * request returned by "generateRequest()"). 9665 * 9666 * @method sortColumn 9667 * @param oColumn {YAHOO.widget.Column} Column instance. 9668 * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or 9669 * YAHOO.widget.DataTable.CLASS_DESC 9670 */ 9671 sortColumn : function(oColumn, sDir) { 9672 if(oColumn && (oColumn instanceof YAHOO.widget.Column)) { 9673 if(!oColumn.sortable) { 9674 Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE); 9675 } 9676 9677 // Validate given direction 9678 if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) { 9679 sDir = null; 9680 } 9681 9682 // Get the sort dir 9683 var sSortDir = sDir || this.getColumnSortDir(oColumn); 9684 9685 // Is the Column currently sorted? 9686 var oSortedBy = this.get("sortedBy") || {}; 9687 var bSorted = (oSortedBy.key === oColumn.key) ? true : false; 9688 9689 var ok = this.doBeforeSortColumn(oColumn, sSortDir); 9690 if(ok) { 9691 // Server-side sort 9692 if(this.get("dynamicData")) { 9693 // Get current state 9694 var oState = this.getState(); 9695 9696 // Reset record offset, if paginated 9697 if(oState.pagination) { 9698 oState.pagination.recordOffset = 0; 9699 } 9700 9701 // Update sortedBy to new values 9702 oState.sortedBy = { 9703 key: oColumn.key, 9704 dir: sSortDir 9705 }; 9706 9707 // Get the request for the new state 9708 var request = this.get("generateRequest")(oState, this); 9709 9710 // Purge selections 9711 this.unselectAllRows(); 9712 this.unselectAllCells(); 9713 9714 // Send request for new data 9715 var callback = { 9716 success : this.onDataReturnSetRows, 9717 failure : this.onDataReturnSetRows, 9718 argument : oState, // Pass along the new state to the callback 9719 scope : this 9720 }; 9721 this._oDataSource.sendRequest(request, callback); 9722 } 9723 // Client-side sort 9724 else { 9725 // Is there a custom sort handler function defined? 9726 var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ? 9727 // Custom sort function 9728 oColumn.sortOptions.sortFunction : null; 9729 9730 // Sort the Records 9731 if(!bSorted || sDir || sortFnc) { 9732 // Default sort function if necessary 9733 sortFnc = sortFnc || this.get("sortFunction"); 9734 // Get the field to sort 9735 var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field; 9736 9737 // Sort the Records 9738 this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false), sField); 9739 } 9740 // Just reverse the Records 9741 else { 9742 this._oRecordSet.reverseRecords(); 9743 } 9744 9745 // Reset to first page if paginated 9746 var oPaginator = this.get('paginator'); 9747 if (oPaginator) { 9748 // Set page silently, so as not to fire change event. 9749 oPaginator.setPage(1,true); 9750 } 9751 9752 // Update UI via sortedBy 9753 this.render(); 9754 this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn}); 9755 } 9756 9757 this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir}); 9758 YAHOO.log("Column \"" + oColumn.key + "\" sorted \"" + sSortDir + "\"", "info", this.toString()); 9759 return; 9760 } 9761 } 9762 YAHOO.log("Could not sort Column \"" + oColumn.key + "\"", "warn", this.toString()); 9763 }, 9764 9765 /** 9766 * Sets given Column to given pixel width. If new width is less than minimum 9767 * width, sets to minimum width. Updates oColumn.width value. 9768 * 9769 * @method setColumnWidth 9770 * @param oColumn {YAHOO.widget.Column} Column instance. 9771 * @param nWidth {Number} New width in pixels. A null value auto-sizes Column, 9772 * subject to minWidth and maxAutoWidth validations. 9773 */ 9774 setColumnWidth : function(oColumn, nWidth) { 9775 if(!(oColumn instanceof YAHOO.widget.Column)) { 9776 oColumn = this.getColumn(oColumn); 9777 } 9778 if(oColumn) { 9779 // Validate new width against minimum width 9780 if(lang.isNumber(nWidth)) { 9781 // This is why we must require a Number... :-| 9782 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth; 9783 9784 // Save state 9785 oColumn.width = nWidth; 9786 9787 // Resize the DOM elements 9788 this._setColumnWidth(oColumn, nWidth+"px"); 9789 9790 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth}); 9791 YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString()); 9792 } 9793 // Unsets a width to auto-size 9794 else if(nWidth === null) { 9795 // Save state 9796 oColumn.width = nWidth; 9797 9798 // Resize the DOM elements 9799 this._setColumnWidth(oColumn, "auto"); 9800 this.validateColumnWidths(oColumn); 9801 this.fireEvent("columnUnsetWidthEvent",{column:oColumn}); 9802 YAHOO.log("Column " + oColumn + " width unset", "info", this.toString()); 9803 } 9804 9805 // Bug 2339454: resize then sort misaligment 9806 this._clearTrTemplateEl(); 9807 } 9808 else { 9809 YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString()); 9810 } 9811 }, 9812 9813 /** 9814 * Sets liner DIV elements of given Column to given width. When value should be 9815 * auto-calculated to fit content overflow is set to visible, otherwise overflow 9816 * is set to hidden. No validations against minimum width and no updating 9817 * Column.width value. 9818 * 9819 * @method _setColumnWidth 9820 * @param oColumn {YAHOO.widget.Column} Column instance. 9821 * @param sWidth {String} New width value. 9822 * @param sOverflow {String} Should be "hidden" when Column width is explicitly 9823 * being set to a value, but should be "visible" when Column is meant to auto-fit content. 9824 * @private 9825 */ 9826 _setColumnWidth : function(oColumn, sWidth, sOverflow) { 9827 if(oColumn && (oColumn.getKeyIndex() !== null)) { 9828 sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden'); 9829 9830 // Dynamic style algorithm 9831 if(!DT._bDynStylesFallback) { 9832 this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow); 9833 } 9834 // Dynamic function algorithm 9835 else { 9836 this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow); 9837 } 9838 } 9839 else { 9840 YAHOO.log("Could not set width of unknown Column " + oColumn + " to " + sWidth, "warn", this.toString()); 9841 } 9842 }, 9843 9844 /** 9845 * Updates width of a Column's liner DIV elements by dynamically creating a 9846 * STYLE node and writing and updating CSS style rules to it. If this fails during 9847 * runtime, the fallback method _setColumnWidthDynFunction() will be called. 9848 * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is 9849 * nested within another TABLE element. For these cases, it is recommended to 9850 * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE. 9851 * 9852 * @method _setColumnWidthDynStyles 9853 * @param oColumn {YAHOO.widget.Column} Column instance. 9854 * @param sWidth {String} New width value. 9855 * @private 9856 */ 9857 _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) { 9858 var s = DT._elDynStyleNode, 9859 rule; 9860 9861 // Create a new STYLE node 9862 if(!s) { 9863 s = document.createElement('style'); 9864 s.type = 'text/css'; 9865 s = document.getElementsByTagName('head').item(0).appendChild(s); 9866 DT._elDynStyleNode = s; 9867 } 9868 9869 // We have a STYLE node to update 9870 if(s) { 9871 // Use unique classname for this Column instance as a hook for resizing 9872 var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER; 9873 9874 // Hide for performance 9875 if(this._elTbody) { 9876 this._elTbody.style.display = 'none'; 9877 } 9878 9879 rule = DT._oDynStyles[sClassname]; 9880 9881 // The Column does not yet have a rule 9882 if(!rule) { 9883 if(s.styleSheet && s.styleSheet.addRule) { 9884 s.styleSheet.addRule(sClassname,"overflow:"+sOverflow); 9885 s.styleSheet.addRule(sClassname,'width:'+sWidth); 9886 rule = s.styleSheet.rules[s.styleSheet.rules.length-1]; 9887 DT._oDynStyles[sClassname] = rule; 9888 } 9889 else if(s.sheet && s.sheet.insertRule) { 9890 s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length); 9891 rule = s.sheet.cssRules[s.sheet.cssRules.length-1]; 9892 DT._oDynStyles[sClassname] = rule; 9893 } 9894 } 9895 // We have a rule to update 9896 else { 9897 rule.style.overflow = sOverflow; 9898 rule.style.width = sWidth; 9899 } 9900 9901 // Unhide 9902 if(this._elTbody) { 9903 this._elTbody.style.display = ''; 9904 } 9905 } 9906 9907 // That was not a success, we must call the fallback routine 9908 if(!rule) { 9909 DT._bDynStylesFallback = true; 9910 this._setColumnWidthDynFunction(oColumn, sWidth); 9911 } 9912 }, 9913 9914 /** 9915 * Updates width of a Column's liner DIV elements by dynamically creating a 9916 * function to update all element style properties in one pass. Note: This 9917 * technique is not supported in sandboxed environments that prohibit EVALs. 9918 * 9919 * @method _setColumnWidthDynFunction 9920 * @param oColumn {YAHOO.widget.Column} Column instance. 9921 * @param sWidth {String} New width value. 9922 * @private 9923 */ 9924 _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) { 9925 // TODO: why is this here? 9926 if(sWidth == 'auto') { 9927 sWidth = ''; 9928 } 9929 9930 // Create one function for each value of rows.length 9931 var rowslen = this._elTbody ? this._elTbody.rows.length : 0; 9932 9933 // Dynamically create the function 9934 if (!this._aDynFunctions[rowslen]) { 9935 9936 //Compile a custom function to do all the liner div width 9937 //assignments at the same time. A unique function is required 9938 //for each unique number of rows in _elTbody. This will 9939 //result in a function declaration like: 9940 //function (oColumn,sWidth,sOverflow) { 9941 // var colIdx = oColumn.getKeyIndex(); 9942 // oColumn.getThLinerEl().style.overflow = 9943 // this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow = 9944 // this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow = 9945 // ... (for all row indices in this._elTbody.rows.length - 1) 9946 // this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow = 9947 // sOverflow; 9948 // oColumn.getThLinerEl().style.width = 9949 // this._elTbody.rows[0].cells[colIdx].firstChild.style.width = 9950 // this._elTbody.rows[1].cells[colIdx].firstChild.style.width = 9951 // ... (for all row indices in this._elTbody.rows.length - 1) 9952 // this._elTbody.rows[99].cells[colIdx].firstChild.style.width = 9953 // sWidth; 9954 //} 9955 9956 var i,j,k; 9957 var resizerDef = [ 9958 'var colIdx=oColumn.getKeyIndex();', 9959 'oColumn.getThLinerEl().style.overflow=' 9960 ]; 9961 for (i=rowslen-1, j=2; i >= 0; --i) { 9962 resizerDef[j++] = 'this._elTbody.rows['; 9963 resizerDef[j++] = i; 9964 resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow='; 9965 } 9966 resizerDef[j] = 'sOverflow;'; 9967 resizerDef[j+1] = 'oColumn.getThLinerEl().style.width='; 9968 for (i=rowslen-1, k=j+2; i >= 0; --i) { 9969 resizerDef[k++] = 'this._elTbody.rows['; 9970 resizerDef[k++] = i; 9971 resizerDef[k++] = '].cells[colIdx].firstChild.style.width='; 9972 } 9973 resizerDef[k] = 'sWidth;'; 9974 this._aDynFunctions[rowslen] = 9975 new Function('oColumn','sWidth','sOverflow',resizerDef.join('')); 9976 } 9977 9978 // Get the function to execute 9979 var resizerFn = this._aDynFunctions[rowslen]; 9980 9981 // TODO: Hide TBODY for performance in _setColumnWidthDynFunction? 9982 if (resizerFn) { 9983 resizerFn.call(this,oColumn,sWidth,sOverflow); 9984 } 9985 }, 9986 9987 /** 9988 * For one or all Columns, when Column is not hidden, width is not set, and minWidth 9989 * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth. 9990 * 9991 * @method validateColumnWidths 9992 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated. 9993 */ 9994 validateColumnWidths : function(oColumn) { 9995 var elColgroup = this._elColgroup; 9996 var elColgroupClone = elColgroup.cloneNode(true); 9997 var bNeedsValidation = false; 9998 var allKeys = this._oColumnSet.keys; 9999 var elThLiner; 10000 // Validate just one Column's minWidth and/or maxAutoWidth 10001 if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) { 10002 elThLiner = oColumn.getThLinerEl(); 10003 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) { 10004 elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width = 10005 oColumn.minWidth + 10006 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) + 10007 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px"; 10008 bNeedsValidation = true; 10009 } 10010 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) { 10011 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden"); 10012 } 10013 } 10014 // Validate all Columns 10015 else { 10016 for(var i=0, len=allKeys.length; i<len; i++) { 10017 oColumn = allKeys[i]; 10018 if(!oColumn.hidden && !oColumn.width) { 10019 elThLiner = oColumn.getThLinerEl(); 10020 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) { 10021 elColgroupClone.childNodes[i].style.width = 10022 oColumn.minWidth + 10023 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) + 10024 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px"; 10025 bNeedsValidation = true; 10026 } 10027 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) { 10028 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden"); 10029 } 10030 } 10031 } 10032 } 10033 if(bNeedsValidation) { 10034 elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup); 10035 this._elColgroup = elColgroupClone; 10036 } 10037 }, 10038 10039 /** 10040 * Clears minWidth. 10041 * 10042 * @method _clearMinWidth 10043 * @param oColumn {YAHOO.widget.Column} Which Column. 10044 * @private 10045 */ 10046 _clearMinWidth : function(oColumn) { 10047 if(oColumn.getKeyIndex() !== null) { 10048 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = ''; 10049 } 10050 }, 10051 10052 /** 10053 * Restores minWidth. 10054 * 10055 * @method _restoreMinWidth 10056 * @param oColumn {YAHOO.widget.Column} Which Column. 10057 * @private 10058 */ 10059 _restoreMinWidth : function(oColumn) { 10060 if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) { 10061 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px'; 10062 } 10063 }, 10064 10065 /** 10066 * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only 10067 * hide/show non-nested Columns, and top-level parent Columns (which will 10068 * hide/show all children Columns). 10069 * 10070 * @method hideColumn 10071 * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column 10072 * instance, TH/TD element (or child of a TH/TD element), a Column key, or a 10073 * ColumnSet key index. 10074 */ 10075 hideColumn : function(oColumn) { 10076 if(!(oColumn instanceof YAHOO.widget.Column)) { 10077 oColumn = this.getColumn(oColumn); 10078 } 10079 // Only top-level Columns can get hidden due to issues in FF2 and SF3 10080 if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) { 10081 10082 var allrows = this.getTbodyEl().rows; 10083 var l = allrows.length; 10084 var allDescendants = this._oColumnSet.getDescendants(oColumn); 10085 10086 // Hide each nested Column 10087 for(var i=0, len=allDescendants.length; i<len; i++) { 10088 var thisColumn = allDescendants[i]; 10089 thisColumn.hidden = true; 10090 10091 // Style the head cell 10092 Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN); 10093 10094 // Does this Column have body cells? 10095 var thisKeyIndex = thisColumn.getKeyIndex(); 10096 if(thisKeyIndex !== null) { 10097 // Clear minWidth 10098 this._clearMinWidth(oColumn); 10099 10100 // Style the body cells 10101 for(var j=0;j<l;j++) { 10102 Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN); 10103 } 10104 } 10105 10106 this.fireEvent("columnHideEvent",{column:thisColumn}); 10107 YAHOO.log("Column \"" + oColumn.key + "\" hidden", "info", this.toString()); 10108 } 10109 10110 this._repaintOpera(); 10111 this._clearTrTemplateEl(); 10112 } 10113 else { 10114 YAHOO.log("Could not hide Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be hidden", "warn", this.toString()); 10115 } 10116 }, 10117 10118 /** 10119 * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only 10120 * hide/show non-nested Columns, and top-level parent Columns (which will 10121 * hide/show all children Columns). 10122 * 10123 * @method showColumn 10124 * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column 10125 * instance, TH/TD element (or child of a TH/TD element), a Column key, or a 10126 * ColumnSet key index. 10127 */ 10128 showColumn : function(oColumn) { 10129 if(!(oColumn instanceof YAHOO.widget.Column)) { 10130 oColumn = this.getColumn(oColumn); 10131 } 10132 // Only top-level Columns can get hidden 10133 if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) { 10134 var allrows = this.getTbodyEl().rows; 10135 var l = allrows.length; 10136 var allDescendants = this._oColumnSet.getDescendants(oColumn); 10137 10138 // Show each nested Column 10139 for(var i=0, len=allDescendants.length; i<len; i++) { 10140 var thisColumn = allDescendants[i]; 10141 thisColumn.hidden = false; 10142 10143 // Unstyle the head cell 10144 Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN); 10145 10146 // Does this Column have body cells? 10147 var thisKeyIndex = thisColumn.getKeyIndex(); 10148 if(thisKeyIndex !== null) { 10149 // Restore minWidth 10150 this._restoreMinWidth(oColumn); 10151 10152 10153 // Unstyle the body cells 10154 for(var j=0;j<l;j++) { 10155 Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN); 10156 } 10157 } 10158 10159 this.fireEvent("columnShowEvent",{column:thisColumn}); 10160 YAHOO.log("Column \"" + oColumn.key + "\" shown", "info", this.toString()); 10161 } 10162 this._clearTrTemplateEl(); 10163 } 10164 else { 10165 YAHOO.log("Could not show Column \"" + lang.dump(oColumn) + "\". Only non-nested Columns can be shown", "warn", this.toString()); 10166 } 10167 }, 10168 10169 /** 10170 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove 10171 * non-nested Columns, and top-level parent Columns (which will remove all 10172 * children Columns). 10173 * 10174 * @method removeColumn 10175 * @param oColumn {YAHOO.widget.Column} Column instance. 10176 * @return oColumn {YAHOO.widget.Column} Removed Column instance. 10177 */ 10178 removeColumn : function(oColumn) { 10179 // Validate Column 10180 if(!(oColumn instanceof YAHOO.widget.Column)) { 10181 oColumn = this.getColumn(oColumn); 10182 } 10183 if(oColumn) { 10184 var nColTreeIndex = oColumn.getTreeIndex(); 10185 if(nColTreeIndex !== null) { 10186 // Which key index(es) 10187 var i, len, 10188 aKeyIndexes = oColumn.getKeyIndex(); 10189 // Must be a parent Column 10190 if(aKeyIndexes === null) { 10191 var descKeyIndexes = []; 10192 var allDescendants = this._oColumnSet.getDescendants(oColumn); 10193 for(i=0, len=allDescendants.length; i<len; i++) { 10194 // Is this descendant a key Column? 10195 var thisKey = allDescendants[i].getKeyIndex(); 10196 if(thisKey !== null) { 10197 descKeyIndexes[descKeyIndexes.length] = thisKey; 10198 } 10199 } 10200 if(descKeyIndexes.length > 0) { 10201 aKeyIndexes = descKeyIndexes; 10202 } 10203 } 10204 // Must be a key Column 10205 else { 10206 aKeyIndexes = [aKeyIndexes]; 10207 } 10208 10209 if(aKeyIndexes !== null) { 10210 // Sort the indexes so we can remove from the right 10211 aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);}); 10212 10213 // Destroy previous THEAD 10214 this._destroyTheadEl(); 10215 10216 // Create new THEAD 10217 var aOrigColumnDefs = this._oColumnSet.getDefinitions(); 10218 oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0]; 10219 this._initColumnSet(aOrigColumnDefs); 10220 this._initTheadEl(); 10221 10222 // Remove COL 10223 for(i=aKeyIndexes.length-1; i>-1; i--) { 10224 this._removeColgroupColEl(aKeyIndexes[i]); 10225 } 10226 10227 // Remove TD 10228 var allRows = this._elTbody.rows; 10229 if(allRows.length > 0) { 10230 var loopN = this.get("renderLoopSize"), 10231 loopEnd = allRows.length; 10232 this._oChainRender.add({ 10233 method: function(oArg) { 10234 if((this instanceof DT) && this._sId) { 10235 var i = oArg.nCurrentRow, 10236 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length, 10237 aIndexes = oArg.aIndexes, 10238 j; 10239 for(; i < len; ++i) { 10240 for(j = aIndexes.length-1; j>-1; j--) { 10241 allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]); 10242 } 10243 } 10244 oArg.nCurrentRow = i; 10245 } 10246 }, 10247 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1, 10248 argument: {nCurrentRow:0, aIndexes:aKeyIndexes}, 10249 scope: this, 10250 timeout: (loopN > 0) ? 0 : -1 10251 }); 10252 this._runRenderChain(); 10253 } 10254 10255 this.fireEvent("columnRemoveEvent",{column:oColumn}); 10256 YAHOO.log("Column \"" + oColumn.key + "\" removed", "info", this.toString()); 10257 return oColumn; 10258 } 10259 } 10260 } 10261 YAHOO.log("Could not remove Column \"" + oColumn.key + "\". Only non-nested Columns can be removed", "warn", this.toString()); 10262 }, 10263 10264 /** 10265 * Inserts given Column at the index if given, otherwise at the end. NOTE: You 10266 * can only add non-nested Columns and top-level parent Columns. You cannot add 10267 * a nested Column to an existing parent. 10268 * 10269 * @method insertColumn 10270 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column 10271 * definition or a Column instance. 10272 * @param index {Number} (optional) New tree index. 10273 * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 10274 */ 10275 insertColumn : function(oColumn, index) { 10276 // Validate Column 10277 if(oColumn instanceof YAHOO.widget.Column) { 10278 oColumn = oColumn.getDefinition(); 10279 } 10280 else if(oColumn.constructor !== Object) { 10281 YAHOO.log("Could not insert Column \"" + oColumn + "\" due to invalid argument", "warn", this.toString()); 10282 return; 10283 } 10284 10285 // Validate index or append new Column to the end of the ColumnSet 10286 var oColumnSet = this._oColumnSet; 10287 if(!lang.isValue(index) || !lang.isNumber(index)) { 10288 index = oColumnSet.tree[0].length; 10289 } 10290 10291 // Destroy previous THEAD 10292 this._destroyTheadEl(); 10293 10294 // Create new THEAD 10295 var aNewColumnDefs = this._oColumnSet.getDefinitions(); 10296 aNewColumnDefs.splice(index, 0, oColumn); 10297 this._initColumnSet(aNewColumnDefs); 10298 this._initTheadEl(); 10299 10300 // Need to refresh the reference 10301 oColumnSet = this._oColumnSet; 10302 var oNewColumn = oColumnSet.tree[0][index]; 10303 10304 // Get key index(es) for new Column 10305 var i, len, 10306 descKeyIndexes = []; 10307 var allDescendants = oColumnSet.getDescendants(oNewColumn); 10308 for(i=0, len=allDescendants.length; i<len; i++) { 10309 // Is this descendant a key Column? 10310 var thisKey = allDescendants[i].getKeyIndex(); 10311 if(thisKey !== null) { 10312 descKeyIndexes[descKeyIndexes.length] = thisKey; 10313 } 10314 } 10315 10316 if(descKeyIndexes.length > 0) { 10317 // Sort the indexes 10318 var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0]; 10319 10320 // Add COL 10321 for(i=descKeyIndexes.length-1; i>-1; i--) { 10322 this._insertColgroupColEl(descKeyIndexes[i]); 10323 } 10324 10325 // Add TD 10326 var allRows = this._elTbody.rows; 10327 if(allRows.length > 0) { 10328 var loopN = this.get("renderLoopSize"), 10329 loopEnd = allRows.length; 10330 10331 // Get templates for each new TD 10332 var aTdTemplates = [], 10333 elTdTemplate; 10334 for(i=0, len=descKeyIndexes.length; i<len; i++) { 10335 var thisKeyIndex = descKeyIndexes[i]; 10336 elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true); 10337 elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1)); 10338 aTdTemplates[thisKeyIndex] = elTdTemplate; 10339 } 10340 10341 this._oChainRender.add({ 10342 method: function(oArg) { 10343 if((this instanceof DT) && this._sId) { 10344 var i = oArg.nCurrentRow, j, 10345 descKeyIndexes = oArg.descKeyIndexes, 10346 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length, 10347 nextSibling; 10348 for(; i < len; ++i) { 10349 nextSibling = allRows[i].childNodes[newIndex] || null; 10350 for(j=descKeyIndexes.length-1; j>-1; j--) { 10351 allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling); 10352 } 10353 } 10354 oArg.nCurrentRow = i; 10355 } 10356 }, 10357 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1, 10358 argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes}, 10359 scope: this, 10360 timeout: (loopN > 0) ? 0 : -1 10361 }); 10362 this._runRenderChain(); 10363 } 10364 10365 this.fireEvent("columnInsertEvent",{column:oColumn,index:index}); 10366 YAHOO.log("Column \"" + oColumn.key + "\" inserted into index " + index, "info", this.toString()); 10367 return oNewColumn; 10368 } 10369 }, 10370 10371 /** 10372 * Removes given Column and inserts into given tree index. NOTE: You 10373 * can only reorder non-nested Columns and top-level parent Columns. You cannot 10374 * reorder a nested Column to an existing parent. 10375 * 10376 * @method reorderColumn 10377 * @param oColumn {YAHOO.widget.Column} Column instance. 10378 * @param index {Number} New tree index. 10379 * @return oColumn {YAHOO.widget.Column} Reordered Column instance. 10380 */ 10381 reorderColumn : function(oColumn, index) { 10382 // Validate Column and new index 10383 if(!(oColumn instanceof YAHOO.widget.Column)) { 10384 oColumn = this.getColumn(oColumn); 10385 } 10386 if(oColumn && YAHOO.lang.isNumber(index)) { 10387 var nOrigTreeIndex = oColumn.getTreeIndex(); 10388 if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) { 10389 // Which key index(es) 10390 var i, len, 10391 aOrigKeyIndexes = oColumn.getKeyIndex(), 10392 allDescendants, 10393 descKeyIndexes = [], 10394 thisKey; 10395 // Must be a parent Column... 10396 if(aOrigKeyIndexes === null) { 10397 allDescendants = this._oColumnSet.getDescendants(oColumn); 10398 for(i=0, len=allDescendants.length; i<len; i++) { 10399 // Is this descendant a key Column? 10400 thisKey = allDescendants[i].getKeyIndex(); 10401 if(thisKey !== null) { 10402 descKeyIndexes[descKeyIndexes.length] = thisKey; 10403 } 10404 } 10405 if(descKeyIndexes.length > 0) { 10406 aOrigKeyIndexes = descKeyIndexes; 10407 } 10408 } 10409 // ...or else must be a key Column 10410 else { 10411 aOrigKeyIndexes = [aOrigKeyIndexes]; 10412 } 10413 10414 if(aOrigKeyIndexes !== null) { 10415 // Sort the indexes 10416 aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);}); 10417 10418 // Destroy previous THEAD 10419 this._destroyTheadEl(); 10420 10421 // Create new THEAD 10422 var aColumnDefs = this._oColumnSet.getDefinitions(); 10423 var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0]; 10424 aColumnDefs.splice(index, 0, oColumnDef); 10425 this._initColumnSet(aColumnDefs); 10426 this._initTheadEl(); 10427 10428 // Need to refresh the reference 10429 var oNewColumn = this._oColumnSet.tree[0][index]; 10430 10431 // What are new key index(es) 10432 var aNewKeyIndexes = oNewColumn.getKeyIndex(); 10433 // Must be a parent Column 10434 if(aNewKeyIndexes === null) { 10435 descKeyIndexes = []; 10436 allDescendants = this._oColumnSet.getDescendants(oNewColumn); 10437 for(i=0, len=allDescendants.length; i<len; i++) { 10438 // Is this descendant a key Column? 10439 thisKey = allDescendants[i].getKeyIndex(); 10440 if(thisKey !== null) { 10441 descKeyIndexes[descKeyIndexes.length] = thisKey; 10442 } 10443 } 10444 if(descKeyIndexes.length > 0) { 10445 aNewKeyIndexes = descKeyIndexes; 10446 } 10447 } 10448 // Must be a key Column 10449 else { 10450 aNewKeyIndexes = [aNewKeyIndexes]; 10451 } 10452 10453 // Sort the new indexes and grab the first one for the new location 10454 var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0]; 10455 10456 // Reorder COL 10457 this._reorderColgroupColEl(aOrigKeyIndexes, newIndex); 10458 10459 // Reorder TD 10460 var allRows = this._elTbody.rows; 10461 if(allRows.length > 0) { 10462 var loopN = this.get("renderLoopSize"), 10463 loopEnd = allRows.length; 10464 this._oChainRender.add({ 10465 method: function(oArg) { 10466 if((this instanceof DT) && this._sId) { 10467 var i = oArg.nCurrentRow, j, tmpTds, nextSibling, 10468 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length, 10469 aIndexes = oArg.aIndexes, thisTr; 10470 // For each row 10471 for(; i < len; ++i) { 10472 tmpTds = []; 10473 thisTr = allRows[i]; 10474 10475 // Remove each TD 10476 for(j=aIndexes.length-1; j>-1; j--) { 10477 tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]])); 10478 } 10479 10480 // Insert each TD 10481 nextSibling = thisTr.childNodes[newIndex] || null; 10482 for(j=tmpTds.length-1; j>-1; j--) { 10483 thisTr.insertBefore(tmpTds[j], nextSibling); 10484 } 10485 } 10486 oArg.nCurrentRow = i; 10487 } 10488 }, 10489 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1, 10490 argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes}, 10491 scope: this, 10492 timeout: (loopN > 0) ? 0 : -1 10493 }); 10494 this._runRenderChain(); 10495 } 10496 10497 this.fireEvent("columnReorderEvent",{column:oNewColumn, oldIndex:nOrigTreeIndex}); 10498 YAHOO.log("Column \"" + oNewColumn.key + "\" reordered", "info", this.toString()); 10499 return oNewColumn; 10500 } 10501 } 10502 } 10503 YAHOO.log("Could not reorder Column \"" + oColumn.key + "\". Only non-nested Columns can be reordered", "warn", this.toString()); 10504 }, 10505 10506 /** 10507 * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only 10508 * select/unselect non-nested Columns, and bottom-level key Columns. 10509 * 10510 * @method selectColumn 10511 * @param column {HTMLElement | String | Number} DOM reference or ID string to a 10512 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index. 10513 */ 10514 selectColumn : function(oColumn) { 10515 oColumn = this.getColumn(oColumn); 10516 if(oColumn && !oColumn.selected) { 10517 // Only bottom-level Columns can get hidden 10518 if(oColumn.getKeyIndex() !== null) { 10519 oColumn.selected = true; 10520 10521 // Update head cell 10522 var elTh = oColumn.getThEl(); 10523 Dom.addClass(elTh,DT.CLASS_SELECTED); 10524 10525 // Update body cells 10526 var allRows = this.getTbodyEl().rows; 10527 var oChainRender = this._oChainRender; 10528 oChainRender.add({ 10529 method: function(oArg) { 10530 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) { 10531 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED); 10532 } 10533 oArg.rowIndex++; 10534 }, 10535 scope: this, 10536 iterations: allRows.length, 10537 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()} 10538 }); 10539 10540 this._clearTrTemplateEl(); 10541 10542 this._elTbody.style.display = "none"; 10543 this._runRenderChain(); 10544 this._elTbody.style.display = ""; 10545 10546 this.fireEvent("columnSelectEvent",{column:oColumn}); 10547 YAHOO.log("Column \"" + oColumn.key + "\" selected", "info", this.toString()); 10548 } 10549 else { 10550 YAHOO.log("Could not select Column \"" + oColumn.key + "\". Only non-nested Columns can be selected", "warn", this.toString()); 10551 } 10552 } 10553 }, 10554 10555 /** 10556 * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only 10557 * select/unselect non-nested Columns, and bottom-level key Columns. 10558 * 10559 * @method unselectColumn 10560 * @param column {HTMLElement | String | Number} DOM reference or ID string to a 10561 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index. 10562 */ 10563 unselectColumn : function(oColumn) { 10564 oColumn = this.getColumn(oColumn); 10565 if(oColumn && oColumn.selected) { 10566 // Only bottom-level Columns can get hidden 10567 if(oColumn.getKeyIndex() !== null) { 10568 oColumn.selected = false; 10569 10570 // Update head cell 10571 var elTh = oColumn.getThEl(); 10572 Dom.removeClass(elTh,DT.CLASS_SELECTED); 10573 10574 // Update body cells 10575 var allRows = this.getTbodyEl().rows; 10576 var oChainRender = this._oChainRender; 10577 oChainRender.add({ 10578 method: function(oArg) { 10579 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) { 10580 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED); 10581 } 10582 oArg.rowIndex++; 10583 }, 10584 scope: this, 10585 iterations:allRows.length, 10586 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()} 10587 }); 10588 10589 this._clearTrTemplateEl(); 10590 10591 this._elTbody.style.display = "none"; 10592 this._runRenderChain(); 10593 this._elTbody.style.display = ""; 10594 10595 this.fireEvent("columnUnselectEvent",{column:oColumn}); 10596 YAHOO.log("Column \"" + oColumn.key + "\" unselected", "info", this.toString()); 10597 } 10598 else { 10599 YAHOO.log("Could not unselect Column \"" + oColumn.key + "\". Only non-nested Columns can be unselected", "warn", this.toString()); 10600 } 10601 } 10602 }, 10603 10604 /** 10605 * Returns an array selected Column instances. 10606 * 10607 * @method getSelectedColumns 10608 * @return {YAHOO.widget.Column[]} Array of Column instances. 10609 */ 10610 getSelectedColumns : function(oColumn) { 10611 var selectedColumns = []; 10612 var aKeys = this._oColumnSet.keys; 10613 for(var i=0,len=aKeys.length; i<len; i++) { 10614 if(aKeys[i].selected) { 10615 selectedColumns[selectedColumns.length] = aKeys[i]; 10616 } 10617 } 10618 return selectedColumns; 10619 }, 10620 10621 /** 10622 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column. 10623 * NOTE: You cannot highlight/unhighlight nested Columns. You can only 10624 * highlight/unhighlight non-nested Columns, and bottom-level key Columns. 10625 * 10626 * @method highlightColumn 10627 * @param column {HTMLElement | String | Number} DOM reference or ID string to a 10628 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index. 10629 */ 10630 highlightColumn : function(column) { 10631 var oColumn = this.getColumn(column); 10632 // Only bottom-level Columns can get highlighted 10633 if(oColumn && (oColumn.getKeyIndex() !== null)) { 10634 // Update head cell 10635 var elTh = oColumn.getThEl(); 10636 Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED); 10637 10638 // Update body cells 10639 var allRows = this.getTbodyEl().rows; 10640 var oChainRender = this._oChainRender; 10641 oChainRender.add({ 10642 method: function(oArg) { 10643 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) { 10644 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED); 10645 } 10646 oArg.rowIndex++; 10647 }, 10648 scope: this, 10649 iterations:allRows.length, 10650 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}, 10651 timeout: -1 10652 }); 10653 this._elTbody.style.display = "none"; 10654 this._runRenderChain(); 10655 this._elTbody.style.display = ""; 10656 10657 this.fireEvent("columnHighlightEvent",{column:oColumn}); 10658 YAHOO.log("Column \"" + oColumn.key + "\" highlighed", "info", this.toString()); 10659 } 10660 else { 10661 YAHOO.log("Could not highlight Column \"" + oColumn.key + "\". Only non-nested Columns can be highlighted", "warn", this.toString()); 10662 } 10663 }, 10664 10665 /** 10666 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column. 10667 * NOTE: You cannot highlight/unhighlight nested Columns. You can only 10668 * highlight/unhighlight non-nested Columns, and bottom-level key Columns. 10669 * 10670 * @method unhighlightColumn 10671 * @param column {HTMLElement | String | Number} DOM reference or ID string to a 10672 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index. 10673 */ 10674 unhighlightColumn : function(column) { 10675 var oColumn = this.getColumn(column); 10676 // Only bottom-level Columns can get highlighted 10677 if(oColumn && (oColumn.getKeyIndex() !== null)) { 10678 // Update head cell 10679 var elTh = oColumn.getThEl(); 10680 Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED); 10681 10682 // Update body cells 10683 var allRows = this.getTbodyEl().rows; 10684 var oChainRender = this._oChainRender; 10685 oChainRender.add({ 10686 method: function(oArg) { 10687 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) { 10688 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED); 10689 } 10690 oArg.rowIndex++; 10691 }, 10692 scope: this, 10693 iterations:allRows.length, 10694 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}, 10695 timeout: -1 10696 }); 10697 this._elTbody.style.display = "none"; 10698 this._runRenderChain(); 10699 this._elTbody.style.display = ""; 10700 10701 this.fireEvent("columnUnhighlightEvent",{column:oColumn}); 10702 YAHOO.log("Column \"" + oColumn.key + "\" unhighlighted", "info", this.toString()); 10703 } 10704 else { 10705 YAHOO.log("Could not unhighlight Column \"" + oColumn.key + "\". Only non-nested Columns can be unhighlighted", "warn", this.toString()); 10706 } 10707 }, 10708 10709 10710 10711 10712 10713 10714 10715 10716 10717 10718 10719 10720 10721 10722 10723 10724 10725 10726 10727 10728 10729 10730 10731 10732 10733 10734 10735 10736 10737 10738 10739 10740 10741 10742 10743 10744 10745 10746 10747 10748 10749 10750 10751 10752 // ROW FUNCTIONS 10753 10754 /** 10755 * Adds one new Record of data into the RecordSet at the index if given, 10756 * otherwise at the end. If the new Record is in page view, the 10757 * corresponding DOM elements are also updated. 10758 * 10759 * @method addRow 10760 * @param oData {Object} Object literal of data for the row. 10761 * @param index {Number} (optional) RecordSet position index at which to add data. 10762 */ 10763 addRow : function(oData, index) { 10764 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) { 10765 YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString()); 10766 return; 10767 } 10768 10769 if(oData && lang.isObject(oData)) { 10770 var oRecord = this._oRecordSet.addRecord(oData, index); 10771 if(oRecord) { 10772 var recIndex; 10773 var oPaginator = this.get('paginator'); 10774 10775 // Paginated 10776 if (oPaginator) { 10777 // Update the paginator's totalRecords 10778 var totalRecords = oPaginator.get('totalRecords'); 10779 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) { 10780 oPaginator.set('totalRecords',totalRecords + 1); 10781 } 10782 10783 recIndex = this.getRecordIndex(oRecord); 10784 var endRecIndex = (oPaginator.getPageRecords())[1]; 10785 10786 // New record affects the view 10787 if (recIndex <= endRecIndex) { 10788 // Defer UI updates to the render method 10789 this.render(); 10790 } 10791 10792 this.fireEvent("rowAddEvent", {record:oRecord}); 10793 YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString()); 10794 return; 10795 } 10796 // Not paginated 10797 else { 10798 recIndex = this.getRecordIndex(oRecord); 10799 if(lang.isNumber(recIndex)) { 10800 // Add the TR element 10801 this._oChainRender.add({ 10802 method: function(oArg) { 10803 if((this instanceof DT) && this._sId) { 10804 var oRecord = oArg.record; 10805 var recIndex = oArg.recIndex; 10806 var elNewTr = this._addTrEl(oRecord); 10807 if(elNewTr) { 10808 var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null; 10809 this._elTbody.insertBefore(elNewTr, elNext); 10810 10811 // Set FIRST/LAST 10812 if(recIndex === 0) { 10813 this._setFirstRow(); 10814 } 10815 if(elNext === null) { 10816 this._setLastRow(); 10817 } 10818 // Set EVEN/ODD 10819 this._setRowStripes(); 10820 10821 this.hideTableMessage(); 10822 10823 this.fireEvent("rowAddEvent", {record:oRecord}); 10824 YAHOO.log("Added a row for Record " + YAHOO.lang.dump(oRecord) + " at RecordSet index " + recIndex, "info", this.toString()); 10825 } 10826 } 10827 }, 10828 argument: {record: oRecord, recIndex: recIndex}, 10829 scope: this, 10830 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1 10831 }); 10832 this._runRenderChain(); 10833 return; 10834 } 10835 } 10836 } 10837 } 10838 YAHOO.log("Could not add row at index " + index + " with " + lang.dump(oData), "warn", this.toString()); 10839 }, 10840 10841 /** 10842 * Convenience method to add multiple rows. 10843 * 10844 * @method addRows 10845 * @param aData {Object[]} Array of object literal data for the rows. 10846 * @param index {Number} (optional) RecordSet position index at which to add data. 10847 */ 10848 addRows : function(aData, index) { 10849 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) { 10850 YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString()); 10851 return; 10852 } 10853 10854 if(lang.isArray(aData)) { 10855 var aRecords = this._oRecordSet.addRecords(aData, index); 10856 if(aRecords) { 10857 var recIndex = this.getRecordIndex(aRecords[0]); 10858 10859 // Paginated 10860 var oPaginator = this.get('paginator'); 10861 if (oPaginator) { 10862 // Update the paginator's totalRecords 10863 var totalRecords = oPaginator.get('totalRecords'); 10864 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) { 10865 oPaginator.set('totalRecords',totalRecords + aRecords.length); 10866 } 10867 10868 var endRecIndex = (oPaginator.getPageRecords())[1]; 10869 10870 // At least one of the new records affects the view 10871 if (recIndex <= endRecIndex) { 10872 this.render(); 10873 } 10874 10875 this.fireEvent("rowsAddEvent", {records:aRecords}); 10876 YAHOO.log("Added " + aRecords.length + 10877 " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) + 10878 " with data " + lang.dump(aData), "info", this.toString()); 10879 return; 10880 } 10881 // Not paginated 10882 else { 10883 // Add the TR elements 10884 var loopN = this.get("renderLoopSize"); 10885 var loopEnd = recIndex + aData.length; 10886 var nRowsNeeded = (loopEnd - recIndex); // how many needed 10887 var isLast = (recIndex >= this._elTbody.rows.length); 10888 this._oChainRender.add({ 10889 method: function(oArg) { 10890 if((this instanceof DT) && this._sId) { 10891 var aRecords = oArg.aRecords, 10892 i = oArg.nCurrentRow, 10893 j = oArg.nCurrentRecord, 10894 len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd, 10895 df = document.createDocumentFragment(), 10896 elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null; 10897 for(; i < len; i++, j++) { 10898 df.appendChild(this._addTrEl(aRecords[j])); 10899 } 10900 this._elTbody.insertBefore(df, elNext); 10901 oArg.nCurrentRow = i; 10902 oArg.nCurrentRecord = j; 10903 } 10904 }, 10905 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1, 10906 argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords}, 10907 scope: this, 10908 timeout: (loopN > 0) ? 0 : -1 10909 }); 10910 this._oChainRender.add({ 10911 method: function(oArg) { 10912 var recIndex = oArg.recIndex; 10913 // Set FIRST/LAST 10914 if(recIndex === 0) { 10915 this._setFirstRow(); 10916 } 10917 if(oArg.isLast) { 10918 this._setLastRow(); 10919 } 10920 // Set EVEN/ODD 10921 this._setRowStripes(); 10922 10923 this.fireEvent("rowsAddEvent", {records:aRecords}); 10924 YAHOO.log("Added " + aRecords.length + 10925 " rows at index " + this._oRecordSet.getRecordIndex(aRecords[0]) + 10926 " with data " + lang.dump(aData), "info", this.toString()); 10927 }, 10928 argument: {recIndex: recIndex, isLast: isLast}, 10929 scope: this, 10930 timeout: -1 // Needs to run immediately after the DOM insertions above 10931 }); 10932 this._runRenderChain(); 10933 this.hideTableMessage(); 10934 return; 10935 } 10936 } 10937 } 10938 YAHOO.log("Could not add rows at index " + index + " with " + lang.dump(aData), "warn", this.toString()); 10939 }, 10940 10941 /** 10942 * For the given row, updates the associated Record with the given data. If the 10943 * row is on current page, the corresponding DOM elements are also updated. 10944 * 10945 * @method updateRow 10946 * @param row {YAHOO.widget.Record | Number | HTMLElement | String} 10947 * Which row to update: By Record instance, by Record's RecordSet 10948 * position index, by HTMLElement reference to the TR element, or by ID string 10949 * of the TR element. 10950 * @param oData {Object} Object literal of data for the row. 10951 */ 10952 updateRow : function(row, oData) { 10953 var index = row; 10954 if (!lang.isNumber(index)) { 10955 index = this.getRecordIndex(row); 10956 } 10957 10958 // Update the Record 10959 if(lang.isNumber(index) && (index >= 0)) { 10960 var oRecordSet = this._oRecordSet, 10961 oldRecord = oRecordSet.getRecord(index); 10962 10963 if(oldRecord) { 10964 var updatedRecord = this._oRecordSet.setRecord(oData, index), 10965 elRow = this.getTrEl(oldRecord), 10966 // Copy data from the Record for the event that gets fired later 10967 oldData = oldRecord ? oldRecord.getData() : null; 10968 10969 if(updatedRecord) { 10970 // Update selected rows as necessary 10971 var tracker = this._aSelections || [], 10972 i=0, 10973 oldId = oldRecord.getId(), 10974 newId = updatedRecord.getId(); 10975 for(; i<tracker.length; i++) { 10976 if((tracker[i] === oldId)) { 10977 tracker[i] = newId; 10978 } 10979 else if(tracker[i].recordId === oldId) { 10980 tracker[i].recordId = newId; 10981 } 10982 } 10983 10984 // Update anchors as necessary 10985 if(this._oAnchorRecord && this._oAnchorRecord.getId() === oldId) { 10986 this._oAnchorRecord = updatedRecord; 10987 } 10988 if(this._oAnchorCell && this._oAnchorCell.record.getId() === oldId) { 10989 this._oAnchorCell.record = updatedRecord; 10990 } 10991 10992 // Update the TR only if row is on current page 10993 this._oChainRender.add({ 10994 method: function() { 10995 if((this instanceof DT) && this._sId) { 10996 // Paginated 10997 var oPaginator = this.get('paginator'); 10998 if (oPaginator) { 10999 var pageStartIndex = (oPaginator.getPageRecords())[0], 11000 pageLastIndex = (oPaginator.getPageRecords())[1]; 11001 11002 // At least one of the new records affects the view 11003 if ((index >= pageStartIndex) || (index <= pageLastIndex)) { 11004 this.render(); 11005 } 11006 } 11007 else { 11008 if(elRow) { 11009 this._updateTrEl(elRow, updatedRecord); 11010 } 11011 else { 11012 this.getTbodyEl().appendChild(this._addTrEl(updatedRecord)); 11013 } 11014 } 11015 this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData}); 11016 YAHOO.log("DataTable row updated: Record ID = " + updatedRecord.getId() + 11017 ", Record index = " + this.getRecordIndex(updatedRecord) + 11018 ", page row index = " + this.getTrIndex(updatedRecord), "info", this.toString()); 11019 } 11020 }, 11021 scope: this, 11022 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1 11023 }); 11024 this._runRenderChain(); 11025 return; 11026 } 11027 } 11028 } 11029 YAHOO.log("Could not update row " + row + " with the data : " + lang.dump(oData), "warn", this.toString()); 11030 return; 11031 }, 11032 11033 /** 11034 * Starting with the given row, updates associated Records with the given data. 11035 * The number of rows to update are determined by the array of data provided. 11036 * Undefined data (i.e., not an object literal) causes a row to be skipped. If 11037 * any of the rows are on current page, the corresponding DOM elements are also 11038 * updated. 11039 * 11040 * @method updateRows 11041 * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String} 11042 * Starting row to update: By Record instance, by Record's RecordSet 11043 * position index, by HTMLElement reference to the TR element, or by ID string 11044 * of the TR element. 11045 * @param aData {Object[]} Array of object literal of data for the rows. 11046 */ 11047 updateRows : function(startrow, aData) { 11048 if(lang.isArray(aData)) { 11049 var startIndex = startrow, 11050 oRecordSet = this._oRecordSet, 11051 lastRowIndex = oRecordSet.getLength(); 11052 11053 if (!lang.isNumber(startrow)) { 11054 startIndex = this.getRecordIndex(startrow); 11055 } 11056 11057 if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) { 11058 var lastIndex = startIndex + aData.length, 11059 aOldRecords = oRecordSet.getRecords(startIndex, aData.length), 11060 aNewRecords = oRecordSet.setRecords(aData, startIndex); 11061 if(aNewRecords) { 11062 var tracker = this._aSelections || [], 11063 i=0, j, newRecord, newId, oldId, 11064 anchorRecord = this._oAnchorRecord ? this._oAnchorRecord.getId() : null, 11065 anchorCell = this._oAnchorCell ? this._oAnchorCell.record.getId() : null; 11066 for(; i<aOldRecords.length; i++) { 11067 oldId = aOldRecords[i].getId(); 11068 newRecord = aNewRecords[i]; 11069 newId = newRecord.getId(); 11070 11071 // Update selected rows as necessary 11072 for(j=0; j<tracker.length; j++) { 11073 if((tracker[j] === oldId)) { 11074 tracker[j] = newId; 11075 } 11076 else if(tracker[j].recordId === oldId) { 11077 tracker[j].recordId = newId; 11078 } 11079 } 11080 11081 // Update anchors as necessary 11082 if(anchorRecord && anchorRecord === oldId) { 11083 this._oAnchorRecord = newRecord; 11084 } 11085 if(anchorCell && anchorCell === oldId) { 11086 this._oAnchorCell.record = newRecord; 11087 } 11088 } 11089 11090 // Paginated 11091 var oPaginator = this.get('paginator'); 11092 if (oPaginator) { 11093 var pageStartIndex = (oPaginator.getPageRecords())[0], 11094 pageLastIndex = (oPaginator.getPageRecords())[1]; 11095 11096 // At least one of the new records affects the view 11097 if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) { 11098 this.render(); 11099 } 11100 11101 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords}); 11102 YAHOO.log("Added " + aNewRecords.length + 11103 " rows starting at index " + startIndex + 11104 " with data " + lang.dump(aData), "info", this.toString()); 11105 return; 11106 } 11107 // Not paginated 11108 else { 11109 // Update the TR elements 11110 var loopN = this.get("renderLoopSize"), 11111 rowCount = aData.length, // how many needed 11112 isLast = (lastIndex >= lastRowIndex), 11113 isAdding = (lastIndex > lastRowIndex); 11114 11115 this._oChainRender.add({ 11116 method: function(oArg) { 11117 if((this instanceof DT) && this._sId) { 11118 var aRecords = oArg.aRecords, 11119 i = oArg.nCurrentRow, 11120 j = oArg.nDataPointer, 11121 len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length; 11122 11123 for(; i < len; i++,j++) { 11124 if(isAdding && (i>=lastRowIndex)) { 11125 this._elTbody.appendChild(this._addTrEl(aRecords[j])); 11126 } 11127 else { 11128 this._updateTrEl(this._elTbody.rows[i], aRecords[j]); 11129 } 11130 } 11131 oArg.nCurrentRow = i; 11132 oArg.nDataPointer = j; 11133 } 11134 }, 11135 iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1, 11136 argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding}, 11137 scope: this, 11138 timeout: (loopN > 0) ? 0 : -1 11139 }); 11140 this._oChainRender.add({ 11141 method: function(oArg) { 11142 var recIndex = oArg.recIndex; 11143 // Set FIRST/LAST 11144 if(recIndex === 0) { 11145 this._setFirstRow(); 11146 } 11147 if(oArg.isLast) { 11148 this._setLastRow(); 11149 } 11150 // Set EVEN/ODD 11151 this._setRowStripes(); 11152 11153 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords}); 11154 YAHOO.log("Added " + aNewRecords.length + 11155 " rows starting at index " + startIndex + 11156 " with data " + lang.dump(aData), "info", this.toString()); 11157 }, 11158 argument: {recIndex: startIndex, isLast: isLast}, 11159 scope: this, 11160 timeout: -1 // Needs to run immediately after the DOM insertions above 11161 }); 11162 this._runRenderChain(); 11163 this.hideTableMessage(); 11164 return; 11165 } 11166 } 11167 } 11168 } 11169 YAHOO.log("Could not update rows at " + startrow + " with " + lang.dump(aData), "warn", this.toString()); 11170 }, 11171 11172 /** 11173 * Deletes the given row's Record from the RecordSet. If the row is on current page, 11174 * the corresponding DOM elements are also deleted. 11175 * 11176 * @method deleteRow 11177 * @param row {HTMLElement | String | Number} DOM element reference or ID string 11178 * to DataTable page element or RecordSet index. 11179 */ 11180 deleteRow : function(row) { 11181 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row); 11182 if(lang.isNumber(nRecordIndex)) { 11183 var oRecord = this.getRecord(nRecordIndex); 11184 if(oRecord) { 11185 var nTrIndex = this.getTrIndex(nRecordIndex); 11186 11187 // Remove from selection tracker if there 11188 var sRecordId = oRecord.getId(); 11189 var tracker = this._aSelections || []; 11190 for(var j=tracker.length-1; j>-1; j--) { 11191 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) || 11192 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) { 11193 tracker.splice(j,1); 11194 } 11195 } 11196 11197 // Delete Record from RecordSet 11198 var oData = this._oRecordSet.deleteRecord(nRecordIndex); 11199 11200 // Update the UI 11201 if(oData) { 11202 // If paginated and the deleted row was on this or a prior page, just 11203 // re-render 11204 var oPaginator = this.get('paginator'); 11205 if (oPaginator) { 11206 // Update the paginator's totalRecords 11207 var totalRecords = oPaginator.get('totalRecords'), 11208 // must capture before the totalRecords change because 11209 // Paginator shifts to previous page automatically 11210 rng = oPaginator.getPageRecords(); 11211 11212 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) { 11213 oPaginator.set('totalRecords',totalRecords - 1); 11214 } 11215 11216 // The deleted record was on this or a prior page, re-render 11217 if (!rng || nRecordIndex <= rng[1]) { 11218 this.render(); 11219 } 11220 11221 this._oChainRender.add({ 11222 method: function() { 11223 if((this instanceof DT) && this._sId) { 11224 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex}); 11225 YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString()); 11226 } 11227 }, 11228 scope: this, 11229 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1 11230 }); 11231 this._runRenderChain(); 11232 } 11233 // Not paginated 11234 else { 11235 if(lang.isNumber(nTrIndex)) { 11236 this._oChainRender.add({ 11237 method: function() { 11238 if((this instanceof DT) && this._sId) { 11239 var isLast = (nRecordIndex === this._oRecordSet.getLength());//(nTrIndex == this.getLastTrEl().sectionRowIndex); 11240 this._deleteTrEl(nTrIndex); 11241 11242 // Post-delete tasks 11243 if(this._elTbody.rows.length > 0) { 11244 // Set FIRST/LAST 11245 if(nTrIndex === 0) { 11246 this._setFirstRow(); 11247 } 11248 if(isLast) { 11249 this._setLastRow(); 11250 } 11251 // Set EVEN/ODD 11252 if(nTrIndex != this._elTbody.rows.length) { 11253 this._setRowStripes(nTrIndex); 11254 } 11255 } 11256 11257 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex}); 11258 YAHOO.log("Deleted row with data " + YAHOO.lang.dump(oData) + " at RecordSet index " + nRecordIndex + " and page row index " + nTrIndex, "info", this.toString()); 11259 } 11260 }, 11261 scope: this, 11262 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1 11263 }); 11264 this._runRenderChain(); 11265 return; 11266 } 11267 } 11268 } 11269 } 11270 } 11271 YAHOO.log("Could not delete row: " + row, "warn", this.toString()); 11272 return null; 11273 }, 11274 11275 /** 11276 * Convenience method to delete multiple rows. 11277 * 11278 * @method deleteRows 11279 * @param row {HTMLElement | String | Number} DOM element reference or ID string 11280 * to DataTable page element or RecordSet index. 11281 * @param count {Number} (optional) How many rows to delete. A negative value 11282 * will delete towards the beginning. 11283 */ 11284 deleteRows : function(row, count) { 11285 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row); 11286 if(lang.isNumber(nRecordIndex)) { 11287 var oRecord = this.getRecord(nRecordIndex); 11288 if(oRecord) { 11289 var nTrIndex = this.getTrIndex(nRecordIndex); 11290 11291 // Remove from selection tracker if there 11292 var sRecordId = oRecord.getId(); 11293 var tracker = this._aSelections || []; 11294 for(var j=tracker.length-1; j>-1; j--) { 11295 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) || 11296 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) { 11297 tracker.splice(j,1); 11298 } 11299 } 11300 11301 // Delete Record from RecordSet 11302 var highIndex = nRecordIndex; 11303 var lowIndex = nRecordIndex; 11304 11305 // Validate count and account for negative value 11306 if(count && lang.isNumber(count)) { 11307 highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex; 11308 lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1; 11309 count = (count > 0) ? count : count*-1; 11310 if(lowIndex < 0) { 11311 lowIndex = 0; 11312 count = highIndex - lowIndex + 1; 11313 } 11314 } 11315 else { 11316 count = 1; 11317 } 11318 11319 var aData = this._oRecordSet.deleteRecords(lowIndex, count); 11320 11321 // Update the UI 11322 if(aData) { 11323 var oPaginator = this.get('paginator'), 11324 loopN = this.get("renderLoopSize"); 11325 // If paginated and the deleted row was on this or a prior page, just 11326 // re-render 11327 if (oPaginator) { 11328 // Update the paginator's totalRecords 11329 var totalRecords = oPaginator.get('totalRecords'), 11330 // must capture before the totalRecords change because 11331 // Paginator shifts to previous page automatically 11332 rng = oPaginator.getPageRecords(); 11333 11334 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) { 11335 oPaginator.set('totalRecords',totalRecords - aData.length); 11336 } 11337 11338 // The records were on this or a prior page, re-render 11339 if (!rng || lowIndex <= rng[1]) { 11340 this.render(); 11341 } 11342 11343 this._oChainRender.add({ 11344 method: function(oArg) { 11345 if((this instanceof DT) && this._sId) { 11346 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count}); 11347 YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString()); 11348 } 11349 }, 11350 scope: this, 11351 timeout: (loopN > 0) ? 0 : -1 11352 }); 11353 this._runRenderChain(); 11354 return; 11355 } 11356 // Not paginated 11357 else { 11358 if(lang.isNumber(nTrIndex)) { 11359 // Delete the TR elements starting with highest index 11360 var loopEnd = lowIndex; 11361 var nRowsNeeded = count; // how many needed 11362 this._oChainRender.add({ 11363 method: function(oArg) { 11364 if((this instanceof DT) && this._sId) { 11365 var i = oArg.nCurrentRow, 11366 len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1; 11367 for(; i>len; --i) { 11368 this._deleteTrEl(i); 11369 } 11370 oArg.nCurrentRow = i; 11371 } 11372 }, 11373 iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1, 11374 argument: {nCurrentRow:highIndex}, 11375 scope: this, 11376 timeout: (loopN > 0) ? 0 : -1 11377 }); 11378 this._oChainRender.add({ 11379 method: function() { 11380 // Post-delete tasks 11381 if(this._elTbody.rows.length > 0) { 11382 this._setFirstRow(); 11383 this._setLastRow(); 11384 this._setRowStripes(); 11385 } 11386 11387 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count}); 11388 YAHOO.log("DataTable " + count + " rows deleted starting at index " + lowIndex, "info", this.toString()); 11389 }, 11390 scope: this, 11391 timeout: -1 // Needs to run immediately after the DOM deletions above 11392 }); 11393 this._runRenderChain(); 11394 return; 11395 } 11396 } 11397 } 11398 } 11399 } 11400 YAHOO.log("Could not delete " + count + " rows at row " + row, "warn", this.toString()); 11401 return null; 11402 }, 11403 11404 11405 11406 11407 11408 11409 11410 11411 11412 11413 11414 11415 11416 11417 11418 11419 11420 11421 11422 11423 11424 11425 11426 11427 11428 11429 11430 11431 11432 11433 11434 11435 11436 11437 11438 11439 11440 11441 11442 11443 11444 11445 11446 11447 11448 11449 // CELL FUNCTIONS 11450 11451 /** 11452 * Outputs markup into the given TD based on given Record. 11453 * 11454 * @method formatCell 11455 * @param elLiner {HTMLElement} The liner DIV element within the TD. 11456 * @param oRecord {YAHOO.widget.Record} (Optional) Record instance. 11457 * @param oColumn {YAHOO.widget.Column} (Optional) Column instance. 11458 */ 11459 formatCell : function(elLiner, oRecord, oColumn) { 11460 if(!oRecord) { 11461 oRecord = this.getRecord(elLiner); 11462 } 11463 if(!oColumn) { 11464 oColumn = this.getColumn(this.getCellIndex(elLiner.parentNode)); 11465 } 11466 11467 if(oRecord && oColumn) { 11468 var sField = oColumn.field; 11469 var oData = oRecord.getData(sField); 11470 11471 var fnFormatter = typeof oColumn.formatter === 'function' ? 11472 oColumn.formatter : 11473 DT.Formatter[oColumn.formatter+''] || 11474 DT.Formatter.defaultFormatter; 11475 11476 // Apply special formatter 11477 if(fnFormatter) { 11478 fnFormatter.call(this, elLiner, oRecord, oColumn, oData); 11479 } 11480 else { 11481 elLiner.innerHTML = oData; 11482 } 11483 11484 this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elLiner}); 11485 } 11486 else { 11487 YAHOO.log("Could not format cell " + elLiner, "error", this.toString()); 11488 } 11489 }, 11490 11491 /** 11492 * For the given row and column, updates the Record with the given data. If the 11493 * cell is on current page, the corresponding DOM elements are also updated. 11494 * 11495 * @method updateCell 11496 * @param oRecord {YAHOO.widget.Record} Record instance. 11497 * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index. 11498 * @param oData {Object} New data value for the cell. 11499 * @param skipRender {Boolean} Skips render step. Editors that update multiple 11500 * cells in ScrollingDataTable should render only on the last call to updateCell(). 11501 */ 11502 updateCell : function(oRecord, oColumn, oData, skipRender) { 11503 // Validate Column and Record 11504 oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn); 11505 if(oColumn && oColumn.getField() && (oRecord instanceof YAHOO.widget.Record)) { 11506 var sKey = oColumn.getField(), 11507 11508 // Copy data from the Record for the event that gets fired later 11509 //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData()); 11510 oldData = oRecord.getData(sKey); 11511 11512 // Update Record with new data 11513 this._oRecordSet.updateRecordValue(oRecord, sKey, oData); 11514 11515 // Update the TD only if row is on current page 11516 var elTd = this.getTdEl({record: oRecord, column: oColumn}); 11517 if(elTd) { 11518 this._oChainRender.add({ 11519 method: function() { 11520 if((this instanceof DT) && this._sId) { 11521 this.formatCell(elTd.firstChild, oRecord, oColumn); 11522 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData}); 11523 YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() + 11524 ", Record index = " + this.getRecordIndex(oRecord) + 11525 ", page row index = " + this.getTrIndex(oRecord) + 11526 ", Column key = " + oColumn.getKey(), "info", this.toString()); 11527 } 11528 }, 11529 scope: this, 11530 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1 11531 }); 11532 // Bug 2529024 11533 if(!skipRender) { 11534 this._runRenderChain(); 11535 } 11536 } 11537 else { 11538 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData}); 11539 YAHOO.log("DataTable cell updated: Record ID = " + oRecord.getId() + 11540 ", Record index = " + this.getRecordIndex(oRecord) + 11541 ", page row index = " + this.getTrIndex(oRecord) + 11542 ", Column key = " + oColumn.getKey(), "info", this.toString()); 11543 } 11544 } 11545 }, 11546 11547 11548 11549 11550 11551 11552 11553 11554 11555 11556 11557 11558 11559 11560 11561 11562 11563 11564 11565 11566 11567 11568 11569 11570 11571 11572 11573 11574 11575 11576 11577 11578 11579 11580 11581 11582 11583 11584 11585 11586 11587 11588 11589 11590 11591 11592 11593 11594 11595 11596 11597 // PAGINATION 11598 /** 11599 * Method executed during set() operation for the "paginator" attribute. 11600 * Adds and/or severs event listeners between DataTable and Paginator 11601 * 11602 * @method _updatePaginator 11603 * @param newPag {Paginator} Paginator instance (or null) for DataTable to use 11604 * @private 11605 */ 11606 _updatePaginator : function (newPag) { 11607 var oldPag = this.get('paginator'); 11608 if (oldPag && newPag !== oldPag) { 11609 oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true); 11610 } 11611 if (newPag) { 11612 newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true); 11613 } 11614 }, 11615 11616 /** 11617 * Update the UI infrastructure in response to a "paginator" attribute change. 11618 * 11619 * @method _handlePaginatorChange 11620 * @param e {Object} Change event object containing keys 'type','newValue', 11621 * and 'prevValue' 11622 * @private 11623 */ 11624 _handlePaginatorChange : function (e) { 11625 if (e.prevValue === e.newValue) { return; } 11626 11627 var newPag = e.newValue, 11628 oldPag = e.prevValue, 11629 containers = this._defaultPaginatorContainers(); 11630 11631 if (oldPag) { 11632 if (oldPag.getContainerNodes()[0] == containers[0]) { 11633 oldPag.set('containers',[]); 11634 } 11635 oldPag.destroy(); 11636 11637 // Convenience: share the default containers if possible. 11638 // Otherwise, remove the default containers from the DOM. 11639 if (containers[0]) { 11640 if (newPag && !newPag.getContainerNodes().length) { 11641 newPag.set('containers',containers); 11642 } else { 11643 // No new Paginator to use existing containers, OR new 11644 // Paginator has configured containers. 11645 for (var i = containers.length - 1; i >= 0; --i) { 11646 if (containers[i]) { 11647 containers[i].parentNode.removeChild(containers[i]); 11648 } 11649 } 11650 } 11651 } 11652 } 11653 11654 if (!this._bInit) { 11655 this.render(); 11656 11657 } 11658 11659 if (newPag) { 11660 this.renderPaginator(); 11661 } 11662 11663 }, 11664 11665 /** 11666 * Returns the default containers used for Paginators. If create param is 11667 * passed, the containers will be created and added to the DataTable container. 11668 * 11669 * @method _defaultPaginatorContainers 11670 * @param create {boolean} Create the default containers if not found 11671 * @private 11672 */ 11673 _defaultPaginatorContainers : function (create) { 11674 var above_id = this._sId + '-paginator0', 11675 below_id = this._sId + '-paginator1', 11676 above = Dom.get(above_id), 11677 below = Dom.get(below_id); 11678 11679 if (create && (!above || !below)) { 11680 // One above and one below the table 11681 if (!above) { 11682 above = document.createElement('div'); 11683 above.id = above_id; 11684 Dom.addClass(above, DT.CLASS_PAGINATOR); 11685 11686 this._elContainer.insertBefore(above,this._elContainer.firstChild); 11687 } 11688 11689 if (!below) { 11690 below = document.createElement('div'); 11691 below.id = below_id; 11692 Dom.addClass(below, DT.CLASS_PAGINATOR); 11693 11694 this._elContainer.appendChild(below); 11695 } 11696 } 11697 11698 return [above,below]; 11699 }, 11700 11701 /** 11702 * Calls Paginator's destroy() method 11703 * 11704 * @method _destroyPaginator 11705 * @private 11706 */ 11707 _destroyPaginator : function () { 11708 var oldPag = this.get('paginator'); 11709 if (oldPag) { 11710 oldPag.destroy(); 11711 } 11712 }, 11713 11714 /** 11715 * Renders the Paginator to the DataTable UI 11716 * 11717 * @method renderPaginator 11718 */ 11719 renderPaginator : function () { 11720 var pag = this.get("paginator"); 11721 if (!pag) { return; } 11722 11723 // Add the containers if the Paginator is not configured with containers 11724 if (!pag.getContainerNodes().length) { 11725 pag.set('containers',this._defaultPaginatorContainers(true)); 11726 } 11727 11728 pag.render(); 11729 }, 11730 11731 /** 11732 * Overridable method gives implementers a hook to show loading message before 11733 * changing Paginator value. 11734 * 11735 * @method doBeforePaginatorChange 11736 * @param oPaginatorState {Object} An object literal describing the proposed pagination state. 11737 * @return {Boolean} Return true to continue changing Paginator value. 11738 */ 11739 doBeforePaginatorChange : function(oPaginatorState) { 11740 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING); 11741 return true; 11742 }, 11743 11744 /** 11745 * Responds to new Pagination states. By default, updates the UI to reflect the 11746 * new state. If "dynamicData" is true, current selections are purged before 11747 * a request is sent to the DataSource for data for the new state (using the 11748 * request returned by "generateRequest()"). 11749 * 11750 * @method onPaginatorChangeRequest 11751 * @param oPaginatorState {Object} An object literal describing the proposed pagination state. 11752 */ 11753 onPaginatorChangeRequest : function (oPaginatorState) { 11754 var ok = this.doBeforePaginatorChange(oPaginatorState); 11755 if(ok) { 11756 // Server-side pagination 11757 if(this.get("dynamicData")) { 11758 // Get the current state 11759 var oState = this.getState(); 11760 11761 // Update pagination values 11762 oState.pagination = oPaginatorState; 11763 11764 // Get the request for the new state 11765 var request = this.get("generateRequest")(oState, this); 11766 11767 // Purge selections 11768 this.unselectAllRows(); 11769 this.unselectAllCells(); 11770 11771 // Get the new data from the server 11772 var callback = { 11773 success : this.onDataReturnSetRows, 11774 failure : this.onDataReturnSetRows, 11775 argument : oState, // Pass along the new state to the callback 11776 scope : this 11777 }; 11778 this._oDataSource.sendRequest(request, callback); 11779 } 11780 // Client-side pagination 11781 else { 11782 // Set the core pagination values silently (the second param) 11783 // to avoid looping back through the changeRequest mechanism 11784 oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true); 11785 oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true); 11786 11787 // Update the UI 11788 this.render(); 11789 } 11790 } 11791 else { 11792 YAHOO.log("Could not change Paginator value \"" + oPaginatorState + "\"", "warn", this.toString()); 11793 } 11794 }, 11795 11796 11797 11798 11799 11800 11801 11802 11803 11804 11805 11806 11807 11808 11809 11810 11811 11812 11813 11814 11815 11816 11817 11818 11819 11820 11821 11822 11823 11824 11825 11826 11827 11828 11829 11830 11831 11832 11833 11834 11835 11836 11837 11838 11839 11840 11841 11842 11843 11844 11845 // SELECTION/HIGHLIGHTING 11846 11847 /* 11848 * Reference to last highlighted cell element 11849 * 11850 * @property _elLastHighlightedTd 11851 * @type HTMLElement 11852 * @private 11853 */ 11854 _elLastHighlightedTd : null, 11855 11856 /* 11857 * ID string of last highlighted row element 11858 * 11859 * @property _sLastHighlightedTrElId 11860 * @type String 11861 * @private 11862 */ 11863 //_sLastHighlightedTrElId : null, 11864 11865 /** 11866 * Array to track row selections (by sRecordId) and/or cell selections 11867 * (by {recordId:sRecordId, columnKey:sColumnKey}) 11868 * 11869 * @property _aSelections 11870 * @type Object[] 11871 * @private 11872 */ 11873 _aSelections : null, 11874 11875 /** 11876 * Record instance of the row selection anchor. 11877 * 11878 * @property _oAnchorRecord 11879 * @type YAHOO.widget.Record 11880 * @private 11881 */ 11882 _oAnchorRecord : null, 11883 11884 /** 11885 * Object literal representing cell selection anchor: 11886 * {recordId:sRecordId, columnKey:sColumnKey}. 11887 * 11888 * @property _oAnchorCell 11889 * @type Object 11890 * @private 11891 */ 11892 _oAnchorCell : null, 11893 11894 /** 11895 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED 11896 * from all TR elements on the page. 11897 * 11898 * @method _unselectAllTrEls 11899 * @private 11900 */ 11901 _unselectAllTrEls : function() { 11902 var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody); 11903 Dom.removeClass(selectedRows, DT.CLASS_SELECTED); 11904 }, 11905 11906 /** 11907 * Returns object literal of values that represent the selection trigger. Used 11908 * to determine selection behavior resulting from a key event. 11909 * 11910 * @method _getSelectionTrigger 11911 * @private 11912 */ 11913 _getSelectionTrigger : function() { 11914 var sMode = this.get("selectionMode"); 11915 var oTrigger = {}; 11916 var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex; 11917 11918 // Cell mode 11919 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) { 11920 oTriggerCell = this.getLastSelectedCell(); 11921 // No selected cells found 11922 if(!oTriggerCell) { 11923 return null; 11924 } 11925 else { 11926 oTriggerRecord = this.getRecord(oTriggerCell.recordId); 11927 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord); 11928 elTriggerRow = this.getTrEl(oTriggerRecord); 11929 nTriggerTrIndex = this.getTrIndex(elTriggerRow); 11930 11931 // Selected cell not found on this page 11932 if(nTriggerTrIndex === null) { 11933 return null; 11934 } 11935 else { 11936 oTrigger.record = oTriggerRecord; 11937 oTrigger.recordIndex = nTriggerRecordIndex; 11938 oTrigger.el = this.getTdEl(oTriggerCell); 11939 oTrigger.trIndex = nTriggerTrIndex; 11940 oTrigger.column = this.getColumn(oTriggerCell.columnKey); 11941 oTrigger.colKeyIndex = oTrigger.column.getKeyIndex(); 11942 oTrigger.cell = oTriggerCell; 11943 return oTrigger; 11944 } 11945 } 11946 } 11947 // Row mode 11948 else { 11949 oTriggerRecord = this.getLastSelectedRecord(); 11950 // No selected rows found 11951 if(!oTriggerRecord) { 11952 return null; 11953 } 11954 else { 11955 // Selected row found, but is it on current page? 11956 oTriggerRecord = this.getRecord(oTriggerRecord); 11957 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord); 11958 elTriggerRow = this.getTrEl(oTriggerRecord); 11959 nTriggerTrIndex = this.getTrIndex(elTriggerRow); 11960 11961 // Selected row not found on this page 11962 if(nTriggerTrIndex === null) { 11963 return null; 11964 } 11965 else { 11966 oTrigger.record = oTriggerRecord; 11967 oTrigger.recordIndex = nTriggerRecordIndex; 11968 oTrigger.el = elTriggerRow; 11969 oTrigger.trIndex = nTriggerTrIndex; 11970 return oTrigger; 11971 } 11972 } 11973 } 11974 }, 11975 11976 /** 11977 * Returns object literal of values that represent the selection anchor. Used 11978 * to determine selection behavior resulting from a user event. 11979 * 11980 * @method _getSelectionAnchor 11981 * @param oTrigger {Object} (Optional) Object literal of selection trigger values 11982 * (for key events). 11983 * @private 11984 */ 11985 _getSelectionAnchor : function(oTrigger) { 11986 var sMode = this.get("selectionMode"); 11987 var oAnchor = {}; 11988 var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex; 11989 11990 // Cell mode 11991 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) { 11992 // Validate anchor cell 11993 var oAnchorCell = this._oAnchorCell; 11994 if(!oAnchorCell) { 11995 if(oTrigger) { 11996 oAnchorCell = this._oAnchorCell = oTrigger.cell; 11997 } 11998 else { 11999 return null; 12000 } 12001 } 12002 oAnchorRecord = this._oAnchorCell.record; 12003 nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord); 12004 nAnchorTrIndex = this.getTrIndex(oAnchorRecord); 12005 // If anchor cell is not on this page... 12006 if(nAnchorTrIndex === null) { 12007 // ...set TR index equal to top TR 12008 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) { 12009 nAnchorTrIndex = 0; 12010 } 12011 // ...set TR index equal to bottom TR 12012 else { 12013 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl()); 12014 } 12015 } 12016 12017 oAnchor.record = oAnchorRecord; 12018 oAnchor.recordIndex = nAnchorRecordIndex; 12019 oAnchor.trIndex = nAnchorTrIndex; 12020 oAnchor.column = this._oAnchorCell.column; 12021 oAnchor.colKeyIndex = oAnchor.column.getKeyIndex(); 12022 oAnchor.cell = oAnchorCell; 12023 return oAnchor; 12024 } 12025 // Row mode 12026 else { 12027 oAnchorRecord = this._oAnchorRecord; 12028 if(!oAnchorRecord) { 12029 if(oTrigger) { 12030 oAnchorRecord = this._oAnchorRecord = oTrigger.record; 12031 } 12032 else { 12033 return null; 12034 } 12035 } 12036 12037 nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord); 12038 nAnchorTrIndex = this.getTrIndex(oAnchorRecord); 12039 // If anchor row is not on this page... 12040 if(nAnchorTrIndex === null) { 12041 // ...set TR index equal to top TR 12042 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) { 12043 nAnchorTrIndex = 0; 12044 } 12045 // ...set TR index equal to bottom TR 12046 else { 12047 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl()); 12048 } 12049 } 12050 12051 oAnchor.record = oAnchorRecord; 12052 oAnchor.recordIndex = nAnchorRecordIndex; 12053 oAnchor.trIndex = nAnchorTrIndex; 12054 return oAnchor; 12055 } 12056 }, 12057 12058 /** 12059 * Determines selection behavior resulting from a mouse event when selection mode 12060 * is set to "standard". 12061 * 12062 * @method _handleStandardSelectionByMouse 12063 * @param oArgs.event {HTMLEvent} Event object. 12064 * @param oArgs.target {HTMLElement} Target element. 12065 * @private 12066 */ 12067 _handleStandardSelectionByMouse : function(oArgs) { 12068 var elTarget = oArgs.target; 12069 12070 // Validate target row 12071 var elTargetRow = this.getTrEl(elTarget); 12072 if(elTargetRow) { 12073 var e = oArgs.event; 12074 var bSHIFT = e.shiftKey; 12075 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey); 12076 12077 var oTargetRecord = this.getRecord(elTargetRow); 12078 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord); 12079 12080 var oAnchor = this._getSelectionAnchor(); 12081 12082 var i; 12083 12084 // Both SHIFT and CTRL 12085 if(bSHIFT && bCTRL) { 12086 // Validate anchor 12087 if(oAnchor) { 12088 if(this.isSelected(oAnchor.record)) { 12089 // Select all rows between anchor row and target row, including target row 12090 if(oAnchor.recordIndex < nTargetRecordIndex) { 12091 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) { 12092 if(!this.isSelected(i)) { 12093 this.selectRow(i); 12094 } 12095 } 12096 } 12097 // Select all rows between target row and anchor row, including target row 12098 else { 12099 for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) { 12100 if(!this.isSelected(i)) { 12101 this.selectRow(i); 12102 } 12103 } 12104 } 12105 } 12106 else { 12107 // Unselect all rows between anchor row and target row 12108 if(oAnchor.recordIndex < nTargetRecordIndex) { 12109 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) { 12110 if(this.isSelected(i)) { 12111 this.unselectRow(i); 12112 } 12113 } 12114 } 12115 // Unselect all rows between target row and anchor row 12116 else { 12117 for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) { 12118 if(this.isSelected(i)) { 12119 this.unselectRow(i); 12120 } 12121 } 12122 } 12123 // Select the target row 12124 this.selectRow(oTargetRecord); 12125 } 12126 } 12127 // Invalid anchor 12128 else { 12129 // Set anchor 12130 this._oAnchorRecord = oTargetRecord; 12131 12132 // Toggle selection of target 12133 if(this.isSelected(oTargetRecord)) { 12134 this.unselectRow(oTargetRecord); 12135 } 12136 else { 12137 this.selectRow(oTargetRecord); 12138 } 12139 } 12140 } 12141 // Only SHIFT 12142 else if(bSHIFT) { 12143 this.unselectAllRows(); 12144 12145 // Validate anchor 12146 if(oAnchor) { 12147 // Select all rows between anchor row and target row, 12148 // including the anchor row and target row 12149 if(oAnchor.recordIndex < nTargetRecordIndex) { 12150 for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) { 12151 this.selectRow(i); 12152 } 12153 } 12154 // Select all rows between target row and anchor row, 12155 // including the target row and anchor row 12156 else { 12157 for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) { 12158 this.selectRow(i); 12159 } 12160 } 12161 } 12162 // Invalid anchor 12163 else { 12164 // Set anchor 12165 this._oAnchorRecord = oTargetRecord; 12166 12167 // Select target row only 12168 this.selectRow(oTargetRecord); 12169 } 12170 } 12171 // Only CTRL 12172 else if(bCTRL) { 12173 // Set anchor 12174 this._oAnchorRecord = oTargetRecord; 12175 12176 // Toggle selection of target 12177 if(this.isSelected(oTargetRecord)) { 12178 this.unselectRow(oTargetRecord); 12179 } 12180 else { 12181 this.selectRow(oTargetRecord); 12182 } 12183 } 12184 // Neither SHIFT nor CTRL 12185 else { 12186 this._handleSingleSelectionByMouse(oArgs); 12187 return; 12188 } 12189 } 12190 }, 12191 12192 /** 12193 * Determines selection behavior resulting from a key event when selection mode 12194 * is set to "standard". 12195 * 12196 * @method _handleStandardSelectionByKey 12197 * @param e {HTMLEvent} Event object. 12198 * @private 12199 */ 12200 _handleStandardSelectionByKey : function(e) { 12201 var nKey = Ev.getCharCode(e); 12202 12203 if((nKey == 38) || (nKey == 40)) { 12204 var bSHIFT = e.shiftKey; 12205 12206 // Validate trigger 12207 var oTrigger = this._getSelectionTrigger(); 12208 // Arrow selection only works if last selected row is on current page 12209 if(!oTrigger) { 12210 return null; 12211 } 12212 12213 Ev.stopEvent(e); 12214 12215 // Validate anchor 12216 var oAnchor = this._getSelectionAnchor(oTrigger); 12217 12218 // Determine which direction we're going to 12219 if(bSHIFT) { 12220 // Selecting down away from anchor row 12221 if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) { 12222 this.selectRow(this.getNextTrEl(oTrigger.el)); 12223 } 12224 // Selecting up away from anchor row 12225 else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) { 12226 this.selectRow(this.getPreviousTrEl(oTrigger.el)); 12227 } 12228 // Unselect trigger 12229 else { 12230 this.unselectRow(oTrigger.el); 12231 } 12232 } 12233 else { 12234 this._handleSingleSelectionByKey(e); 12235 } 12236 } 12237 }, 12238 12239 /** 12240 * Determines selection behavior resulting from a mouse event when selection mode 12241 * is set to "single". 12242 * 12243 * @method _handleSingleSelectionByMouse 12244 * @param oArgs.event {HTMLEvent} Event object. 12245 * @param oArgs.target {HTMLElement} Target element. 12246 * @private 12247 */ 12248 _handleSingleSelectionByMouse : function(oArgs) { 12249 var elTarget = oArgs.target; 12250 12251 // Validate target row 12252 var elTargetRow = this.getTrEl(elTarget); 12253 if(elTargetRow) { 12254 var oTargetRecord = this.getRecord(elTargetRow); 12255 12256 // Set anchor 12257 this._oAnchorRecord = oTargetRecord; 12258 12259 // Select only target 12260 this.unselectAllRows(); 12261 this.selectRow(oTargetRecord); 12262 } 12263 }, 12264 12265 /** 12266 * Determines selection behavior resulting from a key event when selection mode 12267 * is set to "single". 12268 * 12269 * @method _handleSingleSelectionByKey 12270 * @param e {HTMLEvent} Event object. 12271 * @private 12272 */ 12273 _handleSingleSelectionByKey : function(e) { 12274 var nKey = Ev.getCharCode(e); 12275 12276 if((nKey == 38) || (nKey == 40)) { 12277 // Validate trigger 12278 var oTrigger = this._getSelectionTrigger(); 12279 // Arrow selection only works if last selected row is on current page 12280 if(!oTrigger) { 12281 return null; 12282 } 12283 12284 Ev.stopEvent(e); 12285 12286 // Determine the new row to select 12287 var elNew; 12288 if(nKey == 38) { // arrow up 12289 elNew = this.getPreviousTrEl(oTrigger.el); 12290 12291 // Validate new row 12292 if(elNew === null) { 12293 //TODO: wrap around to last tr on current page 12294 //elNew = this.getLastTrEl(); 12295 12296 //TODO: wrap back to last tr of previous page 12297 12298 // Top row selection is sticky 12299 elNew = this.getFirstTrEl(); 12300 } 12301 } 12302 else if(nKey == 40) { // arrow down 12303 elNew = this.getNextTrEl(oTrigger.el); 12304 12305 // Validate new row 12306 if(elNew === null) { 12307 //TODO: wrap around to first tr on current page 12308 //elNew = this.getFirstTrEl(); 12309 12310 //TODO: wrap forward to first tr of previous page 12311 12312 // Bottom row selection is sticky 12313 elNew = this.getLastTrEl(); 12314 } 12315 } 12316 12317 // Unselect all rows 12318 this.unselectAllRows(); 12319 12320 // Select the new row 12321 this.selectRow(elNew); 12322 12323 // Set new anchor 12324 this._oAnchorRecord = this.getRecord(elNew); 12325 } 12326 }, 12327 12328 /** 12329 * Determines selection behavior resulting from a mouse event when selection mode 12330 * is set to "cellblock". 12331 * 12332 * @method _handleCellBlockSelectionByMouse 12333 * @param oArgs.event {HTMLEvent} Event object. 12334 * @param oArgs.target {HTMLElement} Target element. 12335 * @private 12336 */ 12337 _handleCellBlockSelectionByMouse : function(oArgs) { 12338 var elTarget = oArgs.target; 12339 12340 // Validate target cell 12341 var elTargetCell = this.getTdEl(elTarget); 12342 if(elTargetCell) { 12343 var e = oArgs.event; 12344 var bSHIFT = e.shiftKey; 12345 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey); 12346 12347 var elTargetRow = this.getTrEl(elTargetCell); 12348 var nTargetTrIndex = this.getTrIndex(elTargetRow); 12349 var oTargetColumn = this.getColumn(elTargetCell); 12350 var nTargetColKeyIndex = oTargetColumn.getKeyIndex(); 12351 var oTargetRecord = this.getRecord(elTargetRow); 12352 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord); 12353 var oTargetCell = {record:oTargetRecord, column:oTargetColumn}; 12354 12355 var oAnchor = this._getSelectionAnchor(); 12356 12357 var allRows = this.getTbodyEl().rows; 12358 var startIndex, endIndex, currentRow, i, j; 12359 12360 // Both SHIFT and CTRL 12361 if(bSHIFT && bCTRL) { 12362 12363 // Validate anchor 12364 if(oAnchor) { 12365 // Anchor is selected 12366 if(this.isSelected(oAnchor.cell)) { 12367 // All cells are on the same row 12368 if(oAnchor.recordIndex === nTargetRecordIndex) { 12369 // Select all cells between anchor cell and target cell, including target cell 12370 if(oAnchor.colKeyIndex < nTargetColKeyIndex) { 12371 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) { 12372 this.selectCell(elTargetRow.cells[i]); 12373 } 12374 } 12375 // Select all cells between target cell and anchor cell, including target cell 12376 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) { 12377 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) { 12378 this.selectCell(elTargetRow.cells[i]); 12379 } 12380 } 12381 } 12382 // Anchor row is above target row 12383 else if(oAnchor.recordIndex < nTargetRecordIndex) { 12384 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex); 12385 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex); 12386 12387 // Select all cells from startIndex to endIndex on rows between anchor row and target row 12388 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) { 12389 for(j=startIndex; j<=endIndex; j++) { 12390 this.selectCell(allRows[i].cells[j]); 12391 } 12392 } 12393 } 12394 // Anchor row is below target row 12395 else { 12396 startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex); 12397 endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex); 12398 12399 // Select all cells from startIndex to endIndex on rows between target row and anchor row 12400 for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) { 12401 for(j=endIndex; j>=startIndex; j--) { 12402 this.selectCell(allRows[i].cells[j]); 12403 } 12404 } 12405 } 12406 } 12407 // Anchor cell is unselected 12408 else { 12409 // All cells are on the same row 12410 if(oAnchor.recordIndex === nTargetRecordIndex) { 12411 // Unselect all cells between anchor cell and target cell 12412 if(oAnchor.colKeyIndex < nTargetColKeyIndex) { 12413 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) { 12414 this.unselectCell(elTargetRow.cells[i]); 12415 } 12416 } 12417 // Select all cells between target cell and anchor cell 12418 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) { 12419 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) { 12420 this.unselectCell(elTargetRow.cells[i]); 12421 } 12422 } 12423 } 12424 // Anchor row is above target row 12425 if(oAnchor.recordIndex < nTargetRecordIndex) { 12426 // Unselect all cells from anchor cell to target cell 12427 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) { 12428 currentRow = allRows[i]; 12429 for(j=0; j<currentRow.cells.length; j++) { 12430 // This is the anchor row, only unselect cells after the anchor cell 12431 if(currentRow.sectionRowIndex === oAnchor.trIndex) { 12432 if(j>oAnchor.colKeyIndex) { 12433 this.unselectCell(currentRow.cells[j]); 12434 } 12435 } 12436 // This is the target row, only unelect cells before the target cell 12437 else if(currentRow.sectionRowIndex === nTargetTrIndex) { 12438 if(j<nTargetColKeyIndex) { 12439 this.unselectCell(currentRow.cells[j]); 12440 } 12441 } 12442 // Unselect all cells on this row 12443 else { 12444 this.unselectCell(currentRow.cells[j]); 12445 } 12446 } 12447 } 12448 } 12449 // Anchor row is below target row 12450 else { 12451 // Unselect all cells from target cell to anchor cell 12452 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) { 12453 currentRow = allRows[i]; 12454 for(j=0; j<currentRow.cells.length; j++) { 12455 // This is the target row, only unselect cells after the target cell 12456 if(currentRow.sectionRowIndex == nTargetTrIndex) { 12457 if(j>nTargetColKeyIndex) { 12458 this.unselectCell(currentRow.cells[j]); 12459 } 12460 } 12461 // This is the anchor row, only unselect cells before the anchor cell 12462 else if(currentRow.sectionRowIndex == oAnchor.trIndex) { 12463 if(j<oAnchor.colKeyIndex) { 12464 this.unselectCell(currentRow.cells[j]); 12465 } 12466 } 12467 // Unselect all cells on this row 12468 else { 12469 this.unselectCell(currentRow.cells[j]); 12470 } 12471 } 12472 } 12473 } 12474 12475 // Select the target cell 12476 this.selectCell(elTargetCell); 12477 } 12478 } 12479 // Invalid anchor 12480 else { 12481 // Set anchor 12482 this._oAnchorCell = oTargetCell; 12483 12484 // Toggle selection of target 12485 if(this.isSelected(oTargetCell)) { 12486 this.unselectCell(oTargetCell); 12487 } 12488 else { 12489 this.selectCell(oTargetCell); 12490 } 12491 } 12492 12493 } 12494 // Only SHIFT 12495 else if(bSHIFT) { 12496 this.unselectAllCells(); 12497 12498 // Validate anchor 12499 if(oAnchor) { 12500 // All cells are on the same row 12501 if(oAnchor.recordIndex === nTargetRecordIndex) { 12502 // Select all cells between anchor cell and target cell, 12503 // including the anchor cell and target cell 12504 if(oAnchor.colKeyIndex < nTargetColKeyIndex) { 12505 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) { 12506 this.selectCell(elTargetRow.cells[i]); 12507 } 12508 } 12509 // Select all cells between target cell and anchor cell 12510 // including the target cell and anchor cell 12511 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) { 12512 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) { 12513 this.selectCell(elTargetRow.cells[i]); 12514 } 12515 } 12516 } 12517 // Anchor row is above target row 12518 else if(oAnchor.recordIndex < nTargetRecordIndex) { 12519 // Select the cellblock from anchor cell to target cell 12520 // including the anchor cell and the target cell 12521 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex); 12522 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex); 12523 12524 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) { 12525 for(j=startIndex; j<=endIndex; j++) { 12526 this.selectCell(allRows[i].cells[j]); 12527 } 12528 } 12529 } 12530 // Anchor row is below target row 12531 else { 12532 // Select the cellblock from target cell to anchor cell 12533 // including the target cell and the anchor cell 12534 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex); 12535 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex); 12536 12537 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) { 12538 for(j=startIndex; j<=endIndex; j++) { 12539 this.selectCell(allRows[i].cells[j]); 12540 } 12541 } 12542 } 12543 } 12544 // Invalid anchor 12545 else { 12546 // Set anchor 12547 this._oAnchorCell = oTargetCell; 12548 12549 // Select target only 12550 this.selectCell(oTargetCell); 12551 } 12552 } 12553 // Only CTRL 12554 else if(bCTRL) { 12555 12556 // Set anchor 12557 this._oAnchorCell = oTargetCell; 12558 12559 // Toggle selection of target 12560 if(this.isSelected(oTargetCell)) { 12561 this.unselectCell(oTargetCell); 12562 } 12563 else { 12564 this.selectCell(oTargetCell); 12565 } 12566 12567 } 12568 // Neither SHIFT nor CTRL 12569 else { 12570 this._handleSingleCellSelectionByMouse(oArgs); 12571 } 12572 } 12573 }, 12574 12575 /** 12576 * Determines selection behavior resulting from a key event when selection mode 12577 * is set to "cellblock". 12578 * 12579 * @method _handleCellBlockSelectionByKey 12580 * @param e {HTMLEvent} Event object. 12581 * @private 12582 */ 12583 _handleCellBlockSelectionByKey : function(e) { 12584 var nKey = Ev.getCharCode(e); 12585 var bSHIFT = e.shiftKey; 12586 if((nKey == 9) || !bSHIFT) { 12587 this._handleSingleCellSelectionByKey(e); 12588 return; 12589 } 12590 12591 if((nKey > 36) && (nKey < 41)) { 12592 // Validate trigger 12593 var oTrigger = this._getSelectionTrigger(); 12594 // Arrow selection only works if last selected row is on current page 12595 if(!oTrigger) { 12596 return null; 12597 } 12598 12599 Ev.stopEvent(e); 12600 12601 // Validate anchor 12602 var oAnchor = this._getSelectionAnchor(oTrigger); 12603 12604 var i, startIndex, endIndex, elNew, elNewRow; 12605 var allRows = this.getTbodyEl().rows; 12606 var elThisRow = oTrigger.el.parentNode; 12607 12608 // Determine which direction we're going to 12609 12610 if(nKey == 40) { // arrow down 12611 // Selecting away from anchor cell 12612 if(oAnchor.recordIndex <= oTrigger.recordIndex) { 12613 // Select the horiz block on the next row... 12614 // ...making sure there is room below the trigger row 12615 elNewRow = this.getNextTrEl(oTrigger.el); 12616 if(elNewRow) { 12617 startIndex = oAnchor.colKeyIndex; 12618 endIndex = oTrigger.colKeyIndex; 12619 // ...going left 12620 if(startIndex > endIndex) { 12621 for(i=startIndex; i>=endIndex; i--) { 12622 elNew = elNewRow.cells[i]; 12623 this.selectCell(elNew); 12624 } 12625 } 12626 // ... going right 12627 else { 12628 for(i=startIndex; i<=endIndex; i++) { 12629 elNew = elNewRow.cells[i]; 12630 this.selectCell(elNew); 12631 } 12632 } 12633 } 12634 } 12635 // Unselecting towards anchor cell 12636 else { 12637 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex); 12638 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex); 12639 // Unselect the horiz block on this row towards the next row 12640 for(i=startIndex; i<=endIndex; i++) { 12641 this.unselectCell(elThisRow.cells[i]); 12642 } 12643 } 12644 } 12645 // Arrow up 12646 else if(nKey == 38) { 12647 // Selecting away from anchor cell 12648 if(oAnchor.recordIndex >= oTrigger.recordIndex) { 12649 // Select the horiz block on the previous row... 12650 // ...making sure there is room 12651 elNewRow = this.getPreviousTrEl(oTrigger.el); 12652 if(elNewRow) { 12653 // Select in order from anchor to trigger... 12654 startIndex = oAnchor.colKeyIndex; 12655 endIndex = oTrigger.colKeyIndex; 12656 // ...going left 12657 if(startIndex > endIndex) { 12658 for(i=startIndex; i>=endIndex; i--) { 12659 elNew = elNewRow.cells[i]; 12660 this.selectCell(elNew); 12661 } 12662 } 12663 // ... going right 12664 else { 12665 for(i=startIndex; i<=endIndex; i++) { 12666 elNew = elNewRow.cells[i]; 12667 this.selectCell(elNew); 12668 } 12669 } 12670 } 12671 } 12672 // Unselecting towards anchor cell 12673 else { 12674 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex); 12675 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex); 12676 // Unselect the horiz block on this row towards the previous row 12677 for(i=startIndex; i<=endIndex; i++) { 12678 this.unselectCell(elThisRow.cells[i]); 12679 } 12680 } 12681 } 12682 // Arrow right 12683 else if(nKey == 39) { 12684 // Selecting away from anchor cell 12685 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) { 12686 // Select the next vert block to the right... 12687 // ...making sure there is room 12688 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) { 12689 // Select in order from anchor to trigger... 12690 startIndex = oAnchor.trIndex; 12691 endIndex = oTrigger.trIndex; 12692 // ...going up 12693 if(startIndex > endIndex) { 12694 for(i=startIndex; i>=endIndex; i--) { 12695 elNew = allRows[i].cells[oTrigger.colKeyIndex+1]; 12696 this.selectCell(elNew); 12697 } 12698 } 12699 // ... going down 12700 else { 12701 for(i=startIndex; i<=endIndex; i++) { 12702 elNew = allRows[i].cells[oTrigger.colKeyIndex+1]; 12703 this.selectCell(elNew); 12704 } 12705 } 12706 } 12707 } 12708 // Unselecting towards anchor cell 12709 else { 12710 // Unselect the vert block on this column towards the right 12711 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex); 12712 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex); 12713 for(i=startIndex; i<=endIndex; i++) { 12714 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]); 12715 } 12716 } 12717 } 12718 // Arrow left 12719 else if(nKey == 37) { 12720 // Selecting away from anchor cell 12721 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) { 12722 //Select the previous vert block to the left 12723 if(oTrigger.colKeyIndex > 0) { 12724 // Select in order from anchor to trigger... 12725 startIndex = oAnchor.trIndex; 12726 endIndex = oTrigger.trIndex; 12727 // ...going up 12728 if(startIndex > endIndex) { 12729 for(i=startIndex; i>=endIndex; i--) { 12730 elNew = allRows[i].cells[oTrigger.colKeyIndex-1]; 12731 this.selectCell(elNew); 12732 } 12733 } 12734 // ... going down 12735 else { 12736 for(i=startIndex; i<=endIndex; i++) { 12737 elNew = allRows[i].cells[oTrigger.colKeyIndex-1]; 12738 this.selectCell(elNew); 12739 } 12740 } 12741 } 12742 } 12743 // Unselecting towards anchor cell 12744 else { 12745 // Unselect the vert block on this column towards the left 12746 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex); 12747 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex); 12748 for(i=startIndex; i<=endIndex; i++) { 12749 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]); 12750 } 12751 } 12752 } 12753 } 12754 }, 12755 12756 /** 12757 * Determines selection behavior resulting from a mouse event when selection mode 12758 * is set to "cellrange". 12759 * 12760 * @method _handleCellRangeSelectionByMouse 12761 * @param oArgs.event {HTMLEvent} Event object. 12762 * @param oArgs.target {HTMLElement} Target element. 12763 * @private 12764 */ 12765 _handleCellRangeSelectionByMouse : function(oArgs) { 12766 var elTarget = oArgs.target; 12767 12768 // Validate target cell 12769 var elTargetCell = this.getTdEl(elTarget); 12770 if(elTargetCell) { 12771 var e = oArgs.event; 12772 var bSHIFT = e.shiftKey; 12773 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey); 12774 12775 var elTargetRow = this.getTrEl(elTargetCell); 12776 var nTargetTrIndex = this.getTrIndex(elTargetRow); 12777 var oTargetColumn = this.getColumn(elTargetCell); 12778 var nTargetColKeyIndex = oTargetColumn.getKeyIndex(); 12779 var oTargetRecord = this.getRecord(elTargetRow); 12780 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord); 12781 var oTargetCell = {record:oTargetRecord, column:oTargetColumn}; 12782 12783 var oAnchor = this._getSelectionAnchor(); 12784 12785 var allRows = this.getTbodyEl().rows; 12786 var currentRow, i, j; 12787 12788 // Both SHIFT and CTRL 12789 if(bSHIFT && bCTRL) { 12790 12791 // Validate anchor 12792 if(oAnchor) { 12793 // Anchor is selected 12794 if(this.isSelected(oAnchor.cell)) { 12795 // All cells are on the same row 12796 if(oAnchor.recordIndex === nTargetRecordIndex) { 12797 // Select all cells between anchor cell and target cell, including target cell 12798 if(oAnchor.colKeyIndex < nTargetColKeyIndex) { 12799 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) { 12800 this.selectCell(elTargetRow.cells[i]); 12801 } 12802 } 12803 // Select all cells between target cell and anchor cell, including target cell 12804 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) { 12805 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) { 12806 this.selectCell(elTargetRow.cells[i]); 12807 } 12808 } 12809 } 12810 // Anchor row is above target row 12811 else if(oAnchor.recordIndex < nTargetRecordIndex) { 12812 // Select all cells on anchor row from anchor cell to the end of the row 12813 for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) { 12814 this.selectCell(elTargetRow.cells[i]); 12815 } 12816 12817 // Select all cells on all rows between anchor row and target row 12818 for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) { 12819 for(j=0; j<allRows[i].cells.length; j++){ 12820 this.selectCell(allRows[i].cells[j]); 12821 } 12822 } 12823 12824 // Select all cells on target row from first cell to the target cell 12825 for(i=0; i<=nTargetColKeyIndex; i++) { 12826 this.selectCell(elTargetRow.cells[i]); 12827 } 12828 } 12829 // Anchor row is below target row 12830 else { 12831 // Select all cells on target row from target cell to the end of the row 12832 for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) { 12833 this.selectCell(elTargetRow.cells[i]); 12834 } 12835 12836 // Select all cells on all rows between target row and anchor row 12837 for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) { 12838 for(j=0; j<allRows[i].cells.length; j++){ 12839 this.selectCell(allRows[i].cells[j]); 12840 } 12841 } 12842 12843 // Select all cells on anchor row from first cell to the anchor cell 12844 for(i=0; i<oAnchor.colKeyIndex; i++) { 12845 this.selectCell(elTargetRow.cells[i]); 12846 } 12847 } 12848 } 12849 // Anchor cell is unselected 12850 else { 12851 // All cells are on the same row 12852 if(oAnchor.recordIndex === nTargetRecordIndex) { 12853 // Unselect all cells between anchor cell and target cell 12854 if(oAnchor.colKeyIndex < nTargetColKeyIndex) { 12855 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) { 12856 this.unselectCell(elTargetRow.cells[i]); 12857 } 12858 } 12859 // Select all cells between target cell and anchor cell 12860 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) { 12861 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) { 12862 this.unselectCell(elTargetRow.cells[i]); 12863 } 12864 } 12865 } 12866 // Anchor row is above target row 12867 if(oAnchor.recordIndex < nTargetRecordIndex) { 12868 // Unselect all cells from anchor cell to target cell 12869 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) { 12870 currentRow = allRows[i]; 12871 for(j=0; j<currentRow.cells.length; j++) { 12872 // This is the anchor row, only unselect cells after the anchor cell 12873 if(currentRow.sectionRowIndex === oAnchor.trIndex) { 12874 if(j>oAnchor.colKeyIndex) { 12875 this.unselectCell(currentRow.cells[j]); 12876 } 12877 } 12878 // This is the target row, only unelect cells before the target cell 12879 else if(currentRow.sectionRowIndex === nTargetTrIndex) { 12880 if(j<nTargetColKeyIndex) { 12881 this.unselectCell(currentRow.cells[j]); 12882 } 12883 } 12884 // Unselect all cells on this row 12885 else { 12886 this.unselectCell(currentRow.cells[j]); 12887 } 12888 } 12889 } 12890 } 12891 // Anchor row is below target row 12892 else { 12893 // Unselect all cells from target cell to anchor cell 12894 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) { 12895 currentRow = allRows[i]; 12896 for(j=0; j<currentRow.cells.length; j++) { 12897 // This is the target row, only unselect cells after the target cell 12898 if(currentRow.sectionRowIndex == nTargetTrIndex) { 12899 if(j>nTargetColKeyIndex) { 12900 this.unselectCell(currentRow.cells[j]); 12901 } 12902 } 12903 // This is the anchor row, only unselect cells before the anchor cell 12904 else if(currentRow.sectionRowIndex == oAnchor.trIndex) { 12905 if(j<oAnchor.colKeyIndex) { 12906 this.unselectCell(currentRow.cells[j]); 12907 } 12908 } 12909 // Unselect all cells on this row 12910 else { 12911 this.unselectCell(currentRow.cells[j]); 12912 } 12913 } 12914 } 12915 } 12916 12917 // Select the target cell 12918 this.selectCell(elTargetCell); 12919 } 12920 } 12921 // Invalid anchor 12922 else { 12923 // Set anchor 12924 this._oAnchorCell = oTargetCell; 12925 12926 // Toggle selection of target 12927 if(this.isSelected(oTargetCell)) { 12928 this.unselectCell(oTargetCell); 12929 } 12930 else { 12931 this.selectCell(oTargetCell); 12932 } 12933 } 12934 } 12935 // Only SHIFT 12936 else if(bSHIFT) { 12937 12938 this.unselectAllCells(); 12939 12940 // Validate anchor 12941 if(oAnchor) { 12942 // All cells are on the same row 12943 if(oAnchor.recordIndex === nTargetRecordIndex) { 12944 // Select all cells between anchor cell and target cell, 12945 // including the anchor cell and target cell 12946 if(oAnchor.colKeyIndex < nTargetColKeyIndex) { 12947 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) { 12948 this.selectCell(elTargetRow.cells[i]); 12949 } 12950 } 12951 // Select all cells between target cell and anchor cell 12952 // including the target cell and anchor cell 12953 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) { 12954 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) { 12955 this.selectCell(elTargetRow.cells[i]); 12956 } 12957 } 12958 } 12959 // Anchor row is above target row 12960 else if(oAnchor.recordIndex < nTargetRecordIndex) { 12961 // Select all cells from anchor cell to target cell 12962 // including the anchor cell and target cell 12963 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) { 12964 currentRow = allRows[i]; 12965 for(j=0; j<currentRow.cells.length; j++) { 12966 // This is the anchor row, only select the anchor cell and after 12967 if(currentRow.sectionRowIndex == oAnchor.trIndex) { 12968 if(j>=oAnchor.colKeyIndex) { 12969 this.selectCell(currentRow.cells[j]); 12970 } 12971 } 12972 // This is the target row, only select the target cell and before 12973 else if(currentRow.sectionRowIndex == nTargetTrIndex) { 12974 if(j<=nTargetColKeyIndex) { 12975 this.selectCell(currentRow.cells[j]); 12976 } 12977 } 12978 // Select all cells on this row 12979 else { 12980 this.selectCell(currentRow.cells[j]); 12981 } 12982 } 12983 } 12984 } 12985 // Anchor row is below target row 12986 else { 12987 // Select all cells from target cell to anchor cell, 12988 // including the target cell and anchor cell 12989 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) { 12990 currentRow = allRows[i]; 12991 for(j=0; j<currentRow.cells.length; j++) { 12992 // This is the target row, only select the target cell and after 12993 if(currentRow.sectionRowIndex == nTargetTrIndex) { 12994 if(j>=nTargetColKeyIndex) { 12995 this.selectCell(currentRow.cells[j]); 12996 } 12997 } 12998 // This is the anchor row, only select the anchor cell and before 12999 else if(currentRow.sectionRowIndex == oAnchor.trIndex) { 13000 if(j<=oAnchor.colKeyIndex) { 13001 this.selectCell(currentRow.cells[j]); 13002 } 13003 } 13004 // Select all cells on this row 13005 else { 13006 this.selectCell(currentRow.cells[j]); 13007 } 13008 } 13009 } 13010 } 13011 } 13012 // Invalid anchor 13013 else { 13014 // Set anchor 13015 this._oAnchorCell = oTargetCell; 13016 13017 // Select target only 13018 this.selectCell(oTargetCell); 13019 } 13020 13021 13022 } 13023 // Only CTRL 13024 else if(bCTRL) { 13025 13026 // Set anchor 13027 this._oAnchorCell = oTargetCell; 13028 13029 // Toggle selection of target 13030 if(this.isSelected(oTargetCell)) { 13031 this.unselectCell(oTargetCell); 13032 } 13033 else { 13034 this.selectCell(oTargetCell); 13035 } 13036 13037 } 13038 // Neither SHIFT nor CTRL 13039 else { 13040 this._handleSingleCellSelectionByMouse(oArgs); 13041 } 13042 } 13043 }, 13044 13045 /** 13046 * Determines selection behavior resulting from a key event when selection mode 13047 * is set to "cellrange". 13048 * 13049 * @method _handleCellRangeSelectionByKey 13050 * @param e {HTMLEvent} Event object. 13051 * @private 13052 */ 13053 _handleCellRangeSelectionByKey : function(e) { 13054 var nKey = Ev.getCharCode(e); 13055 var bSHIFT = e.shiftKey; 13056 if((nKey == 9) || !bSHIFT) { 13057 this._handleSingleCellSelectionByKey(e); 13058 return; 13059 } 13060 13061 if((nKey > 36) && (nKey < 41)) { 13062 // Validate trigger 13063 var oTrigger = this._getSelectionTrigger(); 13064 // Arrow selection only works if last selected row is on current page 13065 if(!oTrigger) { 13066 return null; 13067 } 13068 13069 Ev.stopEvent(e); 13070 13071 // Validate anchor 13072 var oAnchor = this._getSelectionAnchor(oTrigger); 13073 13074 var i, elNewRow, elNew; 13075 var allRows = this.getTbodyEl().rows; 13076 var elThisRow = oTrigger.el.parentNode; 13077 13078 // Arrow down 13079 if(nKey == 40) { 13080 elNewRow = this.getNextTrEl(oTrigger.el); 13081 13082 // Selecting away from anchor cell 13083 if(oAnchor.recordIndex <= oTrigger.recordIndex) { 13084 // Select all cells to the end of this row 13085 for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){ 13086 elNew = elThisRow.cells[i]; 13087 this.selectCell(elNew); 13088 } 13089 13090 // Select some of the cells on the next row down 13091 if(elNewRow) { 13092 for(i=0; i<=oTrigger.colKeyIndex; i++){ 13093 elNew = elNewRow.cells[i]; 13094 this.selectCell(elNew); 13095 } 13096 } 13097 } 13098 // Unselecting towards anchor cell 13099 else { 13100 // Unselect all cells to the end of this row 13101 for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){ 13102 this.unselectCell(elThisRow.cells[i]); 13103 } 13104 13105 // Unselect some of the cells on the next row down 13106 if(elNewRow) { 13107 for(i=0; i<oTrigger.colKeyIndex; i++){ 13108 this.unselectCell(elNewRow.cells[i]); 13109 } 13110 } 13111 } 13112 } 13113 // Arrow up 13114 else if(nKey == 38) { 13115 elNewRow = this.getPreviousTrEl(oTrigger.el); 13116 13117 // Selecting away from anchor cell 13118 if(oAnchor.recordIndex >= oTrigger.recordIndex) { 13119 // Select all the cells to the beginning of this row 13120 for(i=oTrigger.colKeyIndex-1; i>-1; i--){ 13121 elNew = elThisRow.cells[i]; 13122 this.selectCell(elNew); 13123 } 13124 13125 // Select some of the cells from the end of the previous row 13126 if(elNewRow) { 13127 for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){ 13128 elNew = elNewRow.cells[i]; 13129 this.selectCell(elNew); 13130 } 13131 } 13132 } 13133 // Unselecting towards anchor cell 13134 else { 13135 // Unselect all the cells to the beginning of this row 13136 for(i=oTrigger.colKeyIndex; i>-1; i--){ 13137 this.unselectCell(elThisRow.cells[i]); 13138 } 13139 13140 // Unselect some of the cells from the end of the previous row 13141 if(elNewRow) { 13142 for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){ 13143 this.unselectCell(elNewRow.cells[i]); 13144 } 13145 } 13146 } 13147 } 13148 // Arrow right 13149 else if(nKey == 39) { 13150 elNewRow = this.getNextTrEl(oTrigger.el); 13151 13152 // Selecting away from anchor cell 13153 if(oAnchor.recordIndex < oTrigger.recordIndex) { 13154 // Select the next cell to the right 13155 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) { 13156 elNew = elThisRow.cells[oTrigger.colKeyIndex+1]; 13157 this.selectCell(elNew); 13158 } 13159 // Select the first cell of the next row 13160 else if(elNewRow) { 13161 elNew = elNewRow.cells[0]; 13162 this.selectCell(elNew); 13163 } 13164 } 13165 // Unselecting towards anchor cell 13166 else if(oAnchor.recordIndex > oTrigger.recordIndex) { 13167 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]); 13168 13169 // Unselect this cell towards the right 13170 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) { 13171 } 13172 // Unselect this cells towards the first cell of the next row 13173 else { 13174 } 13175 } 13176 // Anchor is on this row 13177 else { 13178 // Selecting away from anchor 13179 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) { 13180 // Select the next cell to the right 13181 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) { 13182 elNew = elThisRow.cells[oTrigger.colKeyIndex+1]; 13183 this.selectCell(elNew); 13184 } 13185 // Select the first cell on the next row 13186 else if(oTrigger.trIndex < allRows.length-1){ 13187 elNew = elNewRow.cells[0]; 13188 this.selectCell(elNew); 13189 } 13190 } 13191 // Unselecting towards anchor 13192 else { 13193 // Unselect this cell towards the right 13194 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]); 13195 } 13196 } 13197 } 13198 // Arrow left 13199 else if(nKey == 37) { 13200 elNewRow = this.getPreviousTrEl(oTrigger.el); 13201 13202 // Unselecting towards the anchor 13203 if(oAnchor.recordIndex < oTrigger.recordIndex) { 13204 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]); 13205 13206 // Unselect this cell towards the left 13207 if(oTrigger.colKeyIndex > 0) { 13208 } 13209 // Unselect this cell towards the last cell of the previous row 13210 else { 13211 } 13212 } 13213 // Selecting towards the anchor 13214 else if(oAnchor.recordIndex > oTrigger.recordIndex) { 13215 // Select the next cell to the left 13216 if(oTrigger.colKeyIndex > 0) { 13217 elNew = elThisRow.cells[oTrigger.colKeyIndex-1]; 13218 this.selectCell(elNew); 13219 } 13220 // Select the last cell of the previous row 13221 else if(oTrigger.trIndex > 0){ 13222 elNew = elNewRow.cells[elNewRow.cells.length-1]; 13223 this.selectCell(elNew); 13224 } 13225 } 13226 // Anchor is on this row 13227 else { 13228 // Selecting away from anchor cell 13229 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) { 13230 // Select the next cell to the left 13231 if(oTrigger.colKeyIndex > 0) { 13232 elNew = elThisRow.cells[oTrigger.colKeyIndex-1]; 13233 this.selectCell(elNew); 13234 } 13235 // Select the last cell of the previous row 13236 else if(oTrigger.trIndex > 0){ 13237 elNew = elNewRow.cells[elNewRow.cells.length-1]; 13238 this.selectCell(elNew); 13239 } 13240 } 13241 // Unselecting towards anchor cell 13242 else { 13243 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]); 13244 13245 // Unselect this cell towards the left 13246 if(oTrigger.colKeyIndex > 0) { 13247 } 13248 // Unselect this cell towards the last cell of the previous row 13249 else { 13250 } 13251 } 13252 } 13253 } 13254 } 13255 }, 13256 13257 /** 13258 * Determines selection behavior resulting from a mouse event when selection mode 13259 * is set to "singlecell". 13260 * 13261 * @method _handleSingleCellSelectionByMouse 13262 * @param oArgs.event {HTMLEvent} Event object. 13263 * @param oArgs.target {HTMLElement} Target element. 13264 * @private 13265 */ 13266 _handleSingleCellSelectionByMouse : function(oArgs) { 13267 var elTarget = oArgs.target; 13268 13269 // Validate target cell 13270 var elTargetCell = this.getTdEl(elTarget); 13271 if(elTargetCell) { 13272 var elTargetRow = this.getTrEl(elTargetCell); 13273 var oTargetRecord = this.getRecord(elTargetRow); 13274 var oTargetColumn = this.getColumn(elTargetCell); 13275 var oTargetCell = {record:oTargetRecord, column:oTargetColumn}; 13276 13277 // Set anchor 13278 this._oAnchorCell = oTargetCell; 13279 13280 // Select only target 13281 this.unselectAllCells(); 13282 this.selectCell(oTargetCell); 13283 } 13284 }, 13285 13286 /** 13287 * Determines selection behavior resulting from a key event when selection mode 13288 * is set to "singlecell". 13289 * 13290 * @method _handleSingleCellSelectionByKey 13291 * @param e {HTMLEvent} Event object. 13292 * @private 13293 */ 13294 _handleSingleCellSelectionByKey : function(e) { 13295 var nKey = Ev.getCharCode(e); 13296 if((nKey == 9) || ((nKey > 36) && (nKey < 41))) { 13297 var bSHIFT = e.shiftKey; 13298 13299 // Validate trigger 13300 var oTrigger = this._getSelectionTrigger(); 13301 // Arrow selection only works if last selected row is on current page 13302 if(!oTrigger) { 13303 return null; 13304 } 13305 13306 // Determine the new cell to select 13307 var elNew; 13308 if(nKey == 40) { // Arrow down 13309 elNew = this.getBelowTdEl(oTrigger.el); 13310 13311 // Validate new cell 13312 if(elNew === null) { 13313 //TODO: wrap around to first tr on current page 13314 13315 //TODO: wrap forward to first tr of next page 13316 13317 // Bottom selection is sticky 13318 elNew = oTrigger.el; 13319 } 13320 } 13321 else if(nKey == 38) { // Arrow up 13322 elNew = this.getAboveTdEl(oTrigger.el); 13323 13324 // Validate new cell 13325 if(elNew === null) { 13326 //TODO: wrap around to last tr on current page 13327 13328 //TODO: wrap back to last tr of previous page 13329 13330 // Top selection is sticky 13331 elNew = oTrigger.el; 13332 } 13333 } 13334 else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab 13335 elNew = this.getNextTdEl(oTrigger.el); 13336 13337 // Validate new cell 13338 if(elNew === null) { 13339 //TODO: wrap around to first td on current page 13340 13341 //TODO: wrap forward to first td of next page 13342 13343 // Top-left selection is sticky, and release TAB focus 13344 //elNew = oTrigger.el; 13345 return; 13346 } 13347 } 13348 else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab 13349 elNew = this.getPreviousTdEl(oTrigger.el); 13350 13351 // Validate new cell 13352 if(elNew === null) { 13353 //TODO: wrap around to last td on current page 13354 13355 //TODO: wrap back to last td of previous page 13356 13357 // Bottom-right selection is sticky, and release TAB focus 13358 //elNew = oTrigger.el; 13359 return; 13360 } 13361 } 13362 13363 Ev.stopEvent(e); 13364 13365 // Unselect all cells 13366 this.unselectAllCells(); 13367 13368 // Select the new cell 13369 this.selectCell(elNew); 13370 13371 // Set new anchor 13372 this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)}; 13373 } 13374 }, 13375 13376 /** 13377 * Returns array of selected TR elements on the page. 13378 * 13379 * @method getSelectedTrEls 13380 * @return {HTMLElement[]} Array of selected TR elements. 13381 */ 13382 getSelectedTrEls : function() { 13383 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody); 13384 }, 13385 13386 /** 13387 * Sets given row to the selected state. 13388 * 13389 * @method selectRow 13390 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element 13391 * reference or ID string, Record instance, or RecordSet position index. 13392 */ 13393 selectRow : function(row) { 13394 var oRecord, elRow; 13395 13396 if(row instanceof YAHOO.widget.Record) { 13397 oRecord = this._oRecordSet.getRecord(row); 13398 elRow = this.getTrEl(oRecord); 13399 } 13400 else if(lang.isNumber(row)) { 13401 oRecord = this.getRecord(row); 13402 elRow = this.getTrEl(oRecord); 13403 } 13404 else { 13405 elRow = this.getTrEl(row); 13406 oRecord = this.getRecord(elRow); 13407 } 13408 13409 if(oRecord) { 13410 // Update selection trackers 13411 var tracker = this._aSelections || []; 13412 var sRecordId = oRecord.getId(); 13413 var index = -1; 13414 13415 // Remove if already there: 13416 // Use Array.indexOf if available... 13417 /*if(tracker.indexOf && (tracker.indexOf(sRecordId) > -1)) { 13418 tracker.splice(tracker.indexOf(sRecordId),1); 13419 }*/ 13420 if(tracker.indexOf) { 13421 index = tracker.indexOf(sRecordId); 13422 13423 } 13424 // ...or do it the old-fashioned way 13425 else { 13426 for(var j=tracker.length-1; j>-1; j--) { 13427 if(tracker[j] === sRecordId){ 13428 index = j; 13429 break; 13430 } 13431 } 13432 } 13433 if(index > -1) { 13434 tracker.splice(index,1); 13435 } 13436 13437 // Add to the end 13438 tracker.push(sRecordId); 13439 this._aSelections = tracker; 13440 13441 // Update trackers 13442 if(!this._oAnchorRecord) { 13443 this._oAnchorRecord = oRecord; 13444 } 13445 13446 // Update UI 13447 if(elRow) { 13448 Dom.addClass(elRow, DT.CLASS_SELECTED); 13449 } 13450 13451 this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow}); 13452 YAHOO.log("Selected " + elRow, "info", this.toString()); 13453 } 13454 else { 13455 YAHOO.log("Could not select row " + row, "warn", this.toString()); 13456 } 13457 }, 13458 13459 /** 13460 * Sets given row to the unselected state. 13461 * 13462 * @method unselectRow 13463 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element 13464 * reference or ID string, Record instance, or RecordSet position index. 13465 */ 13466 unselectRow : function(row) { 13467 var elRow = this.getTrEl(row); 13468 13469 var oRecord; 13470 if(row instanceof YAHOO.widget.Record) { 13471 oRecord = this._oRecordSet.getRecord(row); 13472 } 13473 else if(lang.isNumber(row)) { 13474 oRecord = this.getRecord(row); 13475 } 13476 else { 13477 oRecord = this.getRecord(elRow); 13478 } 13479 13480 if(oRecord) { 13481 // Update selection trackers 13482 var tracker = this._aSelections || []; 13483 var sRecordId = oRecord.getId(); 13484 var index = -1; 13485 13486 // Use Array.indexOf if available... 13487 if(tracker.indexOf) { 13488 index = tracker.indexOf(sRecordId); 13489 } 13490 // ...or do it the old-fashioned way 13491 else { 13492 for(var j=tracker.length-1; j>-1; j--) { 13493 if(tracker[j] === sRecordId){ 13494 index = j; 13495 break; 13496 } 13497 } 13498 } 13499 if(index > -1) { 13500 // Update tracker 13501 tracker.splice(index,1); 13502 this._aSelections = tracker; 13503 13504 // Update the UI 13505 Dom.removeClass(elRow, DT.CLASS_SELECTED); 13506 13507 this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow}); 13508 YAHOO.log("Unselected " + elRow, "info", this.toString()); 13509 13510 return; 13511 } 13512 } 13513 YAHOO.log("Could not unselect row " + row, "warn", this.toString()); 13514 }, 13515 13516 /** 13517 * Clears out all row selections. 13518 * 13519 * @method unselectAllRows 13520 */ 13521 unselectAllRows : function() { 13522 // Remove all rows from tracker 13523 var tracker = this._aSelections || [], 13524 recId, 13525 removed = []; 13526 for(var j=tracker.length-1; j>-1; j--) { 13527 if(lang.isString(tracker[j])){ 13528 recId = tracker.splice(j,1); 13529 removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId); 13530 } 13531 } 13532 13533 // Update tracker 13534 this._aSelections = tracker; 13535 13536 // Update UI 13537 this._unselectAllTrEls(); 13538 13539 this.fireEvent("unselectAllRowsEvent", {records: removed}); 13540 YAHOO.log("Unselected all rows", "info", this.toString()); 13541 }, 13542 13543 /** 13544 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED 13545 * from all TD elements in the internal tracker. 13546 * 13547 * @method _unselectAllTdEls 13548 * @private 13549 */ 13550 _unselectAllTdEls : function() { 13551 var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody); 13552 Dom.removeClass(selectedCells, DT.CLASS_SELECTED); 13553 }, 13554 13555 /** 13556 * Returns array of selected TD elements on the page. 13557 * 13558 * @method getSelectedTdEls 13559 * @return {HTMLElement[]} Array of selected TD elements. 13560 */ 13561 getSelectedTdEls : function() { 13562 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody); 13563 }, 13564 13565 /** 13566 * Sets given cell to the selected state. 13567 * 13568 * @method selectCell 13569 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or 13570 * object literal of syntax {record:oRecord, column:oColumn}. 13571 */ 13572 selectCell : function(cell) { 13573 //TODO: accept {record} in selectCell() 13574 var elCell = this.getTdEl(cell); 13575 13576 if(elCell) { 13577 var oRecord = this.getRecord(elCell); 13578 var oColumn = this.getColumn(this.getCellIndex(elCell)); 13579 var sColumnKey = oColumn.getKey(); 13580 13581 if(oRecord && sColumnKey) { 13582 // Get Record ID 13583 var tracker = this._aSelections || []; 13584 var sRecordId = oRecord.getId(); 13585 13586 // Remove if there 13587 for(var j=tracker.length-1; j>-1; j--) { 13588 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){ 13589 tracker.splice(j,1); 13590 break; 13591 } 13592 } 13593 13594 // Add to the end 13595 tracker.push({recordId:sRecordId, columnKey:sColumnKey}); 13596 13597 // Update trackers 13598 this._aSelections = tracker; 13599 if(!this._oAnchorCell) { 13600 this._oAnchorCell = {record:oRecord, column:oColumn}; 13601 } 13602 13603 // Update the UI 13604 Dom.addClass(elCell, DT.CLASS_SELECTED); 13605 13606 this.fireEvent("cellSelectEvent", {record:oRecord, column:oColumn, key: sColumnKey, el:elCell}); 13607 YAHOO.log("Selected " + elCell, "info", this.toString()); 13608 return; 13609 } 13610 } 13611 YAHOO.log("Could not select cell " + cell, "warn", this.toString()); 13612 }, 13613 13614 /** 13615 * Sets given cell to the unselected state. 13616 * 13617 * @method unselectCell 13618 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or 13619 * object literal of syntax {record:oRecord, column:oColumn}. 13620 * @param cell {HTMLElement | String} DOM element reference or ID string 13621 * to DataTable page element or RecordSet index. 13622 */ 13623 unselectCell : function(cell) { 13624 var elCell = this.getTdEl(cell); 13625 13626 if(elCell) { 13627 var oRecord = this.getRecord(elCell); 13628 var oColumn = this.getColumn(this.getCellIndex(elCell)); 13629 var sColumnKey = oColumn.getKey(); 13630 13631 if(oRecord && sColumnKey) { 13632 // Get Record ID 13633 var tracker = this._aSelections || []; 13634 var id = oRecord.getId(); 13635 13636 // Is it selected? 13637 for(var j=tracker.length-1; j>-1; j--) { 13638 if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){ 13639 // Remove from tracker 13640 tracker.splice(j,1); 13641 13642 // Update tracker 13643 this._aSelections = tracker; 13644 13645 // Update the UI 13646 Dom.removeClass(elCell, DT.CLASS_SELECTED); 13647 13648 this.fireEvent("cellUnselectEvent", {record:oRecord, column: oColumn, key:sColumnKey, el:elCell}); 13649 YAHOO.log("Unselected " + elCell, "info", this.toString()); 13650 return; 13651 } 13652 } 13653 } 13654 } 13655 YAHOO.log("Could not unselect cell " + cell, "warn", this.toString()); 13656 }, 13657 13658 /** 13659 * Clears out all cell selections. 13660 * 13661 * @method unselectAllCells 13662 */ 13663 unselectAllCells : function() { 13664 // Remove all cells from tracker 13665 var tracker = this._aSelections || []; 13666 for(var j=tracker.length-1; j>-1; j--) { 13667 if(lang.isObject(tracker[j])){ 13668 tracker.splice(j,1); 13669 } 13670 } 13671 13672 // Update tracker 13673 this._aSelections = tracker; 13674 13675 // Update UI 13676 this._unselectAllTdEls(); 13677 13678 //TODO: send data to unselectAllCellsEvent handler 13679 this.fireEvent("unselectAllCellsEvent"); 13680 YAHOO.log("Unselected all cells", "info", this.toString()); 13681 }, 13682 13683 /** 13684 * Returns true if given item is selected, false otherwise. 13685 * 13686 * @method isSelected 13687 * @param o {String | HTMLElement | YAHOO.widget.Record | Number 13688 * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by 13689 * reference or ID string, a Record instance, a RecordSet position index, 13690 * or an object literal representation 13691 * of a cell. 13692 * @return {Boolean} True if item is selected. 13693 */ 13694 isSelected : function(o) { 13695 if(o && (o.ownerDocument == document)) { 13696 return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED)); 13697 } 13698 else { 13699 var oRecord, sRecordId, j; 13700 var tracker = this._aSelections; 13701 if(tracker && tracker.length > 0) { 13702 // Looking for a Record? 13703 if(o instanceof YAHOO.widget.Record) { 13704 oRecord = o; 13705 } 13706 else if(lang.isNumber(o)) { 13707 oRecord = this.getRecord(o); 13708 } 13709 if(oRecord) { 13710 sRecordId = oRecord.getId(); 13711 13712 // Is it there? 13713 // Use Array.indexOf if available... 13714 if(tracker.indexOf) { 13715 if(tracker.indexOf(sRecordId) > -1) { 13716 return true; 13717 } 13718 } 13719 // ...or do it the old-fashioned way 13720 else { 13721 for(j=tracker.length-1; j>-1; j--) { 13722 if(tracker[j] === sRecordId){ 13723 return true; 13724 } 13725 } 13726 } 13727 } 13728 // Looking for a cell 13729 else if(o.record && o.column){ 13730 sRecordId = o.record.getId(); 13731 var sColumnKey = o.column.getKey(); 13732 13733 for(j=tracker.length-1; j>-1; j--) { 13734 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){ 13735 return true; 13736 } 13737 } 13738 } 13739 } 13740 } 13741 return false; 13742 }, 13743 13744 /** 13745 * Returns selected rows as an array of Record IDs. 13746 * 13747 * @method getSelectedRows 13748 * @return {String[]} Array of selected rows by Record ID. 13749 */ 13750 getSelectedRows : function() { 13751 var aSelectedRows = []; 13752 var tracker = this._aSelections || []; 13753 for(var j=0; j<tracker.length; j++) { 13754 if(lang.isString(tracker[j])){ 13755 aSelectedRows.push(tracker[j]); 13756 } 13757 } 13758 return aSelectedRows; 13759 }, 13760 13761 /** 13762 * Returns selected cells as an array of object literals: 13763 * {recordId:sRecordId, columnKey:sColumnKey}. 13764 * 13765 * @method getSelectedCells 13766 * @return {Object[]} Array of selected cells by Record ID and Column ID. 13767 */ 13768 getSelectedCells : function() { 13769 var aSelectedCells = []; 13770 var tracker = this._aSelections || []; 13771 for(var j=0; j<tracker.length; j++) { 13772 if(tracker[j] && lang.isObject(tracker[j])){ 13773 aSelectedCells.push(tracker[j]); 13774 } 13775 } 13776 return aSelectedCells; 13777 }, 13778 13779 /** 13780 * Returns last selected Record ID. 13781 * 13782 * @method getLastSelectedRecord 13783 * @return {String} Record ID of last selected row. 13784 */ 13785 getLastSelectedRecord : function() { 13786 var tracker = this._aSelections; 13787 if(tracker && tracker.length > 0) { 13788 for(var i=tracker.length-1; i>-1; i--) { 13789 if(lang.isString(tracker[i])){ 13790 return tracker[i]; 13791 } 13792 } 13793 } 13794 }, 13795 13796 /** 13797 * Returns last selected cell as an object literal: 13798 * {recordId:sRecordId, columnKey:sColumnKey}. 13799 * 13800 * @method getLastSelectedCell 13801 * @return {Object} Object literal representation of a cell. 13802 */ 13803 getLastSelectedCell : function() { 13804 var tracker = this._aSelections; 13805 if(tracker && tracker.length > 0) { 13806 for(var i=tracker.length-1; i>-1; i--) { 13807 if(tracker[i].recordId && tracker[i].columnKey){ 13808 return tracker[i]; 13809 } 13810 } 13811 } 13812 }, 13813 13814 /** 13815 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row. 13816 * 13817 * @method highlightRow 13818 * @param row {HTMLElement | String} DOM element reference or ID string. 13819 */ 13820 highlightRow : function(row) { 13821 var elRow = this.getTrEl(row); 13822 13823 if(elRow) { 13824 // Make sure previous row is unhighlighted 13825 /* if(this._sLastHighlightedTrElId) { 13826 Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED); 13827 }*/ 13828 var oRecord = this.getRecord(elRow); 13829 Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED); 13830 //this._sLastHighlightedTrElId = elRow.id; 13831 this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow}); 13832 YAHOO.log("Highlighted " + elRow, "info", this.toString()); 13833 return; 13834 } 13835 YAHOO.log("Could not highlight row " + row, "warn", this.toString()); 13836 }, 13837 13838 /** 13839 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row. 13840 * 13841 * @method unhighlightRow 13842 * @param row {HTMLElement | String} DOM element reference or ID string. 13843 */ 13844 unhighlightRow : function(row) { 13845 var elRow = this.getTrEl(row); 13846 13847 if(elRow) { 13848 var oRecord = this.getRecord(elRow); 13849 Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED); 13850 this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow}); 13851 YAHOO.log("Unhighlighted " + elRow, "info", this.toString()); 13852 return; 13853 } 13854 YAHOO.log("Could not unhighlight row " + row, "warn", this.toString()); 13855 }, 13856 13857 /** 13858 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell. 13859 * 13860 * @method highlightCell 13861 * @param cell {HTMLElement | String} DOM element reference or ID string. 13862 */ 13863 highlightCell : function(cell) { 13864 var elCell = this.getTdEl(cell); 13865 13866 if(elCell) { 13867 // Make sure previous cell is unhighlighted 13868 if(this._elLastHighlightedTd) { 13869 this.unhighlightCell(this._elLastHighlightedTd); 13870 } 13871 13872 var oRecord = this.getRecord(elCell); 13873 var oColumn = this.getColumn(this.getCellIndex(elCell)); 13874 var sColumnKey = oColumn.getKey(); 13875 Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED); 13876 this._elLastHighlightedTd = elCell; 13877 this.fireEvent("cellHighlightEvent", {record:oRecord, column:oColumn, key:sColumnKey, el:elCell}); 13878 YAHOO.log("Highlighted " + elCell, "info", this.toString()); 13879 return; 13880 } 13881 YAHOO.log("Could not highlight cell " + cell, "warn", this.toString()); 13882 }, 13883 13884 /** 13885 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell. 13886 * 13887 * @method unhighlightCell 13888 * @param cell {HTMLElement | String} DOM element reference or ID string. 13889 */ 13890 unhighlightCell : function(cell) { 13891 var elCell = this.getTdEl(cell); 13892 13893 if(elCell) { 13894 var oRecord = this.getRecord(elCell); 13895 Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED); 13896 this._elLastHighlightedTd = null; 13897 this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(this.getCellIndex(elCell)), key:this.getColumn(this.getCellIndex(elCell)).getKey(), el:elCell}); 13898 YAHOO.log("Unhighlighted " + elCell, "info", this.toString()); 13899 return; 13900 } 13901 YAHOO.log("Could not unhighlight cell " + cell, "warn", this.toString()); 13902 }, 13903 13904 13905 13906 13907 13908 13909 13910 13911 13912 13913 13914 13915 13916 13917 13918 13919 13920 13921 13922 13923 13924 13925 13926 13927 13928 13929 13930 13931 13932 13933 13934 13935 13936 13937 13938 13939 13940 13941 13942 13943 13944 13945 13946 13947 13948 // INLINE EDITING 13949 13950 /** 13951 * Assigns CellEditor instance to existing Column. 13952 * @method addCellEditor 13953 * @param oColumn {YAHOO.widget.Column} Column instance. 13954 * @param oEditor {YAHOO.wdiget.CellEditor} CellEditor instance. 13955 */ 13956 addCellEditor : function(oColumn, oEditor) { 13957 oColumn.editor = oEditor; 13958 oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true); 13959 oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true); 13960 oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true); 13961 oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true); 13962 oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true); 13963 oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true); 13964 oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true); 13965 oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true); 13966 }, 13967 13968 /** 13969 * Returns current CellEditor instance, or null. 13970 * @method getCellEditor 13971 * @return {YAHOO.widget.CellEditor} CellEditor instance. 13972 */ 13973 getCellEditor : function() { 13974 return this._oCellEditor; 13975 }, 13976 13977 13978 /** 13979 * Activates and shows CellEditor instance for the given cell while deactivating and 13980 * canceling previous CellEditor. It is baked into DataTable that only one CellEditor 13981 * can be active at any given time. 13982 * 13983 * @method showCellEditor 13984 * @param elCell {HTMLElement | String} Cell to edit. 13985 */ 13986 showCellEditor : function(elCell, oRecord, oColumn) { 13987 // Get a particular CellEditor 13988 elCell = this.getTdEl(elCell); 13989 if(elCell) { 13990 oColumn = this.getColumn(elCell); 13991 if(oColumn && oColumn.editor) { 13992 var oCellEditor = this._oCellEditor; 13993 // Clean up active CellEditor 13994 if(oCellEditor) { 13995 if(this._oCellEditor.cancel) { 13996 this._oCellEditor.cancel(); 13997 } 13998 else if(oCellEditor.isActive) { 13999 this.cancelCellEditor(); 14000 } 14001 } 14002 14003 if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) { 14004 // Get CellEditor 14005 oCellEditor = oColumn.editor; 14006 var ok = oCellEditor.attach(this, elCell); 14007 if(ok) { 14008 oCellEditor.render(); 14009 oCellEditor.move(); 14010 ok = this.doBeforeShowCellEditor(oCellEditor); 14011 if(ok) { 14012 oCellEditor.show(); 14013 this._oCellEditor = oCellEditor; 14014 } 14015 } 14016 } 14017 // Backward compatibility 14018 else { 14019 if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) { 14020 oRecord = this.getRecord(elCell); 14021 } 14022 if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) { 14023 oColumn = this.getColumn(elCell); 14024 } 14025 if(oRecord && oColumn) { 14026 if(!this._oCellEditor || this._oCellEditor.container) { 14027 this._initCellEditorEl(); 14028 } 14029 14030 // Update Editor values 14031 oCellEditor = this._oCellEditor; 14032 oCellEditor.cell = elCell; 14033 oCellEditor.record = oRecord; 14034 oCellEditor.column = oColumn; 14035 oCellEditor.validator = (oColumn.editorOptions && 14036 lang.isFunction(oColumn.editorOptions.validator)) ? 14037 oColumn.editorOptions.validator : null; 14038 oCellEditor.value = oRecord.getData(oColumn.key); 14039 oCellEditor.defaultValue = null; 14040 14041 // Move Editor 14042 var elContainer = oCellEditor.container; 14043 var x = Dom.getX(elCell); 14044 var y = Dom.getY(elCell); 14045 14046 // SF doesn't get xy for cells in scrolling table 14047 // when tbody display is set to block 14048 if(isNaN(x) || isNaN(y)) { 14049 x = elCell.offsetLeft + // cell pos relative to table 14050 Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document 14051 this._elTbody.scrollLeft; // minus tbody scroll 14052 y = elCell.offsetTop + // cell pos relative to table 14053 Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document 14054 this._elTbody.scrollTop + // minus tbody scroll 14055 this._elThead.offsetHeight; // account for fixed THEAD cells 14056 } 14057 14058 elContainer.style.left = x + "px"; 14059 elContainer.style.top = y + "px"; 14060 14061 // Hook to customize the UI 14062 this.doBeforeShowCellEditor(this._oCellEditor); 14063 14064 //TODO: This is temporarily up here due so elements can be focused 14065 // Show Editor 14066 elContainer.style.display = ""; 14067 14068 // Handle ESC key 14069 Ev.addListener(elContainer, "keydown", function(e, oSelf) { 14070 // ESC hides Cell Editor 14071 if((e.keyCode == 27)) { 14072 oSelf.cancelCellEditor(); 14073 oSelf.focusTbodyEl(); 14074 } 14075 else { 14076 oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e}); 14077 } 14078 }, this); 14079 14080 // Render Editor markup 14081 var fnEditor; 14082 if(lang.isString(oColumn.editor)) { 14083 switch(oColumn.editor) { 14084 case "checkbox": 14085 fnEditor = DT.editCheckbox; 14086 break; 14087 case "date": 14088 fnEditor = DT.editDate; 14089 break; 14090 case "dropdown": 14091 fnEditor = DT.editDropdown; 14092 break; 14093 case "radio": 14094 fnEditor = DT.editRadio; 14095 break; 14096 case "textarea": 14097 fnEditor = DT.editTextarea; 14098 break; 14099 case "textbox": 14100 fnEditor = DT.editTextbox; 14101 break; 14102 default: 14103 fnEditor = null; 14104 } 14105 } 14106 else if(lang.isFunction(oColumn.editor)) { 14107 fnEditor = oColumn.editor; 14108 } 14109 14110 if(fnEditor) { 14111 // Create DOM input elements 14112 fnEditor(this._oCellEditor, this); 14113 14114 // Show Save/Cancel buttons 14115 if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) { 14116 this.showCellEditorBtns(elContainer); 14117 } 14118 14119 oCellEditor.isActive = true; 14120 14121 //TODO: verify which args to pass 14122 this.fireEvent("editorShowEvent", {editor:oCellEditor}); 14123 YAHOO.log("Cell Editor shown for " + elCell, "info", this.toString()); 14124 return; 14125 } 14126 } 14127 14128 14129 14130 14131 } 14132 } 14133 } 14134 }, 14135 14136 /** 14137 * Backward compatibility. 14138 * 14139 * @method _initCellEditorEl 14140 * @private 14141 * @deprecated Use BaseCellEditor class. 14142 */ 14143 _initCellEditorEl : function() { 14144 // Attach Cell Editor container element as first child of body 14145 var elCellEditor = document.createElement("div"); 14146 elCellEditor.id = this._sId + "-celleditor"; 14147 elCellEditor.style.display = "none"; 14148 elCellEditor.tabIndex = 0; 14149 Dom.addClass(elCellEditor, DT.CLASS_EDITOR); 14150 var elFirstChild = Dom.getFirstChild(document.body); 14151 if(elFirstChild) { 14152 elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild); 14153 } 14154 else { 14155 elCellEditor = document.body.appendChild(elCellEditor); 14156 } 14157 14158 // Internal tracker of Cell Editor values 14159 var oCellEditor = {}; 14160 oCellEditor.container = elCellEditor; 14161 oCellEditor.value = null; 14162 oCellEditor.isActive = false; 14163 this._oCellEditor = oCellEditor; 14164 }, 14165 14166 /** 14167 * Overridable abstract method to customize CellEditor before showing. 14168 * 14169 * @method doBeforeShowCellEditor 14170 * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance. 14171 * @return {Boolean} Return true to continue showing CellEditor. 14172 */ 14173 doBeforeShowCellEditor : function(oCellEditor) { 14174 return true; 14175 }, 14176 14177 /** 14178 * Saves active CellEditor input to Record and upates DOM UI. 14179 * 14180 * @method saveCellEditor 14181 */ 14182 saveCellEditor : function() { 14183 if(this._oCellEditor) { 14184 if(this._oCellEditor.save) { 14185 this._oCellEditor.save(); 14186 } 14187 // Backward compatibility 14188 else if(this._oCellEditor.isActive) { 14189 var newData = this._oCellEditor.value; 14190 // Copy the data to pass to the event 14191 //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key)); 14192 var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key); 14193 14194 // Validate input data 14195 if(this._oCellEditor.validator) { 14196 newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor); 14197 if(newData === null ) { 14198 this.resetCellEditor(); 14199 this.fireEvent("editorRevertEvent", 14200 {editor:this._oCellEditor, oldData:oldData, newData:newData}); 14201 YAHOO.log("Could not save Cell Editor input due to invalid data " + 14202 lang.dump(newData), "warn", this.toString()); 14203 return; 14204 } 14205 } 14206 // Update the Record 14207 this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value); 14208 // Update the UI 14209 this.formatCell(this._oCellEditor.cell.firstChild, this._oCellEditor.record, this._oCellEditor.column); 14210 14211 // Bug fix 1764044 14212 this._oChainRender.add({ 14213 method: function() { 14214 this.validateColumnWidths(); 14215 }, 14216 scope: this 14217 }); 14218 this._oChainRender.run(); 14219 // Clear out the Cell Editor 14220 this.resetCellEditor(); 14221 14222 this.fireEvent("editorSaveEvent", 14223 {editor:this._oCellEditor, oldData:oldData, newData:newData}); 14224 YAHOO.log("Cell Editor input saved", "info", this.toString()); 14225 } 14226 } 14227 }, 14228 14229 /** 14230 * Cancels active CellEditor. 14231 * 14232 * @method cancelCellEditor 14233 */ 14234 cancelCellEditor : function() { 14235 if(this._oCellEditor) { 14236 if(this._oCellEditor.cancel) { 14237 this._oCellEditor.cancel(); 14238 } 14239 // Backward compatibility 14240 else if(this._oCellEditor.isActive) { 14241 this.resetCellEditor(); 14242 //TODO: preserve values for the event? 14243 this.fireEvent("editorCancelEvent", {editor:this._oCellEditor}); 14244 YAHOO.log("Cell Editor input canceled", "info", this.toString()); 14245 } 14246 14247 YAHOO.log("CellEditor input canceled", "info", this.toString()); 14248 } 14249 }, 14250 14251 /** 14252 * Destroys active CellEditor instance and UI. 14253 * 14254 * @method destroyCellEditor 14255 */ 14256 destroyCellEditor : function() { 14257 if(this._oCellEditor) { 14258 this._oCellEditor.destroy(); 14259 this._oCellEditor = null; 14260 } 14261 }, 14262 14263 /** 14264 * Passes through showEvent of the active CellEditor. 14265 * 14266 * @method _onEditorShowEvent 14267 * @param oArgs {Object} Custom Event args. 14268 * @private 14269 */ 14270 _onEditorShowEvent : function(oArgs) { 14271 this.fireEvent("editorShowEvent", oArgs); 14272 }, 14273 14274 /** 14275 * Passes through keydownEvent of the active CellEditor. 14276 * @param oArgs {Object} Custom Event args. 14277 * 14278 * @method _onEditorKeydownEvent 14279 * @private 14280 */ 14281 _onEditorKeydownEvent : function(oArgs) { 14282 this.fireEvent("editorKeydownEvent", oArgs); 14283 }, 14284 14285 /** 14286 * Passes through revertEvent of the active CellEditor. 14287 * 14288 * @method _onEditorRevertEvent 14289 * @param oArgs {Object} Custom Event args. 14290 * @private 14291 */ 14292 _onEditorRevertEvent : function(oArgs) { 14293 this.fireEvent("editorRevertEvent", oArgs); 14294 }, 14295 14296 /** 14297 * Passes through saveEvent of the active CellEditor. 14298 * 14299 * @method _onEditorSaveEvent 14300 * @param oArgs {Object} Custom Event args. 14301 * @private 14302 */ 14303 _onEditorSaveEvent : function(oArgs) { 14304 this.fireEvent("editorSaveEvent", oArgs); 14305 }, 14306 14307 /** 14308 * Passes through cancelEvent of the active CellEditor. 14309 * 14310 * @method _onEditorCancelEvent 14311 * @param oArgs {Object} Custom Event args. 14312 * @private 14313 */ 14314 _onEditorCancelEvent : function(oArgs) { 14315 this.fireEvent("editorCancelEvent", oArgs); 14316 }, 14317 14318 /** 14319 * Passes through blurEvent of the active CellEditor. 14320 * 14321 * @method _onEditorBlurEvent 14322 * @param oArgs {Object} Custom Event args. 14323 * @private 14324 */ 14325 _onEditorBlurEvent : function(oArgs) { 14326 this.fireEvent("editorBlurEvent", oArgs); 14327 }, 14328 14329 /** 14330 * Passes through blockEvent of the active CellEditor. 14331 * 14332 * @method _onEditorBlockEvent 14333 * @param oArgs {Object} Custom Event args. 14334 * @private 14335 */ 14336 _onEditorBlockEvent : function(oArgs) { 14337 this.fireEvent("editorBlockEvent", oArgs); 14338 }, 14339 14340 /** 14341 * Passes through unblockEvent of the active CellEditor. 14342 * 14343 * @method _onEditorUnblockEvent 14344 * @param oArgs {Object} Custom Event args. 14345 * @private 14346 */ 14347 _onEditorUnblockEvent : function(oArgs) { 14348 this.fireEvent("editorUnblockEvent", oArgs); 14349 }, 14350 14351 /** 14352 * Public handler of the editorBlurEvent. By default, saves on blur if 14353 * disableBtns is true, otherwise cancels on blur. 14354 * 14355 * @method onEditorBlurEvent 14356 * @param oArgs {Object} Custom Event args. 14357 */ 14358 onEditorBlurEvent : function(oArgs) { 14359 if(oArgs.editor.disableBtns) { 14360 // Save on blur 14361 if(oArgs.editor.save) { // Backward incompatible 14362 oArgs.editor.save(); 14363 } 14364 } 14365 else if(oArgs.editor.cancel) { // Backward incompatible 14366 // Cancel on blur 14367 oArgs.editor.cancel(); 14368 } 14369 }, 14370 14371 /** 14372 * Public handler of the editorBlockEvent. By default, disables DataTable UI. 14373 * 14374 * @method onEditorBlockEvent 14375 * @param oArgs {Object} Custom Event args. 14376 */ 14377 onEditorBlockEvent : function(oArgs) { 14378 this.disable(); 14379 }, 14380 14381 /** 14382 * Public handler of the editorUnblockEvent. By default, undisables DataTable UI. 14383 * 14384 * @method onEditorUnblockEvent 14385 * @param oArgs {Object} Custom Event args. 14386 */ 14387 onEditorUnblockEvent : function(oArgs) { 14388 this.undisable(); 14389 }, 14390 14391 14392 14393 14394 14395 14396 14397 14398 14399 14400 14401 14402 14403 14404 14405 14406 14407 14408 14409 14410 14411 14412 14413 14414 14415 14416 14417 14418 14419 14420 14421 14422 14423 14424 14425 14426 14427 14428 // ABSTRACT METHODS 14429 14430 /** 14431 * Overridable method gives implementers a hook to access data before 14432 * it gets added to RecordSet and rendered to the TBODY. 14433 * 14434 * @method doBeforeLoadData 14435 * @param sRequest {String} Original request. 14436 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14437 * @param oPayload {MIXED} additional arguments 14438 * @return {Boolean} Return true to continue loading data into RecordSet and 14439 * updating DataTable with new Records, false to cancel. 14440 */ 14441 doBeforeLoadData : function(sRequest, oResponse, oPayload) { 14442 return true; 14443 }, 14444 14445 14446 14447 14448 14449 14450 14451 14452 14453 14454 14455 14456 14457 14458 14459 14460 14461 14462 14463 14464 14465 14466 14467 14468 14469 14470 14471 14472 14473 14474 14475 14476 14477 14478 14479 14480 14481 14482 14483 14484 14485 14486 14487 14488 14489 14490 14491 14492 14493 14494 14495 14496 14497 14498 14499 14500 14501 14502 14503 14504 14505 14506 14507 ///////////////////////////////////////////////////////////////////////////// 14508 // 14509 // Public Custom Event Handlers 14510 // 14511 ///////////////////////////////////////////////////////////////////////////// 14512 14513 /** 14514 * Custom event handler to sort Column. 14515 * 14516 * @method onEventSortColumn 14517 * @param oArgs.event {HTMLEvent} Event object. 14518 * @param oArgs.target {HTMLElement} Target element. 14519 */ 14520 onEventSortColumn : function(oArgs) { 14521 //TODO: support form elements in sortable columns 14522 var evt = oArgs.event; 14523 var target = oArgs.target; 14524 14525 var el = this.getThEl(target) || this.getTdEl(target); 14526 if(el) { 14527 var oColumn = this.getColumn(el); 14528 if(oColumn.sortable) { 14529 Ev.stopEvent(evt); 14530 this.sortColumn(oColumn); 14531 } 14532 } 14533 else { 14534 YAHOO.log("Could not find Column for " + target, "warn", this.toString()); 14535 } 14536 }, 14537 14538 /** 14539 * Custom event handler to select Column. 14540 * 14541 * @method onEventSelectColumn 14542 * @param oArgs.event {HTMLEvent} Event object. 14543 * @param oArgs.target {HTMLElement} Target element. 14544 */ 14545 onEventSelectColumn : function(oArgs) { 14546 this.selectColumn(oArgs.target); 14547 }, 14548 14549 /** 14550 * Custom event handler to highlight Column. Accounts for spurious 14551 * caused-by-child events. 14552 * 14553 * @method onEventHighlightColumn 14554 * @param oArgs.event {HTMLEvent} Event object. 14555 * @param oArgs.target {HTMLElement} Target element. 14556 */ 14557 onEventHighlightColumn : function(oArgs) { 14558 this.highlightColumn(oArgs.target); 14559 }, 14560 14561 /** 14562 * Custom event handler to unhighlight Column. Accounts for spurious 14563 * caused-by-child events. 14564 * 14565 * @method onEventUnhighlightColumn 14566 * @param oArgs.event {HTMLEvent} Event object. 14567 * @param oArgs.target {HTMLElement} Target element. 14568 */ 14569 onEventUnhighlightColumn : function(oArgs) { 14570 this.unhighlightColumn(oArgs.target); 14571 }, 14572 14573 /** 14574 * Custom event handler to manage selection according to desktop paradigm. 14575 * 14576 * @method onEventSelectRow 14577 * @param oArgs.event {HTMLEvent} Event object. 14578 * @param oArgs.target {HTMLElement} Target element. 14579 */ 14580 onEventSelectRow : function(oArgs) { 14581 var sMode = this.get("selectionMode"); 14582 if(sMode == "single") { 14583 this._handleSingleSelectionByMouse(oArgs); 14584 } 14585 else { 14586 this._handleStandardSelectionByMouse(oArgs); 14587 } 14588 }, 14589 14590 /** 14591 * Custom event handler to select cell. 14592 * 14593 * @method onEventSelectCell 14594 * @param oArgs.event {HTMLEvent} Event object. 14595 * @param oArgs.target {HTMLElement} Target element. 14596 */ 14597 onEventSelectCell : function(oArgs) { 14598 var sMode = this.get("selectionMode"); 14599 if(sMode == "cellblock") { 14600 this._handleCellBlockSelectionByMouse(oArgs); 14601 } 14602 else if(sMode == "cellrange") { 14603 this._handleCellRangeSelectionByMouse(oArgs); 14604 } 14605 else { 14606 this._handleSingleCellSelectionByMouse(oArgs); 14607 } 14608 }, 14609 14610 /** 14611 * Custom event handler to highlight row. Accounts for spurious 14612 * caused-by-child events. 14613 * 14614 * @method onEventHighlightRow 14615 * @param oArgs.event {HTMLEvent} Event object. 14616 * @param oArgs.target {HTMLElement} Target element. 14617 */ 14618 onEventHighlightRow : function(oArgs) { 14619 this.highlightRow(oArgs.target); 14620 }, 14621 14622 /** 14623 * Custom event handler to unhighlight row. Accounts for spurious 14624 * caused-by-child events. 14625 * 14626 * @method onEventUnhighlightRow 14627 * @param oArgs.event {HTMLEvent} Event object. 14628 * @param oArgs.target {HTMLElement} Target element. 14629 */ 14630 onEventUnhighlightRow : function(oArgs) { 14631 this.unhighlightRow(oArgs.target); 14632 }, 14633 14634 /** 14635 * Custom event handler to highlight cell. Accounts for spurious 14636 * caused-by-child events. 14637 * 14638 * @method onEventHighlightCell 14639 * @param oArgs.event {HTMLEvent} Event object. 14640 * @param oArgs.target {HTMLElement} Target element. 14641 */ 14642 onEventHighlightCell : function(oArgs) { 14643 this.highlightCell(oArgs.target); 14644 }, 14645 14646 /** 14647 * Custom event handler to unhighlight cell. Accounts for spurious 14648 * caused-by-child events. 14649 * 14650 * @method onEventUnhighlightCell 14651 * @param oArgs.event {HTMLEvent} Event object. 14652 * @param oArgs.target {HTMLElement} Target element. 14653 */ 14654 onEventUnhighlightCell : function(oArgs) { 14655 this.unhighlightCell(oArgs.target); 14656 }, 14657 14658 /** 14659 * Custom event handler to format cell. 14660 * 14661 * @method onEventFormatCell 14662 * @param oArgs.event {HTMLEvent} Event object. 14663 * @param oArgs.target {HTMLElement} Target element. 14664 */ 14665 onEventFormatCell : function(oArgs) { 14666 var target = oArgs.target; 14667 14668 var elCell = this.getTdEl(target); 14669 if(elCell) { 14670 var oColumn = this.getColumn(this.getCellIndex(elCell)); 14671 this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn); 14672 } 14673 else { 14674 YAHOO.log("Could not format cell " + target, "warn", this.toString()); 14675 } 14676 }, 14677 14678 /** 14679 * Custom event handler to edit cell. 14680 * 14681 * @method onEventShowCellEditor 14682 * @param oArgs.event {HTMLEvent} Event object. 14683 * @param oArgs.target {HTMLElement} Target element. 14684 */ 14685 onEventShowCellEditor : function(oArgs) { 14686 if(!this.isDisabled()) { 14687 this.showCellEditor(oArgs.target); 14688 } 14689 }, 14690 14691 /** 14692 * Custom event handler to save active CellEditor input. 14693 * 14694 * @method onEventSaveCellEditor 14695 */ 14696 onEventSaveCellEditor : function(oArgs) { 14697 if(this._oCellEditor) { 14698 if(this._oCellEditor.save) { 14699 this._oCellEditor.save(); 14700 } 14701 // Backward compatibility 14702 else { 14703 this.saveCellEditor(); 14704 } 14705 } 14706 }, 14707 14708 /** 14709 * Custom event handler to cancel active CellEditor. 14710 * 14711 * @method onEventCancelCellEditor 14712 */ 14713 onEventCancelCellEditor : function(oArgs) { 14714 if(this._oCellEditor) { 14715 if(this._oCellEditor.cancel) { 14716 this._oCellEditor.cancel(); 14717 } 14718 // Backward compatibility 14719 else { 14720 this.cancelCellEditor(); 14721 } 14722 } 14723 }, 14724 14725 /** 14726 * Callback function receives data from DataSource and populates an entire 14727 * DataTable with Records and TR elements, clearing previous Records, if any. 14728 * 14729 * @method onDataReturnInitializeTable 14730 * @param sRequest {String} Original request. 14731 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14732 * @param oPayload {MIXED} (optional) Additional argument(s) 14733 */ 14734 onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) { 14735 if((this instanceof DT) && this._sId) { 14736 this.initializeTable(); 14737 14738 this.onDataReturnSetRows(sRequest,oResponse,oPayload); 14739 } 14740 }, 14741 14742 /** 14743 * Callback function receives reponse from DataSource, replaces all existing 14744 * Records in RecordSet, updates TR elements with new data, and updates state 14745 * UI for pagination and sorting from payload data, if necessary. 14746 * 14747 * @method onDataReturnReplaceRows 14748 * @param oRequest {MIXED} Original generated request. 14749 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14750 * @param oPayload {MIXED} (optional) Additional argument(s) 14751 */ 14752 onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) { 14753 if((this instanceof DT) && this._sId) { 14754 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload}); 14755 14756 // Pass data through abstract method for any transformations 14757 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload), 14758 pag = this.get('paginator'), 14759 index = 0; 14760 14761 // Data ok to set 14762 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) { 14763 // Update Records 14764 this._oRecordSet.reset(); 14765 14766 if (this.get('dynamicData')) { 14767 if (oPayload && oPayload.pagination && 14768 lang.isNumber(oPayload.pagination.recordOffset)) { 14769 index = oPayload.pagination.recordOffset; 14770 } else if (pag) { 14771 index = pag.getStartIndex(); 14772 } 14773 } 14774 14775 this._oRecordSet.setRecords(oResponse.results, index | 0); 14776 14777 // Update state 14778 this._handleDataReturnPayload(oRequest, oResponse, oPayload); 14779 14780 // Update UI 14781 this.render(); 14782 } 14783 // Error 14784 else if(ok && oResponse.error) { 14785 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR); 14786 } 14787 } 14788 }, 14789 14790 /** 14791 * Callback function receives data from DataSource and appends to an existing 14792 * DataTable new Records and, if applicable, creates or updates 14793 * corresponding TR elements. 14794 * 14795 * @method onDataReturnAppendRows 14796 * @param sRequest {String} Original request. 14797 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14798 * @param oPayload {MIXED} (optional) Additional argument(s) 14799 */ 14800 onDataReturnAppendRows : function(sRequest, oResponse, oPayload) { 14801 if((this instanceof DT) && this._sId) { 14802 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload}); 14803 14804 // Pass data through abstract method for any transformations 14805 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload); 14806 14807 // Data ok to append 14808 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) { 14809 // Append rows 14810 this.addRows(oResponse.results); 14811 14812 // Update state 14813 this._handleDataReturnPayload(sRequest, oResponse, oPayload); 14814 } 14815 // Error 14816 else if(ok && oResponse.error) { 14817 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR); 14818 } 14819 } 14820 }, 14821 14822 /** 14823 * Callback function receives data from DataSource and inserts new records 14824 * starting at the index specified in oPayload.insertIndex. The value for 14825 * oPayload.insertIndex can be populated when sending the request to the DataSource, 14826 * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime. 14827 * If applicable, creates or updates corresponding TR elements. 14828 * 14829 * @method onDataReturnInsertRows 14830 * @param sRequest {String} Original request. 14831 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14832 * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex. 14833 */ 14834 onDataReturnInsertRows : function(sRequest, oResponse, oPayload) { 14835 if((this instanceof DT) && this._sId) { 14836 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload}); 14837 14838 // Pass data through abstract method for any transformations 14839 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload); 14840 14841 // Data ok to append 14842 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) { 14843 // Insert rows 14844 this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0)); 14845 14846 // Update state 14847 this._handleDataReturnPayload(sRequest, oResponse, oPayload); 14848 } 14849 // Error 14850 else if(ok && oResponse.error) { 14851 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR); 14852 } 14853 } 14854 }, 14855 14856 /** 14857 * Callback function receives data from DataSource and incrementally updates Records 14858 * starting at the index specified in oPayload.updateIndex. The value for 14859 * oPayload.updateIndex can be populated when sending the request to the DataSource, 14860 * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime. 14861 * If applicable, creates or updates corresponding TR elements. 14862 * 14863 * @method onDataReturnUpdateRows 14864 * @param sRequest {String} Original request. 14865 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14866 * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex. 14867 */ 14868 onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) { 14869 if((this instanceof DT) && this._sId) { 14870 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload}); 14871 14872 // Pass data through abstract method for any transformations 14873 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload); 14874 14875 // Data ok to append 14876 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) { 14877 // Insert rows 14878 this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results); 14879 14880 // Update state 14881 this._handleDataReturnPayload(sRequest, oResponse, oPayload); 14882 } 14883 // Error 14884 else if(ok && oResponse.error) { 14885 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR); 14886 } 14887 } 14888 }, 14889 14890 /** 14891 * Callback function receives reponse from DataSource and populates the 14892 * RecordSet with the results. 14893 * 14894 * @method onDataReturnSetRows 14895 * @param oRequest {MIXED} Original generated request. 14896 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14897 * @param oPayload {MIXED} (optional) Additional argument(s) 14898 */ 14899 onDataReturnSetRows : function(oRequest, oResponse, oPayload) { 14900 if((this instanceof DT) && this._sId) { 14901 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload}); 14902 14903 // Pass data through abstract method for any transformations 14904 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload), 14905 pag = this.get('paginator'), 14906 index = 0; 14907 14908 // Data ok to set 14909 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) { 14910 // Update Records 14911 if (this.get('dynamicData')) { 14912 if (oPayload && oPayload.pagination && 14913 lang.isNumber(oPayload.pagination.recordOffset)) { 14914 index = oPayload.pagination.recordOffset; 14915 } else if (pag) { 14916 index = pag.getStartIndex(); 14917 } 14918 14919 this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default 14920 } 14921 14922 this._oRecordSet.setRecords(oResponse.results, index | 0); 14923 14924 // Update state 14925 this._handleDataReturnPayload(oRequest, oResponse, oPayload); 14926 14927 // Update UI 14928 this.render(); 14929 } 14930 // Error 14931 else if(ok && oResponse.error) { 14932 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR); 14933 } 14934 } 14935 else { 14936 YAHOO.log("Instance destroyed before data returned.","info",this.toString()); 14937 } 14938 }, 14939 14940 /** 14941 * Hook to update oPayload before consumption. 14942 * 14943 * @method handleDataReturnPayload 14944 * @param oRequest {MIXED} Original generated request. 14945 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14946 * @param oPayload {MIXED} State values. 14947 * @return oPayload {MIXED} State values. 14948 */ 14949 handleDataReturnPayload : function (oRequest, oResponse, oPayload) { 14950 return oPayload || {}; 14951 }, 14952 14953 /** 14954 * Updates the DataTable with state data sent in an onDataReturn* payload. 14955 * 14956 * @method _handleDataReturnPayload 14957 * @param oRequest {MIXED} Original generated request. 14958 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14959 * @param oPayload {MIXED} State values 14960 * @private 14961 */ 14962 _handleDataReturnPayload : function (oRequest, oResponse, oPayload) { 14963 oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload); 14964 if(oPayload) { 14965 // Update pagination 14966 var oPaginator = this.get('paginator'); 14967 if (oPaginator) { 14968 // Update totalRecords 14969 if(this.get("dynamicData")) { 14970 if (widget.Paginator.isNumeric(oPayload.totalRecords)) { 14971 oPaginator.set('totalRecords',oPayload.totalRecords); 14972 } 14973 } 14974 else { 14975 oPaginator.set('totalRecords',this._oRecordSet.getLength()); 14976 } 14977 // Update other paginator values 14978 if (lang.isObject(oPayload.pagination)) { 14979 oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage); 14980 oPaginator.set('recordOffset',oPayload.pagination.recordOffset); 14981 } 14982 } 14983 14984 // Update sorting 14985 if (oPayload.sortedBy) { 14986 // Set the sorting values in preparation for refresh 14987 this.set('sortedBy', oPayload.sortedBy); 14988 } 14989 // Backwards compatibility for sorting 14990 else if (oPayload.sorting) { 14991 // Set the sorting values in preparation for refresh 14992 this.set('sortedBy', oPayload.sorting); 14993 } 14994 } 14995 }, 14996 14997 14998 14999 15000 15001 15002 15003 15004 15005 15006 15007 15008 15009 15010 15011 15012 15013 15014 15015 15016 15017 15018 15019 15020 15021 15022 15023 15024 15025 15026 15027 15028 15029 ///////////////////////////////////////////////////////////////////////////// 15030 // 15031 // Custom Events 15032 // 15033 ///////////////////////////////////////////////////////////////////////////// 15034 15035 /** 15036 * Fired when the DataTable's rows are rendered from an initialized state. 15037 * 15038 * @event initEvent 15039 */ 15040 15041 /** 15042 * Fired before the DataTable's DOM is rendered or modified. 15043 * 15044 * @event beforeRenderEvent 15045 */ 15046 15047 /** 15048 * Fired when the DataTable's DOM is rendered or modified. 15049 * 15050 * @event renderEvent 15051 */ 15052 15053 /** 15054 * Fired when the DataTable's post-render routine is complete, including 15055 * Column width validations. 15056 * 15057 * @event postRenderEvent 15058 */ 15059 15060 /** 15061 * Fired when the DataTable is disabled. 15062 * 15063 * @event disableEvent 15064 */ 15065 15066 /** 15067 * Fired when the DataTable is undisabled. 15068 * 15069 * @event undisableEvent 15070 */ 15071 15072 /** 15073 * Fired when data is returned from DataSource but before it is consumed by 15074 * DataTable. 15075 * 15076 * @event dataReturnEvent 15077 * @param oArgs.request {String} Original request. 15078 * @param oArgs.response {Object} Response object. 15079 */ 15080 15081 /** 15082 * Fired when the DataTable has a focus event. 15083 * 15084 * @event tableFocusEvent 15085 */ 15086 15087 /** 15088 * Fired when the DataTable THEAD element has a focus event. 15089 * 15090 * @event theadFocusEvent 15091 */ 15092 15093 /** 15094 * Fired when the DataTable TBODY element has a focus event. 15095 * 15096 * @event tbodyFocusEvent 15097 */ 15098 15099 /** 15100 * Fired when the DataTable has a blur event. 15101 * 15102 * @event tableBlurEvent 15103 */ 15104 15105 /*TODO implement theadBlurEvent 15106 * Fired when the DataTable THEAD element has a blur event. 15107 * 15108 * @event theadBlurEvent 15109 */ 15110 15111 /*TODO: implement tbodyBlurEvent 15112 * Fired when the DataTable TBODY element has a blur event. 15113 * 15114 * @event tbodyBlurEvent 15115 */ 15116 15117 /** 15118 * Fired when the DataTable has a key event. 15119 * 15120 * @event tableKeyEvent 15121 * @param oArgs.event {HTMLEvent} The event object. 15122 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 15123 */ 15124 15125 /** 15126 * Fired when the DataTable THEAD element has a key event. 15127 * 15128 * @event theadKeyEvent 15129 * @param oArgs.event {HTMLEvent} The event object. 15130 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 15131 */ 15132 15133 /** 15134 * Fired when the DataTable TBODY element has a key event. 15135 * 15136 * @event tbodyKeyEvent 15137 * @param oArgs.event {HTMLEvent} The event object. 15138 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 15139 */ 15140 15141 /** 15142 * Fired when the DataTable has a mouseover. 15143 * 15144 * @event tableMouseoverEvent 15145 * @param oArgs.event {HTMLEvent} The event object. 15146 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 15147 * 15148 */ 15149 15150 /** 15151 * Fired when the DataTable has a mouseout. 15152 * 15153 * @event tableMouseoutEvent 15154 * @param oArgs.event {HTMLEvent} The event object. 15155 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 15156 * 15157 */ 15158 15159 /** 15160 * Fired when the DataTable has a mousedown. 15161 * 15162 * @event tableMousedownEvent 15163 * @param oArgs.event {HTMLEvent} The event object. 15164 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 15165 * 15166 */ 15167 15168 /** 15169 * Fired when the DataTable has a mouseup. 15170 * 15171 * @event tableMouseupEvent 15172 * @param oArgs.event {HTMLEvent} The event object. 15173 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 15174 * 15175 */ 15176 15177 /** 15178 * Fired when the DataTable has a click. 15179 * 15180 * @event tableClickEvent 15181 * @param oArgs.event {HTMLEvent} The event object. 15182 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 15183 * 15184 */ 15185 15186 /** 15187 * Fired when the DataTable has a dblclick. 15188 * 15189 * @event tableDblclickEvent 15190 * @param oArgs.event {HTMLEvent} The event object. 15191 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 15192 * 15193 */ 15194 15195 /** 15196 * Fired when a message is shown in the DataTable's message element. 15197 * 15198 * @event tableMsgShowEvent 15199 * @param oArgs.html {HTML} The HTML displayed. 15200 * @param oArgs.className {String} The className assigned. 15201 * 15202 */ 15203 15204 /** 15205 * Fired when the DataTable's message element is hidden. 15206 * 15207 * @event tableMsgHideEvent 15208 */ 15209 15210 /** 15211 * Fired when a THEAD row has a mouseover. 15212 * 15213 * @event theadRowMouseoverEvent 15214 * @param oArgs.event {HTMLEvent} The event object. 15215 * @param oArgs.target {HTMLElement} The TR element. 15216 */ 15217 15218 /** 15219 * Fired when a THEAD row has a mouseout. 15220 * 15221 * @event theadRowMouseoutEvent 15222 * @param oArgs.event {HTMLEvent} The event object. 15223 * @param oArgs.target {HTMLElement} The TR element. 15224 */ 15225 15226 /** 15227 * Fired when a THEAD row has a mousedown. 15228 * 15229 * @event theadRowMousedownEvent 15230 * @param oArgs.event {HTMLEvent} The event object. 15231 * @param oArgs.target {HTMLElement} The TR element. 15232 */ 15233 15234 /** 15235 * Fired when a THEAD row has a mouseup. 15236 * 15237 * @event theadRowMouseupEvent 15238 * @param oArgs.event {HTMLEvent} The event object. 15239 * @param oArgs.target {HTMLElement} The TR element. 15240 */ 15241 15242 /** 15243 * Fired when a THEAD row has a click. 15244 * 15245 * @event theadRowClickEvent 15246 * @param oArgs.event {HTMLEvent} The event object. 15247 * @param oArgs.target {HTMLElement} The TR element. 15248 */ 15249 15250 /** 15251 * Fired when a THEAD row has a dblclick. 15252 * 15253 * @event theadRowDblclickEvent 15254 * @param oArgs.event {HTMLEvent} The event object. 15255 * @param oArgs.target {HTMLElement} The TR element. 15256 */ 15257 15258 /** 15259 * Fired when a THEAD cell has a mouseover. 15260 * 15261 * @event theadCellMouseoverEvent 15262 * @param oArgs.event {HTMLEvent} The event object. 15263 * @param oArgs.target {HTMLElement} The TH element. 15264 * 15265 */ 15266 15267 /** 15268 * Fired when a THEAD cell has a mouseout. 15269 * 15270 * @event theadCellMouseoutEvent 15271 * @param oArgs.event {HTMLEvent} The event object. 15272 * @param oArgs.target {HTMLElement} The TH element. 15273 * 15274 */ 15275 15276 /** 15277 * Fired when a THEAD cell has a mousedown. 15278 * 15279 * @event theadCellMousedownEvent 15280 * @param oArgs.event {HTMLEvent} The event object. 15281 * @param oArgs.target {HTMLElement} The TH element. 15282 */ 15283 15284 /** 15285 * Fired when a THEAD cell has a mouseup. 15286 * 15287 * @event theadCellMouseupEvent 15288 * @param oArgs.event {HTMLEvent} The event object. 15289 * @param oArgs.target {HTMLElement} The TH element. 15290 */ 15291 15292 /** 15293 * Fired when a THEAD cell has a click. 15294 * 15295 * @event theadCellClickEvent 15296 * @param oArgs.event {HTMLEvent} The event object. 15297 * @param oArgs.target {HTMLElement} The TH element. 15298 */ 15299 15300 /** 15301 * Fired when a THEAD cell has a dblclick. 15302 * 15303 * @event theadCellDblclickEvent 15304 * @param oArgs.event {HTMLEvent} The event object. 15305 * @param oArgs.target {HTMLElement} The TH element. 15306 */ 15307 15308 /** 15309 * Fired when a THEAD label has a mouseover. 15310 * 15311 * @event theadLabelMouseoverEvent 15312 * @param oArgs.event {HTMLEvent} The event object. 15313 * @param oArgs.target {HTMLElement} The SPAN element. 15314 * 15315 */ 15316 15317 /** 15318 * Fired when a THEAD label has a mouseout. 15319 * 15320 * @event theadLabelMouseoutEvent 15321 * @param oArgs.event {HTMLEvent} The event object. 15322 * @param oArgs.target {HTMLElement} The SPAN element. 15323 * 15324 */ 15325 15326 /** 15327 * Fired when a THEAD label has a mousedown. 15328 * 15329 * @event theadLabelMousedownEvent 15330 * @param oArgs.event {HTMLEvent} The event object. 15331 * @param oArgs.target {HTMLElement} The SPAN element. 15332 */ 15333 15334 /** 15335 * Fired when a THEAD label has a mouseup. 15336 * 15337 * @event theadLabelMouseupEvent 15338 * @param oArgs.event {HTMLEvent} The event object. 15339 * @param oArgs.target {HTMLElement} The SPAN element. 15340 */ 15341 15342 /** 15343 * Fired when a THEAD label has a click. 15344 * 15345 * @event theadLabelClickEvent 15346 * @param oArgs.event {HTMLEvent} The event object. 15347 * @param oArgs.target {HTMLElement} The SPAN element. 15348 */ 15349 15350 /** 15351 * Fired when a THEAD label has a dblclick. 15352 * 15353 * @event theadLabelDblclickEvent 15354 * @param oArgs.event {HTMLEvent} The event object. 15355 * @param oArgs.target {HTMLElement} The SPAN element. 15356 */ 15357 15358 /** 15359 * Fired when a column is sorted. 15360 * 15361 * @event columnSortEvent 15362 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15363 * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC 15364 * or YAHOO.widget.DataTable.CLASS_DESC. 15365 */ 15366 15367 /** 15368 * Fired when a column width is set. 15369 * 15370 * @event columnSetWidthEvent 15371 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15372 * @param oArgs.width {Number} The width in pixels. 15373 */ 15374 15375 /** 15376 * Fired when a column width is unset. 15377 * 15378 * @event columnUnsetWidthEvent 15379 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15380 */ 15381 15382 /** 15383 * Fired when a column is drag-resized. 15384 * 15385 * @event columnResizeEvent 15386 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15387 * @param oArgs.target {HTMLElement} The TH element. 15388 * @param oArgs.width {Number} Width in pixels. 15389 */ 15390 15391 /** 15392 * Fired when a Column is moved to a new index. 15393 * 15394 * @event columnReorderEvent 15395 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15396 * @param oArgs.oldIndex {Number} The previous tree index position. 15397 */ 15398 15399 /** 15400 * Fired when a column is hidden. 15401 * 15402 * @event columnHideEvent 15403 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15404 */ 15405 15406 /** 15407 * Fired when a column is shown. 15408 * 15409 * @event columnShowEvent 15410 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15411 */ 15412 15413 /** 15414 * Fired when a column is selected. 15415 * 15416 * @event columnSelectEvent 15417 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15418 */ 15419 15420 /** 15421 * Fired when a column is unselected. 15422 * 15423 * @event columnUnselectEvent 15424 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15425 */ 15426 /** 15427 * Fired when a column is removed. 15428 * 15429 * @event columnRemoveEvent 15430 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15431 */ 15432 15433 /** 15434 * Fired when a column is inserted. 15435 * 15436 * @event columnInsertEvent 15437 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15438 * @param oArgs.index {Number} The index position. 15439 */ 15440 15441 /** 15442 * Fired when a column is highlighted. 15443 * 15444 * @event columnHighlightEvent 15445 * @param oArgs.column {YAHOO.widget.Column} The highlighted Column. 15446 */ 15447 15448 /** 15449 * Fired when a column is unhighlighted. 15450 * 15451 * @event columnUnhighlightEvent 15452 * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column. 15453 */ 15454 15455 15456 /** 15457 * Fired when a row has a mouseover. 15458 * 15459 * @event rowMouseoverEvent 15460 * @param oArgs.event {HTMLEvent} The event object. 15461 * @param oArgs.target {HTMLElement} The TR element. 15462 */ 15463 15464 /** 15465 * Fired when a row has a mouseout. 15466 * 15467 * @event rowMouseoutEvent 15468 * @param oArgs.event {HTMLEvent} The event object. 15469 * @param oArgs.target {HTMLElement} The TR element. 15470 */ 15471 15472 /** 15473 * Fired when a row has a mousedown. 15474 * 15475 * @event rowMousedownEvent 15476 * @param oArgs.event {HTMLEvent} The event object. 15477 * @param oArgs.target {HTMLElement} The TR element. 15478 */ 15479 15480 /** 15481 * Fired when a row has a mouseup. 15482 * 15483 * @event rowMouseupEvent 15484 * @param oArgs.event {HTMLEvent} The event object. 15485 * @param oArgs.target {HTMLElement} The TR element. 15486 */ 15487 15488 /** 15489 * Fired when a row has a click. 15490 * 15491 * @event rowClickEvent 15492 * @param oArgs.event {HTMLEvent} The event object. 15493 * @param oArgs.target {HTMLElement} The TR element. 15494 */ 15495 15496 /** 15497 * Fired when a row has a dblclick. 15498 * 15499 * @event rowDblclickEvent 15500 * @param oArgs.event {HTMLEvent} The event object. 15501 * @param oArgs.target {HTMLElement} The TR element. 15502 */ 15503 15504 /** 15505 * Fired when a row is added. 15506 * 15507 * @event rowAddEvent 15508 * @param oArgs.record {YAHOO.widget.Record} The added Record. 15509 */ 15510 15511 /** 15512 * Fired when rows are added. 15513 * 15514 * @event rowsAddEvent 15515 * @param oArgs.record {YAHOO.widget.Record[]} The added Records. 15516 */ 15517 15518 /** 15519 * Fired when a row is updated. 15520 * 15521 * @event rowUpdateEvent 15522 * @param oArgs.record {YAHOO.widget.Record} The updated Record. 15523 * @param oArgs.oldData {Object} Object literal of the old data. 15524 */ 15525 15526 /** 15527 * Fired when a row is deleted. 15528 * 15529 * @event rowDeleteEvent 15530 * @param oArgs.oldData {Object} Object literal of the deleted data. 15531 * @param oArgs.recordIndex {Number} Index of the deleted Record. 15532 * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page. 15533 */ 15534 15535 /** 15536 * Fired when rows are deleted. 15537 * 15538 * @event rowsDeleteEvent 15539 * @param oArgs.oldData {Object[]} Array of object literals of the deleted data. 15540 * @param oArgs.recordIndex {Number} Index of the first deleted Record. 15541 * @param oArgs.count {Number} Number of deleted Records. 15542 */ 15543 15544 /** 15545 * Fired when a row is selected. 15546 * 15547 * @event rowSelectEvent 15548 * @param oArgs.el {HTMLElement} The selected TR element, if applicable. 15549 * @param oArgs.record {YAHOO.widget.Record} The selected Record. 15550 */ 15551 15552 /** 15553 * Fired when a row is unselected. 15554 * 15555 * @event rowUnselectEvent 15556 * @param oArgs.el {HTMLElement} The unselected TR element, if applicable. 15557 * @param oArgs.record {YAHOO.widget.Record} The unselected Record. 15558 */ 15559 15560 /** 15561 * Fired when all row selections are cleared. 15562 * 15563 * @event unselectAllRowsEvent 15564 */ 15565 15566 /** 15567 * Fired when a row is highlighted. 15568 * 15569 * @event rowHighlightEvent 15570 * @param oArgs.el {HTMLElement} The highlighted TR element. 15571 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record. 15572 */ 15573 15574 /** 15575 * Fired when a row is unhighlighted. 15576 * 15577 * @event rowUnhighlightEvent 15578 * @param oArgs.el {HTMLElement} The highlighted TR element. 15579 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record. 15580 */ 15581 15582 /** 15583 * Fired when a cell is updated. 15584 * 15585 * @event cellUpdateEvent 15586 * @param oArgs.record {YAHOO.widget.Record} The updated Record. 15587 * @param oArgs.column {YAHOO.widget.Column} The updated Column. 15588 * @param oArgs.oldData {Object} Original data value of the updated cell. 15589 */ 15590 15591 /** 15592 * Fired when a cell has a mouseover. 15593 * 15594 * @event cellMouseoverEvent 15595 * @param oArgs.event {HTMLEvent} The event object. 15596 * @param oArgs.target {HTMLElement} The TD element. 15597 */ 15598 15599 /** 15600 * Fired when a cell has a mouseout. 15601 * 15602 * @event cellMouseoutEvent 15603 * @param oArgs.event {HTMLEvent} The event object. 15604 * @param oArgs.target {HTMLElement} The TD element. 15605 */ 15606 15607 /** 15608 * Fired when a cell has a mousedown. 15609 * 15610 * @event cellMousedownEvent 15611 * @param oArgs.event {HTMLEvent} The event object. 15612 * @param oArgs.target {HTMLElement} The TD element. 15613 */ 15614 15615 /** 15616 * Fired when a cell has a mouseup. 15617 * 15618 * @event cellMouseupEvent 15619 * @param oArgs.event {HTMLEvent} The event object. 15620 * @param oArgs.target {HTMLElement} The TD element. 15621 */ 15622 15623 /** 15624 * Fired when a cell has a click. 15625 * 15626 * @event cellClickEvent 15627 * @param oArgs.event {HTMLEvent} The event object. 15628 * @param oArgs.target {HTMLElement} The TD element. 15629 */ 15630 15631 /** 15632 * Fired when a cell has a dblclick. 15633 * 15634 * @event cellDblclickEvent 15635 * @param oArgs.event {HTMLEvent} The event object. 15636 * @param oArgs.target {HTMLElement} The TD element. 15637 */ 15638 15639 /** 15640 * Fired when a cell is formatted. 15641 * 15642 * @event cellFormatEvent 15643 * @param oArgs.el {HTMLElement} The formatted TD element. 15644 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance. 15645 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance. 15646 * @param oArgs.key {String} (deprecated) The key of the formatted cell. 15647 */ 15648 15649 /** 15650 * Fired when a cell is selected. 15651 * 15652 * @event cellSelectEvent 15653 * @param oArgs.el {HTMLElement} The selected TD element. 15654 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance. 15655 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance. 15656 * @param oArgs.key {String} (deprecated) The key of the selected cell. 15657 */ 15658 15659 /** 15660 * Fired when a cell is unselected. 15661 * 15662 * @event cellUnselectEvent 15663 * @param oArgs.el {HTMLElement} The unselected TD element. 15664 * @param oArgs.record {YAHOO.widget.Record} The associated Record. 15665 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance. 15666 * @param oArgs.key {String} (deprecated) The key of the unselected cell. 15667 15668 */ 15669 15670 /** 15671 * Fired when a cell is highlighted. 15672 * 15673 * @event cellHighlightEvent 15674 * @param oArgs.el {HTMLElement} The highlighted TD element. 15675 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance. 15676 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance. 15677 * @param oArgs.key {String} (deprecated) The key of the highlighted cell. 15678 15679 */ 15680 15681 /** 15682 * Fired when a cell is unhighlighted. 15683 * 15684 * @event cellUnhighlightEvent 15685 * @param oArgs.el {HTMLElement} The unhighlighted TD element. 15686 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance. 15687 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance. 15688 * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell. 15689 15690 */ 15691 15692 /** 15693 * Fired when all cell selections are cleared. 15694 * 15695 * @event unselectAllCellsEvent 15696 */ 15697 15698 /** 15699 * Fired when a CellEditor is shown. 15700 * 15701 * @event editorShowEvent 15702 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15703 */ 15704 15705 /** 15706 * Fired when a CellEditor has a keydown. 15707 * 15708 * @event editorKeydownEvent 15709 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15710 * @param oArgs.event {HTMLEvent} The event object. 15711 */ 15712 15713 /** 15714 * Fired when a CellEditor input is reverted. 15715 * 15716 * @event editorRevertEvent 15717 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15718 * @param oArgs.newData {Object} New data value from form input field. 15719 * @param oArgs.oldData {Object} Old data value. 15720 */ 15721 15722 /** 15723 * Fired when a CellEditor input is saved. 15724 * 15725 * @event editorSaveEvent 15726 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15727 * @param oArgs.newData {Object} New data value from form input field. 15728 * @param oArgs.oldData {Object} Old data value. 15729 */ 15730 15731 /** 15732 * Fired when a CellEditor input is canceled. 15733 * 15734 * @event editorCancelEvent 15735 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15736 */ 15737 15738 /** 15739 * Fired when a CellEditor has a blur event. 15740 * 15741 * @event editorBlurEvent 15742 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15743 */ 15744 15745 /** 15746 * Fired when a CellEditor is blocked. 15747 * 15748 * @event editorBlockEvent 15749 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15750 */ 15751 15752 /** 15753 * Fired when a CellEditor is unblocked. 15754 * 15755 * @event editorUnblockEvent 15756 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15757 */ 15758 15759 15760 15761 15762 15763 /** 15764 * Fired when a link is clicked. 15765 * 15766 * @event linkClickEvent 15767 * @param oArgs.event {HTMLEvent} The event object. 15768 * @param oArgs.target {HTMLElement} The A element. 15769 */ 15770 15771 /** 15772 * Fired when a BUTTON element or INPUT element of type "button", "image", 15773 * "submit", "reset" is clicked. 15774 * 15775 * @event buttonClickEvent 15776 * @param oArgs.event {HTMLEvent} The event object. 15777 * @param oArgs.target {HTMLElement} The BUTTON element. 15778 */ 15779 15780 /** 15781 * Fired when a CHECKBOX element is clicked. 15782 * 15783 * @event checkboxClickEvent 15784 * @param oArgs.event {HTMLEvent} The event object. 15785 * @param oArgs.target {HTMLElement} The CHECKBOX element. 15786 */ 15787 15788 /** 15789 * Fired when a SELECT element is changed. 15790 * 15791 * @event dropdownChangeEvent 15792 * @param oArgs.event {HTMLEvent} The event object. 15793 * @param oArgs.target {HTMLElement} The SELECT element. 15794 */ 15795 15796 /** 15797 * Fired when a RADIO element is clicked. 15798 * 15799 * @event radioClickEvent 15800 * @param oArgs.event {HTMLEvent} The event object. 15801 * @param oArgs.target {HTMLElement} The RADIO element. 15802 */ 15803 15804 15805 15806 15807 15808 15809 15810 15811 15812 15813 15814 15815 15816 15817 15818 15819 15820 15821 15822 15823 15824 15825 15826 15827 15828 15829 ///////////////////////////////////////////////////////////////////////////// 15830 // 15831 // Deprecated APIs 15832 // 15833 ///////////////////////////////////////////////////////////////////////////// 15834 15835 /* 15836 * @method showCellEditorBtns 15837 * @deprecated Use CellEditor.renderBtns() 15838 */ 15839 showCellEditorBtns : function(elContainer) { 15840 // Buttons 15841 var elBtnsDiv = elContainer.appendChild(document.createElement("div")); 15842 Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON); 15843 15844 // Save button 15845 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button")); 15846 Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT); 15847 elSaveBtn.innerHTML = "OK"; 15848 Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) { 15849 oSelf.onEventSaveCellEditor(oArgs, oSelf); 15850 oSelf.focusTbodyEl(); 15851 }, this, true); 15852 15853 // Cancel button 15854 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button")); 15855 elCancelBtn.innerHTML = "Cancel"; 15856 Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) { 15857 oSelf.onEventCancelCellEditor(oArgs, oSelf); 15858 oSelf.focusTbodyEl(); 15859 }, this, true); 15860 15861 YAHOO.log("The method showCellEditorBtns() has been deprecated." + 15862 " Please use the CellEditor class.", "warn", this.toString()); 15863 }, 15864 15865 /** 15866 * @method resetCellEditor 15867 * @deprecated Use destroyCellEditor 15868 */ 15869 resetCellEditor : function() { 15870 var elContainer = this._oCellEditor.container; 15871 elContainer.style.display = "none"; 15872 Ev.purgeElement(elContainer, true); 15873 elContainer.innerHTML = ""; 15874 this._oCellEditor.value = null; 15875 this._oCellEditor.isActive = false; 15876 15877 YAHOO.log("The method resetCellEditor() has been deprecated." + 15878 " Please use the CellEditor class.", "warn", this.toString()); 15879 }, 15880 15881 /** 15882 * @event editorUpdateEvent 15883 * @deprecated Use CellEditor class. 15884 */ 15885 15886 /** 15887 * @method getBody 15888 * @deprecated Use getTbodyEl(). 15889 */ 15890 getBody : function() { 15891 // Backward compatibility 15892 YAHOO.log("The method getBody() has been deprecated" + 15893 " in favor of getTbodyEl()", "warn", this.toString()); 15894 return this.getTbodyEl(); 15895 }, 15896 15897 /** 15898 * @method getCell 15899 * @deprecated Use getTdEl(). 15900 */ 15901 getCell : function(index) { 15902 // Backward compatibility 15903 YAHOO.log("The method getCell() has been deprecated" + 15904 " in favor of getTdEl()", "warn", this.toString()); 15905 return this.getTdEl(index); 15906 }, 15907 15908 /** 15909 * @method getRow 15910 * @deprecated Use getTrEl(). 15911 */ 15912 getRow : function(index) { 15913 // Backward compatibility 15914 YAHOO.log("The method getRow() has been deprecated" + 15915 " in favor of getTrEl()", "warn", this.toString()); 15916 return this.getTrEl(index); 15917 }, 15918 15919 /** 15920 * @method refreshView 15921 * @deprecated Use render. 15922 */ 15923 refreshView : function() { 15924 // Backward compatibility 15925 YAHOO.log("The method refreshView() has been deprecated" + 15926 " in favor of render()", "warn", this.toString()); 15927 this.render(); 15928 }, 15929 15930 /** 15931 * @method select 15932 * @deprecated Use selectRow. 15933 */ 15934 select : function(els) { 15935 // Backward compatibility 15936 YAHOO.log("The method select() has been deprecated" + 15937 " in favor of selectRow()", "warn", this.toString()); 15938 if(!lang.isArray(els)) { 15939 els = [els]; 15940 } 15941 for(var i=0; i<els.length; i++) { 15942 this.selectRow(els[i]); 15943 } 15944 }, 15945 15946 /** 15947 * @method onEventEditCell 15948 * @deprecated Use onEventShowCellEditor. 15949 */ 15950 onEventEditCell : function(oArgs) { 15951 // Backward compatibility 15952 YAHOO.log("The method onEventEditCell() has been deprecated" + 15953 " in favor of onEventShowCellEditor()", "warn", this.toString()); 15954 this.onEventShowCellEditor(oArgs); 15955 }, 15956 15957 /** 15958 * @method _syncColWidths 15959 * @deprecated Use validateColumnWidths. 15960 */ 15961 _syncColWidths : function() { 15962 // Backward compatibility 15963 YAHOO.log("The method _syncColWidths() has been deprecated" + 15964 " in favor of validateColumnWidths()", "warn", this.toString()); 15965 this.validateColumnWidths(); 15966 } 15967 15968 /** 15969 * @event headerRowMouseoverEvent 15970 * @deprecated Use theadRowMouseoverEvent. 15971 */ 15972 15973 /** 15974 * @event headerRowMouseoutEvent 15975 * @deprecated Use theadRowMouseoutEvent. 15976 */ 15977 15978 /** 15979 * @event headerRowMousedownEvent 15980 * @deprecated Use theadRowMousedownEvent. 15981 */ 15982 15983 /** 15984 * @event headerRowClickEvent 15985 * @deprecated Use theadRowClickEvent. 15986 */ 15987 15988 /** 15989 * @event headerRowDblclickEvent 15990 * @deprecated Use theadRowDblclickEvent. 15991 */ 15992 15993 /** 15994 * @event headerCellMouseoverEvent 15995 * @deprecated Use theadCellMouseoverEvent. 15996 */ 15997 15998 /** 15999 * @event headerCellMouseoutEvent 16000 * @deprecated Use theadCellMouseoutEvent. 16001 */ 16002 16003 /** 16004 * @event headerCellMousedownEvent 16005 * @deprecated Use theadCellMousedownEvent. 16006 */ 16007 16008 /** 16009 * @event headerCellClickEvent 16010 * @deprecated Use theadCellClickEvent. 16011 */ 16012 16013 /** 16014 * @event headerCellDblclickEvent 16015 * @deprecated Use theadCellDblclickEvent. 16016 */ 16017 16018 /** 16019 * @event headerLabelMouseoverEvent 16020 * @deprecated Use theadLabelMouseoverEvent. 16021 */ 16022 16023 /** 16024 * @event headerLabelMouseoutEvent 16025 * @deprecated Use theadLabelMouseoutEvent. 16026 */ 16027 16028 /** 16029 * @event headerLabelMousedownEvent 16030 * @deprecated Use theadLabelMousedownEvent. 16031 */ 16032 16033 /** 16034 * @event headerLabelClickEvent 16035 * @deprecated Use theadLabelClickEvent. 16036 */ 16037 16038 /** 16039 * @event headerLabelDbllickEvent 16040 * @deprecated Use theadLabelDblclickEvent. 16041 */ 16042 16043 }); 16044 16045 /** 16046 * Alias for onDataReturnSetRows for backward compatibility 16047 * @method onDataReturnSetRecords 16048 * @deprecated Use onDataReturnSetRows 16049 */ 16050 DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows; 16051 16052 /** 16053 * Alias for onPaginatorChange for backward compatibility 16054 * @method onPaginatorChange 16055 * @deprecated Use onPaginatorChangeRequest 16056 */ 16057 DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest; 16058 16059 ///////////////////////////////////////////////////////////////////////////// 16060 // 16061 // Deprecated static APIs 16062 // 16063 ///////////////////////////////////////////////////////////////////////////// 16064 /** 16065 * @method DataTable.editCheckbox 16066 * @deprecated Use YAHOO.widget.CheckboxCellEditor. 16067 */ 16068 DT.editCheckbox = function() {}; 16069 16070 /** 16071 * @method DataTable.editDate 16072 * @deprecated Use YAHOO.widget.DateCellEditor. 16073 */ 16074 DT.editDate = function() {}; 16075 16076 /** 16077 * @method DataTable.editDropdown 16078 * @deprecated Use YAHOO.widget.DropdownCellEditor. 16079 */ 16080 DT.editDropdown = function() {}; 16081 16082 /** 16083 * @method DataTable.editRadio 16084 * @deprecated Use YAHOO.widget.RadioCellEditor. 16085 */ 16086 DT.editRadio = function() {}; 16087 16088 /** 16089 * @method DataTable.editTextarea 16090 * @deprecated Use YAHOO.widget.TextareaCellEditor 16091 */ 16092 DT.editTextarea = function() {}; 16093 16094 /** 16095 * @method DataTable.editTextbox 16096 * @deprecated Use YAHOO.widget.TextboxCellEditor 16097 */ 16098 DT.editTextbox= function() {}; 16099 16100 })(); 16101 16102 (function () { 16103 16104 var lang = YAHOO.lang, 16105 util = YAHOO.util, 16106 widget = YAHOO.widget, 16107 ua = YAHOO.env.ua, 16108 16109 Dom = util.Dom, 16110 Ev = util.Event, 16111 DS = util.DataSourceBase, 16112 DT = widget.DataTable, 16113 Pag = widget.Paginator; 16114 16115 /** 16116 * The ScrollingDataTable class extends the DataTable class to provide 16117 * functionality for x-scrolling, y-scrolling, and xy-scrolling. 16118 * 16119 * @namespace YAHOO.widget 16120 * @class ScrollingDataTable 16121 * @extends YAHOO.widget.DataTable 16122 * @constructor 16123 * @param elContainer {HTMLElement} Container element for the TABLE. 16124 * @param aColumnDefs {Object[]} Array of object literal Column definitions. 16125 * @param oDataSource {YAHOO.util.DataSource} DataSource instance. 16126 * @param oConfigs {object} (optional) Object literal of configuration values. 16127 */ 16128 widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) { 16129 oConfigs = oConfigs || {}; 16130 16131 // Prevent infinite loop 16132 if(oConfigs.scrollable) { 16133 oConfigs.scrollable = false; 16134 } 16135 16136 this._init(); 16137 16138 widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs); 16139 16140 // Once per instance 16141 this.subscribe("columnShowEvent", this._onColumnChange); 16142 }; 16143 16144 var SDT = widget.ScrollingDataTable; 16145 16146 ///////////////////////////////////////////////////////////////////////////// 16147 // 16148 // Public constants 16149 // 16150 ///////////////////////////////////////////////////////////////////////////// 16151 lang.augmentObject(SDT, { 16152 16153 /** 16154 * Class name assigned to inner DataTable header container. 16155 * 16156 * @property DataTable.CLASS_HEADER 16157 * @type String 16158 * @static 16159 * @final 16160 * @default "yui-dt-hd" 16161 */ 16162 CLASS_HEADER : "yui-dt-hd", 16163 16164 /** 16165 * Class name assigned to inner DataTable body container. 16166 * 16167 * @property DataTable.CLASS_BODY 16168 * @type String 16169 * @static 16170 * @final 16171 * @default "yui-dt-bd" 16172 */ 16173 CLASS_BODY : "yui-dt-bd" 16174 }); 16175 16176 lang.extend(SDT, DT, { 16177 16178 /** 16179 * Container for fixed header TABLE element. 16180 * 16181 * @property _elHdContainer 16182 * @type HTMLElement 16183 * @private 16184 */ 16185 _elHdContainer : null, 16186 16187 /** 16188 * Fixed header TABLE element. 16189 * 16190 * @property _elHdTable 16191 * @type HTMLElement 16192 * @private 16193 */ 16194 _elHdTable : null, 16195 16196 /** 16197 * Container for scrolling body TABLE element. 16198 * 16199 * @property _elBdContainer 16200 * @type HTMLElement 16201 * @private 16202 */ 16203 _elBdContainer : null, 16204 16205 /** 16206 * Body THEAD element. 16207 * 16208 * @property _elBdThead 16209 * @type HTMLElement 16210 * @private 16211 */ 16212 _elBdThead : null, 16213 16214 /** 16215 * Offscreen container to temporarily clone SDT for auto-width calculation. 16216 * 16217 * @property _elTmpContainer 16218 * @type HTMLElement 16219 * @private 16220 */ 16221 _elTmpContainer : null, 16222 16223 /** 16224 * Offscreen TABLE element for auto-width calculation. 16225 * 16226 * @property _elTmpTable 16227 * @type HTMLElement 16228 * @private 16229 */ 16230 _elTmpTable : null, 16231 16232 /** 16233 * True if x-scrollbar is currently visible. 16234 * @property _bScrollbarX 16235 * @type Boolean 16236 * @private 16237 */ 16238 _bScrollbarX : null, 16239 16240 16241 16242 16243 16244 16245 16246 16247 16248 16249 16250 16251 16252 16253 16254 ///////////////////////////////////////////////////////////////////////////// 16255 // 16256 // Superclass methods 16257 // 16258 ///////////////////////////////////////////////////////////////////////////// 16259 16260 /** 16261 * Implementation of Element's abstract method. Sets up config values. 16262 * 16263 * @method initAttributes 16264 * @param oConfigs {Object} (Optional) Object literal definition of configuration values. 16265 * @private 16266 */ 16267 16268 initAttributes : function(oConfigs) { 16269 oConfigs = oConfigs || {}; 16270 SDT.superclass.initAttributes.call(this, oConfigs); 16271 16272 /** 16273 * @attribute width 16274 * @description Table width for scrollable tables (e.g., "40em"). 16275 * @type String 16276 */ 16277 this.setAttributeConfig("width", { 16278 value: null, 16279 validator: lang.isString, 16280 method: function(oParam) { 16281 if(this._elHdContainer && this._elBdContainer) { 16282 this._elHdContainer.style.width = oParam; 16283 this._elBdContainer.style.width = oParam; 16284 this._syncScrollX(); 16285 this._syncScrollOverhang(); 16286 } 16287 } 16288 }); 16289 16290 /** 16291 * @attribute height 16292 * @description Table body height for scrollable tables, not including headers (e.g., "40em"). 16293 * @type String 16294 */ 16295 this.setAttributeConfig("height", { 16296 value: null, 16297 validator: lang.isString, 16298 method: function(oParam) { 16299 if(this._elHdContainer && this._elBdContainer) { 16300 this._elBdContainer.style.height = oParam; 16301 this._syncScrollX(); 16302 this._syncScrollY(); 16303 this._syncScrollOverhang(); 16304 } 16305 } 16306 }); 16307 16308 /** 16309 * @attribute COLOR_COLUMNFILLER 16310 * @description CSS color value assigned to header filler on scrollable tables. 16311 * @type String 16312 * @default "#F2F2F2" 16313 */ 16314 this.setAttributeConfig("COLOR_COLUMNFILLER", { 16315 value: "#F2F2F2", 16316 validator: lang.isString, 16317 method: function(oParam) { 16318 if(this._elHdContainer) { 16319 this._elHdContainer.style.backgroundColor = oParam; 16320 } 16321 } 16322 }); 16323 }, 16324 16325 /** 16326 * Initializes internal variables. 16327 * 16328 * @method _init 16329 * @private 16330 */ 16331 _init : function() { 16332 this._elHdContainer = null; 16333 this._elHdTable = null; 16334 this._elBdContainer = null; 16335 this._elBdThead = null; 16336 this._elTmpContainer = null; 16337 this._elTmpTable = null; 16338 }, 16339 16340 /** 16341 * Initializes DOM elements for a ScrollingDataTable, including creation of 16342 * two separate TABLE elements. 16343 * 16344 * @method _initDomElements 16345 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 16346 * return {Boolean} False in case of error, otherwise true 16347 * @private 16348 */ 16349 _initDomElements : function(elContainer) { 16350 // Outer and inner containers 16351 this._initContainerEl(elContainer); 16352 if(this._elContainer && this._elHdContainer && this._elBdContainer) { 16353 // TABLEs 16354 this._initTableEl(); 16355 16356 if(this._elHdTable && this._elTable) { 16357 // COLGROUPs 16358 ///this._initColgroupEl(this._elHdTable, this._elTable); 16359 this._initColgroupEl(this._elHdTable); 16360 16361 // THEADs 16362 this._initTheadEl(this._elHdTable, this._elTable); 16363 16364 // Primary TBODY 16365 this._initTbodyEl(this._elTable); 16366 // Message TBODY 16367 this._initMsgTbodyEl(this._elTable); 16368 } 16369 } 16370 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody || 16371 !this._elHdTable || !this._elBdThead) { 16372 YAHOO.log("Could not instantiate DataTable due to an invalid DOM elements", "error", this.toString()); 16373 return false; 16374 } 16375 else { 16376 return true; 16377 } 16378 }, 16379 16380 /** 16381 * Destroy's the DataTable outer and inner container elements, if available. 16382 * 16383 * @method _destroyContainerEl 16384 * @param elContainer {HTMLElement} Reference to the container element. 16385 * @private 16386 */ 16387 _destroyContainerEl : function(elContainer) { 16388 Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE); 16389 SDT.superclass._destroyContainerEl.call(this, elContainer); 16390 this._elHdContainer = null; 16391 this._elBdContainer = null; 16392 }, 16393 16394 /** 16395 * Initializes the DataTable outer container element and creates inner header 16396 * and body container elements. 16397 * 16398 * @method _initContainerEl 16399 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 16400 * @private 16401 */ 16402 _initContainerEl : function(elContainer) { 16403 SDT.superclass._initContainerEl.call(this, elContainer); 16404 16405 if(this._elContainer) { 16406 elContainer = this._elContainer; // was constructor input, now is DOM ref 16407 Dom.addClass(elContainer, DT.CLASS_SCROLLABLE); 16408 16409 // Container for header TABLE 16410 var elHdContainer = document.createElement("div"); 16411 elHdContainer.style.width = this.get("width") || ""; 16412 elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER"); 16413 Dom.addClass(elHdContainer, SDT.CLASS_HEADER); 16414 this._elHdContainer = elHdContainer; 16415 elContainer.appendChild(elHdContainer); 16416 16417 // Container for body TABLE 16418 var elBdContainer = document.createElement("div"); 16419 elBdContainer.style.width = this.get("width") || ""; 16420 elBdContainer.style.height = this.get("height") || ""; 16421 Dom.addClass(elBdContainer, SDT.CLASS_BODY); 16422 Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers 16423 this._elBdContainer = elBdContainer; 16424 elContainer.appendChild(elBdContainer); 16425 } 16426 }, 16427 16428 /** 16429 * Creates HTML markup CAPTION element. 16430 * 16431 * @method _initCaptionEl 16432 * @param sCaption {String} Text for caption. 16433 * @private 16434 */ 16435 _initCaptionEl : function(sCaption) { 16436 // Not yet supported 16437 /*if(this._elHdTable && sCaption) { 16438 // Create CAPTION element 16439 if(!this._elCaption) { 16440 this._elCaption = this._elHdTable.createCaption(); 16441 } 16442 // Set CAPTION value 16443 this._elCaption.innerHTML = sCaption; 16444 } 16445 else if(this._elCaption) { 16446 this._elCaption.parentNode.removeChild(this._elCaption); 16447 }*/ 16448 }, 16449 16450 /** 16451 * Destroy's the DataTable head TABLE element, if available. 16452 * 16453 * @method _destroyHdTableEl 16454 * @private 16455 */ 16456 _destroyHdTableEl : function() { 16457 var elTable = this._elHdTable; 16458 if(elTable) { 16459 Ev.purgeElement(elTable, true); 16460 elTable.parentNode.removeChild(elTable); 16461 16462 // A little out of place, but where else can we null out these extra elements? 16463 ///this._elBdColgroup = null; 16464 this._elBdThead = null; 16465 } 16466 }, 16467 16468 /** 16469 * Initializes ScrollingDataTable TABLE elements into the two inner containers. 16470 * 16471 * @method _initTableEl 16472 * @private 16473 */ 16474 _initTableEl : function() { 16475 // Head TABLE 16476 if(this._elHdContainer) { 16477 this._destroyHdTableEl(); 16478 16479 // Create TABLE 16480 this._elHdTable = this._elHdContainer.appendChild(document.createElement("table")); 16481 16482 // Set up mouseover/mouseout events via mouseenter/mouseleave delegation 16483 Ev.delegate(this._elHdTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this); 16484 Ev.delegate(this._elHdTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this); 16485 } 16486 // Body TABLE 16487 SDT.superclass._initTableEl.call(this, this._elBdContainer); 16488 }, 16489 16490 /** 16491 * Initializes ScrollingDataTable THEAD elements into the two inner containers. 16492 * 16493 * @method _initTheadEl 16494 * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference. 16495 * @param elTable {HTMLElement} (optional) TABLE element reference. 16496 * @private 16497 */ 16498 _initTheadEl : function(elHdTable, elTable) { 16499 elHdTable = elHdTable || this._elHdTable; 16500 elTable = elTable || this._elTable; 16501 16502 // Scrolling body's THEAD 16503 this._initBdTheadEl(elTable); 16504 // Standard fixed head THEAD 16505 SDT.superclass._initTheadEl.call(this, elHdTable); 16506 }, 16507 16508 /** 16509 * SDT changes ID so as not to duplicate the accessibility TH IDs. 16510 * 16511 * @method _initThEl 16512 * @param elTh {HTMLElement} TH element reference. 16513 * @param oColumn {YAHOO.widget.Column} Column object. 16514 * @private 16515 */ 16516 _initThEl : function(elTh, oColumn) { 16517 SDT.superclass._initThEl.call(this, elTh, oColumn); 16518 elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD 16519 }, 16520 16521 /** 16522 * Destroy's the DataTable body THEAD element, if available. 16523 * 16524 * @method _destroyBdTheadEl 16525 * @private 16526 */ 16527 _destroyBdTheadEl : function() { 16528 var elBdThead = this._elBdThead; 16529 if(elBdThead) { 16530 var elTable = elBdThead.parentNode; 16531 Ev.purgeElement(elBdThead, true); 16532 elTable.removeChild(elBdThead); 16533 this._elBdThead = null; 16534 16535 this._destroyColumnHelpers(); 16536 } 16537 }, 16538 16539 /** 16540 * Initializes body THEAD element. 16541 * 16542 * @method _initBdTheadEl 16543 * @param elTable {HTMLElement} TABLE element into which to create THEAD. 16544 * @return {HTMLElement} Initialized THEAD element. 16545 * @private 16546 */ 16547 _initBdTheadEl : function(elTable) { 16548 if(elTable) { 16549 // Destroy previous 16550 this._destroyBdTheadEl(); 16551 16552 var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild); 16553 16554 // Add TRs to the THEAD; 16555 var oColumnSet = this._oColumnSet, 16556 colTree = oColumnSet.tree, 16557 elTh, elTheadTr, oColumn, i, j, k, len; 16558 16559 for(i=0, k=colTree.length; i<k; i++) { 16560 elTheadTr = elThead.appendChild(document.createElement("tr")); 16561 16562 // ...and create TH cells 16563 for(j=0, len=colTree[i].length; j<len; j++) { 16564 oColumn = colTree[i][j]; 16565 elTh = elTheadTr.appendChild(document.createElement("th")); 16566 this._initBdThEl(elTh,oColumn,i,j); 16567 } 16568 } 16569 this._elBdThead = elThead; 16570 YAHOO.log("Accessibility TH cells for " + this._oColumnSet.keys.length + " keys created","info",this.toString()); 16571 } 16572 }, 16573 16574 /** 16575 * Populates TH element for the body THEAD element. 16576 * 16577 * @method _initBdThEl 16578 * @param elTh {HTMLElement} TH element reference. 16579 * @param oColumn {YAHOO.widget.Column} Column object. 16580 * @private 16581 */ 16582 _initBdThEl : function(elTh, oColumn) { 16583 elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility 16584 elTh.rowSpan = oColumn.getRowspan(); 16585 elTh.colSpan = oColumn.getColspan(); 16586 // Assign abbr attribute 16587 if(oColumn.abbr) { 16588 elTh.abbr = oColumn.abbr; 16589 } 16590 16591 // TODO: strip links and form elements 16592 var sKey = oColumn.getKey(); 16593 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey; 16594 elTh.innerHTML = sLabel; 16595 }, 16596 16597 /** 16598 * Initializes ScrollingDataTable TBODY element for data 16599 * 16600 * @method _initTbodyEl 16601 * @param elTable {HTMLElement} TABLE element into which to create TBODY . 16602 * @private 16603 */ 16604 _initTbodyEl : function(elTable) { 16605 SDT.superclass._initTbodyEl.call(this, elTable); 16606 16607 // Bug 2105534 - Safari 3 gap 16608 // Bug 2492591 - IE8 offsetTop 16609 elTable.style.marginTop = (this._elTbody.offsetTop > 0) ? 16610 "-"+this._elTbody.offsetTop+"px" : 0; 16611 }, 16612 16613 16614 16615 16616 16617 16618 16619 16620 16621 16622 16623 16624 16625 16626 16627 16628 16629 16630 16631 16632 16633 16634 16635 16636 16637 16638 16639 16640 16641 /** 16642 * Sets focus on the given element. 16643 * 16644 * @method _focusEl 16645 * @param el {HTMLElement} Element. 16646 * @private 16647 */ 16648 _focusEl : function(el) { 16649 el = el || this._elTbody; 16650 var oSelf = this; 16651 this._storeScrollPositions(); 16652 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets 16653 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing 16654 // strange unexpected things as the user clicks on buttons and other controls. 16655 16656 // Bug 1921135: Wrap the whole thing in a setTimeout 16657 setTimeout(function() { 16658 setTimeout(function() { 16659 try { 16660 el.focus(); 16661 oSelf._restoreScrollPositions(); 16662 } 16663 catch(e) { 16664 } 16665 },0); 16666 }, 0); 16667 }, 16668 16669 16670 16671 16672 16673 16674 16675 16676 16677 16678 16679 16680 16681 16682 16683 16684 16685 16686 16687 /** 16688 * Internal wrapper calls run() on render Chain instance. 16689 * 16690 * @method _runRenderChain 16691 * @private 16692 */ 16693 _runRenderChain : function() { 16694 this._storeScrollPositions(); 16695 this._oChainRender.run(); 16696 }, 16697 16698 /** 16699 * Stores scroll positions so they can be restored after a render. 16700 * 16701 * @method _storeScrollPositions 16702 * @private 16703 */ 16704 _storeScrollPositions : function() { 16705 this._nScrollTop = this._elBdContainer.scrollTop; 16706 this._nScrollLeft = this._elBdContainer.scrollLeft; 16707 }, 16708 16709 /** 16710 * Clears stored scroll positions to interrupt the automatic restore mechanism. 16711 * Useful for setting scroll positions programmatically rather than as part of 16712 * the post-render cleanup process. 16713 * 16714 * @method clearScrollPositions 16715 * @private 16716 */ 16717 clearScrollPositions : function() { 16718 this._nScrollTop = 0; 16719 this._nScrollLeft = 0; 16720 }, 16721 16722 /** 16723 * Restores scroll positions to stored value. 16724 * 16725 * @method _retoreScrollPositions 16726 * @private 16727 */ 16728 _restoreScrollPositions : function() { 16729 // Reset scroll positions 16730 if(this._nScrollTop) { 16731 this._elBdContainer.scrollTop = this._nScrollTop; 16732 this._nScrollTop = null; 16733 } 16734 if(this._nScrollLeft) { 16735 this._elBdContainer.scrollLeft = this._nScrollLeft; 16736 // Bug 2529024 16737 this._elHdContainer.scrollLeft = this._nScrollLeft; 16738 this._nScrollLeft = null; 16739 } 16740 }, 16741 16742 /** 16743 * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable. 16744 * 16745 * @method _validateColumnWidth 16746 * @param oColumn {YAHOO.widget.Column} Column instance. 16747 * @param elTd {HTMLElement} TD element to validate against. 16748 * @private 16749 */ 16750 _validateColumnWidth : function(oColumn, elTd) { 16751 // Only Columns without widths that are not hidden 16752 if(!oColumn.width && !oColumn.hidden) { 16753 var elTh = oColumn.getThEl(); 16754 // Unset a calculated auto-width 16755 if(oColumn._calculatedWidth) { 16756 this._setColumnWidth(oColumn, "auto", "visible"); 16757 } 16758 // Compare auto-widths 16759 if(elTh.offsetWidth !== elTd.offsetWidth) { 16760 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ? 16761 oColumn.getThLinerEl() : elTd.firstChild; 16762 16763 // Grab the wider liner width, unless the minWidth is wider 16764 var newWidth = Math.max(0, 16765 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)), 16766 oColumn.minWidth); 16767 16768 var sOverflow = 'visible'; 16769 16770 // Now validate against maxAutoWidth 16771 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) { 16772 newWidth = oColumn.maxAutoWidth; 16773 sOverflow = "hidden"; 16774 } 16775 16776 // Set to the wider auto-width 16777 this._elTbody.style.display = "none"; 16778 this._setColumnWidth(oColumn, newWidth+'px', sOverflow); 16779 oColumn._calculatedWidth = newWidth; 16780 this._elTbody.style.display = ""; 16781 } 16782 } 16783 }, 16784 16785 /** 16786 * For one or all Columns of a ScrollingDataTable, when Column is not hidden, 16787 * and width is not set, syncs widths of header and body cells and 16788 * validates that width against minWidth and/or maxAutoWidth as necessary. 16789 * 16790 * @method validateColumnWidths 16791 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated. 16792 */ 16793 validateColumnWidths : function(oColumn) { 16794 // Validate there is at least one TR with proper TDs 16795 var allKeys = this._oColumnSet.keys, 16796 allKeysLength = allKeys.length, 16797 elRow = this.getFirstTrEl(); 16798 16799 // Reset overhang for IE 16800 if(ua.ie) { 16801 this._setOverhangValue(1); 16802 } 16803 16804 if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) { 16805 // Temporarily unsnap container since it causes inaccurate calculations 16806 var sWidth = this.get("width"); 16807 if(sWidth) { 16808 this._elHdContainer.style.width = ""; 16809 this._elBdContainer.style.width = ""; 16810 } 16811 this._elContainer.style.width = ""; 16812 16813 //Validate just one Column 16814 if(oColumn && lang.isNumber(oColumn.getKeyIndex())) { 16815 this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]); 16816 } 16817 // Iterate through all Columns to unset calculated widths in one pass 16818 else { 16819 var elTd, todos = [], thisTodo, i, len; 16820 for(i=0; i<allKeysLength; i++) { 16821 oColumn = allKeys[i]; 16822 // Only Columns without widths that are not hidden, unset a calculated auto-width 16823 if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) { 16824 todos[todos.length] = oColumn; 16825 } 16826 } 16827 16828 this._elTbody.style.display = "none"; 16829 for(i=0, len=todos.length; i<len; i++) { 16830 this._setColumnWidth(todos[i], "auto", "visible"); 16831 } 16832 this._elTbody.style.display = ""; 16833 16834 todos = []; 16835 16836 // Iterate through all Columns and make the store the adjustments to make in one pass 16837 for(i=0; i<allKeysLength; i++) { 16838 oColumn = allKeys[i]; 16839 elTd = elRow.childNodes[i]; 16840 // Only Columns without widths that are not hidden 16841 if(!oColumn.width && !oColumn.hidden) { 16842 var elTh = oColumn.getThEl(); 16843 16844 // Compare auto-widths 16845 if(elTh.offsetWidth !== elTd.offsetWidth) { 16846 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ? 16847 oColumn.getThLinerEl() : elTd.firstChild; 16848 16849 // Grab the wider liner width, unless the minWidth is wider 16850 var newWidth = Math.max(0, 16851 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)), 16852 oColumn.minWidth); 16853 16854 var sOverflow = 'visible'; 16855 16856 // Now validate against maxAutoWidth 16857 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) { 16858 newWidth = oColumn.maxAutoWidth; 16859 sOverflow = "hidden"; 16860 } 16861 16862 todos[todos.length] = [oColumn, newWidth, sOverflow]; 16863 } 16864 } 16865 } 16866 16867 this._elTbody.style.display = "none"; 16868 for(i=0, len=todos.length; i<len; i++) { 16869 thisTodo = todos[i]; 16870 // Set to the wider auto-width 16871 this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]); 16872 thisTodo[0]._calculatedWidth = thisTodo[1]; 16873 } 16874 this._elTbody.style.display = ""; 16875 } 16876 16877 // Resnap unsnapped containers 16878 if(sWidth) { 16879 this._elHdContainer.style.width = sWidth; 16880 this._elBdContainer.style.width = sWidth; 16881 } 16882 } 16883 16884 this._syncScroll(); 16885 this._restoreScrollPositions(); 16886 }, 16887 16888 /** 16889 * Syncs padding around scrollable tables, including Column header right-padding 16890 * and container width and height. 16891 * 16892 * @method _syncScroll 16893 * @private 16894 */ 16895 _syncScroll : function() { 16896 this._syncScrollX(); 16897 this._syncScrollY(); 16898 this._syncScrollOverhang(); 16899 if(ua.opera) { 16900 // Bug 1925874 16901 this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft; 16902 if(!this.get("width")) { 16903 // Bug 1926125 16904 document.body.style += ''; 16905 } 16906 } 16907 }, 16908 16909 /** 16910 * Snaps container width for y-scrolling tables. 16911 * 16912 * @method _syncScrollY 16913 * @private 16914 */ 16915 _syncScrollY : function() { 16916 var elTbody = this._elTbody, 16917 elBdContainer = this._elBdContainer; 16918 16919 // X-scrolling not enabled 16920 if(!this.get("width")) { 16921 // Snap outer container width to content 16922 this._elContainer.style.width = 16923 (elBdContainer.scrollHeight > elBdContainer.clientHeight) ? 16924 // but account for y-scrollbar since it is visible 16925 (elTbody.parentNode.clientWidth + 19) + "px" : 16926 // no y-scrollbar, just borders 16927 (elTbody.parentNode.clientWidth + 2) + "px"; 16928 } 16929 }, 16930 16931 /** 16932 * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width. 16933 * 16934 * @method _syncScrollX 16935 * @private 16936 */ 16937 _syncScrollX : function() { 16938 var elTbody = this._elTbody, 16939 elBdContainer = this._elBdContainer; 16940 16941 // IE 6 and 7 only when y-scrolling not enabled 16942 if(!this.get("height") && (ua.ie)) { 16943 // Snap outer container height to content 16944 elBdContainer.style.height = 16945 // but account for x-scrollbar if it is visible 16946 (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ? 16947 (elTbody.parentNode.offsetHeight + 18) + "px" : 16948 elTbody.parentNode.offsetHeight + "px"; 16949 } 16950 16951 // Sync message tbody 16952 if(this._elTbody.rows.length === 0) { 16953 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px"; 16954 } 16955 else { 16956 this._elMsgTbody.parentNode.style.width = ""; 16957 } 16958 }, 16959 16960 /** 16961 * Adds/removes Column header overhang as necesary. 16962 * 16963 * @method _syncScrollOverhang 16964 * @private 16965 */ 16966 _syncScrollOverhang : function() { 16967 var elBdContainer = this._elBdContainer, 16968 // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table 16969 nPadding = 1; 16970 16971 // Y-scrollbar is visible, which is when the overhang needs to jut out 16972 if((elBdContainer.scrollHeight > elBdContainer.clientHeight) && 16973 // X-scrollbar is also visible, which means the right is jagged, not flush with the Column 16974 (elBdContainer.scrollWidth > elBdContainer.clientWidth)) { 16975 nPadding = 18; 16976 } 16977 16978 this._setOverhangValue(nPadding); 16979 16980 }, 16981 16982 /** 16983 * Sets Column header overhang to given width. 16984 * 16985 * @method _setOverhangValue 16986 * @param nBorderWidth {Number} Value of new border for overhang. 16987 * @private 16988 */ 16989 _setOverhangValue : function(nBorderWidth) { 16990 var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [], 16991 len = aLastHeaders.length, 16992 sPrefix = this._sId+"-fixedth-", 16993 sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER"); 16994 16995 this._elThead.style.display = "none"; 16996 for(var i=0; i<len; i++) { 16997 Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue; 16998 } 16999 this._elThead.style.display = ""; 17000 }, 17001 17002 17003 17004 17005 17006 17007 17008 17009 17010 17011 17012 17013 17014 17015 17016 17017 17018 17019 17020 17021 17022 17023 17024 17025 17026 17027 17028 17029 17030 17031 17032 17033 17034 17035 17036 17037 17038 17039 /** 17040 * Returns DOM reference to the DataTable's fixed header container element. 17041 * 17042 * @method getHdContainerEl 17043 * @return {HTMLElement} Reference to DIV element. 17044 */ 17045 getHdContainerEl : function() { 17046 return this._elHdContainer; 17047 }, 17048 17049 /** 17050 * Returns DOM reference to the DataTable's scrolling body container element. 17051 * 17052 * @method getBdContainerEl 17053 * @return {HTMLElement} Reference to DIV element. 17054 */ 17055 getBdContainerEl : function() { 17056 return this._elBdContainer; 17057 }, 17058 17059 /** 17060 * Returns DOM reference to the DataTable's fixed header TABLE element. 17061 * 17062 * @method getHdTableEl 17063 * @return {HTMLElement} Reference to TABLE element. 17064 */ 17065 getHdTableEl : function() { 17066 return this._elHdTable; 17067 }, 17068 17069 /** 17070 * Returns DOM reference to the DataTable's scrolling body TABLE element. 17071 * 17072 * @method getBdTableEl 17073 * @return {HTMLElement} Reference to TABLE element. 17074 */ 17075 getBdTableEl : function() { 17076 return this._elTable; 17077 }, 17078 17079 /** 17080 * Disables ScrollingDataTable UI. 17081 * 17082 * @method disable 17083 */ 17084 disable : function() { 17085 var elMask = this._elMask; 17086 elMask.style.width = this._elBdContainer.offsetWidth + "px"; 17087 elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px"; 17088 elMask.style.display = ""; 17089 this.fireEvent("disableEvent"); 17090 }, 17091 17092 /** 17093 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove 17094 * non-nested Columns, and top-level parent Columns (which will remove all 17095 * children Columns). 17096 * 17097 * @method removeColumn 17098 * @param oColumn {YAHOO.widget.Column} Column instance. 17099 * @return oColumn {YAHOO.widget.Column} Removed Column instance. 17100 */ 17101 removeColumn : function(oColumn) { 17102 // Store scroll pos 17103 var hdPos = this._elHdContainer.scrollLeft; 17104 var bdPos = this._elBdContainer.scrollLeft; 17105 17106 // Call superclass method 17107 oColumn = SDT.superclass.removeColumn.call(this, oColumn); 17108 17109 // Restore scroll pos 17110 this._elHdContainer.scrollLeft = hdPos; 17111 this._elBdContainer.scrollLeft = bdPos; 17112 17113 return oColumn; 17114 }, 17115 17116 /** 17117 * Inserts given Column at the index if given, otherwise at the end. NOTE: You 17118 * can only add non-nested Columns and top-level parent Columns. You cannot add 17119 * a nested Column to an existing parent. 17120 * 17121 * @method insertColumn 17122 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column 17123 * definition or a Column instance. 17124 * @param index {Number} (optional) New tree index. 17125 * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 17126 */ 17127 insertColumn : function(oColumn, index) { 17128 // Store scroll pos 17129 var hdPos = this._elHdContainer.scrollLeft; 17130 var bdPos = this._elBdContainer.scrollLeft; 17131 17132 // Call superclass method 17133 var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index); 17134 17135 // Restore scroll pos 17136 this._elHdContainer.scrollLeft = hdPos; 17137 this._elBdContainer.scrollLeft = bdPos; 17138 17139 return oNewColumn; 17140 }, 17141 17142 /** 17143 * Removes given Column and inserts into given tree index. NOTE: You 17144 * can only reorder non-nested Columns and top-level parent Columns. You cannot 17145 * reorder a nested Column to an existing parent. 17146 * 17147 * @method reorderColumn 17148 * @param oColumn {YAHOO.widget.Column} Column instance. 17149 * @param index {Number} New tree index. 17150 */ 17151 reorderColumn : function(oColumn, index) { 17152 // Store scroll pos 17153 var hdPos = this._elHdContainer.scrollLeft; 17154 var bdPos = this._elBdContainer.scrollLeft; 17155 17156 // Call superclass method 17157 var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index); 17158 17159 // Restore scroll pos 17160 this._elHdContainer.scrollLeft = hdPos; 17161 this._elBdContainer.scrollLeft = bdPos; 17162 17163 return oNewColumn; 17164 }, 17165 17166 /** 17167 * Sets given Column to given pixel width. If new width is less than minWidth 17168 * width, sets to minWidth. Updates oColumn.width value. 17169 * 17170 * @method setColumnWidth 17171 * @param oColumn {YAHOO.widget.Column} Column instance. 17172 * @param nWidth {Number} New width in pixels. 17173 */ 17174 setColumnWidth : function(oColumn, nWidth) { 17175 oColumn = this.getColumn(oColumn); 17176 if(oColumn) { 17177 this._storeScrollPositions(); 17178 17179 // Validate new width against minWidth 17180 if(lang.isNumber(nWidth)) { 17181 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth; 17182 17183 // Save state 17184 oColumn.width = nWidth; 17185 17186 // Resize the DOM elements 17187 this._setColumnWidth(oColumn, nWidth+"px"); 17188 this._syncScroll(); 17189 17190 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth}); 17191 YAHOO.log("Set width of Column " + oColumn + " to " + nWidth + "px", "info", this.toString()); 17192 } 17193 // Unsets a width to auto-size 17194 else if(nWidth === null) { 17195 // Save state 17196 oColumn.width = nWidth; 17197 17198 // Resize the DOM elements 17199 this._setColumnWidth(oColumn, "auto"); 17200 this.validateColumnWidths(oColumn); 17201 this.fireEvent("columnUnsetWidthEvent",{column:oColumn}); 17202 YAHOO.log("Column " + oColumn + " width unset", "info", this.toString()); 17203 } 17204 17205 // Bug 2339454: resize then sort misaligment 17206 this._clearTrTemplateEl(); 17207 } 17208 else { 17209 YAHOO.log("Could not set width of Column " + oColumn + " to " + nWidth + "px", "warn", this.toString()); 17210 } 17211 }, 17212 17213 /** 17214 * Scrolls to given row or cell 17215 * 17216 * @method scrollTo 17217 * @param to {YAHOO.widget.Record | HTMLElement } Itme to scroll to. 17218 */ 17219 scrollTo : function(to) { 17220 var td = this.getTdEl(to); 17221 if(td) { 17222 this.clearScrollPositions(); 17223 this.getBdContainerEl().scrollLeft = td.offsetLeft; 17224 this.getBdContainerEl().scrollTop = td.parentNode.offsetTop; 17225 } 17226 else { 17227 var tr = this.getTrEl(to); 17228 if(tr) { 17229 this.clearScrollPositions(); 17230 this.getBdContainerEl().scrollTop = tr.offsetTop; 17231 } 17232 } 17233 }, 17234 17235 /** 17236 * Displays message within secondary TBODY. 17237 * 17238 * @method showTableMessage 17239 * @param sHTML {String} (optional) Value for innerHTMlang. 17240 * @param sClassName {String} (optional) Classname. 17241 */ 17242 showTableMessage : function(sHTML, sClassName) { 17243 var elCell = this._elMsgTd; 17244 if(lang.isString(sHTML)) { 17245 elCell.firstChild.innerHTML = sHTML; 17246 } 17247 if(lang.isString(sClassName)) { 17248 Dom.addClass(elCell.firstChild, sClassName); 17249 } 17250 17251 // Needed for SDT only 17252 var elThead = this.getTheadEl(); 17253 var elTable = elThead.parentNode; 17254 var newWidth = elTable.offsetWidth; 17255 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px"; 17256 17257 this._elMsgTbody.style.display = ""; 17258 17259 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName}); 17260 YAHOO.log("DataTable showing message: " + sHTML, "info", this.toString()); 17261 }, 17262 17263 17264 17265 17266 17267 17268 17269 17270 17271 17272 17273 17274 17275 ///////////////////////////////////////////////////////////////////////////// 17276 // 17277 // Private Custom Event Handlers 17278 // 17279 ///////////////////////////////////////////////////////////////////////////// 17280 17281 /** 17282 * Handles Column mutations 17283 * 17284 * @method onColumnChange 17285 * @param oArgs {Object} Custom Event data. 17286 */ 17287 _onColumnChange : function(oArg) { 17288 // Figure out which Column changed 17289 var oColumn = (oArg.column) ? oArg.column : 17290 (oArg.editor) ? oArg.editor.column : null; 17291 this._storeScrollPositions(); 17292 this.validateColumnWidths(oColumn); 17293 }, 17294 17295 17296 17297 17298 17299 17300 17301 17302 17303 17304 17305 17306 17307 17308 17309 ///////////////////////////////////////////////////////////////////////////// 17310 // 17311 // Private DOM Event Handlers 17312 // 17313 ///////////////////////////////////////////////////////////////////////////// 17314 17315 /** 17316 * Syncs scrolltop and scrollleft of all TABLEs. 17317 * 17318 * @method _onScroll 17319 * @param e {HTMLEvent} The scroll event. 17320 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance. 17321 * @private 17322 */ 17323 _onScroll : function(e, oSelf) { 17324 oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft; 17325 17326 if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) { 17327 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor}); 17328 oSelf.cancelCellEditor(); 17329 } 17330 17331 var elTarget = Ev.getTarget(e); 17332 var elTag = elTarget.nodeName.toLowerCase(); 17333 oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget}); 17334 }, 17335 17336 /** 17337 * Handles keydown events on the THEAD element. 17338 * 17339 * @method _onTheadKeydown 17340 * @param e {HTMLEvent} The key event. 17341 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance. 17342 * @private 17343 */ 17344 _onTheadKeydown : function(e, oSelf) { 17345 // If tabbing to next TH label link causes THEAD to scroll, 17346 // need to sync scrollLeft with TBODY 17347 if(Ev.getCharCode(e) === 9) { 17348 setTimeout(function() { 17349 if((oSelf instanceof SDT) && oSelf._sId) { 17350 oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft; 17351 } 17352 },0); 17353 } 17354 17355 var elTarget = Ev.getTarget(e); 17356 var elTag = elTarget.nodeName.toLowerCase(); 17357 var bKeepBubbling = true; 17358 while(elTarget && (elTag != "table")) { 17359 switch(elTag) { 17360 case "body": 17361 return; 17362 case "input": 17363 case "textarea": 17364 // TODO: implement textareaKeyEvent 17365 break; 17366 case "thead": 17367 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e}); 17368 break; 17369 default: 17370 break; 17371 } 17372 if(bKeepBubbling === false) { 17373 return; 17374 } 17375 else { 17376 elTarget = elTarget.parentNode; 17377 if(elTarget) { 17378 elTag = elTarget.nodeName.toLowerCase(); 17379 } 17380 } 17381 } 17382 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e}); 17383 } 17384 17385 17386 17387 17388 /** 17389 * Fired when a fixed scrolling DataTable has a scroll. 17390 * 17391 * @event tableScrollEvent 17392 * @param oArgs.event {HTMLEvent} The event object. 17393 * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE) 17394 * or the DataTable's TBODY element (everyone else). 17395 * 17396 */ 17397 17398 17399 17400 17401 }); 17402 17403 })(); 17404 17405 (function () { 17406 17407 var lang = YAHOO.lang, 17408 util = YAHOO.util, 17409 widget = YAHOO.widget, 17410 ua = YAHOO.env.ua, 17411 17412 Dom = util.Dom, 17413 Ev = util.Event, 17414 17415 DT = widget.DataTable; 17416 /****************************************************************************/ 17417 /****************************************************************************/ 17418 /****************************************************************************/ 17419 17420 /** 17421 * The BaseCellEditor class provides base functionality common to all inline cell 17422 * editors for a DataTable widget. 17423 * 17424 * @namespace YAHOO.widget 17425 * @class BaseCellEditor 17426 * @uses YAHOO.util.EventProvider 17427 * @constructor 17428 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors. 17429 * @param oConfigs {Object} (Optional) Object literal of configs. 17430 */ 17431 widget.BaseCellEditor = function(sType, oConfigs) { 17432 this._sId = this._sId || Dom.generateId(null, "yui-ceditor"); // "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++; 17433 YAHOO.widget.BaseCellEditor._nCount++; 17434 this._sType = sType; 17435 17436 // Validate inputs 17437 this._initConfigs(oConfigs); 17438 17439 // Create Custom Events 17440 this._initEvents(); 17441 17442 // UI needs to be drawn 17443 this._needsRender = true; 17444 }; 17445 17446 var BCE = widget.BaseCellEditor; 17447 17448 ///////////////////////////////////////////////////////////////////////////// 17449 // 17450 // Static members 17451 // 17452 ///////////////////////////////////////////////////////////////////////////// 17453 lang.augmentObject(BCE, { 17454 17455 /** 17456 * Global instance counter. 17457 * 17458 * @property CellEditor._nCount 17459 * @type Number 17460 * @static 17461 * @default 0 17462 * @private 17463 */ 17464 _nCount : 0, 17465 17466 /** 17467 * Class applied to CellEditor container. 17468 * 17469 * @property CellEditor.CLASS_CELLEDITOR 17470 * @type String 17471 * @static 17472 * @default "yui-ceditor" 17473 */ 17474 CLASS_CELLEDITOR : "yui-ceditor" 17475 17476 }); 17477 17478 BCE.prototype = { 17479 ///////////////////////////////////////////////////////////////////////////// 17480 // 17481 // Private members 17482 // 17483 ///////////////////////////////////////////////////////////////////////////// 17484 /** 17485 * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique 17486 * DOM ID strings and log messages. 17487 * 17488 * @property _sId 17489 * @type String 17490 * @private 17491 */ 17492 _sId : null, 17493 17494 /** 17495 * Editor type. 17496 * 17497 * @property _sType 17498 * @type String 17499 * @private 17500 */ 17501 _sType : null, 17502 17503 /** 17504 * DataTable instance. 17505 * 17506 * @property _oDataTable 17507 * @type YAHOO.widget.DataTable 17508 * @private 17509 */ 17510 _oDataTable : null, 17511 17512 /** 17513 * Column instance. 17514 * 17515 * @property _oColumn 17516 * @type YAHOO.widget.Column 17517 * @default null 17518 * @private 17519 */ 17520 _oColumn : null, 17521 17522 /** 17523 * Record instance. 17524 * 17525 * @property _oRecord 17526 * @type YAHOO.widget.Record 17527 * @default null 17528 * @private 17529 */ 17530 _oRecord : null, 17531 17532 /** 17533 * TD element. 17534 * 17535 * @property _elTd 17536 * @type HTMLElement 17537 * @default null 17538 * @private 17539 */ 17540 _elTd : null, 17541 17542 /** 17543 * Container for inline editor. 17544 * 17545 * @property _elContainer 17546 * @type HTMLElement 17547 * @private 17548 */ 17549 _elContainer : null, 17550 17551 /** 17552 * Reference to Cancel button, if available. 17553 * 17554 * @property _elCancelBtn 17555 * @type HTMLElement 17556 * @default null 17557 * @private 17558 */ 17559 _elCancelBtn : null, 17560 17561 /** 17562 * Reference to Save button, if available. 17563 * 17564 * @property _elSaveBtn 17565 * @type HTMLElement 17566 * @default null 17567 * @private 17568 */ 17569 _elSaveBtn : null, 17570 17571 17572 17573 17574 17575 17576 17577 17578 ///////////////////////////////////////////////////////////////////////////// 17579 // 17580 // Private methods 17581 // 17582 ///////////////////////////////////////////////////////////////////////////// 17583 17584 /** 17585 * Initialize configs. 17586 * 17587 * @method _initConfigs 17588 * @private 17589 */ 17590 _initConfigs : function(oConfigs) { 17591 // Object literal defines CellEditor configs 17592 if(oConfigs && YAHOO.lang.isObject(oConfigs)) { 17593 for(var sConfig in oConfigs) { 17594 if(sConfig) { 17595 this[sConfig] = oConfigs[sConfig]; 17596 } 17597 } 17598 } 17599 }, 17600 17601 /** 17602 * Initialize Custom Events. 17603 * 17604 * @method _initEvents 17605 * @private 17606 */ 17607 _initEvents : function() { 17608 this.createEvent("showEvent"); 17609 this.createEvent("keydownEvent"); 17610 this.createEvent("invalidDataEvent"); 17611 this.createEvent("revertEvent"); 17612 this.createEvent("saveEvent"); 17613 this.createEvent("cancelEvent"); 17614 this.createEvent("blurEvent"); 17615 this.createEvent("blockEvent"); 17616 this.createEvent("unblockEvent"); 17617 }, 17618 17619 /** 17620 * Initialize container element. 17621 * 17622 * @method _initContainerEl 17623 * @private 17624 */ 17625 _initContainerEl : function() { 17626 if(this._elContainer) { 17627 YAHOO.util.Event.purgeElement(this._elContainer, true); 17628 this._elContainer.innerHTML = ""; 17629 } 17630 17631 var elContainer = document.createElement("div"); 17632 elContainer.id = this.getId() + "-container"; // Needed for tracking blur event 17633 elContainer.style.display = "none"; 17634 elContainer.tabIndex = 0; 17635 17636 this.className = lang.isArray(this.className) ? this.className : this.className ? [this.className] : []; 17637 this.className[this.className.length] = DT.CLASS_EDITOR; 17638 elContainer.className = this.className.join(" "); 17639 17640 document.body.insertBefore(elContainer, document.body.firstChild); 17641 this._elContainer = elContainer; 17642 }, 17643 17644 /** 17645 * Initialize container shim element. 17646 * 17647 * @method _initShimEl 17648 * @private 17649 */ 17650 _initShimEl : function() { 17651 // Iframe shim 17652 if(this.useIFrame) { 17653 if(!this._elIFrame) { 17654 var elIFrame = document.createElement("iframe"); 17655 elIFrame.src = "javascript:false"; 17656 elIFrame.frameBorder = 0; 17657 elIFrame.scrolling = "no"; 17658 elIFrame.style.display = "none"; 17659 elIFrame.className = DT.CLASS_EDITOR_SHIM; 17660 elIFrame.tabIndex = -1; 17661 elIFrame.role = "presentation"; 17662 elIFrame.title = "Presentational iframe shim"; 17663 document.body.insertBefore(elIFrame, document.body.firstChild); 17664 this._elIFrame = elIFrame; 17665 } 17666 } 17667 }, 17668 17669 /** 17670 * Hides CellEditor UI at end of interaction. 17671 * 17672 * @method _hide 17673 */ 17674 _hide : function() { 17675 this.getContainerEl().style.display = "none"; 17676 if(this._elIFrame) { 17677 this._elIFrame.style.display = "none"; 17678 } 17679 this.isActive = false; 17680 this.getDataTable()._oCellEditor = null; 17681 }, 17682 17683 17684 17685 17686 17687 17688 17689 17690 17691 17692 17693 ///////////////////////////////////////////////////////////////////////////// 17694 // 17695 // Public properties 17696 // 17697 ///////////////////////////////////////////////////////////////////////////// 17698 /** 17699 * Implementer defined function that can submit the input value to a server. This 17700 * function must accept the arguments fnCallback and oNewValue. When the submission 17701 * is complete, the function must also call fnCallback(bSuccess, oNewValue) to 17702 * finish the save routine in the CellEditor. This function can also be used to 17703 * perform extra validation or input value manipulation. 17704 * 17705 * @property asyncSubmitter 17706 * @type HTMLFunction 17707 */ 17708 asyncSubmitter : null, 17709 17710 /** 17711 * Current value. 17712 * 17713 * @property value 17714 * @type MIXED 17715 */ 17716 value : null, 17717 17718 /** 17719 * Default value in case Record data is undefined. NB: Null values will not trigger 17720 * the default value. 17721 * 17722 * @property defaultValue 17723 * @type MIXED 17724 * @default null 17725 */ 17726 defaultValue : null, 17727 17728 /** 17729 * Validator function for input data, called from the DataTable instance scope, 17730 * receives the arguments (inputValue, currentValue, editorInstance) and returns 17731 * either the validated (or type-converted) value or undefined. 17732 * 17733 * @property validator 17734 * @type HTMLFunction 17735 * @default null 17736 */ 17737 validator : null, 17738 17739 /** 17740 * If validation is enabled, resets input field of invalid data. 17741 * 17742 * @property resetInvalidData 17743 * @type Boolean 17744 * @default true 17745 */ 17746 resetInvalidData : true, 17747 17748 /** 17749 * True if currently active. 17750 * 17751 * @property isActive 17752 * @type Boolean 17753 */ 17754 isActive : false, 17755 17756 /** 17757 * Text to display on Save button. 17758 * 17759 * @property LABEL_SAVE 17760 * @type HTML 17761 * @default "Save" 17762 */ 17763 LABEL_SAVE : "Save", 17764 17765 /** 17766 * Text to display on Cancel button. 17767 * 17768 * @property LABEL_CANCEL 17769 * @type HTML 17770 * @default "Cancel" 17771 */ 17772 LABEL_CANCEL : "Cancel", 17773 17774 /** 17775 * True if Save/Cancel buttons should not be displayed in the CellEditor. 17776 * 17777 * @property disableBtns 17778 * @type Boolean 17779 * @default false 17780 */ 17781 disableBtns : false, 17782 17783 /** 17784 * True if iframe shim for container element should be enabled. 17785 * 17786 * @property useIFrame 17787 * @type Boolean 17788 * @default false 17789 */ 17790 useIFrame : false, 17791 17792 /** 17793 * Custom CSS class or array of classes applied to the container element. 17794 * 17795 * @property className 17796 * @type String || String[] 17797 */ 17798 className : null, 17799 17800 17801 17802 17803 17804 ///////////////////////////////////////////////////////////////////////////// 17805 // 17806 // Public methods 17807 // 17808 ///////////////////////////////////////////////////////////////////////////// 17809 /** 17810 * CellEditor instance name, for logging. 17811 * 17812 * @method toString 17813 * @return {String} Unique name of the CellEditor instance. 17814 */ 17815 17816 toString : function() { 17817 return "CellEditor instance " + this._sId; 17818 }, 17819 17820 /** 17821 * CellEditor unique ID. 17822 * 17823 * @method getId 17824 * @return {String} Unique ID of the CellEditor instance. 17825 */ 17826 17827 getId : function() { 17828 return this._sId; 17829 }, 17830 17831 /** 17832 * Returns reference to associated DataTable instance. 17833 * 17834 * @method getDataTable 17835 * @return {YAHOO.widget.DataTable} DataTable instance. 17836 */ 17837 17838 getDataTable : function() { 17839 return this._oDataTable; 17840 }, 17841 17842 /** 17843 * Returns reference to associated Column instance. 17844 * 17845 * @method getColumn 17846 * @return {YAHOO.widget.Column} Column instance. 17847 */ 17848 17849 getColumn : function() { 17850 return this._oColumn; 17851 }, 17852 17853 /** 17854 * Returns reference to associated Record instance. 17855 * 17856 * @method getRecord 17857 * @return {YAHOO.widget.Record} Record instance. 17858 */ 17859 17860 getRecord : function() { 17861 return this._oRecord; 17862 }, 17863 17864 17865 17866 /** 17867 * Returns reference to associated TD element. 17868 * 17869 * @method getTdEl 17870 * @return {HTMLElement} TD element. 17871 */ 17872 17873 getTdEl : function() { 17874 return this._elTd; 17875 }, 17876 17877 /** 17878 * Returns container element. 17879 * 17880 * @method getContainerEl 17881 * @return {HTMLElement} Reference to container element. 17882 */ 17883 17884 getContainerEl : function() { 17885 return this._elContainer; 17886 }, 17887 17888 /** 17889 * Nulls out the entire CellEditor instance and related objects, removes attached 17890 * event listeners, and clears out DOM elements inside the container, removes 17891 * container from the DOM. 17892 * 17893 * @method destroy 17894 */ 17895 destroy : function() { 17896 this.unsubscribeAll(); 17897 17898 // Column is late-binding in attach() 17899 var oColumn = this.getColumn(); 17900 if(oColumn) { 17901 oColumn.editor = null; 17902 } 17903 17904 var elContainer = this.getContainerEl(); 17905 if (elContainer) { 17906 Ev.purgeElement(elContainer, true); 17907 elContainer.parentNode.removeChild(elContainer); 17908 } 17909 }, 17910 17911 /** 17912 * Renders DOM elements and attaches event listeners. 17913 * 17914 * @method render 17915 */ 17916 render : function() { 17917 if (!this._needsRender) { 17918 return; 17919 } 17920 17921 this._initContainerEl(); 17922 this._initShimEl(); 17923 17924 // Handle ESC key 17925 Ev.addListener(this.getContainerEl(), "keydown", function(e, oSelf) { 17926 // ESC cancels Cell Editor 17927 if((e.keyCode == 27)) { 17928 var target = Ev.getTarget(e); 17929 // workaround for Mac FF3 bug that disabled clicks when ESC hit when 17930 // select is open. [bug 2273056] 17931 if (target.nodeName && target.nodeName.toLowerCase() === 'select') { 17932 target.blur(); 17933 } 17934 oSelf.cancel(); 17935 } 17936 // Pass through event 17937 oSelf.fireEvent("keydownEvent", {editor:oSelf, event:e}); 17938 }, this); 17939 17940 this.renderForm(); 17941 17942 // Show Save/Cancel buttons 17943 if(!this.disableBtns) { 17944 this.renderBtns(); 17945 } 17946 17947 this.doAfterRender(); 17948 this._needsRender = false; 17949 }, 17950 17951 /** 17952 * Renders Save/Cancel buttons. 17953 * 17954 * @method renderBtns 17955 */ 17956 renderBtns : function() { 17957 // Buttons 17958 var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div")); 17959 elBtnsDiv.className = DT.CLASS_BUTTON; 17960 17961 // Save button 17962 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button")); 17963 elSaveBtn.className = DT.CLASS_DEFAULT; 17964 elSaveBtn.innerHTML = this.LABEL_SAVE; 17965 Ev.addListener(elSaveBtn, "click", function(oArgs) { 17966 this.save(); 17967 }, this, true); 17968 this._elSaveBtn = elSaveBtn; 17969 17970 // Cancel button 17971 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button")); 17972 elCancelBtn.innerHTML = this.LABEL_CANCEL; 17973 Ev.addListener(elCancelBtn, "click", function(oArgs) { 17974 this.cancel(); 17975 }, this, true); 17976 this._elCancelBtn = elCancelBtn; 17977 }, 17978 17979 /** 17980 * Attach CellEditor for a new interaction. 17981 * 17982 * @method attach 17983 * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance. 17984 * @param elCell {HTMLElement} Cell to edit. 17985 */ 17986 attach : function(oDataTable, elCell) { 17987 // Validate 17988 if(oDataTable instanceof YAHOO.widget.DataTable) { 17989 this._oDataTable = oDataTable; 17990 17991 // Validate cell 17992 elCell = oDataTable.getTdEl(elCell); 17993 if(elCell) { 17994 this._elTd = elCell; 17995 17996 // Validate Column 17997 var oColumn = oDataTable.getColumn(elCell); 17998 if(oColumn) { 17999 this._oColumn = oColumn; 18000 18001 // Validate Record 18002 var oRecord = oDataTable.getRecord(elCell); 18003 if(oRecord) { 18004 this._oRecord = oRecord; 18005 var value = oRecord.getData(this.getColumn().getField()); 18006 this.value = (value !== undefined) ? value : this.defaultValue; 18007 return true; 18008 } 18009 } 18010 } 18011 } 18012 YAHOO.log("Could not attach CellEditor","error",this.toString()); 18013 return false; 18014 }, 18015 18016 /** 18017 * Moves container into position for display. 18018 * 18019 * @method move 18020 */ 18021 move : function() { 18022 // Move Editor 18023 var elContainer = this.getContainerEl(), 18024 elTd = this.getTdEl(), 18025 x = Dom.getX(elTd), 18026 y = Dom.getY(elTd); 18027 18028 //TODO: remove scrolling logic 18029 // SF doesn't get xy for cells in scrolling table 18030 // when tbody display is set to block 18031 if(isNaN(x) || isNaN(y)) { 18032 var elTbody = this.getDataTable().getTbodyEl(); 18033 x = elTd.offsetLeft + // cell pos relative to table 18034 Dom.getX(elTbody.parentNode) - // plus table pos relative to document 18035 elTbody.scrollLeft; // minus tbody scroll 18036 y = elTd.offsetTop + // cell pos relative to table 18037 Dom.getY(elTbody.parentNode) - // plus table pos relative to document 18038 elTbody.scrollTop + // minus tbody scroll 18039 this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells 18040 } 18041 18042 elContainer.style.left = x + "px"; 18043 elContainer.style.top = y + "px"; 18044 18045 if(this._elIFrame) { 18046 this._elIFrame.style.left = x + "px"; 18047 this._elIFrame.style.top = y + "px"; 18048 } 18049 }, 18050 18051 /** 18052 * Displays CellEditor UI in the correct position. 18053 * 18054 * @method show 18055 */ 18056 show : function() { 18057 var elContainer = this.getContainerEl(), 18058 elIFrame = this._elIFrame; 18059 this.resetForm(); 18060 this.isActive = true; 18061 elContainer.style.display = ""; 18062 if(elIFrame) { 18063 elIFrame.style.width = elContainer.offsetWidth + "px"; 18064 elIFrame.style.height = elContainer.offsetHeight + "px"; 18065 elIFrame.style.display = ""; 18066 } 18067 this.focus(); 18068 this.fireEvent("showEvent", {editor:this}); 18069 YAHOO.log("CellEditor shown", "info", this.toString()); 18070 }, 18071 18072 /** 18073 * Fires blockEvent 18074 * 18075 * @method block 18076 */ 18077 block : function() { 18078 this.fireEvent("blockEvent", {editor:this}); 18079 YAHOO.log("CellEditor blocked", "info", this.toString()); 18080 }, 18081 18082 /** 18083 * Fires unblockEvent 18084 * 18085 * @method unblock 18086 */ 18087 unblock : function() { 18088 this.fireEvent("unblockEvent", {editor:this}); 18089 YAHOO.log("CellEditor unblocked", "info", this.toString()); 18090 }, 18091 18092 /** 18093 * Saves value of CellEditor and hides UI. 18094 * 18095 * @method save 18096 */ 18097 save : function() { 18098 // Get new value 18099 var inputValue = this.getInputValue(); 18100 var validValue = inputValue; 18101 18102 // Validate new value 18103 if(this.validator) { 18104 validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this); 18105 if(validValue === undefined ) { 18106 if(this.resetInvalidData) { 18107 this.resetForm(); 18108 } 18109 this.fireEvent("invalidDataEvent", 18110 {editor:this, oldData:this.value, newData:inputValue}); 18111 YAHOO.log("Could not save Cell Editor input due to invalid data " + 18112 lang.dump(inputValue), "warn", this.toString()); 18113 return; 18114 } 18115 } 18116 18117 var oSelf = this; 18118 var finishSave = function(bSuccess, oNewValue) { 18119 var oOrigValue = oSelf.value; 18120 if(bSuccess) { 18121 // Update new value 18122 oSelf.value = oNewValue; 18123 oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue); 18124 18125 // Hide CellEditor 18126 oSelf._hide(); 18127 18128 oSelf.fireEvent("saveEvent", 18129 {editor:oSelf, oldData:oOrigValue, newData:oSelf.value}); 18130 YAHOO.log("Cell Editor input saved", "info", this.toString()); 18131 } 18132 else { 18133 oSelf.resetForm(); 18134 oSelf.fireEvent("revertEvent", 18135 {editor:oSelf, oldData:oOrigValue, newData:oNewValue}); 18136 YAHOO.log("Could not save Cell Editor input " + 18137 lang.dump(oNewValue), "warn", oSelf.toString()); 18138 } 18139 oSelf.unblock(); 18140 }; 18141 18142 this.block(); 18143 if(lang.isFunction(this.asyncSubmitter)) { 18144 this.asyncSubmitter.call(this, finishSave, validValue); 18145 } 18146 else { 18147 finishSave(true, validValue); 18148 } 18149 }, 18150 18151 /** 18152 * Cancels CellEditor input and hides UI. 18153 * 18154 * @method cancel 18155 */ 18156 cancel : function() { 18157 if(this.isActive) { 18158 this._hide(); 18159 this.fireEvent("cancelEvent", {editor:this}); 18160 YAHOO.log("CellEditor canceled", "info", this.toString()); 18161 } 18162 else { 18163 YAHOO.log("Unable to cancel CellEditor", "warn", this.toString()); 18164 } 18165 }, 18166 18167 /** 18168 * Renders form elements. 18169 * 18170 * @method renderForm 18171 */ 18172 renderForm : function() { 18173 // To be implemented by subclass 18174 }, 18175 18176 /** 18177 * Access to add additional event listeners. 18178 * 18179 * @method doAfterRender 18180 */ 18181 doAfterRender : function() { 18182 // To be implemented by subclass 18183 }, 18184 18185 18186 /** 18187 * After rendering form, if disabledBtns is set to true, then sets up a mechanism 18188 * to save input without them. 18189 * 18190 * @method handleDisabledBtns 18191 */ 18192 handleDisabledBtns : function() { 18193 // To be implemented by subclass 18194 }, 18195 18196 /** 18197 * Resets CellEditor UI to initial state. 18198 * 18199 * @method resetForm 18200 */ 18201 resetForm : function() { 18202 // To be implemented by subclass 18203 }, 18204 18205 /** 18206 * Sets focus in CellEditor. 18207 * 18208 * @method focus 18209 */ 18210 focus : function() { 18211 // To be implemented by subclass 18212 }, 18213 18214 /** 18215 * Retrieves input value from CellEditor. 18216 * 18217 * @method getInputValue 18218 */ 18219 getInputValue : function() { 18220 // To be implemented by subclass 18221 } 18222 18223 }; 18224 18225 lang.augmentProto(BCE, util.EventProvider); 18226 18227 18228 ///////////////////////////////////////////////////////////////////////////// 18229 // 18230 // Custom Events 18231 // 18232 ///////////////////////////////////////////////////////////////////////////// 18233 18234 /** 18235 * Fired when a CellEditor is shown. 18236 * 18237 * @event showEvent 18238 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18239 */ 18240 18241 /** 18242 * Fired when a CellEditor has a keydown. 18243 * 18244 * @event keydownEvent 18245 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18246 * @param oArgs.event {HTMLEvent} The event object. 18247 */ 18248 18249 /** 18250 * Fired when a CellEditor input is reverted due to invalid data. 18251 * 18252 * @event invalidDataEvent 18253 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18254 * @param oArgs.newData {Object} New data value from form input field. 18255 * @param oArgs.oldData {Object} Old data value. 18256 */ 18257 18258 /** 18259 * Fired when a CellEditor input is reverted due to asyncSubmitter failure. 18260 * 18261 * @event revertEvent 18262 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18263 * @param oArgs.newData {Object} New data value from form input field. 18264 * @param oArgs.oldData {Object} Old data value. 18265 */ 18266 18267 /** 18268 * Fired when a CellEditor input is saved. 18269 * 18270 * @event saveEvent 18271 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18272 * @param oArgs.newData {Object} New data value from form input field. 18273 * @param oArgs.oldData {Object} Old data value. 18274 */ 18275 18276 /** 18277 * Fired when a CellEditor input is canceled. 18278 * 18279 * @event cancelEvent 18280 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18281 */ 18282 18283 /** 18284 * Fired when a CellEditor has a blur event. 18285 * 18286 * @event blurEvent 18287 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18288 */ 18289 18290 18291 18292 18293 18294 18295 18296 18297 18298 18299 18300 18301 18302 18303 /****************************************************************************/ 18304 /****************************************************************************/ 18305 /****************************************************************************/ 18306 18307 /** 18308 * The CheckboxCellEditor class provides functionality for inline editing 18309 * DataTable cell data with checkboxes. 18310 * 18311 * @namespace YAHOO.widget 18312 * @class CheckboxCellEditor 18313 * @extends YAHOO.widget.BaseCellEditor 18314 * @constructor 18315 * @param oConfigs {Object} (Optional) Object literal of configs. 18316 */ 18317 widget.CheckboxCellEditor = function(oConfigs) { 18318 oConfigs = oConfigs || {}; 18319 this._sId = this._sId || Dom.generateId(null, "yui-checkboxceditor"); // "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++; 18320 YAHOO.widget.BaseCellEditor._nCount++; 18321 widget.CheckboxCellEditor.superclass.constructor.call(this, oConfigs.type || "checkbox", oConfigs); 18322 }; 18323 18324 // CheckboxCellEditor extends BaseCellEditor 18325 lang.extend(widget.CheckboxCellEditor, BCE, { 18326 18327 ///////////////////////////////////////////////////////////////////////////// 18328 // 18329 // CheckboxCellEditor public properties 18330 // 18331 ///////////////////////////////////////////////////////////////////////////// 18332 /** 18333 * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"]) 18334 * or a an array of objects (e.g., [{label:"red", value:"#FF0000"}, 18335 * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]). String 18336 * values are treated as markup and inserted into the DOM as innerHTML. 18337 * 18338 * @property checkboxOptions 18339 * @type HTML[] | Object[] 18340 */ 18341 checkboxOptions : null, 18342 18343 /** 18344 * Reference to the checkbox elements. 18345 * 18346 * @property checkboxes 18347 * @type HTMLElement[] 18348 */ 18349 checkboxes : null, 18350 18351 /** 18352 * Array of checked values 18353 * 18354 * @property value 18355 * @type String[] 18356 */ 18357 value : null, 18358 18359 ///////////////////////////////////////////////////////////////////////////// 18360 // 18361 // CheckboxCellEditor public methods 18362 // 18363 ///////////////////////////////////////////////////////////////////////////// 18364 18365 /** 18366 * Render a form with input(s) type=checkbox. 18367 * 18368 * @method renderForm 18369 */ 18370 renderForm : function() { 18371 if(lang.isArray(this.checkboxOptions)) { 18372 var checkboxOption, checkboxValue, checkboxId, elLabel, j, len; 18373 18374 // Create the checkbox buttons in an IE-friendly way... 18375 for(j=0,len=this.checkboxOptions.length; j<len; j++) { 18376 checkboxOption = this.checkboxOptions[j]; 18377 checkboxValue = lang.isValue(checkboxOption.value) ? 18378 checkboxOption.value : checkboxOption; 18379 18380 checkboxId = this.getId() + "-chk" + j; 18381 this.getContainerEl().innerHTML += "<input type=\"checkbox\"" + 18382 " id=\"" + checkboxId + "\"" + // Needed for label 18383 " value=\"" + checkboxValue + "\" />"; 18384 18385 // Create the labels in an IE-friendly way 18386 elLabel = this.getContainerEl().appendChild(document.createElement("label")); 18387 elLabel.htmlFor = checkboxId; 18388 elLabel.innerHTML = lang.isValue(checkboxOption.label) ? 18389 checkboxOption.label : checkboxOption; 18390 } 18391 18392 // Store the reference to the checkbox elements 18393 var allCheckboxes = []; 18394 for(j=0; j<len; j++) { 18395 allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2]; 18396 } 18397 this.checkboxes = allCheckboxes; 18398 18399 if(this.disableBtns) { 18400 this.handleDisabledBtns(); 18401 } 18402 } 18403 else { 18404 YAHOO.log("Could not find checkboxOptions", "error", this.toString()); 18405 } 18406 }, 18407 18408 /** 18409 * After rendering form, if disabledBtns is set to true, then sets up a mechanism 18410 * to save input without them. 18411 * 18412 * @method handleDisabledBtns 18413 */ 18414 handleDisabledBtns : function() { 18415 Ev.addListener(this.getContainerEl(), "click", function(v){ 18416 if(Ev.getTarget(v).tagName.toLowerCase() === "input") { 18417 // Save on blur 18418 this.save(); 18419 } 18420 }, this, true); 18421 }, 18422 18423 /** 18424 * Resets CheckboxCellEditor UI to initial state. 18425 * 18426 * @method resetForm 18427 */ 18428 resetForm : function() { 18429 // Normalize to array 18430 var originalValues = lang.isArray(this.value) ? this.value : [this.value]; 18431 18432 // Match checks to value 18433 for(var i=0, j=this.checkboxes.length; i<j; i++) { 18434 this.checkboxes[i].checked = false; 18435 for(var k=0, len=originalValues.length; k<len; k++) { 18436 if(this.checkboxes[i].value == originalValues[k]) { 18437 this.checkboxes[i].checked = true; 18438 } 18439 } 18440 } 18441 }, 18442 18443 /** 18444 * Sets focus in CheckboxCellEditor. 18445 * 18446 * @method focus 18447 */ 18448 focus : function() { 18449 this.checkboxes[0].focus(); 18450 }, 18451 18452 /** 18453 * Retrieves input value from CheckboxCellEditor. 18454 * 18455 * @method getInputValue 18456 */ 18457 getInputValue : function() { 18458 var checkedValues = []; 18459 for(var i=0, j=this.checkboxes.length; i<j; i++) { 18460 if(this.checkboxes[i].checked) { 18461 checkedValues[checkedValues.length] = this.checkboxes[i].value; 18462 } 18463 } 18464 return checkedValues; 18465 } 18466 18467 }); 18468 18469 // Copy static members to CheckboxCellEditor class 18470 lang.augmentObject(widget.CheckboxCellEditor, BCE); 18471 18472 18473 18474 18475 18476 18477 18478 18479 /****************************************************************************/ 18480 /****************************************************************************/ 18481 /****************************************************************************/ 18482 18483 /** 18484 * The DataCellEditor class provides functionality for inline editing 18485 * DataTable cell data with a YUI Calendar. 18486 * 18487 * @namespace YAHOO.widget 18488 * @class DateCellEditor 18489 * @extends YAHOO.widget.BaseCellEditor 18490 * @constructor 18491 * @param oConfigs {Object} (Optional) Object literal of configs. 18492 */ 18493 widget.DateCellEditor = function(oConfigs) { 18494 oConfigs = oConfigs || {}; 18495 this._sId = this._sId || Dom.generateId(null, "yui-dateceditor"); // "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++; 18496 YAHOO.widget.BaseCellEditor._nCount++; 18497 widget.DateCellEditor.superclass.constructor.call(this, oConfigs.type || "date", oConfigs); 18498 }; 18499 18500 // CheckboxCellEditor extends BaseCellEditor 18501 lang.extend(widget.DateCellEditor, BCE, { 18502 18503 ///////////////////////////////////////////////////////////////////////////// 18504 // 18505 // DateCellEditor public properties 18506 // 18507 ///////////////////////////////////////////////////////////////////////////// 18508 /** 18509 * Reference to Calendar instance. 18510 * 18511 * @property calendar 18512 * @type YAHOO.widget.Calendar 18513 */ 18514 calendar : null, 18515 18516 /** 18517 * Configs for the calendar instance, to be passed to Calendar constructor. 18518 * 18519 * @property calendarOptions 18520 * @type Object 18521 */ 18522 calendarOptions : null, 18523 18524 /** 18525 * Default value. 18526 * 18527 * @property defaultValue 18528 * @type Date 18529 * @default new Date() 18530 */ 18531 defaultValue : new Date(), 18532 18533 18534 ///////////////////////////////////////////////////////////////////////////// 18535 // 18536 // DateCellEditor public methods 18537 // 18538 ///////////////////////////////////////////////////////////////////////////// 18539 18540 /** 18541 * Render a Calendar. 18542 * 18543 * @method renderForm 18544 */ 18545 renderForm : function() { 18546 // Calendar widget 18547 if(YAHOO.widget.Calendar) { 18548 var calContainer = this.getContainerEl().appendChild(document.createElement("div")); 18549 calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor 18550 var calendar = 18551 new YAHOO.widget.Calendar(this.getId() + "-date", 18552 calContainer.id, this.calendarOptions); 18553 calendar.render(); 18554 calContainer.style.cssFloat = "none"; 18555 18556 // Bug 2528576 18557 calendar.hideEvent.subscribe(function() {this.cancel();}, this, true); 18558 18559 if(ua.ie) { 18560 var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div")); 18561 calFloatClearer.style.clear = "both"; 18562 } 18563 18564 this.calendar = calendar; 18565 18566 if(this.disableBtns) { 18567 this.handleDisabledBtns(); 18568 } 18569 } 18570 else { 18571 YAHOO.log("Could not find YUI Calendar", "error", this.toString()); 18572 } 18573 18574 }, 18575 18576 /** 18577 * After rendering form, if disabledBtns is set to true, then sets up a mechanism 18578 * to save input without them. 18579 * 18580 * @method handleDisabledBtns 18581 */ 18582 handleDisabledBtns : function() { 18583 this.calendar.selectEvent.subscribe(function(v){ 18584 // Save on select 18585 this.save(); 18586 }, this, true); 18587 }, 18588 18589 /** 18590 * Resets DateCellEditor UI to initial state. 18591 * 18592 * @method resetForm 18593 */ 18594 resetForm : function() { 18595 var value = this.value || (new Date()); 18596 this.calendar.select(value); 18597 this.calendar.cfg.setProperty("pagedate",value,false); 18598 this.calendar.render(); 18599 // Bug 2528576 18600 this.calendar.show(); 18601 }, 18602 18603 /** 18604 * Sets focus in DateCellEditor. 18605 * 18606 * @method focus 18607 */ 18608 focus : function() { 18609 // To be impmlemented by subclass 18610 }, 18611 18612 /** 18613 * Retrieves input value from DateCellEditor. 18614 * 18615 * @method getInputValue 18616 */ 18617 getInputValue : function() { 18618 return this.calendar.getSelectedDates()[0]; 18619 } 18620 18621 }); 18622 18623 // Copy static members to DateCellEditor class 18624 lang.augmentObject(widget.DateCellEditor, BCE); 18625 18626 18627 18628 18629 18630 18631 18632 18633 18634 /****************************************************************************/ 18635 /****************************************************************************/ 18636 /****************************************************************************/ 18637 18638 /** 18639 * The DropdownCellEditor class provides functionality for inline editing 18640 * DataTable cell data a SELECT element. 18641 * 18642 * @namespace YAHOO.widget 18643 * @class DropdownCellEditor 18644 * @extends YAHOO.widget.BaseCellEditor 18645 * @constructor 18646 * @param oConfigs {Object} (Optional) Object literal of configs. 18647 */ 18648 widget.DropdownCellEditor = function(oConfigs) { 18649 oConfigs = oConfigs || {}; 18650 this._sId = this._sId || Dom.generateId(null, "yui-dropdownceditor"); // "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++; 18651 YAHOO.widget.BaseCellEditor._nCount++; 18652 widget.DropdownCellEditor.superclass.constructor.call(this, oConfigs.type || "dropdown", oConfigs); 18653 }; 18654 18655 // DropdownCellEditor extends BaseCellEditor 18656 lang.extend(widget.DropdownCellEditor, BCE, { 18657 18658 ///////////////////////////////////////////////////////////////////////////// 18659 // 18660 // DropdownCellEditor public properties 18661 // 18662 ///////////////////////////////////////////////////////////////////////////// 18663 /** 18664 * Array of dropdown values. Can either be a simple array (e.g., 18665 * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g., 18666 * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"}, 18667 * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]). String 18668 * values are treated as markup and inserted into the DOM as innerHTML. 18669 * 18670 * @property dropdownOptions 18671 * @type HTML[] | Object[] 18672 */ 18673 dropdownOptions : null, 18674 18675 /** 18676 * Reference to Dropdown element. 18677 * 18678 * @property dropdown 18679 * @type HTMLElement 18680 */ 18681 dropdown : null, 18682 18683 /** 18684 * Enables multi-select. 18685 * 18686 * @property multiple 18687 * @type Boolean 18688 */ 18689 multiple : false, 18690 18691 /** 18692 * Specifies number of visible options. 18693 * 18694 * @property size 18695 * @type Number 18696 */ 18697 size : null, 18698 18699 ///////////////////////////////////////////////////////////////////////////// 18700 // 18701 // DropdownCellEditor public methods 18702 // 18703 ///////////////////////////////////////////////////////////////////////////// 18704 18705 /** 18706 * Render a form with select element. 18707 * 18708 * @method renderForm 18709 */ 18710 renderForm : function() { 18711 var elDropdown = this.getContainerEl().appendChild(document.createElement("select")); 18712 elDropdown.style.zoom = 1; 18713 if(this.multiple) { 18714 elDropdown.multiple = "multiple"; 18715 } 18716 if(lang.isNumber(this.size)) { 18717 elDropdown.size = this.size; 18718 } 18719 this.dropdown = elDropdown; 18720 18721 if(lang.isArray(this.dropdownOptions)) { 18722 var dropdownOption, elOption; 18723 for(var i=0, j=this.dropdownOptions.length; i<j; i++) { 18724 dropdownOption = this.dropdownOptions[i]; 18725 elOption = document.createElement("option"); 18726 elOption.value = (lang.isValue(dropdownOption.value)) ? 18727 dropdownOption.value : dropdownOption; 18728 elOption.innerHTML = (lang.isValue(dropdownOption.label)) ? 18729 dropdownOption.label : dropdownOption; 18730 elOption = elDropdown.appendChild(elOption); 18731 } 18732 18733 if(this.disableBtns) { 18734 this.handleDisabledBtns(); 18735 } 18736 } 18737 }, 18738 18739 /** 18740 * After rendering form, if disabledBtns is set to true, then sets up a mechanism 18741 * to save input without them. 18742 * 18743 * @method handleDisabledBtns 18744 */ 18745 handleDisabledBtns : function() { 18746 // Save on blur for multi-select 18747 if(this.multiple) { 18748 Ev.addListener(this.dropdown, "blur", function(v){ 18749 // Save on change 18750 this.save(); 18751 }, this, true); 18752 } 18753 // Save on change for single-select 18754 else { 18755 if(!ua.ie) { 18756 Ev.addListener(this.dropdown, "change", function(v){ 18757 // Save on change 18758 this.save(); 18759 }, this, true); 18760 } 18761 else { 18762 // Bug 2529274: "change" event is not keyboard accessible in IE6 18763 Ev.addListener(this.dropdown, "blur", function(v){ 18764 this.save(); 18765 }, this, true); 18766 Ev.addListener(this.dropdown, "click", function(v){ 18767 this.save(); 18768 }, this, true); 18769 } 18770 } 18771 }, 18772 18773 /** 18774 * Resets DropdownCellEditor UI to initial state. 18775 * 18776 * @method resetForm 18777 */ 18778 resetForm : function() { 18779 var allOptions = this.dropdown.options, 18780 i=0, j=allOptions.length; 18781 18782 // Look for multi-select selections 18783 if(lang.isArray(this.value)) { 18784 var allValues = this.value, 18785 m=0, n=allValues.length, 18786 hash = {}; 18787 // Reset all selections and stash options in a value hash 18788 for(; i<j; i++) { 18789 allOptions[i].selected = false; 18790 hash[allOptions[i].value] = allOptions[i]; 18791 } 18792 for(; m<n; m++) { 18793 if(hash[allValues[m]]) { 18794 hash[allValues[m]].selected = true; 18795 } 18796 } 18797 } 18798 // Only need to look for a single selection 18799 else { 18800 for(; i<j; i++) { 18801 if(this.value == allOptions[i].value) { 18802 allOptions[i].selected = true; 18803 } 18804 } 18805 } 18806 }, 18807 18808 /** 18809 * Sets focus in DropdownCellEditor. 18810 * 18811 * @method focus 18812 */ 18813 focus : function() { 18814 this.getDataTable()._focusEl(this.dropdown); 18815 }, 18816 18817 /** 18818 * Retrieves input value from DropdownCellEditor. 18819 * 18820 * @method getInputValue 18821 */ 18822 getInputValue : function() { 18823 var allOptions = this.dropdown.options; 18824 18825 // Look for multiple selections 18826 if(this.multiple) { 18827 var values = [], 18828 i=0, j=allOptions.length; 18829 for(; i<j; i++) { 18830 if(allOptions[i].selected) { 18831 values.push(allOptions[i].value); 18832 } 18833 } 18834 return values; 18835 } 18836 // Only need to look for single selection 18837 else { 18838 return allOptions[allOptions.selectedIndex].value; 18839 } 18840 } 18841 18842 }); 18843 18844 // Copy static members to DropdownCellEditor class 18845 lang.augmentObject(widget.DropdownCellEditor, BCE); 18846 18847 18848 18849 18850 18851 18852 /****************************************************************************/ 18853 /****************************************************************************/ 18854 /****************************************************************************/ 18855 18856 /** 18857 * The RadioCellEditor class provides functionality for inline editing 18858 * DataTable cell data with radio buttons. 18859 * 18860 * @namespace YAHOO.widget 18861 * @class RadioCellEditor 18862 * @extends YAHOO.widget.BaseCellEditor 18863 * @constructor 18864 * @param oConfigs {Object} (Optional) Object literal of configs. 18865 */ 18866 widget.RadioCellEditor = function(oConfigs) { 18867 oConfigs = oConfigs || {}; 18868 this._sId = this._sId || Dom.generateId(null, "yui-radioceditor"); // "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++; 18869 YAHOO.widget.BaseCellEditor._nCount++; 18870 widget.RadioCellEditor.superclass.constructor.call(this, oConfigs.type || "radio", oConfigs); 18871 }; 18872 18873 // RadioCellEditor extends BaseCellEditor 18874 lang.extend(widget.RadioCellEditor, BCE, { 18875 18876 ///////////////////////////////////////////////////////////////////////////// 18877 // 18878 // RadioCellEditor public properties 18879 // 18880 ///////////////////////////////////////////////////////////////////////////// 18881 /** 18882 * Reference to radio elements. 18883 * 18884 * @property radios 18885 * @type HTMLElement[] 18886 */ 18887 radios : null, 18888 18889 /** 18890 * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"]) 18891 * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1}, 18892 * {label:"maybe", value:0}]). String values are treated as markup and inserted 18893 * into the DOM as innerHTML. 18894 * 18895 * @property radioOptions 18896 * @type HTML[] | Object[] 18897 */ 18898 radioOptions : null, 18899 18900 ///////////////////////////////////////////////////////////////////////////// 18901 // 18902 // RadioCellEditor public methods 18903 // 18904 ///////////////////////////////////////////////////////////////////////////// 18905 18906 /** 18907 * Render a form with input(s) type=radio. 18908 * 18909 * @method renderForm 18910 */ 18911 renderForm : function() { 18912 if(lang.isArray(this.radioOptions)) { 18913 var radioOption, radioValue, radioId, elLabel; 18914 18915 // Create the radio buttons in an IE-friendly way 18916 for(var i=0, len=this.radioOptions.length; i<len; i++) { 18917 radioOption = this.radioOptions[i]; 18918 radioValue = lang.isValue(radioOption.value) ? 18919 radioOption.value : radioOption; 18920 radioId = this.getId() + "-radio" + i; 18921 this.getContainerEl().innerHTML += "<input type=\"radio\"" + 18922 " name=\"" + this.getId() + "\"" + 18923 " value=\"" + radioValue + "\"" + 18924 " id=\"" + radioId + "\" />"; // Needed for label 18925 18926 // Create the labels in an IE-friendly way 18927 elLabel = this.getContainerEl().appendChild(document.createElement("label")); 18928 elLabel.htmlFor = radioId; 18929 elLabel.innerHTML = (lang.isValue(radioOption.label)) ? 18930 radioOption.label : radioOption; 18931 } 18932 18933 // Store the reference to the checkbox elements 18934 var allRadios = [], 18935 elRadio; 18936 for(var j=0; j<len; j++) { 18937 elRadio = this.getContainerEl().childNodes[j*2]; 18938 allRadios[allRadios.length] = elRadio; 18939 } 18940 this.radios = allRadios; 18941 18942 if(this.disableBtns) { 18943 this.handleDisabledBtns(); 18944 } 18945 } 18946 else { 18947 YAHOO.log("Could not find radioOptions", "error", this.toString()); 18948 } 18949 }, 18950 18951 /** 18952 * After rendering form, if disabledBtns is set to true, then sets up a mechanism 18953 * to save input without them. 18954 * 18955 * @method handleDisabledBtns 18956 */ 18957 handleDisabledBtns : function() { 18958 Ev.addListener(this.getContainerEl(), "click", function(v){ 18959 if(Ev.getTarget(v).tagName.toLowerCase() === "input") { 18960 // Save on blur 18961 this.save(); 18962 } 18963 }, this, true); 18964 }, 18965 18966 /** 18967 * Resets RadioCellEditor UI to initial state. 18968 * 18969 * @method resetForm 18970 */ 18971 resetForm : function() { 18972 for(var i=0, j=this.radios.length; i<j; i++) { 18973 var elRadio = this.radios[i]; 18974 if(this.value == elRadio.value) { 18975 elRadio.checked = true; 18976 return; 18977 } 18978 } 18979 }, 18980 18981 /** 18982 * Sets focus in RadioCellEditor. 18983 * 18984 * @method focus 18985 */ 18986 focus : function() { 18987 for(var i=0, j=this.radios.length; i<j; i++) { 18988 if(this.radios[i].checked) { 18989 this.radios[i].focus(); 18990 return; 18991 } 18992 } 18993 }, 18994 18995 /** 18996 * Retrieves input value from RadioCellEditor. 18997 * 18998 * @method getInputValue 18999 */ 19000 getInputValue : function() { 19001 for(var i=0, j=this.radios.length; i<j; i++) { 19002 if(this.radios[i].checked) { 19003 return this.radios[i].value; 19004 } 19005 } 19006 } 19007 19008 }); 19009 19010 // Copy static members to RadioCellEditor class 19011 lang.augmentObject(widget.RadioCellEditor, BCE); 19012 19013 19014 19015 19016 19017 19018 /****************************************************************************/ 19019 /****************************************************************************/ 19020 /****************************************************************************/ 19021 19022 /** 19023 * The TextareaCellEditor class provides functionality for inline editing 19024 * DataTable cell data with a TEXTAREA element. 19025 * 19026 * @namespace YAHOO.widget 19027 * @class TextareaCellEditor 19028 * @extends YAHOO.widget.BaseCellEditor 19029 * @constructor 19030 * @param oConfigs {Object} (Optional) Object literal of configs. 19031 */ 19032 widget.TextareaCellEditor = function(oConfigs) { 19033 oConfigs = oConfigs || {}; 19034 this._sId = this._sId || Dom.generateId(null, "yui-textareaceditor");// "yui-textareaceditor" + ; 19035 YAHOO.widget.BaseCellEditor._nCount++; 19036 widget.TextareaCellEditor.superclass.constructor.call(this, oConfigs.type || "textarea", oConfigs); 19037 }; 19038 19039 // TextareaCellEditor extends BaseCellEditor 19040 lang.extend(widget.TextareaCellEditor, BCE, { 19041 19042 ///////////////////////////////////////////////////////////////////////////// 19043 // 19044 // TextareaCellEditor public properties 19045 // 19046 ///////////////////////////////////////////////////////////////////////////// 19047 /** 19048 * Reference to textarea element. 19049 * 19050 * @property textarea 19051 * @type HTMLElement 19052 */ 19053 textarea : null, 19054 19055 19056 ///////////////////////////////////////////////////////////////////////////// 19057 // 19058 // TextareaCellEditor public methods 19059 // 19060 ///////////////////////////////////////////////////////////////////////////// 19061 19062 /** 19063 * Render a form with textarea. 19064 * 19065 * @method renderForm 19066 */ 19067 renderForm : function() { 19068 var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea")); 19069 this.textarea = elTextarea; 19070 19071 if(this.disableBtns) { 19072 this.handleDisabledBtns(); 19073 } 19074 }, 19075 19076 /** 19077 * After rendering form, if disabledBtns is set to true, then sets up a mechanism 19078 * to save input without them. 19079 * 19080 * @method handleDisabledBtns 19081 */ 19082 handleDisabledBtns : function() { 19083 Ev.addListener(this.textarea, "blur", function(v){ 19084 // Save on blur 19085 this.save(); 19086 }, this, true); 19087 }, 19088 19089 /** 19090 * Moves TextareaCellEditor UI to a cell. 19091 * 19092 * @method move 19093 */ 19094 move : function() { 19095 this.textarea.style.width = this.getTdEl().offsetWidth + "px"; 19096 this.textarea.style.height = "3em"; 19097 YAHOO.widget.TextareaCellEditor.superclass.move.call(this); 19098 }, 19099 19100 /** 19101 * Resets TextareaCellEditor UI to initial state. 19102 * 19103 * @method resetForm 19104 */ 19105 resetForm : function() { 19106 this.textarea.value = this.value; 19107 }, 19108 19109 /** 19110 * Sets focus in TextareaCellEditor. 19111 * 19112 * @method focus 19113 */ 19114 focus : function() { 19115 // Bug 2303181, Bug 2263600 19116 this.getDataTable()._focusEl(this.textarea); 19117 this.textarea.select(); 19118 }, 19119 19120 /** 19121 * Retrieves input value from TextareaCellEditor. 19122 * 19123 * @method getInputValue 19124 */ 19125 getInputValue : function() { 19126 return this.textarea.value; 19127 } 19128 19129 }); 19130 19131 // Copy static members to TextareaCellEditor class 19132 lang.augmentObject(widget.TextareaCellEditor, BCE); 19133 19134 19135 19136 19137 19138 19139 19140 19141 19142 /****************************************************************************/ 19143 /****************************************************************************/ 19144 /****************************************************************************/ 19145 19146 /** 19147 * The TextboxCellEditor class provides functionality for inline editing 19148 * DataTable cell data with an INPUT TYPE=TEXT element. 19149 * 19150 * @namespace YAHOO.widget 19151 * @class TextboxCellEditor 19152 * @extends YAHOO.widget.BaseCellEditor 19153 * @constructor 19154 * @param oConfigs {Object} (Optional) Object literal of configs. 19155 */ 19156 widget.TextboxCellEditor = function(oConfigs) { 19157 oConfigs = oConfigs || {}; 19158 this._sId = this._sId || Dom.generateId(null, "yui-textboxceditor");// "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++; 19159 YAHOO.widget.BaseCellEditor._nCount++; 19160 widget.TextboxCellEditor.superclass.constructor.call(this, oConfigs.type || "textbox", oConfigs); 19161 }; 19162 19163 // TextboxCellEditor extends BaseCellEditor 19164 lang.extend(widget.TextboxCellEditor, BCE, { 19165 19166 ///////////////////////////////////////////////////////////////////////////// 19167 // 19168 // TextboxCellEditor public properties 19169 // 19170 ///////////////////////////////////////////////////////////////////////////// 19171 /** 19172 * Reference to the textbox element. 19173 * 19174 * @property textbox 19175 */ 19176 textbox : null, 19177 19178 ///////////////////////////////////////////////////////////////////////////// 19179 // 19180 // TextboxCellEditor public methods 19181 // 19182 ///////////////////////////////////////////////////////////////////////////// 19183 19184 /** 19185 * Render a form with input type=text. 19186 * 19187 * @method renderForm 19188 */ 19189 renderForm : function() { 19190 var elTextbox; 19191 // Bug 1802582: SF3/Mac needs a form element wrapping the input 19192 if(ua.webkit>420) { 19193 elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input")); 19194 } 19195 else { 19196 elTextbox = this.getContainerEl().appendChild(document.createElement("input")); 19197 } 19198 elTextbox.type = "text"; 19199 this.textbox = elTextbox; 19200 19201 // Save on enter by default 19202 // Bug: 1802582 Set up a listener on each textbox to track on keypress 19203 // since SF/OP can't preventDefault on keydown 19204 Ev.addListener(elTextbox, "keypress", function(v){ 19205 if((v.keyCode === 13)) { 19206 // Prevent form submit 19207 YAHOO.util.Event.preventDefault(v); 19208 this.save(); 19209 } 19210 }, this, true); 19211 19212 if(this.disableBtns) { 19213 // By default this is no-op since enter saves by default 19214 this.handleDisabledBtns(); 19215 } 19216 }, 19217 19218 /** 19219 * Moves TextboxCellEditor UI to a cell. 19220 * 19221 * @method move 19222 */ 19223 move : function() { 19224 this.textbox.style.width = this.getTdEl().offsetWidth + "px"; 19225 widget.TextboxCellEditor.superclass.move.call(this); 19226 }, 19227 19228 /** 19229 * Resets TextboxCellEditor UI to initial state. 19230 * 19231 * @method resetForm 19232 */ 19233 resetForm : function() { 19234 this.textbox.value = lang.isValue(this.value) ? this.value.toString() : ""; 19235 }, 19236 19237 /** 19238 * Sets focus in TextboxCellEditor. 19239 * 19240 * @method focus 19241 */ 19242 focus : function() { 19243 // Bug 2303181, Bug 2263600 19244 this.getDataTable()._focusEl(this.textbox); 19245 this.textbox.select(); 19246 }, 19247 19248 /** 19249 * Returns new value for TextboxCellEditor. 19250 * 19251 * @method getInputValue 19252 */ 19253 getInputValue : function() { 19254 return this.textbox.value; 19255 } 19256 19257 }); 19258 19259 // Copy static members to TextboxCellEditor class 19260 lang.augmentObject(widget.TextboxCellEditor, BCE); 19261 19262 19263 19264 19265 19266 19267 19268 ///////////////////////////////////////////////////////////////////////////// 19269 // 19270 // DataTable extension 19271 // 19272 ///////////////////////////////////////////////////////////////////////////// 19273 19274 /** 19275 * CellEditor subclasses. 19276 * @property DataTable.Editors 19277 * @type Object 19278 * @static 19279 */ 19280 DT.Editors = { 19281 checkbox : widget.CheckboxCellEditor, 19282 "date" : widget.DateCellEditor, 19283 dropdown : widget.DropdownCellEditor, 19284 radio : widget.RadioCellEditor, 19285 textarea : widget.TextareaCellEditor, 19286 textbox : widget.TextboxCellEditor 19287 }; 19288 19289 /****************************************************************************/ 19290 /****************************************************************************/ 19291 /****************************************************************************/ 19292 19293 /** 19294 * Factory class for instantiating a BaseCellEditor subclass. 19295 * 19296 * @namespace YAHOO.widget 19297 * @class CellEditor 19298 * @extends YAHOO.widget.BaseCellEditor 19299 * @constructor 19300 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors. 19301 * @param oConfigs {Object} (Optional) Object literal of configs. 19302 */ 19303 widget.CellEditor = function(sType, oConfigs) { 19304 // Point to one of the subclasses 19305 if(sType && DT.Editors[sType]) { 19306 lang.augmentObject(BCE, DT.Editors[sType]); 19307 return new DT.Editors[sType](oConfigs); 19308 } 19309 else { 19310 return new BCE(null, oConfigs); 19311 } 19312 }; 19313 19314 var CE = widget.CellEditor; 19315 19316 // Copy static members to CellEditor class 19317 lang.augmentObject(CE, BCE); 19318 19319 19320 })(); 19321 19322 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.9.0", build: "2800"}); 19323 19324 }, '2.9.0' ,{"requires": ["yui2-yahoo", "yui2-dom", "yui2-event", "yui2-skin-sam-datatable", "yui2-element", "yui2-datasource"], "optional": ["yui2-skin-sam-paginator", "yui2-paginator", "yui2-dragdrop", "yui2-skin-sam-calendar", "yui2-calendar"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |