[ 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 return false; 360 } 361 362 363 if (type == "mouseenter" || type == "mouseleave") { 364 365 if (!Event._createMouseDelegate) { 366 return false; 367 } 368 369 // Look up the real event--either mouseover or mouseout 370 sType = Event._getType(type); 371 372 fnMouseDelegate = Event._createMouseDelegate(fn, obj, overrideContext); 373 374 fnDelegate = Event._createDelegate(function (event, matchedEl, container) { 375 376 return fnMouseDelegate.call(matchedEl, event, container); 377 378 }, filter, obj, overrideContext); 379 380 } 381 else { 382 383 fnDelegate = Event._createDelegate(fn, filter, obj, overrideContext); 384 385 } 386 387 delegates.push([container, sType, fn, fnDelegate]); 388 389 return Event.on(container, sType, fnDelegate); 390 391 }, 392 393 394 /** 395 * Removes a delegated event listener. 396 * 397 * @method removeDelegate 398 * 399 * @param {String|HTMLElement|Array|NodeList} container An id, an element 400 * reference, or a collection of ids and/or elements to remove 401 * the listener from. 402 * @param {String} type The type of event to remove. 403 * @param {Function} fn The method the event invokes. If fn is 404 * undefined, then all event listeners for the type of event are 405 * removed. 406 * @return {boolean} Returns true if the unbind was successful, false 407 * otherwise. 408 * @static 409 * @for Event 410 */ 411 removeDelegate: function (container, type, fn) { 412 413 var sType = type, 414 returnVal = false, 415 index, 416 cacheItem; 417 418 // Look up the real event--either mouseover or mouseout 419 if (type == "mouseenter" || type == "mouseleave") { 420 sType = Event._getType(type); 421 } 422 423 index = Event._getCacheIndex(delegates, container, sType, fn); 424 425 if (index >= 0) { 426 cacheItem = delegates[index]; 427 } 428 429 430 if (container && cacheItem) { 431 432 returnVal = Event.removeListener(cacheItem[0], cacheItem[1], cacheItem[3]); 433 434 if (returnVal) { 435 delete delegates[index][2]; 436 delete delegates[index][3]; 437 delegates.splice(index, 1); 438 } 439 440 } 441 442 return returnVal; 443 444 } 445 446 }); 447 448 }()); 449 450 451 /** 452 * Augments the Event Utility with support for the mouseenter and mouseleave 453 * events: A mouseenter event fires the first time the mouse enters an 454 * element; a mouseleave event first the first time the mouse leaves an 455 * element. 456 * 457 * @module event-mouseenter 458 * @title Event Utility mouseenter and mouseout Module 459 * @namespace YAHOO.util 460 * @requires event 461 */ 462 463 (function () { 464 465 var Event = YAHOO.util.Event, 466 Lang = YAHOO.lang, 467 468 addListener = Event.addListener, 469 removeListener = Event.removeListener, 470 getListeners = Event.getListeners, 471 472 delegates = [], 473 474 specialTypes = { 475 mouseenter: "mouseover", 476 mouseleave: "mouseout" 477 }, 478 479 remove = function(el, type, fn) { 480 481 var index = Event._getCacheIndex(delegates, el, type, fn), 482 cacheItem, 483 returnVal; 484 485 if (index >= 0) { 486 cacheItem = delegates[index]; 487 } 488 489 if (el && cacheItem) { 490 491 // removeListener will translate the value of type 492 returnVal = removeListener.call(Event, cacheItem[0], type, cacheItem[3]); 493 494 if (returnVal) { 495 delete delegates[index][2]; 496 delete delegates[index][3]; 497 delegates.splice(index, 1); 498 } 499 500 } 501 502 return returnVal; 503 504 }; 505 506 507 Lang.augmentObject(Event._specialTypes, specialTypes); 508 509 Lang.augmentObject(Event, { 510 511 /** 512 * Creates a delegate function used to call mouseover and mouseleave 513 * event listeners specified via the 514 * <code>YAHOO.util.Event.addListener</code> 515 * or <code>YAHOO.util.Event.on</code> method. 516 * 517 * @method _createMouseDelegate 518 * 519 * @param {Function} fn The method (event listener) to call 520 * @param {Object} obj An arbitrary object that will be 521 * passed as a parameter to the listener 522 * @param {Boolean|object} overrideContext If true, the value of the 523 * obj parameter becomes the execution context 524 * of the listener. If an object, this object 525 * becomes the execution context. 526 * @return {Function} Function that will call the event listener 527 * specified by either the <code>YAHOO.util.Event.addListener</code> 528 * or <code>YAHOO.util.Event.on</code> method. 529 * @private 530 * @static 531 * @for Event 532 */ 533 _createMouseDelegate: function (fn, obj, overrideContext) { 534 535 return function (event, container) { 536 537 var el = this, 538 relatedTarget = Event.getRelatedTarget(event), 539 context, 540 args; 541 542 if (el != relatedTarget && !YAHOO.util.Dom.isAncestor(el, relatedTarget)) { 543 544 context = el; 545 546 if (overrideContext) { 547 if (overrideContext === true) { 548 context = obj; 549 } else { 550 context = overrideContext; 551 } 552 } 553 554 // The default args passed back to a mouseenter or 555 // mouseleave listener are: the event, and any object 556 // the user passed when subscribing 557 558 args = [event, obj]; 559 560 // Add the element and delegation container as arguments 561 // when delegating mouseenter and mouseleave 562 563 if (container) { 564 args.splice(1, 0, el, container); 565 } 566 567 return fn.apply(context, args); 568 569 } 570 571 }; 572 573 }, 574 575 addListener: function (el, type, fn, obj, overrideContext) { 576 577 var fnDelegate, 578 returnVal; 579 580 if (specialTypes[type]) { 581 582 fnDelegate = Event._createMouseDelegate(fn, obj, overrideContext); 583 584 fnDelegate.mouseDelegate = true; 585 586 delegates.push([el, type, fn, fnDelegate]); 587 588 // addListener will translate the value of type 589 returnVal = addListener.call(Event, el, type, fnDelegate); 590 591 } 592 else { 593 returnVal = addListener.apply(Event, arguments); 594 } 595 596 return returnVal; 597 598 }, 599 600 removeListener: function (el, type, fn) { 601 602 var returnVal; 603 604 if (specialTypes[type]) { 605 returnVal = remove.apply(Event, arguments); 606 } 607 else { 608 returnVal = removeListener.apply(Event, arguments); 609 } 610 611 return returnVal; 612 613 }, 614 615 getListeners: function (el, type) { 616 617 // If the user specified the type as mouseover or mouseout, 618 // need to filter out those used by mouseenter and mouseleave. 619 // If the user specified the type as mouseenter or mouseleave, 620 // need to filter out the true mouseover and mouseout listeners. 621 622 var listeners = [], 623 elListeners, 624 bMouseOverOrOut = (type === "mouseover" || type === "mouseout"), 625 bMouseDelegate, 626 i, 627 l; 628 629 if (type && (bMouseOverOrOut || specialTypes[type])) { 630 631 elListeners = getListeners.call(Event, el, this._getType(type)); 632 633 if (elListeners) { 634 635 for (i=elListeners.length-1; i>-1; i--) { 636 637 l = elListeners[i]; 638 bMouseDelegate = l.fn.mouseDelegate; 639 640 if ((specialTypes[type] && bMouseDelegate) || (bMouseOverOrOut && !bMouseDelegate)) { 641 listeners.push(l); 642 } 643 644 } 645 646 } 647 648 } 649 else { 650 listeners = getListeners.apply(Event, arguments); 651 } 652 653 return (listeners && listeners.length) ? listeners : null; 654 655 } 656 657 }, true); 658 659 Event.on = Event.addListener; 660 661 }()); 662 YAHOO.register("event-mouseenter", YAHOO.util.Event, {version: "2.9.0", build: "2800"}); 663 664 var Y = YAHOO, 665 Y_DOM = YAHOO.util.Dom, 666 EMPTY_ARRAY = [], 667 Y_UA = Y.env.ua, 668 Y_Lang = Y.lang, 669 Y_DOC = document, 670 Y_DOCUMENT_ELEMENT = Y_DOC.documentElement, 671 672 Y_DOM_inDoc = Y_DOM.inDocument, 673 Y_mix = Y_Lang.augmentObject, 674 Y_guid = Y_DOM.generateId, 675 676 Y_getDoc = function(element) { 677 var doc = Y_DOC; 678 if (element) { 679 doc = (element.nodeType === 9) ? element : // element === document 680 element.ownerDocument || // element === DOM node 681 element.document || // element === window 682 Y_DOC; // default 683 } 684 685 return doc; 686 }, 687 688 Y_Array = function(o, startIdx) { 689 var l, a, start = startIdx || 0; 690 691 // IE errors when trying to slice HTMLElement collections 692 try { 693 return Array.prototype.slice.call(o, start); 694 } catch (e) { 695 a = []; 696 l = o.length; 697 for (; start < l; start++) { 698 a.push(o[start]); 699 } 700 return a; 701 } 702 }, 703 704 Y_DOM_allById = function(id, root) { 705 root = root || Y_DOC; 706 var nodes = [], 707 ret = [], 708 i, 709 node; 710 711 if (root.querySelectorAll) { 712 ret = root.querySelectorAll('[id="' + id + '"]'); 713 } else if (root.all) { 714 nodes = root.all(id); 715 716 if (nodes) { 717 // root.all may return HTMLElement or HTMLCollection. 718 // some elements are also HTMLCollection (FORM, SELECT). 719 if (nodes.nodeName) { 720 if (nodes.id === id) { // avoid false positive on name 721 ret.push(nodes); 722 nodes = EMPTY_ARRAY; // done, no need to filter 723 } else { // prep for filtering 724 nodes = [nodes]; 725 } 726 } 727 728 if (nodes.length) { 729 // filter out matches on node.name 730 // and element.id as reference to element with id === 'id' 731 for (i = 0; node = nodes[i++];) { 732 if (node.id === id || 733 (node.attributes && node.attributes.id && 734 node.attributes.id.value === id)) { 735 ret.push(node); 736 } 737 } 738 } 739 } 740 } else { 741 ret = [Y_getDoc(root).getElementById(id)]; 742 } 743 744 return ret; 745 }; 746 747 /** 748 * The selector-native module provides support for native querySelector 749 * @module dom 750 * @submodule selector-native 751 * @for Selector 752 */ 753 754 /** 755 * Provides support for using CSS selectors to query the DOM 756 * @class Selector 757 * @static 758 * @for Selector 759 */ 760 761 var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition', 762 OWNER_DOCUMENT = 'ownerDocument', 763 764 Selector = { 765 _foundCache: [], 766 767 useNative: true, 768 769 _compare: ('sourceIndex' in Y_DOCUMENT_ELEMENT) ? 770 function(nodeA, nodeB) { 771 var a = nodeA.sourceIndex, 772 b = nodeB.sourceIndex; 773 774 if (a === b) { 775 return 0; 776 } else if (a > b) { 777 return 1; 778 } 779 780 return -1; 781 782 } : (Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION] ? 783 function(nodeA, nodeB) { 784 if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) { 785 return -1; 786 } else { 787 return 1; 788 } 789 } : 790 function(nodeA, nodeB) { 791 var rangeA, rangeB, compare; 792 if (nodeA && nodeB) { 793 rangeA = nodeA[OWNER_DOCUMENT].createRange(); 794 rangeA.setStart(nodeA, 0); 795 rangeB = nodeB[OWNER_DOCUMENT].createRange(); 796 rangeB.setStart(nodeB, 0); 797 compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END 798 } 799 800 return compare; 801 802 }), 803 804 _sort: function(nodes) { 805 if (nodes) { 806 nodes = Y_Array(nodes, 0, true); 807 if (nodes.sort) { 808 nodes.sort(Selector._compare); 809 } 810 } 811 812 return nodes; 813 }, 814 815 _deDupe: function(nodes) { 816 var ret = [], 817 i, node; 818 819 for (i = 0; (node = nodes[i++]);) { 820 if (!node._found) { 821 ret[ret.length] = node; 822 node._found = true; 823 } 824 } 825 826 for (i = 0; (node = ret[i++]);) { 827 node._found = null; 828 node.removeAttribute('_found'); 829 } 830 831 return ret; 832 }, 833 834 /** 835 * Retrieves a set of nodes based on a given CSS selector. 836 * @method query 837 * 838 * @param {string} selector The CSS Selector to test the node against. 839 * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc 840 * @param {Boolean} firstOnly optional Whether or not to return only the first match. 841 * @return {Array} An array of nodes that match the given selector. 842 * @static 843 */ 844 query: function(selector, root, firstOnly, skipNative) { 845 if (typeof root == 'string') { 846 root = Y_DOM.get(root); 847 if (!root) { 848 return (firstOnly) ? null : []; 849 } 850 } else { 851 root = root || Y_DOC; 852 } 853 854 var ret = [], 855 useNative = (Selector.useNative && Y_DOC.querySelector && !skipNative), 856 queries = [[selector, root]], 857 query, 858 result, 859 i, 860 fn = (useNative) ? Selector._nativeQuery : Selector._bruteQuery; 861 862 if (selector && fn) { 863 // split group into seperate queries 864 if (!skipNative && // already done if skipping 865 (!useNative || root.tagName)) { // split native when element scoping is needed 866 queries = Selector._splitQueries(selector, root); 867 } 868 869 for (i = 0; (query = queries[i++]);) { 870 result = fn(query[0], query[1], firstOnly); 871 if (!firstOnly) { // coerce DOM Collection to Array 872 result = Y_Array(result, 0, true); 873 } 874 if (result) { 875 ret = ret.concat(result); 876 } 877 } 878 879 if (queries.length > 1) { // remove dupes and sort by doc order 880 ret = Selector._sort(Selector._deDupe(ret)); 881 } 882 } 883 884 Y.log('query: ' + selector + ' returning: ' + ret.length, 'info', 'Selector'); 885 return (firstOnly) ? (ret[0] || null) : ret; 886 887 }, 888 889 // allows element scoped queries to begin with combinator 890 // e.g. query('> p', document.body) === query('body > p') 891 _splitQueries: function(selector, node) { 892 var groups = selector.split(','), 893 queries = [], 894 prefix = '', 895 i, len; 896 897 if (node) { 898 // enforce for element scoping 899 if (node.tagName) { 900 node.id = node.id || Y_guid(); 901 prefix = '[id="' + node.id + '"] '; 902 } 903 904 for (i = 0, len = groups.length; i < len; ++i) { 905 selector = prefix + groups[i]; 906 queries.push([selector, node]); 907 } 908 } 909 910 return queries; 911 }, 912 913 _nativeQuery: function(selector, root, one) { 914 if (Y_UA.webkit && selector.indexOf(':checked') > -1 && 915 (Selector.pseudos && Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected" 916 return Selector.query(selector, root, one, true); // redo with skipNative true to try brute query 917 } 918 try { 919 //Y.log('trying native query with: ' + selector, 'info', 'selector-native'); 920 return root['querySelector' + (one ? '' : 'All')](selector); 921 } catch(e) { // fallback to brute if available 922 //Y.log('native query error; reverting to brute query with: ' + selector, 'info', 'selector-native'); 923 return Selector.query(selector, root, one, true); // redo with skipNative true 924 } 925 }, 926 927 filter: function(nodes, selector) { 928 var ret = [], 929 i, node; 930 931 if (nodes && selector) { 932 for (i = 0; (node = nodes[i++]);) { 933 if (Selector.test(node, selector)) { 934 ret[ret.length] = node; 935 } 936 } 937 } else { 938 Y.log('invalid filter input (nodes: ' + nodes + 939 ', selector: ' + selector + ')', 'warn', 'Selector'); 940 } 941 942 return ret; 943 }, 944 945 test: function(node, selector, root) { 946 var ret = false, 947 groups = selector.split(','), 948 useFrag = false, 949 parent, 950 item, 951 items, 952 frag, 953 i, j, group; 954 955 if (node && node.tagName) { // only test HTMLElements 956 957 // we need a root if off-doc 958 if (!root && !Y_DOM_inDoc(node)) { 959 parent = node.parentNode; 960 if (parent) { 961 root = parent; 962 } else { // only use frag when no parent to query 963 frag = node[OWNER_DOCUMENT].createDocumentFragment(); 964 frag.appendChild(node); 965 root = frag; 966 useFrag = true; 967 } 968 } 969 root = root || node[OWNER_DOCUMENT]; 970 971 if (!node.id) { 972 node.id = Y_guid(); 973 } 974 for (i = 0; (group = groups[i++]);) { // TODO: off-dom test 975 group += '[id="' + node.id + '"]'; 976 items = Selector.query(group, root); 977 978 for (j = 0; item = items[j++];) { 979 if (item === node) { 980 ret = true; 981 break; 982 } 983 } 984 if (ret) { 985 break; 986 } 987 } 988 989 if (useFrag) { // cleanup 990 frag.removeChild(node); 991 } 992 } 993 994 return ret; 995 } 996 997 }; 998 999 YAHOO.util.Selector = Selector; 1000 /** 1001 * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements. 1002 * @module dom 1003 * @submodule selector-css2 1004 * @for Selector 1005 */ 1006 1007 /** 1008 * Provides helper methods for collecting and filtering DOM elements. 1009 */ 1010 1011 var PARENT_NODE = 'parentNode', 1012 TAG_NAME = 'tagName', 1013 ATTRIBUTES = 'attributes', 1014 COMBINATOR = 'combinator', 1015 PSEUDOS = 'pseudos', 1016 1017 SelectorCSS2 = { 1018 _reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move? 1019 SORT_RESULTS: true, 1020 _children: function(node, tag) { 1021 var ret = node.children, 1022 i, 1023 children = [], 1024 childNodes, 1025 child; 1026 1027 if (node.children && tag && node.children.tags) { 1028 children = node.children.tags(tag); 1029 } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children 1030 childNodes = ret || node.childNodes; 1031 ret = []; 1032 for (i = 0; (child = childNodes[i++]);) { 1033 if (child.tagName) { 1034 if (!tag || tag === child.tagName) { 1035 ret.push(child); 1036 } 1037 } 1038 } 1039 } 1040 1041 return ret || []; 1042 }, 1043 1044 _re: { 1045 //attr: /(\[.*\])/g, 1046 attr: /(\[[^\]]*\])/g, 1047 //esc: /\\[:\[][\w\d\]]*/gi, 1048 esc: /\\[:\[\]\(\)#\.\'\>+~"]/gi, 1049 //pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\))*)/i 1050 pseudos: /(\([^\)]*\))/g 1051 }, 1052 1053 /** 1054 * Mapping of shorthand tokens to corresponding attribute selector 1055 * @property shorthand 1056 * @type object 1057 */ 1058 shorthand: { 1059 //'\\#([^\\s\\\\(\\[:]*)': '[id=$1]', 1060 '\\#(-?[_a-z]+[-\\w\\uE000]*)': '[id=$1]', 1061 //'\\#([^\\s\\\.:\\[\\]]*)': '[id=$1]', 1062 //'\\.([^\\s\\\\(\\[:]*)': '[className=$1]' 1063 '\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]' 1064 }, 1065 1066 /** 1067 * List of operators and corresponding boolean functions. 1068 * These functions are passed the attribute and the current node's value of the attribute. 1069 * @property operators 1070 * @type object 1071 */ 1072 operators: { 1073 '': function(node, attr) { return !!node.getAttribute(attr); }, // Just test for existence of attribute 1074 //'': '.+', 1075 //'=': '^{val}$', // equality 1076 '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited 1077 '|=': '^{val}(?:-|$)' // optional hyphen-delimited 1078 }, 1079 1080 pseudos: { 1081 'first-child': function(node) { 1082 return Selector._children(node[PARENT_NODE])[0] === node; 1083 } 1084 }, 1085 1086 _bruteQuery: function(selector, root, firstOnly) { 1087 var ret = [], 1088 nodes = [], 1089 tokens = Selector._tokenize(selector), 1090 token = tokens[tokens.length - 1], 1091 rootDoc = Y_getDoc(root), 1092 child, 1093 id, 1094 className, 1095 tagName; 1096 1097 1098 // if we have an initial ID, set to root when in document 1099 /* 1100 if (tokens[0] && rootDoc === root && 1101 (id = tokens[0].id) && 1102 rootDoc.getElementById(id)) { 1103 root = rootDoc.getElementById(id); 1104 } 1105 */ 1106 1107 if (token) { 1108 // prefilter nodes 1109 id = token.id; 1110 className = token.className; 1111 tagName = token.tagName || '*'; 1112 1113 if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags 1114 // try ID first, unless no root.all && root not in document 1115 // (root.all works off document, but not getElementById) 1116 // TODO: move to allById? 1117 if (id && (root.all || (root.nodeType === 9 || Y_DOM_inDoc(root)))) { 1118 nodes = Y_DOM_allById(id, root); 1119 // try className 1120 } else if (className) { 1121 nodes = root.getElementsByClassName(className); 1122 } else { // default to tagName 1123 nodes = root.getElementsByTagName(tagName); 1124 } 1125 1126 } else { // brute getElementsByTagName('*') 1127 child = root.firstChild; 1128 while (child) { 1129 if (child.tagName) { // only collect HTMLElements 1130 nodes.push(child); 1131 } 1132 child = child.nextSilbing || child.firstChild; 1133 } 1134 } 1135 if (nodes.length) { 1136 ret = Selector._filterNodes(nodes, tokens, firstOnly); 1137 } 1138 } 1139 1140 return ret; 1141 }, 1142 1143 _filterNodes: function(nodes, tokens, firstOnly) { 1144 var i = 0, 1145 j, 1146 len = tokens.length, 1147 n = len - 1, 1148 result = [], 1149 node = nodes[0], 1150 tmpNode = node, 1151 getters = Selector.getters, 1152 operator, 1153 combinator, 1154 token, 1155 path, 1156 pass, 1157 //FUNCTION = 'function', 1158 value, 1159 tests, 1160 test; 1161 1162 //do { 1163 for (i = 0; (tmpNode = node = nodes[i++]);) { 1164 n = len - 1; 1165 path = null; 1166 1167 testLoop: 1168 while (tmpNode && tmpNode.tagName) { 1169 token = tokens[n]; 1170 tests = token.tests; 1171 j = tests.length; 1172 if (j && !pass) { 1173 while ((test = tests[--j])) { 1174 operator = test[1]; 1175 if (getters[test[0]]) { 1176 value = getters[test[0]](tmpNode, test[0]); 1177 } else { 1178 value = tmpNode[test[0]]; 1179 // use getAttribute for non-standard attributes 1180 if (value === undefined && tmpNode.getAttribute) { 1181 value = tmpNode.getAttribute(test[0]); 1182 } 1183 } 1184 1185 if ((operator === '=' && value !== test[2]) || // fast path for equality 1186 (typeof operator !== 'string' && // protect against String.test monkey-patch (Moo) 1187 operator.test && !operator.test(value)) || // regex test 1188 (!operator.test && // protect against RegExp as function (webkit) 1189 typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test 1190 1191 // skip non element nodes or non-matching tags 1192 if ((tmpNode = tmpNode[path])) { 1193 while (tmpNode && 1194 (!tmpNode.tagName || 1195 (token.tagName && token.tagName !== tmpNode.tagName)) 1196 ) { 1197 tmpNode = tmpNode[path]; 1198 } 1199 } 1200 continue testLoop; 1201 } 1202 } 1203 } 1204 1205 n--; // move to next token 1206 // now that we've passed the test, move up the tree by combinator 1207 if (!pass && (combinator = token.combinator)) { 1208 path = combinator.axis; 1209 tmpNode = tmpNode[path]; 1210 1211 // skip non element nodes 1212 while (tmpNode && !tmpNode.tagName) { 1213 tmpNode = tmpNode[path]; 1214 } 1215 1216 if (combinator.direct) { // one pass only 1217 path = null; 1218 } 1219 1220 } else { // success if we made it this far 1221 result.push(node); 1222 if (firstOnly) { 1223 return result; 1224 } 1225 break; 1226 } 1227 } 1228 }// while (tmpNode = node = nodes[++i]); 1229 node = tmpNode = null; 1230 return result; 1231 }, 1232 1233 combinators: { 1234 ' ': { 1235 axis: 'parentNode' 1236 }, 1237 1238 '>': { 1239 axis: 'parentNode', 1240 direct: true 1241 }, 1242 1243 1244 '+': { 1245 axis: 'previousSibling', 1246 direct: true 1247 } 1248 }, 1249 1250 _parsers: [ 1251 { 1252 name: ATTRIBUTES, 1253 //re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i, 1254 re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i, 1255 fn: function(match, token) { 1256 var operator = match[2] || '', 1257 operators = Selector.operators, 1258 escVal = (match[3]) ? match[3].replace(/\\/g, '') : '', 1259 test; 1260 1261 // add prefiltering for ID and CLASS 1262 if ((match[1] === 'id' && operator === '=') || 1263 (match[1] === 'className' && 1264 Y_DOCUMENT_ELEMENT.getElementsByClassName && 1265 (operator === '~=' || operator === '='))) { 1266 token.prefilter = match[1]; 1267 1268 1269 match[3] = escVal; 1270 1271 // escape all but ID for prefilter, which may run through QSA (via Dom.allById) 1272 token[match[1]] = (match[1] === 'id') ? match[3] : escVal; 1273 1274 } 1275 1276 // add tests 1277 if (operator in operators) { 1278 test = operators[operator]; 1279 if (typeof test === 'string') { 1280 match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1'); 1281 test = new RegExp(test.replace('{val}', match[3])); 1282 } 1283 match[2] = test; 1284 } 1285 if (!token.last || token.prefilter !== match[1]) { 1286 return match.slice(1); 1287 } 1288 } 1289 1290 }, 1291 { 1292 name: TAG_NAME, 1293 re: /^((?:-?[_a-z]+[\w-]*)|\*)/i, 1294 fn: function(match, token) { 1295 var tag = match[1].toUpperCase(); 1296 token.tagName = tag; 1297 1298 if (tag !== '*' && (!token.last || token.prefilter)) { 1299 return [TAG_NAME, '=', tag]; 1300 } 1301 if (!token.prefilter) { 1302 token.prefilter = 'tagName'; 1303 } 1304 } 1305 }, 1306 { 1307 name: COMBINATOR, 1308 re: /^\s*([>+~]|\s)\s*/, 1309 fn: function(match, token) { 1310 } 1311 }, 1312 { 1313 name: PSEUDOS, 1314 re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i, 1315 fn: function(match, token) { 1316 var test = Selector[PSEUDOS][match[1]]; 1317 if (test) { // reorder match array and unescape special chars for tests 1318 if (match[2]) { 1319 match[2] = match[2].replace(/\\/g, ''); 1320 } 1321 return [match[2], test]; 1322 } else { // selector token not supported (possibly missing CSS3 module) 1323 return false; 1324 } 1325 } 1326 } 1327 ], 1328 1329 _getToken: function(token) { 1330 return { 1331 tagName: null, 1332 id: null, 1333 className: null, 1334 attributes: {}, 1335 combinator: null, 1336 tests: [] 1337 }; 1338 }, 1339 1340 /** 1341 Break selector into token units per simple selector. 1342 Combinator is attached to the previous token. 1343 */ 1344 _tokenize: function(selector) { 1345 selector = selector || ''; 1346 selector = Selector._replaceShorthand(Y_Lang.trim(selector)); 1347 var token = Selector._getToken(), // one token per simple selector (left selector holds combinator) 1348 query = selector, // original query for debug report 1349 tokens = [], // array of tokens 1350 found = false, // whether or not any matches were found this pass 1351 match, // the regex match 1352 test, 1353 i, parser; 1354 1355 /* 1356 Search for selector patterns, store, and strip them from the selector string 1357 until no patterns match (invalid selector) or we run out of chars. 1358 1359 Multiple attributes and pseudos are allowed, in any order. 1360 for example: 1361 'form:first-child[type=button]:not(button)[lang|=en]' 1362 */ 1363 1364 outer: 1365 do { 1366 found = false; // reset after full pass 1367 1368 for (i = 0; (parser = Selector._parsers[i++]);) { 1369 if ( (match = parser.re.exec(selector)) ) { // note assignment 1370 if (parser.name !== COMBINATOR ) { 1371 token.selector = selector; 1372 } 1373 selector = selector.replace(match[0], ''); // strip current match from selector 1374 if (!selector.length) { 1375 token.last = true; 1376 } 1377 1378 if (Selector._attrFilters[match[1]]) { // convert class to className, etc. 1379 match[1] = Selector._attrFilters[match[1]]; 1380 } 1381 1382 test = parser.fn(match, token); 1383 if (test === false) { // selector not supported 1384 found = false; 1385 break outer; 1386 } else if (test) { 1387 token.tests.push(test); 1388 } 1389 1390 if (!selector.length || parser.name === COMBINATOR) { 1391 tokens.push(token); 1392 token = Selector._getToken(token); 1393 if (parser.name === COMBINATOR) { 1394 token.combinator = Selector.combinators[match[1]]; 1395 } 1396 } 1397 found = true; 1398 1399 1400 } 1401 } 1402 } while (found && selector.length); 1403 1404 if (!found || selector.length) { // not fully parsed 1405 Y.log('query: ' + query + ' contains unsupported token in: ' + selector, 'warn', 'Selector'); 1406 tokens = []; 1407 } 1408 return tokens; 1409 }, 1410 1411 _replaceShorthand: function(selector) { 1412 var shorthand = Selector.shorthand, 1413 esc = selector.match(Selector._re.esc), // pull escaped colon, brackets, etc. 1414 attrs, 1415 pseudos, 1416 re, i, len; 1417 1418 if (esc) { 1419 selector = selector.replace(Selector._re.esc, '\uE000'); 1420 } 1421 1422 attrs = selector.match(Selector._re.attr); 1423 pseudos = selector.match(Selector._re.pseudos); 1424 1425 if (attrs) { 1426 selector = selector.replace(Selector._re.attr, '\uE001'); 1427 } 1428 1429 if (pseudos) { 1430 selector = selector.replace(Selector._re.pseudos, '\uE002'); 1431 } 1432 1433 1434 for (re in shorthand) { 1435 if (shorthand.hasOwnProperty(re)) { 1436 selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]); 1437 } 1438 } 1439 1440 if (attrs) { 1441 for (i = 0, len = attrs.length; i < len; ++i) { 1442 selector = selector.replace(/\uE001/, attrs[i]); 1443 } 1444 } 1445 1446 if (pseudos) { 1447 for (i = 0, len = pseudos.length; i < len; ++i) { 1448 selector = selector.replace(/\uE002/, pseudos[i]); 1449 } 1450 } 1451 1452 selector = selector.replace(/\[/g, '\uE003'); 1453 selector = selector.replace(/\]/g, '\uE004'); 1454 1455 selector = selector.replace(/\(/g, '\uE005'); 1456 selector = selector.replace(/\)/g, '\uE006'); 1457 1458 if (esc) { 1459 for (i = 0, len = esc.length; i < len; ++i) { 1460 selector = selector.replace('\uE000', esc[i]); 1461 } 1462 } 1463 1464 return selector; 1465 }, 1466 1467 _attrFilters: { 1468 'class': 'className', 1469 'for': 'htmlFor' 1470 }, 1471 1472 getters: { 1473 href: function(node, attr) { 1474 return Y_DOM.getAttribute(node, attr); 1475 } 1476 } 1477 }; 1478 1479 Y_mix(Selector, SelectorCSS2, true); 1480 Selector.getters.src = Selector.getters.rel = Selector.getters.href; 1481 1482 // IE wants class with native queries 1483 if (Selector.useNative && Y_DOC.querySelector) { 1484 Selector.shorthand['\\.([^\\s\\\\(\\[:]*)'] = '[class~=$1]'; 1485 } 1486 1487 /** 1488 * The selector css3 module provides support for css3 selectors. 1489 * @module dom 1490 * @submodule selector-css3 1491 * @for Selector 1492 */ 1493 1494 /* 1495 an+b = get every _a_th node starting at the _b_th 1496 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 1497 1n+b = get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n") 1498 an+0 = get every _a_th element, "0" may be omitted 1499 */ 1500 1501 Selector._reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/; 1502 1503 Selector._getNth = function(node, expr, tag, reverse) { 1504 Selector._reNth.test(expr); 1505 var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_) 1506 n = RegExp.$2, // "n" 1507 oddeven = RegExp.$3, // "odd" or "even" 1508 b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_ 1509 result = [], 1510 siblings = Selector._children(node.parentNode, tag), 1511 op; 1512 1513 if (oddeven) { 1514 a = 2; // always every other 1515 op = '+'; 1516 n = 'n'; 1517 b = (oddeven === 'odd') ? 1 : 0; 1518 } else if ( isNaN(a) ) { 1519 a = (n) ? 1 : 0; // start from the first or no repeat 1520 } 1521 1522 if (a === 0) { // just the first 1523 if (reverse) { 1524 b = siblings.length - b + 1; 1525 } 1526 1527 if (siblings[b - 1] === node) { 1528 return true; 1529 } else { 1530 return false; 1531 } 1532 1533 } else if (a < 0) { 1534 reverse = !!reverse; 1535 a = Math.abs(a); 1536 } 1537 1538 if (!reverse) { 1539 for (var i = b - 1, len = siblings.length; i < len; i += a) { 1540 if ( i >= 0 && siblings[i] === node ) { 1541 return true; 1542 } 1543 } 1544 } else { 1545 for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) { 1546 if ( i < len && siblings[i] === node ) { 1547 return true; 1548 } 1549 } 1550 } 1551 return false; 1552 }; 1553 1554 Y_mix(Selector.pseudos, { 1555 'root': function(node) { 1556 return node === node.ownerDocument.documentElement; 1557 }, 1558 1559 'nth-child': function(node, expr) { 1560 return Selector._getNth(node, expr); 1561 }, 1562 1563 'nth-last-child': function(node, expr) { 1564 return Selector._getNth(node, expr, null, true); 1565 }, 1566 1567 'nth-of-type': function(node, expr) { 1568 return Selector._getNth(node, expr, node.tagName); 1569 }, 1570 1571 'nth-last-of-type': function(node, expr) { 1572 return Selector._getNth(node, expr, node.tagName, true); 1573 }, 1574 1575 'last-child': function(node) { 1576 var children = Selector._children(node.parentNode); 1577 return children[children.length - 1] === node; 1578 }, 1579 1580 'first-of-type': function(node) { 1581 return Selector._children(node.parentNode, node.tagName)[0] === node; 1582 }, 1583 1584 'last-of-type': function(node) { 1585 var children = Selector._children(node.parentNode, node.tagName); 1586 return children[children.length - 1] === node; 1587 }, 1588 1589 'only-child': function(node) { 1590 var children = Selector._children(node.parentNode); 1591 return children.length === 1 && children[0] === node; 1592 }, 1593 1594 'only-of-type': function(node) { 1595 var children = Selector._children(node.parentNode, node.tagName); 1596 return children.length === 1 && children[0] === node; 1597 }, 1598 1599 'empty': function(node) { 1600 return node.childNodes.length === 0; 1601 }, 1602 1603 'not': function(node, expr) { 1604 return !Selector.test(node, expr); 1605 }, 1606 1607 'contains': function(node, expr) { 1608 var text = node.innerText || node.textContent || ''; 1609 return text.indexOf(expr) > -1; 1610 }, 1611 1612 'checked': function(node) { 1613 return (node.checked === true || node.selected === true); 1614 }, 1615 1616 enabled: function(node) { 1617 return (node.disabled !== undefined && !node.disabled); 1618 }, 1619 1620 disabled: function(node) { 1621 return (node.disabled); 1622 } 1623 }); 1624 1625 Y_mix(Selector.operators, { 1626 '^=': '^{val}', // Match starts with value 1627 '!=': function(node, attr, val) { return node[attr] !== val; }, // Match starts with value 1628 '$=': '{val}$', // Match ends with value 1629 '*=': '{val}' // Match contains value as substring 1630 }); 1631 1632 Selector.combinators['~'] = { 1633 axis: 'previousSibling' 1634 }; 1635 YAHOO.register("selector", YAHOO.util.Selector, {version: "2.9.0", build: "2800"}); 1636 1637 1638 1639 /****************************************************************************/ 1640 /****************************************************************************/ 1641 /****************************************************************************/ 1642 1643 var Dom = YAHOO.util.Dom; 1644 1645 /** 1646 * The ColumnSet class defines and manages a DataTable's Columns, 1647 * including nested hierarchies and access to individual Column instances. 1648 * 1649 * @namespace YAHOO.widget 1650 * @class ColumnSet 1651 * @uses YAHOO.util.EventProvider 1652 * @constructor 1653 * @param aDefinitions {Object[]} Array of object literals that define cells in 1654 * the THEAD. 1655 */ 1656 YAHOO.widget.ColumnSet = function(aDefinitions) { 1657 this._sId = Dom.generateId(null, "yui-cs"); // "yui-cs" + YAHOO.widget.ColumnSet._nCount; 1658 1659 // First clone the defs 1660 aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions); 1661 this._init(aDefinitions); 1662 1663 YAHOO.widget.ColumnSet._nCount++; 1664 }; 1665 1666 ///////////////////////////////////////////////////////////////////////////// 1667 // 1668 // Private member variables 1669 // 1670 ///////////////////////////////////////////////////////////////////////////// 1671 1672 /** 1673 * Internal class variable to index multiple ColumnSet instances. 1674 * 1675 * @property ColumnSet._nCount 1676 * @type Number 1677 * @private 1678 * @static 1679 */ 1680 YAHOO.widget.ColumnSet._nCount = 0; 1681 1682 YAHOO.widget.ColumnSet.prototype = { 1683 /** 1684 * Unique instance name. 1685 * 1686 * @property _sId 1687 * @type String 1688 * @private 1689 */ 1690 _sId : null, 1691 1692 /** 1693 * Array of object literal Column definitions passed to the constructor. 1694 * 1695 * @property _aDefinitions 1696 * @type Object[] 1697 * @private 1698 */ 1699 _aDefinitions : null, 1700 1701 ///////////////////////////////////////////////////////////////////////////// 1702 // 1703 // Public member variables 1704 // 1705 ///////////////////////////////////////////////////////////////////////////// 1706 1707 /** 1708 * Top-down tree representation of Column hierarchy. 1709 * 1710 * @property tree 1711 * @type YAHOO.widget.Column[] 1712 */ 1713 tree : null, 1714 1715 /** 1716 * Flattened representation of all Columns. 1717 * 1718 * @property flat 1719 * @type YAHOO.widget.Column[] 1720 * @default [] 1721 */ 1722 flat : null, 1723 1724 /** 1725 * Array of Columns that map one-to-one to a table column. 1726 * 1727 * @property keys 1728 * @type YAHOO.widget.Column[] 1729 * @default [] 1730 */ 1731 keys : null, 1732 1733 /** 1734 * ID index of nested parent hierarchies for HEADERS accessibility attribute. 1735 * 1736 * @property headers 1737 * @type String[] 1738 * @default [] 1739 */ 1740 headers : null, 1741 1742 ///////////////////////////////////////////////////////////////////////////// 1743 // 1744 // Private methods 1745 // 1746 ///////////////////////////////////////////////////////////////////////////// 1747 1748 /** 1749 * Initializes ColumnSet instance with data from Column definitions. 1750 * 1751 * @method _init 1752 * @param aDefinitions {Object[]} Array of object literals that define cells in 1753 * the THEAD . 1754 * @private 1755 */ 1756 1757 _init : function(aDefinitions) { 1758 // DOM tree representation of all Columns 1759 var tree = []; 1760 // Flat representation of all Columns 1761 var flat = []; 1762 // Flat representation of only Columns that are meant to display data 1763 var keys = []; 1764 // Array of HEADERS attribute values for all keys in the "keys" array 1765 var headers = []; 1766 1767 // Tracks current node list depth being tracked 1768 var nodeDepth = -1; 1769 1770 // Internal recursive function to define Column instances 1771 var parseColumns = function(nodeList, parent) { 1772 // One level down 1773 nodeDepth++; 1774 1775 // Create corresponding tree node if not already there for this depth 1776 if(!tree[nodeDepth]) { 1777 tree[nodeDepth] = []; 1778 } 1779 1780 1781 // Parse each node at this depth for attributes and any children 1782 for(var j=0; j<nodeList.length; j++) { 1783 var currentNode = nodeList[j]; 1784 1785 // Instantiate a new Column for each node 1786 var oColumn = new YAHOO.widget.Column(currentNode); 1787 1788 // Cross-reference Column ID back to the original object literal definition 1789 currentNode.yuiColumnId = oColumn._sId; 1790 1791 // Add the new Column to the flat list 1792 flat.push(oColumn); 1793 1794 // Assign its parent as an attribute, if applicable 1795 if(parent) { 1796 oColumn._oParent = parent; 1797 } 1798 1799 // The Column has descendants 1800 if(YAHOO.lang.isArray(currentNode.children)) { 1801 oColumn.children = currentNode.children; 1802 1803 // Determine COLSPAN value for this Column 1804 var terminalChildNodes = 0; 1805 var countTerminalChildNodes = function(ancestor) { 1806 var descendants = ancestor.children; 1807 // Drill down each branch and count terminal nodes 1808 for(var k=0; k<descendants.length; k++) { 1809 // Keep drilling down 1810 if(YAHOO.lang.isArray(descendants[k].children)) { 1811 countTerminalChildNodes(descendants[k]); 1812 } 1813 // Reached branch terminus 1814 else { 1815 terminalChildNodes++; 1816 } 1817 } 1818 }; 1819 countTerminalChildNodes(currentNode); 1820 oColumn._nColspan = terminalChildNodes; 1821 1822 // Cascade certain properties to children if not defined on their own 1823 var currentChildren = currentNode.children; 1824 for(var k=0; k<currentChildren.length; k++) { 1825 var child = currentChildren[k]; 1826 if(oColumn.className && (child.className === undefined)) { 1827 child.className = oColumn.className; 1828 } 1829 if(oColumn.editor && (child.editor === undefined)) { 1830 child.editor = oColumn.editor; 1831 } 1832 //TODO: Deprecated 1833 if(oColumn.editorOptions && (child.editorOptions === undefined)) { 1834 child.editorOptions = oColumn.editorOptions; 1835 } 1836 if(oColumn.formatter && (child.formatter === undefined)) { 1837 child.formatter = oColumn.formatter; 1838 } 1839 if(oColumn.resizeable && (child.resizeable === undefined)) { 1840 child.resizeable = oColumn.resizeable; 1841 } 1842 if(oColumn.sortable && (child.sortable === undefined)) { 1843 child.sortable = oColumn.sortable; 1844 } 1845 if(oColumn.hidden) { 1846 child.hidden = true; 1847 } 1848 if(oColumn.width && (child.width === undefined)) { 1849 child.width = oColumn.width; 1850 } 1851 if(oColumn.minWidth && (child.minWidth === undefined)) { 1852 child.minWidth = oColumn.minWidth; 1853 } 1854 if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) { 1855 child.maxAutoWidth = oColumn.maxAutoWidth; 1856 } 1857 // Backward compatibility 1858 if(oColumn.type && (child.type === undefined)) { 1859 child.type = oColumn.type; 1860 } 1861 if(oColumn.type && !oColumn.formatter) { 1862 oColumn.formatter = oColumn.type; 1863 } 1864 if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) { 1865 oColumn.label = oColumn.text; 1866 } 1867 if(oColumn.parser) { 1868 } 1869 if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) || 1870 (oColumn.sortOptions.descFunction))) { 1871 } 1872 } 1873 1874 // The children themselves must also be parsed for Column instances 1875 if(!tree[nodeDepth+1]) { 1876 tree[nodeDepth+1] = []; 1877 } 1878 parseColumns(currentChildren, oColumn); 1879 } 1880 // This Column does not have any children 1881 else { 1882 oColumn._nKeyIndex = keys.length; 1883 oColumn._nColspan = 1; 1884 keys.push(oColumn); 1885 } 1886 1887 // Add the Column to the top-down tree 1888 tree[nodeDepth].push(oColumn); 1889 } 1890 nodeDepth--; 1891 }; 1892 1893 // Parse out Column instances from the array of object literals 1894 if(YAHOO.lang.isArray(aDefinitions)) { 1895 parseColumns(aDefinitions); 1896 1897 // Store the array 1898 this._aDefinitions = aDefinitions; 1899 } 1900 else { 1901 return null; 1902 } 1903 1904 var i; 1905 1906 // Determine ROWSPAN value for each Column in the tree 1907 var parseTreeForRowspan = function(tree) { 1908 var maxRowDepth = 1; 1909 var currentRow; 1910 var currentColumn; 1911 1912 // Calculate the max depth of descendants for this row 1913 var countMaxRowDepth = function(row, tmpRowDepth) { 1914 tmpRowDepth = tmpRowDepth || 1; 1915 1916 for(var n=0; n<row.length; n++) { 1917 var col = row[n]; 1918 // Column has children, so keep counting 1919 if(YAHOO.lang.isArray(col.children)) { 1920 tmpRowDepth++; 1921 countMaxRowDepth(col.children, tmpRowDepth); 1922 tmpRowDepth--; 1923 } 1924 // No children, is it the max depth? 1925 else { 1926 if(tmpRowDepth > maxRowDepth) { 1927 maxRowDepth = tmpRowDepth; 1928 } 1929 } 1930 1931 } 1932 }; 1933 1934 // Count max row depth for each row 1935 for(var m=0; m<tree.length; m++) { 1936 currentRow = tree[m]; 1937 countMaxRowDepth(currentRow); 1938 1939 // Assign the right ROWSPAN values to each Column in the row 1940 for(var p=0; p<currentRow.length; p++) { 1941 currentColumn = currentRow[p]; 1942 if(!YAHOO.lang.isArray(currentColumn.children)) { 1943 currentColumn._nRowspan = maxRowDepth; 1944 } 1945 else { 1946 currentColumn._nRowspan = 1; 1947 } 1948 } 1949 1950 // Reset counter for next row 1951 maxRowDepth = 1; 1952 } 1953 }; 1954 parseTreeForRowspan(tree); 1955 1956 // Store tree index values 1957 for(i=0; i<tree[0].length; i++) { 1958 tree[0][i]._nTreeIndex = i; 1959 } 1960 1961 // Store header relationships in an array for HEADERS attribute 1962 var recurseAncestorsForHeaders = function(i, oColumn) { 1963 headers[i].push(oColumn.getSanitizedKey()); 1964 if(oColumn._oParent) { 1965 recurseAncestorsForHeaders(i, oColumn._oParent); 1966 } 1967 }; 1968 for(i=0; i<keys.length; i++) { 1969 headers[i] = []; 1970 recurseAncestorsForHeaders(i, keys[i]); 1971 headers[i] = headers[i].reverse(); 1972 } 1973 1974 // Save to the ColumnSet instance 1975 this.tree = tree; 1976 this.flat = flat; 1977 this.keys = keys; 1978 this.headers = headers; 1979 }, 1980 1981 ///////////////////////////////////////////////////////////////////////////// 1982 // 1983 // Public methods 1984 // 1985 ///////////////////////////////////////////////////////////////////////////// 1986 1987 /** 1988 * Returns unique name of the ColumnSet instance. 1989 * 1990 * @method getId 1991 * @return {String} Unique name of the ColumnSet instance. 1992 */ 1993 1994 getId : function() { 1995 return this._sId; 1996 }, 1997 1998 /** 1999 * ColumnSet instance name, for logging. 2000 * 2001 * @method toString 2002 * @return {String} Unique name of the ColumnSet instance. 2003 */ 2004 2005 toString : function() { 2006 return "ColumnSet instance " + this._sId; 2007 }, 2008 2009 /** 2010 * Public accessor to the definitions array. 2011 * 2012 * @method getDefinitions 2013 * @return {Object[]} Array of object literal Column definitions. 2014 */ 2015 2016 getDefinitions : function() { 2017 var aDefinitions = this._aDefinitions; 2018 2019 // Internal recursive function to define Column instances 2020 var parseColumns = function(nodeList, oSelf) { 2021 // Parse each node at this depth for attributes and any children 2022 for(var j=0; j<nodeList.length; j++) { 2023 var currentNode = nodeList[j]; 2024 2025 // Get the Column for each node 2026 var oColumn = oSelf.getColumnById(currentNode.yuiColumnId); 2027 2028 if(oColumn) { 2029 // Update the current values 2030 var oDefinition = oColumn.getDefinition(); 2031 for(var name in oDefinition) { 2032 if(YAHOO.lang.hasOwnProperty(oDefinition, name)) { 2033 currentNode[name] = oDefinition[name]; 2034 } 2035 } 2036 } 2037 2038 // The Column has descendants 2039 if(YAHOO.lang.isArray(currentNode.children)) { 2040 // The children themselves must also be parsed for Column instances 2041 parseColumns(currentNode.children, oSelf); 2042 } 2043 } 2044 }; 2045 2046 parseColumns(aDefinitions, this); 2047 this._aDefinitions = aDefinitions; 2048 return aDefinitions; 2049 }, 2050 2051 /** 2052 * Returns Column instance with given ID. 2053 * 2054 * @method getColumnById 2055 * @param column {String} Column ID. 2056 * @return {YAHOO.widget.Column} Column instance. 2057 */ 2058 2059 getColumnById : function(column) { 2060 if(YAHOO.lang.isString(column)) { 2061 var allColumns = this.flat; 2062 for(var i=allColumns.length-1; i>-1; i--) { 2063 if(allColumns[i]._sId === column) { 2064 return allColumns[i]; 2065 } 2066 } 2067 } 2068 return null; 2069 }, 2070 2071 /** 2072 * Returns Column instance with given key or ColumnSet key index. 2073 * 2074 * @method getColumn 2075 * @param column {String | Number} Column key or ColumnSet key index. 2076 * @return {YAHOO.widget.Column} Column instance. 2077 */ 2078 2079 getColumn : function(column) { 2080 if(YAHOO.lang.isNumber(column) && this.keys[column]) { 2081 return this.keys[column]; 2082 } 2083 else if(YAHOO.lang.isString(column)) { 2084 var allColumns = this.flat; 2085 var aColumns = []; 2086 for(var i=0; i<allColumns.length; i++) { 2087 if(allColumns[i].key === column) { 2088 aColumns.push(allColumns[i]); 2089 } 2090 } 2091 if(aColumns.length === 1) { 2092 return aColumns[0]; 2093 } 2094 else if(aColumns.length > 1) { 2095 return aColumns; 2096 } 2097 } 2098 return null; 2099 }, 2100 2101 /** 2102 * Public accessor returns array of given Column's desendants (if any), including itself. 2103 * 2104 * @method getDescendants 2105 * @parem {YAHOO.widget.Column} Column instance. 2106 * @return {Array} Array including the Column itself and all descendants (if any). 2107 */ 2108 getDescendants : function(oColumn) { 2109 var oSelf = this; 2110 var allDescendants = []; 2111 var i; 2112 2113 // Recursive function to loop thru all children 2114 var parse = function(oParent) { 2115 allDescendants.push(oParent); 2116 // This Column has children 2117 if(oParent.children) { 2118 for(i=0; i<oParent.children.length; i++) { 2119 parse(oSelf.getColumn(oParent.children[i].key)); 2120 } 2121 } 2122 }; 2123 parse(oColumn); 2124 2125 return allDescendants; 2126 } 2127 }; 2128 2129 /****************************************************************************/ 2130 /****************************************************************************/ 2131 /****************************************************************************/ 2132 2133 /** 2134 * The Column class defines and manages attributes of DataTable Columns 2135 * 2136 * @namespace YAHOO.widget 2137 * @class Column 2138 * @constructor 2139 * @param oConfigs {Object} Object literal of definitions. 2140 */ 2141 YAHOO.widget.Column = function(oConfigs) { 2142 this._sId = Dom.generateId(null, "yui-col"); // "yui-col" + YAHOO.widget.Column._nCount; 2143 2144 // Object literal defines Column attributes 2145 if(oConfigs && YAHOO.lang.isObject(oConfigs)) { 2146 for(var sConfig in oConfigs) { 2147 if(sConfig) { 2148 this[sConfig] = oConfigs[sConfig]; 2149 } 2150 } 2151 } 2152 2153 // Assign a key if not found 2154 if(!YAHOO.lang.isValue(this.key)) { 2155 this.key = Dom.generateId(null, "yui-dt-col"); //"yui-dt-col" + YAHOO.widget.Column._nCount; 2156 } 2157 2158 // Assign a field if not found, defaults to key 2159 if(!YAHOO.lang.isValue(this.field)) { 2160 this.field = this.key; 2161 } 2162 2163 // Increment counter 2164 YAHOO.widget.Column._nCount++; 2165 2166 // Backward compatibility 2167 if(this.width && !YAHOO.lang.isNumber(this.width)) { 2168 this.width = null; 2169 } 2170 if(this.editor && YAHOO.lang.isString(this.editor)) { 2171 this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions); 2172 } 2173 }; 2174 2175 ///////////////////////////////////////////////////////////////////////////// 2176 // 2177 // Private member variables 2178 // 2179 ///////////////////////////////////////////////////////////////////////////// 2180 2181 YAHOO.lang.augmentObject(YAHOO.widget.Column, { 2182 /** 2183 * Internal class variable to index multiple Column instances. 2184 * 2185 * @property Column._nCount 2186 * @type Number 2187 * @private 2188 * @static 2189 */ 2190 _nCount : 0, 2191 2192 formatCheckbox : function(elCell, oRecord, oColumn, oData) { 2193 YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData); 2194 }, 2195 2196 formatCurrency : function(elCell, oRecord, oColumn, oData) { 2197 YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData); 2198 }, 2199 2200 formatDate : function(elCell, oRecord, oColumn, oData) { 2201 YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData); 2202 }, 2203 2204 formatEmail : function(elCell, oRecord, oColumn, oData) { 2205 YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData); 2206 }, 2207 2208 formatLink : function(elCell, oRecord, oColumn, oData) { 2209 YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData); 2210 }, 2211 2212 formatNumber : function(elCell, oRecord, oColumn, oData) { 2213 YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData); 2214 }, 2215 2216 formatSelect : function(elCell, oRecord, oColumn, oData) { 2217 YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData); 2218 } 2219 }); 2220 2221 YAHOO.widget.Column.prototype = { 2222 /** 2223 * Unique String identifier assigned at instantiation. 2224 * 2225 * @property _sId 2226 * @type String 2227 * @private 2228 */ 2229 _sId : null, 2230 2231 /** 2232 * Reference to Column's current position index within its ColumnSet's keys 2233 * array, if applicable. This property only applies to non-nested and bottom- 2234 * level child Columns. 2235 * 2236 * @property _nKeyIndex 2237 * @type Number 2238 * @private 2239 */ 2240 _nKeyIndex : null, 2241 2242 /** 2243 * Reference to Column's current position index within its ColumnSet's tree 2244 * array, if applicable. This property only applies to non-nested and top- 2245 * level parent Columns. 2246 * 2247 * @property _nTreeIndex 2248 * @type Number 2249 * @private 2250 */ 2251 _nTreeIndex : null, 2252 2253 /** 2254 * Number of table cells the Column spans. 2255 * 2256 * @property _nColspan 2257 * @type Number 2258 * @private 2259 */ 2260 _nColspan : 1, 2261 2262 /** 2263 * Number of table rows the Column spans. 2264 * 2265 * @property _nRowspan 2266 * @type Number 2267 * @private 2268 */ 2269 _nRowspan : 1, 2270 2271 /** 2272 * Column's parent Column instance, or null. 2273 * 2274 * @property _oParent 2275 * @type YAHOO.widget.Column 2276 * @private 2277 */ 2278 _oParent : null, 2279 2280 /** 2281 * The DOM reference to the associated TH element. 2282 * 2283 * @property _elTh 2284 * @type HTMLElement 2285 * @private 2286 */ 2287 _elTh : null, 2288 2289 /** 2290 * The DOM reference to the associated TH element's liner DIV element. 2291 * 2292 * @property _elThLiner 2293 * @type HTMLElement 2294 * @private 2295 */ 2296 _elThLiner : null, 2297 2298 /** 2299 * The DOM reference to the associated TH element's label SPAN element. 2300 * 2301 * @property _elThLabel 2302 * @type HTMLElement 2303 * @private 2304 */ 2305 _elThLabel : null, 2306 2307 /** 2308 * The DOM reference to the associated resizerelement (if any). 2309 * 2310 * @property _elResizer 2311 * @type HTMLElement 2312 * @private 2313 */ 2314 _elResizer : null, 2315 2316 /** 2317 * Internal width tracker. 2318 * 2319 * @property _nWidth 2320 * @type Number 2321 * @private 2322 */ 2323 _nWidth : null, 2324 2325 /** 2326 * For unreg() purposes, a reference to the Column's DragDrop instance. 2327 * 2328 * @property _dd 2329 * @type YAHOO.util.DragDrop 2330 * @private 2331 */ 2332 _dd : null, 2333 2334 /** 2335 * For unreg() purposes, a reference to the Column resizer's DragDrop instance. 2336 * 2337 * @property _ddResizer 2338 * @type YAHOO.util.DragDrop 2339 * @private 2340 */ 2341 _ddResizer : null, 2342 2343 ///////////////////////////////////////////////////////////////////////////// 2344 // 2345 // Public member variables 2346 // 2347 ///////////////////////////////////////////////////////////////////////////// 2348 2349 /** 2350 * Unique name, required. If "label" property is not provided, the "key" 2351 * value will be treated as markup and inserted into the DOM as innerHTML. 2352 * 2353 * @property key 2354 * @type String|HTML 2355 */ 2356 key : null, 2357 2358 /** 2359 * Associated database field, or null. 2360 * 2361 * @property field 2362 * @type String 2363 */ 2364 field : null, 2365 2366 /** 2367 * Value displayed as Column header in the TH element. String value is 2368 * treated as markup and inserted into the DOM as innerHTML. 2369 * 2370 * @property label 2371 * @type HTML 2372 */ 2373 label : null, 2374 2375 /** 2376 * Column head cell ABBR for accessibility. 2377 * 2378 * @property abbr 2379 * @type String 2380 */ 2381 abbr : null, 2382 2383 /** 2384 * Array of object literals that define children (nested headers) of a Column. 2385 * 2386 * @property children 2387 * @type Object[] 2388 */ 2389 children : null, 2390 2391 /** 2392 * Column width (in pixels). 2393 * 2394 * @property width 2395 * @type Number 2396 */ 2397 width : null, 2398 2399 /** 2400 * Minimum Column width (in pixels). 2401 * 2402 * @property minWidth 2403 * @type Number 2404 * @default null 2405 */ 2406 minWidth : null, 2407 2408 /** 2409 * When a width is not defined for a Column, maxAutoWidth defines an upper 2410 * limit that the Column should be auto-sized to. If resizeable is enabled, 2411 * users may still resize to a greater width. Most useful for Columns intended 2412 * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very 2413 * wide Columns from disrupting visual readability by inducing truncation. 2414 * 2415 * @property maxAutoWidth 2416 * @type Number 2417 * @default null 2418 */ 2419 maxAutoWidth : null, 2420 2421 /** 2422 * True if Column is in hidden state. 2423 * 2424 * @property hidden 2425 * @type Boolean 2426 * @default false 2427 */ 2428 hidden : false, 2429 2430 /** 2431 * True if Column is in selected state. 2432 * 2433 * @property selected 2434 * @type Boolean 2435 * @default false 2436 */ 2437 selected : false, 2438 2439 /** 2440 * Custom CSS class or array of classes to be applied to every cell in the Column. 2441 * 2442 * @property className 2443 * @type String || String[] 2444 */ 2445 className : null, 2446 2447 /** 2448 * Cell formatter function, or a shortcut pointer to a function in the 2449 * DataTable.Formatter object. The function, called from the DataTable's 2450 * formatCell method, renders markup into the cell liner 2451 * element and accepts the following arguments: 2452 * <dl> 2453 * <dt>elLiner</dt> 2454 * <dd>The element to write innerHTML to.</dd> 2455 * <dt>oRecord</dt> 2456 * <dd>The associated Record for the row.</dd> 2457 * <dt>oColumn</dt> 2458 * <dd>The Column instance for the cell.</dd> 2459 * <dt>oData</dt> 2460 * <dd>The data value for the cell.</dd> 2461 * </dl> 2462 * 2463 * @property formatter 2464 * @type String || HTMLFunction 2465 */ 2466 formatter : null, 2467 2468 /** 2469 * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter. 2470 * 2471 * @property currencyOptions 2472 * @type Object 2473 * @default null 2474 */ 2475 currencyOptions : null, 2476 2477 /** 2478 * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter. 2479 * 2480 * @property dateOptions 2481 * @type Object 2482 * @default null 2483 */ 2484 dateOptions : null, 2485 2486 /** 2487 * Array of dropdown values for formatter:"dropdown" cases. Can either be a 2488 * simple array (e.g., ["Alabama","Alaska","Arizona","Arkansas"]) or a an 2489 * array of objects (e.g., [{label:"Alabama", value:"AL"}, 2490 * {label:"Alaska", value:"AK"}, {label:"Arizona", value:"AZ"}, 2491 * {label:"Arkansas", value:"AR"}]). String values are treated as markup and 2492 * inserted into the DOM as innerHTML. 2493 * 2494 * @property dropdownOptions 2495 * @type HTML[] | Object[] 2496 */ 2497 dropdownOptions : null, 2498 2499 /** 2500 * A CellEditor instance, otherwise Column is not editable. 2501 * 2502 * @property editor 2503 * @type YAHOO.widget.CellEditor 2504 */ 2505 editor : null, 2506 2507 /** 2508 * True if Column is resizeable, false otherwise. The Drag & Drop Utility is 2509 * required to enable this feature. Only bottom-level and non-nested Columns are 2510 * resizeble. 2511 * 2512 * @property resizeable 2513 * @type Boolean 2514 * @default false 2515 */ 2516 resizeable : false, 2517 2518 /** 2519 * True if Column is sortable, false otherwise. 2520 * 2521 * @property sortable 2522 * @type Boolean 2523 * @default false 2524 */ 2525 sortable : false, 2526 2527 /** 2528 * @property sortOptions.defaultOrder 2529 * @deprecated Use sortOptions.defaultDir. 2530 */ 2531 /** 2532 * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC. 2533 * 2534 * @property sortOptions.defaultDir 2535 * @type String 2536 * @default null 2537 */ 2538 /** 2539 * Custom field to sort on. 2540 * 2541 * @property sortOptions.field 2542 * @type String 2543 * @default null 2544 */ 2545 /** 2546 * Custom sort handler. Signature: sortFunction(a, b, desc, field) where field is the sortOptions.field value 2547 * 2548 * @property sortOptions.sortFunction 2549 * @type Function 2550 * @default null 2551 */ 2552 sortOptions : null, 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 ///////////////////////////////////////////////////////////////////////////// 2569 // 2570 // Public methods 2571 // 2572 ///////////////////////////////////////////////////////////////////////////// 2573 2574 /** 2575 * Returns unique ID string. 2576 * 2577 * @method getId 2578 * @return {String} Unique ID string. 2579 */ 2580 getId : function() { 2581 return this._sId; 2582 }, 2583 2584 /** 2585 * Column instance name, for logging. 2586 * 2587 * @method toString 2588 * @return {String} Column's unique name. 2589 */ 2590 toString : function() { 2591 return "Column instance " + this._sId; 2592 }, 2593 2594 /** 2595 * Returns object literal definition. 2596 * 2597 * @method getDefinition 2598 * @return {Object} Object literal definition. 2599 */ 2600 getDefinition : function() { 2601 var oDefinition = {}; 2602 2603 // Update the definition 2604 oDefinition.abbr = this.abbr; 2605 oDefinition.className = this.className; 2606 oDefinition.editor = this.editor; 2607 oDefinition.editorOptions = this.editorOptions; //TODO: deprecated 2608 oDefinition.field = this.field; 2609 oDefinition.formatter = this.formatter; 2610 oDefinition.hidden = this.hidden; 2611 oDefinition.key = this.key; 2612 oDefinition.label = this.label; 2613 oDefinition.minWidth = this.minWidth; 2614 oDefinition.maxAutoWidth = this.maxAutoWidth; 2615 oDefinition.resizeable = this.resizeable; 2616 oDefinition.selected = this.selected; 2617 oDefinition.sortable = this.sortable; 2618 oDefinition.sortOptions = this.sortOptions; 2619 oDefinition.width = this.width; 2620 2621 // Bug 2529147 2622 oDefinition._calculatedWidth = this._calculatedWidth; 2623 2624 return oDefinition; 2625 }, 2626 2627 /** 2628 * Returns unique Column key. 2629 * 2630 * @method getKey 2631 * @return {String} Column key. 2632 */ 2633 getKey : function() { 2634 return this.key; 2635 }, 2636 2637 /** 2638 * Returns field. 2639 * 2640 * @method getField 2641 * @return {String} Column field. 2642 */ 2643 getField : function() { 2644 return this.field; 2645 }, 2646 2647 /** 2648 * Returns Column key which has been sanitized for DOM (class and ID) usage 2649 * starts with letter, contains only letters, numbers, hyphen, or period. 2650 * 2651 * @method getSanitizedKey 2652 * @return {String} Sanitized Column key. 2653 */ 2654 getSanitizedKey : function() { 2655 return this.getKey().replace(/[^\w\-]/g,""); 2656 }, 2657 2658 /** 2659 * Public accessor returns Column's current position index within its 2660 * ColumnSet's keys array, if applicable. Only non-nested and bottom-level 2661 * child Columns will return a value. 2662 * 2663 * @method getKeyIndex 2664 * @return {Number} Position index, or null. 2665 */ 2666 getKeyIndex : function() { 2667 return this._nKeyIndex; 2668 }, 2669 2670 /** 2671 * Public accessor returns Column's current position index within its 2672 * ColumnSet's tree array, if applicable. Only non-nested and top-level parent 2673 * Columns will return a value; 2674 * 2675 * @method getTreeIndex 2676 * @return {Number} Position index, or null. 2677 */ 2678 getTreeIndex : function() { 2679 return this._nTreeIndex; 2680 }, 2681 2682 /** 2683 * Public accessor returns Column's parent instance if any, or null otherwise. 2684 * 2685 * @method getParent 2686 * @return {YAHOO.widget.Column} Column's parent instance. 2687 */ 2688 getParent : function() { 2689 return this._oParent; 2690 }, 2691 2692 /** 2693 * Public accessor returns Column's calculated COLSPAN value. 2694 * 2695 * @method getColspan 2696 * @return {Number} Column's COLSPAN value. 2697 */ 2698 getColspan : function() { 2699 return this._nColspan; 2700 }, 2701 // Backward compatibility 2702 getColSpan : function() { 2703 return this.getColspan(); 2704 }, 2705 2706 /** 2707 * Public accessor returns Column's calculated ROWSPAN value. 2708 * 2709 * @method getRowspan 2710 * @return {Number} Column's ROWSPAN value. 2711 */ 2712 getRowspan : function() { 2713 return this._nRowspan; 2714 }, 2715 2716 /** 2717 * Returns DOM reference to the key TH element. 2718 * 2719 * @method getThEl 2720 * @return {HTMLElement} TH element. 2721 */ 2722 getThEl : function() { 2723 return this._elTh; 2724 }, 2725 2726 /** 2727 * Returns DOM reference to the TH's liner DIV element. Introduced since 2728 * resizeable Columns may have an extra resizer liner, making the DIV liner 2729 * not reliably the TH element's first child. 2730 * 2731 * @method getThLInerEl 2732 * @return {HTMLElement} TH element. 2733 */ 2734 getThLinerEl : function() { 2735 return this._elThLiner; 2736 }, 2737 2738 /** 2739 * Returns DOM reference to the resizer element, or null. 2740 * 2741 * @method getResizerEl 2742 * @return {HTMLElement} DIV element. 2743 */ 2744 getResizerEl : function() { 2745 return this._elResizer; 2746 }, 2747 2748 // Backward compatibility 2749 /** 2750 * @method getColEl 2751 * @deprecated Use getThEl 2752 */ 2753 getColEl : function() { 2754 return this.getThEl(); 2755 }, 2756 getIndex : function() { 2757 return this.getKeyIndex(); 2758 }, 2759 format : function() { 2760 } 2761 }; 2762 2763 /****************************************************************************/ 2764 /****************************************************************************/ 2765 /****************************************************************************/ 2766 2767 /** 2768 * Sort static utility to support Column sorting. 2769 * 2770 * @namespace YAHOO.util 2771 * @class Sort 2772 * @static 2773 */ 2774 YAHOO.util.Sort = { 2775 ///////////////////////////////////////////////////////////////////////////// 2776 // 2777 // Public methods 2778 // 2779 ///////////////////////////////////////////////////////////////////////////// 2780 2781 /** 2782 * Comparator function for simple case-insensitive string sorting. 2783 * 2784 * @method compare 2785 * @param a {Object} First sort argument. 2786 * @param b {Object} Second sort argument. 2787 * @param desc {Boolean} True if sort direction is descending, false if 2788 * sort direction is ascending. 2789 * @return {Boolean} Return -1 when a < b. Return 0 when a = b. 2790 * Return 1 when a > b. 2791 */ 2792 compare: function(a, b, desc) { 2793 if((a === null) || (typeof a == "undefined")) { 2794 if((b === null) || (typeof b == "undefined")) { 2795 return 0; 2796 } 2797 else { 2798 return 1; 2799 } 2800 } 2801 else if((b === null) || (typeof b == "undefined")) { 2802 return -1; 2803 } 2804 2805 if(a.constructor == String) { 2806 a = a.toLowerCase(); 2807 } 2808 if(b.constructor == String) { 2809 b = b.toLowerCase(); 2810 } 2811 if(a < b) { 2812 return (desc) ? 1 : -1; 2813 } 2814 else if (a > b) { 2815 return (desc) ? -1 : 1; 2816 } 2817 else { 2818 return 0; 2819 } 2820 } 2821 }; 2822 2823 /****************************************************************************/ 2824 /****************************************************************************/ 2825 /****************************************************************************/ 2826 2827 /** 2828 * ColumnDD subclasses DragDrop to support rearrangeable Columns. 2829 * 2830 * @namespace YAHOO.util 2831 * @class ColumnDD 2832 * @extends YAHOO.util.DDProxy 2833 * @constructor 2834 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 2835 * @param oColumn {YAHOO.widget.Column} Column instance. 2836 * @param elTh {HTMLElement} TH element reference. 2837 * @param elTarget {HTMLElement} Drag target element. 2838 */ 2839 YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) { 2840 if(oDataTable && oColumn && elTh && elTarget) { 2841 this.datatable = oDataTable; 2842 this.table = oDataTable.getTableEl(); 2843 this.column = oColumn; 2844 this.headCell = elTh; 2845 this.pointer = elTarget; 2846 this.newIndex = null; 2847 this.init(elTh); 2848 this.initFrame(); // Needed for DDProxy 2849 this.invalidHandleTypes = {}; 2850 2851 // Set top/bottom padding to account for children of nested columns 2852 this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0); 2853 2854 YAHOO.util.Event.on(window, 'resize', function() { 2855 this.initConstraints(); 2856 }, this, true); 2857 } 2858 else { 2859 } 2860 }; 2861 2862 if(YAHOO.util.DDProxy) { 2863 YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, { 2864 initConstraints: function() { 2865 //Get the top, right, bottom and left positions 2866 var region = YAHOO.util.Dom.getRegion(this.table), 2867 //Get the element we are working on 2868 el = this.getEl(), 2869 //Get the xy position of it 2870 xy = YAHOO.util.Dom.getXY(el), 2871 //Get the width and height 2872 width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10), 2873 height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10), 2874 //Set left to x minus left 2875 left = ((xy[0] - region.left) + 15), //Buffer of 15px 2876 //Set right to right minus x minus width 2877 right = ((region.right - xy[0] - width) + 15); 2878 2879 //Set the constraints based on the above calculations 2880 this.setXConstraint(left, right); 2881 this.setYConstraint(10, 10); 2882 }, 2883 _resizeProxy: function() { 2884 YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this, arguments); 2885 var dragEl = this.getDragEl(), 2886 el = this.getEl(); 2887 2888 YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px'); 2889 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block'); 2890 var xy = YAHOO.util.Dom.getXY(el); 2891 YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]); 2892 2893 YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px"); 2894 YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px'); 2895 YAHOO.util.Dom.setXY(this.dragEl, xy); 2896 }, 2897 onMouseDown: function() { 2898 this.initConstraints(); 2899 this.resetConstraints(); 2900 }, 2901 clickValidator: function(e) { 2902 if(!this.column.hidden) { 2903 var target = YAHOO.util.Event.getTarget(e); 2904 return ( this.isValidHandleChild(target) && 2905 (this.id == this.handleElId || 2906 this.DDM.handleWasClicked(target, this.id)) ); 2907 } 2908 }, 2909 onDragOver: function(ev, id) { 2910 // Validate target as a Column 2911 var target = this.datatable.getColumn(id); 2912 if(target) { 2913 // Validate target as a top-level parent 2914 var targetIndex = target.getTreeIndex(); 2915 while((targetIndex === null) && target.getParent()) { 2916 target = target.getParent(); 2917 targetIndex = target.getTreeIndex(); 2918 } 2919 if(targetIndex !== null) { 2920 // Are we placing to left or right of target? 2921 var elTarget = target.getThEl(); 2922 var newIndex = targetIndex; 2923 var mouseX = YAHOO.util.Event.getPageX(ev), 2924 targetX = YAHOO.util.Dom.getX(elTarget), 2925 midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2), 2926 currentIndex = this.column.getTreeIndex(); 2927 2928 if (mouseX < midX) { 2929 YAHOO.util.Dom.setX(this.pointer, targetX); 2930 } else { 2931 var targetWidth = parseInt(elTarget.offsetWidth, 10); 2932 YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth)); 2933 newIndex++; 2934 } 2935 if (targetIndex > currentIndex) { 2936 newIndex--; 2937 } 2938 if(newIndex < 0) { 2939 newIndex = 0; 2940 } 2941 else if(newIndex > this.datatable.getColumnSet().tree[0].length) { 2942 newIndex = this.datatable.getColumnSet().tree[0].length; 2943 } 2944 this.newIndex = newIndex; 2945 } 2946 } 2947 }, 2948 onDragDrop: function() { 2949 this.datatable.reorderColumn(this.column, this.newIndex); 2950 }, 2951 endDrag: function() { 2952 this.newIndex = null; 2953 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none'); 2954 } 2955 }); 2956 } 2957 2958 /****************************************************************************/ 2959 /****************************************************************************/ 2960 /****************************************************************************/ 2961 2962 /** 2963 * ColumnResizer subclasses DragDrop to support resizeable Columns. 2964 * 2965 * @namespace YAHOO.util 2966 * @class ColumnResizer 2967 * @extends YAHOO.util.DDProxy 2968 * @constructor 2969 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 2970 * @param oColumn {YAHOO.widget.Column} Column instance. 2971 * @param elTh {HTMLElement} TH element reference. 2972 * @param sHandleElId {String} DOM ID of the handle element that causes the resize. 2973 * @param elProxy {HTMLElement} Resizer proxy element. 2974 */ 2975 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) { 2976 if(oDataTable && oColumn && elTh && sHandleId) { 2977 this.datatable = oDataTable; 2978 this.column = oColumn; 2979 this.headCell = elTh; 2980 this.headCellLiner = oColumn.getThLinerEl(); 2981 this.resizerLiner = elTh.firstChild; 2982 this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id}); 2983 this.initFrame(); // Needed for proxy 2984 this.resetResizerEl(); // Needed when rowspan > 0 2985 2986 // Set right padding for bug 1858462 2987 this.setPadding(0, 1, 0, 0); 2988 } 2989 else { 2990 } 2991 }; 2992 2993 if(YAHOO.util.DD) { 2994 YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, { 2995 ///////////////////////////////////////////////////////////////////////////// 2996 // 2997 // Public methods 2998 // 2999 ///////////////////////////////////////////////////////////////////////////// 3000 /** 3001 * Resets resizer element. 3002 * 3003 * @method resetResizerEl 3004 */ 3005 resetResizerEl : function() { 3006 var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style; 3007 resizerStyle.left = "auto"; 3008 resizerStyle.right = 0; 3009 resizerStyle.top = "auto"; 3010 resizerStyle.bottom = 0; 3011 resizerStyle.height = this.headCell.offsetHeight+"px"; 3012 }, 3013 3014 ///////////////////////////////////////////////////////////////////////////// 3015 // 3016 // Public DOM event handlers 3017 // 3018 ///////////////////////////////////////////////////////////////////////////// 3019 3020 /** 3021 * Handles mouseup events on the Column resizer. 3022 * 3023 * @method onMouseUp 3024 * @param e {string} The mouseup event 3025 */ 3026 onMouseUp : function(e) { 3027 // Reset height of all resizer els in case TH's have changed height 3028 var allKeys = this.datatable.getColumnSet().keys, 3029 col; 3030 for(var i=0, len=allKeys.length; i<len; i++) { 3031 col = allKeys[i]; 3032 if(col._ddResizer) { 3033 col._ddResizer.resetResizerEl(); 3034 } 3035 } 3036 this.resetResizerEl(); 3037 3038 var el = this.headCellLiner; 3039 var newWidth = el.offsetWidth - 3040 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) - 3041 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0); 3042 3043 this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth}); 3044 }, 3045 3046 /** 3047 * Handles mousedown events on the Column resizer. 3048 * 3049 * @method onMouseDown 3050 * @param e {string} The mousedown event 3051 */ 3052 onMouseDown : function(e) { 3053 this.startWidth = this.headCellLiner.offsetWidth; 3054 this.startX = YAHOO.util.Event.getXY(e)[0]; 3055 this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) + 3056 (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0); 3057 }, 3058 3059 /** 3060 * Custom clickValidator to ensure Column is not in hidden state. 3061 * 3062 * @method clickValidator 3063 * @param {Event} e 3064 * @private 3065 */ 3066 clickValidator : function(e) { 3067 if(!this.column.hidden) { 3068 var target = YAHOO.util.Event.getTarget(e); 3069 return ( this.isValidHandleChild(target) && 3070 (this.id == this.handleElId || 3071 this.DDM.handleWasClicked(target, this.id)) ); 3072 } 3073 }, 3074 3075 /** 3076 * Handles start drag on the Column resizer. 3077 * 3078 * @method startDrag 3079 * @param e {string} The drag event 3080 */ 3081 startDrag : function() { 3082 // Shrinks height of all resizer els to not hold open TH els 3083 var allKeys = this.datatable.getColumnSet().keys, 3084 thisKey = this.column.getKeyIndex(), 3085 col; 3086 for(var i=0, len=allKeys.length; i<len; i++) { 3087 col = allKeys[i]; 3088 if(col._ddResizer) { 3089 YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em"; 3090 } 3091 } 3092 }, 3093 3094 /** 3095 * Handles drag events on the Column resizer. 3096 * 3097 * @method onDrag 3098 * @param e {string} The drag event 3099 */ 3100 onDrag : function(e) { 3101 var newX = YAHOO.util.Event.getXY(e)[0]; 3102 if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) { 3103 var offsetX = newX - this.startX; 3104 var newWidth = this.startWidth + offsetX - this.nLinerPadding; 3105 if(newWidth > 0) { 3106 this.datatable.setColumnWidth(this.column, newWidth); 3107 } 3108 } 3109 } 3110 }); 3111 } 3112 3113 ///////////////////////////////////////////////////////////////////////////// 3114 // 3115 // Deprecated 3116 // 3117 ///////////////////////////////////////////////////////////////////////////// 3118 3119 /** 3120 * @property editorOptions 3121 * @deprecated Pass configs directly to CellEditor constructor. 3122 */ 3123 3124 3125 (function () { 3126 3127 var lang = YAHOO.lang, 3128 util = YAHOO.util, 3129 widget = YAHOO.widget, 3130 3131 Dom = util.Dom, 3132 Ev = util.Event, 3133 DT = widget.DataTable; 3134 3135 /****************************************************************************/ 3136 /****************************************************************************/ 3137 /****************************************************************************/ 3138 3139 /** 3140 * A RecordSet defines and manages a set of Records. 3141 * 3142 * @namespace YAHOO.widget 3143 * @class RecordSet 3144 * @param data {Object || Object[]} An object literal or an array of data. 3145 * @constructor 3146 */ 3147 YAHOO.widget.RecordSet = function(data) { 3148 this._init(data); 3149 }; 3150 3151 var RS = widget.RecordSet; 3152 3153 /** 3154 * Internal class variable to name multiple Recordset instances. 3155 * 3156 * @property RecordSet._nCount 3157 * @type Number 3158 * @private 3159 * @static 3160 */ 3161 RS._nCount = 0; 3162 3163 RS.prototype = { 3164 3165 ///////////////////////////////////////////////////////////////////////////// 3166 // 3167 // Private member variables 3168 // 3169 ///////////////////////////////////////////////////////////////////////////// 3170 /** 3171 * Unique String identifier assigned at instantiation. 3172 * 3173 * @property _sId 3174 * @type String 3175 * @private 3176 */ 3177 _sId : null, 3178 3179 /** 3180 * Internal counter of how many Records are in the RecordSet. 3181 * 3182 * @property _length 3183 * @type Number 3184 * @private 3185 * @deprecated No longer used 3186 */ 3187 //_length : null, 3188 3189 ///////////////////////////////////////////////////////////////////////////// 3190 // 3191 // Private methods 3192 // 3193 ///////////////////////////////////////////////////////////////////////////// 3194 3195 /** 3196 * Initializer. 3197 * 3198 * @method _init 3199 * @param data {Object || Object[]} An object literal or an array of data. 3200 * @private 3201 */ 3202 _init : function(data) { 3203 // Internal variables 3204 this._sId = Dom.generateId(null, "yui-rs");// "yui-rs" + widget.RecordSet._nCount; 3205 widget.RecordSet._nCount++; 3206 this._records = []; 3207 //this._length = 0; 3208 3209 this._initEvents(); 3210 3211 if(data) { 3212 if(lang.isArray(data)) { 3213 this.addRecords(data); 3214 } 3215 else if(lang.isObject(data)) { 3216 this.addRecord(data); 3217 } 3218 } 3219 3220 }, 3221 3222 /** 3223 * Initializes custom events. 3224 * 3225 * @method _initEvents 3226 * @private 3227 */ 3228 _initEvents : function() { 3229 this.createEvent("recordAddEvent"); 3230 this.createEvent("recordsAddEvent"); 3231 this.createEvent("recordSetEvent"); 3232 this.createEvent("recordsSetEvent"); 3233 this.createEvent("recordUpdateEvent"); 3234 this.createEvent("recordDeleteEvent"); 3235 this.createEvent("recordsDeleteEvent"); 3236 this.createEvent("resetEvent"); 3237 this.createEvent("recordValueUpdateEvent"); 3238 }, 3239 3240 /** 3241 * Adds one Record to the RecordSet at the given index. If index is null, 3242 * then adds the Record to the end of the RecordSet. 3243 * 3244 * @method _addRecord 3245 * @param oData {Object} An object literal of data. 3246 * @param index {Number} (optional) Position index. 3247 * @return {YAHOO.widget.Record} A Record instance. 3248 * @private 3249 */ 3250 _addRecord : function(oData, index) { 3251 var oRecord = new YAHOO.widget.Record(oData); 3252 3253 if(YAHOO.lang.isNumber(index) && (index > -1)) { 3254 this._records.splice(index,0,oRecord); 3255 } 3256 else { 3257 //index = this.getLength(); 3258 //this._records[index] = oRecord; 3259 this._records[this._records.length] = oRecord; 3260 } 3261 //this._length++; 3262 return oRecord; 3263 }, 3264 3265 /** 3266 * Sets/replaces one Record to the RecordSet at the given index. Existing 3267 * Records with higher indexes are not shifted. If no index specified, the 3268 * Record is added to the end of the RecordSet. 3269 * 3270 * @method _setRecord 3271 * @param oData {Object} An object literal of data. 3272 * @param index {Number} (optional) Position index. 3273 * @return {YAHOO.widget.Record} A Record instance. 3274 * @private 3275 */ 3276 _setRecord : function(oData, index) { 3277 if (!lang.isNumber(index) || index < 0) { 3278 index = this._records.length; 3279 } 3280 return (this._records[index] = new widget.Record(oData)); 3281 /* 3282 if(lang.isNumber(index) && (index > -1)) { 3283 this._records[index] = oRecord; 3284 if((index+1) > this.getLength()) { 3285 this._length = index+1; 3286 } 3287 } 3288 else { 3289 this._records[this.getLength()] = oRecord; 3290 this._length++; 3291 } 3292 return oRecord; 3293 */ 3294 }, 3295 3296 /** 3297 * Deletes Records from the RecordSet at the given index. If range is null, 3298 * then only one Record is deleted. 3299 * 3300 * @method _deleteRecord 3301 * @param index {Number} Position index. 3302 * @param range {Number} (optional) How many Records to delete 3303 * @private 3304 */ 3305 _deleteRecord : function(index, range) { 3306 if(!lang.isNumber(range) || (range < 0)) { 3307 range = 1; 3308 } 3309 this._records.splice(index, range); 3310 //this._length = this._length - range; 3311 }, 3312 3313 ///////////////////////////////////////////////////////////////////////////// 3314 // 3315 // Public methods 3316 // 3317 ///////////////////////////////////////////////////////////////////////////// 3318 3319 /** 3320 * Returns unique name of the RecordSet instance. 3321 * 3322 * @method getId 3323 * @return {String} Unique name of the RecordSet instance. 3324 */ 3325 getId : function() { 3326 return this._sId; 3327 }, 3328 3329 /** 3330 * Public accessor to the unique name of the RecordSet instance. 3331 * 3332 * @method toString 3333 * @return {String} Unique name of the RecordSet instance. 3334 */ 3335 toString : function() { 3336 return "RecordSet instance " + this._sId; 3337 }, 3338 3339 /** 3340 * Returns the number of Records held in the RecordSet. 3341 * 3342 * @method getLength 3343 * @return {Number} Number of records in the RecordSet. 3344 */ 3345 getLength : function() { 3346 //return this._length; 3347 return this._records.length; 3348 }, 3349 3350 /** 3351 * Returns Record by ID or RecordSet position index. 3352 * 3353 * @method getRecord 3354 * @param record {YAHOO.widget.Record | Number | String} Record instance, 3355 * RecordSet position index, or Record ID. 3356 * @return {YAHOO.widget.Record} Record object. 3357 */ 3358 getRecord : function(record) { 3359 var i; 3360 if(record instanceof widget.Record) { 3361 for(i=0; i<this._records.length; i++) { 3362 if(this._records[i] && (this._records[i]._sId === record._sId)) { 3363 return record; 3364 } 3365 } 3366 } 3367 else if(lang.isNumber(record)) { 3368 if((record > -1) && (record < this.getLength())) { 3369 return this._records[record]; 3370 } 3371 } 3372 else if(lang.isString(record)) { 3373 for(i=0; i<this._records.length; i++) { 3374 if(this._records[i] && (this._records[i]._sId === record)) { 3375 return this._records[i]; 3376 } 3377 } 3378 } 3379 // Not a valid Record for this RecordSet 3380 return null; 3381 3382 }, 3383 3384 /** 3385 * Returns an array of Records from the RecordSet. 3386 * 3387 * @method getRecords 3388 * @param index {Number} (optional) Recordset position index of which Record to 3389 * start at. 3390 * @param range {Number} (optional) Number of Records to get. 3391 * @return {YAHOO.widget.Record[]} Array of Records starting at given index and 3392 * length equal to given range. If index is not given, all Records are returned. 3393 */ 3394 getRecords : function(index, range) { 3395 if(!lang.isNumber(index)) { 3396 return this._records; 3397 } 3398 if(!lang.isNumber(range)) { 3399 return this._records.slice(index); 3400 } 3401 return this._records.slice(index, index+range); 3402 }, 3403 3404 /** 3405 * Returns a boolean indicating whether Records exist in the RecordSet at the 3406 * specified index range. Returns true if and only if a Record exists at each 3407 * index in the range. 3408 * @method hasRecords 3409 * @param index 3410 * @param range 3411 * @return {Boolean} true if all indices are populated in the RecordSet 3412 */ 3413 hasRecords : function (index, range) { 3414 var recs = this.getRecords(index,range); 3415 for (var i = 0; i < range; ++i) { 3416 if (typeof recs[i] === 'undefined') { 3417 return false; 3418 } 3419 } 3420 return true; 3421 }, 3422 3423 /** 3424 * Returns current position index for the given Record. 3425 * 3426 * @method getRecordIndex 3427 * @param oRecord {YAHOO.widget.Record} Record instance. 3428 * @return {Number} Record's RecordSet position index. 3429 */ 3430 3431 getRecordIndex : function(oRecord) { 3432 if(oRecord) { 3433 for(var i=this._records.length-1; i>-1; i--) { 3434 if(this._records[i] && oRecord.getId() === this._records[i].getId()) { 3435 return i; 3436 } 3437 } 3438 } 3439 return null; 3440 3441 }, 3442 3443 /** 3444 * Adds one Record to the RecordSet at the given index. If index is null, 3445 * then adds the Record to the end of the RecordSet. 3446 * 3447 * @method addRecord 3448 * @param oData {Object} An object literal of data. 3449 * @param index {Number} (optional) Position index. 3450 * @return {YAHOO.widget.Record} A Record instance. 3451 */ 3452 addRecord : function(oData, index) { 3453 if(lang.isObject(oData)) { 3454 var oRecord = this._addRecord(oData, index); 3455 this.fireEvent("recordAddEvent",{record:oRecord,data:oData}); 3456 return oRecord; 3457 } 3458 else { 3459 return null; 3460 } 3461 }, 3462 3463 /** 3464 * Adds multiple Records at once to the RecordSet at the given index with the 3465 * given object literal data. If index is null, then the new Records are 3466 * added to the end of the RecordSet. 3467 * 3468 * @method addRecords 3469 * @param aData {Object[]} An object literal data or an array of data object literals. 3470 * @param index {Number} (optional) Position index. 3471 * @return {YAHOO.widget.Record[]} An array of Record instances. 3472 */ 3473 addRecords : function(aData, index) { 3474 if(lang.isArray(aData)) { 3475 var newRecords = [], 3476 idx,i,len; 3477 3478 index = lang.isNumber(index) ? index : this._records.length; 3479 idx = index; 3480 3481 // Can't go backwards bc we need to preserve order 3482 for(i=0,len=aData.length; i<len; ++i) { 3483 if(lang.isObject(aData[i])) { 3484 var record = this._addRecord(aData[i], idx++); 3485 newRecords.push(record); 3486 } 3487 } 3488 this.fireEvent("recordsAddEvent",{records:newRecords,data:aData}); 3489 return newRecords; 3490 } 3491 else if(lang.isObject(aData)) { 3492 var oRecord = this._addRecord(aData); 3493 this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData}); 3494 return oRecord; 3495 } 3496 else { 3497 return null; 3498 } 3499 }, 3500 3501 /** 3502 * Sets or replaces one Record to the RecordSet at the given index. Unlike 3503 * addRecord, an existing Record at that index is not shifted to preserve it. 3504 * If no index is specified, it adds the Record to the end of the RecordSet. 3505 * 3506 * @method setRecord 3507 * @param oData {Object} An object literal of data. 3508 * @param index {Number} (optional) Position index. 3509 * @return {YAHOO.widget.Record} A Record instance. 3510 */ 3511 setRecord : function(oData, index) { 3512 if(lang.isObject(oData)) { 3513 var oRecord = this._setRecord(oData, index); 3514 this.fireEvent("recordSetEvent",{record:oRecord,data:oData}); 3515 return oRecord; 3516 } 3517 else { 3518 return null; 3519 } 3520 }, 3521 3522 /** 3523 * Sets or replaces multiple Records at once to the RecordSet with the given 3524 * data, starting at the given index. If index is not specified, then the new 3525 * Records are added to the end of the RecordSet. 3526 * 3527 * @method setRecords 3528 * @param aData {Object[]} An array of object literal data. 3529 * @param index {Number} (optional) Position index. 3530 * @return {YAHOO.widget.Record[]} An array of Record instances. 3531 */ 3532 setRecords : function(aData, index) { 3533 var Rec = widget.Record, 3534 a = lang.isArray(aData) ? aData : [aData], 3535 added = [], 3536 i = 0, l = a.length, j = 0; 3537 3538 index = parseInt(index,10)|0; 3539 3540 for(; i < l; ++i) { 3541 if (typeof a[i] === 'object' && a[i]) { 3542 added[j++] = this._records[index + i] = new Rec(a[i]); 3543 } 3544 } 3545 3546 this.fireEvent("recordsSetEvent",{records:added,data:aData}); 3547 // Backward compatibility for bug 1918245 3548 this.fireEvent("recordsSet",{records:added,data:aData}); 3549 3550 if (a.length && !added.length) { 3551 } 3552 3553 return added; 3554 }, 3555 3556 /** 3557 * Updates given Record with given data. 3558 * 3559 * @method updateRecord 3560 * @param record {YAHOO.widget.Record | Number | String} A Record instance, 3561 * a RecordSet position index, or a Record ID. 3562 * @param oData {Object} Object literal of new data. 3563 * @return {YAHOO.widget.Record} Updated Record, or null. 3564 */ 3565 updateRecord : function(record, oData) { 3566 var oRecord = this.getRecord(record); 3567 if(oRecord && lang.isObject(oData)) { 3568 // Copy data from the Record for the event that gets fired later 3569 var oldData = {}; 3570 for(var key in oRecord._oData) { 3571 if(lang.hasOwnProperty(oRecord._oData, key)) { 3572 oldData[key] = oRecord._oData[key]; 3573 } 3574 } 3575 oRecord._oData = oData; 3576 this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData}); 3577 return oRecord; 3578 } 3579 else { 3580 return null; 3581 } 3582 }, 3583 3584 /** 3585 * @method updateKey 3586 * @deprecated Use updateRecordValue 3587 */ 3588 updateKey : function(record, sKey, oData) { 3589 this.updateRecordValue(record, sKey, oData); 3590 }, 3591 /** 3592 * Sets given Record at given key to given data. 3593 * 3594 * @method updateRecordValue 3595 * @param record {YAHOO.widget.Record | Number | String} A Record instance, 3596 * a RecordSet position index, or a Record ID. 3597 * @param sKey {String} Key name. 3598 * @param oData {Object} New data. 3599 */ 3600 updateRecordValue : function(record, sKey, oData) { 3601 var oRecord = this.getRecord(record); 3602 if(oRecord) { 3603 var oldData = null; 3604 var keyValue = oRecord._oData[sKey]; 3605 // Copy data from the Record for the event that gets fired later 3606 if(keyValue && lang.isObject(keyValue)) { 3607 oldData = {}; 3608 for(var key in keyValue) { 3609 if(lang.hasOwnProperty(keyValue, key)) { 3610 oldData[key] = keyValue[key]; 3611 } 3612 } 3613 } 3614 // Copy by value 3615 else { 3616 oldData = keyValue; 3617 } 3618 3619 oRecord._oData[sKey] = oData; 3620 this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData}); 3621 this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData}); 3622 } 3623 else { 3624 } 3625 }, 3626 3627 /** 3628 * Replaces all Records in RecordSet with new object literal data. 3629 * 3630 * @method replaceRecords 3631 * @param data {Object || Object[]} An object literal of data or an array of 3632 * data object literals. 3633 * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or 3634 * an array of Records. 3635 */ 3636 replaceRecords : function(data) { 3637 this.reset(); 3638 return this.addRecords(data); 3639 }, 3640 3641 /** 3642 * Sorts all Records by given function. Records keep their unique IDs but will 3643 * have new RecordSet position indexes. 3644 * 3645 * @method sortRecords 3646 * @param fnSort {Function} Reference to a sort function. 3647 * @param desc {Boolean} True if sort direction is descending, false if sort 3648 * direction is ascending. 3649 * @param field {String} The field to sort by, from sortOptions.field 3650 * @return {YAHOO.widget.Record[]} Sorted array of Records. 3651 */ 3652 sortRecords : function(fnSort, desc, field) { 3653 return this._records.sort(function(a, b) {return fnSort(a, b, desc, field);}); 3654 }, 3655 3656 /** 3657 * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"]. 3658 * 3659 * @method reverseRecords 3660 * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records. 3661 */ 3662 reverseRecords : function() { 3663 return this._records.reverse(); 3664 }, 3665 3666 /** 3667 * Removes the Record at the given position index from the RecordSet. If a range 3668 * is also provided, removes that many Records, starting from the index. Length 3669 * of RecordSet is correspondingly shortened. 3670 * 3671 * @method deleteRecord 3672 * @param index {Number} Record's RecordSet position index. 3673 * @return {Object} A copy of the data held by the deleted Record. 3674 */ 3675 deleteRecord : function(index) { 3676 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) { 3677 var oData = this.getRecord(index).getData(); 3678 3679 this._deleteRecord(index); 3680 this.fireEvent("recordDeleteEvent",{data:oData,index:index}); 3681 return oData; 3682 } 3683 else { 3684 return null; 3685 } 3686 }, 3687 3688 /** 3689 * Removes the Record at the given position index from the RecordSet. If a range 3690 * is also provided, removes that many Records, starting from the index. Length 3691 * of RecordSet is correspondingly shortened. 3692 * 3693 * @method deleteRecords 3694 * @param index {Number} Record's RecordSet position index. 3695 * @param range {Number} (optional) How many Records to delete. 3696 * @return {Object[]} An array of copies of the data held by the deleted Records. 3697 */ 3698 deleteRecords : function(index, range) { 3699 if(!lang.isNumber(range)) { 3700 range = 1; 3701 } 3702 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) { 3703 var recordsToDelete = this.getRecords(index, range); 3704 var deletedData = [], // this mistakenly held Records, not data 3705 deletedObjects = []; // this passes data only 3706 3707 for(var i=0; i<recordsToDelete.length; i++) { 3708 deletedData[deletedData.length] = recordsToDelete[i]; // backward compatibility 3709 deletedObjects[deletedObjects.length] = recordsToDelete[i].getData(); 3710 } 3711 this._deleteRecord(index, range); 3712 3713 this.fireEvent("recordsDeleteEvent",{data:deletedData,deletedData:deletedObjects,index:index}); 3714 3715 return deletedData; 3716 } 3717 else { 3718 return null; 3719 } 3720 }, 3721 3722 /** 3723 * Deletes all Records from the RecordSet. 3724 * 3725 * @method reset 3726 */ 3727 reset : function() { 3728 this._records = []; 3729 //this._length = 0; 3730 this.fireEvent("resetEvent"); 3731 } 3732 }; 3733 3734 ///////////////////////////////////////////////////////////////////////////// 3735 // 3736 // Custom Events 3737 // 3738 ///////////////////////////////////////////////////////////////////////////// 3739 3740 // RecordSet uses EventProvider 3741 lang.augmentProto(RS, util.EventProvider); 3742 3743 /** 3744 * Fired when a new Record is added to the RecordSet. 3745 * 3746 * @event recordAddEvent 3747 * @param oArgs.record {YAHOO.widget.Record} The Record instance. 3748 * @param oArgs.data {Object} Data added. 3749 */ 3750 3751 /** 3752 * Fired when multiple Records are added to the RecordSet at once. 3753 * 3754 * @event recordsAddEvent 3755 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances. 3756 * @param oArgs.data {Object[]} Data added. 3757 */ 3758 3759 /** 3760 * Fired when a Record is set in the RecordSet. 3761 * 3762 * @event recordSetEvent 3763 * @param oArgs.record {YAHOO.widget.Record} The Record instance. 3764 * @param oArgs.data {Object} Data added. 3765 */ 3766 3767 /** 3768 * Fired when multiple Records are set in the RecordSet at once. 3769 * 3770 * @event recordsSetEvent 3771 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances. 3772 * @param oArgs.data {Object[]} Data added. 3773 */ 3774 3775 /** 3776 * Fired when a Record is updated with new data. 3777 * 3778 * @event recordUpdateEvent 3779 * @param oArgs.record {YAHOO.widget.Record} The Record instance. 3780 * @param oArgs.newData {Object} New data. 3781 * @param oArgs.oldData {Object} Old data. 3782 */ 3783 3784 /** 3785 * Fired when a Record is deleted from the RecordSet. 3786 * 3787 * @event recordDeleteEvent 3788 * @param oArgs.data {Object} The data held by the deleted Record, 3789 * or an array of data object literals if multiple Records were deleted at once. 3790 * @param oArgs.index {Object} Index of the deleted Record. 3791 */ 3792 3793 /** 3794 * Fired when multiple Records are deleted from the RecordSet at once. 3795 * 3796 * @event recordsDeleteEvent 3797 * @param oArgs.data {Object[]} An array of deleted Records. 3798 * @param oArgs.deletedData {Object[]} An array of deleted data. 3799 * @param oArgs.index {Object} Index of the first deleted Record. 3800 */ 3801 3802 /** 3803 * Fired when all Records are deleted from the RecordSet at once. 3804 * 3805 * @event resetEvent 3806 */ 3807 3808 /** 3809 * @event keyUpdateEvent 3810 * @deprecated Use recordValueUpdateEvent 3811 */ 3812 3813 /** 3814 * Fired when a Record value is updated with new data. 3815 * 3816 * @event recordValueUpdateEvent 3817 * @param oArgs.record {YAHOO.widget.Record} The Record instance. 3818 * @param oArgs.key {String} The updated key. 3819 * @param oArgs.newData {Object} New data. 3820 * @param oArgs.oldData {Object} Old data. 3821 * 3822 */ 3823 3824 3825 /****************************************************************************/ 3826 /****************************************************************************/ 3827 /****************************************************************************/ 3828 3829 /** 3830 * The Record class defines a DataTable record. 3831 * 3832 * @namespace YAHOO.widget 3833 * @class Record 3834 * @constructor 3835 * @param oConfigs {Object} (optional) Object literal of key/value pairs. 3836 */ 3837 YAHOO.widget.Record = function(oLiteral) { 3838 this._nCount = widget.Record._nCount; 3839 this._sId = Dom.generateId(null, "yui-rec");//"yui-rec" + this._nCount; 3840 widget.Record._nCount++; 3841 this._oData = {}; 3842 if(lang.isObject(oLiteral)) { 3843 for(var sKey in oLiteral) { 3844 if(lang.hasOwnProperty(oLiteral, sKey)) { 3845 this._oData[sKey] = oLiteral[sKey]; 3846 } 3847 } 3848 } 3849 }; 3850 3851 ///////////////////////////////////////////////////////////////////////////// 3852 // 3853 // Private member variables 3854 // 3855 ///////////////////////////////////////////////////////////////////////////// 3856 3857 /** 3858 * Internal class variable to give unique IDs to Record instances. 3859 * 3860 * @property Record._nCount 3861 * @type Number 3862 * @private 3863 */ 3864 YAHOO.widget.Record._nCount = 0; 3865 3866 YAHOO.widget.Record.prototype = { 3867 /** 3868 * Immutable unique count assigned at instantiation. Remains constant while a 3869 * Record's position index can change from sorting. 3870 * 3871 * @property _nCount 3872 * @type Number 3873 * @private 3874 */ 3875 _nCount : null, 3876 3877 /** 3878 * Immutable unique ID assigned at instantiation. Remains constant while a 3879 * Record's position index can change from sorting. 3880 * 3881 * @property _sId 3882 * @type String 3883 * @private 3884 */ 3885 _sId : null, 3886 3887 /** 3888 * Holds data for the Record in an object literal. 3889 * 3890 * @property _oData 3891 * @type Object 3892 * @private 3893 */ 3894 _oData : null, 3895 3896 ///////////////////////////////////////////////////////////////////////////// 3897 // 3898 // Public member variables 3899 // 3900 ///////////////////////////////////////////////////////////////////////////// 3901 3902 ///////////////////////////////////////////////////////////////////////////// 3903 // 3904 // Public methods 3905 // 3906 ///////////////////////////////////////////////////////////////////////////// 3907 3908 /** 3909 * Returns unique count assigned at instantiation. 3910 * 3911 * @method getCount 3912 * @return Number 3913 */ 3914 getCount : function() { 3915 return this._nCount; 3916 }, 3917 3918 /** 3919 * Returns unique ID assigned at instantiation. 3920 * 3921 * @method getId 3922 * @return String 3923 */ 3924 getId : function() { 3925 return this._sId; 3926 }, 3927 3928 /** 3929 * Returns data for the Record for a field if given, or the entire object 3930 * literal otherwise. 3931 * 3932 * @method getData 3933 * @param sField {String} (Optional) The field from which to retrieve data value. 3934 * @return Object 3935 */ 3936 getData : function(sField) { 3937 if(lang.isString(sField)) { 3938 return this._oData[sField]; 3939 } 3940 else { 3941 return this._oData; 3942 } 3943 }, 3944 3945 /** 3946 * Sets given data at the given key. Use the RecordSet method updateRecordValue to trigger 3947 * events. 3948 * 3949 * @method setData 3950 * @param sKey {String} The key of the new value. 3951 * @param oData {MIXED} The new value. 3952 */ 3953 setData : function(sKey, oData) { 3954 this._oData[sKey] = oData; 3955 } 3956 }; 3957 3958 })(); 3959 3960 (function () { 3961 3962 var lang = YAHOO.lang, 3963 util = YAHOO.util, 3964 widget = YAHOO.widget, 3965 ua = YAHOO.env.ua, 3966 3967 Dom = util.Dom, 3968 Ev = util.Event, 3969 DS = util.DataSourceBase; 3970 3971 /** 3972 * The DataTable widget provides a progressively enhanced DHTML control for 3973 * displaying tabular data across A-grade browsers. 3974 * 3975 * @module datatable 3976 * @requires yahoo, dom, event, element, datasource 3977 * @optional dragdrop, dragdrop 3978 * @title DataTable Widget 3979 */ 3980 3981 /****************************************************************************/ 3982 /****************************************************************************/ 3983 /****************************************************************************/ 3984 3985 /** 3986 * DataTable class for the YUI DataTable widget. 3987 * 3988 * @namespace YAHOO.widget 3989 * @class DataTable 3990 * @extends YAHOO.util.Element 3991 * @constructor 3992 * @param elContainer {HTMLElement} Container element for the TABLE. 3993 * @param aColumnDefs {Object[]} Array of object literal Column definitions. 3994 * @param oDataSource {YAHOO.util.DataSource} DataSource instance. 3995 * @param oConfigs {object} (optional) Object literal of configuration values. 3996 */ 3997 YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) { 3998 var DT = widget.DataTable; 3999 4000 //////////////////////////////////////////////////////////////////////////// 4001 // Backward compatibility for SDT, but prevent infinite loops 4002 4003 if(oConfigs && oConfigs.scrollable) { 4004 return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs); 4005 } 4006 4007 //////////////////////////////////////////////////////////////////////////// 4008 // Initialization 4009 4010 // Internal vars 4011 this._nIndex = DT._nCount; 4012 this._sId = Dom.generateId(null, "yui-dt");// "yui-dt"+this._nIndex; 4013 this._oChainRender = new YAHOO.util.Chain(); 4014 this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true); 4015 4016 // Initialize configs 4017 this._initConfigs(oConfigs); 4018 4019 // Initialize DataSource 4020 this._initDataSource(oDataSource); 4021 if(!this._oDataSource) { 4022 return; 4023 } 4024 4025 // Initialize ColumnSet 4026 this._initColumnSet(aColumnDefs); 4027 if(!this._oColumnSet) { 4028 return; 4029 } 4030 4031 // Initialize RecordSet 4032 this._initRecordSet(); 4033 if(!this._oRecordSet) { 4034 } 4035 4036 // Initialize Attributes 4037 DT.superclass.constructor.call(this, elContainer, this.configs); 4038 4039 // Initialize DOM elements 4040 var okDom = this._initDomElements(elContainer); 4041 if(!okDom) { 4042 return; 4043 } 4044 4045 // Show message as soon as config is available 4046 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING); 4047 4048 //////////////////////////////////////////////////////////////////////////// 4049 // Once per instance 4050 this._initEvents(); 4051 4052 DT._nCount++; 4053 DT._nCurrentCount++; 4054 4055 //////////////////////////////////////////////////////////////////////////// 4056 // Data integration 4057 4058 // Send a simple initial request 4059 var oCallback = { 4060 success : this.onDataReturnSetRows, 4061 failure : this.onDataReturnSetRows, 4062 scope : this, 4063 argument: this.getState() 4064 }; 4065 4066 var initialLoad = this.get("initialLoad"); 4067 if(initialLoad === true) { 4068 this._oDataSource.sendRequest(this.get("initialRequest"), oCallback); 4069 } 4070 // Do not send an initial request at all 4071 else if(initialLoad === false) { 4072 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY); 4073 } 4074 // Send an initial request with a custom payload 4075 else { 4076 var oCustom = initialLoad || {}; 4077 oCallback.argument = oCustom.argument || {}; 4078 this._oDataSource.sendRequest(oCustom.request, oCallback); 4079 } 4080 }; 4081 4082 var DT = widget.DataTable; 4083 4084 ///////////////////////////////////////////////////////////////////////////// 4085 // 4086 // Public constants 4087 // 4088 ///////////////////////////////////////////////////////////////////////////// 4089 4090 lang.augmentObject(DT, { 4091 4092 /** 4093 * Class name assigned to outer DataTable container. 4094 * 4095 * @property DataTable.CLASS_DATATABLE 4096 * @type String 4097 * @static 4098 * @final 4099 * @default "yui-dt" 4100 */ 4101 CLASS_DATATABLE : "yui-dt", 4102 4103 /** 4104 * Class name assigned to liner DIV elements. 4105 * 4106 * @property DataTable.CLASS_LINER 4107 * @type String 4108 * @static 4109 * @final 4110 * @default "yui-dt-liner" 4111 */ 4112 CLASS_LINER : "yui-dt-liner", 4113 4114 /** 4115 * Class name assigned to display label elements. 4116 * 4117 * @property DataTable.CLASS_LABEL 4118 * @type String 4119 * @static 4120 * @final 4121 * @default "yui-dt-label" 4122 */ 4123 CLASS_LABEL : "yui-dt-label", 4124 4125 /** 4126 * Class name assigned to messaging elements. 4127 * 4128 * @property DataTable.CLASS_MESSAGE 4129 * @type String 4130 * @static 4131 * @final 4132 * @default "yui-dt-message" 4133 */ 4134 CLASS_MESSAGE : "yui-dt-message", 4135 4136 /** 4137 * Class name assigned to mask element when DataTable is disabled. 4138 * 4139 * @property DataTable.CLASS_MASK 4140 * @type String 4141 * @static 4142 * @final 4143 * @default "yui-dt-mask" 4144 */ 4145 CLASS_MASK : "yui-dt-mask", 4146 4147 /** 4148 * Class name assigned to data elements. 4149 * 4150 * @property DataTable.CLASS_DATA 4151 * @type String 4152 * @static 4153 * @final 4154 * @default "yui-dt-data" 4155 */ 4156 CLASS_DATA : "yui-dt-data", 4157 4158 /** 4159 * Class name assigned to Column drag target. 4160 * 4161 * @property DataTable.CLASS_COLTARGET 4162 * @type String 4163 * @static 4164 * @final 4165 * @default "yui-dt-coltarget" 4166 */ 4167 CLASS_COLTARGET : "yui-dt-coltarget", 4168 4169 /** 4170 * Class name assigned to resizer handle elements. 4171 * 4172 * @property DataTable.CLASS_RESIZER 4173 * @type String 4174 * @static 4175 * @final 4176 * @default "yui-dt-resizer" 4177 */ 4178 CLASS_RESIZER : "yui-dt-resizer", 4179 4180 /** 4181 * Class name assigned to resizer liner elements. 4182 * 4183 * @property DataTable.CLASS_RESIZERLINER 4184 * @type String 4185 * @static 4186 * @final 4187 * @default "yui-dt-resizerliner" 4188 */ 4189 CLASS_RESIZERLINER : "yui-dt-resizerliner", 4190 4191 /** 4192 * Class name assigned to resizer proxy elements. 4193 * 4194 * @property DataTable.CLASS_RESIZERPROXY 4195 * @type String 4196 * @static 4197 * @final 4198 * @default "yui-dt-resizerproxy" 4199 */ 4200 CLASS_RESIZERPROXY : "yui-dt-resizerproxy", 4201 4202 /** 4203 * Class name assigned to CellEditor container elements. 4204 * 4205 * @property DataTable.CLASS_EDITOR 4206 * @type String 4207 * @static 4208 * @final 4209 * @default "yui-dt-editor" 4210 */ 4211 CLASS_EDITOR : "yui-dt-editor", 4212 4213 /** 4214 * Class name assigned to CellEditor container shim. 4215 * 4216 * @property DataTable.CLASS_EDITOR_SHIM 4217 * @type String 4218 * @static 4219 * @final 4220 * @default "yui-dt-editor-shim" 4221 */ 4222 CLASS_EDITOR_SHIM : "yui-dt-editor-shim", 4223 4224 /** 4225 * Class name assigned to paginator container elements. 4226 * 4227 * @property DataTable.CLASS_PAGINATOR 4228 * @type String 4229 * @static 4230 * @final 4231 * @default "yui-dt-paginator" 4232 */ 4233 CLASS_PAGINATOR : "yui-dt-paginator", 4234 4235 /** 4236 * Class name assigned to page number indicators. 4237 * 4238 * @property DataTable.CLASS_PAGE 4239 * @type String 4240 * @static 4241 * @final 4242 * @default "yui-dt-page" 4243 */ 4244 CLASS_PAGE : "yui-dt-page", 4245 4246 /** 4247 * Class name assigned to default indicators. 4248 * 4249 * @property DataTable.CLASS_DEFAULT 4250 * @type String 4251 * @static 4252 * @final 4253 * @default "yui-dt-default" 4254 */ 4255 CLASS_DEFAULT : "yui-dt-default", 4256 4257 /** 4258 * Class name assigned to previous indicators. 4259 * 4260 * @property DataTable.CLASS_PREVIOUS 4261 * @type String 4262 * @static 4263 * @final 4264 * @default "yui-dt-previous" 4265 */ 4266 CLASS_PREVIOUS : "yui-dt-previous", 4267 4268 /** 4269 * Class name assigned next indicators. 4270 * 4271 * @property DataTable.CLASS_NEXT 4272 * @type String 4273 * @static 4274 * @final 4275 * @default "yui-dt-next" 4276 */ 4277 CLASS_NEXT : "yui-dt-next", 4278 4279 /** 4280 * Class name assigned to first elements. 4281 * 4282 * @property DataTable.CLASS_FIRST 4283 * @type String 4284 * @static 4285 * @final 4286 * @default "yui-dt-first" 4287 */ 4288 CLASS_FIRST : "yui-dt-first", 4289 4290 /** 4291 * Class name assigned to last elements. 4292 * 4293 * @property DataTable.CLASS_LAST 4294 * @type String 4295 * @static 4296 * @final 4297 * @default "yui-dt-last" 4298 */ 4299 CLASS_LAST : "yui-dt-last", 4300 4301 /** 4302 * Class name assigned to Record elements. 4303 * 4304 * @property DataTable.CLASS_REC 4305 * @type String 4306 * @static 4307 * @final 4308 * @default "yui-dt-rec" 4309 */ 4310 CLASS_REC : "yui-dt-rec", 4311 4312 /** 4313 * Class name assigned to even elements. 4314 * 4315 * @property DataTable.CLASS_EVEN 4316 * @type String 4317 * @static 4318 * @final 4319 * @default "yui-dt-even" 4320 */ 4321 CLASS_EVEN : "yui-dt-even", 4322 4323 /** 4324 * Class name assigned to odd elements. 4325 * 4326 * @property DataTable.CLASS_ODD 4327 * @type String 4328 * @static 4329 * @final 4330 * @default "yui-dt-odd" 4331 */ 4332 CLASS_ODD : "yui-dt-odd", 4333 4334 /** 4335 * Class name assigned to selected elements. 4336 * 4337 * @property DataTable.CLASS_SELECTED 4338 * @type String 4339 * @static 4340 * @final 4341 * @default "yui-dt-selected" 4342 */ 4343 CLASS_SELECTED : "yui-dt-selected", 4344 4345 /** 4346 * Class name assigned to highlighted elements. 4347 * 4348 * @property DataTable.CLASS_HIGHLIGHTED 4349 * @type String 4350 * @static 4351 * @final 4352 * @default "yui-dt-highlighted" 4353 */ 4354 CLASS_HIGHLIGHTED : "yui-dt-highlighted", 4355 4356 /** 4357 * Class name assigned to hidden elements. 4358 * 4359 * @property DataTable.CLASS_HIDDEN 4360 * @type String 4361 * @static 4362 * @final 4363 * @default "yui-dt-hidden" 4364 */ 4365 CLASS_HIDDEN : "yui-dt-hidden", 4366 4367 /** 4368 * Class name assigned to disabled elements. 4369 * 4370 * @property DataTable.CLASS_DISABLED 4371 * @type String 4372 * @static 4373 * @final 4374 * @default "yui-dt-disabled" 4375 */ 4376 CLASS_DISABLED : "yui-dt-disabled", 4377 4378 /** 4379 * Class name assigned to empty indicators. 4380 * 4381 * @property DataTable.CLASS_EMPTY 4382 * @type String 4383 * @static 4384 * @final 4385 * @default "yui-dt-empty" 4386 */ 4387 CLASS_EMPTY : "yui-dt-empty", 4388 4389 /** 4390 * Class name assigned to loading indicatorx. 4391 * 4392 * @property DataTable.CLASS_LOADING 4393 * @type String 4394 * @static 4395 * @final 4396 * @default "yui-dt-loading" 4397 */ 4398 CLASS_LOADING : "yui-dt-loading", 4399 4400 /** 4401 * Class name assigned to error indicators. 4402 * 4403 * @property DataTable.CLASS_ERROR 4404 * @type String 4405 * @static 4406 * @final 4407 * @default "yui-dt-error" 4408 */ 4409 CLASS_ERROR : "yui-dt-error", 4410 4411 /** 4412 * Class name assigned to editable elements. 4413 * 4414 * @property DataTable.CLASS_EDITABLE 4415 * @type String 4416 * @static 4417 * @final 4418 * @default "yui-dt-editable" 4419 */ 4420 CLASS_EDITABLE : "yui-dt-editable", 4421 4422 /** 4423 * Class name assigned to draggable elements. 4424 * 4425 * @property DataTable.CLASS_DRAGGABLE 4426 * @type String 4427 * @static 4428 * @final 4429 * @default "yui-dt-draggable" 4430 */ 4431 CLASS_DRAGGABLE : "yui-dt-draggable", 4432 4433 /** 4434 * Class name assigned to resizeable elements. 4435 * 4436 * @property DataTable.CLASS_RESIZEABLE 4437 * @type String 4438 * @static 4439 * @final 4440 * @default "yui-dt-resizeable" 4441 */ 4442 CLASS_RESIZEABLE : "yui-dt-resizeable", 4443 4444 /** 4445 * Class name assigned to scrollable elements. 4446 * 4447 * @property DataTable.CLASS_SCROLLABLE 4448 * @type String 4449 * @static 4450 * @final 4451 * @default "yui-dt-scrollable" 4452 */ 4453 CLASS_SCROLLABLE : "yui-dt-scrollable", 4454 4455 /** 4456 * Class name assigned to sortable elements. 4457 * 4458 * @property DataTable.CLASS_SORTABLE 4459 * @type String 4460 * @static 4461 * @final 4462 * @default "yui-dt-sortable" 4463 */ 4464 CLASS_SORTABLE : "yui-dt-sortable", 4465 4466 /** 4467 * Class name assigned to ascending elements. 4468 * 4469 * @property DataTable.CLASS_ASC 4470 * @type String 4471 * @static 4472 * @final 4473 * @default "yui-dt-asc" 4474 */ 4475 CLASS_ASC : "yui-dt-asc", 4476 4477 /** 4478 * Class name assigned to descending elements. 4479 * 4480 * @property DataTable.CLASS_DESC 4481 * @type String 4482 * @static 4483 * @final 4484 * @default "yui-dt-desc" 4485 */ 4486 CLASS_DESC : "yui-dt-desc", 4487 4488 /** 4489 * Class name assigned to BUTTON elements and/or container elements. 4490 * 4491 * @property DataTable.CLASS_BUTTON 4492 * @type String 4493 * @static 4494 * @final 4495 * @default "yui-dt-button" 4496 */ 4497 CLASS_BUTTON : "yui-dt-button", 4498 4499 /** 4500 * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements. 4501 * 4502 * @property DataTable.CLASS_CHECKBOX 4503 * @type String 4504 * @static 4505 * @final 4506 * @default "yui-dt-checkbox" 4507 */ 4508 CLASS_CHECKBOX : "yui-dt-checkbox", 4509 4510 /** 4511 * Class name assigned to SELECT elements and/or container elements. 4512 * 4513 * @property DataTable.CLASS_DROPDOWN 4514 * @type String 4515 * @static 4516 * @final 4517 * @default "yui-dt-dropdown" 4518 */ 4519 CLASS_DROPDOWN : "yui-dt-dropdown", 4520 4521 /** 4522 * Class name assigned to INPUT TYPE=RADIO elements and/or container elements. 4523 * 4524 * @property DataTable.CLASS_RADIO 4525 * @type String 4526 * @static 4527 * @final 4528 * @default "yui-dt-radio" 4529 */ 4530 CLASS_RADIO : "yui-dt-radio", 4531 4532 ///////////////////////////////////////////////////////////////////////// 4533 // 4534 // Private static properties 4535 // 4536 ///////////////////////////////////////////////////////////////////////// 4537 4538 /** 4539 * Internal class variable for indexing multiple DataTable instances. 4540 * 4541 * @property DataTable._nCount 4542 * @type Number 4543 * @private 4544 * @static 4545 */ 4546 _nCount : 0, 4547 4548 /** 4549 * Internal class variable tracking current number of DataTable instances, 4550 * so that certain class values can be reset when all instances are destroyed. 4551 * 4552 * @property DataTable._nCurrentCount 4553 * @type Number 4554 * @private 4555 * @static 4556 */ 4557 _nCurrentCount : 0, 4558 4559 /** 4560 * Reference to the STYLE node that is dynamically created and updated 4561 * in order to manage Column widths. 4562 * 4563 * @property DataTable._elDynStyleNode 4564 * @type HTMLElement 4565 * @private 4566 * @static 4567 */ 4568 _elDynStyleNode : null, 4569 4570 /** 4571 * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility. 4572 * 4573 * @property DataTable._bDynStylesFallback 4574 * @type boolean 4575 * @private 4576 * @static 4577 */ 4578 _bDynStylesFallback : (ua.ie) ? true : false, 4579 4580 /** 4581 * Object literal hash of Columns and their dynamically create style rules. 4582 * 4583 * @property DataTable._oDynStyles 4584 * @type Object 4585 * @private 4586 * @static 4587 */ 4588 _oDynStyles : {}, 4589 4590 ///////////////////////////////////////////////////////////////////////// 4591 // 4592 // Private static methods 4593 // 4594 ///////////////////////////////////////////////////////////////////////// 4595 4596 /** 4597 * Clones object literal or array of object literals. 4598 * 4599 * @method DataTable._cloneObject 4600 * @param o {Object} Object. 4601 * @private 4602 * @static 4603 */ 4604 _cloneObject: function(o) { 4605 if(!lang.isValue(o)) { 4606 return o; 4607 } 4608 4609 var copy = {}; 4610 4611 if(o instanceof YAHOO.widget.BaseCellEditor) { 4612 copy = o; 4613 } 4614 else if(Object.prototype.toString.apply(o) === "[object RegExp]") { 4615 copy = o; 4616 } 4617 else if(lang.isFunction(o)) { 4618 copy = o; 4619 } 4620 else if(lang.isArray(o)) { 4621 var array = []; 4622 for(var i=0,len=o.length;i<len;i++) { 4623 array[i] = DT._cloneObject(o[i]); 4624 } 4625 copy = array; 4626 } 4627 else if(lang.isObject(o)) { 4628 for (var x in o){ 4629 if(lang.hasOwnProperty(o, x)) { 4630 if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) { 4631 copy[x] = DT._cloneObject(o[x]); 4632 } 4633 else { 4634 copy[x] = o[x]; 4635 } 4636 } 4637 } 4638 } 4639 else { 4640 copy = o; 4641 } 4642 4643 return copy; 4644 }, 4645 4646 /** 4647 * Formats a BUTTON element. 4648 * 4649 * @method DataTable.formatButton 4650 * @param el {HTMLElement} The element to format with markup. 4651 * @param oRecord {YAHOO.widget.Record} Record instance. 4652 * @param oColumn {YAHOO.widget.Column} Column instance. 4653 * @param oData {HTML} Data value for the cell. By default, the value 4654 * is what gets written to the BUTTON. String values are treated as markup 4655 * and inserted into the DOM with innerHTML. 4656 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4657 * @static 4658 */ 4659 formatButton : function(el, oRecord, oColumn, oData, oDataTable) { 4660 var sValue = lang.isValue(oData) ? oData : "Click"; 4661 //TODO: support YAHOO.widget.Button 4662 //if(YAHOO.widget.Button) { 4663 4664 //} 4665 //else { 4666 el.innerHTML = "<button type=\"button\" class=\""+ 4667 DT.CLASS_BUTTON + "\">" + sValue + "</button>"; 4668 //} 4669 }, 4670 4671 /** 4672 * Formats a CHECKBOX element. 4673 * 4674 * @method DataTable.formatCheckbox 4675 * @param el {HTMLElement} The element to format with markup. 4676 * @param oRecord {YAHOO.widget.Record} Record instance. 4677 * @param oColumn {YAHOO.widget.Column} Column instance. 4678 * @param oData {Object | Boolean | HTML} Data value for the cell. Can be a simple 4679 * Boolean to indicate whether checkbox is checked or not. Can be object literal 4680 * {checked:bBoolean, label:sLabel}. String values are treated as markup 4681 * and inserted into the DOM with innerHTML. 4682 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4683 * @static 4684 */ 4685 formatCheckbox : function(el, oRecord, oColumn, oData, oDataTable) { 4686 var bChecked = oData; 4687 bChecked = (bChecked) ? " checked=\"checked\"" : ""; 4688 el.innerHTML = "<input type=\"checkbox\"" + bChecked + 4689 " class=\"" + DT.CLASS_CHECKBOX + "\" />"; 4690 }, 4691 4692 /** 4693 * Formats currency. Default unit is USD. 4694 * 4695 * @method DataTable.formatCurrency 4696 * @param el {HTMLElement} The element to format with markup. 4697 * @param oRecord {YAHOO.widget.Record} Record instance. 4698 * @param oColumn {YAHOO.widget.Column} Column instance. 4699 * @param oData {Number} Data value for the cell. 4700 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4701 * @static 4702 */ 4703 formatCurrency : function(el, oRecord, oColumn, oData, oDataTable) { 4704 var oDT = oDataTable || this; 4705 el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || oDT.get("currencyOptions")); 4706 }, 4707 4708 /** 4709 * Formats JavaScript Dates. 4710 * 4711 * @method DataTable.formatDate 4712 * @param el {HTMLElement} The element to format with markup. 4713 * @param oRecord {YAHOO.widget.Record} Record instance. 4714 * @param oColumn {YAHOO.widget.Column} Column instance. 4715 * @param oData {Object} Data value for the cell, or null. String values are 4716 * treated as markup and inserted into the DOM with innerHTML. 4717 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4718 * @static 4719 */ 4720 formatDate : function(el, oRecord, oColumn, oData, oDataTable) { 4721 var oDT = oDataTable || this, 4722 oConfig = oColumn.dateOptions || oDT.get("dateOptions"); 4723 el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale); 4724 }, 4725 4726 /** 4727 * Formats SELECT elements. 4728 * 4729 * @method DataTable.formatDropdown 4730 * @param el {HTMLElement} The element to format with markup. 4731 * @param oRecord {YAHOO.widget.Record} Record instance. 4732 * @param oColumn {YAHOO.widget.Column} Column instance. 4733 * @param oData {Object} Data value for the cell, or null. String values may 4734 * be treated as markup and inserted into the DOM with innerHTML as element 4735 * label. 4736 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4737 * @static 4738 */ 4739 formatDropdown : function(el, oRecord, oColumn, oData, oDataTable) { 4740 var oDT = oDataTable || this, 4741 selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field), 4742 options = (lang.isArray(oColumn.dropdownOptions)) ? 4743 oColumn.dropdownOptions : null, 4744 4745 selectEl, 4746 collection = el.getElementsByTagName("select"); 4747 4748 // Create the form element only once, so we can attach the onChange listener 4749 if(collection.length === 0) { 4750 // Create SELECT element 4751 selectEl = document.createElement("select"); 4752 selectEl.className = DT.CLASS_DROPDOWN; 4753 selectEl = el.appendChild(selectEl); 4754 4755 // Add event listener 4756 Ev.addListener(selectEl,"change",oDT._onDropdownChange,oDT); 4757 } 4758 4759 selectEl = collection[0]; 4760 4761 // Update the form element 4762 if(selectEl) { 4763 // Clear out previous options 4764 selectEl.innerHTML = ""; 4765 4766 // We have options to populate 4767 if(options) { 4768 // Create OPTION elements 4769 for(var i=0; i<options.length; i++) { 4770 var option = options[i]; 4771 var optionEl = document.createElement("option"); 4772 optionEl.value = (lang.isValue(option.value)) ? 4773 option.value : option; 4774 // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor 4775 optionEl.innerHTML = (lang.isValue(option.text)) ? 4776 option.text : (lang.isValue(option.label)) ? option.label : option; 4777 optionEl = selectEl.appendChild(optionEl); 4778 if (optionEl.value == selectedValue) { 4779 optionEl.selected = true; 4780 } 4781 } 4782 } 4783 // Selected value is our only option 4784 else { 4785 selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>"; 4786 } 4787 } 4788 else { 4789 el.innerHTML = lang.isValue(oData) ? oData : ""; 4790 } 4791 }, 4792 4793 /** 4794 * Formats emails. 4795 * 4796 * @method DataTable.formatEmail 4797 * @param el {HTMLElement} The element to format with markup. 4798 * @param oRecord {YAHOO.widget.Record} Record instance. 4799 * @param oColumn {YAHOO.widget.Column} Column instance. 4800 * @param oData {String} Data value for the cell, or null. Values are 4801 * HTML-escaped. 4802 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4803 * @static 4804 */ 4805 formatEmail : function(el, oRecord, oColumn, oData, oDataTable) { 4806 if(lang.isString(oData)) { 4807 oData = lang.escapeHTML(oData); 4808 el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>"; 4809 } 4810 else { 4811 el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : ""; 4812 } 4813 }, 4814 4815 /** 4816 * Formats links. 4817 * 4818 * @method DataTable.formatLink 4819 * @param el {HTMLElement} The element to format with markup. 4820 * @param oRecord {YAHOO.widget.Record} Record instance. 4821 * @param oColumn {YAHOO.widget.Column} Column instance. 4822 * @param oData {String} Data value for the cell, or null. Values are 4823 * HTML-escaped 4824 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4825 * @static 4826 */ 4827 formatLink : function(el, oRecord, oColumn, oData, oDataTable) { 4828 if(lang.isString(oData)) { 4829 oData = lang.escapeHTML(oData); 4830 el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>"; 4831 } 4832 else { 4833 el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : ""; 4834 } 4835 }, 4836 4837 /** 4838 * Formats numbers. 4839 * 4840 * @method DataTable.formatNumber 4841 * @param el {HTMLElement} The element to format with markup. 4842 * @param oRecord {YAHOO.widget.Record} Record instance. 4843 * @param oColumn {YAHOO.widget.Column} Column instance. 4844 * @param oData {Object} Data value for the cell, or null. 4845 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4846 * @static 4847 */ 4848 formatNumber : function(el, oRecord, oColumn, oData, oDataTable) { 4849 var oDT = oDataTable || this; 4850 el.innerHTML = util.Number.format(oData, oColumn.numberOptions || oDT.get("numberOptions")); 4851 }, 4852 4853 /** 4854 * Formats INPUT TYPE=RADIO elements. 4855 * 4856 * @method DataTable.formatRadio 4857 * @param el {HTMLElement} The element to format with markup. 4858 * @param oRecord {YAHOO.widget.Record} Record instance. 4859 * @param oColumn {YAHOO.widget.Column} Column instance. 4860 * @param oData {Object} (Optional) Data value for the cell. 4861 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4862 * @static 4863 */ 4864 formatRadio : function(el, oRecord, oColumn, oData, oDataTable) { 4865 var oDT = oDataTable || this, 4866 bChecked = oData; 4867 bChecked = (bChecked) ? " checked=\"checked\"" : ""; 4868 el.innerHTML = "<input type=\"radio\"" + bChecked + 4869 " name=\""+oDT.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" + 4870 " class=\"" + DT.CLASS_RADIO+ "\" />"; 4871 }, 4872 4873 /** 4874 * Formats text strings. 4875 * 4876 * @method DataTable.formatText 4877 * @param el {HTMLElement} The element to format with markup. 4878 * @param oRecord {YAHOO.widget.Record} Record instance. 4879 * @param oColumn {YAHOO.widget.Column} Column instance. 4880 * @param oData {String} (Optional) Data value for the cell. Values are 4881 * HTML-escaped. 4882 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4883 * @static 4884 */ 4885 formatText : function(el, oRecord, oColumn, oData, oDataTable) { 4886 var value = (lang.isValue(oData)) ? oData : ""; 4887 el.innerHTML = lang.escapeHTML(value.toString()); 4888 }, 4889 4890 /** 4891 * Formats TEXTAREA elements. 4892 * 4893 * @method DataTable.formatTextarea 4894 * @param el {HTMLElement} The element to format with markup. 4895 * @param oRecord {YAHOO.widget.Record} Record instance. 4896 * @param oColumn {YAHOO.widget.Column} Column instance. 4897 * @param oData {Object} (Optional) Data value for the cell. Values are 4898 * HTML-escaped. 4899 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4900 * @static 4901 */ 4902 formatTextarea : function(el, oRecord, oColumn, oData, oDataTable) { 4903 var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "", 4904 markup = "<textarea>" + value + "</textarea>"; 4905 el.innerHTML = markup; 4906 }, 4907 4908 /** 4909 * Formats INPUT TYPE=TEXT elements. 4910 * 4911 * @method DataTable.formatTextbox 4912 * @param el {HTMLElement} The element to format with markup. 4913 * @param oRecord {YAHOO.widget.Record} Record instance. 4914 * @param oColumn {YAHOO.widget.Column} Column instance. 4915 * @param oData {Object} (Optional) Data value for the cell. Values are 4916 * HTML-escaped. 4917 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4918 * @static 4919 */ 4920 formatTextbox : function(el, oRecord, oColumn, oData, oDataTable) { 4921 var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "", 4922 markup = "<input type=\"text\" value=\"" + value + "\" />"; 4923 el.innerHTML = markup; 4924 }, 4925 4926 /** 4927 * Default cell formatter 4928 * 4929 * @method DataTable.formatDefault 4930 * @param el {HTMLElement} The element to format with markup. 4931 * @param oRecord {YAHOO.widget.Record} Record instance. 4932 * @param oColumn {YAHOO.widget.Column} Column instance. 4933 * @param oData {HTML} (Optional) Data value for the cell. String values are 4934 * treated as markup and inserted into the DOM with innerHTML. 4935 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance. 4936 * @static 4937 */ 4938 formatDefault : function(el, oRecord, oColumn, oData, oDataTable) { 4939 el.innerHTML = (lang.isValue(oData) && oData !== "") ? oData.toString() : " "; 4940 }, 4941 4942 /** 4943 * Validates data value to type Number, doing type conversion as 4944 * necessary. A valid Number value is return, else null is returned 4945 * if input value does not validate. 4946 * 4947 * 4948 * @method DataTable.validateNumber 4949 * @param oData {Object} Data to validate. 4950 * @static 4951 */ 4952 validateNumber : function(oData) { 4953 //Convert to number 4954 var number = oData * 1; 4955 4956 // Validate 4957 if(lang.isNumber(number)) { 4958 return number; 4959 } 4960 else { 4961 return undefined; 4962 } 4963 } 4964 }); 4965 4966 // Done in separate step so referenced functions are defined. 4967 /** 4968 * Registry of cell formatting functions, enables shortcut pointers in Column 4969 * definition formatter value (i.e., {key:"myColumn", formatter:"date"}). 4970 * @property DataTable.Formatter 4971 * @type Object 4972 * @static 4973 */ 4974 DT.Formatter = { 4975 button : DT.formatButton, 4976 checkbox : DT.formatCheckbox, 4977 currency : DT.formatCurrency, 4978 "date" : DT.formatDate, 4979 dropdown : DT.formatDropdown, 4980 email : DT.formatEmail, 4981 link : DT.formatLink, 4982 "number" : DT.formatNumber, 4983 radio : DT.formatRadio, 4984 text : DT.formatText, 4985 textarea : DT.formatTextarea, 4986 textbox : DT.formatTextbox, 4987 4988 defaultFormatter : DT.formatDefault 4989 }; 4990 4991 lang.extend(DT, util.Element, { 4992 4993 ///////////////////////////////////////////////////////////////////////////// 4994 // 4995 // Superclass methods 4996 // 4997 ///////////////////////////////////////////////////////////////////////////// 4998 4999 /** 5000 * Implementation of Element's abstract method. Sets up config values. 5001 * 5002 * @method initAttributes 5003 * @param oConfigs {Object} (Optional) Object literal definition of configuration values. 5004 * @private 5005 */ 5006 5007 initAttributes : function(oConfigs) { 5008 oConfigs = oConfigs || {}; 5009 DT.superclass.initAttributes.call(this, oConfigs); 5010 5011 /** 5012 * @attribute summary 5013 * @description String value for the SUMMARY attribute. 5014 * @type String 5015 * @default "" 5016 */ 5017 this.setAttributeConfig("summary", { 5018 value: "", 5019 validator: lang.isString, 5020 method: function(sSummary) { 5021 if(this._elTable) { 5022 this._elTable.summary = sSummary; 5023 } 5024 } 5025 }); 5026 5027 /** 5028 * @attribute selectionMode 5029 * @description Specifies row or cell selection mode. Accepts the following strings: 5030 * <dl> 5031 * <dt>"standard"</dt> 5032 * <dd>Standard row selection with support for modifier keys to enable 5033 * multiple selections.</dd> 5034 * 5035 * <dt>"single"</dt> 5036 * <dd>Row selection with modifier keys disabled to not allow 5037 * multiple selections.</dd> 5038 * 5039 * <dt>"singlecell"</dt> 5040 * <dd>Cell selection with modifier keys disabled to not allow 5041 * multiple selections.</dd> 5042 * 5043 * <dt>"cellblock"</dt> 5044 * <dd>Cell selection with support for modifier keys to enable multiple 5045 * selections in a block-fashion, like a spreadsheet.</dd> 5046 * 5047 * <dt>"cellrange"</dt> 5048 * <dd>Cell selection with support for modifier keys to enable multiple 5049 * selections in a range-fashion, like a calendar.</dd> 5050 * </dl> 5051 * 5052 * @default "standard" 5053 * @type String 5054 */ 5055 this.setAttributeConfig("selectionMode", { 5056 value: "standard", 5057 validator: lang.isString 5058 }); 5059 5060 /** 5061 * @attribute sortedBy 5062 * @description Object literal provides metadata for initial sort values if 5063 * data will arrive pre-sorted: 5064 * <dl> 5065 * <dt>sortedBy.key</dt> 5066 * <dd>{String} Key of sorted Column</dd> 5067 * <dt>sortedBy.dir</dt> 5068 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd> 5069 * </dl> 5070 * @type Object | null 5071 */ 5072 this.setAttributeConfig("sortedBy", { 5073 value: null, 5074 // TODO: accepted array for nested sorts 5075 validator: function(oNewSortedBy) { 5076 if(oNewSortedBy) { 5077 return (lang.isObject(oNewSortedBy) && oNewSortedBy.key); 5078 } 5079 else { 5080 return (oNewSortedBy === null); 5081 } 5082 }, 5083 method: function(oNewSortedBy) { 5084 // Stash the previous value 5085 var oOldSortedBy = this.get("sortedBy"); 5086 5087 // Workaround for bug 1827195 5088 this._configs.sortedBy.value = oNewSortedBy; 5089 5090 // Remove ASC/DESC from TH 5091 var oOldColumn, 5092 nOldColumnKeyIndex, 5093 oNewColumn, 5094 nNewColumnKeyIndex; 5095 5096 if(this._elThead) { 5097 if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) { 5098 oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key); 5099 nOldColumnKeyIndex = oOldColumn.getKeyIndex(); 5100 5101 // Remove previous UI from THEAD 5102 var elOldTh = oOldColumn.getThEl(); 5103 Dom.removeClass(elOldTh, oOldSortedBy.dir); 5104 this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy); 5105 } 5106 if(oNewSortedBy) { 5107 oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key); 5108 nNewColumnKeyIndex = oNewColumn.getKeyIndex(); 5109 5110 // Update THEAD with new UI 5111 var elNewTh = oNewColumn.getThEl(); 5112 // Backward compatibility 5113 if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") || (oNewSortedBy.dir == "desc"))) { 5114 var newClass = (oNewSortedBy.dir == "desc") ? 5115 DT.CLASS_DESC : 5116 DT.CLASS_ASC; 5117 Dom.addClass(elNewTh, newClass); 5118 } 5119 else { 5120 var sortClass = oNewSortedBy.dir || DT.CLASS_ASC; 5121 Dom.addClass(elNewTh, sortClass); 5122 } 5123 this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy); 5124 } 5125 } 5126 5127 if(this._elTbody) { 5128 // Update TBODY UI 5129 this._elTbody.style.display = "none"; 5130 var allRows = this._elTbody.rows, 5131 allCells; 5132 for(var i=allRows.length-1; i>-1; i--) { 5133 allCells = allRows[i].childNodes; 5134 if(allCells[nOldColumnKeyIndex]) { 5135 Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir); 5136 } 5137 if(allCells[nNewColumnKeyIndex]) { 5138 Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir); 5139 } 5140 } 5141 this._elTbody.style.display = ""; 5142 } 5143 5144 this._clearTrTemplateEl(); 5145 } 5146 }); 5147 5148 /** 5149 * @attribute paginator 5150 * @description An instance of YAHOO.widget.Paginator. 5151 * @default null 5152 * @type {Object|YAHOO.widget.Paginator} 5153 */ 5154 this.setAttributeConfig("paginator", { 5155 value : null, 5156 validator : function (val) { 5157 return val === null || val instanceof widget.Paginator; 5158 }, 5159 method : function () { this._updatePaginator.apply(this,arguments); } 5160 }); 5161 5162 /** 5163 * @attribute caption 5164 * @description Value for the CAPTION element. String values are treated as 5165 * markup and inserted into the DOM with innerHTML. NB: Not supported in 5166 * ScrollingDataTable. 5167 * @type HTML 5168 */ 5169 this.setAttributeConfig("caption", { 5170 value: null, 5171 validator: lang.isString, 5172 method: function(sCaption) { 5173 this._initCaptionEl(sCaption); 5174 } 5175 }); 5176 5177 /** 5178 * @attribute draggableColumns 5179 * @description True if Columns are draggable to reorder, false otherwise. 5180 * The Drag & Drop Utility is required to enable this feature. Only top-level 5181 * and non-nested Columns are draggable. Write once. 5182 * @default false 5183 * @type Boolean 5184 */ 5185 this.setAttributeConfig("draggableColumns", { 5186 value: false, 5187 validator: lang.isBoolean, 5188 method: function(oParam) { 5189 if(this._elThead) { 5190 if(oParam) { 5191 this._initDraggableColumns(); 5192 } 5193 else { 5194 this._destroyDraggableColumns(); 5195 } 5196 } 5197 } 5198 }); 5199 5200 /** 5201 * @attribute renderLoopSize 5202 * @description A value greater than 0 enables DOM rendering of rows to be 5203 * executed from a non-blocking timeout queue and sets how many rows to be 5204 * rendered per timeout. Recommended for very large data sets. 5205 * @type Number 5206 * @default 0 5207 */ 5208 this.setAttributeConfig("renderLoopSize", { 5209 value: 0, 5210 validator: lang.isNumber 5211 }); 5212 5213 /** 5214 * @attribute sortFunction 5215 * @description Default Column sort function, receives the following args: 5216 * <dl> 5217 * <dt>a {Object}</dt> 5218 * <dd>First sort argument.</dd> 5219 * <dt>b {Object}</dt> 5220 * <dd>Second sort argument.</dd> 5221 5222 * <dt>desc {Boolean}</dt> 5223 * <dd>True if sort direction is descending, false if 5224 * sort direction is ascending.</dd> 5225 * <dt>field {String}</dt> 5226 * <dd>The field to sort by, from sortOptions.field</dd> 5227 * </dl> 5228 * @type function 5229 */ 5230 this.setAttributeConfig("sortFunction", { 5231 value: function(a, b, desc, field) { 5232 var compare = YAHOO.util.Sort.compare, 5233 sorted = compare(a.getData(field),b.getData(field), desc); 5234 if(sorted === 0) { 5235 return compare(a.getCount(),b.getCount(), desc); // Bug 1932978 5236 } 5237 else { 5238 return sorted; 5239 } 5240 } 5241 }); 5242 5243 /** 5244 * @attribute formatRow 5245 * @description A function that accepts a TR element and its associated Record 5246 * for custom formatting. The function must return TRUE in order to automatically 5247 * continue formatting of child TD elements, else TD elements will not be 5248 * automatically formatted. 5249 * @type function 5250 * @default null 5251 */ 5252 this.setAttributeConfig("formatRow", { 5253 value: null, 5254 validator: lang.isFunction 5255 }); 5256 5257 /** 5258 * @attribute generateRequest 5259 * @description A function that converts an object literal of desired DataTable 5260 * states into a request value which is then passed to the DataSource's 5261 * sendRequest method in order to retrieve data for those states. This 5262 * function is passed an object literal of state data and a reference to the 5263 * DataTable instance: 5264 * 5265 * <dl> 5266 * <dt>pagination<dt> 5267 * <dd> 5268 * <dt>offsetRecord</dt> 5269 * <dd>{Number} Index of the first Record of the desired page</dd> 5270 * <dt>rowsPerPage</dt> 5271 * <dd>{Number} Number of rows per page</dd> 5272 * </dd> 5273 * <dt>sortedBy</dt> 5274 * <dd> 5275 * <dt>key</dt> 5276 * <dd>{String} Key of sorted Column</dd> 5277 * <dt>dir</dt> 5278 * <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd> 5279 * </dd> 5280 * <dt>self</dt> 5281 * <dd>The DataTable instance</dd> 5282 * </dl> 5283 * 5284 * and by default returns a String of syntax: 5285 * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}" 5286 * @type function 5287 * @default HTMLFunction 5288 */ 5289 this.setAttributeConfig("generateRequest", { 5290 value: function(oState, oSelf) { 5291 // Set defaults 5292 oState = oState || {pagination:null, sortedBy:null}; 5293 var sort = encodeURIComponent((oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey()); 5294 var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc"; 5295 var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0; 5296 var results = (oState.pagination) ? oState.pagination.rowsPerPage : null; 5297 5298 // Build the request 5299 return "sort=" + sort + 5300 "&dir=" + dir + 5301 "&startIndex=" + startIndex + 5302 ((results !== null) ? "&results=" + results : ""); 5303 }, 5304 validator: lang.isFunction 5305 }); 5306 5307 /** 5308 * @attribute initialRequest 5309 * @description Defines the initial request that gets sent to the DataSource 5310 * during initialization. Value is ignored if initialLoad is set to any value 5311 * other than true. 5312 * @type MIXED 5313 * @default null 5314 */ 5315 this.setAttributeConfig("initialRequest", { 5316 value: null 5317 }); 5318 5319 /** 5320 * @attribute initialLoad 5321 * @description Determines whether or not to load data at instantiation. By 5322 * default, will trigger a sendRequest() to the DataSource and pass in the 5323 * request defined by initialRequest. If set to false, data will not load 5324 * at instantiation. Alternatively, implementers who wish to work with a 5325 * custom payload may pass in an object literal with the following values: 5326 * 5327 * <dl> 5328 * <dt>request (MIXED)</dt> 5329 * <dd>Request value.</dd> 5330 * 5331 * <dt>argument (MIXED)</dt> 5332 * <dd>Custom data that will be passed through to the callback function.</dd> 5333 * </dl> 5334 * 5335 * 5336 * @type Boolean | Object 5337 * @default true 5338 */ 5339 this.setAttributeConfig("initialLoad", { 5340 value: true 5341 }); 5342 5343 /** 5344 * @attribute dynamicData 5345 * @description If true, sorting and pagination are relegated to the DataSource 5346 * for handling, using the request returned by the "generateRequest" function. 5347 * Each new DataSource response blows away all previous Records. False by default, so 5348 * sorting and pagination will be handled directly on the client side, without 5349 * causing any new requests for data from the DataSource. 5350 * @type Boolean 5351 * @default false 5352 */ 5353 this.setAttributeConfig("dynamicData", { 5354 value: false, 5355 validator: lang.isBoolean 5356 }); 5357 5358 /** 5359 * @attribute MSG_EMPTY 5360 * @description Message to display if DataTable has no data. String 5361 * values are treated as markup and inserted into the DOM with innerHTML. 5362 * @type HTML 5363 * @default "No records found." 5364 */ 5365 this.setAttributeConfig("MSG_EMPTY", { 5366 value: "No records found.", 5367 validator: lang.isString 5368 }); 5369 5370 /** 5371 * @attribute MSG_LOADING 5372 * @description Message to display while DataTable is loading data. String 5373 * values are treated as markup and inserted into the DOM with innerHTML. 5374 * @type HTML 5375 * @default "Loading..." 5376 */ 5377 this.setAttributeConfig("MSG_LOADING", { 5378 value: "Loading...", 5379 validator: lang.isString 5380 }); 5381 5382 /** 5383 * @attribute MSG_ERROR 5384 * @description Message to display while DataTable has data error. String 5385 * values are treated as markup and inserted into the DOM with innerHTML. 5386 * @type HTML 5387 * @default "Data error." 5388 */ 5389 this.setAttributeConfig("MSG_ERROR", { 5390 value: "Data error.", 5391 validator: lang.isString 5392 }); 5393 5394 /** 5395 * @attribute MSG_SORTASC 5396 * @description Message to display in tooltip to sort Column in ascending 5397 * order. String values are treated as markup and inserted into the DOM as 5398 * innerHTML. 5399 * @type HTML 5400 * @default "Click to sort ascending" 5401 */ 5402 this.setAttributeConfig("MSG_SORTASC", { 5403 value: "Click to sort ascending", 5404 validator: lang.isString, 5405 method: function(sParam) { 5406 if(this._elThead) { 5407 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) { 5408 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) { 5409 allKeys[i]._elThLabel.firstChild.title = sParam; 5410 } 5411 } 5412 } 5413 } 5414 }); 5415 5416 /** 5417 * @attribute MSG_SORTDESC 5418 * @description Message to display in tooltip to sort Column in descending 5419 * order. String values are treated as markup and inserted into the DOM as 5420 * innerHTML. 5421 * @type HTML 5422 * @default "Click to sort descending" 5423 */ 5424 this.setAttributeConfig("MSG_SORTDESC", { 5425 value: "Click to sort descending", 5426 validator: lang.isString, 5427 method: function(sParam) { 5428 if(this._elThead) { 5429 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) { 5430 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) { 5431 allKeys[i]._elThLabel.firstChild.title = sParam; 5432 } 5433 } 5434 } 5435 } 5436 }); 5437 5438 /** 5439 * @attribute currencySymbol 5440 * @deprecated Use currencyOptions. 5441 */ 5442 this.setAttributeConfig("currencySymbol", { 5443 value: "$", 5444 validator: lang.isString 5445 }); 5446 5447 /** 5448 * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter. 5449 * @attribute currencyOptions 5450 * @type Object 5451 * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","} 5452 */ 5453 this.setAttributeConfig("currencyOptions", { 5454 value: { 5455 prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol 5456 decimalPlaces:2, 5457 decimalSeparator:".", 5458 thousandsSeparator:"," 5459 } 5460 }); 5461 5462 /** 5463 * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter. 5464 * @attribute dateOptions 5465 * @type Object 5466 * @default {format:"%m/%d/%Y", locale:"en"} 5467 */ 5468 this.setAttributeConfig("dateOptions", { 5469 value: {format:"%m/%d/%Y", locale:"en"} 5470 }); 5471 5472 /** 5473 * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter. 5474 * @attribute numberOptions 5475 * @type Object 5476 * @default {decimalPlaces:0, thousandsSeparator:","} 5477 */ 5478 this.setAttributeConfig("numberOptions", { 5479 value: { 5480 decimalPlaces:0, 5481 thousandsSeparator:"," 5482 } 5483 }); 5484 5485 }, 5486 5487 ///////////////////////////////////////////////////////////////////////////// 5488 // 5489 // Private member variables 5490 // 5491 ///////////////////////////////////////////////////////////////////////////// 5492 5493 /** 5494 * True if instance is initialized, so as to fire the initEvent after render. 5495 * 5496 * @property _bInit 5497 * @type Boolean 5498 * @default true 5499 * @private 5500 */ 5501 _bInit : true, 5502 5503 /** 5504 * Index assigned to instance. 5505 * 5506 * @property _nIndex 5507 * @type Number 5508 * @private 5509 */ 5510 _nIndex : null, 5511 5512 /** 5513 * Counter for IDs assigned to TR elements. 5514 * 5515 * @property _nTrCount 5516 * @type Number 5517 * @private 5518 */ 5519 _nTrCount : 0, 5520 5521 /** 5522 * Counter for IDs assigned to TD elements. 5523 * 5524 * @property _nTdCount 5525 * @type Number 5526 * @private 5527 */ 5528 _nTdCount : 0, 5529 5530 /** 5531 * Unique id assigned to instance "yui-dtN", useful prefix for generating unique 5532 * DOM ID strings and log messages. 5533 * 5534 * @property _sId 5535 * @type String 5536 * @private 5537 */ 5538 _sId : null, 5539 5540 /** 5541 * Render chain. 5542 * 5543 * @property _oChainRender 5544 * @type YAHOO.util.Chain 5545 * @private 5546 */ 5547 _oChainRender : null, 5548 5549 /** 5550 * DOM reference to the container element for the DataTable instance into which 5551 * all other elements get created. 5552 * 5553 * @property _elContainer 5554 * @type HTMLElement 5555 * @private 5556 */ 5557 _elContainer : null, 5558 5559 /** 5560 * DOM reference to the mask element for the DataTable instance which disables it. 5561 * 5562 * @property _elMask 5563 * @type HTMLElement 5564 * @private 5565 */ 5566 _elMask : null, 5567 5568 /** 5569 * DOM reference to the TABLE element for the DataTable instance. 5570 * 5571 * @property _elTable 5572 * @type HTMLElement 5573 * @private 5574 */ 5575 _elTable : null, 5576 5577 /** 5578 * DOM reference to the CAPTION element for the DataTable instance. 5579 * 5580 * @property _elCaption 5581 * @type HTMLElement 5582 * @private 5583 */ 5584 _elCaption : null, 5585 5586 /** 5587 * DOM reference to the COLGROUP element for the DataTable instance. 5588 * 5589 * @property _elColgroup 5590 * @type HTMLElement 5591 * @private 5592 */ 5593 _elColgroup : null, 5594 5595 /** 5596 * DOM reference to the THEAD element for the DataTable instance. 5597 * 5598 * @property _elThead 5599 * @type HTMLElement 5600 * @private 5601 */ 5602 _elThead : null, 5603 5604 /** 5605 * DOM reference to the primary TBODY element for the DataTable instance. 5606 * 5607 * @property _elTbody 5608 * @type HTMLElement 5609 * @private 5610 */ 5611 _elTbody : null, 5612 5613 /** 5614 * DOM reference to the secondary TBODY element used to display DataTable messages. 5615 * 5616 * @property _elMsgTbody 5617 * @type HTMLElement 5618 * @private 5619 */ 5620 _elMsgTbody : null, 5621 5622 /** 5623 * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages. 5624 * 5625 * @property _elMsgTr 5626 * @type HTMLElement 5627 * @private 5628 */ 5629 _elMsgTr : null, 5630 5631 /** 5632 * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages. 5633 * 5634 * @property _elMsgTd 5635 * @type HTMLElement 5636 * @private 5637 */ 5638 _elMsgTd : null, 5639 5640 /** 5641 * Element reference to shared Column drag target. 5642 * 5643 * @property _elColumnDragTarget 5644 * @type HTMLElement 5645 * @private 5646 */ 5647 _elColumnDragTarget : null, 5648 5649 /** 5650 * Element reference to shared Column resizer proxy. 5651 * 5652 * @property _elColumnResizerProxy 5653 * @type HTMLElement 5654 * @private 5655 */ 5656 _elColumnResizerProxy : null, 5657 5658 /** 5659 * DataSource instance for the DataTable instance. 5660 * 5661 * @property _oDataSource 5662 * @type YAHOO.util.DataSource 5663 * @private 5664 */ 5665 _oDataSource : null, 5666 5667 /** 5668 * ColumnSet instance for the DataTable instance. 5669 * 5670 * @property _oColumnSet 5671 * @type YAHOO.widget.ColumnSet 5672 * @private 5673 */ 5674 _oColumnSet : null, 5675 5676 /** 5677 * RecordSet instance for the DataTable instance. 5678 * 5679 * @property _oRecordSet 5680 * @type YAHOO.widget.RecordSet 5681 * @private 5682 */ 5683 _oRecordSet : null, 5684 5685 /** 5686 * The active CellEditor instance for the DataTable instance. 5687 * 5688 * @property _oCellEditor 5689 * @type YAHOO.widget.CellEditor 5690 * @private 5691 */ 5692 _oCellEditor : null, 5693 5694 /** 5695 * ID string of first TR element of the current DataTable page. 5696 * 5697 * @property _sFirstTrId 5698 * @type String 5699 * @private 5700 */ 5701 _sFirstTrId : null, 5702 5703 /** 5704 * ID string of the last TR element of the current DataTable page. 5705 * 5706 * @property _sLastTrId 5707 * @type String 5708 * @private 5709 */ 5710 _sLastTrId : null, 5711 5712 /** 5713 * Template row to create all new rows from. 5714 * @property _elTrTemplate 5715 * @type {HTMLElement} 5716 * @private 5717 */ 5718 _elTrTemplate : null, 5719 5720 /** 5721 * Sparse array of custom functions to set column widths for browsers that don't 5722 * support dynamic CSS rules. Functions are added at the index representing 5723 * the number of rows they update. 5724 * 5725 * @property _aDynFunctions 5726 * @type Array 5727 * @private 5728 */ 5729 _aDynFunctions : [], 5730 5731 /** 5732 * Disabled state. 5733 * 5734 * @property _disabled 5735 * @type Boolean 5736 * @private 5737 */ 5738 _disabled : false, 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 5754 5755 5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 ///////////////////////////////////////////////////////////////////////////// 5768 // 5769 // Private methods 5770 // 5771 ///////////////////////////////////////////////////////////////////////////// 5772 5773 /** 5774 * Clears browser text selection. Useful to call on rowSelectEvent or 5775 * cellSelectEvent to prevent clicks or dblclicks from selecting text in the 5776 * browser. 5777 * 5778 * @method clearTextSelection 5779 */ 5780 clearTextSelection : function() { 5781 var sel; 5782 if(window.getSelection) { 5783 sel = window.getSelection(); 5784 } 5785 else if(document.getSelection) { 5786 sel = document.getSelection(); 5787 } 5788 else if(document.selection) { 5789 sel = document.selection; 5790 } 5791 if(sel) { 5792 if(sel.empty) { 5793 sel.empty(); 5794 } 5795 else if (sel.removeAllRanges) { 5796 sel.removeAllRanges(); 5797 } 5798 else if(sel.collapse) { 5799 sel.collapse(); 5800 } 5801 } 5802 }, 5803 5804 /** 5805 * Sets focus on the given element. 5806 * 5807 * @method _focusEl 5808 * @param el {HTMLElement} Element. 5809 * @private 5810 */ 5811 _focusEl : function(el) { 5812 el = el || this._elTbody; 5813 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets 5814 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing 5815 // strange unexpected things as the user clicks on buttons and other controls. 5816 setTimeout(function() { 5817 try { 5818 el.focus(); 5819 } 5820 catch(e) { 5821 } 5822 },0); 5823 }, 5824 5825 /** 5826 * Forces Gecko repaint. 5827 * 5828 * @method _repaintGecko 5829 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body. 5830 * @private 5831 */ 5832 _repaintGecko : (ua.gecko) ? 5833 function(el) { 5834 el = el || this._elContainer; 5835 var parent = el.parentNode; 5836 var nextSibling = el.nextSibling; 5837 parent.insertBefore(parent.removeChild(el), nextSibling); 5838 } : function() {}, 5839 5840 /** 5841 * Forces Opera repaint. 5842 * 5843 * @method _repaintOpera 5844 * @private 5845 */ 5846 _repaintOpera : (ua.opera) ? 5847 function() { 5848 if(ua.opera) { 5849 document.documentElement.className += " "; 5850 document.documentElement.className = YAHOO.lang.trim(document.documentElement.className); 5851 } 5852 } : function() {} , 5853 5854 /** 5855 * Forces Webkit repaint. 5856 * 5857 * @method _repaintWebkit 5858 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body. 5859 * @private 5860 */ 5861 _repaintWebkit : (ua.webkit) ? 5862 function(el) { 5863 el = el || this._elContainer; 5864 var parent = el.parentNode; 5865 var nextSibling = el.nextSibling; 5866 parent.insertBefore(parent.removeChild(el), nextSibling); 5867 } : function() {}, 5868 5869 5870 5871 5872 5873 5874 5875 5876 5877 5878 5879 5880 5881 5882 5883 5884 5885 5886 5887 5888 5889 5890 // INIT FUNCTIONS 5891 5892 /** 5893 * Initializes object literal of config values. 5894 * 5895 * @method _initConfigs 5896 * @param oConfig {Object} Object literal of config values. 5897 * @private 5898 */ 5899 _initConfigs : function(oConfigs) { 5900 if(!oConfigs || !lang.isObject(oConfigs)) { 5901 oConfigs = {}; 5902 } 5903 this.configs = oConfigs; 5904 }, 5905 5906 /** 5907 * Initializes ColumnSet. 5908 * 5909 * @method _initColumnSet 5910 * @param aColumnDefs {Object[]} Array of object literal Column definitions. 5911 * @private 5912 */ 5913 _initColumnSet : function(aColumnDefs) { 5914 var oColumn, i, len; 5915 5916 if(this._oColumnSet) { 5917 // First clear _oDynStyles for existing ColumnSet and 5918 // uregister CellEditor Custom Events 5919 for(i=0, len=this._oColumnSet.keys.length; i<len; i++) { 5920 oColumn = this._oColumnSet.keys[i]; 5921 DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined; 5922 if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility 5923 oColumn.editor.unsubscribeAll(); 5924 } 5925 } 5926 5927 this._oColumnSet = null; 5928 this._clearTrTemplateEl(); 5929 } 5930 5931 if(lang.isArray(aColumnDefs)) { 5932 this._oColumnSet = new YAHOO.widget.ColumnSet(aColumnDefs); 5933 } 5934 // Backward compatibility 5935 else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) { 5936 this._oColumnSet = aColumnDefs; 5937 } 5938 5939 // Register CellEditor Custom Events 5940 var allKeys = this._oColumnSet.keys; 5941 for(i=0, len=allKeys.length; i<len; i++) { 5942 oColumn = allKeys[i]; 5943 if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility 5944 oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true); 5945 oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true); 5946 oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true); 5947 oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true); 5948 oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true); 5949 oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true); 5950 oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true); 5951 oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true); 5952 } 5953 } 5954 }, 5955 5956 /** 5957 * Initializes DataSource. 5958 * 5959 * @method _initDataSource 5960 * @param oDataSource {YAHOO.util.DataSource} DataSource instance. 5961 * @private 5962 */ 5963 _initDataSource : function(oDataSource) { 5964 this._oDataSource = null; 5965 if(oDataSource && (lang.isFunction(oDataSource.sendRequest))) { 5966 this._oDataSource = oDataSource; 5967 } 5968 // Backward compatibility 5969 else { 5970 var tmpTable = null; 5971 var tmpContainer = this._elContainer; 5972 var i=0; 5973 //TODO: this will break if re-initing DS at runtime for SDT 5974 // Peek in container child nodes to see if TABLE already exists 5975 if(tmpContainer.hasChildNodes()) { 5976 var tmpChildren = tmpContainer.childNodes; 5977 for(i=0; i<tmpChildren.length; i++) { 5978 if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") { 5979 tmpTable = tmpChildren[i]; 5980 break; 5981 } 5982 } 5983 if(tmpTable) { 5984 var tmpFieldsArray = []; 5985 for(; i<this._oColumnSet.keys.length; i++) { 5986 tmpFieldsArray.push({key:this._oColumnSet.keys[i].key}); 5987 } 5988 5989 this._oDataSource = new DS(tmpTable); 5990 this._oDataSource.responseType = DS.TYPE_HTMLTABLE; 5991 this._oDataSource.responseSchema = {fields: tmpFieldsArray}; 5992 } 5993 } 5994 } 5995 }, 5996 5997 /** 5998 * Initializes RecordSet. 5999 * 6000 * @method _initRecordSet 6001 * @private 6002 */ 6003 _initRecordSet : function() { 6004 if(this._oRecordSet) { 6005 this._oRecordSet.reset(); 6006 } 6007 else { 6008 this._oRecordSet = new YAHOO.widget.RecordSet(); 6009 } 6010 }, 6011 6012 /** 6013 * Initializes DOM elements. 6014 * 6015 * @method _initDomElements 6016 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 6017 * return {Boolean} False in case of error, otherwise true 6018 * @private 6019 */ 6020 _initDomElements : function(elContainer) { 6021 // Outer container 6022 this._initContainerEl(elContainer); 6023 // TABLE 6024 this._initTableEl(this._elContainer); 6025 // COLGROUP 6026 this._initColgroupEl(this._elTable); 6027 // THEAD 6028 this._initTheadEl(this._elTable); 6029 6030 // Message TBODY 6031 this._initMsgTbodyEl(this._elTable); 6032 6033 // Primary TBODY 6034 this._initTbodyEl(this._elTable); 6035 6036 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody) { 6037 return false; 6038 } 6039 else { 6040 return true; 6041 } 6042 }, 6043 6044 /** 6045 * Destroy's the DataTable outer container element, if available. 6046 * 6047 * @method _destroyContainerEl 6048 * @param elContainer {HTMLElement} Reference to the container element. 6049 * @private 6050 */ 6051 _destroyContainerEl : function(elContainer) { 6052 var columns = this._oColumnSet.keys, 6053 elements, i; 6054 6055 Dom.removeClass(elContainer, DT.CLASS_DATATABLE); 6056 6057 // Bug 2528783 6058 Ev.purgeElement( elContainer ); 6059 Ev.purgeElement( this._elThead, true ); // recursive to get resize handles 6060 Ev.purgeElement( this._elTbody ); 6061 Ev.purgeElement( this._elMsgTbody ); 6062 6063 // because change doesn't bubble, each select (via formatDropdown) gets 6064 // its own subscription 6065 elements = elContainer.getElementsByTagName( 'select' ); 6066 6067 if ( elements.length ) { 6068 Ev.detachListener( elements, 'change' ); 6069 } 6070 6071 for ( i = columns.length - 1; i >= 0; --i ) { 6072 if ( columns[i].editor ) { 6073 Ev.purgeElement( columns[i].editor._elContainer ); 6074 } 6075 } 6076 6077 elContainer.innerHTML = ""; 6078 6079 this._elContainer = null; 6080 this._elColgroup = null; 6081 this._elThead = null; 6082 this._elTbody = null; 6083 }, 6084 6085 /** 6086 * Initializes the DataTable outer container element, including a mask. 6087 * 6088 * @method _initContainerEl 6089 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 6090 * @private 6091 */ 6092 _initContainerEl : function(elContainer) { 6093 // Validate container 6094 elContainer = Dom.get(elContainer); 6095 6096 if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) { 6097 // Destroy previous 6098 this._destroyContainerEl(elContainer); 6099 6100 Dom.addClass(elContainer, DT.CLASS_DATATABLE); 6101 Ev.addListener(elContainer, "focus", this._onTableFocus, this); 6102 Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this); 6103 this._elContainer = elContainer; 6104 6105 var elMask = document.createElement("div"); 6106 elMask.className = DT.CLASS_MASK; 6107 elMask.style.display = "none"; 6108 this._elMask = elContainer.appendChild(elMask); 6109 } 6110 }, 6111 6112 /** 6113 * Destroy's the DataTable TABLE element, if available. 6114 * 6115 * @method _destroyTableEl 6116 * @private 6117 */ 6118 _destroyTableEl : function() { 6119 var elTable = this._elTable; 6120 if(elTable) { 6121 Ev.purgeElement(elTable, true); 6122 elTable.parentNode.removeChild(elTable); 6123 this._elCaption = null; 6124 this._elColgroup = null; 6125 this._elThead = null; 6126 this._elTbody = null; 6127 } 6128 }, 6129 6130 /** 6131 * Creates HTML markup CAPTION element. 6132 * 6133 * @method _initCaptionEl 6134 * @param sCaption {HTML} Caption value. String values are treated as markup and 6135 * inserted into the DOM with innerHTML. 6136 * @private 6137 */ 6138 _initCaptionEl : function(sCaption) { 6139 if(this._elTable && sCaption) { 6140 // Create CAPTION element 6141 if(!this._elCaption) { 6142 this._elCaption = this._elTable.createCaption(); 6143 } 6144 // Set CAPTION value 6145 this._elCaption.innerHTML = sCaption; 6146 } 6147 else if(this._elCaption) { 6148 this._elCaption.parentNode.removeChild(this._elCaption); 6149 } 6150 }, 6151 6152 /** 6153 * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer 6154 * container element. 6155 * 6156 * @method _initTableEl 6157 * @param elContainer {HTMLElement} Container element into which to create TABLE. 6158 * @private 6159 */ 6160 _initTableEl : function(elContainer) { 6161 if(elContainer) { 6162 // Destroy previous 6163 this._destroyTableEl(); 6164 6165 // Create TABLE 6166 this._elTable = elContainer.appendChild(document.createElement("table")); 6167 6168 // Set SUMMARY attribute 6169 this._elTable.summary = this.get("summary"); 6170 6171 // Create CAPTION element 6172 if(this.get("caption")) { 6173 this._initCaptionEl(this.get("caption")); 6174 } 6175 6176 // Set up mouseover/mouseout events via mouseenter/mouseleave delegation 6177 Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this); 6178 Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this); 6179 Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-data>tr>td", this); 6180 Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-data>tr>td", this); 6181 Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-message>tr>td", this); 6182 Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-message>tr>td", this); 6183 } 6184 }, 6185 6186 /** 6187 * Destroy's the DataTable COLGROUP element, if available. 6188 * 6189 * @method _destroyColgroupEl 6190 * @private 6191 */ 6192 _destroyColgroupEl : function() { 6193 var elColgroup = this._elColgroup; 6194 if(elColgroup) { 6195 var elTable = elColgroup.parentNode; 6196 Ev.purgeElement(elColgroup, true); 6197 elTable.removeChild(elColgroup); 6198 this._elColgroup = null; 6199 } 6200 }, 6201 6202 /** 6203 * Initializes COLGROUP and COL elements for managing minWidth. 6204 * 6205 * @method _initColgroupEl 6206 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP. 6207 * @private 6208 */ 6209 _initColgroupEl : function(elTable) { 6210 if(elTable) { 6211 // Destroy previous 6212 this._destroyColgroupEl(); 6213 6214 // Add COLs to DOCUMENT FRAGMENT 6215 var allCols = this._aColIds || [], 6216 allKeys = this._oColumnSet.keys, 6217 i = 0, len = allCols.length, 6218 elCol, oColumn, 6219 elFragment = document.createDocumentFragment(), 6220 elColTemplate = document.createElement("col"); 6221 6222 for(i=0,len=allKeys.length; i<len; i++) { 6223 oColumn = allKeys[i]; 6224 elCol = elFragment.appendChild(elColTemplate.cloneNode(false)); 6225 } 6226 6227 // Create COLGROUP 6228 var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild); 6229 elColgroup.appendChild(elFragment); 6230 this._elColgroup = elColgroup; 6231 } 6232 }, 6233 6234 /** 6235 * Adds a COL element to COLGROUP at given index. 6236 * 6237 * @method _insertColgroupColEl 6238 * @param index {Number} Index of new COL element. 6239 * @private 6240 */ 6241 _insertColgroupColEl : function(index) { 6242 if(lang.isNumber(index)&& this._elColgroup) { 6243 var nextSibling = this._elColgroup.childNodes[index] || null; 6244 this._elColgroup.insertBefore(document.createElement("col"), nextSibling); 6245 } 6246 }, 6247 6248 /** 6249 * Removes a COL element to COLGROUP at given index. 6250 * 6251 * @method _removeColgroupColEl 6252 * @param index {Number} Index of removed COL element. 6253 * @private 6254 */ 6255 _removeColgroupColEl : function(index) { 6256 if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) { 6257 this._elColgroup.removeChild(this._elColgroup.childNodes[index]); 6258 } 6259 }, 6260 6261 /** 6262 * Reorders a COL element from old index(es) to new index. 6263 * 6264 * @method _reorderColgroupColEl 6265 * @param aKeyIndexes {Number[]} Array of indexes of removed COL element. 6266 * @param newIndex {Number} New index. 6267 * @private 6268 */ 6269 _reorderColgroupColEl : function(aKeyIndexes, newIndex) { 6270 if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) { 6271 var i, 6272 tmpCols = []; 6273 // Remove COL 6274 for(i=aKeyIndexes.length-1; i>-1; i--) { 6275 tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]])); 6276 } 6277 // Insert COL 6278 var nextSibling = this._elColgroup.childNodes[newIndex] || null; 6279 for(i=tmpCols.length-1; i>-1; i--) { 6280 this._elColgroup.insertBefore(tmpCols[i], nextSibling); 6281 } 6282 } 6283 }, 6284 6285 /** 6286 * Destroy's the DataTable THEAD element, if available. 6287 * 6288 * @method _destroyTheadEl 6289 * @private 6290 */ 6291 _destroyTheadEl : function() { 6292 var elThead = this._elThead; 6293 if(elThead) { 6294 var elTable = elThead.parentNode; 6295 Ev.purgeElement(elThead, true); 6296 this._destroyColumnHelpers(); 6297 elTable.removeChild(elThead); 6298 this._elThead = null; 6299 } 6300 }, 6301 6302 /** 6303 * Initializes THEAD element. 6304 * 6305 * @method _initTheadEl 6306 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP. 6307 * @param {HTMLElement} Initialized THEAD element. 6308 * @private 6309 */ 6310 _initTheadEl : function(elTable) { 6311 elTable = elTable || this._elTable; 6312 6313 if(elTable) { 6314 // Destroy previous 6315 this._destroyTheadEl(); 6316 6317 //TODO: append to DOM later for performance 6318 var elThead = (this._elColgroup) ? 6319 elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) : 6320 elTable.appendChild(document.createElement("thead")); 6321 6322 // Set up DOM events for THEAD 6323 Ev.addListener(elThead, "focus", this._onTheadFocus, this); 6324 Ev.addListener(elThead, "keydown", this._onTheadKeydown, this); 6325 Ev.addListener(elThead, "mousedown", this._onTableMousedown, this); 6326 Ev.addListener(elThead, "mouseup", this._onTableMouseup, this); 6327 Ev.addListener(elThead, "click", this._onTheadClick, this); 6328 6329 // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave 6330 // delegation at the TABLE level 6331 6332 // Since we can't listen for click and dblclick on the same element... 6333 // Attach separately to THEAD and TBODY 6334 ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this); 6335 6336 var oColumnSet = this._oColumnSet, 6337 oColumn, i,j, l; 6338 6339 // Add TRs to the THEAD 6340 var colTree = oColumnSet.tree; 6341 var elTh; 6342 for(i=0; i<colTree.length; i++) { 6343 var elTheadTr = elThead.appendChild(document.createElement("tr")); 6344 6345 // ...and create TH cells 6346 for(j=0; j<colTree[i].length; j++) { 6347 oColumn = colTree[i][j]; 6348 elTh = elTheadTr.appendChild(document.createElement("th")); 6349 this._initThEl(elTh,oColumn); 6350 } 6351 6352 // Set FIRST/LAST on THEAD rows 6353 if(i === 0) { 6354 Dom.addClass(elTheadTr, DT.CLASS_FIRST); 6355 } 6356 if(i === (colTree.length-1)) { 6357 Dom.addClass(elTheadTr, DT.CLASS_LAST); 6358 } 6359 6360 } 6361 6362 // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array 6363 var aFirstHeaders = oColumnSet.headers[0] || []; 6364 for(i=0; i<aFirstHeaders.length; i++) { 6365 Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST); 6366 } 6367 var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || []; 6368 for(i=0; i<aLastHeaders.length; i++) { 6369 Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST); 6370 } 6371 6372 6373 ///TODO: try _repaintGecko(this._elContainer) instead 6374 // Bug 1806891 6375 if(ua.webkit && ua.webkit < 420) { 6376 var oSelf = this; 6377 setTimeout(function() { 6378 elThead.style.display = ""; 6379 },0); 6380 elThead.style.display = 'none'; 6381 } 6382 6383 this._elThead = elThead; 6384 6385 // Column helpers needs _elThead to exist 6386 this._initColumnHelpers(); 6387 } 6388 }, 6389 6390 /** 6391 * Populates TH element as defined by Column. 6392 * 6393 * @method _initThEl 6394 * @param elTh {HTMLElement} TH element reference. 6395 * @param oColumn {YAHOO.widget.Column} Column object. 6396 * @private 6397 */ 6398 _initThEl : function(elTh, oColumn) { 6399 elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD 6400 elTh.innerHTML = ""; 6401 elTh.rowSpan = oColumn.getRowspan(); 6402 elTh.colSpan = oColumn.getColspan(); 6403 oColumn._elTh = elTh; 6404 6405 var elThLiner = elTh.appendChild(document.createElement("div")); 6406 elThLiner.id = elTh.id + "-liner"; // Needed for resizer 6407 elThLiner.className = DT.CLASS_LINER; 6408 oColumn._elThLiner = elThLiner; 6409 6410 var elThLabel = elThLiner.appendChild(document.createElement("span")); 6411 elThLabel.className = DT.CLASS_LABEL; 6412 6413 // Assign abbr attribute 6414 if(oColumn.abbr) { 6415 elTh.abbr = oColumn.abbr; 6416 } 6417 // Clear minWidth on hidden Columns 6418 if(oColumn.hidden) { 6419 this._clearMinWidth(oColumn); 6420 } 6421 6422 elTh.className = this._getColumnClassNames(oColumn); 6423 6424 // Set Column width... 6425 if(oColumn.width) { 6426 // Validate minWidth 6427 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ? 6428 oColumn.minWidth : oColumn.width; 6429 // ...for fallback cases 6430 if(DT._bDynStylesFallback) { 6431 elTh.firstChild.style.overflow = 'hidden'; 6432 elTh.firstChild.style.width = nWidth + 'px'; 6433 } 6434 // ...for non fallback cases 6435 else { 6436 this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden'); 6437 } 6438 } 6439 6440 this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy")); 6441 oColumn._elThLabel = elThLabel; 6442 }, 6443 6444 /** 6445 * Outputs markup into the given TH based on given Column. 6446 * 6447 * @method formatTheadCell 6448 * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner, 6449 * not the liner DIV element. 6450 * @param oColumn {YAHOO.widget.Column} Column instance. 6451 * @param oSortedBy {Object} Sort state object literal. 6452 */ 6453 formatTheadCell : function(elCellLabel, oColumn, oSortedBy) { 6454 var sKey = oColumn.getKey(); 6455 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey; 6456 6457 // Add accessibility link for sortable Columns 6458 if(oColumn.sortable) { 6459 // Calculate the direction 6460 var sSortClass = this.getColumnSortDir(oColumn, oSortedBy); 6461 var bDesc = (sSortClass === DT.CLASS_DESC); 6462 6463 // This is the sorted Column 6464 if(oSortedBy && (oColumn.key === oSortedBy.key)) { 6465 bDesc = !(oSortedBy.dir === DT.CLASS_DESC); 6466 } 6467 6468 // Generate a unique HREF for visited status 6469 var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey(); 6470 6471 // Generate a dynamic TITLE for sort status 6472 var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC"); 6473 6474 // Format the element 6475 elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>"; 6476 } 6477 // Just display the label for non-sortable Columns 6478 else { 6479 elCellLabel.innerHTML = sLabel; 6480 } 6481 }, 6482 6483 /** 6484 * Disables DD from top-level Column TH elements. 6485 * 6486 * @method _destroyDraggableColumns 6487 * @private 6488 */ 6489 _destroyDraggableColumns : function() { 6490 var oColumn, elTh; 6491 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) { 6492 oColumn = this._oColumnSet.tree[0][i]; 6493 if(oColumn._dd) { 6494 oColumn._dd = oColumn._dd.unreg(); 6495 Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE); 6496 } 6497 } 6498 6499 // Destroy column drag proxy 6500 this._destroyColumnDragTargetEl(); 6501 }, 6502 6503 /** 6504 * Initializes top-level Column TH elements into DD instances. 6505 * 6506 * @method _initDraggableColumns 6507 * @private 6508 */ 6509 _initDraggableColumns : function() { 6510 this._destroyDraggableColumns(); 6511 if(util.DD) { 6512 var oColumn, elTh, elDragTarget; 6513 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) { 6514 oColumn = this._oColumnSet.tree[0][i]; 6515 elTh = oColumn.getThEl(); 6516 Dom.addClass(elTh, DT.CLASS_DRAGGABLE); 6517 elDragTarget = this._initColumnDragTargetEl(); 6518 oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget); 6519 } 6520 } 6521 else { 6522 } 6523 }, 6524 6525 /** 6526 * Destroys shared Column drag target. 6527 * 6528 * @method _destroyColumnDragTargetEl 6529 * @private 6530 */ 6531 _destroyColumnDragTargetEl : function() { 6532 if(this._elColumnDragTarget) { 6533 var el = this._elColumnDragTarget; 6534 YAHOO.util.Event.purgeElement(el); 6535 el.parentNode.removeChild(el); 6536 this._elColumnDragTarget = null; 6537 } 6538 }, 6539 6540 /** 6541 * Creates HTML markup for shared Column drag target. 6542 * 6543 * @method _initColumnDragTargetEl 6544 * @return {HTMLElement} Reference to Column drag target. 6545 * @private 6546 */ 6547 _initColumnDragTargetEl : function() { 6548 if(!this._elColumnDragTarget) { 6549 // Attach Column drag target element as first child of body 6550 var elColumnDragTarget = document.createElement('div'); 6551 elColumnDragTarget.id = this.getId() + "-coltarget"; 6552 elColumnDragTarget.className = DT.CLASS_COLTARGET; 6553 elColumnDragTarget.style.display = "none"; 6554 document.body.insertBefore(elColumnDragTarget, document.body.firstChild); 6555 6556 // Internal tracker of Column drag target 6557 this._elColumnDragTarget = elColumnDragTarget; 6558 6559 } 6560 return this._elColumnDragTarget; 6561 }, 6562 6563 /** 6564 * Disables resizeability on key Column TH elements. 6565 * 6566 * @method _destroyResizeableColumns 6567 * @private 6568 */ 6569 _destroyResizeableColumns : function() { 6570 var aKeys = this._oColumnSet.keys; 6571 for(var i=0, len=aKeys.length; i<len; i++) { 6572 if(aKeys[i]._ddResizer) { 6573 aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg(); 6574 Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE); 6575 } 6576 } 6577 6578 // Destroy resizer proxy 6579 this._destroyColumnResizerProxyEl(); 6580 }, 6581 6582 /** 6583 * Initializes resizeability on key Column TH elements. 6584 * 6585 * @method _initResizeableColumns 6586 * @private 6587 */ 6588 _initResizeableColumns : function() { 6589 this._destroyResizeableColumns(); 6590 if(util.DD) { 6591 var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick; 6592 for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) { 6593 oColumn = this._oColumnSet.keys[i]; 6594 if(oColumn.resizeable) { 6595 elTh = oColumn.getThEl(); 6596 Dom.addClass(elTh, DT.CLASS_RESIZEABLE); 6597 elThLiner = oColumn.getThLinerEl(); 6598 6599 // Bug 1915349: So resizer is as tall as TH when rowspan > 1 6600 // Create a separate resizer liner with position:relative 6601 elThResizerLiner = elTh.appendChild(document.createElement("div")); 6602 elThResizerLiner.className = DT.CLASS_RESIZERLINER; 6603 6604 // Move TH contents into the new resizer liner 6605 elThResizerLiner.appendChild(elThLiner); 6606 6607 // Create the resizer 6608 elThResizer = elThResizerLiner.appendChild(document.createElement("div")); 6609 elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer 6610 elThResizer.className = DT.CLASS_RESIZER; 6611 oColumn._elResizer = elThResizer; 6612 6613 // Create the resizer proxy, once per instance 6614 elResizerProxy = this._initColumnResizerProxyEl(); 6615 oColumn._ddResizer = new YAHOO.util.ColumnResizer( 6616 this, oColumn, elTh, elThResizer, elResizerProxy); 6617 cancelClick = function(e) { 6618 Ev.stopPropagation(e); 6619 }; 6620 Ev.addListener(elThResizer,"click",cancelClick); 6621 } 6622 } 6623 } 6624 else { 6625 } 6626 }, 6627 6628 /** 6629 * Destroys shared Column resizer proxy. 6630 * 6631 * @method _destroyColumnResizerProxyEl 6632 * @return {HTMLElement} Reference to Column resizer proxy. 6633 * @private 6634 */ 6635 _destroyColumnResizerProxyEl : function() { 6636 if(this._elColumnResizerProxy) { 6637 var el = this._elColumnResizerProxy; 6638 YAHOO.util.Event.purgeElement(el); 6639 el.parentNode.removeChild(el); 6640 this._elColumnResizerProxy = null; 6641 } 6642 }, 6643 6644 /** 6645 * Creates HTML markup for shared Column resizer proxy. 6646 * 6647 * @method _initColumnResizerProxyEl 6648 * @return {HTMLElement} Reference to Column resizer proxy. 6649 * @private 6650 */ 6651 _initColumnResizerProxyEl : function() { 6652 if(!this._elColumnResizerProxy) { 6653 // Attach Column resizer element as first child of body 6654 var elColumnResizerProxy = document.createElement("div"); 6655 elColumnResizerProxy.id = this.getId() + "-colresizerproxy"; // Needed for ColumnResizer 6656 elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY; 6657 document.body.insertBefore(elColumnResizerProxy, document.body.firstChild); 6658 6659 // Internal tracker of Column resizer proxy 6660 this._elColumnResizerProxy = elColumnResizerProxy; 6661 } 6662 return this._elColumnResizerProxy; 6663 }, 6664 6665 /** 6666 * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers. 6667 * 6668 * @method _destroyColumnHelpers 6669 * @private 6670 */ 6671 _destroyColumnHelpers : function() { 6672 this._destroyDraggableColumns(); 6673 this._destroyResizeableColumns(); 6674 }, 6675 6676 /** 6677 * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers. 6678 * 6679 * @method _initColumnHelpers 6680 * @private 6681 */ 6682 _initColumnHelpers : function() { 6683 if(this.get("draggableColumns")) { 6684 this._initDraggableColumns(); 6685 } 6686 this._initResizeableColumns(); 6687 }, 6688 6689 /** 6690 * Destroy's the DataTable TBODY element, if available. 6691 * 6692 * @method _destroyTbodyEl 6693 * @private 6694 */ 6695 _destroyTbodyEl : function() { 6696 var elTbody = this._elTbody; 6697 if(elTbody) { 6698 var elTable = elTbody.parentNode; 6699 Ev.purgeElement(elTbody, true); 6700 elTable.removeChild(elTbody); 6701 this._elTbody = null; 6702 } 6703 }, 6704 6705 /** 6706 * Initializes TBODY element for data. 6707 * 6708 * @method _initTbodyEl 6709 * @param elTable {HTMLElement} TABLE element into which to create TBODY . 6710 * @private 6711 */ 6712 _initTbodyEl : function(elTable) { 6713 if(elTable) { 6714 // Destroy previous 6715 this._destroyTbodyEl(); 6716 6717 // Create TBODY 6718 var elTbody = elTable.appendChild(document.createElement("tbody")); 6719 elTbody.tabIndex = 0; 6720 elTbody.className = DT.CLASS_DATA; 6721 6722 // Set up DOM events for TBODY 6723 Ev.addListener(elTbody, "focus", this._onTbodyFocus, this); 6724 Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this); 6725 Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this); 6726 Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this); 6727 Ev.addListener(elTbody, "click", this._onTbodyClick, this); 6728 6729 // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave 6730 // delegation at the TABLE level 6731 6732 // Since we can't listen for click and dblclick on the same element... 6733 // Attach separately to THEAD and TBODY 6734 ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this); 6735 6736 6737 // IE puts focus outline in the wrong place 6738 if(ua.ie) { 6739 elTbody.hideFocus=true; 6740 } 6741 6742 this._elTbody = elTbody; 6743 } 6744 }, 6745 6746 /** 6747 * Destroy's the DataTable message TBODY element, if available. 6748 * 6749 * @method _destroyMsgTbodyEl 6750 * @private 6751 */ 6752 _destroyMsgTbodyEl : function() { 6753 var elMsgTbody = this._elMsgTbody; 6754 if(elMsgTbody) { 6755 var elTable = elMsgTbody.parentNode; 6756 Ev.purgeElement(elMsgTbody, true); 6757 elTable.removeChild(elMsgTbody); 6758 this._elTbody = null; 6759 } 6760 }, 6761 6762 /** 6763 * Initializes TBODY element for messaging. 6764 * 6765 * @method _initMsgTbodyEl 6766 * @param elTable {HTMLElement} TABLE element into which to create TBODY 6767 * @private 6768 */ 6769 _initMsgTbodyEl : function(elTable) { 6770 if(elTable) { 6771 var elMsgTbody = document.createElement("tbody"); 6772 elMsgTbody.className = DT.CLASS_MESSAGE; 6773 var elMsgTr = elMsgTbody.appendChild(document.createElement("tr")); 6774 elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST; 6775 this._elMsgTr = elMsgTr; 6776 var elMsgTd = elMsgTr.appendChild(document.createElement("td")); 6777 elMsgTd.colSpan = this._oColumnSet.keys.length || 1; 6778 elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST; 6779 this._elMsgTd = elMsgTd; 6780 elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody); 6781 var elMsgLiner = elMsgTd.appendChild(document.createElement("div")); 6782 elMsgLiner.className = DT.CLASS_LINER; 6783 this._elMsgTbody = elMsgTbody; 6784 6785 // Set up DOM events for TBODY 6786 Ev.addListener(elMsgTbody, "focus", this._onTbodyFocus, this); 6787 Ev.addListener(elMsgTbody, "mousedown", this._onTableMousedown, this); 6788 Ev.addListener(elMsgTbody, "mouseup", this._onTableMouseup, this); 6789 Ev.addListener(elMsgTbody, "keydown", this._onTbodyKeydown, this); 6790 Ev.addListener(elMsgTbody, "click", this._onTbodyClick, this); 6791 6792 // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave 6793 // delegation at the TABLE level 6794 } 6795 }, 6796 6797 /** 6798 * Initialize internal event listeners 6799 * 6800 * @method _initEvents 6801 * @private 6802 */ 6803 _initEvents : function () { 6804 // Initialize Column sort 6805 this._initColumnSort(); 6806 6807 // Add the document level click listener 6808 YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this); 6809 6810 // Paginator integration 6811 this.subscribe("paginatorChange",function () { 6812 this._handlePaginatorChange.apply(this,arguments); 6813 }); 6814 6815 this.subscribe("initEvent",function () { 6816 this.renderPaginator(); 6817 }); 6818 6819 // Initialize CellEditor integration 6820 this._initCellEditing(); 6821 }, 6822 6823 /** 6824 * Initializes Column sorting. 6825 * 6826 * @method _initColumnSort 6827 * @private 6828 */ 6829 _initColumnSort : function() { 6830 this.subscribe("theadCellClickEvent", this.onEventSortColumn); 6831 6832 // Backward compatibility 6833 var oSortedBy = this.get("sortedBy"); 6834 if(oSortedBy) { 6835 if(oSortedBy.dir == "desc") { 6836 this._configs.sortedBy.value.dir = DT.CLASS_DESC; 6837 } 6838 else if(oSortedBy.dir == "asc") { 6839 this._configs.sortedBy.value.dir = DT.CLASS_ASC; 6840 } 6841 } 6842 }, 6843 6844 /** 6845 * Initializes CellEditor integration. 6846 * 6847 * @method _initCellEditing 6848 * @private 6849 */ 6850 _initCellEditing : function() { 6851 this.subscribe("editorBlurEvent",function () { 6852 this.onEditorBlurEvent.apply(this,arguments); 6853 }); 6854 this.subscribe("editorBlockEvent",function () { 6855 this.onEditorBlockEvent.apply(this,arguments); 6856 }); 6857 this.subscribe("editorUnblockEvent",function () { 6858 this.onEditorUnblockEvent.apply(this,arguments); 6859 }); 6860 }, 6861 6862 6863 6864 6865 6866 6867 6868 6869 6870 6871 6872 6873 6874 6875 6876 6877 6878 6879 6880 6881 6882 6883 6884 6885 6886 6887 6888 6889 6890 6891 6892 6893 6894 // DOM MUTATION FUNCTIONS 6895 6896 /** 6897 * Retruns classnames to represent current Column states. 6898 * @method _getColumnClassnames 6899 * @param oColumn {YAHOO.widget.Column} Column instance. 6900 * @param aAddClasses {String[]} An array of additional classnames to add to the 6901 * return value. 6902 * @return {String} A String of classnames to be assigned to TH or TD elements 6903 * for given Column. 6904 * @private 6905 */ 6906 _getColumnClassNames : function (oColumn, aAddClasses) { 6907 var allClasses; 6908 6909 // Add CSS classes 6910 if(lang.isString(oColumn.className)) { 6911 // Single custom class 6912 allClasses = [oColumn.className]; 6913 } 6914 else if(lang.isArray(oColumn.className)) { 6915 // Array of custom classes 6916 allClasses = oColumn.className; 6917 } 6918 else { 6919 // no custom classes 6920 allClasses = []; 6921 } 6922 6923 // Hook for setting width with via dynamic style uses key since ID is too disposable 6924 allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey(); 6925 6926 // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":" 6927 allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey(); 6928 6929 var isSortedBy = this.get("sortedBy") || {}; 6930 // Sorted 6931 if(oColumn.key === isSortedBy.key) { 6932 allClasses[allClasses.length] = isSortedBy.dir || ''; 6933 } 6934 // Hidden 6935 if(oColumn.hidden) { 6936 allClasses[allClasses.length] = DT.CLASS_HIDDEN; 6937 } 6938 // Selected 6939 if(oColumn.selected) { 6940 allClasses[allClasses.length] = DT.CLASS_SELECTED; 6941 } 6942 // Sortable 6943 if(oColumn.sortable) { 6944 allClasses[allClasses.length] = DT.CLASS_SORTABLE; 6945 } 6946 // Resizeable 6947 if(oColumn.resizeable) { 6948 allClasses[allClasses.length] = DT.CLASS_RESIZEABLE; 6949 } 6950 // Editable 6951 if(oColumn.editor) { 6952 allClasses[allClasses.length] = DT.CLASS_EDITABLE; 6953 } 6954 6955 // Addtnl classes, including First/Last 6956 if(aAddClasses) { 6957 allClasses = allClasses.concat(aAddClasses); 6958 } 6959 6960 return allClasses.join(' '); 6961 }, 6962 6963 /** 6964 * Clears TR element template in response to any Column state change. 6965 * @method _clearTrTemplateEl 6966 * @private 6967 */ 6968 _clearTrTemplateEl : function () { 6969 this._elTrTemplate = null; 6970 }, 6971 6972 /** 6973 * Returns a new TR element template with TD elements classed with current 6974 * Column states. 6975 * @method _getTrTemplateEl 6976 * @return {HTMLElement} A TR element to be cloned and added to the DOM. 6977 * @private 6978 */ 6979 _getTrTemplateEl : function (oRecord, index) { 6980 // Template is already available 6981 if(this._elTrTemplate) { 6982 return this._elTrTemplate; 6983 } 6984 // Template needs to be created 6985 else { 6986 var d = document, 6987 tr = d.createElement('tr'), 6988 td = d.createElement('td'), 6989 div = d.createElement('div'); 6990 6991 // Append the liner element 6992 td.appendChild(div); 6993 6994 // Create TD elements into DOCUMENT FRAGMENT 6995 var df = document.createDocumentFragment(), 6996 allKeys = this._oColumnSet.keys, 6997 elTd; 6998 6999 // Set state for each TD; 7000 var aAddClasses; 7001 for(var i=0, keysLen=allKeys.length; i<keysLen; i++) { 7002 // Clone the TD template 7003 elTd = td.cloneNode(true); 7004 7005 // Format the base TD 7006 elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1)); 7007 7008 df.appendChild(elTd); 7009 } 7010 tr.appendChild(df); 7011 tr.className = DT.CLASS_REC; 7012 this._elTrTemplate = tr; 7013 return tr; 7014 } 7015 }, 7016 7017 /** 7018 * Formats a basic TD element. 7019 * @method _formatTdEl 7020 * @param oColumn {YAHOO.widget.Column} Associated Column instance. 7021 * @param elTd {HTMLElement} An unformatted TD element. 7022 * @param index {Number} Column key index. 7023 * @param isLast {Boolean} True if Column is last key of the ColumnSet. 7024 * @return {HTMLElement} A formatted TD element. 7025 * @private 7026 */ 7027 _formatTdEl : function (oColumn, elTd, index, isLast) { 7028 var oColumnSet = this._oColumnSet; 7029 7030 // Set the TD's accessibility headers 7031 var allHeaders = oColumnSet.headers, 7032 allColHeaders = allHeaders[index], 7033 sTdHeaders = "", 7034 sHeader; 7035 for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) { 7036 sHeader = this._sId + "-th-" + allColHeaders[j] + ' '; 7037 sTdHeaders += sHeader; 7038 } 7039 elTd.headers = sTdHeaders; 7040 7041 // Class the TD element 7042 var aAddClasses = []; 7043 if(index === 0) { 7044 aAddClasses[aAddClasses.length] = DT.CLASS_FIRST; 7045 } 7046 if(isLast) { 7047 aAddClasses[aAddClasses.length] = DT.CLASS_LAST; 7048 } 7049 elTd.className = this._getColumnClassNames(oColumn, aAddClasses); 7050 7051 // Class the liner element 7052 elTd.firstChild.className = DT.CLASS_LINER; 7053 7054 // Set Column width for fallback cases 7055 if(oColumn.width && DT._bDynStylesFallback) { 7056 // Validate minWidth 7057 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ? 7058 oColumn.minWidth : oColumn.width; 7059 elTd.firstChild.style.overflow = 'hidden'; 7060 elTd.firstChild.style.width = nWidth + 'px'; 7061 } 7062 7063 return elTd; 7064 }, 7065 7066 7067 /** 7068 * Create a new TR element for a given Record and appends it with the correct 7069 * number of Column-state-classed TD elements. Striping is the responsibility of 7070 * the calling function, which may decide to stripe the single row, a subset of 7071 * rows, or all the rows. 7072 * @method _createTrEl 7073 * @param oRecord {YAHOO.widget.Record} Record instance 7074 * @return {HTMLElement} The new TR element. This must be added to the DOM. 7075 * @private 7076 */ 7077 _addTrEl : function (oRecord) { 7078 var elTrTemplate = this._getTrTemplateEl(); 7079 7080 // Clone the TR template. 7081 var elTr = elTrTemplate.cloneNode(true); 7082 7083 // Populate content 7084 return this._updateTrEl(elTr,oRecord); 7085 }, 7086 7087 /** 7088 * Formats the contents of the given TR's TD elements with data from the given 7089 * Record. Only innerHTML should change, nothing structural. 7090 * 7091 * @method _updateTrEl 7092 * @param elTr {HTMLElement} The TR element to update. 7093 * @param oRecord {YAHOO.widget.Record} The associated Record instance. 7094 * @return {HTMLElement} DOM reference to the new TR element. 7095 * @private 7096 */ 7097 _updateTrEl : function(elTr, oRecord) { 7098 var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true; 7099 if(ok) { 7100 // Hide the row to prevent constant reflows 7101 elTr.style.display = 'none'; 7102 7103 // Update TD elements with new data 7104 var allTds = elTr.childNodes, 7105 elTd; 7106 for(var i=0,len=allTds.length; i<len; ++i) { 7107 elTd = allTds[i]; 7108 7109 // Set the cell content 7110 this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]); 7111 } 7112 7113 // Redisplay the row for reflow 7114 elTr.style.display = ''; 7115 } 7116 7117 // Record-to-TR association and tracking of FIRST/LAST 7118 var oldId = elTr.id, 7119 newId = oRecord.getId(); 7120 if(this._sFirstTrId === oldId) { 7121 this._sFirstTrId = newId; 7122 } 7123 if(this._sLastTrId === oldId) { 7124 this._sLastTrId = newId; 7125 } 7126 elTr.id = newId; 7127 return elTr; 7128 }, 7129 7130 7131 /** 7132 * Deletes TR element by DOM reference or by DataTable page row index. 7133 * 7134 * @method _deleteTrEl 7135 * @param row {HTMLElement | Number} TR element reference or Datatable page row index. 7136 * @return {Boolean} Returns true if successful, else returns false. 7137 * @private 7138 */ 7139 _deleteTrEl : function(row) { 7140 var rowIndex; 7141 7142 // Get page row index for the element 7143 if(!lang.isNumber(row)) { 7144 rowIndex = Dom.get(row).sectionRowIndex; 7145 } 7146 else { 7147 rowIndex = row; 7148 } 7149 if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) { 7150 // Cannot use tbody.deleteRow due to IE6 instability 7151 //return this._elTbody.deleteRow(rowIndex); 7152 return this._elTbody.removeChild(this._elTbody.rows[row]); 7153 } 7154 else { 7155 return null; 7156 } 7157 }, 7158 7159 7160 7161 7162 7163 7164 7165 7166 7167 7168 7169 7170 7171 7172 7173 7174 7175 7176 7177 7178 7179 7180 7181 7182 7183 7184 7185 // CSS/STATE FUNCTIONS 7186 7187 7188 7189 7190 /** 7191 * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element 7192 * of the DataTable page and updates internal tracker. 7193 * 7194 * @method _unsetFirstRow 7195 * @private 7196 */ 7197 _unsetFirstRow : function() { 7198 // Remove FIRST 7199 if(this._sFirstTrId) { 7200 Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST); 7201 this._sFirstTrId = null; 7202 } 7203 }, 7204 7205 /** 7206 * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element 7207 * of the DataTable page and updates internal tracker. 7208 * 7209 * @method _setFirstRow 7210 * @private 7211 */ 7212 _setFirstRow : function() { 7213 this._unsetFirstRow(); 7214 var elTr = this.getFirstTrEl(); 7215 if(elTr) { 7216 // Set FIRST 7217 Dom.addClass(elTr, DT.CLASS_FIRST); 7218 this._sFirstTrId = elTr.id; 7219 } 7220 }, 7221 7222 /** 7223 * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element 7224 * of the DataTable page and updates internal tracker. 7225 * 7226 * @method _unsetLastRow 7227 * @private 7228 */ 7229 _unsetLastRow : function() { 7230 // Unassign previous class 7231 if(this._sLastTrId) { 7232 Dom.removeClass(this._sLastTrId, DT.CLASS_LAST); 7233 this._sLastTrId = null; 7234 } 7235 }, 7236 7237 /** 7238 * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element 7239 * of the DataTable page and updates internal tracker. 7240 * 7241 * @method _setLastRow 7242 * @private 7243 */ 7244 _setLastRow : function() { 7245 this._unsetLastRow(); 7246 var elTr = this.getLastTrEl(); 7247 if(elTr) { 7248 // Assign class 7249 Dom.addClass(elTr, DT.CLASS_LAST); 7250 this._sLastTrId = elTr.id; 7251 } 7252 }, 7253 7254 /** 7255 * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements. 7256 * 7257 * @method _setRowStripes 7258 * @param row {HTMLElement | String | Number} (optional) HTML TR element reference 7259 * or string ID, or page row index of where to start striping. 7260 * @param range {Number} (optional) If given, how many rows to stripe, otherwise 7261 * stripe all the rows until the end. 7262 * @private 7263 */ 7264 _setRowStripes : function(row, range) { 7265 // Default values stripe all rows 7266 var allRows = this._elTbody.rows, 7267 nStartIndex = 0, 7268 nEndIndex = allRows.length, 7269 aOdds = [], nOddIdx = 0, 7270 aEvens = [], nEvenIdx = 0; 7271 7272 // Stripe a subset 7273 if((row !== null) && (row !== undefined)) { 7274 // Validate given start row 7275 var elStartRow = this.getTrEl(row); 7276 if(elStartRow) { 7277 nStartIndex = elStartRow.sectionRowIndex; 7278 7279 // Validate given range 7280 if(lang.isNumber(range) && (range > 1)) { 7281 nEndIndex = nStartIndex + range; 7282 } 7283 } 7284 } 7285 7286 for(var i=nStartIndex; i<nEndIndex; i++) { 7287 if(i%2) { 7288 aOdds[nOddIdx++] = allRows[i]; 7289 } else { 7290 aEvens[nEvenIdx++] = allRows[i]; 7291 } 7292 } 7293 7294 if (aOdds.length) { 7295 Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD); 7296 } 7297 7298 if (aEvens.length) { 7299 Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN); 7300 } 7301 }, 7302 7303 /** 7304 * Assigns the class DT.CLASS_SELECTED to TR and TD elements. 7305 * 7306 * @method _setSelections 7307 * @private 7308 */ 7309 _setSelections : function() { 7310 // Keep track of selected rows 7311 var allSelectedRows = this.getSelectedRows(); 7312 // Keep track of selected cells 7313 var allSelectedCells = this.getSelectedCells(); 7314 // Anything to select? 7315 if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) { 7316 var oColumnSet = this._oColumnSet, 7317 el; 7318 // Loop over each row 7319 for(var i=0; i<allSelectedRows.length; i++) { 7320 el = Dom.get(allSelectedRows[i]); 7321 if(el) { 7322 Dom.addClass(el, DT.CLASS_SELECTED); 7323 } 7324 } 7325 // Loop over each cell 7326 for(i=0; i<allSelectedCells.length; i++) { 7327 el = Dom.get(allSelectedCells[i].recordId); 7328 if(el) { 7329 Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED); 7330 } 7331 } 7332 } 7333 }, 7334 7335 7336 7337 7338 7339 7340 7341 7342 7343 7344 7345 7346 7347 7348 7349 7350 7351 7352 7353 7354 7355 7356 7357 7358 7359 7360 7361 7362 7363 7364 7365 7366 7367 7368 7369 7370 7371 7372 7373 7374 7375 7376 7377 ///////////////////////////////////////////////////////////////////////////// 7378 // 7379 // Private DOM Event Handlers 7380 // 7381 ///////////////////////////////////////////////////////////////////////////// 7382 7383 /** 7384 * Validates minWidths whenever the render chain ends. 7385 * 7386 * @method _onRenderChainEnd 7387 * @private 7388 */ 7389 _onRenderChainEnd : function() { 7390 // Hide loading message 7391 this.hideTableMessage(); 7392 7393 // Show empty message 7394 if(this._elTbody.rows.length === 0) { 7395 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY); 7396 } 7397 7398 // Execute in timeout thread to give implementers a chance 7399 // to subscribe after the constructor 7400 var oSelf = this; 7401 setTimeout(function() { 7402 if((oSelf instanceof DT) && oSelf._sId) { 7403 // Init event 7404 if(oSelf._bInit) { 7405 oSelf._bInit = false; 7406 oSelf.fireEvent("initEvent"); 7407 } 7408 7409 // Render event 7410 oSelf.fireEvent("renderEvent"); 7411 // Backward compatibility 7412 oSelf.fireEvent("refreshEvent"); 7413 7414 // Post-render routine 7415 oSelf.validateColumnWidths(); 7416 7417 // Post-render event 7418 oSelf.fireEvent("postRenderEvent"); 7419 7420 /*if(YAHOO.example.Performance.trialStart) { 7421 YAHOO.example.Performance.trialStart = null; 7422 }*/ 7423 7424 } 7425 }, 0); 7426 }, 7427 7428 /** 7429 * Handles click events on the DOCUMENT. 7430 * 7431 * @method _onDocumentClick 7432 * @param e {HTMLEvent} The click event. 7433 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7434 * @private 7435 */ 7436 _onDocumentClick : function(e, oSelf) { 7437 var elTarget = Ev.getTarget(e); 7438 var elTag = elTarget.nodeName.toLowerCase(); 7439 7440 if(!Dom.isAncestor(oSelf._elContainer, elTarget)) { 7441 oSelf.fireEvent("tableBlurEvent"); 7442 7443 // Fires editorBlurEvent when click is not within the TABLE. 7444 // For cases when click is within the TABLE, due to timing issues, 7445 // the editorBlurEvent needs to get fired by the lower-level DOM click 7446 // handlers below rather than by the TABLE click handler directly. 7447 if(oSelf._oCellEditor) { 7448 if(oSelf._oCellEditor.getContainerEl) { 7449 var elContainer = oSelf._oCellEditor.getContainerEl(); 7450 // Only if the click was not within the CellEditor container 7451 if(!Dom.isAncestor(elContainer, elTarget) && 7452 (elContainer.id !== elTarget.id)) { 7453 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor}); 7454 } 7455 } 7456 // Backward Compatibility 7457 else if(oSelf._oCellEditor.isActive) { 7458 // Only if the click was not within the Cell Editor container 7459 if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) && 7460 (oSelf._oCellEditor.container.id !== elTarget.id)) { 7461 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor}); 7462 } 7463 } 7464 } 7465 } 7466 }, 7467 7468 /** 7469 * Handles focus events on the DataTable instance. 7470 * 7471 * @method _onTableFocus 7472 * @param e {HTMLEvent} The focus event. 7473 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7474 * @private 7475 */ 7476 _onTableFocus : function(e, oSelf) { 7477 oSelf.fireEvent("tableFocusEvent"); 7478 }, 7479 7480 /** 7481 * Handles focus events on the THEAD element. 7482 * 7483 * @method _onTheadFocus 7484 * @param e {HTMLEvent} The focus event. 7485 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7486 * @private 7487 */ 7488 _onTheadFocus : function(e, oSelf) { 7489 oSelf.fireEvent("theadFocusEvent"); 7490 oSelf.fireEvent("tableFocusEvent"); 7491 }, 7492 7493 /** 7494 * Handles focus events on the TBODY element. 7495 * 7496 * @method _onTbodyFocus 7497 * @param e {HTMLEvent} The focus event. 7498 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7499 * @private 7500 */ 7501 _onTbodyFocus : function(e, oSelf) { 7502 oSelf.fireEvent("tbodyFocusEvent"); 7503 oSelf.fireEvent("tableFocusEvent"); 7504 }, 7505 7506 /** 7507 * Handles mouseover events on the DataTable instance. 7508 * 7509 * @method _onTableMouseover 7510 * @param e {HTMLEvent} The mouseover event. 7511 * @param origTarget {HTMLElement} The mouseenter delegated element. 7512 * @param container {HTMLElement} The mouseenter delegation container. 7513 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7514 * @private 7515 */ 7516 _onTableMouseover : function(e, origTarget, container, oSelf) { 7517 var elTarget = origTarget; 7518 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7519 var bKeepBubbling = true; 7520 while(elTarget && (elTag != "table")) { 7521 switch(elTag) { 7522 case "body": 7523 return; 7524 case "a": 7525 break; 7526 case "td": 7527 bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e}); 7528 break; 7529 case "span": 7530 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) { 7531 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e}); 7532 // Backward compatibility 7533 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e}); 7534 } 7535 break; 7536 case "th": 7537 bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e}); 7538 // Backward compatibility 7539 bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e}); 7540 break; 7541 case "tr": 7542 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") { 7543 bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e}); 7544 // Backward compatibility 7545 bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e}); 7546 } 7547 else { 7548 bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e}); 7549 } 7550 break; 7551 default: 7552 break; 7553 } 7554 if(bKeepBubbling === false) { 7555 return; 7556 } 7557 else { 7558 elTarget = elTarget.parentNode; 7559 if(elTarget) { 7560 elTag = elTarget.nodeName.toLowerCase(); 7561 } 7562 } 7563 } 7564 oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7565 }, 7566 7567 /** 7568 * Handles mouseout events on the DataTable instance. 7569 * 7570 * @method _onTableMouseout 7571 * @param e {HTMLEvent} The mouseout event. 7572 * @param origTarget {HTMLElement} The mouseleave delegated element. 7573 * @param container {HTMLElement} The mouseleave delegation container. 7574 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7575 * @private 7576 */ 7577 _onTableMouseout : function(e, origTarget, container, oSelf) { 7578 var elTarget = origTarget; 7579 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7580 var bKeepBubbling = true; 7581 while(elTarget && (elTag != "table")) { 7582 switch(elTag) { 7583 case "body": 7584 return; 7585 case "a": 7586 break; 7587 case "td": 7588 bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e}); 7589 break; 7590 case "span": 7591 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) { 7592 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e}); 7593 // Backward compatibility 7594 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e}); 7595 } 7596 break; 7597 case "th": 7598 bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e}); 7599 // Backward compatibility 7600 bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e}); 7601 break; 7602 case "tr": 7603 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") { 7604 bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e}); 7605 // Backward compatibility 7606 bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e}); 7607 } 7608 else { 7609 bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e}); 7610 } 7611 break; 7612 default: 7613 break; 7614 } 7615 if(bKeepBubbling === false) { 7616 return; 7617 } 7618 else { 7619 elTarget = elTarget.parentNode; 7620 if(elTarget) { 7621 elTag = elTarget.nodeName.toLowerCase(); 7622 } 7623 } 7624 } 7625 oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7626 }, 7627 7628 /** 7629 * Handles mousedown events on the DataTable instance. 7630 * 7631 * @method _onTableMousedown 7632 * @param e {HTMLEvent} The mousedown event. 7633 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7634 * @private 7635 */ 7636 _onTableMousedown : function(e, oSelf) { 7637 var elTarget = Ev.getTarget(e); 7638 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7639 var bKeepBubbling = true; 7640 while(elTarget && (elTag != "table")) { 7641 switch(elTag) { 7642 case "body": 7643 return; 7644 case "a": 7645 break; 7646 case "td": 7647 bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e}); 7648 break; 7649 case "span": 7650 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) { 7651 bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e}); 7652 // Backward compatibility 7653 bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e}); 7654 } 7655 break; 7656 case "th": 7657 bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e}); 7658 // Backward compatibility 7659 bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e}); 7660 break; 7661 case "tr": 7662 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") { 7663 bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e}); 7664 // Backward compatibility 7665 bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e}); 7666 } 7667 else { 7668 bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e}); 7669 } 7670 break; 7671 default: 7672 break; 7673 } 7674 if(bKeepBubbling === false) { 7675 return; 7676 } 7677 else { 7678 elTarget = elTarget.parentNode; 7679 if(elTarget) { 7680 elTag = elTarget.nodeName.toLowerCase(); 7681 } 7682 } 7683 } 7684 oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7685 }, 7686 7687 /** 7688 * Handles mouseup events on the DataTable instance. 7689 * 7690 * @method _onTableMouseup 7691 * @param e {HTMLEvent} The mouseup event. 7692 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7693 * @private 7694 */ 7695 _onTableMouseup : function(e, oSelf) { 7696 var elTarget = Ev.getTarget(e); 7697 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7698 var bKeepBubbling = true; 7699 while(elTarget && (elTag != "table")) { 7700 switch(elTag) { 7701 case "body": 7702 return; 7703 case "a": 7704 break; 7705 case "td": 7706 bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e}); 7707 break; 7708 case "span": 7709 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) { 7710 bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e}); 7711 // Backward compatibility 7712 bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e}); 7713 } 7714 break; 7715 case "th": 7716 bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e}); 7717 // Backward compatibility 7718 bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e}); 7719 break; 7720 case "tr": 7721 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") { 7722 bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e}); 7723 // Backward compatibility 7724 bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e}); 7725 } 7726 else { 7727 bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e}); 7728 } 7729 break; 7730 default: 7731 break; 7732 } 7733 if(bKeepBubbling === false) { 7734 return; 7735 } 7736 else { 7737 elTarget = elTarget.parentNode; 7738 if(elTarget) { 7739 elTag = elTarget.nodeName.toLowerCase(); 7740 } 7741 } 7742 } 7743 oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7744 }, 7745 7746 /** 7747 * Handles dblclick events on the DataTable instance. 7748 * 7749 * @method _onTableDblclick 7750 * @param e {HTMLEvent} The dblclick event. 7751 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7752 * @private 7753 */ 7754 _onTableDblclick : function(e, oSelf) { 7755 var elTarget = Ev.getTarget(e); 7756 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7757 var bKeepBubbling = true; 7758 while(elTarget && (elTag != "table")) { 7759 switch(elTag) { 7760 case "body": 7761 return; 7762 case "td": 7763 bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e}); 7764 break; 7765 case "span": 7766 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) { 7767 bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e}); 7768 // Backward compatibility 7769 bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e}); 7770 } 7771 break; 7772 case "th": 7773 bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e}); 7774 // Backward compatibility 7775 bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e}); 7776 break; 7777 case "tr": 7778 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") { 7779 bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e}); 7780 // Backward compatibility 7781 bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e}); 7782 } 7783 else { 7784 bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e}); 7785 } 7786 break; 7787 default: 7788 break; 7789 } 7790 if(bKeepBubbling === false) { 7791 return; 7792 } 7793 else { 7794 elTarget = elTarget.parentNode; 7795 if(elTarget) { 7796 elTag = elTarget.nodeName.toLowerCase(); 7797 } 7798 } 7799 } 7800 oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7801 }, 7802 /** 7803 * Handles keydown events on the THEAD element. 7804 * 7805 * @method _onTheadKeydown 7806 * @param e {HTMLEvent} The key event. 7807 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7808 * @private 7809 */ 7810 _onTheadKeydown : function(e, oSelf) { 7811 var elTarget = Ev.getTarget(e); 7812 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7813 var bKeepBubbling = true; 7814 while(elTarget && (elTag != "table")) { 7815 switch(elTag) { 7816 case "body": 7817 return; 7818 case "input": 7819 case "textarea": 7820 // TODO: implement textareaKeyEvent 7821 break; 7822 case "thead": 7823 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e}); 7824 break; 7825 default: 7826 break; 7827 } 7828 if(bKeepBubbling === false) { 7829 return; 7830 } 7831 else { 7832 elTarget = elTarget.parentNode; 7833 if(elTarget) { 7834 elTag = elTarget.nodeName.toLowerCase(); 7835 } 7836 } 7837 } 7838 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7839 }, 7840 7841 /** 7842 * Handles keydown events on the TBODY element. Handles selection behavior, 7843 * provides hooks for ENTER to edit functionality. 7844 * 7845 * @method _onTbodyKeydown 7846 * @param e {HTMLEvent} The key event. 7847 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7848 * @private 7849 */ 7850 _onTbodyKeydown : function(e, oSelf) { 7851 var sMode = oSelf.get("selectionMode"); 7852 7853 if(sMode == "standard") { 7854 oSelf._handleStandardSelectionByKey(e); 7855 } 7856 else if(sMode == "single") { 7857 oSelf._handleSingleSelectionByKey(e); 7858 } 7859 else if(sMode == "cellblock") { 7860 oSelf._handleCellBlockSelectionByKey(e); 7861 } 7862 else if(sMode == "cellrange") { 7863 oSelf._handleCellRangeSelectionByKey(e); 7864 } 7865 else if(sMode == "singlecell") { 7866 oSelf._handleSingleCellSelectionByKey(e); 7867 } 7868 7869 if(oSelf._oCellEditor) { 7870 if(oSelf._oCellEditor.fireEvent) { 7871 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor}); 7872 } 7873 else if(oSelf._oCellEditor.isActive) { 7874 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor}); 7875 } 7876 } 7877 7878 var elTarget = Ev.getTarget(e); 7879 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(); 7880 var bKeepBubbling = true; 7881 while(elTarget && (elTag != "table")) { 7882 switch(elTag) { 7883 case "body": 7884 return; 7885 case "tbody": 7886 bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e}); 7887 break; 7888 default: 7889 break; 7890 } 7891 if(bKeepBubbling === false) { 7892 return; 7893 } 7894 else { 7895 elTarget = elTarget.parentNode; 7896 if(elTarget) { 7897 elTag = elTarget.nodeName.toLowerCase(); 7898 } 7899 } 7900 } 7901 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7902 }, 7903 7904 /** 7905 * Handles click events on the THEAD element. 7906 * 7907 * @method _onTheadClick 7908 * @param e {HTMLEvent} The click event. 7909 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 7910 * @private 7911 */ 7912 _onTheadClick : function(e, oSelf) { 7913 // This blurs the CellEditor 7914 if(oSelf._oCellEditor) { 7915 if(oSelf._oCellEditor.fireEvent) { 7916 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor}); 7917 } 7918 // Backward compatibility 7919 else if(oSelf._oCellEditor.isActive) { 7920 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor}); 7921 } 7922 } 7923 7924 var elTarget = Ev.getTarget(e), 7925 elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(), 7926 bKeepBubbling = true; 7927 while(elTarget && (elTag != "table")) { 7928 switch(elTag) { 7929 case "body": 7930 return; 7931 case "input": 7932 var sType = elTarget.type.toLowerCase(); 7933 if(sType == "checkbox") { 7934 bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e}); 7935 } 7936 else if(sType == "radio") { 7937 bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e}); 7938 } 7939 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) { 7940 if(!elTarget.disabled) { 7941 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e}); 7942 } 7943 else { 7944 bKeepBubbling = false; 7945 } 7946 } 7947 else if (elTarget.disabled){ 7948 bKeepBubbling = false; 7949 } 7950 break; 7951 case "a": 7952 bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e}); 7953 break; 7954 case "button": 7955 if(!elTarget.disabled) { 7956 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e}); 7957 } 7958 else { 7959 bKeepBubbling = false; 7960 } 7961 break; 7962 case "span": 7963 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) { 7964 bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e}); 7965 // Backward compatibility 7966 bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e}); 7967 } 7968 break; 7969 case "th": 7970 bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e}); 7971 // Backward compatibility 7972 bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e}); 7973 break; 7974 case "tr": 7975 bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e}); 7976 // Backward compatibility 7977 bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e}); 7978 break; 7979 default: 7980 break; 7981 } 7982 if(bKeepBubbling === false) { 7983 return; 7984 } 7985 else { 7986 elTarget = elTarget.parentNode; 7987 if(elTarget) { 7988 elTag = elTarget.nodeName.toLowerCase(); 7989 } 7990 } 7991 } 7992 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e}); 7993 }, 7994 7995 /** 7996 * Handles click events on the primary TBODY element. 7997 * 7998 * @method _onTbodyClick 7999 * @param e {HTMLEvent} The click event. 8000 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 8001 * @private 8002 */ 8003 _onTbodyClick : function(e, oSelf) { 8004 // This blurs the CellEditor 8005 if(oSelf._oCellEditor) { 8006 if(oSelf._oCellEditor.fireEvent) { 8007 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor}); 8008 } 8009 else if(oSelf._oCellEditor.isActive) { 8010 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor}); 8011 } 8012 } 8013 8014 // Fire Custom Events 8015 var elTarget = Ev.getTarget(e), 8016 elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(), 8017 bKeepBubbling = true; 8018 while(elTarget && (elTag != "table")) { 8019 switch(elTag) { 8020 case "body": 8021 return; 8022 case "input": 8023 var sType = elTarget.type.toLowerCase(); 8024 if(sType == "checkbox") { 8025 bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e}); 8026 } 8027 else if(sType == "radio") { 8028 bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e}); 8029 } 8030 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) { 8031 if(!elTarget.disabled) { 8032 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e}); 8033 } 8034 else { 8035 bKeepBubbling = false; 8036 } 8037 } 8038 else if (elTarget.disabled){ 8039 bKeepBubbling = false; 8040 } 8041 break; 8042 case "a": 8043 bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e}); 8044 break; 8045 case "button": 8046 if(!elTarget.disabled) { 8047 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e}); 8048 } 8049 else { 8050 bKeepBubbling = false; 8051 } 8052 break; 8053 case "td": 8054 bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e}); 8055 break; 8056 case "tr": 8057 bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e}); 8058 break; 8059 default: 8060 break; 8061 } 8062 if(bKeepBubbling === false) { 8063 return; 8064 } 8065 else { 8066 elTarget = elTarget.parentNode; 8067 if(elTarget) { 8068 elTag = elTarget.nodeName.toLowerCase(); 8069 } 8070 } 8071 } 8072 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e}); 8073 }, 8074 8075 /** 8076 * Handles change events on SELECT elements within DataTable. 8077 * 8078 * @method _onDropdownChange 8079 * @param e {HTMLEvent} The change event. 8080 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance. 8081 * @private 8082 */ 8083 _onDropdownChange : function(e, oSelf) { 8084 var elTarget = Ev.getTarget(e); 8085 oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget}); 8086 }, 8087 8088 8089 8090 8091 8092 8093 8094 8095 8096 8097 8098 8099 8100 8101 8102 8103 8104 8105 8106 8107 8108 8109 8110 8111 8112 8113 8114 8115 8116 8117 8118 8119 ///////////////////////////////////////////////////////////////////////////// 8120 // 8121 // Public member variables 8122 // 8123 ///////////////////////////////////////////////////////////////////////////// 8124 /** 8125 * Returns object literal of initial configs. 8126 * 8127 * @property configs 8128 * @type Object 8129 * @default {} 8130 */ 8131 configs: null, 8132 8133 8134 ///////////////////////////////////////////////////////////////////////////// 8135 // 8136 // Public methods 8137 // 8138 ///////////////////////////////////////////////////////////////////////////// 8139 8140 /** 8141 * Returns unique id assigned to instance, which is a useful prefix for 8142 * generating unique DOM ID strings. 8143 * 8144 * @method getId 8145 * @return {String} Unique ID of the DataSource instance. 8146 */ 8147 getId : function() { 8148 return this._sId; 8149 }, 8150 8151 /** 8152 * DataSource instance name, for logging. 8153 * 8154 * @method toString 8155 * @return {String} Unique name of the DataSource instance. 8156 */ 8157 8158 toString : function() { 8159 return "DataTable instance " + this._sId; 8160 }, 8161 8162 /** 8163 * Returns the DataTable instance's DataSource instance. 8164 * 8165 * @method getDataSource 8166 * @return {YAHOO.util.DataSource} DataSource instance. 8167 */ 8168 getDataSource : function() { 8169 return this._oDataSource; 8170 }, 8171 8172 /** 8173 * Returns the DataTable instance's ColumnSet instance. 8174 * 8175 * @method getColumnSet 8176 * @return {YAHOO.widget.ColumnSet} ColumnSet instance. 8177 */ 8178 getColumnSet : function() { 8179 return this._oColumnSet; 8180 }, 8181 8182 /** 8183 * Returns the DataTable instance's RecordSet instance. 8184 * 8185 * @method getRecordSet 8186 * @return {YAHOO.widget.RecordSet} RecordSet instance. 8187 */ 8188 getRecordSet : function() { 8189 return this._oRecordSet; 8190 }, 8191 8192 /** 8193 * Returns on object literal representing the DataTable instance's current 8194 * state with the following properties: 8195 * <dl> 8196 * <dt>pagination</dt> 8197 * <dd>Instance of YAHOO.widget.Paginator</dd> 8198 * 8199 * <dt>sortedBy</dt> 8200 * <dd> 8201 * <dl> 8202 * <dt>sortedBy.key</dt> 8203 * <dd>{String} Key of sorted Column</dd> 8204 * <dt>sortedBy.dir</dt> 8205 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd> 8206 * </dl> 8207 * </dd> 8208 * 8209 * <dt>selectedRows</dt> 8210 * <dd>Array of selected rows by Record ID.</dd> 8211 * 8212 * <dt>selectedCells</dt> 8213 * <dd>Selected cells as an array of object literals: 8214 * {recordId:sRecordId, columnKey:sColumnKey}</dd> 8215 * </dl> 8216 * 8217 * @method getState 8218 * @return {Object} DataTable instance state object literal values. 8219 */ 8220 getState : function() { 8221 return { 8222 totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(), 8223 pagination: this.get("paginator") ? this.get("paginator").getState() : null, 8224 sortedBy: this.get("sortedBy"), 8225 selectedRows: this.getSelectedRows(), 8226 selectedCells: this.getSelectedCells() 8227 }; 8228 }, 8229 8230 8231 8232 8233 8234 8235 8236 8237 8238 8239 8240 8241 8242 8243 8244 8245 8246 8247 8248 8249 8250 8251 8252 8253 8254 8255 8256 8257 8258 8259 8260 8261 8262 8263 8264 8265 8266 8267 8268 8269 8270 8271 8272 // DOM ACCESSORS 8273 8274 /** 8275 * Returns DOM reference to the DataTable's container element. 8276 * 8277 * @method getContainerEl 8278 * @return {HTMLElement} Reference to DIV element. 8279 */ 8280 getContainerEl : function() { 8281 return this._elContainer; 8282 }, 8283 8284 /** 8285 * Returns DOM reference to the DataTable's TABLE element. 8286 * 8287 * @method getTableEl 8288 * @return {HTMLElement} Reference to TABLE element. 8289 */ 8290 getTableEl : function() { 8291 return this._elTable; 8292 }, 8293 8294 /** 8295 * Returns DOM reference to the DataTable's THEAD element. 8296 * 8297 * @method getTheadEl 8298 * @return {HTMLElement} Reference to THEAD element. 8299 */ 8300 getTheadEl : function() { 8301 return this._elThead; 8302 }, 8303 8304 /** 8305 * Returns DOM reference to the DataTable's primary TBODY element. 8306 * 8307 * @method getTbodyEl 8308 * @return {HTMLElement} Reference to TBODY element. 8309 */ 8310 getTbodyEl : function() { 8311 return this._elTbody; 8312 }, 8313 8314 /** 8315 * Returns DOM reference to the DataTable's secondary TBODY element that is 8316 * used to display messages. 8317 * 8318 * @method getMsgTbodyEl 8319 * @return {HTMLElement} Reference to TBODY element. 8320 */ 8321 getMsgTbodyEl : function() { 8322 return this._elMsgTbody; 8323 }, 8324 8325 /** 8326 * Returns DOM reference to the TD element within the secondary TBODY that is 8327 * used to display messages. 8328 * 8329 * @method getMsgTdEl 8330 * @return {HTMLElement} Reference to TD element. 8331 */ 8332 getMsgTdEl : function() { 8333 return this._elMsgTd; 8334 }, 8335 8336 /** 8337 * Returns the corresponding TR reference for a given DOM element, ID string or 8338 * page row index. If the given identifier is a child of a TR element, 8339 * then DOM tree is traversed until a parent TR element is returned, otherwise 8340 * null. Returns null if the row is not considered a primary row (i.e., row 8341 * extensions). 8342 * 8343 * @method getTrEl 8344 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to 8345 * get: by element reference, ID string, page row index, or Record. 8346 * @return {HTMLElement} Reference to TR element, or null. 8347 */ 8348 getTrEl : function(row) { 8349 // By Record 8350 if(row instanceof YAHOO.widget.Record) { 8351 return document.getElementById(row.getId()); 8352 } 8353 // By page row index 8354 else if(lang.isNumber(row)) { 8355 var dataRows = Dom.getElementsByClassName(DT.CLASS_REC, "tr", this._elTbody); 8356 return dataRows && dataRows[row] ? dataRows[row] : null; 8357 } 8358 // By ID string or element reference 8359 else if(row) { 8360 var elRow = (lang.isString(row)) ? document.getElementById(row) : row; 8361 8362 // Validate HTML element 8363 if(elRow && elRow.ownerDocument == document) { 8364 // Validate TR element 8365 if(elRow.nodeName.toLowerCase() != "tr") { 8366 // Traverse up the DOM to find the corresponding TR element 8367 elRow = Dom.getAncestorByTagName(elRow,"tr"); 8368 } 8369 8370 return elRow; 8371 } 8372 } 8373 8374 return null; 8375 }, 8376 8377 /** 8378 * Returns DOM reference to the first primary TR element in the DataTable page, or null. 8379 * 8380 * @method getFirstTrEl 8381 * @return {HTMLElement} Reference to TR element. 8382 */ 8383 getFirstTrEl : function() { 8384 var allRows = this._elTbody.rows, 8385 i=0; 8386 while(allRows[i]) { 8387 if(this.getRecord(allRows[i])) { 8388 return allRows[i]; 8389 } 8390 i++; 8391 } 8392 return null; 8393 8394 }, 8395 8396 /** 8397 * Returns DOM reference to the last primary TR element in the DataTable page, or null. 8398 * 8399 * @method getLastTrEl 8400 * @return {HTMLElement} Reference to last TR element. 8401 */ 8402 getLastTrEl : function() { 8403 var allRows = this._elTbody.rows, 8404 i=allRows.length-1; 8405 while(i>-1) { 8406 if(this.getRecord(allRows[i])) { 8407 return allRows[i]; 8408 } 8409 i--; 8410 } 8411 return null; 8412 }, 8413 8414 /** 8415 * Returns DOM reference to the next TR element from the given primary TR element, or null. 8416 * 8417 * @method getNextTrEl 8418 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element 8419 * reference, ID string, page row index, or Record from which to get next TR element. 8420 * @param forcePrimary {Boolean} (optional) If true, will only return TR elements 8421 * that correspond to Records. Non-primary rows (such as row expansions) 8422 * will be skipped. 8423 * @return {HTMLElement} Reference to next TR element. 8424 */ 8425 getNextTrEl : function(row, forcePrimary) { 8426 var nThisTrIndex = this.getTrIndex(row); 8427 if(nThisTrIndex !== null) { 8428 var allRows = this._elTbody.rows; 8429 if(forcePrimary) { 8430 while(nThisTrIndex < allRows.length-1) { 8431 row = allRows[nThisTrIndex+1]; 8432 if(this.getRecord(row)) { 8433 return row; 8434 } 8435 nThisTrIndex++; 8436 } 8437 } 8438 else { 8439 if(nThisTrIndex < allRows.length-1) { 8440 return allRows[nThisTrIndex+1]; 8441 } 8442 } 8443 } 8444 8445 return null; 8446 }, 8447 8448 /** 8449 * Returns DOM reference to the previous TR element from the given primary TR element, or null. 8450 * 8451 * @method getPreviousTrEl 8452 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element 8453 * reference, ID string, page row index, or Record from which to get previous TR element. 8454 * @param forcePrimary {Boolean} (optional) If true, will only return TR elements 8455 * from rothat correspond to Records. Non-primary rows (such as row expansions) 8456 * will be skipped. 8457 * @return {HTMLElement} Reference to previous TR element. 8458 */ 8459 getPreviousTrEl : function(row, forcePrimary) { 8460 var nThisTrIndex = this.getTrIndex(row); 8461 if(nThisTrIndex !== null) { 8462 var allRows = this._elTbody.rows; 8463 8464 if(forcePrimary) { 8465 while(nThisTrIndex > 0) { 8466 row = allRows[nThisTrIndex-1]; 8467 if(this.getRecord(row)) { 8468 return row; 8469 } 8470 nThisTrIndex--; 8471 } 8472 } 8473 else { 8474 if(nThisTrIndex > 0) { 8475 return allRows[nThisTrIndex-1]; 8476 } 8477 } 8478 } 8479 8480 return null; 8481 }, 8482 8483 8484 /** 8485 * Workaround for IE bug where hidden or not-in-dom elements cause cellIndex 8486 * value to be incorrect. 8487 * 8488 * @method getCellIndex 8489 * @param cell {HTMLElement | Object} TD element or child of a TD element, or 8490 * object literal of syntax {record:oRecord, column:oColumn}. 8491 * @return {Number} TD.cellIndex value. 8492 */ 8493 getCellIndex : function(cell) { 8494 cell = this.getTdEl(cell); 8495 if(cell) { 8496 if(ua.ie > 0) { 8497 var i=0, 8498 tr = cell.parentNode, 8499 allCells = tr.childNodes, 8500 len = allCells.length; 8501 for(; i<len; i++) { 8502 if(allCells[i] == cell) { 8503 return i; 8504 } 8505 } 8506 } 8507 else { 8508 return cell.cellIndex; 8509 } 8510 } 8511 }, 8512 8513 /** 8514 * Returns DOM reference to a TD liner element. 8515 * 8516 * @method getTdLinerEl 8517 * @param cell {HTMLElement | Object} TD element or child of a TD element, or 8518 * object literal of syntax {record:oRecord, column:oColumn}. 8519 * @return {HTMLElement} Reference to TD liner element. 8520 */ 8521 getTdLinerEl : function(cell) { 8522 var elCell = this.getTdEl(cell); 8523 return elCell.firstChild || null; 8524 }, 8525 8526 /** 8527 * Returns DOM reference to a TD element. Returns null if the row is not 8528 * considered a primary row (i.e., row extensions). 8529 * 8530 * @method getTdEl 8531 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or 8532 * object literal of syntax {record:oRecord, column:oColumn}. 8533 * @return {HTMLElement} Reference to TD element. 8534 */ 8535 getTdEl : function(cell) { 8536 var elCell; 8537 var el = Dom.get(cell); 8538 8539 // Validate HTML element 8540 if(el && (el.ownerDocument == document)) { 8541 // Validate TD element 8542 if(el.nodeName.toLowerCase() != "td") { 8543 // Traverse up the DOM to find the corresponding TR element 8544 elCell = Dom.getAncestorByTagName(el, "td"); 8545 } 8546 else { 8547 elCell = el; 8548 } 8549 8550 // Make sure the TD is in this TBODY or is not in DOM 8551 // Bug 2527707 and bug 2263558 8552 if(elCell && ((elCell.parentNode.parentNode == this._elTbody) || 8553 (elCell.parentNode.parentNode === null) || 8554 (elCell.parentNode.parentNode.nodeType === 11))) { 8555 // Now we can return the TD element 8556 return elCell; 8557 } 8558 } 8559 else if(cell) { 8560 var oRecord, nColKeyIndex; 8561 8562 if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) { 8563 oRecord = this.getRecord(cell.recordId); 8564 var oColumn = this.getColumn(cell.columnKey); 8565 if(oColumn) { 8566 nColKeyIndex = oColumn.getKeyIndex(); 8567 } 8568 8569 } 8570 if(cell.record && cell.column && cell.column.getKeyIndex) { 8571 oRecord = cell.record; 8572 nColKeyIndex = cell.column.getKeyIndex(); 8573 } 8574 var elRow = this.getTrEl(oRecord); 8575 if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) { 8576 return elRow.cells[nColKeyIndex] || null; 8577 } 8578 } 8579 8580 return null; 8581 }, 8582 8583 /** 8584 * Returns DOM reference to the first primary TD element in the DataTable page (by default), 8585 * the first TD element of the optionally given row, or null. 8586 * 8587 * @method getFirstTdEl 8588 * @param row {HTMLElement} (optional) row from which to get first TD 8589 * @return {HTMLElement} Reference to TD element. 8590 */ 8591 getFirstTdEl : function(row) { 8592 var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getFirstTrEl(); 8593 if(elRow) { 8594 if(elRow.cells && elRow.cells.length > 0) { 8595 return elRow.cells[0]; 8596 } 8597 else if(elRow.childNodes && elRow.childNodes.length > 0) { 8598 return elRow.childNodes[0]; 8599 } 8600 } 8601 return null; 8602 }, 8603 8604 /** 8605 * Returns DOM reference to the last primary TD element in the DataTable page (by default), 8606 * the first TD element of the optionally given row, or null. 8607 * 8608 * @method getLastTdEl 8609 * @param row {HTMLElement} (optional) row from which to get first TD 8610 * @return {HTMLElement} Reference to last TD element. 8611 */ 8612 getLastTdEl : function(row) { 8613 var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getLastTrEl(); 8614 if(elRow) { 8615 if(elRow.cells && elRow.cells.length > 0) { 8616 return elRow.cells[elRow.cells.length-1]; 8617 } 8618 else if(elRow.childNodes && elRow.childNodes.length > 0) { 8619 return elRow.childNodes[elRow.childNodes.length-1]; 8620 } 8621 } 8622 return null; 8623 }, 8624 8625 /** 8626 * Returns DOM reference to the next TD element from the given cell, or null. 8627 * 8628 * @method getNextTdEl 8629 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or 8630 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element. 8631 * @return {HTMLElement} Reference to next TD element, or null. 8632 */ 8633 getNextTdEl : function(cell) { 8634 var elCell = this.getTdEl(cell); 8635 if(elCell) { 8636 var nThisTdIndex = this.getCellIndex(elCell); 8637 var elRow = this.getTrEl(elCell); 8638 if(elRow.cells && (elRow.cells.length) > 0 && (nThisTdIndex < elRow.cells.length-1)) { 8639 return elRow.cells[nThisTdIndex+1]; 8640 } 8641 else if(elRow.childNodes && (elRow.childNodes.length) > 0 && (nThisTdIndex < elRow.childNodes.length-1)) { 8642 return elRow.childNodes[nThisTdIndex+1]; 8643 } 8644 else { 8645 var elNextRow = this.getNextTrEl(elRow); 8646 if(elNextRow) { 8647 return elNextRow.cells[0]; 8648 } 8649 } 8650 } 8651 return null; 8652 }, 8653 8654 /** 8655 * Returns DOM reference to the previous TD element from the given cell, or null. 8656 * 8657 * @method getPreviousTdEl 8658 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or 8659 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element. 8660 * @return {HTMLElement} Reference to previous TD element, or null. 8661 */ 8662 getPreviousTdEl : function(cell) { 8663 var elCell = this.getTdEl(cell); 8664 if(elCell) { 8665 var nThisTdIndex = this.getCellIndex(elCell); 8666 var elRow = this.getTrEl(elCell); 8667 if(nThisTdIndex > 0) { 8668 if(elRow.cells && elRow.cells.length > 0) { 8669 return elRow.cells[nThisTdIndex-1]; 8670 } 8671 else if(elRow.childNodes && elRow.childNodes.length > 0) { 8672 return elRow.childNodes[nThisTdIndex-1]; 8673 } 8674 } 8675 else { 8676 var elPreviousRow = this.getPreviousTrEl(elRow); 8677 if(elPreviousRow) { 8678 return this.getLastTdEl(elPreviousRow); 8679 } 8680 } 8681 } 8682 return null; 8683 }, 8684 8685 /** 8686 * Returns DOM reference to the above TD element from the given cell, or null. 8687 * 8688 * @method getAboveTdEl 8689 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or 8690 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element. 8691 * @param forcePrimary {Boolean} (optional) If true, will only return TD elements 8692 * from rows that correspond to Records. Non-primary rows (such as row expansions) 8693 * will be skipped. 8694 * @return {HTMLElement} Reference to above TD element, or null. 8695 */ 8696 getAboveTdEl : function(cell, forcePrimary) { 8697 var elCell = this.getTdEl(cell); 8698 if(elCell) { 8699 var elPreviousRow = this.getPreviousTrEl(elCell, forcePrimary); 8700 if(elPreviousRow ) { 8701 var cellIndex = this.getCellIndex(elCell); 8702 if(elPreviousRow.cells && elPreviousRow.cells.length > 0) { 8703 return elPreviousRow.cells[cellIndex] ? elPreviousRow.cells[cellIndex] : null; 8704 } 8705 else if(elPreviousRow.childNodes && elPreviousRow.childNodes.length > 0) { 8706 return elPreviousRow.childNodes[cellIndex] ? elPreviousRow.childNodes[cellIndex] : null; 8707 } 8708 } 8709 } 8710 return null; 8711 }, 8712 8713 /** 8714 * Returns DOM reference to the below TD element from the given cell, or null. 8715 * 8716 * @method getBelowTdEl 8717 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or 8718 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element. 8719 * @param forcePrimary {Boolean} (optional) If true, will only return TD elements 8720 * from rows that correspond to Records. Non-primary rows (such as row expansions) 8721 * will be skipped. 8722 * @return {HTMLElement} Reference to below TD element, or null. 8723 */ 8724 getBelowTdEl : function(cell, forcePrimary) { 8725 var elCell = this.getTdEl(cell); 8726 if(elCell) { 8727 var elNextRow = this.getNextTrEl(elCell, forcePrimary); 8728 if(elNextRow) { 8729 var cellIndex = this.getCellIndex(elCell); 8730 if(elNextRow.cells && elNextRow.cells.length > 0) { 8731 return elNextRow.cells[cellIndex] ? elNextRow.cells[cellIndex] : null; 8732 } 8733 else if(elNextRow.childNodes && elNextRow.childNodes.length > 0) { 8734 return elNextRow.childNodes[cellIndex] ? elNextRow.childNodes[cellIndex] : null; 8735 } 8736 } 8737 } 8738 return null; 8739 }, 8740 8741 /** 8742 * Returns DOM reference to a TH liner element. Needed to normalize for resizeable 8743 * Columns, which have an additional resizer liner DIV element between the TH 8744 * element and the liner DIV element. 8745 * 8746 * @method getThLinerEl 8747 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance, 8748 * DOM element reference, or string ID. 8749 * @return {HTMLElement} Reference to TH liner element. 8750 */ 8751 getThLinerEl : function(theadCell) { 8752 var oColumn = this.getColumn(theadCell); 8753 return (oColumn) ? oColumn.getThLinerEl() : null; 8754 }, 8755 8756 /** 8757 * Returns DOM reference to a TH element. 8758 * 8759 * @method getThEl 8760 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance, 8761 * DOM element reference, or string ID. 8762 * @return {HTMLElement} Reference to TH element. 8763 */ 8764 getThEl : function(theadCell) { 8765 var elTh; 8766 8767 // Validate Column instance 8768 if(theadCell instanceof YAHOO.widget.Column) { 8769 var oColumn = theadCell; 8770 elTh = oColumn.getThEl(); 8771 if(elTh) { 8772 return elTh; 8773 } 8774 } 8775 // Validate HTML element 8776 else { 8777 var el = Dom.get(theadCell); 8778 8779 if(el && (el.ownerDocument == document)) { 8780 // Validate TH element 8781 if(el.nodeName.toLowerCase() != "th") { 8782 // Traverse up the DOM to find the corresponding TR element 8783 elTh = Dom.getAncestorByTagName(el,"th"); 8784 } 8785 else { 8786 elTh = el; 8787 } 8788 8789 return elTh; 8790 } 8791 } 8792 8793 return null; 8794 }, 8795 8796 /** 8797 * Returns the page row index of given primary row. Returns null if the row is not on the 8798 * current DataTable page, or if row is not considered a primary row (i.e., row 8799 * extensions). 8800 * 8801 * @method getTrIndex 8802 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID 8803 * string reference to an element within the DataTable page, a Record instance, 8804 * or a Record's RecordSet index. 8805 * @return {Number} Page row index, or null if data row does not exist or is not on current page. 8806 */ 8807 getTrIndex : function(row) { 8808 var record = this.getRecord(row), 8809 index = this.getRecordIndex(record), 8810 tr; 8811 if(record) { 8812 tr = this.getTrEl(record); 8813 if(tr) { 8814 return tr.sectionRowIndex; 8815 } 8816 else { 8817 var oPaginator = this.get("paginator"); 8818 if(oPaginator) { 8819 return oPaginator.get('recordOffset') + index; 8820 } 8821 else { 8822 return index; 8823 } 8824 } 8825 } 8826 return null; 8827 }, 8828 8829 8830 8831 8832 8833 8834 8835 8836 8837 8838 8839 8840 8841 8842 8843 8844 8845 8846 8847 8848 8849 8850 8851 8852 8853 8854 8855 8856 8857 8858 8859 8860 8861 8862 8863 8864 8865 8866 8867 8868 8869 8870 8871 8872 8873 8874 // TABLE FUNCTIONS 8875 8876 /** 8877 * Loads new data. Convenience method that calls DataSource's sendRequest() 8878 * method under the hood. 8879 * 8880 * @method load 8881 * @param oConfig {object} Optional configuration parameters: 8882 * 8883 * <dl> 8884 * <dt>request</dt><dd>Pass in a new request, or initialRequest is used.</dd> 8885 * <dt>callback</dt><dd>Pass in DataSource sendRequest() callback object, or the following is used: 8886 * <dl> 8887 * <dt>success</dt><dd>datatable.onDataReturnInitializeTable</dd> 8888 * <dt>failure</dt><dd>datatable.onDataReturnInitializeTable</dd> 8889 * <dt>scope</dt><dd>datatable</dd> 8890 * <dt>argument</dt><dd>datatable.getState()</dd> 8891 * </dl> 8892 * </dd> 8893 * <dt>datasource</dt><dd>Pass in a new DataSource instance to override the current DataSource for this transaction.</dd> 8894 * </dl> 8895 */ 8896 load : function(oConfig) { 8897 oConfig = oConfig || {}; 8898 8899 (oConfig.datasource || this._oDataSource).sendRequest(oConfig.request || this.get("initialRequest"), oConfig.callback || { 8900 success: this.onDataReturnInitializeTable, 8901 failure: this.onDataReturnInitializeTable, 8902 scope: this, 8903 argument: this.getState() 8904 }); 8905 }, 8906 8907 /** 8908 * Resets a RecordSet with the given data and populates the page view 8909 * with the new data. Any previous data, and selection and sort states are 8910 * cleared. New data should be added as a separate step. 8911 * 8912 * @method initializeTable 8913 */ 8914 initializeTable : function() { 8915 // Reset init flag 8916 this._bInit = true; 8917 8918 // Clear the RecordSet 8919 this._oRecordSet.reset(); 8920 8921 // Clear the Paginator's totalRecords if paginating 8922 var pag = this.get('paginator'); 8923 if (pag) { 8924 pag.set('totalRecords',0); 8925 } 8926 8927 // Clear selections 8928 this._unselectAllTrEls(); 8929 this._unselectAllTdEls(); 8930 this._aSelections = null; 8931 this._oAnchorRecord = null; 8932 this._oAnchorCell = null; 8933 8934 // Clear sort 8935 this.set("sortedBy", null); 8936 }, 8937 8938 /** 8939 * Internal wrapper calls run() on render Chain instance. 8940 * 8941 * @method _runRenderChain 8942 * @private 8943 */ 8944 _runRenderChain : function() { 8945 this._oChainRender.run(); 8946 }, 8947 8948 /** 8949 * Returns array of Records for current view. For example, if paginated, it 8950 * returns the subset of Records for current page. 8951 * 8952 * @method _getViewRecords 8953 * @protected 8954 * @return {Array} Array of Records to display in current view. 8955 */ 8956 _getViewRecords : function() { 8957 // Paginator is enabled, show a subset of Records 8958 var oPaginator = this.get('paginator'); 8959 if(oPaginator) { 8960 return this._oRecordSet.getRecords( 8961 oPaginator.getStartIndex(), 8962 oPaginator.getRowsPerPage()); 8963 } 8964 // Not paginated, show all records 8965 else { 8966 return this._oRecordSet.getRecords(); 8967 } 8968 8969 }, 8970 8971 /** 8972 * Renders the view with existing Records from the RecordSet while 8973 * maintaining sort, pagination, and selection states. For performance, reuses 8974 * existing DOM elements when possible while deleting extraneous elements. 8975 * 8976 * @method render 8977 */ 8978 render : function() { 8979 //YAHOO.example.Performance.trialStart = new Date(); 8980 8981 this._oChainRender.stop(); 8982 8983 this.fireEvent("beforeRenderEvent"); 8984 8985 var i, j, k, len, 8986 allRecords = this._getViewRecords(); 8987 8988 8989 // From the top, update in-place existing rows, so as to reuse DOM elements 8990 var elTbody = this._elTbody, 8991 loopN = this.get("renderLoopSize"), 8992 nRecordsLength = allRecords.length; 8993 8994 // Table has rows 8995 if(nRecordsLength > 0) { 8996 elTbody.style.display = "none"; 8997 while(elTbody.lastChild) { 8998 elTbody.removeChild(elTbody.lastChild); 8999 } 9000 elTbody.style.display = ""; 9001 9002 // Set up the loop Chain to render rows 9003 this._oChainRender.add({ 9004 method: function(oArg) { 9005 if((this instanceof DT) && this._sId) { 9006 var i = oArg.nCurrentRecord, 9007 endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ? 9008 nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength), 9009 elRow, nextSibling; 9010 9011 elTbody.style.display = "none"; 9012 9013 for(; i<endRecordIndex; i++) { 9014 elRow = Dom.get(allRecords[i].getId()); 9015 elRow = elRow || this._addTrEl(allRecords[i]); 9016 nextSibling = elTbody.childNodes[i] || null; 9017 elTbody.insertBefore(elRow, nextSibling); 9018 } 9019 elTbody.style.display = ""; 9020 9021 // Set up for the next loop 9022 oArg.nCurrentRecord = i; 9023 } 9024 }, 9025 scope: this, 9026 iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1, 9027 argument: { 9028 nCurrentRecord: 0,//nRecordsLength-1, // Start at first Record 9029 nLoopLength: (loopN > 0) ? loopN : nRecordsLength 9030 }, 9031 timeout: (loopN > 0) ? 0 : -1 9032 }); 9033 9034 // Post-render tasks 9035 this._oChainRender.add({ 9036 method: function(oArg) { 9037 if((this instanceof DT) && this._sId) { 9038 while(elTbody.rows.length > nRecordsLength) { 9039 elTbody.removeChild(elTbody.lastChild); 9040 } 9041 this._setFirstRow(); 9042 this._setLastRow(); 9043 this._setRowStripes(); 9044 this._setSelections(); 9045 } 9046 }, 9047 scope: this, 9048 timeout: (loopN > 0) ? 0 : -1 9049 }); 9050 9051 } 9052 // Table has no rows 9053 else { 9054 // Set up the loop Chain to delete rows 9055 var nTotal = elTbody.rows.length; 9056 if(nTotal > 0) { 9057 this._oChainRender.add({ 9058 method: function(oArg) { 9059 if((this instanceof DT) && this._sId) { 9060 var i = oArg.nCurrent, 9061 loopN = oArg.nLoopLength, 9062 nIterEnd = (i - loopN < 0) ? 0 : i - loopN; 9063 9064 elTbody.style.display = "none"; 9065 9066 for(; i>nIterEnd; i--) { 9067 elTbody.deleteRow(-1); 9068 } 9069 elTbody.style.display = ""; 9070 9071 // Set up for the next loop 9072 oArg.nCurrent = i; 9073 } 9074 }, 9075 scope: this, 9076 iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1, 9077 argument: { 9078 nCurrent: nTotal, 9079 nLoopLength: (loopN > 0) ? loopN : nTotal 9080 }, 9081 timeout: (loopN > 0) ? 0 : -1 9082 }); 9083 } 9084 } 9085 this._runRenderChain(); 9086 }, 9087 9088 /** 9089 * Disables DataTable UI. 9090 * 9091 * @method disable 9092 */ 9093 disable : function() { 9094 this._disabled = true; 9095 var elTable = this._elTable; 9096 var elMask = this._elMask; 9097 elMask.style.width = elTable.offsetWidth + "px"; 9098 elMask.style.height = elTable.offsetHeight + "px"; 9099 elMask.style.left = elTable.offsetLeft + "px"; 9100 elMask.style.display = ""; 9101 this.fireEvent("disableEvent"); 9102 }, 9103 9104 /** 9105 * Undisables DataTable UI. 9106 * 9107 * @method undisable 9108 */ 9109 undisable : function() { 9110 this._disabled = false; 9111 this._elMask.style.display = "none"; 9112 this.fireEvent("undisableEvent"); 9113 }, 9114 9115 /** 9116 * Returns disabled state. 9117 * 9118 * @method isDisabled 9119 * @return {Boolean} True if UI is disabled, otherwise false 9120 */ 9121 isDisabled : function() { 9122 return this._disabled; 9123 }, 9124 9125 /** 9126 * Nulls out the entire DataTable instance and related objects, removes attached 9127 * event listeners, and clears out DOM elements inside the container. After 9128 * calling this method, the instance reference should be expliclitly nulled by 9129 * implementer, as in myDataTable = null. Use with caution! 9130 * 9131 * @method destroy 9132 */ 9133 destroy : function() { 9134 // Store for later 9135 var instanceName = this.toString(); 9136 9137 this._oChainRender.stop(); 9138 9139 // Destroy ColumnDD and ColumnResizers 9140 this._destroyColumnHelpers(); 9141 9142 // Destroy all CellEditors 9143 var oCellEditor; 9144 for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) { 9145 oCellEditor = this._oColumnSet.flat[i].editor; 9146 if(oCellEditor && oCellEditor.destroy) { 9147 oCellEditor.destroy(); 9148 this._oColumnSet.flat[i].editor = null; 9149 } 9150 } 9151 9152 // Destroy Paginator 9153 this._destroyPaginator(); 9154 9155 // Unhook custom events 9156 this._oRecordSet.unsubscribeAll(); 9157 this.unsubscribeAll(); 9158 9159 // Unhook DOM events 9160 Ev.removeListener(document, "click", this._onDocumentClick); 9161 9162 // Clear out the container 9163 this._destroyContainerEl(this._elContainer); 9164 9165 // Null out objects 9166 for(var param in this) { 9167 if(lang.hasOwnProperty(this, param)) { 9168 this[param] = null; 9169 } 9170 } 9171 9172 // Clean up static values 9173 DT._nCurrentCount--; 9174 9175 if(DT._nCurrentCount < 1) { 9176 if(DT._elDynStyleNode) { 9177 document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode); 9178 DT._elDynStyleNode = null; 9179 } 9180 } 9181 9182 }, 9183 9184 /** 9185 * Displays message within secondary TBODY. 9186 * 9187 * @method showTableMessage 9188 * @param sHTML {HTML} (optional) Value for innerHTML. 9189 * @param sClassName {String} (optional) Classname. 9190 */ 9191 showTableMessage : function(sHTML, sClassName) { 9192 var elCell = this._elMsgTd; 9193 if(lang.isString(sHTML)) { 9194 elCell.firstChild.innerHTML = sHTML; 9195 } 9196 if(lang.isString(sClassName)) { 9197 elCell.className = sClassName; 9198 } 9199 9200 this._elMsgTbody.style.display = ""; 9201 9202 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName}); 9203 }, 9204 9205 /** 9206 * Hides secondary TBODY. 9207 * 9208 * @method hideTableMessage 9209 */ 9210 hideTableMessage : function() { 9211 if(this._elMsgTbody.style.display != "none") { 9212 this._elMsgTbody.style.display = "none"; 9213 this._elMsgTbody.parentNode.style.width = ""; 9214 this.fireEvent("tableMsgHideEvent"); 9215 } 9216 }, 9217 9218 /** 9219 * Brings focus to the TBODY element. Alias to focusTbodyEl. 9220 * 9221 * @method focus 9222 */ 9223 focus : function() { 9224 this.focusTbodyEl(); 9225 }, 9226 9227 /** 9228 * Brings focus to the THEAD element. 9229 * 9230 * @method focusTheadEl 9231 */ 9232 focusTheadEl : function() { 9233 this._focusEl(this._elThead); 9234 }, 9235 9236 /** 9237 * Brings focus to the TBODY element. 9238 * 9239 * @method focusTbodyEl 9240 */ 9241 focusTbodyEl : function() { 9242 this._focusEl(this._elTbody); 9243 }, 9244 9245 /** 9246 * Setting display:none on DataTable or any parent may impact width validations. 9247 * After setting display back to "", implementers should call this method to 9248 * manually perform those validations. 9249 * 9250 * @method onShow 9251 */ 9252 onShow : function() { 9253 this.validateColumnWidths(); 9254 9255 for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) { 9256 col = allKeys[i]; 9257 if(col._ddResizer) { 9258 col._ddResizer.resetResizerEl(); 9259 } 9260 } 9261 }, 9262 9263 9264 9265 9266 9267 9268 9269 9270 9271 9272 9273 9274 9275 9276 9277 9278 9279 9280 9281 9282 9283 9284 9285 9286 9287 9288 9289 9290 9291 9292 9293 9294 9295 9296 9297 9298 9299 9300 9301 9302 9303 9304 9305 9306 9307 9308 9309 9310 9311 9312 9313 9314 9315 9316 9317 9318 9319 9320 9321 9322 9323 9324 9325 9326 9327 9328 9329 // RECORDSET FUNCTIONS 9330 9331 /** 9332 * Returns Record index for given TR element or page row index. 9333 * 9334 * @method getRecordIndex 9335 * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR 9336 * element reference or page row index. 9337 * @return {Number} Record's RecordSet index, or null. 9338 */ 9339 getRecordIndex : function(row) { 9340 var nTrIndex; 9341 9342 if(!lang.isNumber(row)) { 9343 // By Record 9344 if(row instanceof YAHOO.widget.Record) { 9345 return this._oRecordSet.getRecordIndex(row); 9346 } 9347 // By element reference 9348 else { 9349 // Find the TR element 9350 var el = this.getTrEl(row); 9351 if(el) { 9352 nTrIndex = el.sectionRowIndex; 9353 } 9354 } 9355 } 9356 // By page row index 9357 else { 9358 nTrIndex = row; 9359 } 9360 9361 if(lang.isNumber(nTrIndex)) { 9362 var oPaginator = this.get("paginator"); 9363 if(oPaginator) { 9364 return oPaginator.get('recordOffset') + nTrIndex; 9365 } 9366 else { 9367 return nTrIndex; 9368 } 9369 } 9370 9371 return null; 9372 }, 9373 9374 /** 9375 * For the given identifier, returns the associated Record instance. 9376 * 9377 * @method getRecord 9378 * @param row {HTMLElement | Number | String} DOM reference to a TR element (or 9379 * child of a TR element), RecordSet position index, or Record ID. 9380 * @return {YAHOO.widget.Record} Record instance. 9381 */ 9382 getRecord : function(row) { 9383 var oRecord = this._oRecordSet.getRecord(row); 9384 9385 if(!oRecord) { 9386 // Validate TR element 9387 var elRow = this.getTrEl(row); 9388 if(elRow) { 9389 oRecord = this._oRecordSet.getRecord(elRow.id); 9390 } 9391 } 9392 9393 if(oRecord instanceof YAHOO.widget.Record) { 9394 return this._oRecordSet.getRecord(oRecord); 9395 } 9396 else { 9397 return null; 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 9439 9440 9441 9442 9443 9444 9445 9446 // COLUMN FUNCTIONS 9447 9448 /** 9449 * For the given identifier, returns the associated Column instance. Note: For 9450 * getting Columns by Column ID string, please use the method getColumnById(). 9451 * 9452 * @method getColumn 9453 * @param column {HTMLElement | String | Number} TH/TD element (or child of a 9454 * TH/TD element), a Column key, or a ColumnSet key index. 9455 * @return {YAHOO.widget.Column} Column instance. 9456 */ 9457 getColumn : function(column) { 9458 var oColumn = this._oColumnSet.getColumn(column); 9459 9460 if(!oColumn) { 9461 // Validate TD element 9462 var elCell = this.getTdEl(column); 9463 if(elCell) { 9464 oColumn = this._oColumnSet.getColumn(this.getCellIndex(elCell)); 9465 } 9466 // Validate TH element 9467 else { 9468 elCell = this.getThEl(column); 9469 if(elCell) { 9470 // Find by TH el ID 9471 var allColumns = this._oColumnSet.flat; 9472 for(var i=0, len=allColumns.length; i<len; i++) { 9473 if(allColumns[i].getThEl().id === elCell.id) { 9474 oColumn = allColumns[i]; 9475 } 9476 } 9477 } 9478 } 9479 } 9480 if(!oColumn) { 9481 } 9482 return oColumn; 9483 }, 9484 9485 /** 9486 * For the given Column ID, returns the associated Column instance. Note: For 9487 * getting Columns by key, please use the method getColumn(). 9488 * 9489 * @method getColumnById 9490 * @param column {String} Column ID string. 9491 * @return {YAHOO.widget.Column} Column instance. 9492 */ 9493 getColumnById : function(column) { 9494 return this._oColumnSet.getColumnById(column); 9495 }, 9496 9497 /** 9498 * For the given Column instance, returns next direction to sort. 9499 * 9500 * @method getColumnSortDir 9501 * @param oColumn {YAHOO.widget.Column} Column instance. 9502 * @param oSortedBy {Object} (optional) Specify the state, or use current state. 9503 * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC. 9504 */ 9505 getColumnSortDir : function(oColumn, oSortedBy) { 9506 // Backward compatibility 9507 if(oColumn.sortOptions && oColumn.sortOptions.defaultDir) { 9508 if(oColumn.sortOptions.defaultDir == "asc") { 9509 oColumn.sortOptions.defaultDir = DT.CLASS_ASC; 9510 } 9511 else if (oColumn.sortOptions.defaultDir == "desc") { 9512 oColumn.sortOptions.defaultDir = DT.CLASS_DESC; 9513 } 9514 } 9515 9516 // What is the Column's default sort direction? 9517 var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC; 9518 9519 // Is the Column currently sorted? 9520 var bSorted = false; 9521 oSortedBy = oSortedBy || this.get("sortedBy"); 9522 if(oSortedBy && (oSortedBy.key === oColumn.key)) { 9523 bSorted = true; 9524 if(oSortedBy.dir) { 9525 sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC; 9526 } 9527 else { 9528 sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC; 9529 } 9530 } 9531 return sortDir; 9532 }, 9533 9534 /** 9535 * Overridable method gives implementers a hook to show loading message before 9536 * sorting Column. 9537 * 9538 * @method doBeforeSortColumn 9539 * @param oColumn {YAHOO.widget.Column} Column instance. 9540 * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or 9541 * YAHOO.widget.DataTable.CLASS_DESC. 9542 * @return {Boolean} Return true to continue sorting Column. 9543 */ 9544 doBeforeSortColumn : function(oColumn, sSortDir) { 9545 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING); 9546 return true; 9547 }, 9548 9549 /** 9550 * Sorts given Column. If "dynamicData" is true, current selections are purged before 9551 * a request is sent to the DataSource for data for the new state (using the 9552 * request returned by "generateRequest()"). 9553 * 9554 * @method sortColumn 9555 * @param oColumn {YAHOO.widget.Column} Column instance. 9556 * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or 9557 * YAHOO.widget.DataTable.CLASS_DESC 9558 */ 9559 sortColumn : function(oColumn, sDir) { 9560 if(oColumn && (oColumn instanceof YAHOO.widget.Column)) { 9561 if(!oColumn.sortable) { 9562 Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE); 9563 } 9564 9565 // Validate given direction 9566 if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) { 9567 sDir = null; 9568 } 9569 9570 // Get the sort dir 9571 var sSortDir = sDir || this.getColumnSortDir(oColumn); 9572 9573 // Is the Column currently sorted? 9574 var oSortedBy = this.get("sortedBy") || {}; 9575 var bSorted = (oSortedBy.key === oColumn.key) ? true : false; 9576 9577 var ok = this.doBeforeSortColumn(oColumn, sSortDir); 9578 if(ok) { 9579 // Server-side sort 9580 if(this.get("dynamicData")) { 9581 // Get current state 9582 var oState = this.getState(); 9583 9584 // Reset record offset, if paginated 9585 if(oState.pagination) { 9586 oState.pagination.recordOffset = 0; 9587 } 9588 9589 // Update sortedBy to new values 9590 oState.sortedBy = { 9591 key: oColumn.key, 9592 dir: sSortDir 9593 }; 9594 9595 // Get the request for the new state 9596 var request = this.get("generateRequest")(oState, this); 9597 9598 // Purge selections 9599 this.unselectAllRows(); 9600 this.unselectAllCells(); 9601 9602 // Send request for new data 9603 var callback = { 9604 success : this.onDataReturnSetRows, 9605 failure : this.onDataReturnSetRows, 9606 argument : oState, // Pass along the new state to the callback 9607 scope : this 9608 }; 9609 this._oDataSource.sendRequest(request, callback); 9610 } 9611 // Client-side sort 9612 else { 9613 // Is there a custom sort handler function defined? 9614 var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ? 9615 // Custom sort function 9616 oColumn.sortOptions.sortFunction : null; 9617 9618 // Sort the Records 9619 if(!bSorted || sDir || sortFnc) { 9620 // Default sort function if necessary 9621 sortFnc = sortFnc || this.get("sortFunction"); 9622 // Get the field to sort 9623 var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field; 9624 9625 // Sort the Records 9626 this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false), sField); 9627 } 9628 // Just reverse the Records 9629 else { 9630 this._oRecordSet.reverseRecords(); 9631 } 9632 9633 // Reset to first page if paginated 9634 var oPaginator = this.get('paginator'); 9635 if (oPaginator) { 9636 // Set page silently, so as not to fire change event. 9637 oPaginator.setPage(1,true); 9638 } 9639 9640 // Update UI via sortedBy 9641 this.render(); 9642 this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn}); 9643 } 9644 9645 this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir}); 9646 return; 9647 } 9648 } 9649 }, 9650 9651 /** 9652 * Sets given Column to given pixel width. If new width is less than minimum 9653 * width, sets to minimum width. Updates oColumn.width value. 9654 * 9655 * @method setColumnWidth 9656 * @param oColumn {YAHOO.widget.Column} Column instance. 9657 * @param nWidth {Number} New width in pixels. A null value auto-sizes Column, 9658 * subject to minWidth and maxAutoWidth validations. 9659 */ 9660 setColumnWidth : function(oColumn, nWidth) { 9661 if(!(oColumn instanceof YAHOO.widget.Column)) { 9662 oColumn = this.getColumn(oColumn); 9663 } 9664 if(oColumn) { 9665 // Validate new width against minimum width 9666 if(lang.isNumber(nWidth)) { 9667 // This is why we must require a Number... :-| 9668 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth; 9669 9670 // Save state 9671 oColumn.width = nWidth; 9672 9673 // Resize the DOM elements 9674 this._setColumnWidth(oColumn, nWidth+"px"); 9675 9676 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth}); 9677 } 9678 // Unsets a width to auto-size 9679 else if(nWidth === null) { 9680 // Save state 9681 oColumn.width = nWidth; 9682 9683 // Resize the DOM elements 9684 this._setColumnWidth(oColumn, "auto"); 9685 this.validateColumnWidths(oColumn); 9686 this.fireEvent("columnUnsetWidthEvent",{column:oColumn}); 9687 } 9688 9689 // Bug 2339454: resize then sort misaligment 9690 this._clearTrTemplateEl(); 9691 } 9692 else { 9693 } 9694 }, 9695 9696 /** 9697 * Sets liner DIV elements of given Column to given width. When value should be 9698 * auto-calculated to fit content overflow is set to visible, otherwise overflow 9699 * is set to hidden. No validations against minimum width and no updating 9700 * Column.width value. 9701 * 9702 * @method _setColumnWidth 9703 * @param oColumn {YAHOO.widget.Column} Column instance. 9704 * @param sWidth {String} New width value. 9705 * @param sOverflow {String} Should be "hidden" when Column width is explicitly 9706 * being set to a value, but should be "visible" when Column is meant to auto-fit content. 9707 * @private 9708 */ 9709 _setColumnWidth : function(oColumn, sWidth, sOverflow) { 9710 if(oColumn && (oColumn.getKeyIndex() !== null)) { 9711 sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden'); 9712 9713 // Dynamic style algorithm 9714 if(!DT._bDynStylesFallback) { 9715 this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow); 9716 } 9717 // Dynamic function algorithm 9718 else { 9719 this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow); 9720 } 9721 } 9722 else { 9723 } 9724 }, 9725 9726 /** 9727 * Updates width of a Column's liner DIV elements by dynamically creating a 9728 * STYLE node and writing and updating CSS style rules to it. If this fails during 9729 * runtime, the fallback method _setColumnWidthDynFunction() will be called. 9730 * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is 9731 * nested within another TABLE element. For these cases, it is recommended to 9732 * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE. 9733 * 9734 * @method _setColumnWidthDynStyles 9735 * @param oColumn {YAHOO.widget.Column} Column instance. 9736 * @param sWidth {String} New width value. 9737 * @private 9738 */ 9739 _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) { 9740 var s = DT._elDynStyleNode, 9741 rule; 9742 9743 // Create a new STYLE node 9744 if(!s) { 9745 s = document.createElement('style'); 9746 s.type = 'text/css'; 9747 s = document.getElementsByTagName('head').item(0).appendChild(s); 9748 DT._elDynStyleNode = s; 9749 } 9750 9751 // We have a STYLE node to update 9752 if(s) { 9753 // Use unique classname for this Column instance as a hook for resizing 9754 var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER; 9755 9756 // Hide for performance 9757 if(this._elTbody) { 9758 this._elTbody.style.display = 'none'; 9759 } 9760 9761 rule = DT._oDynStyles[sClassname]; 9762 9763 // The Column does not yet have a rule 9764 if(!rule) { 9765 if(s.styleSheet && s.styleSheet.addRule) { 9766 s.styleSheet.addRule(sClassname,"overflow:"+sOverflow); 9767 s.styleSheet.addRule(sClassname,'width:'+sWidth); 9768 rule = s.styleSheet.rules[s.styleSheet.rules.length-1]; 9769 DT._oDynStyles[sClassname] = rule; 9770 } 9771 else if(s.sheet && s.sheet.insertRule) { 9772 s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length); 9773 rule = s.sheet.cssRules[s.sheet.cssRules.length-1]; 9774 DT._oDynStyles[sClassname] = rule; 9775 } 9776 } 9777 // We have a rule to update 9778 else { 9779 rule.style.overflow = sOverflow; 9780 rule.style.width = sWidth; 9781 } 9782 9783 // Unhide 9784 if(this._elTbody) { 9785 this._elTbody.style.display = ''; 9786 } 9787 } 9788 9789 // That was not a success, we must call the fallback routine 9790 if(!rule) { 9791 DT._bDynStylesFallback = true; 9792 this._setColumnWidthDynFunction(oColumn, sWidth); 9793 } 9794 }, 9795 9796 /** 9797 * Updates width of a Column's liner DIV elements by dynamically creating a 9798 * function to update all element style properties in one pass. Note: This 9799 * technique is not supported in sandboxed environments that prohibit EVALs. 9800 * 9801 * @method _setColumnWidthDynFunction 9802 * @param oColumn {YAHOO.widget.Column} Column instance. 9803 * @param sWidth {String} New width value. 9804 * @private 9805 */ 9806 _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) { 9807 // TODO: why is this here? 9808 if(sWidth == 'auto') { 9809 sWidth = ''; 9810 } 9811 9812 // Create one function for each value of rows.length 9813 var rowslen = this._elTbody ? this._elTbody.rows.length : 0; 9814 9815 // Dynamically create the function 9816 if (!this._aDynFunctions[rowslen]) { 9817 9818 //Compile a custom function to do all the liner div width 9819 //assignments at the same time. A unique function is required 9820 //for each unique number of rows in _elTbody. This will 9821 //result in a function declaration like: 9822 //function (oColumn,sWidth,sOverflow) { 9823 // var colIdx = oColumn.getKeyIndex(); 9824 // oColumn.getThLinerEl().style.overflow = 9825 // this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow = 9826 // this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow = 9827 // ... (for all row indices in this._elTbody.rows.length - 1) 9828 // this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow = 9829 // sOverflow; 9830 // oColumn.getThLinerEl().style.width = 9831 // this._elTbody.rows[0].cells[colIdx].firstChild.style.width = 9832 // this._elTbody.rows[1].cells[colIdx].firstChild.style.width = 9833 // ... (for all row indices in this._elTbody.rows.length - 1) 9834 // this._elTbody.rows[99].cells[colIdx].firstChild.style.width = 9835 // sWidth; 9836 //} 9837 9838 var i,j,k; 9839 var resizerDef = [ 9840 'var colIdx=oColumn.getKeyIndex();', 9841 'oColumn.getThLinerEl().style.overflow=' 9842 ]; 9843 for (i=rowslen-1, j=2; i >= 0; --i) { 9844 resizerDef[j++] = 'this._elTbody.rows['; 9845 resizerDef[j++] = i; 9846 resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow='; 9847 } 9848 resizerDef[j] = 'sOverflow;'; 9849 resizerDef[j+1] = 'oColumn.getThLinerEl().style.width='; 9850 for (i=rowslen-1, k=j+2; i >= 0; --i) { 9851 resizerDef[k++] = 'this._elTbody.rows['; 9852 resizerDef[k++] = i; 9853 resizerDef[k++] = '].cells[colIdx].firstChild.style.width='; 9854 } 9855 resizerDef[k] = 'sWidth;'; 9856 this._aDynFunctions[rowslen] = 9857 new Function('oColumn','sWidth','sOverflow',resizerDef.join('')); 9858 } 9859 9860 // Get the function to execute 9861 var resizerFn = this._aDynFunctions[rowslen]; 9862 9863 // TODO: Hide TBODY for performance in _setColumnWidthDynFunction? 9864 if (resizerFn) { 9865 resizerFn.call(this,oColumn,sWidth,sOverflow); 9866 } 9867 }, 9868 9869 /** 9870 * For one or all Columns, when Column is not hidden, width is not set, and minWidth 9871 * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth. 9872 * 9873 * @method validateColumnWidths 9874 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated. 9875 */ 9876 validateColumnWidths : function(oColumn) { 9877 var elColgroup = this._elColgroup; 9878 var elColgroupClone = elColgroup.cloneNode(true); 9879 var bNeedsValidation = false; 9880 var allKeys = this._oColumnSet.keys; 9881 var elThLiner; 9882 // Validate just one Column's minWidth and/or maxAutoWidth 9883 if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) { 9884 elThLiner = oColumn.getThLinerEl(); 9885 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) { 9886 elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width = 9887 oColumn.minWidth + 9888 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) + 9889 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px"; 9890 bNeedsValidation = true; 9891 } 9892 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) { 9893 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden"); 9894 } 9895 } 9896 // Validate all Columns 9897 else { 9898 for(var i=0, len=allKeys.length; i<len; i++) { 9899 oColumn = allKeys[i]; 9900 if(!oColumn.hidden && !oColumn.width) { 9901 elThLiner = oColumn.getThLinerEl(); 9902 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) { 9903 elColgroupClone.childNodes[i].style.width = 9904 oColumn.minWidth + 9905 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) + 9906 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px"; 9907 bNeedsValidation = true; 9908 } 9909 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) { 9910 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden"); 9911 } 9912 } 9913 } 9914 } 9915 if(bNeedsValidation) { 9916 elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup); 9917 this._elColgroup = elColgroupClone; 9918 } 9919 }, 9920 9921 /** 9922 * Clears minWidth. 9923 * 9924 * @method _clearMinWidth 9925 * @param oColumn {YAHOO.widget.Column} Which Column. 9926 * @private 9927 */ 9928 _clearMinWidth : function(oColumn) { 9929 if(oColumn.getKeyIndex() !== null) { 9930 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = ''; 9931 } 9932 }, 9933 9934 /** 9935 * Restores minWidth. 9936 * 9937 * @method _restoreMinWidth 9938 * @param oColumn {YAHOO.widget.Column} Which Column. 9939 * @private 9940 */ 9941 _restoreMinWidth : function(oColumn) { 9942 if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) { 9943 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px'; 9944 } 9945 }, 9946 9947 /** 9948 * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only 9949 * hide/show non-nested Columns, and top-level parent Columns (which will 9950 * hide/show all children Columns). 9951 * 9952 * @method hideColumn 9953 * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column 9954 * instance, TH/TD element (or child of a TH/TD element), a Column key, or a 9955 * ColumnSet key index. 9956 */ 9957 hideColumn : function(oColumn) { 9958 if(!(oColumn instanceof YAHOO.widget.Column)) { 9959 oColumn = this.getColumn(oColumn); 9960 } 9961 // Only top-level Columns can get hidden due to issues in FF2 and SF3 9962 if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) { 9963 9964 var allrows = this.getTbodyEl().rows; 9965 var l = allrows.length; 9966 var allDescendants = this._oColumnSet.getDescendants(oColumn); 9967 9968 // Hide each nested Column 9969 for(var i=0, len=allDescendants.length; i<len; i++) { 9970 var thisColumn = allDescendants[i]; 9971 thisColumn.hidden = true; 9972 9973 // Style the head cell 9974 Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN); 9975 9976 // Does this Column have body cells? 9977 var thisKeyIndex = thisColumn.getKeyIndex(); 9978 if(thisKeyIndex !== null) { 9979 // Clear minWidth 9980 this._clearMinWidth(oColumn); 9981 9982 // Style the body cells 9983 for(var j=0;j<l;j++) { 9984 Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN); 9985 } 9986 } 9987 9988 this.fireEvent("columnHideEvent",{column:thisColumn}); 9989 } 9990 9991 this._repaintOpera(); 9992 this._clearTrTemplateEl(); 9993 } 9994 else { 9995 } 9996 }, 9997 9998 /** 9999 * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only 10000 * hide/show non-nested Columns, and top-level parent Columns (which will 10001 * hide/show all children Columns). 10002 * 10003 * @method showColumn 10004 * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column 10005 * instance, TH/TD element (or child of a TH/TD element), a Column key, or a 10006 * ColumnSet key index. 10007 */ 10008 showColumn : function(oColumn) { 10009 if(!(oColumn instanceof YAHOO.widget.Column)) { 10010 oColumn = this.getColumn(oColumn); 10011 } 10012 // Only top-level Columns can get hidden 10013 if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) { 10014 var allrows = this.getTbodyEl().rows; 10015 var l = allrows.length; 10016 var allDescendants = this._oColumnSet.getDescendants(oColumn); 10017 10018 // Show each nested Column 10019 for(var i=0, len=allDescendants.length; i<len; i++) { 10020 var thisColumn = allDescendants[i]; 10021 thisColumn.hidden = false; 10022 10023 // Unstyle the head cell 10024 Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN); 10025 10026 // Does this Column have body cells? 10027 var thisKeyIndex = thisColumn.getKeyIndex(); 10028 if(thisKeyIndex !== null) { 10029 // Restore minWidth 10030 this._restoreMinWidth(oColumn); 10031 10032 10033 // Unstyle the body cells 10034 for(var j=0;j<l;j++) { 10035 Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN); 10036 } 10037 } 10038 10039 this.fireEvent("columnShowEvent",{column:thisColumn}); 10040 } 10041 this._clearTrTemplateEl(); 10042 } 10043 else { 10044 } 10045 }, 10046 10047 /** 10048 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove 10049 * non-nested Columns, and top-level parent Columns (which will remove all 10050 * children Columns). 10051 * 10052 * @method removeColumn 10053 * @param oColumn {YAHOO.widget.Column} Column instance. 10054 * @return oColumn {YAHOO.widget.Column} Removed Column instance. 10055 */ 10056 removeColumn : function(oColumn) { 10057 // Validate Column 10058 if(!(oColumn instanceof YAHOO.widget.Column)) { 10059 oColumn = this.getColumn(oColumn); 10060 } 10061 if(oColumn) { 10062 var nColTreeIndex = oColumn.getTreeIndex(); 10063 if(nColTreeIndex !== null) { 10064 // Which key index(es) 10065 var i, len, 10066 aKeyIndexes = oColumn.getKeyIndex(); 10067 // Must be a parent Column 10068 if(aKeyIndexes === null) { 10069 var descKeyIndexes = []; 10070 var allDescendants = this._oColumnSet.getDescendants(oColumn); 10071 for(i=0, len=allDescendants.length; i<len; i++) { 10072 // Is this descendant a key Column? 10073 var thisKey = allDescendants[i].getKeyIndex(); 10074 if(thisKey !== null) { 10075 descKeyIndexes[descKeyIndexes.length] = thisKey; 10076 } 10077 } 10078 if(descKeyIndexes.length > 0) { 10079 aKeyIndexes = descKeyIndexes; 10080 } 10081 } 10082 // Must be a key Column 10083 else { 10084 aKeyIndexes = [aKeyIndexes]; 10085 } 10086 10087 if(aKeyIndexes !== null) { 10088 // Sort the indexes so we can remove from the right 10089 aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);}); 10090 10091 // Destroy previous THEAD 10092 this._destroyTheadEl(); 10093 10094 // Create new THEAD 10095 var aOrigColumnDefs = this._oColumnSet.getDefinitions(); 10096 oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0]; 10097 this._initColumnSet(aOrigColumnDefs); 10098 this._initTheadEl(); 10099 10100 // Remove COL 10101 for(i=aKeyIndexes.length-1; i>-1; i--) { 10102 this._removeColgroupColEl(aKeyIndexes[i]); 10103 } 10104 10105 // Remove TD 10106 var allRows = this._elTbody.rows; 10107 if(allRows.length > 0) { 10108 var loopN = this.get("renderLoopSize"), 10109 loopEnd = allRows.length; 10110 this._oChainRender.add({ 10111 method: function(oArg) { 10112 if((this instanceof DT) && this._sId) { 10113 var i = oArg.nCurrentRow, 10114 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length, 10115 aIndexes = oArg.aIndexes, 10116 j; 10117 for(; i < len; ++i) { 10118 for(j = aIndexes.length-1; j>-1; j--) { 10119 allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]); 10120 } 10121 } 10122 oArg.nCurrentRow = i; 10123 } 10124 }, 10125 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1, 10126 argument: {nCurrentRow:0, aIndexes:aKeyIndexes}, 10127 scope: this, 10128 timeout: (loopN > 0) ? 0 : -1 10129 }); 10130 this._runRenderChain(); 10131 } 10132 10133 this.fireEvent("columnRemoveEvent",{column:oColumn}); 10134 return oColumn; 10135 } 10136 } 10137 } 10138 }, 10139 10140 /** 10141 * Inserts given Column at the index if given, otherwise at the end. NOTE: You 10142 * can only add non-nested Columns and top-level parent Columns. You cannot add 10143 * a nested Column to an existing parent. 10144 * 10145 * @method insertColumn 10146 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column 10147 * definition or a Column instance. 10148 * @param index {Number} (optional) New tree index. 10149 * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 10150 */ 10151 insertColumn : function(oColumn, index) { 10152 // Validate Column 10153 if(oColumn instanceof YAHOO.widget.Column) { 10154 oColumn = oColumn.getDefinition(); 10155 } 10156 else if(oColumn.constructor !== Object) { 10157 return; 10158 } 10159 10160 // Validate index or append new Column to the end of the ColumnSet 10161 var oColumnSet = this._oColumnSet; 10162 if(!lang.isValue(index) || !lang.isNumber(index)) { 10163 index = oColumnSet.tree[0].length; 10164 } 10165 10166 // Destroy previous THEAD 10167 this._destroyTheadEl(); 10168 10169 // Create new THEAD 10170 var aNewColumnDefs = this._oColumnSet.getDefinitions(); 10171 aNewColumnDefs.splice(index, 0, oColumn); 10172 this._initColumnSet(aNewColumnDefs); 10173 this._initTheadEl(); 10174 10175 // Need to refresh the reference 10176 oColumnSet = this._oColumnSet; 10177 var oNewColumn = oColumnSet.tree[0][index]; 10178 10179 // Get key index(es) for new Column 10180 var i, len, 10181 descKeyIndexes = []; 10182 var allDescendants = oColumnSet.getDescendants(oNewColumn); 10183 for(i=0, len=allDescendants.length; i<len; i++) { 10184 // Is this descendant a key Column? 10185 var thisKey = allDescendants[i].getKeyIndex(); 10186 if(thisKey !== null) { 10187 descKeyIndexes[descKeyIndexes.length] = thisKey; 10188 } 10189 } 10190 10191 if(descKeyIndexes.length > 0) { 10192 // Sort the indexes 10193 var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0]; 10194 10195 // Add COL 10196 for(i=descKeyIndexes.length-1; i>-1; i--) { 10197 this._insertColgroupColEl(descKeyIndexes[i]); 10198 } 10199 10200 // Add TD 10201 var allRows = this._elTbody.rows; 10202 if(allRows.length > 0) { 10203 var loopN = this.get("renderLoopSize"), 10204 loopEnd = allRows.length; 10205 10206 // Get templates for each new TD 10207 var aTdTemplates = [], 10208 elTdTemplate; 10209 for(i=0, len=descKeyIndexes.length; i<len; i++) { 10210 var thisKeyIndex = descKeyIndexes[i]; 10211 elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true); 10212 elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1)); 10213 aTdTemplates[thisKeyIndex] = elTdTemplate; 10214 } 10215 10216 this._oChainRender.add({ 10217 method: function(oArg) { 10218 if((this instanceof DT) && this._sId) { 10219 var i = oArg.nCurrentRow, j, 10220 descKeyIndexes = oArg.descKeyIndexes, 10221 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length, 10222 nextSibling; 10223 for(; i < len; ++i) { 10224 nextSibling = allRows[i].childNodes[newIndex] || null; 10225 for(j=descKeyIndexes.length-1; j>-1; j--) { 10226 allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling); 10227 } 10228 } 10229 oArg.nCurrentRow = i; 10230 } 10231 }, 10232 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1, 10233 argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes}, 10234 scope: this, 10235 timeout: (loopN > 0) ? 0 : -1 10236 }); 10237 this._runRenderChain(); 10238 } 10239 10240 this.fireEvent("columnInsertEvent",{column:oColumn,index:index}); 10241 return oNewColumn; 10242 } 10243 }, 10244 10245 /** 10246 * Removes given Column and inserts into given tree index. NOTE: You 10247 * can only reorder non-nested Columns and top-level parent Columns. You cannot 10248 * reorder a nested Column to an existing parent. 10249 * 10250 * @method reorderColumn 10251 * @param oColumn {YAHOO.widget.Column} Column instance. 10252 * @param index {Number} New tree index. 10253 * @return oColumn {YAHOO.widget.Column} Reordered Column instance. 10254 */ 10255 reorderColumn : function(oColumn, index) { 10256 // Validate Column and new index 10257 if(!(oColumn instanceof YAHOO.widget.Column)) { 10258 oColumn = this.getColumn(oColumn); 10259 } 10260 if(oColumn && YAHOO.lang.isNumber(index)) { 10261 var nOrigTreeIndex = oColumn.getTreeIndex(); 10262 if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) { 10263 // Which key index(es) 10264 var i, len, 10265 aOrigKeyIndexes = oColumn.getKeyIndex(), 10266 allDescendants, 10267 descKeyIndexes = [], 10268 thisKey; 10269 // Must be a parent Column... 10270 if(aOrigKeyIndexes === null) { 10271 allDescendants = this._oColumnSet.getDescendants(oColumn); 10272 for(i=0, len=allDescendants.length; i<len; i++) { 10273 // Is this descendant a key Column? 10274 thisKey = allDescendants[i].getKeyIndex(); 10275 if(thisKey !== null) { 10276 descKeyIndexes[descKeyIndexes.length] = thisKey; 10277 } 10278 } 10279 if(descKeyIndexes.length > 0) { 10280 aOrigKeyIndexes = descKeyIndexes; 10281 } 10282 } 10283 // ...or else must be a key Column 10284 else { 10285 aOrigKeyIndexes = [aOrigKeyIndexes]; 10286 } 10287 10288 if(aOrigKeyIndexes !== null) { 10289 // Sort the indexes 10290 aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);}); 10291 10292 // Destroy previous THEAD 10293 this._destroyTheadEl(); 10294 10295 // Create new THEAD 10296 var aColumnDefs = this._oColumnSet.getDefinitions(); 10297 var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0]; 10298 aColumnDefs.splice(index, 0, oColumnDef); 10299 this._initColumnSet(aColumnDefs); 10300 this._initTheadEl(); 10301 10302 // Need to refresh the reference 10303 var oNewColumn = this._oColumnSet.tree[0][index]; 10304 10305 // What are new key index(es) 10306 var aNewKeyIndexes = oNewColumn.getKeyIndex(); 10307 // Must be a parent Column 10308 if(aNewKeyIndexes === null) { 10309 descKeyIndexes = []; 10310 allDescendants = this._oColumnSet.getDescendants(oNewColumn); 10311 for(i=0, len=allDescendants.length; i<len; i++) { 10312 // Is this descendant a key Column? 10313 thisKey = allDescendants[i].getKeyIndex(); 10314 if(thisKey !== null) { 10315 descKeyIndexes[descKeyIndexes.length] = thisKey; 10316 } 10317 } 10318 if(descKeyIndexes.length > 0) { 10319 aNewKeyIndexes = descKeyIndexes; 10320 } 10321 } 10322 // Must be a key Column 10323 else { 10324 aNewKeyIndexes = [aNewKeyIndexes]; 10325 } 10326 10327 // Sort the new indexes and grab the first one for the new location 10328 var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0]; 10329 10330 // Reorder COL 10331 this._reorderColgroupColEl(aOrigKeyIndexes, newIndex); 10332 10333 // Reorder TD 10334 var allRows = this._elTbody.rows; 10335 if(allRows.length > 0) { 10336 var loopN = this.get("renderLoopSize"), 10337 loopEnd = allRows.length; 10338 this._oChainRender.add({ 10339 method: function(oArg) { 10340 if((this instanceof DT) && this._sId) { 10341 var i = oArg.nCurrentRow, j, tmpTds, nextSibling, 10342 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length, 10343 aIndexes = oArg.aIndexes, thisTr; 10344 // For each row 10345 for(; i < len; ++i) { 10346 tmpTds = []; 10347 thisTr = allRows[i]; 10348 10349 // Remove each TD 10350 for(j=aIndexes.length-1; j>-1; j--) { 10351 tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]])); 10352 } 10353 10354 // Insert each TD 10355 nextSibling = thisTr.childNodes[newIndex] || null; 10356 for(j=tmpTds.length-1; j>-1; j--) { 10357 thisTr.insertBefore(tmpTds[j], nextSibling); 10358 } 10359 } 10360 oArg.nCurrentRow = i; 10361 } 10362 }, 10363 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1, 10364 argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes}, 10365 scope: this, 10366 timeout: (loopN > 0) ? 0 : -1 10367 }); 10368 this._runRenderChain(); 10369 } 10370 10371 this.fireEvent("columnReorderEvent",{column:oNewColumn, oldIndex:nOrigTreeIndex}); 10372 return oNewColumn; 10373 } 10374 } 10375 } 10376 }, 10377 10378 /** 10379 * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only 10380 * select/unselect non-nested Columns, and bottom-level key Columns. 10381 * 10382 * @method selectColumn 10383 * @param column {HTMLElement | String | Number} DOM reference or ID string to a 10384 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index. 10385 */ 10386 selectColumn : function(oColumn) { 10387 oColumn = this.getColumn(oColumn); 10388 if(oColumn && !oColumn.selected) { 10389 // Only bottom-level Columns can get hidden 10390 if(oColumn.getKeyIndex() !== null) { 10391 oColumn.selected = true; 10392 10393 // Update head cell 10394 var elTh = oColumn.getThEl(); 10395 Dom.addClass(elTh,DT.CLASS_SELECTED); 10396 10397 // Update body cells 10398 var allRows = this.getTbodyEl().rows; 10399 var oChainRender = this._oChainRender; 10400 oChainRender.add({ 10401 method: function(oArg) { 10402 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) { 10403 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED); 10404 } 10405 oArg.rowIndex++; 10406 }, 10407 scope: this, 10408 iterations: allRows.length, 10409 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()} 10410 }); 10411 10412 this._clearTrTemplateEl(); 10413 10414 this._elTbody.style.display = "none"; 10415 this._runRenderChain(); 10416 this._elTbody.style.display = ""; 10417 10418 this.fireEvent("columnSelectEvent",{column:oColumn}); 10419 } 10420 else { 10421 } 10422 } 10423 }, 10424 10425 /** 10426 * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only 10427 * select/unselect non-nested Columns, and bottom-level key Columns. 10428 * 10429 * @method unselectColumn 10430 * @param column {HTMLElement | String | Number} DOM reference or ID string to a 10431 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index. 10432 */ 10433 unselectColumn : function(oColumn) { 10434 oColumn = this.getColumn(oColumn); 10435 if(oColumn && oColumn.selected) { 10436 // Only bottom-level Columns can get hidden 10437 if(oColumn.getKeyIndex() !== null) { 10438 oColumn.selected = false; 10439 10440 // Update head cell 10441 var elTh = oColumn.getThEl(); 10442 Dom.removeClass(elTh,DT.CLASS_SELECTED); 10443 10444 // Update body cells 10445 var allRows = this.getTbodyEl().rows; 10446 var oChainRender = this._oChainRender; 10447 oChainRender.add({ 10448 method: function(oArg) { 10449 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) { 10450 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED); 10451 } 10452 oArg.rowIndex++; 10453 }, 10454 scope: this, 10455 iterations:allRows.length, 10456 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()} 10457 }); 10458 10459 this._clearTrTemplateEl(); 10460 10461 this._elTbody.style.display = "none"; 10462 this._runRenderChain(); 10463 this._elTbody.style.display = ""; 10464 10465 this.fireEvent("columnUnselectEvent",{column:oColumn}); 10466 } 10467 else { 10468 } 10469 } 10470 }, 10471 10472 /** 10473 * Returns an array selected Column instances. 10474 * 10475 * @method getSelectedColumns 10476 * @return {YAHOO.widget.Column[]} Array of Column instances. 10477 */ 10478 getSelectedColumns : function(oColumn) { 10479 var selectedColumns = []; 10480 var aKeys = this._oColumnSet.keys; 10481 for(var i=0,len=aKeys.length; i<len; i++) { 10482 if(aKeys[i].selected) { 10483 selectedColumns[selectedColumns.length] = aKeys[i]; 10484 } 10485 } 10486 return selectedColumns; 10487 }, 10488 10489 /** 10490 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column. 10491 * NOTE: You cannot highlight/unhighlight nested Columns. You can only 10492 * highlight/unhighlight non-nested Columns, and bottom-level key Columns. 10493 * 10494 * @method highlightColumn 10495 * @param column {HTMLElement | String | Number} DOM reference or ID string to a 10496 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index. 10497 */ 10498 highlightColumn : function(column) { 10499 var oColumn = this.getColumn(column); 10500 // Only bottom-level Columns can get highlighted 10501 if(oColumn && (oColumn.getKeyIndex() !== null)) { 10502 // Update head cell 10503 var elTh = oColumn.getThEl(); 10504 Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED); 10505 10506 // Update body cells 10507 var allRows = this.getTbodyEl().rows; 10508 var oChainRender = this._oChainRender; 10509 oChainRender.add({ 10510 method: function(oArg) { 10511 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) { 10512 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED); 10513 } 10514 oArg.rowIndex++; 10515 }, 10516 scope: this, 10517 iterations:allRows.length, 10518 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}, 10519 timeout: -1 10520 }); 10521 this._elTbody.style.display = "none"; 10522 this._runRenderChain(); 10523 this._elTbody.style.display = ""; 10524 10525 this.fireEvent("columnHighlightEvent",{column:oColumn}); 10526 } 10527 else { 10528 } 10529 }, 10530 10531 /** 10532 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column. 10533 * NOTE: You cannot highlight/unhighlight nested Columns. You can only 10534 * highlight/unhighlight non-nested Columns, and bottom-level key Columns. 10535 * 10536 * @method unhighlightColumn 10537 * @param column {HTMLElement | String | Number} DOM reference or ID string to a 10538 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index. 10539 */ 10540 unhighlightColumn : function(column) { 10541 var oColumn = this.getColumn(column); 10542 // Only bottom-level Columns can get highlighted 10543 if(oColumn && (oColumn.getKeyIndex() !== null)) { 10544 // Update head cell 10545 var elTh = oColumn.getThEl(); 10546 Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED); 10547 10548 // Update body cells 10549 var allRows = this.getTbodyEl().rows; 10550 var oChainRender = this._oChainRender; 10551 oChainRender.add({ 10552 method: function(oArg) { 10553 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) { 10554 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED); 10555 } 10556 oArg.rowIndex++; 10557 }, 10558 scope: this, 10559 iterations:allRows.length, 10560 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}, 10561 timeout: -1 10562 }); 10563 this._elTbody.style.display = "none"; 10564 this._runRenderChain(); 10565 this._elTbody.style.display = ""; 10566 10567 this.fireEvent("columnUnhighlightEvent",{column:oColumn}); 10568 } 10569 else { 10570 } 10571 }, 10572 10573 10574 10575 10576 10577 10578 10579 10580 10581 10582 10583 10584 10585 10586 10587 10588 10589 10590 10591 10592 10593 10594 10595 10596 10597 10598 10599 10600 10601 10602 10603 10604 10605 10606 10607 10608 10609 10610 10611 10612 10613 10614 10615 10616 // ROW FUNCTIONS 10617 10618 /** 10619 * Adds one new Record of data into the RecordSet at the index if given, 10620 * otherwise at the end. If the new Record is in page view, the 10621 * corresponding DOM elements are also updated. 10622 * 10623 * @method addRow 10624 * @param oData {Object} Object literal of data for the row. 10625 * @param index {Number} (optional) RecordSet position index at which to add data. 10626 */ 10627 addRow : function(oData, index) { 10628 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) { 10629 return; 10630 } 10631 10632 if(oData && lang.isObject(oData)) { 10633 var oRecord = this._oRecordSet.addRecord(oData, index); 10634 if(oRecord) { 10635 var recIndex; 10636 var oPaginator = this.get('paginator'); 10637 10638 // Paginated 10639 if (oPaginator) { 10640 // Update the paginator's totalRecords 10641 var totalRecords = oPaginator.get('totalRecords'); 10642 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) { 10643 oPaginator.set('totalRecords',totalRecords + 1); 10644 } 10645 10646 recIndex = this.getRecordIndex(oRecord); 10647 var endRecIndex = (oPaginator.getPageRecords())[1]; 10648 10649 // New record affects the view 10650 if (recIndex <= endRecIndex) { 10651 // Defer UI updates to the render method 10652 this.render(); 10653 } 10654 10655 this.fireEvent("rowAddEvent", {record:oRecord}); 10656 return; 10657 } 10658 // Not paginated 10659 else { 10660 recIndex = this.getRecordIndex(oRecord); 10661 if(lang.isNumber(recIndex)) { 10662 // Add the TR element 10663 this._oChainRender.add({ 10664 method: function(oArg) { 10665 if((this instanceof DT) && this._sId) { 10666 var oRecord = oArg.record; 10667 var recIndex = oArg.recIndex; 10668 var elNewTr = this._addTrEl(oRecord); 10669 if(elNewTr) { 10670 var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null; 10671 this._elTbody.insertBefore(elNewTr, elNext); 10672 10673 // Set FIRST/LAST 10674 if(recIndex === 0) { 10675 this._setFirstRow(); 10676 } 10677 if(elNext === null) { 10678 this._setLastRow(); 10679 } 10680 // Set EVEN/ODD 10681 this._setRowStripes(); 10682 10683 this.hideTableMessage(); 10684 10685 this.fireEvent("rowAddEvent", {record:oRecord}); 10686 } 10687 } 10688 }, 10689 argument: {record: oRecord, recIndex: recIndex}, 10690 scope: this, 10691 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1 10692 }); 10693 this._runRenderChain(); 10694 return; 10695 } 10696 } 10697 } 10698 } 10699 }, 10700 10701 /** 10702 * Convenience method to add multiple rows. 10703 * 10704 * @method addRows 10705 * @param aData {Object[]} Array of object literal data for the rows. 10706 * @param index {Number} (optional) RecordSet position index at which to add data. 10707 */ 10708 addRows : function(aData, index) { 10709 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) { 10710 return; 10711 } 10712 10713 if(lang.isArray(aData)) { 10714 var aRecords = this._oRecordSet.addRecords(aData, index); 10715 if(aRecords) { 10716 var recIndex = this.getRecordIndex(aRecords[0]); 10717 10718 // Paginated 10719 var oPaginator = this.get('paginator'); 10720 if (oPaginator) { 10721 // Update the paginator's totalRecords 10722 var totalRecords = oPaginator.get('totalRecords'); 10723 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) { 10724 oPaginator.set('totalRecords',totalRecords + aRecords.length); 10725 } 10726 10727 var endRecIndex = (oPaginator.getPageRecords())[1]; 10728 10729 // At least one of the new records affects the view 10730 if (recIndex <= endRecIndex) { 10731 this.render(); 10732 } 10733 10734 this.fireEvent("rowsAddEvent", {records:aRecords}); 10735 return; 10736 } 10737 // Not paginated 10738 else { 10739 // Add the TR elements 10740 var loopN = this.get("renderLoopSize"); 10741 var loopEnd = recIndex + aData.length; 10742 var nRowsNeeded = (loopEnd - recIndex); // how many needed 10743 var isLast = (recIndex >= this._elTbody.rows.length); 10744 this._oChainRender.add({ 10745 method: function(oArg) { 10746 if((this instanceof DT) && this._sId) { 10747 var aRecords = oArg.aRecords, 10748 i = oArg.nCurrentRow, 10749 j = oArg.nCurrentRecord, 10750 len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd, 10751 df = document.createDocumentFragment(), 10752 elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null; 10753 for(; i < len; i++, j++) { 10754 df.appendChild(this._addTrEl(aRecords[j])); 10755 } 10756 this._elTbody.insertBefore(df, elNext); 10757 oArg.nCurrentRow = i; 10758 oArg.nCurrentRecord = j; 10759 } 10760 }, 10761 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1, 10762 argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords}, 10763 scope: this, 10764 timeout: (loopN > 0) ? 0 : -1 10765 }); 10766 this._oChainRender.add({ 10767 method: function(oArg) { 10768 var recIndex = oArg.recIndex; 10769 // Set FIRST/LAST 10770 if(recIndex === 0) { 10771 this._setFirstRow(); 10772 } 10773 if(oArg.isLast) { 10774 this._setLastRow(); 10775 } 10776 // Set EVEN/ODD 10777 this._setRowStripes(); 10778 10779 this.fireEvent("rowsAddEvent", {records:aRecords}); 10780 }, 10781 argument: {recIndex: recIndex, isLast: isLast}, 10782 scope: this, 10783 timeout: -1 // Needs to run immediately after the DOM insertions above 10784 }); 10785 this._runRenderChain(); 10786 this.hideTableMessage(); 10787 return; 10788 } 10789 } 10790 } 10791 }, 10792 10793 /** 10794 * For the given row, updates the associated Record with the given data. If the 10795 * row is on current page, the corresponding DOM elements are also updated. 10796 * 10797 * @method updateRow 10798 * @param row {YAHOO.widget.Record | Number | HTMLElement | String} 10799 * Which row to update: By Record instance, by Record's RecordSet 10800 * position index, by HTMLElement reference to the TR element, or by ID string 10801 * of the TR element. 10802 * @param oData {Object} Object literal of data for the row. 10803 */ 10804 updateRow : function(row, oData) { 10805 var index = row; 10806 if (!lang.isNumber(index)) { 10807 index = this.getRecordIndex(row); 10808 } 10809 10810 // Update the Record 10811 if(lang.isNumber(index) && (index >= 0)) { 10812 var oRecordSet = this._oRecordSet, 10813 oldRecord = oRecordSet.getRecord(index); 10814 10815 if(oldRecord) { 10816 var updatedRecord = this._oRecordSet.setRecord(oData, index), 10817 elRow = this.getTrEl(oldRecord), 10818 // Copy data from the Record for the event that gets fired later 10819 oldData = oldRecord ? oldRecord.getData() : null; 10820 10821 if(updatedRecord) { 10822 // Update selected rows as necessary 10823 var tracker = this._aSelections || [], 10824 i=0, 10825 oldId = oldRecord.getId(), 10826 newId = updatedRecord.getId(); 10827 for(; i<tracker.length; i++) { 10828 if((tracker[i] === oldId)) { 10829 tracker[i] = newId; 10830 } 10831 else if(tracker[i].recordId === oldId) { 10832 tracker[i].recordId = newId; 10833 } 10834 } 10835 10836 // Update anchors as necessary 10837 if(this._oAnchorRecord && this._oAnchorRecord.getId() === oldId) { 10838 this._oAnchorRecord = updatedRecord; 10839 } 10840 if(this._oAnchorCell && this._oAnchorCell.record.getId() === oldId) { 10841 this._oAnchorCell.record = updatedRecord; 10842 } 10843 10844 // Update the TR only if row is on current page 10845 this._oChainRender.add({ 10846 method: function() { 10847 if((this instanceof DT) && this._sId) { 10848 // Paginated 10849 var oPaginator = this.get('paginator'); 10850 if (oPaginator) { 10851 var pageStartIndex = (oPaginator.getPageRecords())[0], 10852 pageLastIndex = (oPaginator.getPageRecords())[1]; 10853 10854 // At least one of the new records affects the view 10855 if ((index >= pageStartIndex) || (index <= pageLastIndex)) { 10856 this.render(); 10857 } 10858 } 10859 else { 10860 if(elRow) { 10861 this._updateTrEl(elRow, updatedRecord); 10862 } 10863 else { 10864 this.getTbodyEl().appendChild(this._addTrEl(updatedRecord)); 10865 } 10866 } 10867 this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData}); 10868 } 10869 }, 10870 scope: this, 10871 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1 10872 }); 10873 this._runRenderChain(); 10874 return; 10875 } 10876 } 10877 } 10878 return; 10879 }, 10880 10881 /** 10882 * Starting with the given row, updates associated Records with the given data. 10883 * The number of rows to update are determined by the array of data provided. 10884 * Undefined data (i.e., not an object literal) causes a row to be skipped. If 10885 * any of the rows are on current page, the corresponding DOM elements are also 10886 * updated. 10887 * 10888 * @method updateRows 10889 * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String} 10890 * Starting row to update: By Record instance, by Record's RecordSet 10891 * position index, by HTMLElement reference to the TR element, or by ID string 10892 * of the TR element. 10893 * @param aData {Object[]} Array of object literal of data for the rows. 10894 */ 10895 updateRows : function(startrow, aData) { 10896 if(lang.isArray(aData)) { 10897 var startIndex = startrow, 10898 oRecordSet = this._oRecordSet, 10899 lastRowIndex = oRecordSet.getLength(); 10900 10901 if (!lang.isNumber(startrow)) { 10902 startIndex = this.getRecordIndex(startrow); 10903 } 10904 10905 if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) { 10906 var lastIndex = startIndex + aData.length, 10907 aOldRecords = oRecordSet.getRecords(startIndex, aData.length), 10908 aNewRecords = oRecordSet.setRecords(aData, startIndex); 10909 if(aNewRecords) { 10910 var tracker = this._aSelections || [], 10911 i=0, j, newRecord, newId, oldId, 10912 anchorRecord = this._oAnchorRecord ? this._oAnchorRecord.getId() : null, 10913 anchorCell = this._oAnchorCell ? this._oAnchorCell.record.getId() : null; 10914 for(; i<aOldRecords.length; i++) { 10915 oldId = aOldRecords[i].getId(); 10916 newRecord = aNewRecords[i]; 10917 newId = newRecord.getId(); 10918 10919 // Update selected rows as necessary 10920 for(j=0; j<tracker.length; j++) { 10921 if((tracker[j] === oldId)) { 10922 tracker[j] = newId; 10923 } 10924 else if(tracker[j].recordId === oldId) { 10925 tracker[j].recordId = newId; 10926 } 10927 } 10928 10929 // Update anchors as necessary 10930 if(anchorRecord && anchorRecord === oldId) { 10931 this._oAnchorRecord = newRecord; 10932 } 10933 if(anchorCell && anchorCell === oldId) { 10934 this._oAnchorCell.record = newRecord; 10935 } 10936 } 10937 10938 // Paginated 10939 var oPaginator = this.get('paginator'); 10940 if (oPaginator) { 10941 var pageStartIndex = (oPaginator.getPageRecords())[0], 10942 pageLastIndex = (oPaginator.getPageRecords())[1]; 10943 10944 // At least one of the new records affects the view 10945 if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) { 10946 this.render(); 10947 } 10948 10949 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords}); 10950 return; 10951 } 10952 // Not paginated 10953 else { 10954 // Update the TR elements 10955 var loopN = this.get("renderLoopSize"), 10956 rowCount = aData.length, // how many needed 10957 isLast = (lastIndex >= lastRowIndex), 10958 isAdding = (lastIndex > lastRowIndex); 10959 10960 this._oChainRender.add({ 10961 method: function(oArg) { 10962 if((this instanceof DT) && this._sId) { 10963 var aRecords = oArg.aRecords, 10964 i = oArg.nCurrentRow, 10965 j = oArg.nDataPointer, 10966 len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length; 10967 10968 for(; i < len; i++,j++) { 10969 if(isAdding && (i>=lastRowIndex)) { 10970 this._elTbody.appendChild(this._addTrEl(aRecords[j])); 10971 } 10972 else { 10973 this._updateTrEl(this._elTbody.rows[i], aRecords[j]); 10974 } 10975 } 10976 oArg.nCurrentRow = i; 10977 oArg.nDataPointer = j; 10978 } 10979 }, 10980 iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1, 10981 argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding}, 10982 scope: this, 10983 timeout: (loopN > 0) ? 0 : -1 10984 }); 10985 this._oChainRender.add({ 10986 method: function(oArg) { 10987 var recIndex = oArg.recIndex; 10988 // Set FIRST/LAST 10989 if(recIndex === 0) { 10990 this._setFirstRow(); 10991 } 10992 if(oArg.isLast) { 10993 this._setLastRow(); 10994 } 10995 // Set EVEN/ODD 10996 this._setRowStripes(); 10997 10998 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords}); 10999 }, 11000 argument: {recIndex: startIndex, isLast: isLast}, 11001 scope: this, 11002 timeout: -1 // Needs to run immediately after the DOM insertions above 11003 }); 11004 this._runRenderChain(); 11005 this.hideTableMessage(); 11006 return; 11007 } 11008 } 11009 } 11010 } 11011 }, 11012 11013 /** 11014 * Deletes the given row's Record from the RecordSet. If the row is on current page, 11015 * the corresponding DOM elements are also deleted. 11016 * 11017 * @method deleteRow 11018 * @param row {HTMLElement | String | Number} DOM element reference or ID string 11019 * to DataTable page element or RecordSet index. 11020 */ 11021 deleteRow : function(row) { 11022 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row); 11023 if(lang.isNumber(nRecordIndex)) { 11024 var oRecord = this.getRecord(nRecordIndex); 11025 if(oRecord) { 11026 var nTrIndex = this.getTrIndex(nRecordIndex); 11027 11028 // Remove from selection tracker if there 11029 var sRecordId = oRecord.getId(); 11030 var tracker = this._aSelections || []; 11031 for(var j=tracker.length-1; j>-1; j--) { 11032 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) || 11033 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) { 11034 tracker.splice(j,1); 11035 } 11036 } 11037 11038 // Delete Record from RecordSet 11039 var oData = this._oRecordSet.deleteRecord(nRecordIndex); 11040 11041 // Update the UI 11042 if(oData) { 11043 // If paginated and the deleted row was on this or a prior page, just 11044 // re-render 11045 var oPaginator = this.get('paginator'); 11046 if (oPaginator) { 11047 // Update the paginator's totalRecords 11048 var totalRecords = oPaginator.get('totalRecords'), 11049 // must capture before the totalRecords change because 11050 // Paginator shifts to previous page automatically 11051 rng = oPaginator.getPageRecords(); 11052 11053 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) { 11054 oPaginator.set('totalRecords',totalRecords - 1); 11055 } 11056 11057 // The deleted record was on this or a prior page, re-render 11058 if (!rng || nRecordIndex <= rng[1]) { 11059 this.render(); 11060 } 11061 11062 this._oChainRender.add({ 11063 method: function() { 11064 if((this instanceof DT) && this._sId) { 11065 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex}); 11066 } 11067 }, 11068 scope: this, 11069 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1 11070 }); 11071 this._runRenderChain(); 11072 } 11073 // Not paginated 11074 else { 11075 if(lang.isNumber(nTrIndex)) { 11076 this._oChainRender.add({ 11077 method: function() { 11078 if((this instanceof DT) && this._sId) { 11079 var isLast = (nRecordIndex === this._oRecordSet.getLength());//(nTrIndex == this.getLastTrEl().sectionRowIndex); 11080 this._deleteTrEl(nTrIndex); 11081 11082 // Post-delete tasks 11083 if(this._elTbody.rows.length > 0) { 11084 // Set FIRST/LAST 11085 if(nTrIndex === 0) { 11086 this._setFirstRow(); 11087 } 11088 if(isLast) { 11089 this._setLastRow(); 11090 } 11091 // Set EVEN/ODD 11092 if(nTrIndex != this._elTbody.rows.length) { 11093 this._setRowStripes(nTrIndex); 11094 } 11095 } 11096 11097 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex}); 11098 } 11099 }, 11100 scope: this, 11101 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1 11102 }); 11103 this._runRenderChain(); 11104 return; 11105 } 11106 } 11107 } 11108 } 11109 } 11110 return null; 11111 }, 11112 11113 /** 11114 * Convenience method to delete multiple rows. 11115 * 11116 * @method deleteRows 11117 * @param row {HTMLElement | String | Number} DOM element reference or ID string 11118 * to DataTable page element or RecordSet index. 11119 * @param count {Number} (optional) How many rows to delete. A negative value 11120 * will delete towards the beginning. 11121 */ 11122 deleteRows : function(row, count) { 11123 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row); 11124 if(lang.isNumber(nRecordIndex)) { 11125 var oRecord = this.getRecord(nRecordIndex); 11126 if(oRecord) { 11127 var nTrIndex = this.getTrIndex(nRecordIndex); 11128 11129 // Remove from selection tracker if there 11130 var sRecordId = oRecord.getId(); 11131 var tracker = this._aSelections || []; 11132 for(var j=tracker.length-1; j>-1; j--) { 11133 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) || 11134 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) { 11135 tracker.splice(j,1); 11136 } 11137 } 11138 11139 // Delete Record from RecordSet 11140 var highIndex = nRecordIndex; 11141 var lowIndex = nRecordIndex; 11142 11143 // Validate count and account for negative value 11144 if(count && lang.isNumber(count)) { 11145 highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex; 11146 lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1; 11147 count = (count > 0) ? count : count*-1; 11148 if(lowIndex < 0) { 11149 lowIndex = 0; 11150 count = highIndex - lowIndex + 1; 11151 } 11152 } 11153 else { 11154 count = 1; 11155 } 11156 11157 var aData = this._oRecordSet.deleteRecords(lowIndex, count); 11158 11159 // Update the UI 11160 if(aData) { 11161 var oPaginator = this.get('paginator'), 11162 loopN = this.get("renderLoopSize"); 11163 // If paginated and the deleted row was on this or a prior page, just 11164 // re-render 11165 if (oPaginator) { 11166 // Update the paginator's totalRecords 11167 var totalRecords = oPaginator.get('totalRecords'), 11168 // must capture before the totalRecords change because 11169 // Paginator shifts to previous page automatically 11170 rng = oPaginator.getPageRecords(); 11171 11172 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) { 11173 oPaginator.set('totalRecords',totalRecords - aData.length); 11174 } 11175 11176 // The records were on this or a prior page, re-render 11177 if (!rng || lowIndex <= rng[1]) { 11178 this.render(); 11179 } 11180 11181 this._oChainRender.add({ 11182 method: function(oArg) { 11183 if((this instanceof DT) && this._sId) { 11184 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count}); 11185 } 11186 }, 11187 scope: this, 11188 timeout: (loopN > 0) ? 0 : -1 11189 }); 11190 this._runRenderChain(); 11191 return; 11192 } 11193 // Not paginated 11194 else { 11195 if(lang.isNumber(nTrIndex)) { 11196 // Delete the TR elements starting with highest index 11197 var loopEnd = lowIndex; 11198 var nRowsNeeded = count; // how many needed 11199 this._oChainRender.add({ 11200 method: function(oArg) { 11201 if((this instanceof DT) && this._sId) { 11202 var i = oArg.nCurrentRow, 11203 len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1; 11204 for(; i>len; --i) { 11205 this._deleteTrEl(i); 11206 } 11207 oArg.nCurrentRow = i; 11208 } 11209 }, 11210 iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1, 11211 argument: {nCurrentRow:highIndex}, 11212 scope: this, 11213 timeout: (loopN > 0) ? 0 : -1 11214 }); 11215 this._oChainRender.add({ 11216 method: function() { 11217 // Post-delete tasks 11218 if(this._elTbody.rows.length > 0) { 11219 this._setFirstRow(); 11220 this._setLastRow(); 11221 this._setRowStripes(); 11222 } 11223 11224 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count}); 11225 }, 11226 scope: this, 11227 timeout: -1 // Needs to run immediately after the DOM deletions above 11228 }); 11229 this._runRenderChain(); 11230 return; 11231 } 11232 } 11233 } 11234 } 11235 } 11236 return null; 11237 }, 11238 11239 11240 11241 11242 11243 11244 11245 11246 11247 11248 11249 11250 11251 11252 11253 11254 11255 11256 11257 11258 11259 11260 11261 11262 11263 11264 11265 11266 11267 11268 11269 11270 11271 11272 11273 11274 11275 11276 11277 11278 11279 11280 11281 11282 11283 11284 // CELL FUNCTIONS 11285 11286 /** 11287 * Outputs markup into the given TD based on given Record. 11288 * 11289 * @method formatCell 11290 * @param elLiner {HTMLElement} The liner DIV element within the TD. 11291 * @param oRecord {YAHOO.widget.Record} (Optional) Record instance. 11292 * @param oColumn {YAHOO.widget.Column} (Optional) Column instance. 11293 */ 11294 formatCell : function(elLiner, oRecord, oColumn) { 11295 if(!oRecord) { 11296 oRecord = this.getRecord(elLiner); 11297 } 11298 if(!oColumn) { 11299 oColumn = this.getColumn(this.getCellIndex(elLiner.parentNode)); 11300 } 11301 11302 if(oRecord && oColumn) { 11303 var sField = oColumn.field; 11304 var oData = oRecord.getData(sField); 11305 11306 var fnFormatter = typeof oColumn.formatter === 'function' ? 11307 oColumn.formatter : 11308 DT.Formatter[oColumn.formatter+''] || 11309 DT.Formatter.defaultFormatter; 11310 11311 // Apply special formatter 11312 if(fnFormatter) { 11313 fnFormatter.call(this, elLiner, oRecord, oColumn, oData); 11314 } 11315 else { 11316 elLiner.innerHTML = oData; 11317 } 11318 11319 this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elLiner}); 11320 } 11321 else { 11322 } 11323 }, 11324 11325 /** 11326 * For the given row and column, updates the Record with the given data. If the 11327 * cell is on current page, the corresponding DOM elements are also updated. 11328 * 11329 * @method updateCell 11330 * @param oRecord {YAHOO.widget.Record} Record instance. 11331 * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index. 11332 * @param oData {Object} New data value for the cell. 11333 * @param skipRender {Boolean} Skips render step. Editors that update multiple 11334 * cells in ScrollingDataTable should render only on the last call to updateCell(). 11335 */ 11336 updateCell : function(oRecord, oColumn, oData, skipRender) { 11337 // Validate Column and Record 11338 oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn); 11339 if(oColumn && oColumn.getField() && (oRecord instanceof YAHOO.widget.Record)) { 11340 var sKey = oColumn.getField(), 11341 11342 // Copy data from the Record for the event that gets fired later 11343 //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData()); 11344 oldData = oRecord.getData(sKey); 11345 11346 // Update Record with new data 11347 this._oRecordSet.updateRecordValue(oRecord, sKey, oData); 11348 11349 // Update the TD only if row is on current page 11350 var elTd = this.getTdEl({record: oRecord, column: oColumn}); 11351 if(elTd) { 11352 this._oChainRender.add({ 11353 method: function() { 11354 if((this instanceof DT) && this._sId) { 11355 this.formatCell(elTd.firstChild, oRecord, oColumn); 11356 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData}); 11357 } 11358 }, 11359 scope: this, 11360 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1 11361 }); 11362 // Bug 2529024 11363 if(!skipRender) { 11364 this._runRenderChain(); 11365 } 11366 } 11367 else { 11368 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData}); 11369 } 11370 } 11371 }, 11372 11373 11374 11375 11376 11377 11378 11379 11380 11381 11382 11383 11384 11385 11386 11387 11388 11389 11390 11391 11392 11393 11394 11395 11396 11397 11398 11399 11400 11401 11402 11403 11404 11405 11406 11407 11408 11409 11410 11411 11412 11413 11414 11415 11416 11417 11418 11419 11420 11421 11422 11423 // PAGINATION 11424 /** 11425 * Method executed during set() operation for the "paginator" attribute. 11426 * Adds and/or severs event listeners between DataTable and Paginator 11427 * 11428 * @method _updatePaginator 11429 * @param newPag {Paginator} Paginator instance (or null) for DataTable to use 11430 * @private 11431 */ 11432 _updatePaginator : function (newPag) { 11433 var oldPag = this.get('paginator'); 11434 if (oldPag && newPag !== oldPag) { 11435 oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true); 11436 } 11437 if (newPag) { 11438 newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true); 11439 } 11440 }, 11441 11442 /** 11443 * Update the UI infrastructure in response to a "paginator" attribute change. 11444 * 11445 * @method _handlePaginatorChange 11446 * @param e {Object} Change event object containing keys 'type','newValue', 11447 * and 'prevValue' 11448 * @private 11449 */ 11450 _handlePaginatorChange : function (e) { 11451 if (e.prevValue === e.newValue) { return; } 11452 11453 var newPag = e.newValue, 11454 oldPag = e.prevValue, 11455 containers = this._defaultPaginatorContainers(); 11456 11457 if (oldPag) { 11458 if (oldPag.getContainerNodes()[0] == containers[0]) { 11459 oldPag.set('containers',[]); 11460 } 11461 oldPag.destroy(); 11462 11463 // Convenience: share the default containers if possible. 11464 // Otherwise, remove the default containers from the DOM. 11465 if (containers[0]) { 11466 if (newPag && !newPag.getContainerNodes().length) { 11467 newPag.set('containers',containers); 11468 } else { 11469 // No new Paginator to use existing containers, OR new 11470 // Paginator has configured containers. 11471 for (var i = containers.length - 1; i >= 0; --i) { 11472 if (containers[i]) { 11473 containers[i].parentNode.removeChild(containers[i]); 11474 } 11475 } 11476 } 11477 } 11478 } 11479 11480 if (!this._bInit) { 11481 this.render(); 11482 11483 } 11484 11485 if (newPag) { 11486 this.renderPaginator(); 11487 } 11488 11489 }, 11490 11491 /** 11492 * Returns the default containers used for Paginators. If create param is 11493 * passed, the containers will be created and added to the DataTable container. 11494 * 11495 * @method _defaultPaginatorContainers 11496 * @param create {boolean} Create the default containers if not found 11497 * @private 11498 */ 11499 _defaultPaginatorContainers : function (create) { 11500 var above_id = this._sId + '-paginator0', 11501 below_id = this._sId + '-paginator1', 11502 above = Dom.get(above_id), 11503 below = Dom.get(below_id); 11504 11505 if (create && (!above || !below)) { 11506 // One above and one below the table 11507 if (!above) { 11508 above = document.createElement('div'); 11509 above.id = above_id; 11510 Dom.addClass(above, DT.CLASS_PAGINATOR); 11511 11512 this._elContainer.insertBefore(above,this._elContainer.firstChild); 11513 } 11514 11515 if (!below) { 11516 below = document.createElement('div'); 11517 below.id = below_id; 11518 Dom.addClass(below, DT.CLASS_PAGINATOR); 11519 11520 this._elContainer.appendChild(below); 11521 } 11522 } 11523 11524 return [above,below]; 11525 }, 11526 11527 /** 11528 * Calls Paginator's destroy() method 11529 * 11530 * @method _destroyPaginator 11531 * @private 11532 */ 11533 _destroyPaginator : function () { 11534 var oldPag = this.get('paginator'); 11535 if (oldPag) { 11536 oldPag.destroy(); 11537 } 11538 }, 11539 11540 /** 11541 * Renders the Paginator to the DataTable UI 11542 * 11543 * @method renderPaginator 11544 */ 11545 renderPaginator : function () { 11546 var pag = this.get("paginator"); 11547 if (!pag) { return; } 11548 11549 // Add the containers if the Paginator is not configured with containers 11550 if (!pag.getContainerNodes().length) { 11551 pag.set('containers',this._defaultPaginatorContainers(true)); 11552 } 11553 11554 pag.render(); 11555 }, 11556 11557 /** 11558 * Overridable method gives implementers a hook to show loading message before 11559 * changing Paginator value. 11560 * 11561 * @method doBeforePaginatorChange 11562 * @param oPaginatorState {Object} An object literal describing the proposed pagination state. 11563 * @return {Boolean} Return true to continue changing Paginator value. 11564 */ 11565 doBeforePaginatorChange : function(oPaginatorState) { 11566 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING); 11567 return true; 11568 }, 11569 11570 /** 11571 * Responds to new Pagination states. By default, updates the UI to reflect the 11572 * new state. If "dynamicData" is true, current selections are purged before 11573 * a request is sent to the DataSource for data for the new state (using the 11574 * request returned by "generateRequest()"). 11575 * 11576 * @method onPaginatorChangeRequest 11577 * @param oPaginatorState {Object} An object literal describing the proposed pagination state. 11578 */ 11579 onPaginatorChangeRequest : function (oPaginatorState) { 11580 var ok = this.doBeforePaginatorChange(oPaginatorState); 11581 if(ok) { 11582 // Server-side pagination 11583 if(this.get("dynamicData")) { 11584 // Get the current state 11585 var oState = this.getState(); 11586 11587 // Update pagination values 11588 oState.pagination = oPaginatorState; 11589 11590 // Get the request for the new state 11591 var request = this.get("generateRequest")(oState, this); 11592 11593 // Purge selections 11594 this.unselectAllRows(); 11595 this.unselectAllCells(); 11596 11597 // Get the new data from the server 11598 var callback = { 11599 success : this.onDataReturnSetRows, 11600 failure : this.onDataReturnSetRows, 11601 argument : oState, // Pass along the new state to the callback 11602 scope : this 11603 }; 11604 this._oDataSource.sendRequest(request, callback); 11605 } 11606 // Client-side pagination 11607 else { 11608 // Set the core pagination values silently (the second param) 11609 // to avoid looping back through the changeRequest mechanism 11610 oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true); 11611 oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true); 11612 11613 // Update the UI 11614 this.render(); 11615 } 11616 } 11617 else { 11618 } 11619 }, 11620 11621 11622 11623 11624 11625 11626 11627 11628 11629 11630 11631 11632 11633 11634 11635 11636 11637 11638 11639 11640 11641 11642 11643 11644 11645 11646 11647 11648 11649 11650 11651 11652 11653 11654 11655 11656 11657 11658 11659 11660 11661 11662 11663 11664 11665 11666 11667 11668 11669 11670 // SELECTION/HIGHLIGHTING 11671 11672 /* 11673 * Reference to last highlighted cell element 11674 * 11675 * @property _elLastHighlightedTd 11676 * @type HTMLElement 11677 * @private 11678 */ 11679 _elLastHighlightedTd : null, 11680 11681 /* 11682 * ID string of last highlighted row element 11683 * 11684 * @property _sLastHighlightedTrElId 11685 * @type String 11686 * @private 11687 */ 11688 //_sLastHighlightedTrElId : null, 11689 11690 /** 11691 * Array to track row selections (by sRecordId) and/or cell selections 11692 * (by {recordId:sRecordId, columnKey:sColumnKey}) 11693 * 11694 * @property _aSelections 11695 * @type Object[] 11696 * @private 11697 */ 11698 _aSelections : null, 11699 11700 /** 11701 * Record instance of the row selection anchor. 11702 * 11703 * @property _oAnchorRecord 11704 * @type YAHOO.widget.Record 11705 * @private 11706 */ 11707 _oAnchorRecord : null, 11708 11709 /** 11710 * Object literal representing cell selection anchor: 11711 * {recordId:sRecordId, columnKey:sColumnKey}. 11712 * 11713 * @property _oAnchorCell 11714 * @type Object 11715 * @private 11716 */ 11717 _oAnchorCell : null, 11718 11719 /** 11720 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED 11721 * from all TR elements on the page. 11722 * 11723 * @method _unselectAllTrEls 11724 * @private 11725 */ 11726 _unselectAllTrEls : function() { 11727 var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody); 11728 Dom.removeClass(selectedRows, DT.CLASS_SELECTED); 11729 }, 11730 11731 /** 11732 * Returns object literal of values that represent the selection trigger. Used 11733 * to determine selection behavior resulting from a key event. 11734 * 11735 * @method _getSelectionTrigger 11736 * @private 11737 */ 11738 _getSelectionTrigger : function() { 11739 var sMode = this.get("selectionMode"); 11740 var oTrigger = {}; 11741 var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex; 11742 11743 // Cell mode 11744 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) { 11745 oTriggerCell = this.getLastSelectedCell(); 11746 // No selected cells found 11747 if(!oTriggerCell) { 11748 return null; 11749 } 11750 else { 11751 oTriggerRecord = this.getRecord(oTriggerCell.recordId); 11752 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord); 11753 elTriggerRow = this.getTrEl(oTriggerRecord); 11754 nTriggerTrIndex = this.getTrIndex(elTriggerRow); 11755 11756 // Selected cell not found on this page 11757 if(nTriggerTrIndex === null) { 11758 return null; 11759 } 11760 else { 11761 oTrigger.record = oTriggerRecord; 11762 oTrigger.recordIndex = nTriggerRecordIndex; 11763 oTrigger.el = this.getTdEl(oTriggerCell); 11764 oTrigger.trIndex = nTriggerTrIndex; 11765 oTrigger.column = this.getColumn(oTriggerCell.columnKey); 11766 oTrigger.colKeyIndex = oTrigger.column.getKeyIndex(); 11767 oTrigger.cell = oTriggerCell; 11768 return oTrigger; 11769 } 11770 } 11771 } 11772 // Row mode 11773 else { 11774 oTriggerRecord = this.getLastSelectedRecord(); 11775 // No selected rows found 11776 if(!oTriggerRecord) { 11777 return null; 11778 } 11779 else { 11780 // Selected row found, but is it on current page? 11781 oTriggerRecord = this.getRecord(oTriggerRecord); 11782 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord); 11783 elTriggerRow = this.getTrEl(oTriggerRecord); 11784 nTriggerTrIndex = this.getTrIndex(elTriggerRow); 11785 11786 // Selected row not found on this page 11787 if(nTriggerTrIndex === null) { 11788 return null; 11789 } 11790 else { 11791 oTrigger.record = oTriggerRecord; 11792 oTrigger.recordIndex = nTriggerRecordIndex; 11793 oTrigger.el = elTriggerRow; 11794 oTrigger.trIndex = nTriggerTrIndex; 11795 return oTrigger; 11796 } 11797 } 11798 } 11799 }, 11800 11801 /** 11802 * Returns object literal of values that represent the selection anchor. Used 11803 * to determine selection behavior resulting from a user event. 11804 * 11805 * @method _getSelectionAnchor 11806 * @param oTrigger {Object} (Optional) Object literal of selection trigger values 11807 * (for key events). 11808 * @private 11809 */ 11810 _getSelectionAnchor : function(oTrigger) { 11811 var sMode = this.get("selectionMode"); 11812 var oAnchor = {}; 11813 var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex; 11814 11815 // Cell mode 11816 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) { 11817 // Validate anchor cell 11818 var oAnchorCell = this._oAnchorCell; 11819 if(!oAnchorCell) { 11820 if(oTrigger) { 11821 oAnchorCell = this._oAnchorCell = oTrigger.cell; 11822 } 11823 else { 11824 return null; 11825 } 11826 } 11827 oAnchorRecord = this._oAnchorCell.record; 11828 nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord); 11829 nAnchorTrIndex = this.getTrIndex(oAnchorRecord); 11830 // If anchor cell is not on this page... 11831 if(nAnchorTrIndex === null) { 11832 // ...set TR index equal to top TR 11833 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) { 11834 nAnchorTrIndex = 0; 11835 } 11836 // ...set TR index equal to bottom TR 11837 else { 11838 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl()); 11839 } 11840 } 11841 11842 oAnchor.record = oAnchorRecord; 11843 oAnchor.recordIndex = nAnchorRecordIndex; 11844 oAnchor.trIndex = nAnchorTrIndex; 11845 oAnchor.column = this._oAnchorCell.column; 11846 oAnchor.colKeyIndex = oAnchor.column.getKeyIndex(); 11847 oAnchor.cell = oAnchorCell; 11848 return oAnchor; 11849 } 11850 // Row mode 11851 else { 11852 oAnchorRecord = this._oAnchorRecord; 11853 if(!oAnchorRecord) { 11854 if(oTrigger) { 11855 oAnchorRecord = this._oAnchorRecord = oTrigger.record; 11856 } 11857 else { 11858 return null; 11859 } 11860 } 11861 11862 nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord); 11863 nAnchorTrIndex = this.getTrIndex(oAnchorRecord); 11864 // If anchor row is not on this page... 11865 if(nAnchorTrIndex === null) { 11866 // ...set TR index equal to top TR 11867 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) { 11868 nAnchorTrIndex = 0; 11869 } 11870 // ...set TR index equal to bottom TR 11871 else { 11872 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl()); 11873 } 11874 } 11875 11876 oAnchor.record = oAnchorRecord; 11877 oAnchor.recordIndex = nAnchorRecordIndex; 11878 oAnchor.trIndex = nAnchorTrIndex; 11879 return oAnchor; 11880 } 11881 }, 11882 11883 /** 11884 * Determines selection behavior resulting from a mouse event when selection mode 11885 * is set to "standard". 11886 * 11887 * @method _handleStandardSelectionByMouse 11888 * @param oArgs.event {HTMLEvent} Event object. 11889 * @param oArgs.target {HTMLElement} Target element. 11890 * @private 11891 */ 11892 _handleStandardSelectionByMouse : function(oArgs) { 11893 var elTarget = oArgs.target; 11894 11895 // Validate target row 11896 var elTargetRow = this.getTrEl(elTarget); 11897 if(elTargetRow) { 11898 var e = oArgs.event; 11899 var bSHIFT = e.shiftKey; 11900 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey); 11901 11902 var oTargetRecord = this.getRecord(elTargetRow); 11903 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord); 11904 11905 var oAnchor = this._getSelectionAnchor(); 11906 11907 var i; 11908 11909 // Both SHIFT and CTRL 11910 if(bSHIFT && bCTRL) { 11911 // Validate anchor 11912 if(oAnchor) { 11913 if(this.isSelected(oAnchor.record)) { 11914 // Select all rows between anchor row and target row, including target row 11915 if(oAnchor.recordIndex < nTargetRecordIndex) { 11916 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) { 11917 if(!this.isSelected(i)) { 11918 this.selectRow(i); 11919 } 11920 } 11921 } 11922 // Select all rows between target row and anchor row, including target row 11923 else { 11924 for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) { 11925 if(!this.isSelected(i)) { 11926 this.selectRow(i); 11927 } 11928 } 11929 } 11930 } 11931 else { 11932 // Unselect all rows between anchor row and target row 11933 if(oAnchor.recordIndex < nTargetRecordIndex) { 11934 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) { 11935 if(this.isSelected(i)) { 11936 this.unselectRow(i); 11937 } 11938 } 11939 } 11940 // Unselect all rows between target row and anchor row 11941 else { 11942 for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) { 11943 if(this.isSelected(i)) { 11944 this.unselectRow(i); 11945 } 11946 } 11947 } 11948 // Select the target row 11949 this.selectRow(oTargetRecord); 11950 } 11951 } 11952 // Invalid anchor 11953 else { 11954 // Set anchor 11955 this._oAnchorRecord = oTargetRecord; 11956 11957 // Toggle selection of target 11958 if(this.isSelected(oTargetRecord)) { 11959 this.unselectRow(oTargetRecord); 11960 } 11961 else { 11962 this.selectRow(oTargetRecord); 11963 } 11964 } 11965 } 11966 // Only SHIFT 11967 else if(bSHIFT) { 11968 this.unselectAllRows(); 11969 11970 // Validate anchor 11971 if(oAnchor) { 11972 // Select all rows between anchor row and target row, 11973 // including the anchor row and target row 11974 if(oAnchor.recordIndex < nTargetRecordIndex) { 11975 for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) { 11976 this.selectRow(i); 11977 } 11978 } 11979 // Select all rows between target row and anchor row, 11980 // including the target row and anchor row 11981 else { 11982 for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) { 11983 this.selectRow(i); 11984 } 11985 } 11986 } 11987 // Invalid anchor 11988 else { 11989 // Set anchor 11990 this._oAnchorRecord = oTargetRecord; 11991 11992 // Select target row only 11993 this.selectRow(oTargetRecord); 11994 } 11995 } 11996 // Only CTRL 11997 else if(bCTRL) { 11998 // Set anchor 11999 this._oAnchorRecord = oTargetRecord; 12000 12001 // Toggle selection of target 12002 if(this.isSelected(oTargetRecord)) { 12003 this.unselectRow(oTargetRecord); 12004 } 12005 else { 12006 this.selectRow(oTargetRecord); 12007 } 12008 } 12009 // Neither SHIFT nor CTRL 12010 else { 12011 this._handleSingleSelectionByMouse(oArgs); 12012 return; 12013 } 12014 } 12015 }, 12016 12017 /** 12018 * Determines selection behavior resulting from a key event when selection mode 12019 * is set to "standard". 12020 * 12021 * @method _handleStandardSelectionByKey 12022 * @param e {HTMLEvent} Event object. 12023 * @private 12024 */ 12025 _handleStandardSelectionByKey : function(e) { 12026 var nKey = Ev.getCharCode(e); 12027 12028 if((nKey == 38) || (nKey == 40)) { 12029 var bSHIFT = e.shiftKey; 12030 12031 // Validate trigger 12032 var oTrigger = this._getSelectionTrigger(); 12033 // Arrow selection only works if last selected row is on current page 12034 if(!oTrigger) { 12035 return null; 12036 } 12037 12038 Ev.stopEvent(e); 12039 12040 // Validate anchor 12041 var oAnchor = this._getSelectionAnchor(oTrigger); 12042 12043 // Determine which direction we're going to 12044 if(bSHIFT) { 12045 // Selecting down away from anchor row 12046 if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) { 12047 this.selectRow(this.getNextTrEl(oTrigger.el)); 12048 } 12049 // Selecting up away from anchor row 12050 else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) { 12051 this.selectRow(this.getPreviousTrEl(oTrigger.el)); 12052 } 12053 // Unselect trigger 12054 else { 12055 this.unselectRow(oTrigger.el); 12056 } 12057 } 12058 else { 12059 this._handleSingleSelectionByKey(e); 12060 } 12061 } 12062 }, 12063 12064 /** 12065 * Determines selection behavior resulting from a mouse event when selection mode 12066 * is set to "single". 12067 * 12068 * @method _handleSingleSelectionByMouse 12069 * @param oArgs.event {HTMLEvent} Event object. 12070 * @param oArgs.target {HTMLElement} Target element. 12071 * @private 12072 */ 12073 _handleSingleSelectionByMouse : function(oArgs) { 12074 var elTarget = oArgs.target; 12075 12076 // Validate target row 12077 var elTargetRow = this.getTrEl(elTarget); 12078 if(elTargetRow) { 12079 var oTargetRecord = this.getRecord(elTargetRow); 12080 12081 // Set anchor 12082 this._oAnchorRecord = oTargetRecord; 12083 12084 // Select only target 12085 this.unselectAllRows(); 12086 this.selectRow(oTargetRecord); 12087 } 12088 }, 12089 12090 /** 12091 * Determines selection behavior resulting from a key event when selection mode 12092 * is set to "single". 12093 * 12094 * @method _handleSingleSelectionByKey 12095 * @param e {HTMLEvent} Event object. 12096 * @private 12097 */ 12098 _handleSingleSelectionByKey : function(e) { 12099 var nKey = Ev.getCharCode(e); 12100 12101 if((nKey == 38) || (nKey == 40)) { 12102 // Validate trigger 12103 var oTrigger = this._getSelectionTrigger(); 12104 // Arrow selection only works if last selected row is on current page 12105 if(!oTrigger) { 12106 return null; 12107 } 12108 12109 Ev.stopEvent(e); 12110 12111 // Determine the new row to select 12112 var elNew; 12113 if(nKey == 38) { // arrow up 12114 elNew = this.getPreviousTrEl(oTrigger.el); 12115 12116 // Validate new row 12117 if(elNew === null) { 12118 //TODO: wrap around to last tr on current page 12119 //elNew = this.getLastTrEl(); 12120 12121 //TODO: wrap back to last tr of previous page 12122 12123 // Top row selection is sticky 12124 elNew = this.getFirstTrEl(); 12125 } 12126 } 12127 else if(nKey == 40) { // arrow down 12128 elNew = this.getNextTrEl(oTrigger.el); 12129 12130 // Validate new row 12131 if(elNew === null) { 12132 //TODO: wrap around to first tr on current page 12133 //elNew = this.getFirstTrEl(); 12134 12135 //TODO: wrap forward to first tr of previous page 12136 12137 // Bottom row selection is sticky 12138 elNew = this.getLastTrEl(); 12139 } 12140 } 12141 12142 // Unselect all rows 12143 this.unselectAllRows(); 12144 12145 // Select the new row 12146 this.selectRow(elNew); 12147 12148 // Set new anchor 12149 this._oAnchorRecord = this.getRecord(elNew); 12150 } 12151 }, 12152 12153 /** 12154 * Determines selection behavior resulting from a mouse event when selection mode 12155 * is set to "cellblock". 12156 * 12157 * @method _handleCellBlockSelectionByMouse 12158 * @param oArgs.event {HTMLEvent} Event object. 12159 * @param oArgs.target {HTMLElement} Target element. 12160 * @private 12161 */ 12162 _handleCellBlockSelectionByMouse : function(oArgs) { 12163 var elTarget = oArgs.target; 12164 12165 // Validate target cell 12166 var elTargetCell = this.getTdEl(elTarget); 12167 if(elTargetCell) { 12168 var e = oArgs.event; 12169 var bSHIFT = e.shiftKey; 12170 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey); 12171 12172 var elTargetRow = this.getTrEl(elTargetCell); 12173 var nTargetTrIndex = this.getTrIndex(elTargetRow); 12174 var oTargetColumn = this.getColumn(elTargetCell); 12175 var nTargetColKeyIndex = oTargetColumn.getKeyIndex(); 12176 var oTargetRecord = this.getRecord(elTargetRow); 12177 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord); 12178 var oTargetCell = {record:oTargetRecord, column:oTargetColumn}; 12179 12180 var oAnchor = this._getSelectionAnchor(); 12181 12182 var allRows = this.getTbodyEl().rows; 12183 var startIndex, endIndex, currentRow, i, j; 12184 12185 // Both SHIFT and CTRL 12186 if(bSHIFT && bCTRL) { 12187 12188 // Validate anchor 12189 if(oAnchor) { 12190 // Anchor is selected 12191 if(this.isSelected(oAnchor.cell)) { 12192 // All cells are on the same row 12193 if(oAnchor.recordIndex === nTargetRecordIndex) { 12194 // Select all cells between anchor cell and target cell, including target cell 12195 if(oAnchor.colKeyIndex < nTargetColKeyIndex) { 12196 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) { 12197 this.selectCell(elTargetRow.cells[i]); 12198 } 12199 } 12200 // Select all cells between target cell and anchor cell, including target cell 12201 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) { 12202 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) { 12203 this.selectCell(elTargetRow.cells[i]); 12204 } 12205 } 12206 } 12207 // Anchor row is above target row 12208 else if(oAnchor.recordIndex < nTargetRecordIndex) { 12209 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex); 12210 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex); 12211 12212 // Select all cells from startIndex to endIndex on rows between anchor row and target row 12213 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) { 12214 for(j=startIndex; j<=endIndex; j++) { 12215 this.selectCell(allRows[i].cells[j]); 12216 } 12217 } 12218 } 12219 // Anchor row is below target row 12220 else { 12221 startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex); 12222 endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex); 12223 12224 // Select all cells from startIndex to endIndex on rows between target row and anchor row 12225 for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) { 12226 for(j=endIndex; j>=startIndex; j--) { 12227 this.selectCell(allRows[i].cells[j]); 12228 } 12229 } 12230 } 12231 } 12232 // Anchor cell is unselected 12233 else { 12234 // All cells are on the same row 12235 if(oAnchor.recordIndex === nTargetRecordIndex) { 12236 // Unselect all cells between anchor cell and target cell 12237 if(oAnchor.colKeyIndex < nTargetColKeyIndex) { 12238 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) { 12239 this.unselectCell(elTargetRow.cells[i]); 12240 } 12241 } 12242 // Select all cells between target cell and anchor cell 12243 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) { 12244 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) { 12245 this.unselectCell(elTargetRow.cells[i]); 12246 } 12247 } 12248 } 12249 // Anchor row is above target row 12250 if(oAnchor.recordIndex < nTargetRecordIndex) { 12251 // Unselect all cells from anchor cell to target cell 12252 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) { 12253 currentRow = allRows[i]; 12254 for(j=0; j<currentRow.cells.length; j++) { 12255 // This is the anchor row, only unselect cells after the anchor cell 12256 if(currentRow.sectionRowIndex === oAnchor.trIndex) { 12257 if(j>oAnchor.colKeyIndex) { 12258 this.unselectCell(currentRow.cells[j]); 12259 } 12260 } 12261 // This is the target row, only unelect cells before the target cell 12262 else if(currentRow.sectionRowIndex === nTargetTrIndex) { 12263 if(j<nTargetColKeyIndex) { 12264 this.unselectCell(currentRow.cells[j]); 12265 } 12266 } 12267 // Unselect all cells on this row 12268 else { 12269 this.unselectCell(currentRow.cells[j]); 12270 } 12271 } 12272 } 12273 } 12274 // Anchor row is below target row 12275 else { 12276 // Unselect all cells from target cell to anchor cell 12277 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) { 12278 currentRow = allRows[i]; 12279 for(j=0; j<currentRow.cells.length; j++) { 12280 // This is the target row, only unselect cells after the target cell 12281 if(currentRow.sectionRowIndex == nTargetTrIndex) { 12282 if(j>nTargetColKeyIndex) { 12283 this.unselectCell(currentRow.cells[j]); 12284 } 12285 } 12286 // This is the anchor row, only unselect cells before the anchor cell 12287 else if(currentRow.sectionRowIndex == oAnchor.trIndex) { 12288 if(j<oAnchor.colKeyIndex) { 12289 this.unselectCell(currentRow.cells[j]); 12290 } 12291 } 12292 // Unselect all cells on this row 12293 else { 12294 this.unselectCell(currentRow.cells[j]); 12295 } 12296 } 12297 } 12298 } 12299 12300 // Select the target cell 12301 this.selectCell(elTargetCell); 12302 } 12303 } 12304 // Invalid anchor 12305 else { 12306 // Set anchor 12307 this._oAnchorCell = oTargetCell; 12308 12309 // Toggle selection of target 12310 if(this.isSelected(oTargetCell)) { 12311 this.unselectCell(oTargetCell); 12312 } 12313 else { 12314 this.selectCell(oTargetCell); 12315 } 12316 } 12317 12318 } 12319 // Only SHIFT 12320 else if(bSHIFT) { 12321 this.unselectAllCells(); 12322 12323 // Validate anchor 12324 if(oAnchor) { 12325 // All cells are on the same row 12326 if(oAnchor.recordIndex === nTargetRecordIndex) { 12327 // Select all cells between anchor cell and target cell, 12328 // including the anchor cell and target cell 12329 if(oAnchor.colKeyIndex < nTargetColKeyIndex) { 12330 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) { 12331 this.selectCell(elTargetRow.cells[i]); 12332 } 12333 } 12334 // Select all cells between target cell and anchor cell 12335 // including the target cell and anchor cell 12336 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) { 12337 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) { 12338 this.selectCell(elTargetRow.cells[i]); 12339 } 12340 } 12341 } 12342 // Anchor row is above target row 12343 else if(oAnchor.recordIndex < nTargetRecordIndex) { 12344 // Select the cellblock from anchor cell to target cell 12345 // including the anchor cell and the target cell 12346 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex); 12347 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex); 12348 12349 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) { 12350 for(j=startIndex; j<=endIndex; j++) { 12351 this.selectCell(allRows[i].cells[j]); 12352 } 12353 } 12354 } 12355 // Anchor row is below target row 12356 else { 12357 // Select the cellblock from target cell to anchor cell 12358 // including the target cell and the anchor cell 12359 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex); 12360 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex); 12361 12362 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) { 12363 for(j=startIndex; j<=endIndex; j++) { 12364 this.selectCell(allRows[i].cells[j]); 12365 } 12366 } 12367 } 12368 } 12369 // Invalid anchor 12370 else { 12371 // Set anchor 12372 this._oAnchorCell = oTargetCell; 12373 12374 // Select target only 12375 this.selectCell(oTargetCell); 12376 } 12377 } 12378 // Only CTRL 12379 else if(bCTRL) { 12380 12381 // Set anchor 12382 this._oAnchorCell = oTargetCell; 12383 12384 // Toggle selection of target 12385 if(this.isSelected(oTargetCell)) { 12386 this.unselectCell(oTargetCell); 12387 } 12388 else { 12389 this.selectCell(oTargetCell); 12390 } 12391 12392 } 12393 // Neither SHIFT nor CTRL 12394 else { 12395 this._handleSingleCellSelectionByMouse(oArgs); 12396 } 12397 } 12398 }, 12399 12400 /** 12401 * Determines selection behavior resulting from a key event when selection mode 12402 * is set to "cellblock". 12403 * 12404 * @method _handleCellBlockSelectionByKey 12405 * @param e {HTMLEvent} Event object. 12406 * @private 12407 */ 12408 _handleCellBlockSelectionByKey : function(e) { 12409 var nKey = Ev.getCharCode(e); 12410 var bSHIFT = e.shiftKey; 12411 if((nKey == 9) || !bSHIFT) { 12412 this._handleSingleCellSelectionByKey(e); 12413 return; 12414 } 12415 12416 if((nKey > 36) && (nKey < 41)) { 12417 // Validate trigger 12418 var oTrigger = this._getSelectionTrigger(); 12419 // Arrow selection only works if last selected row is on current page 12420 if(!oTrigger) { 12421 return null; 12422 } 12423 12424 Ev.stopEvent(e); 12425 12426 // Validate anchor 12427 var oAnchor = this._getSelectionAnchor(oTrigger); 12428 12429 var i, startIndex, endIndex, elNew, elNewRow; 12430 var allRows = this.getTbodyEl().rows; 12431 var elThisRow = oTrigger.el.parentNode; 12432 12433 // Determine which direction we're going to 12434 12435 if(nKey == 40) { // arrow down 12436 // Selecting away from anchor cell 12437 if(oAnchor.recordIndex <= oTrigger.recordIndex) { 12438 // Select the horiz block on the next row... 12439 // ...making sure there is room below the trigger row 12440 elNewRow = this.getNextTrEl(oTrigger.el); 12441 if(elNewRow) { 12442 startIndex = oAnchor.colKeyIndex; 12443 endIndex = oTrigger.colKeyIndex; 12444 // ...going left 12445 if(startIndex > endIndex) { 12446 for(i=startIndex; i>=endIndex; i--) { 12447 elNew = elNewRow.cells[i]; 12448 this.selectCell(elNew); 12449 } 12450 } 12451 // ... going right 12452 else { 12453 for(i=startIndex; i<=endIndex; i++) { 12454 elNew = elNewRow.cells[i]; 12455 this.selectCell(elNew); 12456 } 12457 } 12458 } 12459 } 12460 // Unselecting towards anchor cell 12461 else { 12462 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex); 12463 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex); 12464 // Unselect the horiz block on this row towards the next row 12465 for(i=startIndex; i<=endIndex; i++) { 12466 this.unselectCell(elThisRow.cells[i]); 12467 } 12468 } 12469 } 12470 // Arrow up 12471 else if(nKey == 38) { 12472 // Selecting away from anchor cell 12473 if(oAnchor.recordIndex >= oTrigger.recordIndex) { 12474 // Select the horiz block on the previous row... 12475 // ...making sure there is room 12476 elNewRow = this.getPreviousTrEl(oTrigger.el); 12477 if(elNewRow) { 12478 // Select in order from anchor to trigger... 12479 startIndex = oAnchor.colKeyIndex; 12480 endIndex = oTrigger.colKeyIndex; 12481 // ...going left 12482 if(startIndex > endIndex) { 12483 for(i=startIndex; i>=endIndex; i--) { 12484 elNew = elNewRow.cells[i]; 12485 this.selectCell(elNew); 12486 } 12487 } 12488 // ... going right 12489 else { 12490 for(i=startIndex; i<=endIndex; i++) { 12491 elNew = elNewRow.cells[i]; 12492 this.selectCell(elNew); 12493 } 12494 } 12495 } 12496 } 12497 // Unselecting towards anchor cell 12498 else { 12499 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex); 12500 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex); 12501 // Unselect the horiz block on this row towards the previous row 12502 for(i=startIndex; i<=endIndex; i++) { 12503 this.unselectCell(elThisRow.cells[i]); 12504 } 12505 } 12506 } 12507 // Arrow right 12508 else if(nKey == 39) { 12509 // Selecting away from anchor cell 12510 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) { 12511 // Select the next vert block to the right... 12512 // ...making sure there is room 12513 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) { 12514 // Select in order from anchor to trigger... 12515 startIndex = oAnchor.trIndex; 12516 endIndex = oTrigger.trIndex; 12517 // ...going up 12518 if(startIndex > endIndex) { 12519 for(i=startIndex; i>=endIndex; i--) { 12520 elNew = allRows[i].cells[oTrigger.colKeyIndex+1]; 12521 this.selectCell(elNew); 12522 } 12523 } 12524 // ... going down 12525 else { 12526 for(i=startIndex; i<=endIndex; i++) { 12527 elNew = allRows[i].cells[oTrigger.colKeyIndex+1]; 12528 this.selectCell(elNew); 12529 } 12530 } 12531 } 12532 } 12533 // Unselecting towards anchor cell 12534 else { 12535 // Unselect the vert block on this column towards the right 12536 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex); 12537 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex); 12538 for(i=startIndex; i<=endIndex; i++) { 12539 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]); 12540 } 12541 } 12542 } 12543 // Arrow left 12544 else if(nKey == 37) { 12545 // Selecting away from anchor cell 12546 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) { 12547 //Select the previous vert block to the left 12548 if(oTrigger.colKeyIndex > 0) { 12549 // Select in order from anchor to trigger... 12550 startIndex = oAnchor.trIndex; 12551 endIndex = oTrigger.trIndex; 12552 // ...going up 12553 if(startIndex > endIndex) { 12554 for(i=startIndex; i>=endIndex; i--) { 12555 elNew = allRows[i].cells[oTrigger.colKeyIndex-1]; 12556 this.selectCell(elNew); 12557 } 12558 } 12559 // ... going down 12560 else { 12561 for(i=startIndex; i<=endIndex; i++) { 12562 elNew = allRows[i].cells[oTrigger.colKeyIndex-1]; 12563 this.selectCell(elNew); 12564 } 12565 } 12566 } 12567 } 12568 // Unselecting towards anchor cell 12569 else { 12570 // Unselect the vert block on this column towards the left 12571 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex); 12572 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex); 12573 for(i=startIndex; i<=endIndex; i++) { 12574 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]); 12575 } 12576 } 12577 } 12578 } 12579 }, 12580 12581 /** 12582 * Determines selection behavior resulting from a mouse event when selection mode 12583 * is set to "cellrange". 12584 * 12585 * @method _handleCellRangeSelectionByMouse 12586 * @param oArgs.event {HTMLEvent} Event object. 12587 * @param oArgs.target {HTMLElement} Target element. 12588 * @private 12589 */ 12590 _handleCellRangeSelectionByMouse : function(oArgs) { 12591 var elTarget = oArgs.target; 12592 12593 // Validate target cell 12594 var elTargetCell = this.getTdEl(elTarget); 12595 if(elTargetCell) { 12596 var e = oArgs.event; 12597 var bSHIFT = e.shiftKey; 12598 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey); 12599 12600 var elTargetRow = this.getTrEl(elTargetCell); 12601 var nTargetTrIndex = this.getTrIndex(elTargetRow); 12602 var oTargetColumn = this.getColumn(elTargetCell); 12603 var nTargetColKeyIndex = oTargetColumn.getKeyIndex(); 12604 var oTargetRecord = this.getRecord(elTargetRow); 12605 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord); 12606 var oTargetCell = {record:oTargetRecord, column:oTargetColumn}; 12607 12608 var oAnchor = this._getSelectionAnchor(); 12609 12610 var allRows = this.getTbodyEl().rows; 12611 var currentRow, i, j; 12612 12613 // Both SHIFT and CTRL 12614 if(bSHIFT && bCTRL) { 12615 12616 // Validate anchor 12617 if(oAnchor) { 12618 // Anchor is selected 12619 if(this.isSelected(oAnchor.cell)) { 12620 // All cells are on the same row 12621 if(oAnchor.recordIndex === nTargetRecordIndex) { 12622 // Select all cells between anchor cell and target cell, including target cell 12623 if(oAnchor.colKeyIndex < nTargetColKeyIndex) { 12624 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) { 12625 this.selectCell(elTargetRow.cells[i]); 12626 } 12627 } 12628 // Select all cells between target cell and anchor cell, including target cell 12629 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) { 12630 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) { 12631 this.selectCell(elTargetRow.cells[i]); 12632 } 12633 } 12634 } 12635 // Anchor row is above target row 12636 else if(oAnchor.recordIndex < nTargetRecordIndex) { 12637 // Select all cells on anchor row from anchor cell to the end of the row 12638 for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) { 12639 this.selectCell(elTargetRow.cells[i]); 12640 } 12641 12642 // Select all cells on all rows between anchor row and target row 12643 for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) { 12644 for(j=0; j<allRows[i].cells.length; j++){ 12645 this.selectCell(allRows[i].cells[j]); 12646 } 12647 } 12648 12649 // Select all cells on target row from first cell to the target cell 12650 for(i=0; i<=nTargetColKeyIndex; i++) { 12651 this.selectCell(elTargetRow.cells[i]); 12652 } 12653 } 12654 // Anchor row is below target row 12655 else { 12656 // Select all cells on target row from target cell to the end of the row 12657 for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) { 12658 this.selectCell(elTargetRow.cells[i]); 12659 } 12660 12661 // Select all cells on all rows between target row and anchor row 12662 for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) { 12663 for(j=0; j<allRows[i].cells.length; j++){ 12664 this.selectCell(allRows[i].cells[j]); 12665 } 12666 } 12667 12668 // Select all cells on anchor row from first cell to the anchor cell 12669 for(i=0; i<oAnchor.colKeyIndex; i++) { 12670 this.selectCell(elTargetRow.cells[i]); 12671 } 12672 } 12673 } 12674 // Anchor cell is unselected 12675 else { 12676 // All cells are on the same row 12677 if(oAnchor.recordIndex === nTargetRecordIndex) { 12678 // Unselect all cells between anchor cell and target cell 12679 if(oAnchor.colKeyIndex < nTargetColKeyIndex) { 12680 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) { 12681 this.unselectCell(elTargetRow.cells[i]); 12682 } 12683 } 12684 // Select all cells between target cell and anchor cell 12685 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) { 12686 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) { 12687 this.unselectCell(elTargetRow.cells[i]); 12688 } 12689 } 12690 } 12691 // Anchor row is above target row 12692 if(oAnchor.recordIndex < nTargetRecordIndex) { 12693 // Unselect all cells from anchor cell to target cell 12694 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) { 12695 currentRow = allRows[i]; 12696 for(j=0; j<currentRow.cells.length; j++) { 12697 // This is the anchor row, only unselect cells after the anchor cell 12698 if(currentRow.sectionRowIndex === oAnchor.trIndex) { 12699 if(j>oAnchor.colKeyIndex) { 12700 this.unselectCell(currentRow.cells[j]); 12701 } 12702 } 12703 // This is the target row, only unelect cells before the target cell 12704 else if(currentRow.sectionRowIndex === nTargetTrIndex) { 12705 if(j<nTargetColKeyIndex) { 12706 this.unselectCell(currentRow.cells[j]); 12707 } 12708 } 12709 // Unselect all cells on this row 12710 else { 12711 this.unselectCell(currentRow.cells[j]); 12712 } 12713 } 12714 } 12715 } 12716 // Anchor row is below target row 12717 else { 12718 // Unselect all cells from target cell to anchor cell 12719 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) { 12720 currentRow = allRows[i]; 12721 for(j=0; j<currentRow.cells.length; j++) { 12722 // This is the target row, only unselect cells after the target cell 12723 if(currentRow.sectionRowIndex == nTargetTrIndex) { 12724 if(j>nTargetColKeyIndex) { 12725 this.unselectCell(currentRow.cells[j]); 12726 } 12727 } 12728 // This is the anchor row, only unselect cells before the anchor cell 12729 else if(currentRow.sectionRowIndex == oAnchor.trIndex) { 12730 if(j<oAnchor.colKeyIndex) { 12731 this.unselectCell(currentRow.cells[j]); 12732 } 12733 } 12734 // Unselect all cells on this row 12735 else { 12736 this.unselectCell(currentRow.cells[j]); 12737 } 12738 } 12739 } 12740 } 12741 12742 // Select the target cell 12743 this.selectCell(elTargetCell); 12744 } 12745 } 12746 // Invalid anchor 12747 else { 12748 // Set anchor 12749 this._oAnchorCell = oTargetCell; 12750 12751 // Toggle selection of target 12752 if(this.isSelected(oTargetCell)) { 12753 this.unselectCell(oTargetCell); 12754 } 12755 else { 12756 this.selectCell(oTargetCell); 12757 } 12758 } 12759 } 12760 // Only SHIFT 12761 else if(bSHIFT) { 12762 12763 this.unselectAllCells(); 12764 12765 // Validate anchor 12766 if(oAnchor) { 12767 // All cells are on the same row 12768 if(oAnchor.recordIndex === nTargetRecordIndex) { 12769 // Select all cells between anchor cell and target cell, 12770 // including the anchor cell and target cell 12771 if(oAnchor.colKeyIndex < nTargetColKeyIndex) { 12772 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) { 12773 this.selectCell(elTargetRow.cells[i]); 12774 } 12775 } 12776 // Select all cells between target cell and anchor cell 12777 // including the target cell and anchor cell 12778 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) { 12779 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) { 12780 this.selectCell(elTargetRow.cells[i]); 12781 } 12782 } 12783 } 12784 // Anchor row is above target row 12785 else if(oAnchor.recordIndex < nTargetRecordIndex) { 12786 // Select all cells from anchor cell to target cell 12787 // including the anchor cell and target cell 12788 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) { 12789 currentRow = allRows[i]; 12790 for(j=0; j<currentRow.cells.length; j++) { 12791 // This is the anchor row, only select the anchor cell and after 12792 if(currentRow.sectionRowIndex == oAnchor.trIndex) { 12793 if(j>=oAnchor.colKeyIndex) { 12794 this.selectCell(currentRow.cells[j]); 12795 } 12796 } 12797 // This is the target row, only select the target cell and before 12798 else if(currentRow.sectionRowIndex == nTargetTrIndex) { 12799 if(j<=nTargetColKeyIndex) { 12800 this.selectCell(currentRow.cells[j]); 12801 } 12802 } 12803 // Select all cells on this row 12804 else { 12805 this.selectCell(currentRow.cells[j]); 12806 } 12807 } 12808 } 12809 } 12810 // Anchor row is below target row 12811 else { 12812 // Select all cells from target cell to anchor cell, 12813 // including the target cell and anchor cell 12814 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) { 12815 currentRow = allRows[i]; 12816 for(j=0; j<currentRow.cells.length; j++) { 12817 // This is the target row, only select the target cell and after 12818 if(currentRow.sectionRowIndex == nTargetTrIndex) { 12819 if(j>=nTargetColKeyIndex) { 12820 this.selectCell(currentRow.cells[j]); 12821 } 12822 } 12823 // This is the anchor row, only select the anchor cell and before 12824 else if(currentRow.sectionRowIndex == oAnchor.trIndex) { 12825 if(j<=oAnchor.colKeyIndex) { 12826 this.selectCell(currentRow.cells[j]); 12827 } 12828 } 12829 // Select all cells on this row 12830 else { 12831 this.selectCell(currentRow.cells[j]); 12832 } 12833 } 12834 } 12835 } 12836 } 12837 // Invalid anchor 12838 else { 12839 // Set anchor 12840 this._oAnchorCell = oTargetCell; 12841 12842 // Select target only 12843 this.selectCell(oTargetCell); 12844 } 12845 12846 12847 } 12848 // Only CTRL 12849 else if(bCTRL) { 12850 12851 // Set anchor 12852 this._oAnchorCell = oTargetCell; 12853 12854 // Toggle selection of target 12855 if(this.isSelected(oTargetCell)) { 12856 this.unselectCell(oTargetCell); 12857 } 12858 else { 12859 this.selectCell(oTargetCell); 12860 } 12861 12862 } 12863 // Neither SHIFT nor CTRL 12864 else { 12865 this._handleSingleCellSelectionByMouse(oArgs); 12866 } 12867 } 12868 }, 12869 12870 /** 12871 * Determines selection behavior resulting from a key event when selection mode 12872 * is set to "cellrange". 12873 * 12874 * @method _handleCellRangeSelectionByKey 12875 * @param e {HTMLEvent} Event object. 12876 * @private 12877 */ 12878 _handleCellRangeSelectionByKey : function(e) { 12879 var nKey = Ev.getCharCode(e); 12880 var bSHIFT = e.shiftKey; 12881 if((nKey == 9) || !bSHIFT) { 12882 this._handleSingleCellSelectionByKey(e); 12883 return; 12884 } 12885 12886 if((nKey > 36) && (nKey < 41)) { 12887 // Validate trigger 12888 var oTrigger = this._getSelectionTrigger(); 12889 // Arrow selection only works if last selected row is on current page 12890 if(!oTrigger) { 12891 return null; 12892 } 12893 12894 Ev.stopEvent(e); 12895 12896 // Validate anchor 12897 var oAnchor = this._getSelectionAnchor(oTrigger); 12898 12899 var i, elNewRow, elNew; 12900 var allRows = this.getTbodyEl().rows; 12901 var elThisRow = oTrigger.el.parentNode; 12902 12903 // Arrow down 12904 if(nKey == 40) { 12905 elNewRow = this.getNextTrEl(oTrigger.el); 12906 12907 // Selecting away from anchor cell 12908 if(oAnchor.recordIndex <= oTrigger.recordIndex) { 12909 // Select all cells to the end of this row 12910 for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){ 12911 elNew = elThisRow.cells[i]; 12912 this.selectCell(elNew); 12913 } 12914 12915 // Select some of the cells on the next row down 12916 if(elNewRow) { 12917 for(i=0; i<=oTrigger.colKeyIndex; i++){ 12918 elNew = elNewRow.cells[i]; 12919 this.selectCell(elNew); 12920 } 12921 } 12922 } 12923 // Unselecting towards anchor cell 12924 else { 12925 // Unselect all cells to the end of this row 12926 for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){ 12927 this.unselectCell(elThisRow.cells[i]); 12928 } 12929 12930 // Unselect some of the cells on the next row down 12931 if(elNewRow) { 12932 for(i=0; i<oTrigger.colKeyIndex; i++){ 12933 this.unselectCell(elNewRow.cells[i]); 12934 } 12935 } 12936 } 12937 } 12938 // Arrow up 12939 else if(nKey == 38) { 12940 elNewRow = this.getPreviousTrEl(oTrigger.el); 12941 12942 // Selecting away from anchor cell 12943 if(oAnchor.recordIndex >= oTrigger.recordIndex) { 12944 // Select all the cells to the beginning of this row 12945 for(i=oTrigger.colKeyIndex-1; i>-1; i--){ 12946 elNew = elThisRow.cells[i]; 12947 this.selectCell(elNew); 12948 } 12949 12950 // Select some of the cells from the end of the previous row 12951 if(elNewRow) { 12952 for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){ 12953 elNew = elNewRow.cells[i]; 12954 this.selectCell(elNew); 12955 } 12956 } 12957 } 12958 // Unselecting towards anchor cell 12959 else { 12960 // Unselect all the cells to the beginning of this row 12961 for(i=oTrigger.colKeyIndex; i>-1; i--){ 12962 this.unselectCell(elThisRow.cells[i]); 12963 } 12964 12965 // Unselect some of the cells from the end of the previous row 12966 if(elNewRow) { 12967 for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){ 12968 this.unselectCell(elNewRow.cells[i]); 12969 } 12970 } 12971 } 12972 } 12973 // Arrow right 12974 else if(nKey == 39) { 12975 elNewRow = this.getNextTrEl(oTrigger.el); 12976 12977 // Selecting away from anchor cell 12978 if(oAnchor.recordIndex < oTrigger.recordIndex) { 12979 // Select the next cell to the right 12980 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) { 12981 elNew = elThisRow.cells[oTrigger.colKeyIndex+1]; 12982 this.selectCell(elNew); 12983 } 12984 // Select the first cell of the next row 12985 else if(elNewRow) { 12986 elNew = elNewRow.cells[0]; 12987 this.selectCell(elNew); 12988 } 12989 } 12990 // Unselecting towards anchor cell 12991 else if(oAnchor.recordIndex > oTrigger.recordIndex) { 12992 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]); 12993 12994 // Unselect this cell towards the right 12995 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) { 12996 } 12997 // Unselect this cells towards the first cell of the next row 12998 else { 12999 } 13000 } 13001 // Anchor is on this row 13002 else { 13003 // Selecting away from anchor 13004 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) { 13005 // Select the next cell to the right 13006 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) { 13007 elNew = elThisRow.cells[oTrigger.colKeyIndex+1]; 13008 this.selectCell(elNew); 13009 } 13010 // Select the first cell on the next row 13011 else if(oTrigger.trIndex < allRows.length-1){ 13012 elNew = elNewRow.cells[0]; 13013 this.selectCell(elNew); 13014 } 13015 } 13016 // Unselecting towards anchor 13017 else { 13018 // Unselect this cell towards the right 13019 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]); 13020 } 13021 } 13022 } 13023 // Arrow left 13024 else if(nKey == 37) { 13025 elNewRow = this.getPreviousTrEl(oTrigger.el); 13026 13027 // Unselecting towards the anchor 13028 if(oAnchor.recordIndex < oTrigger.recordIndex) { 13029 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]); 13030 13031 // Unselect this cell towards the left 13032 if(oTrigger.colKeyIndex > 0) { 13033 } 13034 // Unselect this cell towards the last cell of the previous row 13035 else { 13036 } 13037 } 13038 // Selecting towards the anchor 13039 else if(oAnchor.recordIndex > oTrigger.recordIndex) { 13040 // Select the next cell to the left 13041 if(oTrigger.colKeyIndex > 0) { 13042 elNew = elThisRow.cells[oTrigger.colKeyIndex-1]; 13043 this.selectCell(elNew); 13044 } 13045 // Select the last cell of the previous row 13046 else if(oTrigger.trIndex > 0){ 13047 elNew = elNewRow.cells[elNewRow.cells.length-1]; 13048 this.selectCell(elNew); 13049 } 13050 } 13051 // Anchor is on this row 13052 else { 13053 // Selecting away from anchor cell 13054 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) { 13055 // Select the next cell to the left 13056 if(oTrigger.colKeyIndex > 0) { 13057 elNew = elThisRow.cells[oTrigger.colKeyIndex-1]; 13058 this.selectCell(elNew); 13059 } 13060 // Select the last cell of the previous row 13061 else if(oTrigger.trIndex > 0){ 13062 elNew = elNewRow.cells[elNewRow.cells.length-1]; 13063 this.selectCell(elNew); 13064 } 13065 } 13066 // Unselecting towards anchor cell 13067 else { 13068 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]); 13069 13070 // Unselect this cell towards the left 13071 if(oTrigger.colKeyIndex > 0) { 13072 } 13073 // Unselect this cell towards the last cell of the previous row 13074 else { 13075 } 13076 } 13077 } 13078 } 13079 } 13080 }, 13081 13082 /** 13083 * Determines selection behavior resulting from a mouse event when selection mode 13084 * is set to "singlecell". 13085 * 13086 * @method _handleSingleCellSelectionByMouse 13087 * @param oArgs.event {HTMLEvent} Event object. 13088 * @param oArgs.target {HTMLElement} Target element. 13089 * @private 13090 */ 13091 _handleSingleCellSelectionByMouse : function(oArgs) { 13092 var elTarget = oArgs.target; 13093 13094 // Validate target cell 13095 var elTargetCell = this.getTdEl(elTarget); 13096 if(elTargetCell) { 13097 var elTargetRow = this.getTrEl(elTargetCell); 13098 var oTargetRecord = this.getRecord(elTargetRow); 13099 var oTargetColumn = this.getColumn(elTargetCell); 13100 var oTargetCell = {record:oTargetRecord, column:oTargetColumn}; 13101 13102 // Set anchor 13103 this._oAnchorCell = oTargetCell; 13104 13105 // Select only target 13106 this.unselectAllCells(); 13107 this.selectCell(oTargetCell); 13108 } 13109 }, 13110 13111 /** 13112 * Determines selection behavior resulting from a key event when selection mode 13113 * is set to "singlecell". 13114 * 13115 * @method _handleSingleCellSelectionByKey 13116 * @param e {HTMLEvent} Event object. 13117 * @private 13118 */ 13119 _handleSingleCellSelectionByKey : function(e) { 13120 var nKey = Ev.getCharCode(e); 13121 if((nKey == 9) || ((nKey > 36) && (nKey < 41))) { 13122 var bSHIFT = e.shiftKey; 13123 13124 // Validate trigger 13125 var oTrigger = this._getSelectionTrigger(); 13126 // Arrow selection only works if last selected row is on current page 13127 if(!oTrigger) { 13128 return null; 13129 } 13130 13131 // Determine the new cell to select 13132 var elNew; 13133 if(nKey == 40) { // Arrow down 13134 elNew = this.getBelowTdEl(oTrigger.el); 13135 13136 // Validate new cell 13137 if(elNew === null) { 13138 //TODO: wrap around to first tr on current page 13139 13140 //TODO: wrap forward to first tr of next page 13141 13142 // Bottom selection is sticky 13143 elNew = oTrigger.el; 13144 } 13145 } 13146 else if(nKey == 38) { // Arrow up 13147 elNew = this.getAboveTdEl(oTrigger.el); 13148 13149 // Validate new cell 13150 if(elNew === null) { 13151 //TODO: wrap around to last tr on current page 13152 13153 //TODO: wrap back to last tr of previous page 13154 13155 // Top selection is sticky 13156 elNew = oTrigger.el; 13157 } 13158 } 13159 else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab 13160 elNew = this.getNextTdEl(oTrigger.el); 13161 13162 // Validate new cell 13163 if(elNew === null) { 13164 //TODO: wrap around to first td on current page 13165 13166 //TODO: wrap forward to first td of next page 13167 13168 // Top-left selection is sticky, and release TAB focus 13169 //elNew = oTrigger.el; 13170 return; 13171 } 13172 } 13173 else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab 13174 elNew = this.getPreviousTdEl(oTrigger.el); 13175 13176 // Validate new cell 13177 if(elNew === null) { 13178 //TODO: wrap around to last td on current page 13179 13180 //TODO: wrap back to last td of previous page 13181 13182 // Bottom-right selection is sticky, and release TAB focus 13183 //elNew = oTrigger.el; 13184 return; 13185 } 13186 } 13187 13188 Ev.stopEvent(e); 13189 13190 // Unselect all cells 13191 this.unselectAllCells(); 13192 13193 // Select the new cell 13194 this.selectCell(elNew); 13195 13196 // Set new anchor 13197 this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)}; 13198 } 13199 }, 13200 13201 /** 13202 * Returns array of selected TR elements on the page. 13203 * 13204 * @method getSelectedTrEls 13205 * @return {HTMLElement[]} Array of selected TR elements. 13206 */ 13207 getSelectedTrEls : function() { 13208 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody); 13209 }, 13210 13211 /** 13212 * Sets given row to the selected state. 13213 * 13214 * @method selectRow 13215 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element 13216 * reference or ID string, Record instance, or RecordSet position index. 13217 */ 13218 selectRow : function(row) { 13219 var oRecord, elRow; 13220 13221 if(row instanceof YAHOO.widget.Record) { 13222 oRecord = this._oRecordSet.getRecord(row); 13223 elRow = this.getTrEl(oRecord); 13224 } 13225 else if(lang.isNumber(row)) { 13226 oRecord = this.getRecord(row); 13227 elRow = this.getTrEl(oRecord); 13228 } 13229 else { 13230 elRow = this.getTrEl(row); 13231 oRecord = this.getRecord(elRow); 13232 } 13233 13234 if(oRecord) { 13235 // Update selection trackers 13236 var tracker = this._aSelections || []; 13237 var sRecordId = oRecord.getId(); 13238 var index = -1; 13239 13240 // Remove if already there: 13241 // Use Array.indexOf if available... 13242 /*if(tracker.indexOf && (tracker.indexOf(sRecordId) > -1)) { 13243 tracker.splice(tracker.indexOf(sRecordId),1); 13244 }*/ 13245 if(tracker.indexOf) { 13246 index = tracker.indexOf(sRecordId); 13247 13248 } 13249 // ...or do it the old-fashioned way 13250 else { 13251 for(var j=tracker.length-1; j>-1; j--) { 13252 if(tracker[j] === sRecordId){ 13253 index = j; 13254 break; 13255 } 13256 } 13257 } 13258 if(index > -1) { 13259 tracker.splice(index,1); 13260 } 13261 13262 // Add to the end 13263 tracker.push(sRecordId); 13264 this._aSelections = tracker; 13265 13266 // Update trackers 13267 if(!this._oAnchorRecord) { 13268 this._oAnchorRecord = oRecord; 13269 } 13270 13271 // Update UI 13272 if(elRow) { 13273 Dom.addClass(elRow, DT.CLASS_SELECTED); 13274 } 13275 13276 this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow}); 13277 } 13278 else { 13279 } 13280 }, 13281 13282 /** 13283 * Sets given row to the unselected state. 13284 * 13285 * @method unselectRow 13286 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element 13287 * reference or ID string, Record instance, or RecordSet position index. 13288 */ 13289 unselectRow : function(row) { 13290 var elRow = this.getTrEl(row); 13291 13292 var oRecord; 13293 if(row instanceof YAHOO.widget.Record) { 13294 oRecord = this._oRecordSet.getRecord(row); 13295 } 13296 else if(lang.isNumber(row)) { 13297 oRecord = this.getRecord(row); 13298 } 13299 else { 13300 oRecord = this.getRecord(elRow); 13301 } 13302 13303 if(oRecord) { 13304 // Update selection trackers 13305 var tracker = this._aSelections || []; 13306 var sRecordId = oRecord.getId(); 13307 var index = -1; 13308 13309 // Use Array.indexOf if available... 13310 if(tracker.indexOf) { 13311 index = tracker.indexOf(sRecordId); 13312 } 13313 // ...or do it the old-fashioned way 13314 else { 13315 for(var j=tracker.length-1; j>-1; j--) { 13316 if(tracker[j] === sRecordId){ 13317 index = j; 13318 break; 13319 } 13320 } 13321 } 13322 if(index > -1) { 13323 // Update tracker 13324 tracker.splice(index,1); 13325 this._aSelections = tracker; 13326 13327 // Update the UI 13328 Dom.removeClass(elRow, DT.CLASS_SELECTED); 13329 13330 this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow}); 13331 13332 return; 13333 } 13334 } 13335 }, 13336 13337 /** 13338 * Clears out all row selections. 13339 * 13340 * @method unselectAllRows 13341 */ 13342 unselectAllRows : function() { 13343 // Remove all rows from tracker 13344 var tracker = this._aSelections || [], 13345 recId, 13346 removed = []; 13347 for(var j=tracker.length-1; j>-1; j--) { 13348 if(lang.isString(tracker[j])){ 13349 recId = tracker.splice(j,1); 13350 removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId); 13351 } 13352 } 13353 13354 // Update tracker 13355 this._aSelections = tracker; 13356 13357 // Update UI 13358 this._unselectAllTrEls(); 13359 13360 this.fireEvent("unselectAllRowsEvent", {records: removed}); 13361 }, 13362 13363 /** 13364 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED 13365 * from all TD elements in the internal tracker. 13366 * 13367 * @method _unselectAllTdEls 13368 * @private 13369 */ 13370 _unselectAllTdEls : function() { 13371 var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody); 13372 Dom.removeClass(selectedCells, DT.CLASS_SELECTED); 13373 }, 13374 13375 /** 13376 * Returns array of selected TD elements on the page. 13377 * 13378 * @method getSelectedTdEls 13379 * @return {HTMLElement[]} Array of selected TD elements. 13380 */ 13381 getSelectedTdEls : function() { 13382 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody); 13383 }, 13384 13385 /** 13386 * Sets given cell to the selected state. 13387 * 13388 * @method selectCell 13389 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or 13390 * object literal of syntax {record:oRecord, column:oColumn}. 13391 */ 13392 selectCell : function(cell) { 13393 //TODO: accept {record} in selectCell() 13394 var elCell = this.getTdEl(cell); 13395 13396 if(elCell) { 13397 var oRecord = this.getRecord(elCell); 13398 var oColumn = this.getColumn(this.getCellIndex(elCell)); 13399 var sColumnKey = oColumn.getKey(); 13400 13401 if(oRecord && sColumnKey) { 13402 // Get Record ID 13403 var tracker = this._aSelections || []; 13404 var sRecordId = oRecord.getId(); 13405 13406 // Remove if there 13407 for(var j=tracker.length-1; j>-1; j--) { 13408 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){ 13409 tracker.splice(j,1); 13410 break; 13411 } 13412 } 13413 13414 // Add to the end 13415 tracker.push({recordId:sRecordId, columnKey:sColumnKey}); 13416 13417 // Update trackers 13418 this._aSelections = tracker; 13419 if(!this._oAnchorCell) { 13420 this._oAnchorCell = {record:oRecord, column:oColumn}; 13421 } 13422 13423 // Update the UI 13424 Dom.addClass(elCell, DT.CLASS_SELECTED); 13425 13426 this.fireEvent("cellSelectEvent", {record:oRecord, column:oColumn, key: sColumnKey, el:elCell}); 13427 return; 13428 } 13429 } 13430 }, 13431 13432 /** 13433 * Sets given cell to the unselected state. 13434 * 13435 * @method unselectCell 13436 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or 13437 * object literal of syntax {record:oRecord, column:oColumn}. 13438 * @param cell {HTMLElement | String} DOM element reference or ID string 13439 * to DataTable page element or RecordSet index. 13440 */ 13441 unselectCell : function(cell) { 13442 var elCell = this.getTdEl(cell); 13443 13444 if(elCell) { 13445 var oRecord = this.getRecord(elCell); 13446 var oColumn = this.getColumn(this.getCellIndex(elCell)); 13447 var sColumnKey = oColumn.getKey(); 13448 13449 if(oRecord && sColumnKey) { 13450 // Get Record ID 13451 var tracker = this._aSelections || []; 13452 var id = oRecord.getId(); 13453 13454 // Is it selected? 13455 for(var j=tracker.length-1; j>-1; j--) { 13456 if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){ 13457 // Remove from tracker 13458 tracker.splice(j,1); 13459 13460 // Update tracker 13461 this._aSelections = tracker; 13462 13463 // Update the UI 13464 Dom.removeClass(elCell, DT.CLASS_SELECTED); 13465 13466 this.fireEvent("cellUnselectEvent", {record:oRecord, column: oColumn, key:sColumnKey, el:elCell}); 13467 return; 13468 } 13469 } 13470 } 13471 } 13472 }, 13473 13474 /** 13475 * Clears out all cell selections. 13476 * 13477 * @method unselectAllCells 13478 */ 13479 unselectAllCells : function() { 13480 // Remove all cells from tracker 13481 var tracker = this._aSelections || []; 13482 for(var j=tracker.length-1; j>-1; j--) { 13483 if(lang.isObject(tracker[j])){ 13484 tracker.splice(j,1); 13485 } 13486 } 13487 13488 // Update tracker 13489 this._aSelections = tracker; 13490 13491 // Update UI 13492 this._unselectAllTdEls(); 13493 13494 //TODO: send data to unselectAllCellsEvent handler 13495 this.fireEvent("unselectAllCellsEvent"); 13496 }, 13497 13498 /** 13499 * Returns true if given item is selected, false otherwise. 13500 * 13501 * @method isSelected 13502 * @param o {String | HTMLElement | YAHOO.widget.Record | Number 13503 * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by 13504 * reference or ID string, a Record instance, a RecordSet position index, 13505 * or an object literal representation 13506 * of a cell. 13507 * @return {Boolean} True if item is selected. 13508 */ 13509 isSelected : function(o) { 13510 if(o && (o.ownerDocument == document)) { 13511 return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED)); 13512 } 13513 else { 13514 var oRecord, sRecordId, j; 13515 var tracker = this._aSelections; 13516 if(tracker && tracker.length > 0) { 13517 // Looking for a Record? 13518 if(o instanceof YAHOO.widget.Record) { 13519 oRecord = o; 13520 } 13521 else if(lang.isNumber(o)) { 13522 oRecord = this.getRecord(o); 13523 } 13524 if(oRecord) { 13525 sRecordId = oRecord.getId(); 13526 13527 // Is it there? 13528 // Use Array.indexOf if available... 13529 if(tracker.indexOf) { 13530 if(tracker.indexOf(sRecordId) > -1) { 13531 return true; 13532 } 13533 } 13534 // ...or do it the old-fashioned way 13535 else { 13536 for(j=tracker.length-1; j>-1; j--) { 13537 if(tracker[j] === sRecordId){ 13538 return true; 13539 } 13540 } 13541 } 13542 } 13543 // Looking for a cell 13544 else if(o.record && o.column){ 13545 sRecordId = o.record.getId(); 13546 var sColumnKey = o.column.getKey(); 13547 13548 for(j=tracker.length-1; j>-1; j--) { 13549 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){ 13550 return true; 13551 } 13552 } 13553 } 13554 } 13555 } 13556 return false; 13557 }, 13558 13559 /** 13560 * Returns selected rows as an array of Record IDs. 13561 * 13562 * @method getSelectedRows 13563 * @return {String[]} Array of selected rows by Record ID. 13564 */ 13565 getSelectedRows : function() { 13566 var aSelectedRows = []; 13567 var tracker = this._aSelections || []; 13568 for(var j=0; j<tracker.length; j++) { 13569 if(lang.isString(tracker[j])){ 13570 aSelectedRows.push(tracker[j]); 13571 } 13572 } 13573 return aSelectedRows; 13574 }, 13575 13576 /** 13577 * Returns selected cells as an array of object literals: 13578 * {recordId:sRecordId, columnKey:sColumnKey}. 13579 * 13580 * @method getSelectedCells 13581 * @return {Object[]} Array of selected cells by Record ID and Column ID. 13582 */ 13583 getSelectedCells : function() { 13584 var aSelectedCells = []; 13585 var tracker = this._aSelections || []; 13586 for(var j=0; j<tracker.length; j++) { 13587 if(tracker[j] && lang.isObject(tracker[j])){ 13588 aSelectedCells.push(tracker[j]); 13589 } 13590 } 13591 return aSelectedCells; 13592 }, 13593 13594 /** 13595 * Returns last selected Record ID. 13596 * 13597 * @method getLastSelectedRecord 13598 * @return {String} Record ID of last selected row. 13599 */ 13600 getLastSelectedRecord : function() { 13601 var tracker = this._aSelections; 13602 if(tracker && tracker.length > 0) { 13603 for(var i=tracker.length-1; i>-1; i--) { 13604 if(lang.isString(tracker[i])){ 13605 return tracker[i]; 13606 } 13607 } 13608 } 13609 }, 13610 13611 /** 13612 * Returns last selected cell as an object literal: 13613 * {recordId:sRecordId, columnKey:sColumnKey}. 13614 * 13615 * @method getLastSelectedCell 13616 * @return {Object} Object literal representation of a cell. 13617 */ 13618 getLastSelectedCell : function() { 13619 var tracker = this._aSelections; 13620 if(tracker && tracker.length > 0) { 13621 for(var i=tracker.length-1; i>-1; i--) { 13622 if(tracker[i].recordId && tracker[i].columnKey){ 13623 return tracker[i]; 13624 } 13625 } 13626 } 13627 }, 13628 13629 /** 13630 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row. 13631 * 13632 * @method highlightRow 13633 * @param row {HTMLElement | String} DOM element reference or ID string. 13634 */ 13635 highlightRow : function(row) { 13636 var elRow = this.getTrEl(row); 13637 13638 if(elRow) { 13639 // Make sure previous row is unhighlighted 13640 /* if(this._sLastHighlightedTrElId) { 13641 Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED); 13642 }*/ 13643 var oRecord = this.getRecord(elRow); 13644 Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED); 13645 //this._sLastHighlightedTrElId = elRow.id; 13646 this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow}); 13647 return; 13648 } 13649 }, 13650 13651 /** 13652 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row. 13653 * 13654 * @method unhighlightRow 13655 * @param row {HTMLElement | String} DOM element reference or ID string. 13656 */ 13657 unhighlightRow : function(row) { 13658 var elRow = this.getTrEl(row); 13659 13660 if(elRow) { 13661 var oRecord = this.getRecord(elRow); 13662 Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED); 13663 this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow}); 13664 return; 13665 } 13666 }, 13667 13668 /** 13669 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell. 13670 * 13671 * @method highlightCell 13672 * @param cell {HTMLElement | String} DOM element reference or ID string. 13673 */ 13674 highlightCell : function(cell) { 13675 var elCell = this.getTdEl(cell); 13676 13677 if(elCell) { 13678 // Make sure previous cell is unhighlighted 13679 if(this._elLastHighlightedTd) { 13680 this.unhighlightCell(this._elLastHighlightedTd); 13681 } 13682 13683 var oRecord = this.getRecord(elCell); 13684 var oColumn = this.getColumn(this.getCellIndex(elCell)); 13685 var sColumnKey = oColumn.getKey(); 13686 Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED); 13687 this._elLastHighlightedTd = elCell; 13688 this.fireEvent("cellHighlightEvent", {record:oRecord, column:oColumn, key:sColumnKey, el:elCell}); 13689 return; 13690 } 13691 }, 13692 13693 /** 13694 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell. 13695 * 13696 * @method unhighlightCell 13697 * @param cell {HTMLElement | String} DOM element reference or ID string. 13698 */ 13699 unhighlightCell : function(cell) { 13700 var elCell = this.getTdEl(cell); 13701 13702 if(elCell) { 13703 var oRecord = this.getRecord(elCell); 13704 Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED); 13705 this._elLastHighlightedTd = null; 13706 this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(this.getCellIndex(elCell)), key:this.getColumn(this.getCellIndex(elCell)).getKey(), el:elCell}); 13707 return; 13708 } 13709 }, 13710 13711 13712 13713 13714 13715 13716 13717 13718 13719 13720 13721 13722 13723 13724 13725 13726 13727 13728 13729 13730 13731 13732 13733 13734 13735 13736 13737 13738 13739 13740 13741 13742 13743 13744 13745 13746 13747 13748 13749 13750 13751 13752 13753 13754 13755 // INLINE EDITING 13756 13757 /** 13758 * Assigns CellEditor instance to existing Column. 13759 * @method addCellEditor 13760 * @param oColumn {YAHOO.widget.Column} Column instance. 13761 * @param oEditor {YAHOO.wdiget.CellEditor} CellEditor instance. 13762 */ 13763 addCellEditor : function(oColumn, oEditor) { 13764 oColumn.editor = oEditor; 13765 oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true); 13766 oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true); 13767 oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true); 13768 oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true); 13769 oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true); 13770 oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true); 13771 oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true); 13772 oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true); 13773 }, 13774 13775 /** 13776 * Returns current CellEditor instance, or null. 13777 * @method getCellEditor 13778 * @return {YAHOO.widget.CellEditor} CellEditor instance. 13779 */ 13780 getCellEditor : function() { 13781 return this._oCellEditor; 13782 }, 13783 13784 13785 /** 13786 * Activates and shows CellEditor instance for the given cell while deactivating and 13787 * canceling previous CellEditor. It is baked into DataTable that only one CellEditor 13788 * can be active at any given time. 13789 * 13790 * @method showCellEditor 13791 * @param elCell {HTMLElement | String} Cell to edit. 13792 */ 13793 showCellEditor : function(elCell, oRecord, oColumn) { 13794 // Get a particular CellEditor 13795 elCell = this.getTdEl(elCell); 13796 if(elCell) { 13797 oColumn = this.getColumn(elCell); 13798 if(oColumn && oColumn.editor) { 13799 var oCellEditor = this._oCellEditor; 13800 // Clean up active CellEditor 13801 if(oCellEditor) { 13802 if(this._oCellEditor.cancel) { 13803 this._oCellEditor.cancel(); 13804 } 13805 else if(oCellEditor.isActive) { 13806 this.cancelCellEditor(); 13807 } 13808 } 13809 13810 if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) { 13811 // Get CellEditor 13812 oCellEditor = oColumn.editor; 13813 var ok = oCellEditor.attach(this, elCell); 13814 if(ok) { 13815 oCellEditor.render(); 13816 oCellEditor.move(); 13817 ok = this.doBeforeShowCellEditor(oCellEditor); 13818 if(ok) { 13819 oCellEditor.show(); 13820 this._oCellEditor = oCellEditor; 13821 } 13822 } 13823 } 13824 // Backward compatibility 13825 else { 13826 if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) { 13827 oRecord = this.getRecord(elCell); 13828 } 13829 if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) { 13830 oColumn = this.getColumn(elCell); 13831 } 13832 if(oRecord && oColumn) { 13833 if(!this._oCellEditor || this._oCellEditor.container) { 13834 this._initCellEditorEl(); 13835 } 13836 13837 // Update Editor values 13838 oCellEditor = this._oCellEditor; 13839 oCellEditor.cell = elCell; 13840 oCellEditor.record = oRecord; 13841 oCellEditor.column = oColumn; 13842 oCellEditor.validator = (oColumn.editorOptions && 13843 lang.isFunction(oColumn.editorOptions.validator)) ? 13844 oColumn.editorOptions.validator : null; 13845 oCellEditor.value = oRecord.getData(oColumn.key); 13846 oCellEditor.defaultValue = null; 13847 13848 // Move Editor 13849 var elContainer = oCellEditor.container; 13850 var x = Dom.getX(elCell); 13851 var y = Dom.getY(elCell); 13852 13853 // SF doesn't get xy for cells in scrolling table 13854 // when tbody display is set to block 13855 if(isNaN(x) || isNaN(y)) { 13856 x = elCell.offsetLeft + // cell pos relative to table 13857 Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document 13858 this._elTbody.scrollLeft; // minus tbody scroll 13859 y = elCell.offsetTop + // cell pos relative to table 13860 Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document 13861 this._elTbody.scrollTop + // minus tbody scroll 13862 this._elThead.offsetHeight; // account for fixed THEAD cells 13863 } 13864 13865 elContainer.style.left = x + "px"; 13866 elContainer.style.top = y + "px"; 13867 13868 // Hook to customize the UI 13869 this.doBeforeShowCellEditor(this._oCellEditor); 13870 13871 //TODO: This is temporarily up here due so elements can be focused 13872 // Show Editor 13873 elContainer.style.display = ""; 13874 13875 // Handle ESC key 13876 Ev.addListener(elContainer, "keydown", function(e, oSelf) { 13877 // ESC hides Cell Editor 13878 if((e.keyCode == 27)) { 13879 oSelf.cancelCellEditor(); 13880 oSelf.focusTbodyEl(); 13881 } 13882 else { 13883 oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e}); 13884 } 13885 }, this); 13886 13887 // Render Editor markup 13888 var fnEditor; 13889 if(lang.isString(oColumn.editor)) { 13890 switch(oColumn.editor) { 13891 case "checkbox": 13892 fnEditor = DT.editCheckbox; 13893 break; 13894 case "date": 13895 fnEditor = DT.editDate; 13896 break; 13897 case "dropdown": 13898 fnEditor = DT.editDropdown; 13899 break; 13900 case "radio": 13901 fnEditor = DT.editRadio; 13902 break; 13903 case "textarea": 13904 fnEditor = DT.editTextarea; 13905 break; 13906 case "textbox": 13907 fnEditor = DT.editTextbox; 13908 break; 13909 default: 13910 fnEditor = null; 13911 } 13912 } 13913 else if(lang.isFunction(oColumn.editor)) { 13914 fnEditor = oColumn.editor; 13915 } 13916 13917 if(fnEditor) { 13918 // Create DOM input elements 13919 fnEditor(this._oCellEditor, this); 13920 13921 // Show Save/Cancel buttons 13922 if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) { 13923 this.showCellEditorBtns(elContainer); 13924 } 13925 13926 oCellEditor.isActive = true; 13927 13928 //TODO: verify which args to pass 13929 this.fireEvent("editorShowEvent", {editor:oCellEditor}); 13930 return; 13931 } 13932 } 13933 13934 13935 13936 13937 } 13938 } 13939 } 13940 }, 13941 13942 /** 13943 * Backward compatibility. 13944 * 13945 * @method _initCellEditorEl 13946 * @private 13947 * @deprecated Use BaseCellEditor class. 13948 */ 13949 _initCellEditorEl : function() { 13950 // Attach Cell Editor container element as first child of body 13951 var elCellEditor = document.createElement("div"); 13952 elCellEditor.id = this._sId + "-celleditor"; 13953 elCellEditor.style.display = "none"; 13954 elCellEditor.tabIndex = 0; 13955 Dom.addClass(elCellEditor, DT.CLASS_EDITOR); 13956 var elFirstChild = Dom.getFirstChild(document.body); 13957 if(elFirstChild) { 13958 elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild); 13959 } 13960 else { 13961 elCellEditor = document.body.appendChild(elCellEditor); 13962 } 13963 13964 // Internal tracker of Cell Editor values 13965 var oCellEditor = {}; 13966 oCellEditor.container = elCellEditor; 13967 oCellEditor.value = null; 13968 oCellEditor.isActive = false; 13969 this._oCellEditor = oCellEditor; 13970 }, 13971 13972 /** 13973 * Overridable abstract method to customize CellEditor before showing. 13974 * 13975 * @method doBeforeShowCellEditor 13976 * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance. 13977 * @return {Boolean} Return true to continue showing CellEditor. 13978 */ 13979 doBeforeShowCellEditor : function(oCellEditor) { 13980 return true; 13981 }, 13982 13983 /** 13984 * Saves active CellEditor input to Record and upates DOM UI. 13985 * 13986 * @method saveCellEditor 13987 */ 13988 saveCellEditor : function() { 13989 if(this._oCellEditor) { 13990 if(this._oCellEditor.save) { 13991 this._oCellEditor.save(); 13992 } 13993 // Backward compatibility 13994 else if(this._oCellEditor.isActive) { 13995 var newData = this._oCellEditor.value; 13996 // Copy the data to pass to the event 13997 //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key)); 13998 var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key); 13999 14000 // Validate input data 14001 if(this._oCellEditor.validator) { 14002 newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor); 14003 if(newData === null ) { 14004 this.resetCellEditor(); 14005 this.fireEvent("editorRevertEvent", 14006 {editor:this._oCellEditor, oldData:oldData, newData:newData}); 14007 return; 14008 } 14009 } 14010 // Update the Record 14011 this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value); 14012 // Update the UI 14013 this.formatCell(this._oCellEditor.cell.firstChild, this._oCellEditor.record, this._oCellEditor.column); 14014 14015 // Bug fix 1764044 14016 this._oChainRender.add({ 14017 method: function() { 14018 this.validateColumnWidths(); 14019 }, 14020 scope: this 14021 }); 14022 this._oChainRender.run(); 14023 // Clear out the Cell Editor 14024 this.resetCellEditor(); 14025 14026 this.fireEvent("editorSaveEvent", 14027 {editor:this._oCellEditor, oldData:oldData, newData:newData}); 14028 } 14029 } 14030 }, 14031 14032 /** 14033 * Cancels active CellEditor. 14034 * 14035 * @method cancelCellEditor 14036 */ 14037 cancelCellEditor : function() { 14038 if(this._oCellEditor) { 14039 if(this._oCellEditor.cancel) { 14040 this._oCellEditor.cancel(); 14041 } 14042 // Backward compatibility 14043 else if(this._oCellEditor.isActive) { 14044 this.resetCellEditor(); 14045 //TODO: preserve values for the event? 14046 this.fireEvent("editorCancelEvent", {editor:this._oCellEditor}); 14047 } 14048 14049 } 14050 }, 14051 14052 /** 14053 * Destroys active CellEditor instance and UI. 14054 * 14055 * @method destroyCellEditor 14056 */ 14057 destroyCellEditor : function() { 14058 if(this._oCellEditor) { 14059 this._oCellEditor.destroy(); 14060 this._oCellEditor = null; 14061 } 14062 }, 14063 14064 /** 14065 * Passes through showEvent of the active CellEditor. 14066 * 14067 * @method _onEditorShowEvent 14068 * @param oArgs {Object} Custom Event args. 14069 * @private 14070 */ 14071 _onEditorShowEvent : function(oArgs) { 14072 this.fireEvent("editorShowEvent", oArgs); 14073 }, 14074 14075 /** 14076 * Passes through keydownEvent of the active CellEditor. 14077 * @param oArgs {Object} Custom Event args. 14078 * 14079 * @method _onEditorKeydownEvent 14080 * @private 14081 */ 14082 _onEditorKeydownEvent : function(oArgs) { 14083 this.fireEvent("editorKeydownEvent", oArgs); 14084 }, 14085 14086 /** 14087 * Passes through revertEvent of the active CellEditor. 14088 * 14089 * @method _onEditorRevertEvent 14090 * @param oArgs {Object} Custom Event args. 14091 * @private 14092 */ 14093 _onEditorRevertEvent : function(oArgs) { 14094 this.fireEvent("editorRevertEvent", oArgs); 14095 }, 14096 14097 /** 14098 * Passes through saveEvent of the active CellEditor. 14099 * 14100 * @method _onEditorSaveEvent 14101 * @param oArgs {Object} Custom Event args. 14102 * @private 14103 */ 14104 _onEditorSaveEvent : function(oArgs) { 14105 this.fireEvent("editorSaveEvent", oArgs); 14106 }, 14107 14108 /** 14109 * Passes through cancelEvent of the active CellEditor. 14110 * 14111 * @method _onEditorCancelEvent 14112 * @param oArgs {Object} Custom Event args. 14113 * @private 14114 */ 14115 _onEditorCancelEvent : function(oArgs) { 14116 this.fireEvent("editorCancelEvent", oArgs); 14117 }, 14118 14119 /** 14120 * Passes through blurEvent of the active CellEditor. 14121 * 14122 * @method _onEditorBlurEvent 14123 * @param oArgs {Object} Custom Event args. 14124 * @private 14125 */ 14126 _onEditorBlurEvent : function(oArgs) { 14127 this.fireEvent("editorBlurEvent", oArgs); 14128 }, 14129 14130 /** 14131 * Passes through blockEvent of the active CellEditor. 14132 * 14133 * @method _onEditorBlockEvent 14134 * @param oArgs {Object} Custom Event args. 14135 * @private 14136 */ 14137 _onEditorBlockEvent : function(oArgs) { 14138 this.fireEvent("editorBlockEvent", oArgs); 14139 }, 14140 14141 /** 14142 * Passes through unblockEvent of the active CellEditor. 14143 * 14144 * @method _onEditorUnblockEvent 14145 * @param oArgs {Object} Custom Event args. 14146 * @private 14147 */ 14148 _onEditorUnblockEvent : function(oArgs) { 14149 this.fireEvent("editorUnblockEvent", oArgs); 14150 }, 14151 14152 /** 14153 * Public handler of the editorBlurEvent. By default, saves on blur if 14154 * disableBtns is true, otherwise cancels on blur. 14155 * 14156 * @method onEditorBlurEvent 14157 * @param oArgs {Object} Custom Event args. 14158 */ 14159 onEditorBlurEvent : function(oArgs) { 14160 if(oArgs.editor.disableBtns) { 14161 // Save on blur 14162 if(oArgs.editor.save) { // Backward incompatible 14163 oArgs.editor.save(); 14164 } 14165 } 14166 else if(oArgs.editor.cancel) { // Backward incompatible 14167 // Cancel on blur 14168 oArgs.editor.cancel(); 14169 } 14170 }, 14171 14172 /** 14173 * Public handler of the editorBlockEvent. By default, disables DataTable UI. 14174 * 14175 * @method onEditorBlockEvent 14176 * @param oArgs {Object} Custom Event args. 14177 */ 14178 onEditorBlockEvent : function(oArgs) { 14179 this.disable(); 14180 }, 14181 14182 /** 14183 * Public handler of the editorUnblockEvent. By default, undisables DataTable UI. 14184 * 14185 * @method onEditorUnblockEvent 14186 * @param oArgs {Object} Custom Event args. 14187 */ 14188 onEditorUnblockEvent : function(oArgs) { 14189 this.undisable(); 14190 }, 14191 14192 14193 14194 14195 14196 14197 14198 14199 14200 14201 14202 14203 14204 14205 14206 14207 14208 14209 14210 14211 14212 14213 14214 14215 14216 14217 14218 14219 14220 14221 14222 14223 14224 14225 14226 14227 14228 14229 // ABSTRACT METHODS 14230 14231 /** 14232 * Overridable method gives implementers a hook to access data before 14233 * it gets added to RecordSet and rendered to the TBODY. 14234 * 14235 * @method doBeforeLoadData 14236 * @param sRequest {String} Original request. 14237 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14238 * @param oPayload {MIXED} additional arguments 14239 * @return {Boolean} Return true to continue loading data into RecordSet and 14240 * updating DataTable with new Records, false to cancel. 14241 */ 14242 doBeforeLoadData : function(sRequest, oResponse, oPayload) { 14243 return true; 14244 }, 14245 14246 14247 14248 14249 14250 14251 14252 14253 14254 14255 14256 14257 14258 14259 14260 14261 14262 14263 14264 14265 14266 14267 14268 14269 14270 14271 14272 14273 14274 14275 14276 14277 14278 14279 14280 14281 14282 14283 14284 14285 14286 14287 14288 14289 14290 14291 14292 14293 14294 14295 14296 14297 14298 14299 14300 14301 14302 14303 14304 14305 14306 14307 14308 ///////////////////////////////////////////////////////////////////////////// 14309 // 14310 // Public Custom Event Handlers 14311 // 14312 ///////////////////////////////////////////////////////////////////////////// 14313 14314 /** 14315 * Custom event handler to sort Column. 14316 * 14317 * @method onEventSortColumn 14318 * @param oArgs.event {HTMLEvent} Event object. 14319 * @param oArgs.target {HTMLElement} Target element. 14320 */ 14321 onEventSortColumn : function(oArgs) { 14322 //TODO: support form elements in sortable columns 14323 var evt = oArgs.event; 14324 var target = oArgs.target; 14325 14326 var el = this.getThEl(target) || this.getTdEl(target); 14327 if(el) { 14328 var oColumn = this.getColumn(el); 14329 if(oColumn.sortable) { 14330 Ev.stopEvent(evt); 14331 this.sortColumn(oColumn); 14332 } 14333 } 14334 else { 14335 } 14336 }, 14337 14338 /** 14339 * Custom event handler to select Column. 14340 * 14341 * @method onEventSelectColumn 14342 * @param oArgs.event {HTMLEvent} Event object. 14343 * @param oArgs.target {HTMLElement} Target element. 14344 */ 14345 onEventSelectColumn : function(oArgs) { 14346 this.selectColumn(oArgs.target); 14347 }, 14348 14349 /** 14350 * Custom event handler to highlight Column. Accounts for spurious 14351 * caused-by-child events. 14352 * 14353 * @method onEventHighlightColumn 14354 * @param oArgs.event {HTMLEvent} Event object. 14355 * @param oArgs.target {HTMLElement} Target element. 14356 */ 14357 onEventHighlightColumn : function(oArgs) { 14358 this.highlightColumn(oArgs.target); 14359 }, 14360 14361 /** 14362 * Custom event handler to unhighlight Column. Accounts for spurious 14363 * caused-by-child events. 14364 * 14365 * @method onEventUnhighlightColumn 14366 * @param oArgs.event {HTMLEvent} Event object. 14367 * @param oArgs.target {HTMLElement} Target element. 14368 */ 14369 onEventUnhighlightColumn : function(oArgs) { 14370 this.unhighlightColumn(oArgs.target); 14371 }, 14372 14373 /** 14374 * Custom event handler to manage selection according to desktop paradigm. 14375 * 14376 * @method onEventSelectRow 14377 * @param oArgs.event {HTMLEvent} Event object. 14378 * @param oArgs.target {HTMLElement} Target element. 14379 */ 14380 onEventSelectRow : function(oArgs) { 14381 var sMode = this.get("selectionMode"); 14382 if(sMode == "single") { 14383 this._handleSingleSelectionByMouse(oArgs); 14384 } 14385 else { 14386 this._handleStandardSelectionByMouse(oArgs); 14387 } 14388 }, 14389 14390 /** 14391 * Custom event handler to select cell. 14392 * 14393 * @method onEventSelectCell 14394 * @param oArgs.event {HTMLEvent} Event object. 14395 * @param oArgs.target {HTMLElement} Target element. 14396 */ 14397 onEventSelectCell : function(oArgs) { 14398 var sMode = this.get("selectionMode"); 14399 if(sMode == "cellblock") { 14400 this._handleCellBlockSelectionByMouse(oArgs); 14401 } 14402 else if(sMode == "cellrange") { 14403 this._handleCellRangeSelectionByMouse(oArgs); 14404 } 14405 else { 14406 this._handleSingleCellSelectionByMouse(oArgs); 14407 } 14408 }, 14409 14410 /** 14411 * Custom event handler to highlight row. Accounts for spurious 14412 * caused-by-child events. 14413 * 14414 * @method onEventHighlightRow 14415 * @param oArgs.event {HTMLEvent} Event object. 14416 * @param oArgs.target {HTMLElement} Target element. 14417 */ 14418 onEventHighlightRow : function(oArgs) { 14419 this.highlightRow(oArgs.target); 14420 }, 14421 14422 /** 14423 * Custom event handler to unhighlight row. Accounts for spurious 14424 * caused-by-child events. 14425 * 14426 * @method onEventUnhighlightRow 14427 * @param oArgs.event {HTMLEvent} Event object. 14428 * @param oArgs.target {HTMLElement} Target element. 14429 */ 14430 onEventUnhighlightRow : function(oArgs) { 14431 this.unhighlightRow(oArgs.target); 14432 }, 14433 14434 /** 14435 * Custom event handler to highlight cell. Accounts for spurious 14436 * caused-by-child events. 14437 * 14438 * @method onEventHighlightCell 14439 * @param oArgs.event {HTMLEvent} Event object. 14440 * @param oArgs.target {HTMLElement} Target element. 14441 */ 14442 onEventHighlightCell : function(oArgs) { 14443 this.highlightCell(oArgs.target); 14444 }, 14445 14446 /** 14447 * Custom event handler to unhighlight cell. Accounts for spurious 14448 * caused-by-child events. 14449 * 14450 * @method onEventUnhighlightCell 14451 * @param oArgs.event {HTMLEvent} Event object. 14452 * @param oArgs.target {HTMLElement} Target element. 14453 */ 14454 onEventUnhighlightCell : function(oArgs) { 14455 this.unhighlightCell(oArgs.target); 14456 }, 14457 14458 /** 14459 * Custom event handler to format cell. 14460 * 14461 * @method onEventFormatCell 14462 * @param oArgs.event {HTMLEvent} Event object. 14463 * @param oArgs.target {HTMLElement} Target element. 14464 */ 14465 onEventFormatCell : function(oArgs) { 14466 var target = oArgs.target; 14467 14468 var elCell = this.getTdEl(target); 14469 if(elCell) { 14470 var oColumn = this.getColumn(this.getCellIndex(elCell)); 14471 this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn); 14472 } 14473 else { 14474 } 14475 }, 14476 14477 /** 14478 * Custom event handler to edit cell. 14479 * 14480 * @method onEventShowCellEditor 14481 * @param oArgs.event {HTMLEvent} Event object. 14482 * @param oArgs.target {HTMLElement} Target element. 14483 */ 14484 onEventShowCellEditor : function(oArgs) { 14485 if(!this.isDisabled()) { 14486 this.showCellEditor(oArgs.target); 14487 } 14488 }, 14489 14490 /** 14491 * Custom event handler to save active CellEditor input. 14492 * 14493 * @method onEventSaveCellEditor 14494 */ 14495 onEventSaveCellEditor : function(oArgs) { 14496 if(this._oCellEditor) { 14497 if(this._oCellEditor.save) { 14498 this._oCellEditor.save(); 14499 } 14500 // Backward compatibility 14501 else { 14502 this.saveCellEditor(); 14503 } 14504 } 14505 }, 14506 14507 /** 14508 * Custom event handler to cancel active CellEditor. 14509 * 14510 * @method onEventCancelCellEditor 14511 */ 14512 onEventCancelCellEditor : function(oArgs) { 14513 if(this._oCellEditor) { 14514 if(this._oCellEditor.cancel) { 14515 this._oCellEditor.cancel(); 14516 } 14517 // Backward compatibility 14518 else { 14519 this.cancelCellEditor(); 14520 } 14521 } 14522 }, 14523 14524 /** 14525 * Callback function receives data from DataSource and populates an entire 14526 * DataTable with Records and TR elements, clearing previous Records, if any. 14527 * 14528 * @method onDataReturnInitializeTable 14529 * @param sRequest {String} Original request. 14530 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14531 * @param oPayload {MIXED} (optional) Additional argument(s) 14532 */ 14533 onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) { 14534 if((this instanceof DT) && this._sId) { 14535 this.initializeTable(); 14536 14537 this.onDataReturnSetRows(sRequest,oResponse,oPayload); 14538 } 14539 }, 14540 14541 /** 14542 * Callback function receives reponse from DataSource, replaces all existing 14543 * Records in RecordSet, updates TR elements with new data, and updates state 14544 * UI for pagination and sorting from payload data, if necessary. 14545 * 14546 * @method onDataReturnReplaceRows 14547 * @param oRequest {MIXED} Original generated request. 14548 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14549 * @param oPayload {MIXED} (optional) Additional argument(s) 14550 */ 14551 onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) { 14552 if((this instanceof DT) && this._sId) { 14553 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload}); 14554 14555 // Pass data through abstract method for any transformations 14556 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload), 14557 pag = this.get('paginator'), 14558 index = 0; 14559 14560 // Data ok to set 14561 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) { 14562 // Update Records 14563 this._oRecordSet.reset(); 14564 14565 if (this.get('dynamicData')) { 14566 if (oPayload && oPayload.pagination && 14567 lang.isNumber(oPayload.pagination.recordOffset)) { 14568 index = oPayload.pagination.recordOffset; 14569 } else if (pag) { 14570 index = pag.getStartIndex(); 14571 } 14572 } 14573 14574 this._oRecordSet.setRecords(oResponse.results, index | 0); 14575 14576 // Update state 14577 this._handleDataReturnPayload(oRequest, oResponse, oPayload); 14578 14579 // Update UI 14580 this.render(); 14581 } 14582 // Error 14583 else if(ok && oResponse.error) { 14584 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR); 14585 } 14586 } 14587 }, 14588 14589 /** 14590 * Callback function receives data from DataSource and appends to an existing 14591 * DataTable new Records and, if applicable, creates or updates 14592 * corresponding TR elements. 14593 * 14594 * @method onDataReturnAppendRows 14595 * @param sRequest {String} Original request. 14596 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14597 * @param oPayload {MIXED} (optional) Additional argument(s) 14598 */ 14599 onDataReturnAppendRows : function(sRequest, oResponse, oPayload) { 14600 if((this instanceof DT) && this._sId) { 14601 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload}); 14602 14603 // Pass data through abstract method for any transformations 14604 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload); 14605 14606 // Data ok to append 14607 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) { 14608 // Append rows 14609 this.addRows(oResponse.results); 14610 14611 // Update state 14612 this._handleDataReturnPayload(sRequest, oResponse, oPayload); 14613 } 14614 // Error 14615 else if(ok && oResponse.error) { 14616 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR); 14617 } 14618 } 14619 }, 14620 14621 /** 14622 * Callback function receives data from DataSource and inserts new records 14623 * starting at the index specified in oPayload.insertIndex. The value for 14624 * oPayload.insertIndex can be populated when sending the request to the DataSource, 14625 * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime. 14626 * If applicable, creates or updates corresponding TR elements. 14627 * 14628 * @method onDataReturnInsertRows 14629 * @param sRequest {String} Original request. 14630 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14631 * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex. 14632 */ 14633 onDataReturnInsertRows : function(sRequest, oResponse, oPayload) { 14634 if((this instanceof DT) && this._sId) { 14635 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload}); 14636 14637 // Pass data through abstract method for any transformations 14638 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload); 14639 14640 // Data ok to append 14641 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) { 14642 // Insert rows 14643 this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0)); 14644 14645 // Update state 14646 this._handleDataReturnPayload(sRequest, oResponse, oPayload); 14647 } 14648 // Error 14649 else if(ok && oResponse.error) { 14650 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR); 14651 } 14652 } 14653 }, 14654 14655 /** 14656 * Callback function receives data from DataSource and incrementally updates Records 14657 * starting at the index specified in oPayload.updateIndex. The value for 14658 * oPayload.updateIndex can be populated when sending the request to the DataSource, 14659 * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime. 14660 * If applicable, creates or updates corresponding TR elements. 14661 * 14662 * @method onDataReturnUpdateRows 14663 * @param sRequest {String} Original request. 14664 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14665 * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex. 14666 */ 14667 onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) { 14668 if((this instanceof DT) && this._sId) { 14669 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload}); 14670 14671 // Pass data through abstract method for any transformations 14672 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload); 14673 14674 // Data ok to append 14675 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) { 14676 // Insert rows 14677 this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results); 14678 14679 // Update state 14680 this._handleDataReturnPayload(sRequest, oResponse, oPayload); 14681 } 14682 // Error 14683 else if(ok && oResponse.error) { 14684 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR); 14685 } 14686 } 14687 }, 14688 14689 /** 14690 * Callback function receives reponse from DataSource and populates the 14691 * RecordSet with the results. 14692 * 14693 * @method onDataReturnSetRows 14694 * @param oRequest {MIXED} Original generated request. 14695 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14696 * @param oPayload {MIXED} (optional) Additional argument(s) 14697 */ 14698 onDataReturnSetRows : function(oRequest, oResponse, oPayload) { 14699 if((this instanceof DT) && this._sId) { 14700 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload}); 14701 14702 // Pass data through abstract method for any transformations 14703 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload), 14704 pag = this.get('paginator'), 14705 index = 0; 14706 14707 // Data ok to set 14708 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) { 14709 // Update Records 14710 if (this.get('dynamicData')) { 14711 if (oPayload && oPayload.pagination && 14712 lang.isNumber(oPayload.pagination.recordOffset)) { 14713 index = oPayload.pagination.recordOffset; 14714 } else if (pag) { 14715 index = pag.getStartIndex(); 14716 } 14717 14718 this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default 14719 } 14720 14721 this._oRecordSet.setRecords(oResponse.results, index | 0); 14722 14723 // Update state 14724 this._handleDataReturnPayload(oRequest, oResponse, oPayload); 14725 14726 // Update UI 14727 this.render(); 14728 } 14729 // Error 14730 else if(ok && oResponse.error) { 14731 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR); 14732 } 14733 } 14734 else { 14735 } 14736 }, 14737 14738 /** 14739 * Hook to update oPayload before consumption. 14740 * 14741 * @method handleDataReturnPayload 14742 * @param oRequest {MIXED} Original generated request. 14743 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14744 * @param oPayload {MIXED} State values. 14745 * @return oPayload {MIXED} State values. 14746 */ 14747 handleDataReturnPayload : function (oRequest, oResponse, oPayload) { 14748 return oPayload || {}; 14749 }, 14750 14751 /** 14752 * Updates the DataTable with state data sent in an onDataReturn* payload. 14753 * 14754 * @method _handleDataReturnPayload 14755 * @param oRequest {MIXED} Original generated request. 14756 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>. 14757 * @param oPayload {MIXED} State values 14758 * @private 14759 */ 14760 _handleDataReturnPayload : function (oRequest, oResponse, oPayload) { 14761 oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload); 14762 if(oPayload) { 14763 // Update pagination 14764 var oPaginator = this.get('paginator'); 14765 if (oPaginator) { 14766 // Update totalRecords 14767 if(this.get("dynamicData")) { 14768 if (widget.Paginator.isNumeric(oPayload.totalRecords)) { 14769 oPaginator.set('totalRecords',oPayload.totalRecords); 14770 } 14771 } 14772 else { 14773 oPaginator.set('totalRecords',this._oRecordSet.getLength()); 14774 } 14775 // Update other paginator values 14776 if (lang.isObject(oPayload.pagination)) { 14777 oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage); 14778 oPaginator.set('recordOffset',oPayload.pagination.recordOffset); 14779 } 14780 } 14781 14782 // Update sorting 14783 if (oPayload.sortedBy) { 14784 // Set the sorting values in preparation for refresh 14785 this.set('sortedBy', oPayload.sortedBy); 14786 } 14787 // Backwards compatibility for sorting 14788 else if (oPayload.sorting) { 14789 // Set the sorting values in preparation for refresh 14790 this.set('sortedBy', oPayload.sorting); 14791 } 14792 } 14793 }, 14794 14795 14796 14797 14798 14799 14800 14801 14802 14803 14804 14805 14806 14807 14808 14809 14810 14811 14812 14813 14814 14815 14816 14817 14818 14819 14820 14821 14822 14823 14824 14825 14826 14827 ///////////////////////////////////////////////////////////////////////////// 14828 // 14829 // Custom Events 14830 // 14831 ///////////////////////////////////////////////////////////////////////////// 14832 14833 /** 14834 * Fired when the DataTable's rows are rendered from an initialized state. 14835 * 14836 * @event initEvent 14837 */ 14838 14839 /** 14840 * Fired before the DataTable's DOM is rendered or modified. 14841 * 14842 * @event beforeRenderEvent 14843 */ 14844 14845 /** 14846 * Fired when the DataTable's DOM is rendered or modified. 14847 * 14848 * @event renderEvent 14849 */ 14850 14851 /** 14852 * Fired when the DataTable's post-render routine is complete, including 14853 * Column width validations. 14854 * 14855 * @event postRenderEvent 14856 */ 14857 14858 /** 14859 * Fired when the DataTable is disabled. 14860 * 14861 * @event disableEvent 14862 */ 14863 14864 /** 14865 * Fired when the DataTable is undisabled. 14866 * 14867 * @event undisableEvent 14868 */ 14869 14870 /** 14871 * Fired when data is returned from DataSource but before it is consumed by 14872 * DataTable. 14873 * 14874 * @event dataReturnEvent 14875 * @param oArgs.request {String} Original request. 14876 * @param oArgs.response {Object} Response object. 14877 */ 14878 14879 /** 14880 * Fired when the DataTable has a focus event. 14881 * 14882 * @event tableFocusEvent 14883 */ 14884 14885 /** 14886 * Fired when the DataTable THEAD element has a focus event. 14887 * 14888 * @event theadFocusEvent 14889 */ 14890 14891 /** 14892 * Fired when the DataTable TBODY element has a focus event. 14893 * 14894 * @event tbodyFocusEvent 14895 */ 14896 14897 /** 14898 * Fired when the DataTable has a blur event. 14899 * 14900 * @event tableBlurEvent 14901 */ 14902 14903 /*TODO implement theadBlurEvent 14904 * Fired when the DataTable THEAD element has a blur event. 14905 * 14906 * @event theadBlurEvent 14907 */ 14908 14909 /*TODO: implement tbodyBlurEvent 14910 * Fired when the DataTable TBODY element has a blur event. 14911 * 14912 * @event tbodyBlurEvent 14913 */ 14914 14915 /** 14916 * Fired when the DataTable has a key event. 14917 * 14918 * @event tableKeyEvent 14919 * @param oArgs.event {HTMLEvent} The event object. 14920 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 14921 */ 14922 14923 /** 14924 * Fired when the DataTable THEAD element has a key event. 14925 * 14926 * @event theadKeyEvent 14927 * @param oArgs.event {HTMLEvent} The event object. 14928 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 14929 */ 14930 14931 /** 14932 * Fired when the DataTable TBODY element has a key event. 14933 * 14934 * @event tbodyKeyEvent 14935 * @param oArgs.event {HTMLEvent} The event object. 14936 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 14937 */ 14938 14939 /** 14940 * Fired when the DataTable has a mouseover. 14941 * 14942 * @event tableMouseoverEvent 14943 * @param oArgs.event {HTMLEvent} The event object. 14944 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 14945 * 14946 */ 14947 14948 /** 14949 * Fired when the DataTable has a mouseout. 14950 * 14951 * @event tableMouseoutEvent 14952 * @param oArgs.event {HTMLEvent} The event object. 14953 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 14954 * 14955 */ 14956 14957 /** 14958 * Fired when the DataTable has a mousedown. 14959 * 14960 * @event tableMousedownEvent 14961 * @param oArgs.event {HTMLEvent} The event object. 14962 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 14963 * 14964 */ 14965 14966 /** 14967 * Fired when the DataTable has a mouseup. 14968 * 14969 * @event tableMouseupEvent 14970 * @param oArgs.event {HTMLEvent} The event object. 14971 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 14972 * 14973 */ 14974 14975 /** 14976 * Fired when the DataTable has a click. 14977 * 14978 * @event tableClickEvent 14979 * @param oArgs.event {HTMLEvent} The event object. 14980 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 14981 * 14982 */ 14983 14984 /** 14985 * Fired when the DataTable has a dblclick. 14986 * 14987 * @event tableDblclickEvent 14988 * @param oArgs.event {HTMLEvent} The event object. 14989 * @param oArgs.target {HTMLElement} The DataTable's TABLE element. 14990 * 14991 */ 14992 14993 /** 14994 * Fired when a message is shown in the DataTable's message element. 14995 * 14996 * @event tableMsgShowEvent 14997 * @param oArgs.html {HTML} The HTML displayed. 14998 * @param oArgs.className {String} The className assigned. 14999 * 15000 */ 15001 15002 /** 15003 * Fired when the DataTable's message element is hidden. 15004 * 15005 * @event tableMsgHideEvent 15006 */ 15007 15008 /** 15009 * Fired when a THEAD row has a mouseover. 15010 * 15011 * @event theadRowMouseoverEvent 15012 * @param oArgs.event {HTMLEvent} The event object. 15013 * @param oArgs.target {HTMLElement} The TR element. 15014 */ 15015 15016 /** 15017 * Fired when a THEAD row has a mouseout. 15018 * 15019 * @event theadRowMouseoutEvent 15020 * @param oArgs.event {HTMLEvent} The event object. 15021 * @param oArgs.target {HTMLElement} The TR element. 15022 */ 15023 15024 /** 15025 * Fired when a THEAD row has a mousedown. 15026 * 15027 * @event theadRowMousedownEvent 15028 * @param oArgs.event {HTMLEvent} The event object. 15029 * @param oArgs.target {HTMLElement} The TR element. 15030 */ 15031 15032 /** 15033 * Fired when a THEAD row has a mouseup. 15034 * 15035 * @event theadRowMouseupEvent 15036 * @param oArgs.event {HTMLEvent} The event object. 15037 * @param oArgs.target {HTMLElement} The TR element. 15038 */ 15039 15040 /** 15041 * Fired when a THEAD row has a click. 15042 * 15043 * @event theadRowClickEvent 15044 * @param oArgs.event {HTMLEvent} The event object. 15045 * @param oArgs.target {HTMLElement} The TR element. 15046 */ 15047 15048 /** 15049 * Fired when a THEAD row has a dblclick. 15050 * 15051 * @event theadRowDblclickEvent 15052 * @param oArgs.event {HTMLEvent} The event object. 15053 * @param oArgs.target {HTMLElement} The TR element. 15054 */ 15055 15056 /** 15057 * Fired when a THEAD cell has a mouseover. 15058 * 15059 * @event theadCellMouseoverEvent 15060 * @param oArgs.event {HTMLEvent} The event object. 15061 * @param oArgs.target {HTMLElement} The TH element. 15062 * 15063 */ 15064 15065 /** 15066 * Fired when a THEAD cell has a mouseout. 15067 * 15068 * @event theadCellMouseoutEvent 15069 * @param oArgs.event {HTMLEvent} The event object. 15070 * @param oArgs.target {HTMLElement} The TH element. 15071 * 15072 */ 15073 15074 /** 15075 * Fired when a THEAD cell has a mousedown. 15076 * 15077 * @event theadCellMousedownEvent 15078 * @param oArgs.event {HTMLEvent} The event object. 15079 * @param oArgs.target {HTMLElement} The TH element. 15080 */ 15081 15082 /** 15083 * Fired when a THEAD cell has a mouseup. 15084 * 15085 * @event theadCellMouseupEvent 15086 * @param oArgs.event {HTMLEvent} The event object. 15087 * @param oArgs.target {HTMLElement} The TH element. 15088 */ 15089 15090 /** 15091 * Fired when a THEAD cell has a click. 15092 * 15093 * @event theadCellClickEvent 15094 * @param oArgs.event {HTMLEvent} The event object. 15095 * @param oArgs.target {HTMLElement} The TH element. 15096 */ 15097 15098 /** 15099 * Fired when a THEAD cell has a dblclick. 15100 * 15101 * @event theadCellDblclickEvent 15102 * @param oArgs.event {HTMLEvent} The event object. 15103 * @param oArgs.target {HTMLElement} The TH element. 15104 */ 15105 15106 /** 15107 * Fired when a THEAD label has a mouseover. 15108 * 15109 * @event theadLabelMouseoverEvent 15110 * @param oArgs.event {HTMLEvent} The event object. 15111 * @param oArgs.target {HTMLElement} The SPAN element. 15112 * 15113 */ 15114 15115 /** 15116 * Fired when a THEAD label has a mouseout. 15117 * 15118 * @event theadLabelMouseoutEvent 15119 * @param oArgs.event {HTMLEvent} The event object. 15120 * @param oArgs.target {HTMLElement} The SPAN element. 15121 * 15122 */ 15123 15124 /** 15125 * Fired when a THEAD label has a mousedown. 15126 * 15127 * @event theadLabelMousedownEvent 15128 * @param oArgs.event {HTMLEvent} The event object. 15129 * @param oArgs.target {HTMLElement} The SPAN element. 15130 */ 15131 15132 /** 15133 * Fired when a THEAD label has a mouseup. 15134 * 15135 * @event theadLabelMouseupEvent 15136 * @param oArgs.event {HTMLEvent} The event object. 15137 * @param oArgs.target {HTMLElement} The SPAN element. 15138 */ 15139 15140 /** 15141 * Fired when a THEAD label has a click. 15142 * 15143 * @event theadLabelClickEvent 15144 * @param oArgs.event {HTMLEvent} The event object. 15145 * @param oArgs.target {HTMLElement} The SPAN element. 15146 */ 15147 15148 /** 15149 * Fired when a THEAD label has a dblclick. 15150 * 15151 * @event theadLabelDblclickEvent 15152 * @param oArgs.event {HTMLEvent} The event object. 15153 * @param oArgs.target {HTMLElement} The SPAN element. 15154 */ 15155 15156 /** 15157 * Fired when a column is sorted. 15158 * 15159 * @event columnSortEvent 15160 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15161 * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC 15162 * or YAHOO.widget.DataTable.CLASS_DESC. 15163 */ 15164 15165 /** 15166 * Fired when a column width is set. 15167 * 15168 * @event columnSetWidthEvent 15169 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15170 * @param oArgs.width {Number} The width in pixels. 15171 */ 15172 15173 /** 15174 * Fired when a column width is unset. 15175 * 15176 * @event columnUnsetWidthEvent 15177 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15178 */ 15179 15180 /** 15181 * Fired when a column is drag-resized. 15182 * 15183 * @event columnResizeEvent 15184 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15185 * @param oArgs.target {HTMLElement} The TH element. 15186 * @param oArgs.width {Number} Width in pixels. 15187 */ 15188 15189 /** 15190 * Fired when a Column is moved to a new index. 15191 * 15192 * @event columnReorderEvent 15193 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15194 * @param oArgs.oldIndex {Number} The previous tree index position. 15195 */ 15196 15197 /** 15198 * Fired when a column is hidden. 15199 * 15200 * @event columnHideEvent 15201 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15202 */ 15203 15204 /** 15205 * Fired when a column is shown. 15206 * 15207 * @event columnShowEvent 15208 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15209 */ 15210 15211 /** 15212 * Fired when a column is selected. 15213 * 15214 * @event columnSelectEvent 15215 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15216 */ 15217 15218 /** 15219 * Fired when a column is unselected. 15220 * 15221 * @event columnUnselectEvent 15222 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15223 */ 15224 /** 15225 * Fired when a column is removed. 15226 * 15227 * @event columnRemoveEvent 15228 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15229 */ 15230 15231 /** 15232 * Fired when a column is inserted. 15233 * 15234 * @event columnInsertEvent 15235 * @param oArgs.column {YAHOO.widget.Column} The Column instance. 15236 * @param oArgs.index {Number} The index position. 15237 */ 15238 15239 /** 15240 * Fired when a column is highlighted. 15241 * 15242 * @event columnHighlightEvent 15243 * @param oArgs.column {YAHOO.widget.Column} The highlighted Column. 15244 */ 15245 15246 /** 15247 * Fired when a column is unhighlighted. 15248 * 15249 * @event columnUnhighlightEvent 15250 * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column. 15251 */ 15252 15253 15254 /** 15255 * Fired when a row has a mouseover. 15256 * 15257 * @event rowMouseoverEvent 15258 * @param oArgs.event {HTMLEvent} The event object. 15259 * @param oArgs.target {HTMLElement} The TR element. 15260 */ 15261 15262 /** 15263 * Fired when a row has a mouseout. 15264 * 15265 * @event rowMouseoutEvent 15266 * @param oArgs.event {HTMLEvent} The event object. 15267 * @param oArgs.target {HTMLElement} The TR element. 15268 */ 15269 15270 /** 15271 * Fired when a row has a mousedown. 15272 * 15273 * @event rowMousedownEvent 15274 * @param oArgs.event {HTMLEvent} The event object. 15275 * @param oArgs.target {HTMLElement} The TR element. 15276 */ 15277 15278 /** 15279 * Fired when a row has a mouseup. 15280 * 15281 * @event rowMouseupEvent 15282 * @param oArgs.event {HTMLEvent} The event object. 15283 * @param oArgs.target {HTMLElement} The TR element. 15284 */ 15285 15286 /** 15287 * Fired when a row has a click. 15288 * 15289 * @event rowClickEvent 15290 * @param oArgs.event {HTMLEvent} The event object. 15291 * @param oArgs.target {HTMLElement} The TR element. 15292 */ 15293 15294 /** 15295 * Fired when a row has a dblclick. 15296 * 15297 * @event rowDblclickEvent 15298 * @param oArgs.event {HTMLEvent} The event object. 15299 * @param oArgs.target {HTMLElement} The TR element. 15300 */ 15301 15302 /** 15303 * Fired when a row is added. 15304 * 15305 * @event rowAddEvent 15306 * @param oArgs.record {YAHOO.widget.Record} The added Record. 15307 */ 15308 15309 /** 15310 * Fired when rows are added. 15311 * 15312 * @event rowsAddEvent 15313 * @param oArgs.record {YAHOO.widget.Record[]} The added Records. 15314 */ 15315 15316 /** 15317 * Fired when a row is updated. 15318 * 15319 * @event rowUpdateEvent 15320 * @param oArgs.record {YAHOO.widget.Record} The updated Record. 15321 * @param oArgs.oldData {Object} Object literal of the old data. 15322 */ 15323 15324 /** 15325 * Fired when a row is deleted. 15326 * 15327 * @event rowDeleteEvent 15328 * @param oArgs.oldData {Object} Object literal of the deleted data. 15329 * @param oArgs.recordIndex {Number} Index of the deleted Record. 15330 * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page. 15331 */ 15332 15333 /** 15334 * Fired when rows are deleted. 15335 * 15336 * @event rowsDeleteEvent 15337 * @param oArgs.oldData {Object[]} Array of object literals of the deleted data. 15338 * @param oArgs.recordIndex {Number} Index of the first deleted Record. 15339 * @param oArgs.count {Number} Number of deleted Records. 15340 */ 15341 15342 /** 15343 * Fired when a row is selected. 15344 * 15345 * @event rowSelectEvent 15346 * @param oArgs.el {HTMLElement} The selected TR element, if applicable. 15347 * @param oArgs.record {YAHOO.widget.Record} The selected Record. 15348 */ 15349 15350 /** 15351 * Fired when a row is unselected. 15352 * 15353 * @event rowUnselectEvent 15354 * @param oArgs.el {HTMLElement} The unselected TR element, if applicable. 15355 * @param oArgs.record {YAHOO.widget.Record} The unselected Record. 15356 */ 15357 15358 /** 15359 * Fired when all row selections are cleared. 15360 * 15361 * @event unselectAllRowsEvent 15362 */ 15363 15364 /** 15365 * Fired when a row is highlighted. 15366 * 15367 * @event rowHighlightEvent 15368 * @param oArgs.el {HTMLElement} The highlighted TR element. 15369 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record. 15370 */ 15371 15372 /** 15373 * Fired when a row is unhighlighted. 15374 * 15375 * @event rowUnhighlightEvent 15376 * @param oArgs.el {HTMLElement} The highlighted TR element. 15377 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record. 15378 */ 15379 15380 /** 15381 * Fired when a cell is updated. 15382 * 15383 * @event cellUpdateEvent 15384 * @param oArgs.record {YAHOO.widget.Record} The updated Record. 15385 * @param oArgs.column {YAHOO.widget.Column} The updated Column. 15386 * @param oArgs.oldData {Object} Original data value of the updated cell. 15387 */ 15388 15389 /** 15390 * Fired when a cell has a mouseover. 15391 * 15392 * @event cellMouseoverEvent 15393 * @param oArgs.event {HTMLEvent} The event object. 15394 * @param oArgs.target {HTMLElement} The TD element. 15395 */ 15396 15397 /** 15398 * Fired when a cell has a mouseout. 15399 * 15400 * @event cellMouseoutEvent 15401 * @param oArgs.event {HTMLEvent} The event object. 15402 * @param oArgs.target {HTMLElement} The TD element. 15403 */ 15404 15405 /** 15406 * Fired when a cell has a mousedown. 15407 * 15408 * @event cellMousedownEvent 15409 * @param oArgs.event {HTMLEvent} The event object. 15410 * @param oArgs.target {HTMLElement} The TD element. 15411 */ 15412 15413 /** 15414 * Fired when a cell has a mouseup. 15415 * 15416 * @event cellMouseupEvent 15417 * @param oArgs.event {HTMLEvent} The event object. 15418 * @param oArgs.target {HTMLElement} The TD element. 15419 */ 15420 15421 /** 15422 * Fired when a cell has a click. 15423 * 15424 * @event cellClickEvent 15425 * @param oArgs.event {HTMLEvent} The event object. 15426 * @param oArgs.target {HTMLElement} The TD element. 15427 */ 15428 15429 /** 15430 * Fired when a cell has a dblclick. 15431 * 15432 * @event cellDblclickEvent 15433 * @param oArgs.event {HTMLEvent} The event object. 15434 * @param oArgs.target {HTMLElement} The TD element. 15435 */ 15436 15437 /** 15438 * Fired when a cell is formatted. 15439 * 15440 * @event cellFormatEvent 15441 * @param oArgs.el {HTMLElement} The formatted TD element. 15442 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance. 15443 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance. 15444 * @param oArgs.key {String} (deprecated) The key of the formatted cell. 15445 */ 15446 15447 /** 15448 * Fired when a cell is selected. 15449 * 15450 * @event cellSelectEvent 15451 * @param oArgs.el {HTMLElement} The selected TD element. 15452 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance. 15453 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance. 15454 * @param oArgs.key {String} (deprecated) The key of the selected cell. 15455 */ 15456 15457 /** 15458 * Fired when a cell is unselected. 15459 * 15460 * @event cellUnselectEvent 15461 * @param oArgs.el {HTMLElement} The unselected TD element. 15462 * @param oArgs.record {YAHOO.widget.Record} The associated Record. 15463 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance. 15464 * @param oArgs.key {String} (deprecated) The key of the unselected cell. 15465 15466 */ 15467 15468 /** 15469 * Fired when a cell is highlighted. 15470 * 15471 * @event cellHighlightEvent 15472 * @param oArgs.el {HTMLElement} The highlighted TD element. 15473 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance. 15474 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance. 15475 * @param oArgs.key {String} (deprecated) The key of the highlighted cell. 15476 15477 */ 15478 15479 /** 15480 * Fired when a cell is unhighlighted. 15481 * 15482 * @event cellUnhighlightEvent 15483 * @param oArgs.el {HTMLElement} The unhighlighted TD element. 15484 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance. 15485 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance. 15486 * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell. 15487 15488 */ 15489 15490 /** 15491 * Fired when all cell selections are cleared. 15492 * 15493 * @event unselectAllCellsEvent 15494 */ 15495 15496 /** 15497 * Fired when a CellEditor is shown. 15498 * 15499 * @event editorShowEvent 15500 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15501 */ 15502 15503 /** 15504 * Fired when a CellEditor has a keydown. 15505 * 15506 * @event editorKeydownEvent 15507 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15508 * @param oArgs.event {HTMLEvent} The event object. 15509 */ 15510 15511 /** 15512 * Fired when a CellEditor input is reverted. 15513 * 15514 * @event editorRevertEvent 15515 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15516 * @param oArgs.newData {Object} New data value from form input field. 15517 * @param oArgs.oldData {Object} Old data value. 15518 */ 15519 15520 /** 15521 * Fired when a CellEditor input is saved. 15522 * 15523 * @event editorSaveEvent 15524 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15525 * @param oArgs.newData {Object} New data value from form input field. 15526 * @param oArgs.oldData {Object} Old data value. 15527 */ 15528 15529 /** 15530 * Fired when a CellEditor input is canceled. 15531 * 15532 * @event editorCancelEvent 15533 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15534 */ 15535 15536 /** 15537 * Fired when a CellEditor has a blur event. 15538 * 15539 * @event editorBlurEvent 15540 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15541 */ 15542 15543 /** 15544 * Fired when a CellEditor is blocked. 15545 * 15546 * @event editorBlockEvent 15547 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15548 */ 15549 15550 /** 15551 * Fired when a CellEditor is unblocked. 15552 * 15553 * @event editorUnblockEvent 15554 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 15555 */ 15556 15557 15558 15559 15560 15561 /** 15562 * Fired when a link is clicked. 15563 * 15564 * @event linkClickEvent 15565 * @param oArgs.event {HTMLEvent} The event object. 15566 * @param oArgs.target {HTMLElement} The A element. 15567 */ 15568 15569 /** 15570 * Fired when a BUTTON element or INPUT element of type "button", "image", 15571 * "submit", "reset" is clicked. 15572 * 15573 * @event buttonClickEvent 15574 * @param oArgs.event {HTMLEvent} The event object. 15575 * @param oArgs.target {HTMLElement} The BUTTON element. 15576 */ 15577 15578 /** 15579 * Fired when a CHECKBOX element is clicked. 15580 * 15581 * @event checkboxClickEvent 15582 * @param oArgs.event {HTMLEvent} The event object. 15583 * @param oArgs.target {HTMLElement} The CHECKBOX element. 15584 */ 15585 15586 /** 15587 * Fired when a SELECT element is changed. 15588 * 15589 * @event dropdownChangeEvent 15590 * @param oArgs.event {HTMLEvent} The event object. 15591 * @param oArgs.target {HTMLElement} The SELECT element. 15592 */ 15593 15594 /** 15595 * Fired when a RADIO element is clicked. 15596 * 15597 * @event radioClickEvent 15598 * @param oArgs.event {HTMLEvent} The event object. 15599 * @param oArgs.target {HTMLElement} The RADIO element. 15600 */ 15601 15602 15603 15604 15605 15606 15607 15608 15609 15610 15611 15612 15613 15614 15615 15616 15617 15618 15619 15620 15621 15622 15623 15624 15625 15626 15627 ///////////////////////////////////////////////////////////////////////////// 15628 // 15629 // Deprecated APIs 15630 // 15631 ///////////////////////////////////////////////////////////////////////////// 15632 15633 /* 15634 * @method showCellEditorBtns 15635 * @deprecated Use CellEditor.renderBtns() 15636 */ 15637 showCellEditorBtns : function(elContainer) { 15638 // Buttons 15639 var elBtnsDiv = elContainer.appendChild(document.createElement("div")); 15640 Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON); 15641 15642 // Save button 15643 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button")); 15644 Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT); 15645 elSaveBtn.innerHTML = "OK"; 15646 Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) { 15647 oSelf.onEventSaveCellEditor(oArgs, oSelf); 15648 oSelf.focusTbodyEl(); 15649 }, this, true); 15650 15651 // Cancel button 15652 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button")); 15653 elCancelBtn.innerHTML = "Cancel"; 15654 Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) { 15655 oSelf.onEventCancelCellEditor(oArgs, oSelf); 15656 oSelf.focusTbodyEl(); 15657 }, this, true); 15658 15659 }, 15660 15661 /** 15662 * @method resetCellEditor 15663 * @deprecated Use destroyCellEditor 15664 */ 15665 resetCellEditor : function() { 15666 var elContainer = this._oCellEditor.container; 15667 elContainer.style.display = "none"; 15668 Ev.purgeElement(elContainer, true); 15669 elContainer.innerHTML = ""; 15670 this._oCellEditor.value = null; 15671 this._oCellEditor.isActive = false; 15672 15673 }, 15674 15675 /** 15676 * @event editorUpdateEvent 15677 * @deprecated Use CellEditor class. 15678 */ 15679 15680 /** 15681 * @method getBody 15682 * @deprecated Use getTbodyEl(). 15683 */ 15684 getBody : function() { 15685 // Backward compatibility 15686 return this.getTbodyEl(); 15687 }, 15688 15689 /** 15690 * @method getCell 15691 * @deprecated Use getTdEl(). 15692 */ 15693 getCell : function(index) { 15694 // Backward compatibility 15695 return this.getTdEl(index); 15696 }, 15697 15698 /** 15699 * @method getRow 15700 * @deprecated Use getTrEl(). 15701 */ 15702 getRow : function(index) { 15703 // Backward compatibility 15704 return this.getTrEl(index); 15705 }, 15706 15707 /** 15708 * @method refreshView 15709 * @deprecated Use render. 15710 */ 15711 refreshView : function() { 15712 // Backward compatibility 15713 this.render(); 15714 }, 15715 15716 /** 15717 * @method select 15718 * @deprecated Use selectRow. 15719 */ 15720 select : function(els) { 15721 // Backward compatibility 15722 if(!lang.isArray(els)) { 15723 els = [els]; 15724 } 15725 for(var i=0; i<els.length; i++) { 15726 this.selectRow(els[i]); 15727 } 15728 }, 15729 15730 /** 15731 * @method onEventEditCell 15732 * @deprecated Use onEventShowCellEditor. 15733 */ 15734 onEventEditCell : function(oArgs) { 15735 // Backward compatibility 15736 this.onEventShowCellEditor(oArgs); 15737 }, 15738 15739 /** 15740 * @method _syncColWidths 15741 * @deprecated Use validateColumnWidths. 15742 */ 15743 _syncColWidths : function() { 15744 // Backward compatibility 15745 this.validateColumnWidths(); 15746 } 15747 15748 /** 15749 * @event headerRowMouseoverEvent 15750 * @deprecated Use theadRowMouseoverEvent. 15751 */ 15752 15753 /** 15754 * @event headerRowMouseoutEvent 15755 * @deprecated Use theadRowMouseoutEvent. 15756 */ 15757 15758 /** 15759 * @event headerRowMousedownEvent 15760 * @deprecated Use theadRowMousedownEvent. 15761 */ 15762 15763 /** 15764 * @event headerRowClickEvent 15765 * @deprecated Use theadRowClickEvent. 15766 */ 15767 15768 /** 15769 * @event headerRowDblclickEvent 15770 * @deprecated Use theadRowDblclickEvent. 15771 */ 15772 15773 /** 15774 * @event headerCellMouseoverEvent 15775 * @deprecated Use theadCellMouseoverEvent. 15776 */ 15777 15778 /** 15779 * @event headerCellMouseoutEvent 15780 * @deprecated Use theadCellMouseoutEvent. 15781 */ 15782 15783 /** 15784 * @event headerCellMousedownEvent 15785 * @deprecated Use theadCellMousedownEvent. 15786 */ 15787 15788 /** 15789 * @event headerCellClickEvent 15790 * @deprecated Use theadCellClickEvent. 15791 */ 15792 15793 /** 15794 * @event headerCellDblclickEvent 15795 * @deprecated Use theadCellDblclickEvent. 15796 */ 15797 15798 /** 15799 * @event headerLabelMouseoverEvent 15800 * @deprecated Use theadLabelMouseoverEvent. 15801 */ 15802 15803 /** 15804 * @event headerLabelMouseoutEvent 15805 * @deprecated Use theadLabelMouseoutEvent. 15806 */ 15807 15808 /** 15809 * @event headerLabelMousedownEvent 15810 * @deprecated Use theadLabelMousedownEvent. 15811 */ 15812 15813 /** 15814 * @event headerLabelClickEvent 15815 * @deprecated Use theadLabelClickEvent. 15816 */ 15817 15818 /** 15819 * @event headerLabelDbllickEvent 15820 * @deprecated Use theadLabelDblclickEvent. 15821 */ 15822 15823 }); 15824 15825 /** 15826 * Alias for onDataReturnSetRows for backward compatibility 15827 * @method onDataReturnSetRecords 15828 * @deprecated Use onDataReturnSetRows 15829 */ 15830 DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows; 15831 15832 /** 15833 * Alias for onPaginatorChange for backward compatibility 15834 * @method onPaginatorChange 15835 * @deprecated Use onPaginatorChangeRequest 15836 */ 15837 DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest; 15838 15839 ///////////////////////////////////////////////////////////////////////////// 15840 // 15841 // Deprecated static APIs 15842 // 15843 ///////////////////////////////////////////////////////////////////////////// 15844 /** 15845 * @method DataTable.editCheckbox 15846 * @deprecated Use YAHOO.widget.CheckboxCellEditor. 15847 */ 15848 DT.editCheckbox = function() {}; 15849 15850 /** 15851 * @method DataTable.editDate 15852 * @deprecated Use YAHOO.widget.DateCellEditor. 15853 */ 15854 DT.editDate = function() {}; 15855 15856 /** 15857 * @method DataTable.editDropdown 15858 * @deprecated Use YAHOO.widget.DropdownCellEditor. 15859 */ 15860 DT.editDropdown = function() {}; 15861 15862 /** 15863 * @method DataTable.editRadio 15864 * @deprecated Use YAHOO.widget.RadioCellEditor. 15865 */ 15866 DT.editRadio = function() {}; 15867 15868 /** 15869 * @method DataTable.editTextarea 15870 * @deprecated Use YAHOO.widget.TextareaCellEditor 15871 */ 15872 DT.editTextarea = function() {}; 15873 15874 /** 15875 * @method DataTable.editTextbox 15876 * @deprecated Use YAHOO.widget.TextboxCellEditor 15877 */ 15878 DT.editTextbox= function() {}; 15879 15880 })(); 15881 15882 (function () { 15883 15884 var lang = YAHOO.lang, 15885 util = YAHOO.util, 15886 widget = YAHOO.widget, 15887 ua = YAHOO.env.ua, 15888 15889 Dom = util.Dom, 15890 Ev = util.Event, 15891 DS = util.DataSourceBase, 15892 DT = widget.DataTable, 15893 Pag = widget.Paginator; 15894 15895 /** 15896 * The ScrollingDataTable class extends the DataTable class to provide 15897 * functionality for x-scrolling, y-scrolling, and xy-scrolling. 15898 * 15899 * @namespace YAHOO.widget 15900 * @class ScrollingDataTable 15901 * @extends YAHOO.widget.DataTable 15902 * @constructor 15903 * @param elContainer {HTMLElement} Container element for the TABLE. 15904 * @param aColumnDefs {Object[]} Array of object literal Column definitions. 15905 * @param oDataSource {YAHOO.util.DataSource} DataSource instance. 15906 * @param oConfigs {object} (optional) Object literal of configuration values. 15907 */ 15908 widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) { 15909 oConfigs = oConfigs || {}; 15910 15911 // Prevent infinite loop 15912 if(oConfigs.scrollable) { 15913 oConfigs.scrollable = false; 15914 } 15915 15916 this._init(); 15917 15918 widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs); 15919 15920 // Once per instance 15921 this.subscribe("columnShowEvent", this._onColumnChange); 15922 }; 15923 15924 var SDT = widget.ScrollingDataTable; 15925 15926 ///////////////////////////////////////////////////////////////////////////// 15927 // 15928 // Public constants 15929 // 15930 ///////////////////////////////////////////////////////////////////////////// 15931 lang.augmentObject(SDT, { 15932 15933 /** 15934 * Class name assigned to inner DataTable header container. 15935 * 15936 * @property DataTable.CLASS_HEADER 15937 * @type String 15938 * @static 15939 * @final 15940 * @default "yui-dt-hd" 15941 */ 15942 CLASS_HEADER : "yui-dt-hd", 15943 15944 /** 15945 * Class name assigned to inner DataTable body container. 15946 * 15947 * @property DataTable.CLASS_BODY 15948 * @type String 15949 * @static 15950 * @final 15951 * @default "yui-dt-bd" 15952 */ 15953 CLASS_BODY : "yui-dt-bd" 15954 }); 15955 15956 lang.extend(SDT, DT, { 15957 15958 /** 15959 * Container for fixed header TABLE element. 15960 * 15961 * @property _elHdContainer 15962 * @type HTMLElement 15963 * @private 15964 */ 15965 _elHdContainer : null, 15966 15967 /** 15968 * Fixed header TABLE element. 15969 * 15970 * @property _elHdTable 15971 * @type HTMLElement 15972 * @private 15973 */ 15974 _elHdTable : null, 15975 15976 /** 15977 * Container for scrolling body TABLE element. 15978 * 15979 * @property _elBdContainer 15980 * @type HTMLElement 15981 * @private 15982 */ 15983 _elBdContainer : null, 15984 15985 /** 15986 * Body THEAD element. 15987 * 15988 * @property _elBdThead 15989 * @type HTMLElement 15990 * @private 15991 */ 15992 _elBdThead : null, 15993 15994 /** 15995 * Offscreen container to temporarily clone SDT for auto-width calculation. 15996 * 15997 * @property _elTmpContainer 15998 * @type HTMLElement 15999 * @private 16000 */ 16001 _elTmpContainer : null, 16002 16003 /** 16004 * Offscreen TABLE element for auto-width calculation. 16005 * 16006 * @property _elTmpTable 16007 * @type HTMLElement 16008 * @private 16009 */ 16010 _elTmpTable : null, 16011 16012 /** 16013 * True if x-scrollbar is currently visible. 16014 * @property _bScrollbarX 16015 * @type Boolean 16016 * @private 16017 */ 16018 _bScrollbarX : null, 16019 16020 16021 16022 16023 16024 16025 16026 16027 16028 16029 16030 16031 16032 16033 16034 ///////////////////////////////////////////////////////////////////////////// 16035 // 16036 // Superclass methods 16037 // 16038 ///////////////////////////////////////////////////////////////////////////// 16039 16040 /** 16041 * Implementation of Element's abstract method. Sets up config values. 16042 * 16043 * @method initAttributes 16044 * @param oConfigs {Object} (Optional) Object literal definition of configuration values. 16045 * @private 16046 */ 16047 16048 initAttributes : function(oConfigs) { 16049 oConfigs = oConfigs || {}; 16050 SDT.superclass.initAttributes.call(this, oConfigs); 16051 16052 /** 16053 * @attribute width 16054 * @description Table width for scrollable tables (e.g., "40em"). 16055 * @type String 16056 */ 16057 this.setAttributeConfig("width", { 16058 value: null, 16059 validator: lang.isString, 16060 method: function(oParam) { 16061 if(this._elHdContainer && this._elBdContainer) { 16062 this._elHdContainer.style.width = oParam; 16063 this._elBdContainer.style.width = oParam; 16064 this._syncScrollX(); 16065 this._syncScrollOverhang(); 16066 } 16067 } 16068 }); 16069 16070 /** 16071 * @attribute height 16072 * @description Table body height for scrollable tables, not including headers (e.g., "40em"). 16073 * @type String 16074 */ 16075 this.setAttributeConfig("height", { 16076 value: null, 16077 validator: lang.isString, 16078 method: function(oParam) { 16079 if(this._elHdContainer && this._elBdContainer) { 16080 this._elBdContainer.style.height = oParam; 16081 this._syncScrollX(); 16082 this._syncScrollY(); 16083 this._syncScrollOverhang(); 16084 } 16085 } 16086 }); 16087 16088 /** 16089 * @attribute COLOR_COLUMNFILLER 16090 * @description CSS color value assigned to header filler on scrollable tables. 16091 * @type String 16092 * @default "#F2F2F2" 16093 */ 16094 this.setAttributeConfig("COLOR_COLUMNFILLER", { 16095 value: "#F2F2F2", 16096 validator: lang.isString, 16097 method: function(oParam) { 16098 if(this._elHdContainer) { 16099 this._elHdContainer.style.backgroundColor = oParam; 16100 } 16101 } 16102 }); 16103 }, 16104 16105 /** 16106 * Initializes internal variables. 16107 * 16108 * @method _init 16109 * @private 16110 */ 16111 _init : function() { 16112 this._elHdContainer = null; 16113 this._elHdTable = null; 16114 this._elBdContainer = null; 16115 this._elBdThead = null; 16116 this._elTmpContainer = null; 16117 this._elTmpTable = null; 16118 }, 16119 16120 /** 16121 * Initializes DOM elements for a ScrollingDataTable, including creation of 16122 * two separate TABLE elements. 16123 * 16124 * @method _initDomElements 16125 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 16126 * return {Boolean} False in case of error, otherwise true 16127 * @private 16128 */ 16129 _initDomElements : function(elContainer) { 16130 // Outer and inner containers 16131 this._initContainerEl(elContainer); 16132 if(this._elContainer && this._elHdContainer && this._elBdContainer) { 16133 // TABLEs 16134 this._initTableEl(); 16135 16136 if(this._elHdTable && this._elTable) { 16137 // COLGROUPs 16138 ///this._initColgroupEl(this._elHdTable, this._elTable); 16139 this._initColgroupEl(this._elHdTable); 16140 16141 // THEADs 16142 this._initTheadEl(this._elHdTable, this._elTable); 16143 16144 // Primary TBODY 16145 this._initTbodyEl(this._elTable); 16146 // Message TBODY 16147 this._initMsgTbodyEl(this._elTable); 16148 } 16149 } 16150 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody || 16151 !this._elHdTable || !this._elBdThead) { 16152 return false; 16153 } 16154 else { 16155 return true; 16156 } 16157 }, 16158 16159 /** 16160 * Destroy's the DataTable outer and inner container elements, if available. 16161 * 16162 * @method _destroyContainerEl 16163 * @param elContainer {HTMLElement} Reference to the container element. 16164 * @private 16165 */ 16166 _destroyContainerEl : function(elContainer) { 16167 Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE); 16168 SDT.superclass._destroyContainerEl.call(this, elContainer); 16169 this._elHdContainer = null; 16170 this._elBdContainer = null; 16171 }, 16172 16173 /** 16174 * Initializes the DataTable outer container element and creates inner header 16175 * and body container elements. 16176 * 16177 * @method _initContainerEl 16178 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID. 16179 * @private 16180 */ 16181 _initContainerEl : function(elContainer) { 16182 SDT.superclass._initContainerEl.call(this, elContainer); 16183 16184 if(this._elContainer) { 16185 elContainer = this._elContainer; // was constructor input, now is DOM ref 16186 Dom.addClass(elContainer, DT.CLASS_SCROLLABLE); 16187 16188 // Container for header TABLE 16189 var elHdContainer = document.createElement("div"); 16190 elHdContainer.style.width = this.get("width") || ""; 16191 elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER"); 16192 Dom.addClass(elHdContainer, SDT.CLASS_HEADER); 16193 this._elHdContainer = elHdContainer; 16194 elContainer.appendChild(elHdContainer); 16195 16196 // Container for body TABLE 16197 var elBdContainer = document.createElement("div"); 16198 elBdContainer.style.width = this.get("width") || ""; 16199 elBdContainer.style.height = this.get("height") || ""; 16200 Dom.addClass(elBdContainer, SDT.CLASS_BODY); 16201 Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers 16202 this._elBdContainer = elBdContainer; 16203 elContainer.appendChild(elBdContainer); 16204 } 16205 }, 16206 16207 /** 16208 * Creates HTML markup CAPTION element. 16209 * 16210 * @method _initCaptionEl 16211 * @param sCaption {String} Text for caption. 16212 * @private 16213 */ 16214 _initCaptionEl : function(sCaption) { 16215 // Not yet supported 16216 /*if(this._elHdTable && sCaption) { 16217 // Create CAPTION element 16218 if(!this._elCaption) { 16219 this._elCaption = this._elHdTable.createCaption(); 16220 } 16221 // Set CAPTION value 16222 this._elCaption.innerHTML = sCaption; 16223 } 16224 else if(this._elCaption) { 16225 this._elCaption.parentNode.removeChild(this._elCaption); 16226 }*/ 16227 }, 16228 16229 /** 16230 * Destroy's the DataTable head TABLE element, if available. 16231 * 16232 * @method _destroyHdTableEl 16233 * @private 16234 */ 16235 _destroyHdTableEl : function() { 16236 var elTable = this._elHdTable; 16237 if(elTable) { 16238 Ev.purgeElement(elTable, true); 16239 elTable.parentNode.removeChild(elTable); 16240 16241 // A little out of place, but where else can we null out these extra elements? 16242 ///this._elBdColgroup = null; 16243 this._elBdThead = null; 16244 } 16245 }, 16246 16247 /** 16248 * Initializes ScrollingDataTable TABLE elements into the two inner containers. 16249 * 16250 * @method _initTableEl 16251 * @private 16252 */ 16253 _initTableEl : function() { 16254 // Head TABLE 16255 if(this._elHdContainer) { 16256 this._destroyHdTableEl(); 16257 16258 // Create TABLE 16259 this._elHdTable = this._elHdContainer.appendChild(document.createElement("table")); 16260 16261 // Set up mouseover/mouseout events via mouseenter/mouseleave delegation 16262 Ev.delegate(this._elHdTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this); 16263 Ev.delegate(this._elHdTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this); 16264 } 16265 // Body TABLE 16266 SDT.superclass._initTableEl.call(this, this._elBdContainer); 16267 }, 16268 16269 /** 16270 * Initializes ScrollingDataTable THEAD elements into the two inner containers. 16271 * 16272 * @method _initTheadEl 16273 * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference. 16274 * @param elTable {HTMLElement} (optional) TABLE element reference. 16275 * @private 16276 */ 16277 _initTheadEl : function(elHdTable, elTable) { 16278 elHdTable = elHdTable || this._elHdTable; 16279 elTable = elTable || this._elTable; 16280 16281 // Scrolling body's THEAD 16282 this._initBdTheadEl(elTable); 16283 // Standard fixed head THEAD 16284 SDT.superclass._initTheadEl.call(this, elHdTable); 16285 }, 16286 16287 /** 16288 * SDT changes ID so as not to duplicate the accessibility TH IDs. 16289 * 16290 * @method _initThEl 16291 * @param elTh {HTMLElement} TH element reference. 16292 * @param oColumn {YAHOO.widget.Column} Column object. 16293 * @private 16294 */ 16295 _initThEl : function(elTh, oColumn) { 16296 SDT.superclass._initThEl.call(this, elTh, oColumn); 16297 elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD 16298 }, 16299 16300 /** 16301 * Destroy's the DataTable body THEAD element, if available. 16302 * 16303 * @method _destroyBdTheadEl 16304 * @private 16305 */ 16306 _destroyBdTheadEl : function() { 16307 var elBdThead = this._elBdThead; 16308 if(elBdThead) { 16309 var elTable = elBdThead.parentNode; 16310 Ev.purgeElement(elBdThead, true); 16311 elTable.removeChild(elBdThead); 16312 this._elBdThead = null; 16313 16314 this._destroyColumnHelpers(); 16315 } 16316 }, 16317 16318 /** 16319 * Initializes body THEAD element. 16320 * 16321 * @method _initBdTheadEl 16322 * @param elTable {HTMLElement} TABLE element into which to create THEAD. 16323 * @return {HTMLElement} Initialized THEAD element. 16324 * @private 16325 */ 16326 _initBdTheadEl : function(elTable) { 16327 if(elTable) { 16328 // Destroy previous 16329 this._destroyBdTheadEl(); 16330 16331 var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild); 16332 16333 // Add TRs to the THEAD; 16334 var oColumnSet = this._oColumnSet, 16335 colTree = oColumnSet.tree, 16336 elTh, elTheadTr, oColumn, i, j, k, len; 16337 16338 for(i=0, k=colTree.length; i<k; i++) { 16339 elTheadTr = elThead.appendChild(document.createElement("tr")); 16340 16341 // ...and create TH cells 16342 for(j=0, len=colTree[i].length; j<len; j++) { 16343 oColumn = colTree[i][j]; 16344 elTh = elTheadTr.appendChild(document.createElement("th")); 16345 this._initBdThEl(elTh,oColumn,i,j); 16346 } 16347 } 16348 this._elBdThead = elThead; 16349 } 16350 }, 16351 16352 /** 16353 * Populates TH element for the body THEAD element. 16354 * 16355 * @method _initBdThEl 16356 * @param elTh {HTMLElement} TH element reference. 16357 * @param oColumn {YAHOO.widget.Column} Column object. 16358 * @private 16359 */ 16360 _initBdThEl : function(elTh, oColumn) { 16361 elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility 16362 elTh.rowSpan = oColumn.getRowspan(); 16363 elTh.colSpan = oColumn.getColspan(); 16364 // Assign abbr attribute 16365 if(oColumn.abbr) { 16366 elTh.abbr = oColumn.abbr; 16367 } 16368 16369 // TODO: strip links and form elements 16370 var sKey = oColumn.getKey(); 16371 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey; 16372 elTh.innerHTML = sLabel; 16373 }, 16374 16375 /** 16376 * Initializes ScrollingDataTable TBODY element for data 16377 * 16378 * @method _initTbodyEl 16379 * @param elTable {HTMLElement} TABLE element into which to create TBODY . 16380 * @private 16381 */ 16382 _initTbodyEl : function(elTable) { 16383 SDT.superclass._initTbodyEl.call(this, elTable); 16384 16385 // Bug 2105534 - Safari 3 gap 16386 // Bug 2492591 - IE8 offsetTop 16387 elTable.style.marginTop = (this._elTbody.offsetTop > 0) ? 16388 "-"+this._elTbody.offsetTop+"px" : 0; 16389 }, 16390 16391 16392 16393 16394 16395 16396 16397 16398 16399 16400 16401 16402 16403 16404 16405 16406 16407 16408 16409 16410 16411 16412 16413 16414 16415 16416 16417 16418 16419 /** 16420 * Sets focus on the given element. 16421 * 16422 * @method _focusEl 16423 * @param el {HTMLElement} Element. 16424 * @private 16425 */ 16426 _focusEl : function(el) { 16427 el = el || this._elTbody; 16428 var oSelf = this; 16429 this._storeScrollPositions(); 16430 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets 16431 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing 16432 // strange unexpected things as the user clicks on buttons and other controls. 16433 16434 // Bug 1921135: Wrap the whole thing in a setTimeout 16435 setTimeout(function() { 16436 setTimeout(function() { 16437 try { 16438 el.focus(); 16439 oSelf._restoreScrollPositions(); 16440 } 16441 catch(e) { 16442 } 16443 },0); 16444 }, 0); 16445 }, 16446 16447 16448 16449 16450 16451 16452 16453 16454 16455 16456 16457 16458 16459 16460 16461 16462 16463 16464 16465 /** 16466 * Internal wrapper calls run() on render Chain instance. 16467 * 16468 * @method _runRenderChain 16469 * @private 16470 */ 16471 _runRenderChain : function() { 16472 this._storeScrollPositions(); 16473 this._oChainRender.run(); 16474 }, 16475 16476 /** 16477 * Stores scroll positions so they can be restored after a render. 16478 * 16479 * @method _storeScrollPositions 16480 * @private 16481 */ 16482 _storeScrollPositions : function() { 16483 this._nScrollTop = this._elBdContainer.scrollTop; 16484 this._nScrollLeft = this._elBdContainer.scrollLeft; 16485 }, 16486 16487 /** 16488 * Clears stored scroll positions to interrupt the automatic restore mechanism. 16489 * Useful for setting scroll positions programmatically rather than as part of 16490 * the post-render cleanup process. 16491 * 16492 * @method clearScrollPositions 16493 * @private 16494 */ 16495 clearScrollPositions : function() { 16496 this._nScrollTop = 0; 16497 this._nScrollLeft = 0; 16498 }, 16499 16500 /** 16501 * Restores scroll positions to stored value. 16502 * 16503 * @method _retoreScrollPositions 16504 * @private 16505 */ 16506 _restoreScrollPositions : function() { 16507 // Reset scroll positions 16508 if(this._nScrollTop) { 16509 this._elBdContainer.scrollTop = this._nScrollTop; 16510 this._nScrollTop = null; 16511 } 16512 if(this._nScrollLeft) { 16513 this._elBdContainer.scrollLeft = this._nScrollLeft; 16514 // Bug 2529024 16515 this._elHdContainer.scrollLeft = this._nScrollLeft; 16516 this._nScrollLeft = null; 16517 } 16518 }, 16519 16520 /** 16521 * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable. 16522 * 16523 * @method _validateColumnWidth 16524 * @param oColumn {YAHOO.widget.Column} Column instance. 16525 * @param elTd {HTMLElement} TD element to validate against. 16526 * @private 16527 */ 16528 _validateColumnWidth : function(oColumn, elTd) { 16529 // Only Columns without widths that are not hidden 16530 if(!oColumn.width && !oColumn.hidden) { 16531 var elTh = oColumn.getThEl(); 16532 // Unset a calculated auto-width 16533 if(oColumn._calculatedWidth) { 16534 this._setColumnWidth(oColumn, "auto", "visible"); 16535 } 16536 // Compare auto-widths 16537 if(elTh.offsetWidth !== elTd.offsetWidth) { 16538 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ? 16539 oColumn.getThLinerEl() : elTd.firstChild; 16540 16541 // Grab the wider liner width, unless the minWidth is wider 16542 var newWidth = Math.max(0, 16543 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)), 16544 oColumn.minWidth); 16545 16546 var sOverflow = 'visible'; 16547 16548 // Now validate against maxAutoWidth 16549 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) { 16550 newWidth = oColumn.maxAutoWidth; 16551 sOverflow = "hidden"; 16552 } 16553 16554 // Set to the wider auto-width 16555 this._elTbody.style.display = "none"; 16556 this._setColumnWidth(oColumn, newWidth+'px', sOverflow); 16557 oColumn._calculatedWidth = newWidth; 16558 this._elTbody.style.display = ""; 16559 } 16560 } 16561 }, 16562 16563 /** 16564 * For one or all Columns of a ScrollingDataTable, when Column is not hidden, 16565 * and width is not set, syncs widths of header and body cells and 16566 * validates that width against minWidth and/or maxAutoWidth as necessary. 16567 * 16568 * @method validateColumnWidths 16569 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated. 16570 */ 16571 validateColumnWidths : function(oColumn) { 16572 // Validate there is at least one TR with proper TDs 16573 var allKeys = this._oColumnSet.keys, 16574 allKeysLength = allKeys.length, 16575 elRow = this.getFirstTrEl(); 16576 16577 // Reset overhang for IE 16578 if(ua.ie) { 16579 this._setOverhangValue(1); 16580 } 16581 16582 if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) { 16583 // Temporarily unsnap container since it causes inaccurate calculations 16584 var sWidth = this.get("width"); 16585 if(sWidth) { 16586 this._elHdContainer.style.width = ""; 16587 this._elBdContainer.style.width = ""; 16588 } 16589 this._elContainer.style.width = ""; 16590 16591 //Validate just one Column 16592 if(oColumn && lang.isNumber(oColumn.getKeyIndex())) { 16593 this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]); 16594 } 16595 // Iterate through all Columns to unset calculated widths in one pass 16596 else { 16597 var elTd, todos = [], thisTodo, i, len; 16598 for(i=0; i<allKeysLength; i++) { 16599 oColumn = allKeys[i]; 16600 // Only Columns without widths that are not hidden, unset a calculated auto-width 16601 if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) { 16602 todos[todos.length] = oColumn; 16603 } 16604 } 16605 16606 this._elTbody.style.display = "none"; 16607 for(i=0, len=todos.length; i<len; i++) { 16608 this._setColumnWidth(todos[i], "auto", "visible"); 16609 } 16610 this._elTbody.style.display = ""; 16611 16612 todos = []; 16613 16614 // Iterate through all Columns and make the store the adjustments to make in one pass 16615 for(i=0; i<allKeysLength; i++) { 16616 oColumn = allKeys[i]; 16617 elTd = elRow.childNodes[i]; 16618 // Only Columns without widths that are not hidden 16619 if(!oColumn.width && !oColumn.hidden) { 16620 var elTh = oColumn.getThEl(); 16621 16622 // Compare auto-widths 16623 if(elTh.offsetWidth !== elTd.offsetWidth) { 16624 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ? 16625 oColumn.getThLinerEl() : elTd.firstChild; 16626 16627 // Grab the wider liner width, unless the minWidth is wider 16628 var newWidth = Math.max(0, 16629 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)), 16630 oColumn.minWidth); 16631 16632 var sOverflow = 'visible'; 16633 16634 // Now validate against maxAutoWidth 16635 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) { 16636 newWidth = oColumn.maxAutoWidth; 16637 sOverflow = "hidden"; 16638 } 16639 16640 todos[todos.length] = [oColumn, newWidth, sOverflow]; 16641 } 16642 } 16643 } 16644 16645 this._elTbody.style.display = "none"; 16646 for(i=0, len=todos.length; i<len; i++) { 16647 thisTodo = todos[i]; 16648 // Set to the wider auto-width 16649 this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]); 16650 thisTodo[0]._calculatedWidth = thisTodo[1]; 16651 } 16652 this._elTbody.style.display = ""; 16653 } 16654 16655 // Resnap unsnapped containers 16656 if(sWidth) { 16657 this._elHdContainer.style.width = sWidth; 16658 this._elBdContainer.style.width = sWidth; 16659 } 16660 } 16661 16662 this._syncScroll(); 16663 this._restoreScrollPositions(); 16664 }, 16665 16666 /** 16667 * Syncs padding around scrollable tables, including Column header right-padding 16668 * and container width and height. 16669 * 16670 * @method _syncScroll 16671 * @private 16672 */ 16673 _syncScroll : function() { 16674 this._syncScrollX(); 16675 this._syncScrollY(); 16676 this._syncScrollOverhang(); 16677 if(ua.opera) { 16678 // Bug 1925874 16679 this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft; 16680 if(!this.get("width")) { 16681 // Bug 1926125 16682 document.body.style += ''; 16683 } 16684 } 16685 }, 16686 16687 /** 16688 * Snaps container width for y-scrolling tables. 16689 * 16690 * @method _syncScrollY 16691 * @private 16692 */ 16693 _syncScrollY : function() { 16694 var elTbody = this._elTbody, 16695 elBdContainer = this._elBdContainer; 16696 16697 // X-scrolling not enabled 16698 if(!this.get("width")) { 16699 // Snap outer container width to content 16700 this._elContainer.style.width = 16701 (elBdContainer.scrollHeight > elBdContainer.clientHeight) ? 16702 // but account for y-scrollbar since it is visible 16703 (elTbody.parentNode.clientWidth + 19) + "px" : 16704 // no y-scrollbar, just borders 16705 (elTbody.parentNode.clientWidth + 2) + "px"; 16706 } 16707 }, 16708 16709 /** 16710 * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width. 16711 * 16712 * @method _syncScrollX 16713 * @private 16714 */ 16715 _syncScrollX : function() { 16716 var elTbody = this._elTbody, 16717 elBdContainer = this._elBdContainer; 16718 16719 // IE 6 and 7 only when y-scrolling not enabled 16720 if(!this.get("height") && (ua.ie)) { 16721 // Snap outer container height to content 16722 elBdContainer.style.height = 16723 // but account for x-scrollbar if it is visible 16724 (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ? 16725 (elTbody.parentNode.offsetHeight + 18) + "px" : 16726 elTbody.parentNode.offsetHeight + "px"; 16727 } 16728 16729 // Sync message tbody 16730 if(this._elTbody.rows.length === 0) { 16731 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px"; 16732 } 16733 else { 16734 this._elMsgTbody.parentNode.style.width = ""; 16735 } 16736 }, 16737 16738 /** 16739 * Adds/removes Column header overhang as necesary. 16740 * 16741 * @method _syncScrollOverhang 16742 * @private 16743 */ 16744 _syncScrollOverhang : function() { 16745 var elBdContainer = this._elBdContainer, 16746 // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table 16747 nPadding = 1; 16748 16749 // Y-scrollbar is visible, which is when the overhang needs to jut out 16750 if((elBdContainer.scrollHeight > elBdContainer.clientHeight) && 16751 // X-scrollbar is also visible, which means the right is jagged, not flush with the Column 16752 (elBdContainer.scrollWidth > elBdContainer.clientWidth)) { 16753 nPadding = 18; 16754 } 16755 16756 this._setOverhangValue(nPadding); 16757 16758 }, 16759 16760 /** 16761 * Sets Column header overhang to given width. 16762 * 16763 * @method _setOverhangValue 16764 * @param nBorderWidth {Number} Value of new border for overhang. 16765 * @private 16766 */ 16767 _setOverhangValue : function(nBorderWidth) { 16768 var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [], 16769 len = aLastHeaders.length, 16770 sPrefix = this._sId+"-fixedth-", 16771 sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER"); 16772 16773 this._elThead.style.display = "none"; 16774 for(var i=0; i<len; i++) { 16775 Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue; 16776 } 16777 this._elThead.style.display = ""; 16778 }, 16779 16780 16781 16782 16783 16784 16785 16786 16787 16788 16789 16790 16791 16792 16793 16794 16795 16796 16797 16798 16799 16800 16801 16802 16803 16804 16805 16806 16807 16808 16809 16810 16811 16812 16813 16814 16815 16816 16817 /** 16818 * Returns DOM reference to the DataTable's fixed header container element. 16819 * 16820 * @method getHdContainerEl 16821 * @return {HTMLElement} Reference to DIV element. 16822 */ 16823 getHdContainerEl : function() { 16824 return this._elHdContainer; 16825 }, 16826 16827 /** 16828 * Returns DOM reference to the DataTable's scrolling body container element. 16829 * 16830 * @method getBdContainerEl 16831 * @return {HTMLElement} Reference to DIV element. 16832 */ 16833 getBdContainerEl : function() { 16834 return this._elBdContainer; 16835 }, 16836 16837 /** 16838 * Returns DOM reference to the DataTable's fixed header TABLE element. 16839 * 16840 * @method getHdTableEl 16841 * @return {HTMLElement} Reference to TABLE element. 16842 */ 16843 getHdTableEl : function() { 16844 return this._elHdTable; 16845 }, 16846 16847 /** 16848 * Returns DOM reference to the DataTable's scrolling body TABLE element. 16849 * 16850 * @method getBdTableEl 16851 * @return {HTMLElement} Reference to TABLE element. 16852 */ 16853 getBdTableEl : function() { 16854 return this._elTable; 16855 }, 16856 16857 /** 16858 * Disables ScrollingDataTable UI. 16859 * 16860 * @method disable 16861 */ 16862 disable : function() { 16863 var elMask = this._elMask; 16864 elMask.style.width = this._elBdContainer.offsetWidth + "px"; 16865 elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px"; 16866 elMask.style.display = ""; 16867 this.fireEvent("disableEvent"); 16868 }, 16869 16870 /** 16871 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove 16872 * non-nested Columns, and top-level parent Columns (which will remove all 16873 * children Columns). 16874 * 16875 * @method removeColumn 16876 * @param oColumn {YAHOO.widget.Column} Column instance. 16877 * @return oColumn {YAHOO.widget.Column} Removed Column instance. 16878 */ 16879 removeColumn : function(oColumn) { 16880 // Store scroll pos 16881 var hdPos = this._elHdContainer.scrollLeft; 16882 var bdPos = this._elBdContainer.scrollLeft; 16883 16884 // Call superclass method 16885 oColumn = SDT.superclass.removeColumn.call(this, oColumn); 16886 16887 // Restore scroll pos 16888 this._elHdContainer.scrollLeft = hdPos; 16889 this._elBdContainer.scrollLeft = bdPos; 16890 16891 return oColumn; 16892 }, 16893 16894 /** 16895 * Inserts given Column at the index if given, otherwise at the end. NOTE: You 16896 * can only add non-nested Columns and top-level parent Columns. You cannot add 16897 * a nested Column to an existing parent. 16898 * 16899 * @method insertColumn 16900 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column 16901 * definition or a Column instance. 16902 * @param index {Number} (optional) New tree index. 16903 * @return oColumn {YAHOO.widget.Column} Inserted Column instance. 16904 */ 16905 insertColumn : function(oColumn, index) { 16906 // Store scroll pos 16907 var hdPos = this._elHdContainer.scrollLeft; 16908 var bdPos = this._elBdContainer.scrollLeft; 16909 16910 // Call superclass method 16911 var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index); 16912 16913 // Restore scroll pos 16914 this._elHdContainer.scrollLeft = hdPos; 16915 this._elBdContainer.scrollLeft = bdPos; 16916 16917 return oNewColumn; 16918 }, 16919 16920 /** 16921 * Removes given Column and inserts into given tree index. NOTE: You 16922 * can only reorder non-nested Columns and top-level parent Columns. You cannot 16923 * reorder a nested Column to an existing parent. 16924 * 16925 * @method reorderColumn 16926 * @param oColumn {YAHOO.widget.Column} Column instance. 16927 * @param index {Number} New tree index. 16928 */ 16929 reorderColumn : function(oColumn, index) { 16930 // Store scroll pos 16931 var hdPos = this._elHdContainer.scrollLeft; 16932 var bdPos = this._elBdContainer.scrollLeft; 16933 16934 // Call superclass method 16935 var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index); 16936 16937 // Restore scroll pos 16938 this._elHdContainer.scrollLeft = hdPos; 16939 this._elBdContainer.scrollLeft = bdPos; 16940 16941 return oNewColumn; 16942 }, 16943 16944 /** 16945 * Sets given Column to given pixel width. If new width is less than minWidth 16946 * width, sets to minWidth. Updates oColumn.width value. 16947 * 16948 * @method setColumnWidth 16949 * @param oColumn {YAHOO.widget.Column} Column instance. 16950 * @param nWidth {Number} New width in pixels. 16951 */ 16952 setColumnWidth : function(oColumn, nWidth) { 16953 oColumn = this.getColumn(oColumn); 16954 if(oColumn) { 16955 this._storeScrollPositions(); 16956 16957 // Validate new width against minWidth 16958 if(lang.isNumber(nWidth)) { 16959 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth; 16960 16961 // Save state 16962 oColumn.width = nWidth; 16963 16964 // Resize the DOM elements 16965 this._setColumnWidth(oColumn, nWidth+"px"); 16966 this._syncScroll(); 16967 16968 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth}); 16969 } 16970 // Unsets a width to auto-size 16971 else if(nWidth === null) { 16972 // Save state 16973 oColumn.width = nWidth; 16974 16975 // Resize the DOM elements 16976 this._setColumnWidth(oColumn, "auto"); 16977 this.validateColumnWidths(oColumn); 16978 this.fireEvent("columnUnsetWidthEvent",{column:oColumn}); 16979 } 16980 16981 // Bug 2339454: resize then sort misaligment 16982 this._clearTrTemplateEl(); 16983 } 16984 else { 16985 } 16986 }, 16987 16988 /** 16989 * Scrolls to given row or cell 16990 * 16991 * @method scrollTo 16992 * @param to {YAHOO.widget.Record | HTMLElement } Itme to scroll to. 16993 */ 16994 scrollTo : function(to) { 16995 var td = this.getTdEl(to); 16996 if(td) { 16997 this.clearScrollPositions(); 16998 this.getBdContainerEl().scrollLeft = td.offsetLeft; 16999 this.getBdContainerEl().scrollTop = td.parentNode.offsetTop; 17000 } 17001 else { 17002 var tr = this.getTrEl(to); 17003 if(tr) { 17004 this.clearScrollPositions(); 17005 this.getBdContainerEl().scrollTop = tr.offsetTop; 17006 } 17007 } 17008 }, 17009 17010 /** 17011 * Displays message within secondary TBODY. 17012 * 17013 * @method showTableMessage 17014 * @param sHTML {String} (optional) Value for innerHTMlang. 17015 * @param sClassName {String} (optional) Classname. 17016 */ 17017 showTableMessage : function(sHTML, sClassName) { 17018 var elCell = this._elMsgTd; 17019 if(lang.isString(sHTML)) { 17020 elCell.firstChild.innerHTML = sHTML; 17021 } 17022 if(lang.isString(sClassName)) { 17023 Dom.addClass(elCell.firstChild, sClassName); 17024 } 17025 17026 // Needed for SDT only 17027 var elThead = this.getTheadEl(); 17028 var elTable = elThead.parentNode; 17029 var newWidth = elTable.offsetWidth; 17030 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px"; 17031 17032 this._elMsgTbody.style.display = ""; 17033 17034 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName}); 17035 }, 17036 17037 17038 17039 17040 17041 17042 17043 17044 17045 17046 17047 17048 17049 ///////////////////////////////////////////////////////////////////////////// 17050 // 17051 // Private Custom Event Handlers 17052 // 17053 ///////////////////////////////////////////////////////////////////////////// 17054 17055 /** 17056 * Handles Column mutations 17057 * 17058 * @method onColumnChange 17059 * @param oArgs {Object} Custom Event data. 17060 */ 17061 _onColumnChange : function(oArg) { 17062 // Figure out which Column changed 17063 var oColumn = (oArg.column) ? oArg.column : 17064 (oArg.editor) ? oArg.editor.column : null; 17065 this._storeScrollPositions(); 17066 this.validateColumnWidths(oColumn); 17067 }, 17068 17069 17070 17071 17072 17073 17074 17075 17076 17077 17078 17079 17080 17081 17082 17083 ///////////////////////////////////////////////////////////////////////////// 17084 // 17085 // Private DOM Event Handlers 17086 // 17087 ///////////////////////////////////////////////////////////////////////////// 17088 17089 /** 17090 * Syncs scrolltop and scrollleft of all TABLEs. 17091 * 17092 * @method _onScroll 17093 * @param e {HTMLEvent} The scroll event. 17094 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance. 17095 * @private 17096 */ 17097 _onScroll : function(e, oSelf) { 17098 oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft; 17099 17100 if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) { 17101 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor}); 17102 oSelf.cancelCellEditor(); 17103 } 17104 17105 var elTarget = Ev.getTarget(e); 17106 var elTag = elTarget.nodeName.toLowerCase(); 17107 oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget}); 17108 }, 17109 17110 /** 17111 * Handles keydown events on the THEAD element. 17112 * 17113 * @method _onTheadKeydown 17114 * @param e {HTMLEvent} The key event. 17115 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance. 17116 * @private 17117 */ 17118 _onTheadKeydown : function(e, oSelf) { 17119 // If tabbing to next TH label link causes THEAD to scroll, 17120 // need to sync scrollLeft with TBODY 17121 if(Ev.getCharCode(e) === 9) { 17122 setTimeout(function() { 17123 if((oSelf instanceof SDT) && oSelf._sId) { 17124 oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft; 17125 } 17126 },0); 17127 } 17128 17129 var elTarget = Ev.getTarget(e); 17130 var elTag = elTarget.nodeName.toLowerCase(); 17131 var bKeepBubbling = true; 17132 while(elTarget && (elTag != "table")) { 17133 switch(elTag) { 17134 case "body": 17135 return; 17136 case "input": 17137 case "textarea": 17138 // TODO: implement textareaKeyEvent 17139 break; 17140 case "thead": 17141 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e}); 17142 break; 17143 default: 17144 break; 17145 } 17146 if(bKeepBubbling === false) { 17147 return; 17148 } 17149 else { 17150 elTarget = elTarget.parentNode; 17151 if(elTarget) { 17152 elTag = elTarget.nodeName.toLowerCase(); 17153 } 17154 } 17155 } 17156 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e}); 17157 } 17158 17159 17160 17161 17162 /** 17163 * Fired when a fixed scrolling DataTable has a scroll. 17164 * 17165 * @event tableScrollEvent 17166 * @param oArgs.event {HTMLEvent} The event object. 17167 * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE) 17168 * or the DataTable's TBODY element (everyone else). 17169 * 17170 */ 17171 17172 17173 17174 17175 }); 17176 17177 })(); 17178 17179 (function () { 17180 17181 var lang = YAHOO.lang, 17182 util = YAHOO.util, 17183 widget = YAHOO.widget, 17184 ua = YAHOO.env.ua, 17185 17186 Dom = util.Dom, 17187 Ev = util.Event, 17188 17189 DT = widget.DataTable; 17190 /****************************************************************************/ 17191 /****************************************************************************/ 17192 /****************************************************************************/ 17193 17194 /** 17195 * The BaseCellEditor class provides base functionality common to all inline cell 17196 * editors for a DataTable widget. 17197 * 17198 * @namespace YAHOO.widget 17199 * @class BaseCellEditor 17200 * @uses YAHOO.util.EventProvider 17201 * @constructor 17202 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors. 17203 * @param oConfigs {Object} (Optional) Object literal of configs. 17204 */ 17205 widget.BaseCellEditor = function(sType, oConfigs) { 17206 this._sId = this._sId || Dom.generateId(null, "yui-ceditor"); // "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++; 17207 YAHOO.widget.BaseCellEditor._nCount++; 17208 this._sType = sType; 17209 17210 // Validate inputs 17211 this._initConfigs(oConfigs); 17212 17213 // Create Custom Events 17214 this._initEvents(); 17215 17216 // UI needs to be drawn 17217 this._needsRender = true; 17218 }; 17219 17220 var BCE = widget.BaseCellEditor; 17221 17222 ///////////////////////////////////////////////////////////////////////////// 17223 // 17224 // Static members 17225 // 17226 ///////////////////////////////////////////////////////////////////////////// 17227 lang.augmentObject(BCE, { 17228 17229 /** 17230 * Global instance counter. 17231 * 17232 * @property CellEditor._nCount 17233 * @type Number 17234 * @static 17235 * @default 0 17236 * @private 17237 */ 17238 _nCount : 0, 17239 17240 /** 17241 * Class applied to CellEditor container. 17242 * 17243 * @property CellEditor.CLASS_CELLEDITOR 17244 * @type String 17245 * @static 17246 * @default "yui-ceditor" 17247 */ 17248 CLASS_CELLEDITOR : "yui-ceditor" 17249 17250 }); 17251 17252 BCE.prototype = { 17253 ///////////////////////////////////////////////////////////////////////////// 17254 // 17255 // Private members 17256 // 17257 ///////////////////////////////////////////////////////////////////////////// 17258 /** 17259 * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique 17260 * DOM ID strings and log messages. 17261 * 17262 * @property _sId 17263 * @type String 17264 * @private 17265 */ 17266 _sId : null, 17267 17268 /** 17269 * Editor type. 17270 * 17271 * @property _sType 17272 * @type String 17273 * @private 17274 */ 17275 _sType : null, 17276 17277 /** 17278 * DataTable instance. 17279 * 17280 * @property _oDataTable 17281 * @type YAHOO.widget.DataTable 17282 * @private 17283 */ 17284 _oDataTable : null, 17285 17286 /** 17287 * Column instance. 17288 * 17289 * @property _oColumn 17290 * @type YAHOO.widget.Column 17291 * @default null 17292 * @private 17293 */ 17294 _oColumn : null, 17295 17296 /** 17297 * Record instance. 17298 * 17299 * @property _oRecord 17300 * @type YAHOO.widget.Record 17301 * @default null 17302 * @private 17303 */ 17304 _oRecord : null, 17305 17306 /** 17307 * TD element. 17308 * 17309 * @property _elTd 17310 * @type HTMLElement 17311 * @default null 17312 * @private 17313 */ 17314 _elTd : null, 17315 17316 /** 17317 * Container for inline editor. 17318 * 17319 * @property _elContainer 17320 * @type HTMLElement 17321 * @private 17322 */ 17323 _elContainer : null, 17324 17325 /** 17326 * Reference to Cancel button, if available. 17327 * 17328 * @property _elCancelBtn 17329 * @type HTMLElement 17330 * @default null 17331 * @private 17332 */ 17333 _elCancelBtn : null, 17334 17335 /** 17336 * Reference to Save button, if available. 17337 * 17338 * @property _elSaveBtn 17339 * @type HTMLElement 17340 * @default null 17341 * @private 17342 */ 17343 _elSaveBtn : null, 17344 17345 17346 17347 17348 17349 17350 17351 17352 ///////////////////////////////////////////////////////////////////////////// 17353 // 17354 // Private methods 17355 // 17356 ///////////////////////////////////////////////////////////////////////////// 17357 17358 /** 17359 * Initialize configs. 17360 * 17361 * @method _initConfigs 17362 * @private 17363 */ 17364 _initConfigs : function(oConfigs) { 17365 // Object literal defines CellEditor configs 17366 if(oConfigs && YAHOO.lang.isObject(oConfigs)) { 17367 for(var sConfig in oConfigs) { 17368 if(sConfig) { 17369 this[sConfig] = oConfigs[sConfig]; 17370 } 17371 } 17372 } 17373 }, 17374 17375 /** 17376 * Initialize Custom Events. 17377 * 17378 * @method _initEvents 17379 * @private 17380 */ 17381 _initEvents : function() { 17382 this.createEvent("showEvent"); 17383 this.createEvent("keydownEvent"); 17384 this.createEvent("invalidDataEvent"); 17385 this.createEvent("revertEvent"); 17386 this.createEvent("saveEvent"); 17387 this.createEvent("cancelEvent"); 17388 this.createEvent("blurEvent"); 17389 this.createEvent("blockEvent"); 17390 this.createEvent("unblockEvent"); 17391 }, 17392 17393 /** 17394 * Initialize container element. 17395 * 17396 * @method _initContainerEl 17397 * @private 17398 */ 17399 _initContainerEl : function() { 17400 if(this._elContainer) { 17401 YAHOO.util.Event.purgeElement(this._elContainer, true); 17402 this._elContainer.innerHTML = ""; 17403 } 17404 17405 var elContainer = document.createElement("div"); 17406 elContainer.id = this.getId() + "-container"; // Needed for tracking blur event 17407 elContainer.style.display = "none"; 17408 elContainer.tabIndex = 0; 17409 17410 this.className = lang.isArray(this.className) ? this.className : this.className ? [this.className] : []; 17411 this.className[this.className.length] = DT.CLASS_EDITOR; 17412 elContainer.className = this.className.join(" "); 17413 17414 document.body.insertBefore(elContainer, document.body.firstChild); 17415 this._elContainer = elContainer; 17416 }, 17417 17418 /** 17419 * Initialize container shim element. 17420 * 17421 * @method _initShimEl 17422 * @private 17423 */ 17424 _initShimEl : function() { 17425 // Iframe shim 17426 if(this.useIFrame) { 17427 if(!this._elIFrame) { 17428 var elIFrame = document.createElement("iframe"); 17429 elIFrame.src = "javascript:false"; 17430 elIFrame.frameBorder = 0; 17431 elIFrame.scrolling = "no"; 17432 elIFrame.style.display = "none"; 17433 elIFrame.className = DT.CLASS_EDITOR_SHIM; 17434 elIFrame.tabIndex = -1; 17435 elIFrame.role = "presentation"; 17436 elIFrame.title = "Presentational iframe shim"; 17437 document.body.insertBefore(elIFrame, document.body.firstChild); 17438 this._elIFrame = elIFrame; 17439 } 17440 } 17441 }, 17442 17443 /** 17444 * Hides CellEditor UI at end of interaction. 17445 * 17446 * @method _hide 17447 */ 17448 _hide : function() { 17449 this.getContainerEl().style.display = "none"; 17450 if(this._elIFrame) { 17451 this._elIFrame.style.display = "none"; 17452 } 17453 this.isActive = false; 17454 this.getDataTable()._oCellEditor = null; 17455 }, 17456 17457 17458 17459 17460 17461 17462 17463 17464 17465 17466 17467 ///////////////////////////////////////////////////////////////////////////// 17468 // 17469 // Public properties 17470 // 17471 ///////////////////////////////////////////////////////////////////////////// 17472 /** 17473 * Implementer defined function that can submit the input value to a server. This 17474 * function must accept the arguments fnCallback and oNewValue. When the submission 17475 * is complete, the function must also call fnCallback(bSuccess, oNewValue) to 17476 * finish the save routine in the CellEditor. This function can also be used to 17477 * perform extra validation or input value manipulation. 17478 * 17479 * @property asyncSubmitter 17480 * @type HTMLFunction 17481 */ 17482 asyncSubmitter : null, 17483 17484 /** 17485 * Current value. 17486 * 17487 * @property value 17488 * @type MIXED 17489 */ 17490 value : null, 17491 17492 /** 17493 * Default value in case Record data is undefined. NB: Null values will not trigger 17494 * the default value. 17495 * 17496 * @property defaultValue 17497 * @type MIXED 17498 * @default null 17499 */ 17500 defaultValue : null, 17501 17502 /** 17503 * Validator function for input data, called from the DataTable instance scope, 17504 * receives the arguments (inputValue, currentValue, editorInstance) and returns 17505 * either the validated (or type-converted) value or undefined. 17506 * 17507 * @property validator 17508 * @type HTMLFunction 17509 * @default null 17510 */ 17511 validator : null, 17512 17513 /** 17514 * If validation is enabled, resets input field of invalid data. 17515 * 17516 * @property resetInvalidData 17517 * @type Boolean 17518 * @default true 17519 */ 17520 resetInvalidData : true, 17521 17522 /** 17523 * True if currently active. 17524 * 17525 * @property isActive 17526 * @type Boolean 17527 */ 17528 isActive : false, 17529 17530 /** 17531 * Text to display on Save button. 17532 * 17533 * @property LABEL_SAVE 17534 * @type HTML 17535 * @default "Save" 17536 */ 17537 LABEL_SAVE : "Save", 17538 17539 /** 17540 * Text to display on Cancel button. 17541 * 17542 * @property LABEL_CANCEL 17543 * @type HTML 17544 * @default "Cancel" 17545 */ 17546 LABEL_CANCEL : "Cancel", 17547 17548 /** 17549 * True if Save/Cancel buttons should not be displayed in the CellEditor. 17550 * 17551 * @property disableBtns 17552 * @type Boolean 17553 * @default false 17554 */ 17555 disableBtns : false, 17556 17557 /** 17558 * True if iframe shim for container element should be enabled. 17559 * 17560 * @property useIFrame 17561 * @type Boolean 17562 * @default false 17563 */ 17564 useIFrame : false, 17565 17566 /** 17567 * Custom CSS class or array of classes applied to the container element. 17568 * 17569 * @property className 17570 * @type String || String[] 17571 */ 17572 className : null, 17573 17574 17575 17576 17577 17578 ///////////////////////////////////////////////////////////////////////////// 17579 // 17580 // Public methods 17581 // 17582 ///////////////////////////////////////////////////////////////////////////// 17583 /** 17584 * CellEditor instance name, for logging. 17585 * 17586 * @method toString 17587 * @return {String} Unique name of the CellEditor instance. 17588 */ 17589 17590 toString : function() { 17591 return "CellEditor instance " + this._sId; 17592 }, 17593 17594 /** 17595 * CellEditor unique ID. 17596 * 17597 * @method getId 17598 * @return {String} Unique ID of the CellEditor instance. 17599 */ 17600 17601 getId : function() { 17602 return this._sId; 17603 }, 17604 17605 /** 17606 * Returns reference to associated DataTable instance. 17607 * 17608 * @method getDataTable 17609 * @return {YAHOO.widget.DataTable} DataTable instance. 17610 */ 17611 17612 getDataTable : function() { 17613 return this._oDataTable; 17614 }, 17615 17616 /** 17617 * Returns reference to associated Column instance. 17618 * 17619 * @method getColumn 17620 * @return {YAHOO.widget.Column} Column instance. 17621 */ 17622 17623 getColumn : function() { 17624 return this._oColumn; 17625 }, 17626 17627 /** 17628 * Returns reference to associated Record instance. 17629 * 17630 * @method getRecord 17631 * @return {YAHOO.widget.Record} Record instance. 17632 */ 17633 17634 getRecord : function() { 17635 return this._oRecord; 17636 }, 17637 17638 17639 17640 /** 17641 * Returns reference to associated TD element. 17642 * 17643 * @method getTdEl 17644 * @return {HTMLElement} TD element. 17645 */ 17646 17647 getTdEl : function() { 17648 return this._elTd; 17649 }, 17650 17651 /** 17652 * Returns container element. 17653 * 17654 * @method getContainerEl 17655 * @return {HTMLElement} Reference to container element. 17656 */ 17657 17658 getContainerEl : function() { 17659 return this._elContainer; 17660 }, 17661 17662 /** 17663 * Nulls out the entire CellEditor instance and related objects, removes attached 17664 * event listeners, and clears out DOM elements inside the container, removes 17665 * container from the DOM. 17666 * 17667 * @method destroy 17668 */ 17669 destroy : function() { 17670 this.unsubscribeAll(); 17671 17672 // Column is late-binding in attach() 17673 var oColumn = this.getColumn(); 17674 if(oColumn) { 17675 oColumn.editor = null; 17676 } 17677 17678 var elContainer = this.getContainerEl(); 17679 if (elContainer) { 17680 Ev.purgeElement(elContainer, true); 17681 elContainer.parentNode.removeChild(elContainer); 17682 } 17683 }, 17684 17685 /** 17686 * Renders DOM elements and attaches event listeners. 17687 * 17688 * @method render 17689 */ 17690 render : function() { 17691 if (!this._needsRender) { 17692 return; 17693 } 17694 17695 this._initContainerEl(); 17696 this._initShimEl(); 17697 17698 // Handle ESC key 17699 Ev.addListener(this.getContainerEl(), "keydown", function(e, oSelf) { 17700 // ESC cancels Cell Editor 17701 if((e.keyCode == 27)) { 17702 var target = Ev.getTarget(e); 17703 // workaround for Mac FF3 bug that disabled clicks when ESC hit when 17704 // select is open. [bug 2273056] 17705 if (target.nodeName && target.nodeName.toLowerCase() === 'select') { 17706 target.blur(); 17707 } 17708 oSelf.cancel(); 17709 } 17710 // Pass through event 17711 oSelf.fireEvent("keydownEvent", {editor:oSelf, event:e}); 17712 }, this); 17713 17714 this.renderForm(); 17715 17716 // Show Save/Cancel buttons 17717 if(!this.disableBtns) { 17718 this.renderBtns(); 17719 } 17720 17721 this.doAfterRender(); 17722 this._needsRender = false; 17723 }, 17724 17725 /** 17726 * Renders Save/Cancel buttons. 17727 * 17728 * @method renderBtns 17729 */ 17730 renderBtns : function() { 17731 // Buttons 17732 var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div")); 17733 elBtnsDiv.className = DT.CLASS_BUTTON; 17734 17735 // Save button 17736 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button")); 17737 elSaveBtn.className = DT.CLASS_DEFAULT; 17738 elSaveBtn.innerHTML = this.LABEL_SAVE; 17739 Ev.addListener(elSaveBtn, "click", function(oArgs) { 17740 this.save(); 17741 }, this, true); 17742 this._elSaveBtn = elSaveBtn; 17743 17744 // Cancel button 17745 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button")); 17746 elCancelBtn.innerHTML = this.LABEL_CANCEL; 17747 Ev.addListener(elCancelBtn, "click", function(oArgs) { 17748 this.cancel(); 17749 }, this, true); 17750 this._elCancelBtn = elCancelBtn; 17751 }, 17752 17753 /** 17754 * Attach CellEditor for a new interaction. 17755 * 17756 * @method attach 17757 * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance. 17758 * @param elCell {HTMLElement} Cell to edit. 17759 */ 17760 attach : function(oDataTable, elCell) { 17761 // Validate 17762 if(oDataTable instanceof YAHOO.widget.DataTable) { 17763 this._oDataTable = oDataTable; 17764 17765 // Validate cell 17766 elCell = oDataTable.getTdEl(elCell); 17767 if(elCell) { 17768 this._elTd = elCell; 17769 17770 // Validate Column 17771 var oColumn = oDataTable.getColumn(elCell); 17772 if(oColumn) { 17773 this._oColumn = oColumn; 17774 17775 // Validate Record 17776 var oRecord = oDataTable.getRecord(elCell); 17777 if(oRecord) { 17778 this._oRecord = oRecord; 17779 var value = oRecord.getData(this.getColumn().getField()); 17780 this.value = (value !== undefined) ? value : this.defaultValue; 17781 return true; 17782 } 17783 } 17784 } 17785 } 17786 return false; 17787 }, 17788 17789 /** 17790 * Moves container into position for display. 17791 * 17792 * @method move 17793 */ 17794 move : function() { 17795 // Move Editor 17796 var elContainer = this.getContainerEl(), 17797 elTd = this.getTdEl(), 17798 x = Dom.getX(elTd), 17799 y = Dom.getY(elTd); 17800 17801 //TODO: remove scrolling logic 17802 // SF doesn't get xy for cells in scrolling table 17803 // when tbody display is set to block 17804 if(isNaN(x) || isNaN(y)) { 17805 var elTbody = this.getDataTable().getTbodyEl(); 17806 x = elTd.offsetLeft + // cell pos relative to table 17807 Dom.getX(elTbody.parentNode) - // plus table pos relative to document 17808 elTbody.scrollLeft; // minus tbody scroll 17809 y = elTd.offsetTop + // cell pos relative to table 17810 Dom.getY(elTbody.parentNode) - // plus table pos relative to document 17811 elTbody.scrollTop + // minus tbody scroll 17812 this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells 17813 } 17814 17815 elContainer.style.left = x + "px"; 17816 elContainer.style.top = y + "px"; 17817 17818 if(this._elIFrame) { 17819 this._elIFrame.style.left = x + "px"; 17820 this._elIFrame.style.top = y + "px"; 17821 } 17822 }, 17823 17824 /** 17825 * Displays CellEditor UI in the correct position. 17826 * 17827 * @method show 17828 */ 17829 show : function() { 17830 var elContainer = this.getContainerEl(), 17831 elIFrame = this._elIFrame; 17832 this.resetForm(); 17833 this.isActive = true; 17834 elContainer.style.display = ""; 17835 if(elIFrame) { 17836 elIFrame.style.width = elContainer.offsetWidth + "px"; 17837 elIFrame.style.height = elContainer.offsetHeight + "px"; 17838 elIFrame.style.display = ""; 17839 } 17840 this.focus(); 17841 this.fireEvent("showEvent", {editor:this}); 17842 }, 17843 17844 /** 17845 * Fires blockEvent 17846 * 17847 * @method block 17848 */ 17849 block : function() { 17850 this.fireEvent("blockEvent", {editor:this}); 17851 }, 17852 17853 /** 17854 * Fires unblockEvent 17855 * 17856 * @method unblock 17857 */ 17858 unblock : function() { 17859 this.fireEvent("unblockEvent", {editor:this}); 17860 }, 17861 17862 /** 17863 * Saves value of CellEditor and hides UI. 17864 * 17865 * @method save 17866 */ 17867 save : function() { 17868 // Get new value 17869 var inputValue = this.getInputValue(); 17870 var validValue = inputValue; 17871 17872 // Validate new value 17873 if(this.validator) { 17874 validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this); 17875 if(validValue === undefined ) { 17876 if(this.resetInvalidData) { 17877 this.resetForm(); 17878 } 17879 this.fireEvent("invalidDataEvent", 17880 {editor:this, oldData:this.value, newData:inputValue}); 17881 return; 17882 } 17883 } 17884 17885 var oSelf = this; 17886 var finishSave = function(bSuccess, oNewValue) { 17887 var oOrigValue = oSelf.value; 17888 if(bSuccess) { 17889 // Update new value 17890 oSelf.value = oNewValue; 17891 oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue); 17892 17893 // Hide CellEditor 17894 oSelf._hide(); 17895 17896 oSelf.fireEvent("saveEvent", 17897 {editor:oSelf, oldData:oOrigValue, newData:oSelf.value}); 17898 } 17899 else { 17900 oSelf.resetForm(); 17901 oSelf.fireEvent("revertEvent", 17902 {editor:oSelf, oldData:oOrigValue, newData:oNewValue}); 17903 } 17904 oSelf.unblock(); 17905 }; 17906 17907 this.block(); 17908 if(lang.isFunction(this.asyncSubmitter)) { 17909 this.asyncSubmitter.call(this, finishSave, validValue); 17910 } 17911 else { 17912 finishSave(true, validValue); 17913 } 17914 }, 17915 17916 /** 17917 * Cancels CellEditor input and hides UI. 17918 * 17919 * @method cancel 17920 */ 17921 cancel : function() { 17922 if(this.isActive) { 17923 this._hide(); 17924 this.fireEvent("cancelEvent", {editor:this}); 17925 } 17926 else { 17927 } 17928 }, 17929 17930 /** 17931 * Renders form elements. 17932 * 17933 * @method renderForm 17934 */ 17935 renderForm : function() { 17936 // To be implemented by subclass 17937 }, 17938 17939 /** 17940 * Access to add additional event listeners. 17941 * 17942 * @method doAfterRender 17943 */ 17944 doAfterRender : function() { 17945 // To be implemented by subclass 17946 }, 17947 17948 17949 /** 17950 * After rendering form, if disabledBtns is set to true, then sets up a mechanism 17951 * to save input without them. 17952 * 17953 * @method handleDisabledBtns 17954 */ 17955 handleDisabledBtns : function() { 17956 // To be implemented by subclass 17957 }, 17958 17959 /** 17960 * Resets CellEditor UI to initial state. 17961 * 17962 * @method resetForm 17963 */ 17964 resetForm : function() { 17965 // To be implemented by subclass 17966 }, 17967 17968 /** 17969 * Sets focus in CellEditor. 17970 * 17971 * @method focus 17972 */ 17973 focus : function() { 17974 // To be implemented by subclass 17975 }, 17976 17977 /** 17978 * Retrieves input value from CellEditor. 17979 * 17980 * @method getInputValue 17981 */ 17982 getInputValue : function() { 17983 // To be implemented by subclass 17984 } 17985 17986 }; 17987 17988 lang.augmentProto(BCE, util.EventProvider); 17989 17990 17991 ///////////////////////////////////////////////////////////////////////////// 17992 // 17993 // Custom Events 17994 // 17995 ///////////////////////////////////////////////////////////////////////////// 17996 17997 /** 17998 * Fired when a CellEditor is shown. 17999 * 18000 * @event showEvent 18001 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18002 */ 18003 18004 /** 18005 * Fired when a CellEditor has a keydown. 18006 * 18007 * @event keydownEvent 18008 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18009 * @param oArgs.event {HTMLEvent} The event object. 18010 */ 18011 18012 /** 18013 * Fired when a CellEditor input is reverted due to invalid data. 18014 * 18015 * @event invalidDataEvent 18016 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18017 * @param oArgs.newData {Object} New data value from form input field. 18018 * @param oArgs.oldData {Object} Old data value. 18019 */ 18020 18021 /** 18022 * Fired when a CellEditor input is reverted due to asyncSubmitter failure. 18023 * 18024 * @event revertEvent 18025 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18026 * @param oArgs.newData {Object} New data value from form input field. 18027 * @param oArgs.oldData {Object} Old data value. 18028 */ 18029 18030 /** 18031 * Fired when a CellEditor input is saved. 18032 * 18033 * @event saveEvent 18034 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18035 * @param oArgs.newData {Object} New data value from form input field. 18036 * @param oArgs.oldData {Object} Old data value. 18037 */ 18038 18039 /** 18040 * Fired when a CellEditor input is canceled. 18041 * 18042 * @event cancelEvent 18043 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18044 */ 18045 18046 /** 18047 * Fired when a CellEditor has a blur event. 18048 * 18049 * @event blurEvent 18050 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance. 18051 */ 18052 18053 18054 18055 18056 18057 18058 18059 18060 18061 18062 18063 18064 18065 18066 /****************************************************************************/ 18067 /****************************************************************************/ 18068 /****************************************************************************/ 18069 18070 /** 18071 * The CheckboxCellEditor class provides functionality for inline editing 18072 * DataTable cell data with checkboxes. 18073 * 18074 * @namespace YAHOO.widget 18075 * @class CheckboxCellEditor 18076 * @extends YAHOO.widget.BaseCellEditor 18077 * @constructor 18078 * @param oConfigs {Object} (Optional) Object literal of configs. 18079 */ 18080 widget.CheckboxCellEditor = function(oConfigs) { 18081 oConfigs = oConfigs || {}; 18082 this._sId = this._sId || Dom.generateId(null, "yui-checkboxceditor"); // "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++; 18083 YAHOO.widget.BaseCellEditor._nCount++; 18084 widget.CheckboxCellEditor.superclass.constructor.call(this, oConfigs.type || "checkbox", oConfigs); 18085 }; 18086 18087 // CheckboxCellEditor extends BaseCellEditor 18088 lang.extend(widget.CheckboxCellEditor, BCE, { 18089 18090 ///////////////////////////////////////////////////////////////////////////// 18091 // 18092 // CheckboxCellEditor public properties 18093 // 18094 ///////////////////////////////////////////////////////////////////////////// 18095 /** 18096 * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"]) 18097 * or a an array of objects (e.g., [{label:"red", value:"#FF0000"}, 18098 * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]). String 18099 * values are treated as markup and inserted into the DOM as innerHTML. 18100 * 18101 * @property checkboxOptions 18102 * @type HTML[] | Object[] 18103 */ 18104 checkboxOptions : null, 18105 18106 /** 18107 * Reference to the checkbox elements. 18108 * 18109 * @property checkboxes 18110 * @type HTMLElement[] 18111 */ 18112 checkboxes : null, 18113 18114 /** 18115 * Array of checked values 18116 * 18117 * @property value 18118 * @type String[] 18119 */ 18120 value : null, 18121 18122 ///////////////////////////////////////////////////////////////////////////// 18123 // 18124 // CheckboxCellEditor public methods 18125 // 18126 ///////////////////////////////////////////////////////////////////////////// 18127 18128 /** 18129 * Render a form with input(s) type=checkbox. 18130 * 18131 * @method renderForm 18132 */ 18133 renderForm : function() { 18134 if(lang.isArray(this.checkboxOptions)) { 18135 var checkboxOption, checkboxValue, checkboxId, elLabel, j, len; 18136 18137 // Create the checkbox buttons in an IE-friendly way... 18138 for(j=0,len=this.checkboxOptions.length; j<len; j++) { 18139 checkboxOption = this.checkboxOptions[j]; 18140 checkboxValue = lang.isValue(checkboxOption.value) ? 18141 checkboxOption.value : checkboxOption; 18142 18143 checkboxId = this.getId() + "-chk" + j; 18144 this.getContainerEl().innerHTML += "<input type=\"checkbox\"" + 18145 " id=\"" + checkboxId + "\"" + // Needed for label 18146 " value=\"" + checkboxValue + "\" />"; 18147 18148 // Create the labels in an IE-friendly way 18149 elLabel = this.getContainerEl().appendChild(document.createElement("label")); 18150 elLabel.htmlFor = checkboxId; 18151 elLabel.innerHTML = lang.isValue(checkboxOption.label) ? 18152 checkboxOption.label : checkboxOption; 18153 } 18154 18155 // Store the reference to the checkbox elements 18156 var allCheckboxes = []; 18157 for(j=0; j<len; j++) { 18158 allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2]; 18159 } 18160 this.checkboxes = allCheckboxes; 18161 18162 if(this.disableBtns) { 18163 this.handleDisabledBtns(); 18164 } 18165 } 18166 else { 18167 } 18168 }, 18169 18170 /** 18171 * After rendering form, if disabledBtns is set to true, then sets up a mechanism 18172 * to save input without them. 18173 * 18174 * @method handleDisabledBtns 18175 */ 18176 handleDisabledBtns : function() { 18177 Ev.addListener(this.getContainerEl(), "click", function(v){ 18178 if(Ev.getTarget(v).tagName.toLowerCase() === "input") { 18179 // Save on blur 18180 this.save(); 18181 } 18182 }, this, true); 18183 }, 18184 18185 /** 18186 * Resets CheckboxCellEditor UI to initial state. 18187 * 18188 * @method resetForm 18189 */ 18190 resetForm : function() { 18191 // Normalize to array 18192 var originalValues = lang.isArray(this.value) ? this.value : [this.value]; 18193 18194 // Match checks to value 18195 for(var i=0, j=this.checkboxes.length; i<j; i++) { 18196 this.checkboxes[i].checked = false; 18197 for(var k=0, len=originalValues.length; k<len; k++) { 18198 if(this.checkboxes[i].value == originalValues[k]) { 18199 this.checkboxes[i].checked = true; 18200 } 18201 } 18202 } 18203 }, 18204 18205 /** 18206 * Sets focus in CheckboxCellEditor. 18207 * 18208 * @method focus 18209 */ 18210 focus : function() { 18211 this.checkboxes[0].focus(); 18212 }, 18213 18214 /** 18215 * Retrieves input value from CheckboxCellEditor. 18216 * 18217 * @method getInputValue 18218 */ 18219 getInputValue : function() { 18220 var checkedValues = []; 18221 for(var i=0, j=this.checkboxes.length; i<j; i++) { 18222 if(this.checkboxes[i].checked) { 18223 checkedValues[checkedValues.length] = this.checkboxes[i].value; 18224 } 18225 } 18226 return checkedValues; 18227 } 18228 18229 }); 18230 18231 // Copy static members to CheckboxCellEditor class 18232 lang.augmentObject(widget.CheckboxCellEditor, BCE); 18233 18234 18235 18236 18237 18238 18239 18240 18241 /****************************************************************************/ 18242 /****************************************************************************/ 18243 /****************************************************************************/ 18244 18245 /** 18246 * The DataCellEditor class provides functionality for inline editing 18247 * DataTable cell data with a YUI Calendar. 18248 * 18249 * @namespace YAHOO.widget 18250 * @class DateCellEditor 18251 * @extends YAHOO.widget.BaseCellEditor 18252 * @constructor 18253 * @param oConfigs {Object} (Optional) Object literal of configs. 18254 */ 18255 widget.DateCellEditor = function(oConfigs) { 18256 oConfigs = oConfigs || {}; 18257 this._sId = this._sId || Dom.generateId(null, "yui-dateceditor"); // "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++; 18258 YAHOO.widget.BaseCellEditor._nCount++; 18259 widget.DateCellEditor.superclass.constructor.call(this, oConfigs.type || "date", oConfigs); 18260 }; 18261 18262 // CheckboxCellEditor extends BaseCellEditor 18263 lang.extend(widget.DateCellEditor, BCE, { 18264 18265 ///////////////////////////////////////////////////////////////////////////// 18266 // 18267 // DateCellEditor public properties 18268 // 18269 ///////////////////////////////////////////////////////////////////////////// 18270 /** 18271 * Reference to Calendar instance. 18272 * 18273 * @property calendar 18274 * @type YAHOO.widget.Calendar 18275 */ 18276 calendar : null, 18277 18278 /** 18279 * Configs for the calendar instance, to be passed to Calendar constructor. 18280 * 18281 * @property calendarOptions 18282 * @type Object 18283 */ 18284 calendarOptions : null, 18285 18286 /** 18287 * Default value. 18288 * 18289 * @property defaultValue 18290 * @type Date 18291 * @default new Date() 18292 */ 18293 defaultValue : new Date(), 18294 18295 18296 ///////////////////////////////////////////////////////////////////////////// 18297 // 18298 // DateCellEditor public methods 18299 // 18300 ///////////////////////////////////////////////////////////////////////////// 18301 18302 /** 18303 * Render a Calendar. 18304 * 18305 * @method renderForm 18306 */ 18307 renderForm : function() { 18308 // Calendar widget 18309 if(YAHOO.widget.Calendar) { 18310 var calContainer = this.getContainerEl().appendChild(document.createElement("div")); 18311 calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor 18312 var calendar = 18313 new YAHOO.widget.Calendar(this.getId() + "-date", 18314 calContainer.id, this.calendarOptions); 18315 calendar.render(); 18316 calContainer.style.cssFloat = "none"; 18317 18318 // Bug 2528576 18319 calendar.hideEvent.subscribe(function() {this.cancel();}, this, true); 18320 18321 if(ua.ie) { 18322 var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div")); 18323 calFloatClearer.style.clear = "both"; 18324 } 18325 18326 this.calendar = calendar; 18327 18328 if(this.disableBtns) { 18329 this.handleDisabledBtns(); 18330 } 18331 } 18332 else { 18333 } 18334 18335 }, 18336 18337 /** 18338 * After rendering form, if disabledBtns is set to true, then sets up a mechanism 18339 * to save input without them. 18340 * 18341 * @method handleDisabledBtns 18342 */ 18343 handleDisabledBtns : function() { 18344 this.calendar.selectEvent.subscribe(function(v){ 18345 // Save on select 18346 this.save(); 18347 }, this, true); 18348 }, 18349 18350 /** 18351 * Resets DateCellEditor UI to initial state. 18352 * 18353 * @method resetForm 18354 */ 18355 resetForm : function() { 18356 var value = this.value || (new Date()); 18357 this.calendar.select(value); 18358 this.calendar.cfg.setProperty("pagedate",value,false); 18359 this.calendar.render(); 18360 // Bug 2528576 18361 this.calendar.show(); 18362 }, 18363 18364 /** 18365 * Sets focus in DateCellEditor. 18366 * 18367 * @method focus 18368 */ 18369 focus : function() { 18370 // To be impmlemented by subclass 18371 }, 18372 18373 /** 18374 * Retrieves input value from DateCellEditor. 18375 * 18376 * @method getInputValue 18377 */ 18378 getInputValue : function() { 18379 return this.calendar.getSelectedDates()[0]; 18380 } 18381 18382 }); 18383 18384 // Copy static members to DateCellEditor class 18385 lang.augmentObject(widget.DateCellEditor, BCE); 18386 18387 18388 18389 18390 18391 18392 18393 18394 18395 /****************************************************************************/ 18396 /****************************************************************************/ 18397 /****************************************************************************/ 18398 18399 /** 18400 * The DropdownCellEditor class provides functionality for inline editing 18401 * DataTable cell data a SELECT element. 18402 * 18403 * @namespace YAHOO.widget 18404 * @class DropdownCellEditor 18405 * @extends YAHOO.widget.BaseCellEditor 18406 * @constructor 18407 * @param oConfigs {Object} (Optional) Object literal of configs. 18408 */ 18409 widget.DropdownCellEditor = function(oConfigs) { 18410 oConfigs = oConfigs || {}; 18411 this._sId = this._sId || Dom.generateId(null, "yui-dropdownceditor"); // "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++; 18412 YAHOO.widget.BaseCellEditor._nCount++; 18413 widget.DropdownCellEditor.superclass.constructor.call(this, oConfigs.type || "dropdown", oConfigs); 18414 }; 18415 18416 // DropdownCellEditor extends BaseCellEditor 18417 lang.extend(widget.DropdownCellEditor, BCE, { 18418 18419 ///////////////////////////////////////////////////////////////////////////// 18420 // 18421 // DropdownCellEditor public properties 18422 // 18423 ///////////////////////////////////////////////////////////////////////////// 18424 /** 18425 * Array of dropdown values. Can either be a simple array (e.g., 18426 * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g., 18427 * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"}, 18428 * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]). String 18429 * values are treated as markup and inserted into the DOM as innerHTML. 18430 * 18431 * @property dropdownOptions 18432 * @type HTML[] | Object[] 18433 */ 18434 dropdownOptions : null, 18435 18436 /** 18437 * Reference to Dropdown element. 18438 * 18439 * @property dropdown 18440 * @type HTMLElement 18441 */ 18442 dropdown : null, 18443 18444 /** 18445 * Enables multi-select. 18446 * 18447 * @property multiple 18448 * @type Boolean 18449 */ 18450 multiple : false, 18451 18452 /** 18453 * Specifies number of visible options. 18454 * 18455 * @property size 18456 * @type Number 18457 */ 18458 size : null, 18459 18460 ///////////////////////////////////////////////////////////////////////////// 18461 // 18462 // DropdownCellEditor public methods 18463 // 18464 ///////////////////////////////////////////////////////////////////////////// 18465 18466 /** 18467 * Render a form with select element. 18468 * 18469 * @method renderForm 18470 */ 18471 renderForm : function() { 18472 var elDropdown = this.getContainerEl().appendChild(document.createElement("select")); 18473 elDropdown.style.zoom = 1; 18474 if(this.multiple) { 18475 elDropdown.multiple = "multiple"; 18476 } 18477 if(lang.isNumber(this.size)) { 18478 elDropdown.size = this.size; 18479 } 18480 this.dropdown = elDropdown; 18481 18482 if(lang.isArray(this.dropdownOptions)) { 18483 var dropdownOption, elOption; 18484 for(var i=0, j=this.dropdownOptions.length; i<j; i++) { 18485 dropdownOption = this.dropdownOptions[i]; 18486 elOption = document.createElement("option"); 18487 elOption.value = (lang.isValue(dropdownOption.value)) ? 18488 dropdownOption.value : dropdownOption; 18489 elOption.innerHTML = (lang.isValue(dropdownOption.label)) ? 18490 dropdownOption.label : dropdownOption; 18491 elOption = elDropdown.appendChild(elOption); 18492 } 18493 18494 if(this.disableBtns) { 18495 this.handleDisabledBtns(); 18496 } 18497 } 18498 }, 18499 18500 /** 18501 * After rendering form, if disabledBtns is set to true, then sets up a mechanism 18502 * to save input without them. 18503 * 18504 * @method handleDisabledBtns 18505 */ 18506 handleDisabledBtns : function() { 18507 // Save on blur for multi-select 18508 if(this.multiple) { 18509 Ev.addListener(this.dropdown, "blur", function(v){ 18510 // Save on change 18511 this.save(); 18512 }, this, true); 18513 } 18514 // Save on change for single-select 18515 else { 18516 if(!ua.ie) { 18517 Ev.addListener(this.dropdown, "change", function(v){ 18518 // Save on change 18519 this.save(); 18520 }, this, true); 18521 } 18522 else { 18523 // Bug 2529274: "change" event is not keyboard accessible in IE6 18524 Ev.addListener(this.dropdown, "blur", function(v){ 18525 this.save(); 18526 }, this, true); 18527 Ev.addListener(this.dropdown, "click", function(v){ 18528 this.save(); 18529 }, this, true); 18530 } 18531 } 18532 }, 18533 18534 /** 18535 * Resets DropdownCellEditor UI to initial state. 18536 * 18537 * @method resetForm 18538 */ 18539 resetForm : function() { 18540 var allOptions = this.dropdown.options, 18541 i=0, j=allOptions.length; 18542 18543 // Look for multi-select selections 18544 if(lang.isArray(this.value)) { 18545 var allValues = this.value, 18546 m=0, n=allValues.length, 18547 hash = {}; 18548 // Reset all selections and stash options in a value hash 18549 for(; i<j; i++) { 18550 allOptions[i].selected = false; 18551 hash[allOptions[i].value] = allOptions[i]; 18552 } 18553 for(; m<n; m++) { 18554 if(hash[allValues[m]]) { 18555 hash[allValues[m]].selected = true; 18556 } 18557 } 18558 } 18559 // Only need to look for a single selection 18560 else { 18561 for(; i<j; i++) { 18562 if(this.value == allOptions[i].value) { 18563 allOptions[i].selected = true; 18564 } 18565 } 18566 } 18567 }, 18568 18569 /** 18570 * Sets focus in DropdownCellEditor. 18571 * 18572 * @method focus 18573 */ 18574 focus : function() { 18575 this.getDataTable()._focusEl(this.dropdown); 18576 }, 18577 18578 /** 18579 * Retrieves input value from DropdownCellEditor. 18580 * 18581 * @method getInputValue 18582 */ 18583 getInputValue : function() { 18584 var allOptions = this.dropdown.options; 18585 18586 // Look for multiple selections 18587 if(this.multiple) { 18588 var values = [], 18589 i=0, j=allOptions.length; 18590 for(; i<j; i++) { 18591 if(allOptions[i].selected) { 18592 values.push(allOptions[i].value); 18593 } 18594 } 18595 return values; 18596 } 18597 // Only need to look for single selection 18598 else { 18599 return allOptions[allOptions.selectedIndex].value; 18600 } 18601 } 18602 18603 }); 18604 18605 // Copy static members to DropdownCellEditor class 18606 lang.augmentObject(widget.DropdownCellEditor, BCE); 18607 18608 18609 18610 18611 18612 18613 /****************************************************************************/ 18614 /****************************************************************************/ 18615 /****************************************************************************/ 18616 18617 /** 18618 * The RadioCellEditor class provides functionality for inline editing 18619 * DataTable cell data with radio buttons. 18620 * 18621 * @namespace YAHOO.widget 18622 * @class RadioCellEditor 18623 * @extends YAHOO.widget.BaseCellEditor 18624 * @constructor 18625 * @param oConfigs {Object} (Optional) Object literal of configs. 18626 */ 18627 widget.RadioCellEditor = function(oConfigs) { 18628 oConfigs = oConfigs || {}; 18629 this._sId = this._sId || Dom.generateId(null, "yui-radioceditor"); // "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++; 18630 YAHOO.widget.BaseCellEditor._nCount++; 18631 widget.RadioCellEditor.superclass.constructor.call(this, oConfigs.type || "radio", oConfigs); 18632 }; 18633 18634 // RadioCellEditor extends BaseCellEditor 18635 lang.extend(widget.RadioCellEditor, BCE, { 18636 18637 ///////////////////////////////////////////////////////////////////////////// 18638 // 18639 // RadioCellEditor public properties 18640 // 18641 ///////////////////////////////////////////////////////////////////////////// 18642 /** 18643 * Reference to radio elements. 18644 * 18645 * @property radios 18646 * @type HTMLElement[] 18647 */ 18648 radios : null, 18649 18650 /** 18651 * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"]) 18652 * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1}, 18653 * {label:"maybe", value:0}]). String values are treated as markup and inserted 18654 * into the DOM as innerHTML. 18655 * 18656 * @property radioOptions 18657 * @type HTML[] | Object[] 18658 */ 18659 radioOptions : null, 18660 18661 ///////////////////////////////////////////////////////////////////////////// 18662 // 18663 // RadioCellEditor public methods 18664 // 18665 ///////////////////////////////////////////////////////////////////////////// 18666 18667 /** 18668 * Render a form with input(s) type=radio. 18669 * 18670 * @method renderForm 18671 */ 18672 renderForm : function() { 18673 if(lang.isArray(this.radioOptions)) { 18674 var radioOption, radioValue, radioId, elLabel; 18675 18676 // Create the radio buttons in an IE-friendly way 18677 for(var i=0, len=this.radioOptions.length; i<len; i++) { 18678 radioOption = this.radioOptions[i]; 18679 radioValue = lang.isValue(radioOption.value) ? 18680 radioOption.value : radioOption; 18681 radioId = this.getId() + "-radio" + i; 18682 this.getContainerEl().innerHTML += "<input type=\"radio\"" + 18683 " name=\"" + this.getId() + "\"" + 18684 " value=\"" + radioValue + "\"" + 18685 " id=\"" + radioId + "\" />"; // Needed for label 18686 18687 // Create the labels in an IE-friendly way 18688 elLabel = this.getContainerEl().appendChild(document.createElement("label")); 18689 elLabel.htmlFor = radioId; 18690 elLabel.innerHTML = (lang.isValue(radioOption.label)) ? 18691 radioOption.label : radioOption; 18692 } 18693 18694 // Store the reference to the checkbox elements 18695 var allRadios = [], 18696 elRadio; 18697 for(var j=0; j<len; j++) { 18698 elRadio = this.getContainerEl().childNodes[j*2]; 18699 allRadios[allRadios.length] = elRadio; 18700 } 18701 this.radios = allRadios; 18702 18703 if(this.disableBtns) { 18704 this.handleDisabledBtns(); 18705 } 18706 } 18707 else { 18708 } 18709 }, 18710 18711 /** 18712 * After rendering form, if disabledBtns is set to true, then sets up a mechanism 18713 * to save input without them. 18714 * 18715 * @method handleDisabledBtns 18716 */ 18717 handleDisabledBtns : function() { 18718 Ev.addListener(this.getContainerEl(), "click", function(v){ 18719 if(Ev.getTarget(v).tagName.toLowerCase() === "input") { 18720 // Save on blur 18721 this.save(); 18722 } 18723 }, this, true); 18724 }, 18725 18726 /** 18727 * Resets RadioCellEditor UI to initial state. 18728 * 18729 * @method resetForm 18730 */ 18731 resetForm : function() { 18732 for(var i=0, j=this.radios.length; i<j; i++) { 18733 var elRadio = this.radios[i]; 18734 if(this.value == elRadio.value) { 18735 elRadio.checked = true; 18736 return; 18737 } 18738 } 18739 }, 18740 18741 /** 18742 * Sets focus in RadioCellEditor. 18743 * 18744 * @method focus 18745 */ 18746 focus : function() { 18747 for(var i=0, j=this.radios.length; i<j; i++) { 18748 if(this.radios[i].checked) { 18749 this.radios[i].focus(); 18750 return; 18751 } 18752 } 18753 }, 18754 18755 /** 18756 * Retrieves input value from RadioCellEditor. 18757 * 18758 * @method getInputValue 18759 */ 18760 getInputValue : function() { 18761 for(var i=0, j=this.radios.length; i<j; i++) { 18762 if(this.radios[i].checked) { 18763 return this.radios[i].value; 18764 } 18765 } 18766 } 18767 18768 }); 18769 18770 // Copy static members to RadioCellEditor class 18771 lang.augmentObject(widget.RadioCellEditor, BCE); 18772 18773 18774 18775 18776 18777 18778 /****************************************************************************/ 18779 /****************************************************************************/ 18780 /****************************************************************************/ 18781 18782 /** 18783 * The TextareaCellEditor class provides functionality for inline editing 18784 * DataTable cell data with a TEXTAREA element. 18785 * 18786 * @namespace YAHOO.widget 18787 * @class TextareaCellEditor 18788 * @extends YAHOO.widget.BaseCellEditor 18789 * @constructor 18790 * @param oConfigs {Object} (Optional) Object literal of configs. 18791 */ 18792 widget.TextareaCellEditor = function(oConfigs) { 18793 oConfigs = oConfigs || {}; 18794 this._sId = this._sId || Dom.generateId(null, "yui-textareaceditor");// "yui-textareaceditor" + ; 18795 YAHOO.widget.BaseCellEditor._nCount++; 18796 widget.TextareaCellEditor.superclass.constructor.call(this, oConfigs.type || "textarea", oConfigs); 18797 }; 18798 18799 // TextareaCellEditor extends BaseCellEditor 18800 lang.extend(widget.TextareaCellEditor, BCE, { 18801 18802 ///////////////////////////////////////////////////////////////////////////// 18803 // 18804 // TextareaCellEditor public properties 18805 // 18806 ///////////////////////////////////////////////////////////////////////////// 18807 /** 18808 * Reference to textarea element. 18809 * 18810 * @property textarea 18811 * @type HTMLElement 18812 */ 18813 textarea : null, 18814 18815 18816 ///////////////////////////////////////////////////////////////////////////// 18817 // 18818 // TextareaCellEditor public methods 18819 // 18820 ///////////////////////////////////////////////////////////////////////////// 18821 18822 /** 18823 * Render a form with textarea. 18824 * 18825 * @method renderForm 18826 */ 18827 renderForm : function() { 18828 var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea")); 18829 this.textarea = elTextarea; 18830 18831 if(this.disableBtns) { 18832 this.handleDisabledBtns(); 18833 } 18834 }, 18835 18836 /** 18837 * After rendering form, if disabledBtns is set to true, then sets up a mechanism 18838 * to save input without them. 18839 * 18840 * @method handleDisabledBtns 18841 */ 18842 handleDisabledBtns : function() { 18843 Ev.addListener(this.textarea, "blur", function(v){ 18844 // Save on blur 18845 this.save(); 18846 }, this, true); 18847 }, 18848 18849 /** 18850 * Moves TextareaCellEditor UI to a cell. 18851 * 18852 * @method move 18853 */ 18854 move : function() { 18855 this.textarea.style.width = this.getTdEl().offsetWidth + "px"; 18856 this.textarea.style.height = "3em"; 18857 YAHOO.widget.TextareaCellEditor.superclass.move.call(this); 18858 }, 18859 18860 /** 18861 * Resets TextareaCellEditor UI to initial state. 18862 * 18863 * @method resetForm 18864 */ 18865 resetForm : function() { 18866 this.textarea.value = this.value; 18867 }, 18868 18869 /** 18870 * Sets focus in TextareaCellEditor. 18871 * 18872 * @method focus 18873 */ 18874 focus : function() { 18875 // Bug 2303181, Bug 2263600 18876 this.getDataTable()._focusEl(this.textarea); 18877 this.textarea.select(); 18878 }, 18879 18880 /** 18881 * Retrieves input value from TextareaCellEditor. 18882 * 18883 * @method getInputValue 18884 */ 18885 getInputValue : function() { 18886 return this.textarea.value; 18887 } 18888 18889 }); 18890 18891 // Copy static members to TextareaCellEditor class 18892 lang.augmentObject(widget.TextareaCellEditor, BCE); 18893 18894 18895 18896 18897 18898 18899 18900 18901 18902 /****************************************************************************/ 18903 /****************************************************************************/ 18904 /****************************************************************************/ 18905 18906 /** 18907 * The TextboxCellEditor class provides functionality for inline editing 18908 * DataTable cell data with an INPUT TYPE=TEXT element. 18909 * 18910 * @namespace YAHOO.widget 18911 * @class TextboxCellEditor 18912 * @extends YAHOO.widget.BaseCellEditor 18913 * @constructor 18914 * @param oConfigs {Object} (Optional) Object literal of configs. 18915 */ 18916 widget.TextboxCellEditor = function(oConfigs) { 18917 oConfigs = oConfigs || {}; 18918 this._sId = this._sId || Dom.generateId(null, "yui-textboxceditor");// "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++; 18919 YAHOO.widget.BaseCellEditor._nCount++; 18920 widget.TextboxCellEditor.superclass.constructor.call(this, oConfigs.type || "textbox", oConfigs); 18921 }; 18922 18923 // TextboxCellEditor extends BaseCellEditor 18924 lang.extend(widget.TextboxCellEditor, BCE, { 18925 18926 ///////////////////////////////////////////////////////////////////////////// 18927 // 18928 // TextboxCellEditor public properties 18929 // 18930 ///////////////////////////////////////////////////////////////////////////// 18931 /** 18932 * Reference to the textbox element. 18933 * 18934 * @property textbox 18935 */ 18936 textbox : null, 18937 18938 ///////////////////////////////////////////////////////////////////////////// 18939 // 18940 // TextboxCellEditor public methods 18941 // 18942 ///////////////////////////////////////////////////////////////////////////// 18943 18944 /** 18945 * Render a form with input type=text. 18946 * 18947 * @method renderForm 18948 */ 18949 renderForm : function() { 18950 var elTextbox; 18951 // Bug 1802582: SF3/Mac needs a form element wrapping the input 18952 if(ua.webkit>420) { 18953 elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input")); 18954 } 18955 else { 18956 elTextbox = this.getContainerEl().appendChild(document.createElement("input")); 18957 } 18958 elTextbox.type = "text"; 18959 this.textbox = elTextbox; 18960 18961 // Save on enter by default 18962 // Bug: 1802582 Set up a listener on each textbox to track on keypress 18963 // since SF/OP can't preventDefault on keydown 18964 Ev.addListener(elTextbox, "keypress", function(v){ 18965 if((v.keyCode === 13)) { 18966 // Prevent form submit 18967 YAHOO.util.Event.preventDefault(v); 18968 this.save(); 18969 } 18970 }, this, true); 18971 18972 if(this.disableBtns) { 18973 // By default this is no-op since enter saves by default 18974 this.handleDisabledBtns(); 18975 } 18976 }, 18977 18978 /** 18979 * Moves TextboxCellEditor UI to a cell. 18980 * 18981 * @method move 18982 */ 18983 move : function() { 18984 this.textbox.style.width = this.getTdEl().offsetWidth + "px"; 18985 widget.TextboxCellEditor.superclass.move.call(this); 18986 }, 18987 18988 /** 18989 * Resets TextboxCellEditor UI to initial state. 18990 * 18991 * @method resetForm 18992 */ 18993 resetForm : function() { 18994 this.textbox.value = lang.isValue(this.value) ? this.value.toString() : ""; 18995 }, 18996 18997 /** 18998 * Sets focus in TextboxCellEditor. 18999 * 19000 * @method focus 19001 */ 19002 focus : function() { 19003 // Bug 2303181, Bug 2263600 19004 this.getDataTable()._focusEl(this.textbox); 19005 this.textbox.select(); 19006 }, 19007 19008 /** 19009 * Returns new value for TextboxCellEditor. 19010 * 19011 * @method getInputValue 19012 */ 19013 getInputValue : function() { 19014 return this.textbox.value; 19015 } 19016 19017 }); 19018 19019 // Copy static members to TextboxCellEditor class 19020 lang.augmentObject(widget.TextboxCellEditor, BCE); 19021 19022 19023 19024 19025 19026 19027 19028 ///////////////////////////////////////////////////////////////////////////// 19029 // 19030 // DataTable extension 19031 // 19032 ///////////////////////////////////////////////////////////////////////////// 19033 19034 /** 19035 * CellEditor subclasses. 19036 * @property DataTable.Editors 19037 * @type Object 19038 * @static 19039 */ 19040 DT.Editors = { 19041 checkbox : widget.CheckboxCellEditor, 19042 "date" : widget.DateCellEditor, 19043 dropdown : widget.DropdownCellEditor, 19044 radio : widget.RadioCellEditor, 19045 textarea : widget.TextareaCellEditor, 19046 textbox : widget.TextboxCellEditor 19047 }; 19048 19049 /****************************************************************************/ 19050 /****************************************************************************/ 19051 /****************************************************************************/ 19052 19053 /** 19054 * Factory class for instantiating a BaseCellEditor subclass. 19055 * 19056 * @namespace YAHOO.widget 19057 * @class CellEditor 19058 * @extends YAHOO.widget.BaseCellEditor 19059 * @constructor 19060 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors. 19061 * @param oConfigs {Object} (Optional) Object literal of configs. 19062 */ 19063 widget.CellEditor = function(sType, oConfigs) { 19064 // Point to one of the subclasses 19065 if(sType && DT.Editors[sType]) { 19066 lang.augmentObject(BCE, DT.Editors[sType]); 19067 return new DT.Editors[sType](oConfigs); 19068 } 19069 else { 19070 return new BCE(null, oConfigs); 19071 } 19072 }; 19073 19074 var CE = widget.CellEditor; 19075 19076 // Copy static members to CellEditor class 19077 lang.augmentObject(CE, BCE); 19078 19079 19080 })(); 19081 19082 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.9.0", build: "2800"}); 19083 19084 }, '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 |