2 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 * Mechanism to execute a series of callbacks in a non-blocking queue. Each callback is executed via setTimout unless configured with a negative timeout, in which case it is run in blocking mode in the same execution thread as the previous callback. Callbacks can be function references or object literals with the following keys:
10 * <li><code>method</code> - {Function} REQUIRED the callback function.</li>
11 * <li><code>scope</code> - {Object} the scope from which to execute the callback. Default is the global window scope.</li>
12 * <li><code>argument</code> - {Array} parameters to be passed to method as individual arguments.</li>
13 * <li><code>timeout</code> - {number} millisecond delay to wait after previous callback completion before executing this callback. Negative values cause immediate blocking execution. Default 0.</li>
14 * <li><code>until</code> - {Function} boolean function executed before each iteration. Return true to indicate completion and proceed to the next callback.</li>
15 * <li><code>iterations</code> - {Number} number of times to execute the callback before proceeding to the next callback in the chain. Incompatible with <code>until</code>.</li>
18 * @namespace YAHOO.util
21 * @param callback* {Function|Object} Any number of callbacks to initialize the queue
23 YAHOO.util.Chain = function () {
30 this.q = [].slice.call(arguments);
33 * Event fired when the callback queue is emptied via execution (not via
34 * a call to chain.stop().
37 this.createEvent('end');
40 YAHOO.util.Chain.prototype = {
42 * Timeout id used to pause or stop execution and indicate the execution state of the Chain. 0 indicates paused or stopped, -1 indicates blocking execution, and any positive number indicates non-blocking execution.
50 * Begin executing the chain, or resume execution from the last paused position.
52 * @return {Chain} the Chain instance
55 // Grab the first callback in the queue
59 // If there is no callback in the queue or the Chain is currently
60 // in an execution mode, return
62 this.fireEvent('end');
70 if (typeof fn === 'function') {
71 var o = c.scope || {},
72 args = c.argument || [],
76 if (!(args instanceof Array)) {
80 // Execute immediately if the callback timeout is negative.
85 // Execute the callback from scope, with argument
88 } else if (c.iterations) {
89 for (;c.iterations-- > 0;) {
99 // If the until condition is set, check if we're done
102 // Shift this callback from the queue and execute the next
107 // Otherwise if either iterations is not set or we're
108 // executing the last iteration, shift callback from the queue
109 } else if (!c.iterations || !--c.iterations) {
113 // Otherwise set to execute after the configured timeout
114 this.id = setTimeout(function () {
115 // Execute the callback from scope, with argument
117 // Check if the Chain was not paused from inside the callback
119 // Indicate ready to run state
121 // Start the fun all over again
132 * Add a callback to the end of the queue
134 * @param c {Function|Object} the callback function ref or object literal
135 * @return {Chain} the Chain instance
143 * Pause the execution of the Chain after the current execution of the
144 * current callback completes. If called interstitially, clears the
145 * timeout for the pending callback. Paused Chains can be restarted with
148 * @return {Chain} the Chain instance
151 // Conditional added for Caja compatibility
153 clearTimeout(this.id);
160 * Stop and clear the Chain's queue after the current execution of the
161 * current callback completes.
163 * @return {Chain} the Chain instance
171 YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);
174 * Augments the Event Utility with a <code>delegate</code> method that
175 * facilitates easy creation of delegated event listeners. (Note: Using CSS
176 * selectors as the filtering criteria for delegated event listeners requires
177 * inclusion of the Selector Utility.)
179 * @module event-delegate
180 * @title Event Utility Event Delegation Module
181 * @namespace YAHOO.util
187 var Event = YAHOO.util.Event,
192 getMatch = function(el, selector, container) {
196 if (!el || el === container) {
200 returnVal = YAHOO.util.Selector.test(el, selector) ? el: getMatch(el.parentNode, selector, container);
208 Lang.augmentObject(Event, {
211 * Creates a delegate function used to call event listeners specified
212 * via the <code>YAHOO.util.Event.delegate</code> method.
214 * @method _createDelegate
216 * @param {Function} fn The method (event listener) to call.
217 * @param {Function|string} filter Function or CSS selector used to
218 * determine for what element(s) the event listener should be called.
219 * @param {Object} obj An arbitrary object that will be
220 * passed as a parameter to the listener.
221 * @param {Boolean|object} overrideContext If true, the value of the
222 * obj parameter becomes the execution context
223 * of the listener. If an object, this object
224 * becomes the execution context.
225 * @return {Function} Function that will call the event listener
226 * specified by the <code>YAHOO.util.Event.delegate</code> method.
231 _createDelegate: function (fn, filter, obj, overrideContext) {
233 return function (event) {
235 var container = this,
236 target = Event.getTarget(event),
239 // The user might have specified the document object
240 // as the delegation container, in which case it is not
241 // nessary to scope the provided CSS selector(s) to the
242 // delegation container
243 bDocument = (container.nodeType === 9),
251 if (Lang.isFunction(filter)) {
252 matchedEl = filter(target);
254 else if (Lang.isString(filter)) {
261 sID = Event.generateId(container);
264 // Scope all selectors to the container
265 sIDSelector = ("#" + sID + " ");
266 selector = (sIDSelector + filter).replace(/,/gi, ("," + sIDSelector));
271 if (YAHOO.util.Selector.test(target, selector)) {
274 else if (YAHOO.util.Selector.test(target, ((selector.replace(/,/gi, " *,")) + " *"))) {
276 // The target is a descendant of an element matching
277 // the selector, so crawl up to find the ancestor that
278 // matches the selector
280 matchedEl = getMatch(target, selector, container);
289 // The default context for delegated listeners is the
290 // element that matched the filter.
294 if (overrideContext) {
295 if (overrideContext === true) {
298 context = overrideContext;
302 // Call the listener passing in the container and the
303 // element that matched the filter in case the user
306 return fn.call(context, event, matchedEl, container, obj);
316 * Appends a delegated event listener. Delegated event listeners
317 * receive three arguments by default: the DOM event, the element
318 * specified by the filtering function or CSS selector, and the
319 * container element (the element to which the event listener is
320 * bound). (Note: Using the delegate method requires the event-delegate
321 * module. Using CSS selectors as the filtering criteria for delegated
322 * event listeners requires inclusion of the Selector Utility.)
326 * @param {String|HTMLElement|Array|NodeList} container An id, an element
327 * reference, or a collection of ids and/or elements to assign the
329 * @param {String} type The type of event listener to append
330 * @param {Function} fn The method the event invokes
331 * @param {Function|string} filter Function or CSS selector used to
332 * determine for what element(s) the event listener should be called.
333 * When a function is specified, the function should return an
334 * HTML element. Using a CSS Selector requires the inclusion of the
335 * CSS Selector Utility.
336 * @param {Object} obj An arbitrary object that will be
337 * passed as a parameter to the listener
338 * @param {Boolean|object} overrideContext If true, the value of the obj parameter becomes
339 * the execution context of the listener. If an
340 * object, this object becomes the execution
342 * @return {Boolean} Returns true if the action was successful or defered,
343 * false if one or more of the elements
344 * could not have the listener attached,
345 * or if the operation throws an exception.
349 delegate: function (container, type, fn, filter, obj, overrideContext) {
356 if (Lang.isString(filter) && !YAHOO.util.Selector) {
361 if (type == "mouseenter" || type == "mouseleave") {
363 if (!Event._createMouseDelegate) {
367 // Look up the real event--either mouseover or mouseout
368 sType = Event._getType(type);
370 fnMouseDelegate = Event._createMouseDelegate(fn, obj, overrideContext);
372 fnDelegate = Event._createDelegate(function (event, matchedEl, container) {
374 return fnMouseDelegate.call(matchedEl, event, container);
376 }, filter, obj, overrideContext);
381 fnDelegate = Event._createDelegate(fn, filter, obj, overrideContext);
385 delegates.push([container, sType, fn, fnDelegate]);
387 return Event.on(container, sType, fnDelegate);
393 * Removes a delegated event listener.
395 * @method removeDelegate
397 * @param {String|HTMLElement|Array|NodeList} container An id, an element
398 * reference, or a collection of ids and/or elements to remove
400 * @param {String} type The type of event to remove.
401 * @param {Function} fn The method the event invokes. If fn is
402 * undefined, then all event listeners for the type of event are
404 * @return {boolean} Returns true if the unbind was successful, false
409 removeDelegate: function (container, type, fn) {
416 // Look up the real event--either mouseover or mouseout
417 if (type == "mouseenter" || type == "mouseleave") {
418 sType = Event._getType(type);
421 index = Event._getCacheIndex(delegates, container, sType, fn);
424 cacheItem = delegates[index];
428 if (container && cacheItem) {
430 returnVal = Event.removeListener(cacheItem[0], cacheItem[1], cacheItem[3]);
433 delete delegates[index][2];
434 delete delegates[index][3];
435 delegates.splice(index, 1);
450 * Augments the Event Utility with support for the mouseenter and mouseleave
451 * events: A mouseenter event fires the first time the mouse enters an
452 * element; a mouseleave event first the first time the mouse leaves an
455 * @module event-mouseenter
456 * @title Event Utility mouseenter and mouseout Module
457 * @namespace YAHOO.util
463 var Event = YAHOO.util.Event,
466 addListener = Event.addListener,
467 removeListener = Event.removeListener,
468 getListeners = Event.getListeners,
473 mouseenter: "mouseover",
474 mouseleave: "mouseout"
477 remove = function(el, type, fn) {
479 var index = Event._getCacheIndex(delegates, el, type, fn),
484 cacheItem = delegates[index];
487 if (el && cacheItem) {
489 // removeListener will translate the value of type
490 returnVal = removeListener.call(Event, cacheItem[0], type, cacheItem[3]);
493 delete delegates[index][2];
494 delete delegates[index][3];
495 delegates.splice(index, 1);
505 Lang.augmentObject(Event._specialTypes, specialTypes);
507 Lang.augmentObject(Event, {
510 * Creates a delegate function used to call mouseover and mouseleave
511 * event listeners specified via the
512 * <code>YAHOO.util.Event.addListener</code>
513 * or <code>YAHOO.util.Event.on</code> method.
515 * @method _createMouseDelegate
517 * @param {Function} fn The method (event listener) to call
518 * @param {Object} obj An arbitrary object that will be
519 * passed as a parameter to the listener
520 * @param {Boolean|object} overrideContext If true, the value of the
521 * obj parameter becomes the execution context
522 * of the listener. If an object, this object
523 * becomes the execution context.
524 * @return {Function} Function that will call the event listener
525 * specified by either the <code>YAHOO.util.Event.addListener</code>
526 * or <code>YAHOO.util.Event.on</code> method.
531 _createMouseDelegate: function (fn, obj, overrideContext) {
533 return function (event, container) {
536 relatedTarget = Event.getRelatedTarget(event),
540 if (el != relatedTarget && !YAHOO.util.Dom.isAncestor(el, relatedTarget)) {
544 if (overrideContext) {
545 if (overrideContext === true) {
548 context = overrideContext;
552 // The default args passed back to a mouseenter or
553 // mouseleave listener are: the event, and any object
554 // the user passed when subscribing
558 // Add the element and delegation container as arguments
559 // when delegating mouseenter and mouseleave
562 args.splice(1, 0, el, container);
565 return fn.apply(context, args);
573 addListener: function (el, type, fn, obj, overrideContext) {
578 if (specialTypes[type]) {
580 fnDelegate = Event._createMouseDelegate(fn, obj, overrideContext);
582 fnDelegate.mouseDelegate = true;
584 delegates.push([el, type, fn, fnDelegate]);
586 // addListener will translate the value of type
587 returnVal = addListener.call(Event, el, type, fnDelegate);
591 returnVal = addListener.apply(Event, arguments);
598 removeListener: function (el, type, fn) {
602 if (specialTypes[type]) {
603 returnVal = remove.apply(Event, arguments);
606 returnVal = removeListener.apply(Event, arguments);
613 getListeners: function (el, type) {
615 // If the user specified the type as mouseover or mouseout,
616 // need to filter out those used by mouseenter and mouseleave.
617 // If the user specified the type as mouseenter or mouseleave,
618 // need to filter out the true mouseover and mouseout listeners.
622 bMouseOverOrOut = (type === "mouseover" || type === "mouseout"),
627 if (type && (bMouseOverOrOut || specialTypes[type])) {
629 elListeners = getListeners.call(Event, el, this._getType(type));
633 for (i=elListeners.length-1; i>-1; i--) {
636 bMouseDelegate = l.fn.mouseDelegate;
638 if ((specialTypes[type] && bMouseDelegate) || (bMouseOverOrOut && !bMouseDelegate)) {
648 listeners = getListeners.apply(Event, arguments);
651 return (listeners && listeners.length) ? listeners : null;
657 Event.on = Event.addListener;
660 YAHOO.register("event-mouseenter", YAHOO.util.Event, {version: "2.9.0", build: "2800"});
663 Y_DOM = YAHOO.util.Dom,
668 Y_DOCUMENT_ELEMENT = Y_DOC.documentElement,
670 Y_DOM_inDoc = Y_DOM.inDocument,
671 Y_mix = Y_Lang.augmentObject,
672 Y_guid = Y_DOM.generateId,
674 Y_getDoc = function(element) {
677 doc = (element.nodeType === 9) ? element : // element === document
678 element.ownerDocument || // element === DOM node
679 element.document || // element === window
686 Y_Array = function(o, startIdx) {
687 var l, a, start = startIdx || 0;
689 // IE errors when trying to slice HTMLElement collections
691 return Array.prototype.slice.call(o, start);
695 for (; start < l; start++) {
702 Y_DOM_allById = function(id, root) {
703 root = root || Y_DOC;
709 if (root.querySelectorAll) {
710 ret = root.querySelectorAll('[id="' + id + '"]');
711 } else if (root.all) {
712 nodes = root.all(id);
715 // root.all may return HTMLElement or HTMLCollection.
716 // some elements are also HTMLCollection (FORM, SELECT).
717 if (nodes.nodeName) {
718 if (nodes.id === id) { // avoid false positive on name
720 nodes = EMPTY_ARRAY; // done, no need to filter
721 } else { // prep for filtering
727 // filter out matches on node.name
728 // and element.id as reference to element with id === 'id'
729 for (i = 0; node = nodes[i++];) {
730 if (node.id === id ||
731 (node.attributes && node.attributes.id &&
732 node.attributes.id.value === id)) {
739 ret = [Y_getDoc(root).getElementById(id)];
746 * The selector-native module provides support for native querySelector
748 * @submodule selector-native
753 * Provides support for using CSS selectors to query the DOM
759 var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
760 OWNER_DOCUMENT = 'ownerDocument',
767 _compare: ('sourceIndex' in Y_DOCUMENT_ELEMENT) ?
768 function(nodeA, nodeB) {
769 var a = nodeA.sourceIndex,
770 b = nodeB.sourceIndex;
780 } : (Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION] ?
781 function(nodeA, nodeB) {
782 if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
788 function(nodeA, nodeB) {
789 var rangeA, rangeB, compare;
790 if (nodeA && nodeB) {
791 rangeA = nodeA[OWNER_DOCUMENT].createRange();
792 rangeA.setStart(nodeA, 0);
793 rangeB = nodeB[OWNER_DOCUMENT].createRange();
794 rangeB.setStart(nodeB, 0);
795 compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
802 _sort: function(nodes) {
804 nodes = Y_Array(nodes, 0, true);
806 nodes.sort(Selector._compare);
813 _deDupe: function(nodes) {
817 for (i = 0; (node = nodes[i++]);) {
819 ret[ret.length] = node;
824 for (i = 0; (node = ret[i++]);) {
826 node.removeAttribute('_found');
833 * Retrieves a set of nodes based on a given CSS selector.
836 * @param {string} selector The CSS Selector to test the node against.
837 * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
838 * @param {Boolean} firstOnly optional Whether or not to return only the first match.
839 * @return {Array} An array of nodes that match the given selector.
842 query: function(selector, root, firstOnly, skipNative) {
843 if (typeof root == 'string') {
844 root = Y_DOM.get(root);
846 return (firstOnly) ? null : [];
849 root = root || Y_DOC;
853 useNative = (Selector.useNative && Y_DOC.querySelector && !skipNative),
854 queries = [[selector, root]],
858 fn = (useNative) ? Selector._nativeQuery : Selector._bruteQuery;
860 if (selector && fn) {
861 // split group into seperate queries
862 if (!skipNative && // already done if skipping
863 (!useNative || root.tagName)) { // split native when element scoping is needed
864 queries = Selector._splitQueries(selector, root);
867 for (i = 0; (query = queries[i++]);) {
868 result = fn(query[0], query[1], firstOnly);
869 if (!firstOnly) { // coerce DOM Collection to Array
870 result = Y_Array(result, 0, true);
873 ret = ret.concat(result);
877 if (queries.length > 1) { // remove dupes and sort by doc order
878 ret = Selector._sort(Selector._deDupe(ret));
882 Y.log('query: ' + selector + ' returning: ' + ret.length, 'info', 'Selector');
883 return (firstOnly) ? (ret[0] || null) : ret;
887 // allows element scoped queries to begin with combinator
888 // e.g. query('> p', document.body) === query('body > p')
889 _splitQueries: function(selector, node) {
890 var groups = selector.split(','),
896 // enforce for element scoping
898 node.id = node.id || Y_guid();
899 prefix = '[id="' + node.id + '"] ';
902 for (i = 0, len = groups.length; i < len; ++i) {
903 selector = prefix + groups[i];
904 queries.push([selector, node]);
911 _nativeQuery: function(selector, root, one) {
912 if (Y_UA.webkit && selector.indexOf(':checked') > -1 &&
913 (Selector.pseudos && Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected"
914 return Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
917 //Y.log('trying native query with: ' + selector, 'info', 'selector-native');
918 return root['querySelector' + (one ? '' : 'All')](selector);
919 } catch(e) { // fallback to brute if available
920 //Y.log('native query error; reverting to brute query with: ' + selector, 'info', 'selector-native');
921 return Selector.query(selector, root, one, true); // redo with skipNative true
925 filter: function(nodes, selector) {
929 if (nodes && selector) {
930 for (i = 0; (node = nodes[i++]);) {
931 if (Selector.test(node, selector)) {
932 ret[ret.length] = node;
936 Y.log('invalid filter input (nodes: ' + nodes +
937 ', selector: ' + selector + ')', 'warn', 'Selector');
943 test: function(node, selector, root) {
945 groups = selector.split(','),
953 if (node && node.tagName) { // only test HTMLElements
955 // we need a root if off-doc
956 if (!root && !Y_DOM_inDoc(node)) {
957 parent = node.parentNode;
960 } else { // only use frag when no parent to query
961 frag = node[OWNER_DOCUMENT].createDocumentFragment();
962 frag.appendChild(node);
967 root = root || node[OWNER_DOCUMENT];
972 for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
973 group += '[id="' + node.id + '"]';
974 items = Selector.query(group, root);
976 for (j = 0; item = items[j++];) {
987 if (useFrag) { // cleanup
988 frag.removeChild(node);
997 YAHOO.util.Selector = Selector;
999 * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
1001 * @submodule selector-css2
1006 * Provides helper methods for collecting and filtering DOM elements.
1009 var PARENT_NODE = 'parentNode',
1010 TAG_NAME = 'tagName',
1011 ATTRIBUTES = 'attributes',
1012 COMBINATOR = 'combinator',
1013 PSEUDOS = 'pseudos',
1016 _reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move?
1018 _children: function(node, tag) {
1019 var ret = node.children,
1025 if (node.children && tag && node.children.tags) {
1026 children = node.children.tags(tag);
1027 } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
1028 childNodes = ret || node.childNodes;
1030 for (i = 0; (child = childNodes[i++]);) {
1031 if (child.tagName) {
1032 if (!tag || tag === child.tagName) {
1043 //attr: /(\[.*\])/g,
1044 attr: /(\[[^\]]*\])/g,
1045 //esc: /\\[:\[][\w\d\]]*/gi,
1046 esc: /\\[:\[\]\(\)#\.\'\>+~"]/gi,
1047 //pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\))*)/i
1048 pseudos: /(\([^\)]*\))/g
1052 * Mapping of shorthand tokens to corresponding attribute selector
1053 * @property shorthand
1057 //'\\#([^\\s\\\\(\\[:]*)': '[id=$1]',
1058 '\\#(-?[_a-z]+[-\\w\\uE000]*)': '[id=$1]',
1059 //'\\#([^\\s\\\.:\\[\\]]*)': '[id=$1]',
1060 //'\\.([^\\s\\\\(\\[:]*)': '[className=$1]'
1061 '\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]'
1065 * List of operators and corresponding boolean functions.
1066 * These functions are passed the attribute and the current node's value of the attribute.
1067 * @property operators
1071 '': function(node, attr) { return !!node.getAttribute(attr); }, // Just test for existence of attribute
1073 //'=': '^{val}$', // equality
1074 '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
1075 '|=': '^{val}(?:-|$)' // optional hyphen-delimited
1079 'first-child': function(node) {
1080 return Selector._children(node[PARENT_NODE])[0] === node;
1084 _bruteQuery: function(selector, root, firstOnly) {
1087 tokens = Selector._tokenize(selector),
1088 token = tokens[tokens.length - 1],
1089 rootDoc = Y_getDoc(root),
1096 // if we have an initial ID, set to root when in document
1098 if (tokens[0] && rootDoc === root &&
1099 (id = tokens[0].id) &&
1100 rootDoc.getElementById(id)) {
1101 root = rootDoc.getElementById(id);
1108 className = token.className;
1109 tagName = token.tagName || '*';
1111 if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
1112 // try ID first, unless no root.all && root not in document
1113 // (root.all works off document, but not getElementById)
1114 // TODO: move to allById?
1115 if (id && (root.all || (root.nodeType === 9 || Y_DOM_inDoc(root)))) {
1116 nodes = Y_DOM_allById(id, root);
1118 } else if (className) {
1119 nodes = root.getElementsByClassName(className);
1120 } else { // default to tagName
1121 nodes = root.getElementsByTagName(tagName);
1124 } else { // brute getElementsByTagName('*')
1125 child = root.firstChild;
1127 if (child.tagName) { // only collect HTMLElements
1130 child = child.nextSilbing || child.firstChild;
1134 ret = Selector._filterNodes(nodes, tokens, firstOnly);
1141 _filterNodes: function(nodes, tokens, firstOnly) {
1144 len = tokens.length,
1149 getters = Selector.getters,
1155 //FUNCTION = 'function',
1161 for (i = 0; (tmpNode = node = nodes[i++]);) {
1166 while (tmpNode && tmpNode.tagName) {
1168 tests = token.tests;
1171 while ((test = tests[--j])) {
1173 if (getters[test[0]]) {
1174 value = getters[test[0]](tmpNode, test[0]);
1176 value = tmpNode[test[0]];
1177 // use getAttribute for non-standard attributes
1178 if (value === undefined && tmpNode.getAttribute) {
1179 value = tmpNode.getAttribute(test[0]);
1183 if ((operator === '=' && value !== test[2]) || // fast path for equality
1184 (typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
1185 operator.test && !operator.test(value)) || // regex test
1186 (!operator.test && // protect against RegExp as function (webkit)
1187 typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
1189 // skip non element nodes or non-matching tags
1190 if ((tmpNode = tmpNode[path])) {
1192 (!tmpNode.tagName ||
1193 (token.tagName && token.tagName !== tmpNode.tagName))
1195 tmpNode = tmpNode[path];
1203 n--; // move to next token
1204 // now that we've passed the test, move up the tree by combinator
1205 if (!pass && (combinator = token.combinator)) {
1206 path = combinator.axis;
1207 tmpNode = tmpNode[path];
1209 // skip non element nodes
1210 while (tmpNode && !tmpNode.tagName) {
1211 tmpNode = tmpNode[path];
1214 if (combinator.direct) { // one pass only
1218 } else { // success if we made it this far
1226 }// while (tmpNode = node = nodes[++i]);
1227 node = tmpNode = null;
1243 axis: 'previousSibling',
1251 //re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
1252 re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
1253 fn: function(match, token) {
1254 var operator = match[2] || '',
1255 operators = Selector.operators,
1256 escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
1259 // add prefiltering for ID and CLASS
1260 if ((match[1] === 'id' && operator === '=') ||
1261 (match[1] === 'className' &&
1262 Y_DOCUMENT_ELEMENT.getElementsByClassName &&
1263 (operator === '~=' || operator === '='))) {
1264 token.prefilter = match[1];
1269 // escape all but ID for prefilter, which may run through QSA (via Dom.allById)
1270 token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
1275 if (operator in operators) {
1276 test = operators[operator];
1277 if (typeof test === 'string') {
1278 match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
1279 test = new RegExp(test.replace('{val}', match[3]));
1283 if (!token.last || token.prefilter !== match[1]) {
1284 return match.slice(1);
1291 re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
1292 fn: function(match, token) {
1293 var tag = match[1].toUpperCase();
1294 token.tagName = tag;
1296 if (tag !== '*' && (!token.last || token.prefilter)) {
1297 return [TAG_NAME, '=', tag];
1299 if (!token.prefilter) {
1300 token.prefilter = 'tagName';
1306 re: /^\s*([>+~]|\s)\s*/,
1307 fn: function(match, token) {
1312 re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
1313 fn: function(match, token) {
1314 var test = Selector[PSEUDOS][match[1]];
1315 if (test) { // reorder match array and unescape special chars for tests
1317 match[2] = match[2].replace(/\\/g, '');
1319 return [match[2], test];
1320 } else { // selector token not supported (possibly missing CSS3 module)
1327 _getToken: function(token) {
1339 Break selector into token units per simple selector.
1340 Combinator is attached to the previous token.
1342 _tokenize: function(selector) {
1343 selector = selector || '';
1344 selector = Selector._replaceShorthand(Y_Lang.trim(selector));
1345 var token = Selector._getToken(), // one token per simple selector (left selector holds combinator)
1346 query = selector, // original query for debug report
1347 tokens = [], // array of tokens
1348 found = false, // whether or not any matches were found this pass
1349 match, // the regex match
1354 Search for selector patterns, store, and strip them from the selector string
1355 until no patterns match (invalid selector) or we run out of chars.
1357 Multiple attributes and pseudos are allowed, in any order.
1359 'form:first-child[type=button]:not(button)[lang|=en]'
1364 found = false; // reset after full pass
1366 for (i = 0; (parser = Selector._parsers[i++]);) {
1367 if ( (match = parser.re.exec(selector)) ) { // note assignment
1368 if (parser.name !== COMBINATOR ) {
1369 token.selector = selector;
1371 selector = selector.replace(match[0], ''); // strip current match from selector
1372 if (!selector.length) {
1376 if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
1377 match[1] = Selector._attrFilters[match[1]];
1380 test = parser.fn(match, token);
1381 if (test === false) { // selector not supported
1385 token.tests.push(test);
1388 if (!selector.length || parser.name === COMBINATOR) {
1390 token = Selector._getToken(token);
1391 if (parser.name === COMBINATOR) {
1392 token.combinator = Selector.combinators[match[1]];
1400 } while (found && selector.length);
1402 if (!found || selector.length) { // not fully parsed
1403 Y.log('query: ' + query + ' contains unsupported token in: ' + selector, 'warn', 'Selector');
1409 _replaceShorthand: function(selector) {
1410 var shorthand = Selector.shorthand,
1411 esc = selector.match(Selector._re.esc), // pull escaped colon, brackets, etc.
1417 selector = selector.replace(Selector._re.esc, '\uE000');
1420 attrs = selector.match(Selector._re.attr);
1421 pseudos = selector.match(Selector._re.pseudos);
1424 selector = selector.replace(Selector._re.attr, '\uE001');
1428 selector = selector.replace(Selector._re.pseudos, '\uE002');
1432 for (re in shorthand) {
1433 if (shorthand.hasOwnProperty(re)) {
1434 selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
1439 for (i = 0, len = attrs.length; i < len; ++i) {
1440 selector = selector.replace(/\uE001/, attrs[i]);
1445 for (i = 0, len = pseudos.length; i < len; ++i) {
1446 selector = selector.replace(/\uE002/, pseudos[i]);
1450 selector = selector.replace(/\[/g, '\uE003');
1451 selector = selector.replace(/\]/g, '\uE004');
1453 selector = selector.replace(/\(/g, '\uE005');
1454 selector = selector.replace(/\)/g, '\uE006');
1457 for (i = 0, len = esc.length; i < len; ++i) {
1458 selector = selector.replace('\uE000', esc[i]);
1466 'class': 'className',
1471 href: function(node, attr) {
1472 return Y_DOM.getAttribute(node, attr);
1477 Y_mix(Selector, SelectorCSS2, true);
1478 Selector.getters.src = Selector.getters.rel = Selector.getters.href;
1480 // IE wants class with native queries
1481 if (Selector.useNative && Y_DOC.querySelector) {
1482 Selector.shorthand['\\.([^\\s\\\\(\\[:]*)'] = '[class~=$1]';
1486 * The selector css3 module provides support for css3 selectors.
1488 * @submodule selector-css3
1493 an+b = get every _a_th node starting at the _b_th
1494 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
1495 1n+b = get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n")
1496 an+0 = get every _a_th element, "0" may be omitted
1499 Selector._reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;
1501 Selector._getNth = function(node, expr, tag, reverse) {
1502 Selector._reNth.test(expr);
1503 var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_)
1504 n = RegExp.$2, // "n"
1505 oddeven = RegExp.$3, // "odd" or "even"
1506 b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_
1508 siblings = Selector._children(node.parentNode, tag),
1512 a = 2; // always every other
1515 b = (oddeven === 'odd') ? 1 : 0;
1516 } else if ( isNaN(a) ) {
1517 a = (n) ? 1 : 0; // start from the first or no repeat
1520 if (a === 0) { // just the first
1522 b = siblings.length - b + 1;
1525 if (siblings[b - 1] === node) {
1532 reverse = !!reverse;
1537 for (var i = b - 1, len = siblings.length; i < len; i += a) {
1538 if ( i >= 0 && siblings[i] === node ) {
1543 for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) {
1544 if ( i < len && siblings[i] === node ) {
1552 Y_mix(Selector.pseudos, {
1553 'root': function(node) {
1554 return node === node.ownerDocument.documentElement;
1557 'nth-child': function(node, expr) {
1558 return Selector._getNth(node, expr);
1561 'nth-last-child': function(node, expr) {
1562 return Selector._getNth(node, expr, null, true);
1565 'nth-of-type': function(node, expr) {
1566 return Selector._getNth(node, expr, node.tagName);
1569 'nth-last-of-type': function(node, expr) {
1570 return Selector._getNth(node, expr, node.tagName, true);
1573 'last-child': function(node) {
1574 var children = Selector._children(node.parentNode);
1575 return children[children.length - 1] === node;
1578 'first-of-type': function(node) {
1579 return Selector._children(node.parentNode, node.tagName)[0] === node;
1582 'last-of-type': function(node) {
1583 var children = Selector._children(node.parentNode, node.tagName);
1584 return children[children.length - 1] === node;
1587 'only-child': function(node) {
1588 var children = Selector._children(node.parentNode);
1589 return children.length === 1 && children[0] === node;
1592 'only-of-type': function(node) {
1593 var children = Selector._children(node.parentNode, node.tagName);
1594 return children.length === 1 && children[0] === node;
1597 'empty': function(node) {
1598 return node.childNodes.length === 0;
1601 'not': function(node, expr) {
1602 return !Selector.test(node, expr);
1605 'contains': function(node, expr) {
1606 var text = node.innerText || node.textContent || '';
1607 return text.indexOf(expr) > -1;
1610 'checked': function(node) {
1611 return (node.checked === true || node.selected === true);
1614 enabled: function(node) {
1615 return (node.disabled !== undefined && !node.disabled);
1618 disabled: function(node) {
1619 return (node.disabled);
1623 Y_mix(Selector.operators, {
1624 '^=': '^{val}', // Match starts with value
1625 '!=': function(node, attr, val) { return node[attr] !== val; }, // Match starts with value
1626 '$=': '{val}$', // Match ends with value
1627 '*=': '{val}' // Match contains value as substring
1630 Selector.combinators['~'] = {
1631 axis: 'previousSibling'
1633 YAHOO.register("selector", YAHOO.util.Selector, {version: "2.9.0", build: "2800"});
1637 /****************************************************************************/
1638 /****************************************************************************/
1639 /****************************************************************************/
1641 var Dom = YAHOO.util.Dom;
1644 * The ColumnSet class defines and manages a DataTable's Columns,
1645 * including nested hierarchies and access to individual Column instances.
1647 * @namespace YAHOO.widget
1649 * @uses YAHOO.util.EventProvider
1651 * @param aDefinitions {Object[]} Array of object literals that define cells in
1654 YAHOO.widget.ColumnSet = function(aDefinitions) {
1655 this._sId = Dom.generateId(null, "yui-cs"); // "yui-cs" + YAHOO.widget.ColumnSet._nCount;
1657 // First clone the defs
1658 aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions);
1659 this._init(aDefinitions);
1661 YAHOO.widget.ColumnSet._nCount++;
1664 /////////////////////////////////////////////////////////////////////////////
1666 // Private member variables
1668 /////////////////////////////////////////////////////////////////////////////
1671 * Internal class variable to index multiple ColumnSet instances.
1673 * @property ColumnSet._nCount
1678 YAHOO.widget.ColumnSet._nCount = 0;
1680 YAHOO.widget.ColumnSet.prototype = {
1682 * Unique instance name.
1691 * Array of object literal Column definitions passed to the constructor.
1693 * @property _aDefinitions
1697 _aDefinitions : null,
1699 /////////////////////////////////////////////////////////////////////////////
1701 // Public member variables
1703 /////////////////////////////////////////////////////////////////////////////
1706 * Top-down tree representation of Column hierarchy.
1709 * @type YAHOO.widget.Column[]
1714 * Flattened representation of all Columns.
1717 * @type YAHOO.widget.Column[]
1723 * Array of Columns that map one-to-one to a table column.
1726 * @type YAHOO.widget.Column[]
1732 * ID index of nested parent hierarchies for HEADERS accessibility attribute.
1740 /////////////////////////////////////////////////////////////////////////////
1744 /////////////////////////////////////////////////////////////////////////////
1747 * Initializes ColumnSet instance with data from Column definitions.
1750 * @param aDefinitions {Object[]} Array of object literals that define cells in
1755 _init : function(aDefinitions) {
1756 // DOM tree representation of all Columns
1758 // Flat representation of all Columns
1760 // Flat representation of only Columns that are meant to display data
1762 // Array of HEADERS attribute values for all keys in the "keys" array
1765 // Tracks current node list depth being tracked
1768 // Internal recursive function to define Column instances
1769 var parseColumns = function(nodeList, parent) {
1773 // Create corresponding tree node if not already there for this depth
1774 if(!tree[nodeDepth]) {
1775 tree[nodeDepth] = [];
1779 // Parse each node at this depth for attributes and any children
1780 for(var j=0; j<nodeList.length; j++) {
1781 var currentNode = nodeList[j];
1783 // Instantiate a new Column for each node
1784 var oColumn = new YAHOO.widget.Column(currentNode);
1786 // Cross-reference Column ID back to the original object literal definition
1787 currentNode.yuiColumnId = oColumn._sId;
1789 // Add the new Column to the flat list
1792 // Assign its parent as an attribute, if applicable
1794 oColumn._oParent = parent;
1797 // The Column has descendants
1798 if(YAHOO.lang.isArray(currentNode.children)) {
1799 oColumn.children = currentNode.children;
1801 // Determine COLSPAN value for this Column
1802 var terminalChildNodes = 0;
1803 var countTerminalChildNodes = function(ancestor) {
1804 var descendants = ancestor.children;
1805 // Drill down each branch and count terminal nodes
1806 for(var k=0; k<descendants.length; k++) {
1807 // Keep drilling down
1808 if(YAHOO.lang.isArray(descendants[k].children)) {
1809 countTerminalChildNodes(descendants[k]);
1811 // Reached branch terminus
1813 terminalChildNodes++;
1817 countTerminalChildNodes(currentNode);
1818 oColumn._nColspan = terminalChildNodes;
1820 // Cascade certain properties to children if not defined on their own
1821 var currentChildren = currentNode.children;
1822 for(var k=0; k<currentChildren.length; k++) {
1823 var child = currentChildren[k];
1824 if(oColumn.className && (child.className === undefined)) {
1825 child.className = oColumn.className;
1827 if(oColumn.editor && (child.editor === undefined)) {
1828 child.editor = oColumn.editor;
1831 if(oColumn.editorOptions && (child.editorOptions === undefined)) {
1832 child.editorOptions = oColumn.editorOptions;
1834 if(oColumn.formatter && (child.formatter === undefined)) {
1835 child.formatter = oColumn.formatter;
1837 if(oColumn.resizeable && (child.resizeable === undefined)) {
1838 child.resizeable = oColumn.resizeable;
1840 if(oColumn.sortable && (child.sortable === undefined)) {
1841 child.sortable = oColumn.sortable;
1843 if(oColumn.hidden) {
1844 child.hidden = true;
1846 if(oColumn.width && (child.width === undefined)) {
1847 child.width = oColumn.width;
1849 if(oColumn.minWidth && (child.minWidth === undefined)) {
1850 child.minWidth = oColumn.minWidth;
1852 if(oColumn.maxAutoWidth && (child.maxAutoWidth === undefined)) {
1853 child.maxAutoWidth = oColumn.maxAutoWidth;
1855 // Backward compatibility
1856 if(oColumn.type && (child.type === undefined)) {
1857 child.type = oColumn.type;
1859 if(oColumn.type && !oColumn.formatter) {
1860 oColumn.formatter = oColumn.type;
1862 if(oColumn.text && !YAHOO.lang.isValue(oColumn.label)) {
1863 oColumn.label = oColumn.text;
1865 if(oColumn.parser) {
1867 if(oColumn.sortOptions && ((oColumn.sortOptions.ascFunction) ||
1868 (oColumn.sortOptions.descFunction))) {
1872 // The children themselves must also be parsed for Column instances
1873 if(!tree[nodeDepth+1]) {
1874 tree[nodeDepth+1] = [];
1876 parseColumns(currentChildren, oColumn);
1878 // This Column does not have any children
1880 oColumn._nKeyIndex = keys.length;
1881 oColumn._nColspan = 1;
1885 // Add the Column to the top-down tree
1886 tree[nodeDepth].push(oColumn);
1891 // Parse out Column instances from the array of object literals
1892 if(YAHOO.lang.isArray(aDefinitions)) {
1893 parseColumns(aDefinitions);
1896 this._aDefinitions = aDefinitions;
1904 // Determine ROWSPAN value for each Column in the tree
1905 var parseTreeForRowspan = function(tree) {
1906 var maxRowDepth = 1;
1910 // Calculate the max depth of descendants for this row
1911 var countMaxRowDepth = function(row, tmpRowDepth) {
1912 tmpRowDepth = tmpRowDepth || 1;
1914 for(var n=0; n<row.length; n++) {
1916 // Column has children, so keep counting
1917 if(YAHOO.lang.isArray(col.children)) {
1919 countMaxRowDepth(col.children, tmpRowDepth);
1922 // No children, is it the max depth?
1924 if(tmpRowDepth > maxRowDepth) {
1925 maxRowDepth = tmpRowDepth;
1932 // Count max row depth for each row
1933 for(var m=0; m<tree.length; m++) {
1934 currentRow = tree[m];
1935 countMaxRowDepth(currentRow);
1937 // Assign the right ROWSPAN values to each Column in the row
1938 for(var p=0; p<currentRow.length; p++) {
1939 currentColumn = currentRow[p];
1940 if(!YAHOO.lang.isArray(currentColumn.children)) {
1941 currentColumn._nRowspan = maxRowDepth;
1944 currentColumn._nRowspan = 1;
1948 // Reset counter for next row
1952 parseTreeForRowspan(tree);
1954 // Store tree index values
1955 for(i=0; i<tree[0].length; i++) {
1956 tree[0][i]._nTreeIndex = i;
1959 // Store header relationships in an array for HEADERS attribute
1960 var recurseAncestorsForHeaders = function(i, oColumn) {
1961 headers[i].push(oColumn.getSanitizedKey());
1962 if(oColumn._oParent) {
1963 recurseAncestorsForHeaders(i, oColumn._oParent);
1966 for(i=0; i<keys.length; i++) {
1968 recurseAncestorsForHeaders(i, keys[i]);
1969 headers[i] = headers[i].reverse();
1972 // Save to the ColumnSet instance
1976 this.headers = headers;
1979 /////////////////////////////////////////////////////////////////////////////
1983 /////////////////////////////////////////////////////////////////////////////
1986 * Returns unique name of the ColumnSet instance.
1989 * @return {String} Unique name of the ColumnSet instance.
1992 getId : function() {
1997 * ColumnSet instance name, for logging.
2000 * @return {String} Unique name of the ColumnSet instance.
2003 toString : function() {
2004 return "ColumnSet instance " + this._sId;
2008 * Public accessor to the definitions array.
2010 * @method getDefinitions
2011 * @return {Object[]} Array of object literal Column definitions.
2014 getDefinitions : function() {
2015 var aDefinitions = this._aDefinitions;
2017 // Internal recursive function to define Column instances
2018 var parseColumns = function(nodeList, oSelf) {
2019 // Parse each node at this depth for attributes and any children
2020 for(var j=0; j<nodeList.length; j++) {
2021 var currentNode = nodeList[j];
2023 // Get the Column for each node
2024 var oColumn = oSelf.getColumnById(currentNode.yuiColumnId);
2027 // Update the current values
2028 var oDefinition = oColumn.getDefinition();
2029 for(var name in oDefinition) {
2030 if(YAHOO.lang.hasOwnProperty(oDefinition, name)) {
2031 currentNode[name] = oDefinition[name];
2036 // The Column has descendants
2037 if(YAHOO.lang.isArray(currentNode.children)) {
2038 // The children themselves must also be parsed for Column instances
2039 parseColumns(currentNode.children, oSelf);
2044 parseColumns(aDefinitions, this);
2045 this._aDefinitions = aDefinitions;
2046 return aDefinitions;
2050 * Returns Column instance with given ID.
2052 * @method getColumnById
2053 * @param column {String} Column ID.
2054 * @return {YAHOO.widget.Column} Column instance.
2057 getColumnById : function(column) {
2058 if(YAHOO.lang.isString(column)) {
2059 var allColumns = this.flat;
2060 for(var i=allColumns.length-1; i>-1; i--) {
2061 if(allColumns[i]._sId === column) {
2062 return allColumns[i];
2070 * Returns Column instance with given key or ColumnSet key index.
2073 * @param column {String | Number} Column key or ColumnSet key index.
2074 * @return {YAHOO.widget.Column} Column instance.
2077 getColumn : function(column) {
2078 if(YAHOO.lang.isNumber(column) && this.keys[column]) {
2079 return this.keys[column];
2081 else if(YAHOO.lang.isString(column)) {
2082 var allColumns = this.flat;
2084 for(var i=0; i<allColumns.length; i++) {
2085 if(allColumns[i].key === column) {
2086 aColumns.push(allColumns[i]);
2089 if(aColumns.length === 1) {
2092 else if(aColumns.length > 1) {
2100 * Public accessor returns array of given Column's desendants (if any), including itself.
2102 * @method getDescendants
2103 * @parem {YAHOO.widget.Column} Column instance.
2104 * @return {Array} Array including the Column itself and all descendants (if any).
2106 getDescendants : function(oColumn) {
2108 var allDescendants = [];
2111 // Recursive function to loop thru all children
2112 var parse = function(oParent) {
2113 allDescendants.push(oParent);
2114 // This Column has children
2115 if(oParent.children) {
2116 for(i=0; i<oParent.children.length; i++) {
2117 parse(oSelf.getColumn(oParent.children[i].key));
2123 return allDescendants;
2127 /****************************************************************************/
2128 /****************************************************************************/
2129 /****************************************************************************/
2132 * The Column class defines and manages attributes of DataTable Columns
2134 * @namespace YAHOO.widget
2137 * @param oConfigs {Object} Object literal of definitions.
2139 YAHOO.widget.Column = function(oConfigs) {
2140 this._sId = Dom.generateId(null, "yui-col"); // "yui-col" + YAHOO.widget.Column._nCount;
2142 // Object literal defines Column attributes
2143 if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
2144 for(var sConfig in oConfigs) {
2146 this[sConfig] = oConfigs[sConfig];
2151 // Assign a key if not found
2152 if(!YAHOO.lang.isValue(this.key)) {
2153 this.key = Dom.generateId(null, "yui-dt-col"); //"yui-dt-col" + YAHOO.widget.Column._nCount;
2156 // Assign a field if not found, defaults to key
2157 if(!YAHOO.lang.isValue(this.field)) {
2158 this.field = this.key;
2161 // Increment counter
2162 YAHOO.widget.Column._nCount++;
2164 // Backward compatibility
2165 if(this.width && !YAHOO.lang.isNumber(this.width)) {
2168 if(this.editor && YAHOO.lang.isString(this.editor)) {
2169 this.editor = new YAHOO.widget.CellEditor(this.editor, this.editorOptions);
2173 /////////////////////////////////////////////////////////////////////////////
2175 // Private member variables
2177 /////////////////////////////////////////////////////////////////////////////
2179 YAHOO.lang.augmentObject(YAHOO.widget.Column, {
2181 * Internal class variable to index multiple Column instances.
2183 * @property Column._nCount
2190 formatCheckbox : function(elCell, oRecord, oColumn, oData) {
2191 YAHOO.widget.DataTable.formatCheckbox(elCell, oRecord, oColumn, oData);
2194 formatCurrency : function(elCell, oRecord, oColumn, oData) {
2195 YAHOO.widget.DataTable.formatCurrency(elCell, oRecord, oColumn, oData);
2198 formatDate : function(elCell, oRecord, oColumn, oData) {
2199 YAHOO.widget.DataTable.formatDate(elCell, oRecord, oColumn, oData);
2202 formatEmail : function(elCell, oRecord, oColumn, oData) {
2203 YAHOO.widget.DataTable.formatEmail(elCell, oRecord, oColumn, oData);
2206 formatLink : function(elCell, oRecord, oColumn, oData) {
2207 YAHOO.widget.DataTable.formatLink(elCell, oRecord, oColumn, oData);
2210 formatNumber : function(elCell, oRecord, oColumn, oData) {
2211 YAHOO.widget.DataTable.formatNumber(elCell, oRecord, oColumn, oData);
2214 formatSelect : function(elCell, oRecord, oColumn, oData) {
2215 YAHOO.widget.DataTable.formatDropdown(elCell, oRecord, oColumn, oData);
2219 YAHOO.widget.Column.prototype = {
2221 * Unique String identifier assigned at instantiation.
2230 * Reference to Column's current position index within its ColumnSet's keys
2231 * array, if applicable. This property only applies to non-nested and bottom-
2232 * level child Columns.
2234 * @property _nKeyIndex
2241 * Reference to Column's current position index within its ColumnSet's tree
2242 * array, if applicable. This property only applies to non-nested and top-
2243 * level parent Columns.
2245 * @property _nTreeIndex
2252 * Number of table cells the Column spans.
2254 * @property _nColspan
2261 * Number of table rows the Column spans.
2263 * @property _nRowspan
2270 * Column's parent Column instance, or null.
2272 * @property _oParent
2273 * @type YAHOO.widget.Column
2279 * The DOM reference to the associated TH element.
2288 * The DOM reference to the associated TH element's liner DIV element.
2290 * @property _elThLiner
2297 * The DOM reference to the associated TH element's label SPAN element.
2299 * @property _elThLabel
2306 * The DOM reference to the associated resizerelement (if any).
2308 * @property _elResizer
2315 * Internal width tracker.
2324 * For unreg() purposes, a reference to the Column's DragDrop instance.
2327 * @type YAHOO.util.DragDrop
2333 * For unreg() purposes, a reference to the Column resizer's DragDrop instance.
2335 * @property _ddResizer
2336 * @type YAHOO.util.DragDrop
2341 /////////////////////////////////////////////////////////////////////////////
2343 // Public member variables
2345 /////////////////////////////////////////////////////////////////////////////
2348 * Unique name, required. If "label" property is not provided, the "key"
2349 * value will be treated as markup and inserted into the DOM as innerHTML.
2357 * Associated database field, or null.
2365 * Value displayed as Column header in the TH element. String value is
2366 * treated as markup and inserted into the DOM as innerHTML.
2374 * Column head cell ABBR for accessibility.
2382 * Array of object literals that define children (nested headers) of a Column.
2384 * @property children
2390 * Column width (in pixels).
2398 * Minimum Column width (in pixels).
2400 * @property minWidth
2407 * When a width is not defined for a Column, maxAutoWidth defines an upper
2408 * limit that the Column should be auto-sized to. If resizeable is enabled,
2409 * users may still resize to a greater width. Most useful for Columns intended
2410 * to hold long unbroken, unwrapped Strings, such as URLs, to prevent very
2411 * wide Columns from disrupting visual readability by inducing truncation.
2413 * @property maxAutoWidth
2417 maxAutoWidth : null,
2420 * True if Column is in hidden state.
2429 * True if Column is in selected state.
2431 * @property selected
2438 * Custom CSS class or array of classes to be applied to every cell in the Column.
2440 * @property className
2441 * @type String || String[]
2446 * Cell formatter function, or a shortcut pointer to a function in the
2447 * DataTable.Formatter object. The function, called from the DataTable's
2448 * formatCell method, renders markup into the cell liner
2449 * element and accepts the following arguments:
2452 * <dd>The element to write innerHTML to.</dd>
2454 * <dd>The associated Record for the row.</dd>
2456 * <dd>The Column instance for the cell.</dd>
2458 * <dd>The data value for the cell.</dd>
2461 * @property formatter
2462 * @type String || HTMLFunction
2467 * Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
2469 * @property currencyOptions
2473 currencyOptions : null,
2476 * Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
2478 * @property dateOptions
2485 * Array of dropdown values for formatter:"dropdown" cases. Can either be a
2486 * simple array (e.g., ["Alabama","Alaska","Arizona","Arkansas"]) or a an
2487 * array of objects (e.g., [{label:"Alabama", value:"AL"},
2488 * {label:"Alaska", value:"AK"}, {label:"Arizona", value:"AZ"},
2489 * {label:"Arkansas", value:"AR"}]). String values are treated as markup and
2490 * inserted into the DOM as innerHTML.
2492 * @property dropdownOptions
2493 * @type HTML[] | Object[]
2495 dropdownOptions : null,
2498 * A CellEditor instance, otherwise Column is not editable.
2501 * @type YAHOO.widget.CellEditor
2506 * True if Column is resizeable, false otherwise. The Drag & Drop Utility is
2507 * required to enable this feature. Only bottom-level and non-nested Columns are
2510 * @property resizeable
2517 * True if Column is sortable, false otherwise.
2519 * @property sortable
2526 * @property sortOptions.defaultOrder
2527 * @deprecated Use sortOptions.defaultDir.
2530 * Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
2532 * @property sortOptions.defaultDir
2537 * Custom field to sort on.
2539 * @property sortOptions.field
2544 * Custom sort handler. Signature: sortFunction(a, b, desc, field) where field is the sortOptions.field value
2546 * @property sortOptions.sortFunction
2566 /////////////////////////////////////////////////////////////////////////////
2570 /////////////////////////////////////////////////////////////////////////////
2573 * Returns unique ID string.
2576 * @return {String} Unique ID string.
2578 getId : function() {
2583 * Column instance name, for logging.
2586 * @return {String} Column's unique name.
2588 toString : function() {
2589 return "Column instance " + this._sId;
2593 * Returns object literal definition.
2595 * @method getDefinition
2596 * @return {Object} Object literal definition.
2598 getDefinition : function() {
2599 var oDefinition = {};
2601 // Update the definition
2602 oDefinition.abbr = this.abbr;
2603 oDefinition.className = this.className;
2604 oDefinition.editor = this.editor;
2605 oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
2606 oDefinition.field = this.field;
2607 oDefinition.formatter = this.formatter;
2608 oDefinition.hidden = this.hidden;
2609 oDefinition.key = this.key;
2610 oDefinition.label = this.label;
2611 oDefinition.minWidth = this.minWidth;
2612 oDefinition.maxAutoWidth = this.maxAutoWidth;
2613 oDefinition.resizeable = this.resizeable;
2614 oDefinition.selected = this.selected;
2615 oDefinition.sortable = this.sortable;
2616 oDefinition.sortOptions = this.sortOptions;
2617 oDefinition.width = this.width;
2620 oDefinition._calculatedWidth = this._calculatedWidth;
2626 * Returns unique Column key.
2629 * @return {String} Column key.
2631 getKey : function() {
2639 * @return {String} Column field.
2641 getField : function() {
2646 * Returns Column key which has been sanitized for DOM (class and ID) usage
2647 * starts with letter, contains only letters, numbers, hyphen, or period.
2649 * @method getSanitizedKey
2650 * @return {String} Sanitized Column key.
2652 getSanitizedKey : function() {
2653 return this.getKey().replace(/[^\w\-]/g,"");
2657 * Public accessor returns Column's current position index within its
2658 * ColumnSet's keys array, if applicable. Only non-nested and bottom-level
2659 * child Columns will return a value.
2661 * @method getKeyIndex
2662 * @return {Number} Position index, or null.
2664 getKeyIndex : function() {
2665 return this._nKeyIndex;
2669 * Public accessor returns Column's current position index within its
2670 * ColumnSet's tree array, if applicable. Only non-nested and top-level parent
2671 * Columns will return a value;
2673 * @method getTreeIndex
2674 * @return {Number} Position index, or null.
2676 getTreeIndex : function() {
2677 return this._nTreeIndex;
2681 * Public accessor returns Column's parent instance if any, or null otherwise.
2684 * @return {YAHOO.widget.Column} Column's parent instance.
2686 getParent : function() {
2687 return this._oParent;
2691 * Public accessor returns Column's calculated COLSPAN value.
2693 * @method getColspan
2694 * @return {Number} Column's COLSPAN value.
2696 getColspan : function() {
2697 return this._nColspan;
2699 // Backward compatibility
2700 getColSpan : function() {
2701 return this.getColspan();
2705 * Public accessor returns Column's calculated ROWSPAN value.
2707 * @method getRowspan
2708 * @return {Number} Column's ROWSPAN value.
2710 getRowspan : function() {
2711 return this._nRowspan;
2715 * Returns DOM reference to the key TH element.
2718 * @return {HTMLElement} TH element.
2720 getThEl : function() {
2725 * Returns DOM reference to the TH's liner DIV element. Introduced since
2726 * resizeable Columns may have an extra resizer liner, making the DIV liner
2727 * not reliably the TH element's first child.
2729 * @method getThLInerEl
2730 * @return {HTMLElement} TH element.
2732 getThLinerEl : function() {
2733 return this._elThLiner;
2737 * Returns DOM reference to the resizer element, or null.
2739 * @method getResizerEl
2740 * @return {HTMLElement} DIV element.
2742 getResizerEl : function() {
2743 return this._elResizer;
2746 // Backward compatibility
2749 * @deprecated Use getThEl
2751 getColEl : function() {
2752 return this.getThEl();
2754 getIndex : function() {
2755 return this.getKeyIndex();
2757 format : function() {
2761 /****************************************************************************/
2762 /****************************************************************************/
2763 /****************************************************************************/
2766 * Sort static utility to support Column sorting.
2768 * @namespace YAHOO.util
2773 /////////////////////////////////////////////////////////////////////////////
2777 /////////////////////////////////////////////////////////////////////////////
2780 * Comparator function for simple case-insensitive string sorting.
2783 * @param a {Object} First sort argument.
2784 * @param b {Object} Second sort argument.
2785 * @param desc {Boolean} True if sort direction is descending, false if
2786 * sort direction is ascending.
2787 * @return {Boolean} Return -1 when a < b. Return 0 when a = b.
2788 * Return 1 when a > b.
2790 compare: function(a, b, desc) {
2791 if((a === null) || (typeof a == "undefined")) {
2792 if((b === null) || (typeof b == "undefined")) {
2799 else if((b === null) || (typeof b == "undefined")) {
2803 if(a.constructor == String) {
2804 a = a.toLowerCase();
2806 if(b.constructor == String) {
2807 b = b.toLowerCase();
2810 return (desc) ? 1 : -1;
2813 return (desc) ? -1 : 1;
2821 /****************************************************************************/
2822 /****************************************************************************/
2823 /****************************************************************************/
2826 * ColumnDD subclasses DragDrop to support rearrangeable Columns.
2828 * @namespace YAHOO.util
2830 * @extends YAHOO.util.DDProxy
2832 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
2833 * @param oColumn {YAHOO.widget.Column} Column instance.
2834 * @param elTh {HTMLElement} TH element reference.
2835 * @param elTarget {HTMLElement} Drag target element.
2837 YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
2838 if(oDataTable && oColumn && elTh && elTarget) {
2839 this.datatable = oDataTable;
2840 this.table = oDataTable.getTableEl();
2841 this.column = oColumn;
2842 this.headCell = elTh;
2843 this.pointer = elTarget;
2844 this.newIndex = null;
2846 this.initFrame(); // Needed for DDProxy
2847 this.invalidHandleTypes = {};
2849 // Set top/bottom padding to account for children of nested columns
2850 this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
2852 YAHOO.util.Event.on(window, 'resize', function() {
2853 this.initConstraints();
2860 if(YAHOO.util.DDProxy) {
2861 YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
2862 initConstraints: function() {
2863 //Get the top, right, bottom and left positions
2864 var region = YAHOO.util.Dom.getRegion(this.table),
2865 //Get the element we are working on
2867 //Get the xy position of it
2868 xy = YAHOO.util.Dom.getXY(el),
2869 //Get the width and height
2870 width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
2871 height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
2872 //Set left to x minus left
2873 left = ((xy[0] - region.left) + 15), //Buffer of 15px
2874 //Set right to right minus x minus width
2875 right = ((region.right - xy[0] - width) + 15);
2877 //Set the constraints based on the above calculations
2878 this.setXConstraint(left, right);
2879 this.setYConstraint(10, 10);
2881 _resizeProxy: function() {
2882 YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this, arguments);
2883 var dragEl = this.getDragEl(),
2886 YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
2887 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
2888 var xy = YAHOO.util.Dom.getXY(el);
2889 YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
2891 YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
2892 YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
2893 YAHOO.util.Dom.setXY(this.dragEl, xy);
2895 onMouseDown: function() {
2896 this.initConstraints();
2897 this.resetConstraints();
2899 clickValidator: function(e) {
2900 if(!this.column.hidden) {
2901 var target = YAHOO.util.Event.getTarget(e);
2902 return ( this.isValidHandleChild(target) &&
2903 (this.id == this.handleElId ||
2904 this.DDM.handleWasClicked(target, this.id)) );
2907 onDragOver: function(ev, id) {
2908 // Validate target as a Column
2909 var target = this.datatable.getColumn(id);
2911 // Validate target as a top-level parent
2912 var targetIndex = target.getTreeIndex();
2913 while((targetIndex === null) && target.getParent()) {
2914 target = target.getParent();
2915 targetIndex = target.getTreeIndex();
2917 if(targetIndex !== null) {
2918 // Are we placing to left or right of target?
2919 var elTarget = target.getThEl();
2920 var newIndex = targetIndex;
2921 var mouseX = YAHOO.util.Event.getPageX(ev),
2922 targetX = YAHOO.util.Dom.getX(elTarget),
2923 midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
2924 currentIndex = this.column.getTreeIndex();
2926 if (mouseX < midX) {
2927 YAHOO.util.Dom.setX(this.pointer, targetX);
2929 var targetWidth = parseInt(elTarget.offsetWidth, 10);
2930 YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
2933 if (targetIndex > currentIndex) {
2939 else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
2940 newIndex = this.datatable.getColumnSet().tree[0].length;
2942 this.newIndex = newIndex;
2946 onDragDrop: function() {
2947 this.datatable.reorderColumn(this.column, this.newIndex);
2949 endDrag: function() {
2950 this.newIndex = null;
2951 YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
2956 /****************************************************************************/
2957 /****************************************************************************/
2958 /****************************************************************************/
2961 * ColumnResizer subclasses DragDrop to support resizeable Columns.
2963 * @namespace YAHOO.util
2964 * @class ColumnResizer
2965 * @extends YAHOO.util.DDProxy
2967 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
2968 * @param oColumn {YAHOO.widget.Column} Column instance.
2969 * @param elTh {HTMLElement} TH element reference.
2970 * @param sHandleElId {String} DOM ID of the handle element that causes the resize.
2971 * @param elProxy {HTMLElement} Resizer proxy element.
2973 YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
2974 if(oDataTable && oColumn && elTh && sHandleId) {
2975 this.datatable = oDataTable;
2976 this.column = oColumn;
2977 this.headCell = elTh;
2978 this.headCellLiner = oColumn.getThLinerEl();
2979 this.resizerLiner = elTh.firstChild;
2980 this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
2981 this.initFrame(); // Needed for proxy
2982 this.resetResizerEl(); // Needed when rowspan > 0
2984 // Set right padding for bug 1858462
2985 this.setPadding(0, 1, 0, 0);
2992 YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
2993 /////////////////////////////////////////////////////////////////////////////
2997 /////////////////////////////////////////////////////////////////////////////
2999 * Resets resizer element.
3001 * @method resetResizerEl
3003 resetResizerEl : function() {
3004 var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
3005 resizerStyle.left = "auto";
3006 resizerStyle.right = 0;
3007 resizerStyle.top = "auto";
3008 resizerStyle.bottom = 0;
3009 resizerStyle.height = this.headCell.offsetHeight+"px";
3012 /////////////////////////////////////////////////////////////////////////////
3014 // Public DOM event handlers
3016 /////////////////////////////////////////////////////////////////////////////
3019 * Handles mouseup events on the Column resizer.
3022 * @param e {string} The mouseup event
3024 onMouseUp : function(e) {
3025 // Reset height of all resizer els in case TH's have changed height
3026 var allKeys = this.datatable.getColumnSet().keys,
3028 for(var i=0, len=allKeys.length; i<len; i++) {
3030 if(col._ddResizer) {
3031 col._ddResizer.resetResizerEl();
3034 this.resetResizerEl();
3036 var el = this.headCellLiner;
3037 var newWidth = el.offsetWidth -
3038 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingLeft"),10)|0) -
3039 (parseInt(YAHOO.util.Dom.getStyle(el,"paddingRight"),10)|0);
3041 this.datatable.fireEvent("columnResizeEvent", {column:this.column,target:this.headCell,width:newWidth});
3045 * Handles mousedown events on the Column resizer.
3047 * @method onMouseDown
3048 * @param e {string} The mousedown event
3050 onMouseDown : function(e) {
3051 this.startWidth = this.headCellLiner.offsetWidth;
3052 this.startX = YAHOO.util.Event.getXY(e)[0];
3053 this.nLinerPadding = (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingLeft"),10)|0) +
3054 (parseInt(YAHOO.util.Dom.getStyle(this.headCellLiner,"paddingRight"),10)|0);
3058 * Custom clickValidator to ensure Column is not in hidden state.
3060 * @method clickValidator
3064 clickValidator : function(e) {
3065 if(!this.column.hidden) {
3066 var target = YAHOO.util.Event.getTarget(e);
3067 return ( this.isValidHandleChild(target) &&
3068 (this.id == this.handleElId ||
3069 this.DDM.handleWasClicked(target, this.id)) );
3074 * Handles start drag on the Column resizer.
3077 * @param e {string} The drag event
3079 startDrag : function() {
3080 // Shrinks height of all resizer els to not hold open TH els
3081 var allKeys = this.datatable.getColumnSet().keys,
3082 thisKey = this.column.getKeyIndex(),
3084 for(var i=0, len=allKeys.length; i<len; i++) {
3086 if(col._ddResizer) {
3087 YAHOO.util.Dom.get(col._ddResizer.handleElId).style.height = "1em";
3093 * Handles drag events on the Column resizer.
3096 * @param e {string} The drag event
3098 onDrag : function(e) {
3099 var newX = YAHOO.util.Event.getXY(e)[0];
3100 if(newX > YAHOO.util.Dom.getX(this.headCellLiner)) {
3101 var offsetX = newX - this.startX;
3102 var newWidth = this.startWidth + offsetX - this.nLinerPadding;
3104 this.datatable.setColumnWidth(this.column, newWidth);
3111 /////////////////////////////////////////////////////////////////////////////
3115 /////////////////////////////////////////////////////////////////////////////
3118 * @property editorOptions
3119 * @deprecated Pass configs directly to CellEditor constructor.
3125 var lang = YAHOO.lang,
3127 widget = YAHOO.widget,
3131 DT = widget.DataTable;
3133 /****************************************************************************/
3134 /****************************************************************************/
3135 /****************************************************************************/
3138 * A RecordSet defines and manages a set of Records.
3140 * @namespace YAHOO.widget
3142 * @param data {Object || Object[]} An object literal or an array of data.
3145 YAHOO.widget.RecordSet = function(data) {
3149 var RS = widget.RecordSet;
3152 * Internal class variable to name multiple Recordset instances.
3154 * @property RecordSet._nCount
3163 /////////////////////////////////////////////////////////////////////////////
3165 // Private member variables
3167 /////////////////////////////////////////////////////////////////////////////
3169 * Unique String identifier assigned at instantiation.
3178 * Internal counter of how many Records are in the RecordSet.
3183 * @deprecated No longer used
3187 /////////////////////////////////////////////////////////////////////////////
3191 /////////////////////////////////////////////////////////////////////////////
3197 * @param data {Object || Object[]} An object literal or an array of data.
3200 _init : function(data) {
3201 // Internal variables
3202 this._sId = Dom.generateId(null, "yui-rs");// "yui-rs" + widget.RecordSet._nCount;
3203 widget.RecordSet._nCount++;
3210 if(lang.isArray(data)) {
3211 this.addRecords(data);
3213 else if(lang.isObject(data)) {
3214 this.addRecord(data);
3221 * Initializes custom events.
3223 * @method _initEvents
3226 _initEvents : function() {
3227 this.createEvent("recordAddEvent");
3228 this.createEvent("recordsAddEvent");
3229 this.createEvent("recordSetEvent");
3230 this.createEvent("recordsSetEvent");
3231 this.createEvent("recordUpdateEvent");
3232 this.createEvent("recordDeleteEvent");
3233 this.createEvent("recordsDeleteEvent");
3234 this.createEvent("resetEvent");
3235 this.createEvent("recordValueUpdateEvent");
3239 * Adds one Record to the RecordSet at the given index. If index is null,
3240 * then adds the Record to the end of the RecordSet.
3242 * @method _addRecord
3243 * @param oData {Object} An object literal of data.
3244 * @param index {Number} (optional) Position index.
3245 * @return {YAHOO.widget.Record} A Record instance.
3248 _addRecord : function(oData, index) {
3249 var oRecord = new YAHOO.widget.Record(oData);
3251 if(YAHOO.lang.isNumber(index) && (index > -1)) {
3252 this._records.splice(index,0,oRecord);
3255 //index = this.getLength();
3256 //this._records[index] = oRecord;
3257 this._records[this._records.length] = oRecord;
3264 * Sets/replaces one Record to the RecordSet at the given index. Existing
3265 * Records with higher indexes are not shifted. If no index specified, the
3266 * Record is added to the end of the RecordSet.
3268 * @method _setRecord
3269 * @param oData {Object} An object literal of data.
3270 * @param index {Number} (optional) Position index.
3271 * @return {YAHOO.widget.Record} A Record instance.
3274 _setRecord : function(oData, index) {
3275 if (!lang.isNumber(index) || index < 0) {
3276 index = this._records.length;
3278 return (this._records[index] = new widget.Record(oData));
3280 if(lang.isNumber(index) && (index > -1)) {
3281 this._records[index] = oRecord;
3282 if((index+1) > this.getLength()) {
3283 this._length = index+1;
3287 this._records[this.getLength()] = oRecord;
3295 * Deletes Records from the RecordSet at the given index. If range is null,
3296 * then only one Record is deleted.
3298 * @method _deleteRecord
3299 * @param index {Number} Position index.
3300 * @param range {Number} (optional) How many Records to delete
3303 _deleteRecord : function(index, range) {
3304 if(!lang.isNumber(range) || (range < 0)) {
3307 this._records.splice(index, range);
3308 //this._length = this._length - range;
3311 /////////////////////////////////////////////////////////////////////////////
3315 /////////////////////////////////////////////////////////////////////////////
3318 * Returns unique name of the RecordSet instance.
3321 * @return {String} Unique name of the RecordSet instance.
3323 getId : function() {
3328 * Public accessor to the unique name of the RecordSet instance.
3331 * @return {String} Unique name of the RecordSet instance.
3333 toString : function() {
3334 return "RecordSet instance " + this._sId;
3338 * Returns the number of Records held in the RecordSet.
3341 * @return {Number} Number of records in the RecordSet.
3343 getLength : function() {
3344 //return this._length;
3345 return this._records.length;
3349 * Returns Record by ID or RecordSet position index.
3352 * @param record {YAHOO.widget.Record | Number | String} Record instance,
3353 * RecordSet position index, or Record ID.
3354 * @return {YAHOO.widget.Record} Record object.
3356 getRecord : function(record) {
3358 if(record instanceof widget.Record) {
3359 for(i=0; i<this._records.length; i++) {
3360 if(this._records[i] && (this._records[i]._sId === record._sId)) {
3365 else if(lang.isNumber(record)) {
3366 if((record > -1) && (record < this.getLength())) {
3367 return this._records[record];
3370 else if(lang.isString(record)) {
3371 for(i=0; i<this._records.length; i++) {
3372 if(this._records[i] && (this._records[i]._sId === record)) {
3373 return this._records[i];
3377 // Not a valid Record for this RecordSet
3383 * Returns an array of Records from the RecordSet.
3385 * @method getRecords
3386 * @param index {Number} (optional) Recordset position index of which Record to
3388 * @param range {Number} (optional) Number of Records to get.
3389 * @return {YAHOO.widget.Record[]} Array of Records starting at given index and
3390 * length equal to given range. If index is not given, all Records are returned.
3392 getRecords : function(index, range) {
3393 if(!lang.isNumber(index)) {
3394 return this._records;
3396 if(!lang.isNumber(range)) {
3397 return this._records.slice(index);
3399 return this._records.slice(index, index+range);
3403 * Returns a boolean indicating whether Records exist in the RecordSet at the
3404 * specified index range. Returns true if and only if a Record exists at each
3405 * index in the range.
3406 * @method hasRecords
3409 * @return {Boolean} true if all indices are populated in the RecordSet
3411 hasRecords : function (index, range) {
3412 var recs = this.getRecords(index,range);
3413 for (var i = 0; i < range; ++i) {
3414 if (typeof recs[i] === 'undefined') {
3422 * Returns current position index for the given Record.
3424 * @method getRecordIndex
3425 * @param oRecord {YAHOO.widget.Record} Record instance.
3426 * @return {Number} Record's RecordSet position index.
3429 getRecordIndex : function(oRecord) {
3431 for(var i=this._records.length-1; i>-1; i--) {
3432 if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
3442 * Adds one Record to the RecordSet at the given index. If index is null,
3443 * then adds the Record to the end of the RecordSet.
3446 * @param oData {Object} An object literal of data.
3447 * @param index {Number} (optional) Position index.
3448 * @return {YAHOO.widget.Record} A Record instance.
3450 addRecord : function(oData, index) {
3451 if(lang.isObject(oData)) {
3452 var oRecord = this._addRecord(oData, index);
3453 this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
3462 * Adds multiple Records at once to the RecordSet at the given index with the
3463 * given object literal data. If index is null, then the new Records are
3464 * added to the end of the RecordSet.
3466 * @method addRecords
3467 * @param aData {Object[]} An object literal data or an array of data object literals.
3468 * @param index {Number} (optional) Position index.
3469 * @return {YAHOO.widget.Record[]} An array of Record instances.
3471 addRecords : function(aData, index) {
3472 if(lang.isArray(aData)) {
3473 var newRecords = [],
3476 index = lang.isNumber(index) ? index : this._records.length;
3479 // Can't go backwards bc we need to preserve order
3480 for(i=0,len=aData.length; i<len; ++i) {
3481 if(lang.isObject(aData[i])) {
3482 var record = this._addRecord(aData[i], idx++);
3483 newRecords.push(record);
3486 this.fireEvent("recordsAddEvent",{records:newRecords,data:aData});
3489 else if(lang.isObject(aData)) {
3490 var oRecord = this._addRecord(aData);
3491 this.fireEvent("recordsAddEvent",{records:[oRecord],data:aData});
3500 * Sets or replaces one Record to the RecordSet at the given index. Unlike
3501 * addRecord, an existing Record at that index is not shifted to preserve it.
3502 * If no index is specified, it adds the Record to the end of the RecordSet.
3505 * @param oData {Object} An object literal of data.
3506 * @param index {Number} (optional) Position index.
3507 * @return {YAHOO.widget.Record} A Record instance.
3509 setRecord : function(oData, index) {
3510 if(lang.isObject(oData)) {
3511 var oRecord = this._setRecord(oData, index);
3512 this.fireEvent("recordSetEvent",{record:oRecord,data:oData});
3521 * Sets or replaces multiple Records at once to the RecordSet with the given
3522 * data, starting at the given index. If index is not specified, then the new
3523 * Records are added to the end of the RecordSet.
3525 * @method setRecords
3526 * @param aData {Object[]} An array of object literal data.
3527 * @param index {Number} (optional) Position index.
3528 * @return {YAHOO.widget.Record[]} An array of Record instances.
3530 setRecords : function(aData, index) {
3531 var Rec = widget.Record,
3532 a = lang.isArray(aData) ? aData : [aData],
3534 i = 0, l = a.length, j = 0;
3536 index = parseInt(index,10)|0;
3539 if (typeof a[i] === 'object' && a[i]) {
3540 added[j++] = this._records[index + i] = new Rec(a[i]);
3544 this.fireEvent("recordsSetEvent",{records:added,data:aData});
3545 // Backward compatibility for bug 1918245
3546 this.fireEvent("recordsSet",{records:added,data:aData});
3548 if (a.length && !added.length) {
3555 * Updates given Record with given data.
3557 * @method updateRecord
3558 * @param record {YAHOO.widget.Record | Number | String} A Record instance,
3559 * a RecordSet position index, or a Record ID.
3560 * @param oData {Object} Object literal of new data.
3561 * @return {YAHOO.widget.Record} Updated Record, or null.
3563 updateRecord : function(record, oData) {
3564 var oRecord = this.getRecord(record);
3565 if(oRecord && lang.isObject(oData)) {
3566 // Copy data from the Record for the event that gets fired later
3568 for(var key in oRecord._oData) {
3569 if(lang.hasOwnProperty(oRecord._oData, key)) {
3570 oldData[key] = oRecord._oData[key];
3573 oRecord._oData = oData;
3574 this.fireEvent("recordUpdateEvent",{record:oRecord,newData:oData,oldData:oldData});
3584 * @deprecated Use updateRecordValue
3586 updateKey : function(record, sKey, oData) {
3587 this.updateRecordValue(record, sKey, oData);
3590 * Sets given Record at given key to given data.
3592 * @method updateRecordValue
3593 * @param record {YAHOO.widget.Record | Number | String} A Record instance,
3594 * a RecordSet position index, or a Record ID.
3595 * @param sKey {String} Key name.
3596 * @param oData {Object} New data.
3598 updateRecordValue : function(record, sKey, oData) {
3599 var oRecord = this.getRecord(record);
3602 var keyValue = oRecord._oData[sKey];
3603 // Copy data from the Record for the event that gets fired later
3604 if(keyValue && lang.isObject(keyValue)) {
3606 for(var key in keyValue) {
3607 if(lang.hasOwnProperty(keyValue, key)) {
3608 oldData[key] = keyValue[key];
3617 oRecord._oData[sKey] = oData;
3618 this.fireEvent("keyUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
3619 this.fireEvent("recordValueUpdateEvent",{record:oRecord,key:sKey,newData:oData,oldData:oldData});
3626 * Replaces all Records in RecordSet with new object literal data.
3628 * @method replaceRecords
3629 * @param data {Object || Object[]} An object literal of data or an array of
3630 * data object literals.
3631 * @return {YAHOO.widget.Record || YAHOO.widget.Record[]} A Record instance or
3632 * an array of Records.
3634 replaceRecords : function(data) {
3636 return this.addRecords(data);
3640 * Sorts all Records by given function. Records keep their unique IDs but will
3641 * have new RecordSet position indexes.
3643 * @method sortRecords
3644 * @param fnSort {Function} Reference to a sort function.
3645 * @param desc {Boolean} True if sort direction is descending, false if sort
3646 * direction is ascending.
3647 * @param field {String} The field to sort by, from sortOptions.field
3648 * @return {YAHOO.widget.Record[]} Sorted array of Records.
3650 sortRecords : function(fnSort, desc, field) {
3651 return this._records.sort(function(a, b) {return fnSort(a, b, desc, field);});
3655 * Reverses all Records, so ["one", "two", "three"] becomes ["three", "two", "one"].
3657 * @method reverseRecords
3658 * @return {YAHOO.widget.Record[]} Reverse-sorted array of Records.
3660 reverseRecords : function() {
3661 return this._records.reverse();
3665 * Removes the Record at the given position index from the RecordSet. If a range
3666 * is also provided, removes that many Records, starting from the index. Length
3667 * of RecordSet is correspondingly shortened.
3669 * @method deleteRecord
3670 * @param index {Number} Record's RecordSet position index.
3671 * @return {Object} A copy of the data held by the deleted Record.
3673 deleteRecord : function(index) {
3674 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
3675 var oData = this.getRecord(index).getData();
3677 this._deleteRecord(index);
3678 this.fireEvent("recordDeleteEvent",{data:oData,index:index});
3687 * Removes the Record at the given position index from the RecordSet. If a range
3688 * is also provided, removes that many Records, starting from the index. Length
3689 * of RecordSet is correspondingly shortened.
3691 * @method deleteRecords
3692 * @param index {Number} Record's RecordSet position index.
3693 * @param range {Number} (optional) How many Records to delete.
3694 * @return {Object[]} An array of copies of the data held by the deleted Records.
3696 deleteRecords : function(index, range) {
3697 if(!lang.isNumber(range)) {
3700 if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
3701 var recordsToDelete = this.getRecords(index, range);
3702 var deletedData = [], // this mistakenly held Records, not data
3703 deletedObjects = []; // this passes data only
3705 for(var i=0; i<recordsToDelete.length; i++) {
3706 deletedData[deletedData.length] = recordsToDelete[i]; // backward compatibility
3707 deletedObjects[deletedObjects.length] = recordsToDelete[i].getData();
3709 this._deleteRecord(index, range);
3711 this.fireEvent("recordsDeleteEvent",{data:deletedData,deletedData:deletedObjects,index:index});
3721 * Deletes all Records from the RecordSet.
3725 reset : function() {
3728 this.fireEvent("resetEvent");
3732 /////////////////////////////////////////////////////////////////////////////
3736 /////////////////////////////////////////////////////////////////////////////
3738 // RecordSet uses EventProvider
3739 lang.augmentProto(RS, util.EventProvider);
3742 * Fired when a new Record is added to the RecordSet.
3744 * @event recordAddEvent
3745 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3746 * @param oArgs.data {Object} Data added.
3750 * Fired when multiple Records are added to the RecordSet at once.
3752 * @event recordsAddEvent
3753 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
3754 * @param oArgs.data {Object[]} Data added.
3758 * Fired when a Record is set in the RecordSet.
3760 * @event recordSetEvent
3761 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3762 * @param oArgs.data {Object} Data added.
3766 * Fired when multiple Records are set in the RecordSet at once.
3768 * @event recordsSetEvent
3769 * @param oArgs.records {YAHOO.widget.Record[]} An array of Record instances.
3770 * @param oArgs.data {Object[]} Data added.
3774 * Fired when a Record is updated with new data.
3776 * @event recordUpdateEvent
3777 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3778 * @param oArgs.newData {Object} New data.
3779 * @param oArgs.oldData {Object} Old data.
3783 * Fired when a Record is deleted from the RecordSet.
3785 * @event recordDeleteEvent
3786 * @param oArgs.data {Object} The data held by the deleted Record,
3787 * or an array of data object literals if multiple Records were deleted at once.
3788 * @param oArgs.index {Object} Index of the deleted Record.
3792 * Fired when multiple Records are deleted from the RecordSet at once.
3794 * @event recordsDeleteEvent
3795 * @param oArgs.data {Object[]} An array of deleted Records.
3796 * @param oArgs.deletedData {Object[]} An array of deleted data.
3797 * @param oArgs.index {Object} Index of the first deleted Record.
3801 * Fired when all Records are deleted from the RecordSet at once.
3807 * @event keyUpdateEvent
3808 * @deprecated Use recordValueUpdateEvent
3812 * Fired when a Record value is updated with new data.
3814 * @event recordValueUpdateEvent
3815 * @param oArgs.record {YAHOO.widget.Record} The Record instance.
3816 * @param oArgs.key {String} The updated key.
3817 * @param oArgs.newData {Object} New data.
3818 * @param oArgs.oldData {Object} Old data.
3823 /****************************************************************************/
3824 /****************************************************************************/
3825 /****************************************************************************/
3828 * The Record class defines a DataTable record.
3830 * @namespace YAHOO.widget
3833 * @param oConfigs {Object} (optional) Object literal of key/value pairs.
3835 YAHOO.widget.Record = function(oLiteral) {
3836 this._nCount = widget.Record._nCount;
3837 this._sId = Dom.generateId(null, "yui-rec");//"yui-rec" + this._nCount;
3838 widget.Record._nCount++;
3840 if(lang.isObject(oLiteral)) {
3841 for(var sKey in oLiteral) {
3842 if(lang.hasOwnProperty(oLiteral, sKey)) {
3843 this._oData[sKey] = oLiteral[sKey];
3849 /////////////////////////////////////////////////////////////////////////////
3851 // Private member variables
3853 /////////////////////////////////////////////////////////////////////////////
3856 * Internal class variable to give unique IDs to Record instances.
3858 * @property Record._nCount
3862 YAHOO.widget.Record._nCount = 0;
3864 YAHOO.widget.Record.prototype = {
3866 * Immutable unique count assigned at instantiation. Remains constant while a
3867 * Record's position index can change from sorting.
3876 * Immutable unique ID assigned at instantiation. Remains constant while a
3877 * Record's position index can change from sorting.
3886 * Holds data for the Record in an object literal.
3894 /////////////////////////////////////////////////////////////////////////////
3896 // Public member variables
3898 /////////////////////////////////////////////////////////////////////////////
3900 /////////////////////////////////////////////////////////////////////////////
3904 /////////////////////////////////////////////////////////////////////////////
3907 * Returns unique count assigned at instantiation.
3912 getCount : function() {
3913 return this._nCount;
3917 * Returns unique ID assigned at instantiation.
3922 getId : function() {
3927 * Returns data for the Record for a field if given, or the entire object
3928 * literal otherwise.
3931 * @param sField {String} (Optional) The field from which to retrieve data value.
3934 getData : function(sField) {
3935 if(lang.isString(sField)) {
3936 return this._oData[sField];
3944 * Sets given data at the given key. Use the RecordSet method updateRecordValue to trigger
3948 * @param sKey {String} The key of the new value.
3949 * @param oData {MIXED} The new value.
3951 setData : function(sKey, oData) {
3952 this._oData[sKey] = oData;
3960 var lang = YAHOO.lang,
3962 widget = YAHOO.widget,
3967 DS = util.DataSourceBase;
3970 * The DataTable widget provides a progressively enhanced DHTML control for
3971 * displaying tabular data across A-grade browsers.
3974 * @requires yahoo, dom, event, element, datasource
3975 * @optional dragdrop, dragdrop
3976 * @title DataTable Widget
3979 /****************************************************************************/
3980 /****************************************************************************/
3981 /****************************************************************************/
3984 * DataTable class for the YUI DataTable widget.
3986 * @namespace YAHOO.widget
3988 * @extends YAHOO.util.Element
3990 * @param elContainer {HTMLElement} Container element for the TABLE.
3991 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
3992 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
3993 * @param oConfigs {object} (optional) Object literal of configuration values.
3995 YAHOO.widget.DataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
3996 var DT = widget.DataTable;
3998 ////////////////////////////////////////////////////////////////////////////
3999 // Backward compatibility for SDT, but prevent infinite loops
4001 if(oConfigs && oConfigs.scrollable) {
4002 return new YAHOO.widget.ScrollingDataTable(elContainer,aColumnDefs,oDataSource,oConfigs);
4005 ////////////////////////////////////////////////////////////////////////////
4009 this._nIndex = DT._nCount;
4010 this._sId = Dom.generateId(null, "yui-dt");// "yui-dt"+this._nIndex;
4011 this._oChainRender = new YAHOO.util.Chain();
4012 this._oChainRender.subscribe("end",this._onRenderChainEnd, this, true);
4014 // Initialize configs
4015 this._initConfigs(oConfigs);
4017 // Initialize DataSource
4018 this._initDataSource(oDataSource);
4019 if(!this._oDataSource) {
4023 // Initialize ColumnSet
4024 this._initColumnSet(aColumnDefs);
4025 if(!this._oColumnSet) {
4029 // Initialize RecordSet
4030 this._initRecordSet();
4031 if(!this._oRecordSet) {
4034 // Initialize Attributes
4035 DT.superclass.constructor.call(this, elContainer, this.configs);
4037 // Initialize DOM elements
4038 var okDom = this._initDomElements(elContainer);
4043 // Show message as soon as config is available
4044 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
4046 ////////////////////////////////////////////////////////////////////////////
4047 // Once per instance
4051 DT._nCurrentCount++;
4053 ////////////////////////////////////////////////////////////////////////////
4056 // Send a simple initial request
4058 success : this.onDataReturnSetRows,
4059 failure : this.onDataReturnSetRows,
4061 argument: this.getState()
4064 var initialLoad = this.get("initialLoad");
4065 if(initialLoad === true) {
4066 this._oDataSource.sendRequest(this.get("initialRequest"), oCallback);
4068 // Do not send an initial request at all
4069 else if(initialLoad === false) {
4070 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
4072 // Send an initial request with a custom payload
4074 var oCustom = initialLoad || {};
4075 oCallback.argument = oCustom.argument || {};
4076 this._oDataSource.sendRequest(oCustom.request, oCallback);
4080 var DT = widget.DataTable;
4082 /////////////////////////////////////////////////////////////////////////////
4086 /////////////////////////////////////////////////////////////////////////////
4088 lang.augmentObject(DT, {
4091 * Class name assigned to outer DataTable container.
4093 * @property DataTable.CLASS_DATATABLE
4099 CLASS_DATATABLE : "yui-dt",
4102 * Class name assigned to liner DIV elements.
4104 * @property DataTable.CLASS_LINER
4108 * @default "yui-dt-liner"
4110 CLASS_LINER : "yui-dt-liner",
4113 * Class name assigned to display label elements.
4115 * @property DataTable.CLASS_LABEL
4119 * @default "yui-dt-label"
4121 CLASS_LABEL : "yui-dt-label",
4124 * Class name assigned to messaging elements.
4126 * @property DataTable.CLASS_MESSAGE
4130 * @default "yui-dt-message"
4132 CLASS_MESSAGE : "yui-dt-message",
4135 * Class name assigned to mask element when DataTable is disabled.
4137 * @property DataTable.CLASS_MASK
4141 * @default "yui-dt-mask"
4143 CLASS_MASK : "yui-dt-mask",
4146 * Class name assigned to data elements.
4148 * @property DataTable.CLASS_DATA
4152 * @default "yui-dt-data"
4154 CLASS_DATA : "yui-dt-data",
4157 * Class name assigned to Column drag target.
4159 * @property DataTable.CLASS_COLTARGET
4163 * @default "yui-dt-coltarget"
4165 CLASS_COLTARGET : "yui-dt-coltarget",
4168 * Class name assigned to resizer handle elements.
4170 * @property DataTable.CLASS_RESIZER
4174 * @default "yui-dt-resizer"
4176 CLASS_RESIZER : "yui-dt-resizer",
4179 * Class name assigned to resizer liner elements.
4181 * @property DataTable.CLASS_RESIZERLINER
4185 * @default "yui-dt-resizerliner"
4187 CLASS_RESIZERLINER : "yui-dt-resizerliner",
4190 * Class name assigned to resizer proxy elements.
4192 * @property DataTable.CLASS_RESIZERPROXY
4196 * @default "yui-dt-resizerproxy"
4198 CLASS_RESIZERPROXY : "yui-dt-resizerproxy",
4201 * Class name assigned to CellEditor container elements.
4203 * @property DataTable.CLASS_EDITOR
4207 * @default "yui-dt-editor"
4209 CLASS_EDITOR : "yui-dt-editor",
4212 * Class name assigned to CellEditor container shim.
4214 * @property DataTable.CLASS_EDITOR_SHIM
4218 * @default "yui-dt-editor-shim"
4220 CLASS_EDITOR_SHIM : "yui-dt-editor-shim",
4223 * Class name assigned to paginator container elements.
4225 * @property DataTable.CLASS_PAGINATOR
4229 * @default "yui-dt-paginator"
4231 CLASS_PAGINATOR : "yui-dt-paginator",
4234 * Class name assigned to page number indicators.
4236 * @property DataTable.CLASS_PAGE
4240 * @default "yui-dt-page"
4242 CLASS_PAGE : "yui-dt-page",
4245 * Class name assigned to default indicators.
4247 * @property DataTable.CLASS_DEFAULT
4251 * @default "yui-dt-default"
4253 CLASS_DEFAULT : "yui-dt-default",
4256 * Class name assigned to previous indicators.
4258 * @property DataTable.CLASS_PREVIOUS
4262 * @default "yui-dt-previous"
4264 CLASS_PREVIOUS : "yui-dt-previous",
4267 * Class name assigned next indicators.
4269 * @property DataTable.CLASS_NEXT
4273 * @default "yui-dt-next"
4275 CLASS_NEXT : "yui-dt-next",
4278 * Class name assigned to first elements.
4280 * @property DataTable.CLASS_FIRST
4284 * @default "yui-dt-first"
4286 CLASS_FIRST : "yui-dt-first",
4289 * Class name assigned to last elements.
4291 * @property DataTable.CLASS_LAST
4295 * @default "yui-dt-last"
4297 CLASS_LAST : "yui-dt-last",
4300 * Class name assigned to Record elements.
4302 * @property DataTable.CLASS_REC
4306 * @default "yui-dt-rec"
4308 CLASS_REC : "yui-dt-rec",
4311 * Class name assigned to even elements.
4313 * @property DataTable.CLASS_EVEN
4317 * @default "yui-dt-even"
4319 CLASS_EVEN : "yui-dt-even",
4322 * Class name assigned to odd elements.
4324 * @property DataTable.CLASS_ODD
4328 * @default "yui-dt-odd"
4330 CLASS_ODD : "yui-dt-odd",
4333 * Class name assigned to selected elements.
4335 * @property DataTable.CLASS_SELECTED
4339 * @default "yui-dt-selected"
4341 CLASS_SELECTED : "yui-dt-selected",
4344 * Class name assigned to highlighted elements.
4346 * @property DataTable.CLASS_HIGHLIGHTED
4350 * @default "yui-dt-highlighted"
4352 CLASS_HIGHLIGHTED : "yui-dt-highlighted",
4355 * Class name assigned to hidden elements.
4357 * @property DataTable.CLASS_HIDDEN
4361 * @default "yui-dt-hidden"
4363 CLASS_HIDDEN : "yui-dt-hidden",
4366 * Class name assigned to disabled elements.
4368 * @property DataTable.CLASS_DISABLED
4372 * @default "yui-dt-disabled"
4374 CLASS_DISABLED : "yui-dt-disabled",
4377 * Class name assigned to empty indicators.
4379 * @property DataTable.CLASS_EMPTY
4383 * @default "yui-dt-empty"
4385 CLASS_EMPTY : "yui-dt-empty",
4388 * Class name assigned to loading indicatorx.
4390 * @property DataTable.CLASS_LOADING
4394 * @default "yui-dt-loading"
4396 CLASS_LOADING : "yui-dt-loading",
4399 * Class name assigned to error indicators.
4401 * @property DataTable.CLASS_ERROR
4405 * @default "yui-dt-error"
4407 CLASS_ERROR : "yui-dt-error",
4410 * Class name assigned to editable elements.
4412 * @property DataTable.CLASS_EDITABLE
4416 * @default "yui-dt-editable"
4418 CLASS_EDITABLE : "yui-dt-editable",
4421 * Class name assigned to draggable elements.
4423 * @property DataTable.CLASS_DRAGGABLE
4427 * @default "yui-dt-draggable"
4429 CLASS_DRAGGABLE : "yui-dt-draggable",
4432 * Class name assigned to resizeable elements.
4434 * @property DataTable.CLASS_RESIZEABLE
4438 * @default "yui-dt-resizeable"
4440 CLASS_RESIZEABLE : "yui-dt-resizeable",
4443 * Class name assigned to scrollable elements.
4445 * @property DataTable.CLASS_SCROLLABLE
4449 * @default "yui-dt-scrollable"
4451 CLASS_SCROLLABLE : "yui-dt-scrollable",
4454 * Class name assigned to sortable elements.
4456 * @property DataTable.CLASS_SORTABLE
4460 * @default "yui-dt-sortable"
4462 CLASS_SORTABLE : "yui-dt-sortable",
4465 * Class name assigned to ascending elements.
4467 * @property DataTable.CLASS_ASC
4471 * @default "yui-dt-asc"
4473 CLASS_ASC : "yui-dt-asc",
4476 * Class name assigned to descending elements.
4478 * @property DataTable.CLASS_DESC
4482 * @default "yui-dt-desc"
4484 CLASS_DESC : "yui-dt-desc",
4487 * Class name assigned to BUTTON elements and/or container elements.
4489 * @property DataTable.CLASS_BUTTON
4493 * @default "yui-dt-button"
4495 CLASS_BUTTON : "yui-dt-button",
4498 * Class name assigned to INPUT TYPE=CHECKBOX elements and/or container elements.
4500 * @property DataTable.CLASS_CHECKBOX
4504 * @default "yui-dt-checkbox"
4506 CLASS_CHECKBOX : "yui-dt-checkbox",
4509 * Class name assigned to SELECT elements and/or container elements.
4511 * @property DataTable.CLASS_DROPDOWN
4515 * @default "yui-dt-dropdown"
4517 CLASS_DROPDOWN : "yui-dt-dropdown",
4520 * Class name assigned to INPUT TYPE=RADIO elements and/or container elements.
4522 * @property DataTable.CLASS_RADIO
4526 * @default "yui-dt-radio"
4528 CLASS_RADIO : "yui-dt-radio",
4530 /////////////////////////////////////////////////////////////////////////
4532 // Private static properties
4534 /////////////////////////////////////////////////////////////////////////
4537 * Internal class variable for indexing multiple DataTable instances.
4539 * @property DataTable._nCount
4547 * Internal class variable tracking current number of DataTable instances,
4548 * so that certain class values can be reset when all instances are destroyed.
4550 * @property DataTable._nCurrentCount
4558 * Reference to the STYLE node that is dynamically created and updated
4559 * in order to manage Column widths.
4561 * @property DataTable._elDynStyleNode
4566 _elDynStyleNode : null,
4569 * Set to true if _elDynStyleNode cannot be populated due to browser incompatibility.
4571 * @property DataTable._bDynStylesFallback
4576 _bDynStylesFallback : (ua.ie) ? true : false,
4579 * Object literal hash of Columns and their dynamically create style rules.
4581 * @property DataTable._oDynStyles
4588 /////////////////////////////////////////////////////////////////////////
4590 // Private static methods
4592 /////////////////////////////////////////////////////////////////////////
4595 * Clones object literal or array of object literals.
4597 * @method DataTable._cloneObject
4598 * @param o {Object} Object.
4602 _cloneObject: function(o) {
4603 if(!lang.isValue(o)) {
4609 if(o instanceof YAHOO.widget.BaseCellEditor) {
4612 else if(Object.prototype.toString.apply(o) === "[object RegExp]") {
4615 else if(lang.isFunction(o)) {
4618 else if(lang.isArray(o)) {
4620 for(var i=0,len=o.length;i<len;i++) {
4621 array[i] = DT._cloneObject(o[i]);
4625 else if(lang.isObject(o)) {
4627 if(lang.hasOwnProperty(o, x)) {
4628 if(lang.isValue(o[x]) && lang.isObject(o[x]) || lang.isArray(o[x])) {
4629 copy[x] = DT._cloneObject(o[x]);
4645 * Formats a BUTTON element.
4647 * @method DataTable.formatButton
4648 * @param el {HTMLElement} The element to format with markup.
4649 * @param oRecord {YAHOO.widget.Record} Record instance.
4650 * @param oColumn {YAHOO.widget.Column} Column instance.
4651 * @param oData {HTML} Data value for the cell. By default, the value
4652 * is what gets written to the BUTTON. String values are treated as markup
4653 * and inserted into the DOM with innerHTML.
4654 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4657 formatButton : function(el, oRecord, oColumn, oData, oDataTable) {
4658 var sValue = lang.isValue(oData) ? oData : "Click";
4659 //TODO: support YAHOO.widget.Button
4660 //if(YAHOO.widget.Button) {
4664 el.innerHTML = "<button type=\"button\" class=\""+
4665 DT.CLASS_BUTTON + "\">" + sValue + "</button>";
4670 * Formats a CHECKBOX element.
4672 * @method DataTable.formatCheckbox
4673 * @param el {HTMLElement} The element to format with markup.
4674 * @param oRecord {YAHOO.widget.Record} Record instance.
4675 * @param oColumn {YAHOO.widget.Column} Column instance.
4676 * @param oData {Object | Boolean | HTML} Data value for the cell. Can be a simple
4677 * Boolean to indicate whether checkbox is checked or not. Can be object literal
4678 * {checked:bBoolean, label:sLabel}. String values are treated as markup
4679 * and inserted into the DOM with innerHTML.
4680 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4683 formatCheckbox : function(el, oRecord, oColumn, oData, oDataTable) {
4684 var bChecked = oData;
4685 bChecked = (bChecked) ? " checked=\"checked\"" : "";
4686 el.innerHTML = "<input type=\"checkbox\"" + bChecked +
4687 " class=\"" + DT.CLASS_CHECKBOX + "\" />";
4691 * Formats currency. Default unit is USD.
4693 * @method DataTable.formatCurrency
4694 * @param el {HTMLElement} The element to format with markup.
4695 * @param oRecord {YAHOO.widget.Record} Record instance.
4696 * @param oColumn {YAHOO.widget.Column} Column instance.
4697 * @param oData {Number} Data value for the cell.
4698 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4701 formatCurrency : function(el, oRecord, oColumn, oData, oDataTable) {
4702 var oDT = oDataTable || this;
4703 el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || oDT.get("currencyOptions"));
4707 * Formats JavaScript Dates.
4709 * @method DataTable.formatDate
4710 * @param el {HTMLElement} The element to format with markup.
4711 * @param oRecord {YAHOO.widget.Record} Record instance.
4712 * @param oColumn {YAHOO.widget.Column} Column instance.
4713 * @param oData {Object} Data value for the cell, or null. String values are
4714 * treated as markup and inserted into the DOM with innerHTML.
4715 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4718 formatDate : function(el, oRecord, oColumn, oData, oDataTable) {
4719 var oDT = oDataTable || this,
4720 oConfig = oColumn.dateOptions || oDT.get("dateOptions");
4721 el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
4725 * Formats SELECT elements.
4727 * @method DataTable.formatDropdown
4728 * @param el {HTMLElement} The element to format with markup.
4729 * @param oRecord {YAHOO.widget.Record} Record instance.
4730 * @param oColumn {YAHOO.widget.Column} Column instance.
4731 * @param oData {Object} Data value for the cell, or null. String values may
4732 * be treated as markup and inserted into the DOM with innerHTML as element
4734 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4737 formatDropdown : function(el, oRecord, oColumn, oData, oDataTable) {
4738 var oDT = oDataTable || this,
4739 selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
4740 options = (lang.isArray(oColumn.dropdownOptions)) ?
4741 oColumn.dropdownOptions : null,
4744 collection = el.getElementsByTagName("select");
4746 // Create the form element only once, so we can attach the onChange listener
4747 if(collection.length === 0) {
4748 // Create SELECT element
4749 selectEl = document.createElement("select");
4750 selectEl.className = DT.CLASS_DROPDOWN;
4751 selectEl = el.appendChild(selectEl);
4753 // Add event listener
4754 Ev.addListener(selectEl,"change",oDT._onDropdownChange,oDT);
4757 selectEl = collection[0];
4759 // Update the form element
4761 // Clear out previous options
4762 selectEl.innerHTML = "";
4764 // We have options to populate
4766 // Create OPTION elements
4767 for(var i=0; i<options.length; i++) {
4768 var option = options[i];
4769 var optionEl = document.createElement("option");
4770 optionEl.value = (lang.isValue(option.value)) ?
4771 option.value : option;
4772 // Bug 2334323: Support legacy text, support label for consistency with DropdownCellEditor
4773 optionEl.innerHTML = (lang.isValue(option.text)) ?
4774 option.text : (lang.isValue(option.label)) ? option.label : option;
4775 optionEl = selectEl.appendChild(optionEl);
4776 if (optionEl.value == selectedValue) {
4777 optionEl.selected = true;
4781 // Selected value is our only option
4783 selectEl.innerHTML = "<option selected value=\"" + selectedValue + "\">" + selectedValue + "</option>";
4787 el.innerHTML = lang.isValue(oData) ? oData : "";
4794 * @method DataTable.formatEmail
4795 * @param el {HTMLElement} The element to format with markup.
4796 * @param oRecord {YAHOO.widget.Record} Record instance.
4797 * @param oColumn {YAHOO.widget.Column} Column instance.
4798 * @param oData {String} Data value for the cell, or null. Values are
4800 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4803 formatEmail : function(el, oRecord, oColumn, oData, oDataTable) {
4804 if(lang.isString(oData)) {
4805 oData = lang.escapeHTML(oData);
4806 el.innerHTML = "<a href=\"mailto:" + oData + "\">" + oData + "</a>";
4809 el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : "";
4816 * @method DataTable.formatLink
4817 * @param el {HTMLElement} The element to format with markup.
4818 * @param oRecord {YAHOO.widget.Record} Record instance.
4819 * @param oColumn {YAHOO.widget.Column} Column instance.
4820 * @param oData {String} Data value for the cell, or null. Values are
4822 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4825 formatLink : function(el, oRecord, oColumn, oData, oDataTable) {
4826 if(lang.isString(oData)) {
4827 oData = lang.escapeHTML(oData);
4828 el.innerHTML = "<a href=\"" + oData + "\">" + oData + "</a>";
4831 el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : "";
4838 * @method DataTable.formatNumber
4839 * @param el {HTMLElement} The element to format with markup.
4840 * @param oRecord {YAHOO.widget.Record} Record instance.
4841 * @param oColumn {YAHOO.widget.Column} Column instance.
4842 * @param oData {Object} Data value for the cell, or null.
4843 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4846 formatNumber : function(el, oRecord, oColumn, oData, oDataTable) {
4847 var oDT = oDataTable || this;
4848 el.innerHTML = util.Number.format(oData, oColumn.numberOptions || oDT.get("numberOptions"));
4852 * Formats INPUT TYPE=RADIO elements.
4854 * @method DataTable.formatRadio
4855 * @param el {HTMLElement} The element to format with markup.
4856 * @param oRecord {YAHOO.widget.Record} Record instance.
4857 * @param oColumn {YAHOO.widget.Column} Column instance.
4858 * @param oData {Object} (Optional) Data value for the cell.
4859 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4862 formatRadio : function(el, oRecord, oColumn, oData, oDataTable) {
4863 var oDT = oDataTable || this,
4865 bChecked = (bChecked) ? " checked=\"checked\"" : "";
4866 el.innerHTML = "<input type=\"radio\"" + bChecked +
4867 " name=\""+oDT.getId()+"-col-" + oColumn.getSanitizedKey() + "\"" +
4868 " class=\"" + DT.CLASS_RADIO+ "\" />";
4872 * Formats text strings.
4874 * @method DataTable.formatText
4875 * @param el {HTMLElement} The element to format with markup.
4876 * @param oRecord {YAHOO.widget.Record} Record instance.
4877 * @param oColumn {YAHOO.widget.Column} Column instance.
4878 * @param oData {String} (Optional) Data value for the cell. Values are
4880 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4883 formatText : function(el, oRecord, oColumn, oData, oDataTable) {
4884 var value = (lang.isValue(oData)) ? oData : "";
4885 el.innerHTML = lang.escapeHTML(value.toString());
4889 * Formats TEXTAREA elements.
4891 * @method DataTable.formatTextarea
4892 * @param el {HTMLElement} The element to format with markup.
4893 * @param oRecord {YAHOO.widget.Record} Record instance.
4894 * @param oColumn {YAHOO.widget.Column} Column instance.
4895 * @param oData {Object} (Optional) Data value for the cell. Values are
4897 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4900 formatTextarea : function(el, oRecord, oColumn, oData, oDataTable) {
4901 var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "",
4902 markup = "<textarea>" + value + "</textarea>";
4903 el.innerHTML = markup;
4907 * Formats INPUT TYPE=TEXT elements.
4909 * @method DataTable.formatTextbox
4910 * @param el {HTMLElement} The element to format with markup.
4911 * @param oRecord {YAHOO.widget.Record} Record instance.
4912 * @param oColumn {YAHOO.widget.Column} Column instance.
4913 * @param oData {Object} (Optional) Data value for the cell. Values are
4915 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4918 formatTextbox : function(el, oRecord, oColumn, oData, oDataTable) {
4919 var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "",
4920 markup = "<input type=\"text\" value=\"" + value + "\" />";
4921 el.innerHTML = markup;
4925 * Default cell formatter
4927 * @method DataTable.formatDefault
4928 * @param el {HTMLElement} The element to format with markup.
4929 * @param oRecord {YAHOO.widget.Record} Record instance.
4930 * @param oColumn {YAHOO.widget.Column} Column instance.
4931 * @param oData {HTML} (Optional) Data value for the cell. String values are
4932 * treated as markup and inserted into the DOM with innerHTML.
4933 * @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
4936 formatDefault : function(el, oRecord, oColumn, oData, oDataTable) {
4937 el.innerHTML = (lang.isValue(oData) && oData !== "") ? oData.toString() : " ";
4941 * Validates data value to type Number, doing type conversion as
4942 * necessary. A valid Number value is return, else null is returned
4943 * if input value does not validate.
4946 * @method DataTable.validateNumber
4947 * @param oData {Object} Data to validate.
4950 validateNumber : function(oData) {
4952 var number = oData * 1;
4955 if(lang.isNumber(number)) {
4964 // Done in separate step so referenced functions are defined.
4966 * Registry of cell formatting functions, enables shortcut pointers in Column
4967 * definition formatter value (i.e., {key:"myColumn", formatter:"date"}).
4968 * @property DataTable.Formatter
4973 button : DT.formatButton,
4974 checkbox : DT.formatCheckbox,
4975 currency : DT.formatCurrency,
4976 "date" : DT.formatDate,
4977 dropdown : DT.formatDropdown,
4978 email : DT.formatEmail,
4979 link : DT.formatLink,
4980 "number" : DT.formatNumber,
4981 radio : DT.formatRadio,
4982 text : DT.formatText,
4983 textarea : DT.formatTextarea,
4984 textbox : DT.formatTextbox,
4986 defaultFormatter : DT.formatDefault
4989 lang.extend(DT, util.Element, {
4991 /////////////////////////////////////////////////////////////////////////////
4993 // Superclass methods
4995 /////////////////////////////////////////////////////////////////////////////
4998 * Implementation of Element's abstract method. Sets up config values.
5000 * @method initAttributes
5001 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
5005 initAttributes : function(oConfigs) {
5006 oConfigs = oConfigs || {};
5007 DT.superclass.initAttributes.call(this, oConfigs);
5010 * @attribute summary
5011 * @description String value for the SUMMARY attribute.
5015 this.setAttributeConfig("summary", {
5017 validator: lang.isString,
5018 method: function(sSummary) {
5020 this._elTable.summary = sSummary;
5026 * @attribute selectionMode
5027 * @description Specifies row or cell selection mode. Accepts the following strings:
5029 * <dt>"standard"</dt>
5030 * <dd>Standard row selection with support for modifier keys to enable
5031 * multiple selections.</dd>
5034 * <dd>Row selection with modifier keys disabled to not allow
5035 * multiple selections.</dd>
5037 * <dt>"singlecell"</dt>
5038 * <dd>Cell selection with modifier keys disabled to not allow
5039 * multiple selections.</dd>
5041 * <dt>"cellblock"</dt>
5042 * <dd>Cell selection with support for modifier keys to enable multiple
5043 * selections in a block-fashion, like a spreadsheet.</dd>
5045 * <dt>"cellrange"</dt>
5046 * <dd>Cell selection with support for modifier keys to enable multiple
5047 * selections in a range-fashion, like a calendar.</dd>
5050 * @default "standard"
5053 this.setAttributeConfig("selectionMode", {
5055 validator: lang.isString
5059 * @attribute sortedBy
5060 * @description Object literal provides metadata for initial sort values if
5061 * data will arrive pre-sorted:
5063 * <dt>sortedBy.key</dt>
5064 * <dd>{String} Key of sorted Column</dd>
5065 * <dt>sortedBy.dir</dt>
5066 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
5068 * @type Object | null
5070 this.setAttributeConfig("sortedBy", {
5072 // TODO: accepted array for nested sorts
5073 validator: function(oNewSortedBy) {
5075 return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
5078 return (oNewSortedBy === null);
5081 method: function(oNewSortedBy) {
5082 // Stash the previous value
5083 var oOldSortedBy = this.get("sortedBy");
5085 // Workaround for bug 1827195
5086 this._configs.sortedBy.value = oNewSortedBy;
5088 // Remove ASC/DESC from TH
5095 if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
5096 oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
5097 nOldColumnKeyIndex = oOldColumn.getKeyIndex();
5099 // Remove previous UI from THEAD
5100 var elOldTh = oOldColumn.getThEl();
5101 Dom.removeClass(elOldTh, oOldSortedBy.dir);
5102 this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
5105 oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
5106 nNewColumnKeyIndex = oNewColumn.getKeyIndex();
5108 // Update THEAD with new UI
5109 var elNewTh = oNewColumn.getThEl();
5110 // Backward compatibility
5111 if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") || (oNewSortedBy.dir == "desc"))) {
5112 var newClass = (oNewSortedBy.dir == "desc") ?
5115 Dom.addClass(elNewTh, newClass);
5118 var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
5119 Dom.addClass(elNewTh, sortClass);
5121 this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
5127 this._elTbody.style.display = "none";
5128 var allRows = this._elTbody.rows,
5130 for(var i=allRows.length-1; i>-1; i--) {
5131 allCells = allRows[i].childNodes;
5132 if(allCells[nOldColumnKeyIndex]) {
5133 Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
5135 if(allCells[nNewColumnKeyIndex]) {
5136 Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
5139 this._elTbody.style.display = "";
5142 this._clearTrTemplateEl();
5147 * @attribute paginator
5148 * @description An instance of YAHOO.widget.Paginator.
5150 * @type {Object|YAHOO.widget.Paginator}
5152 this.setAttributeConfig("paginator", {
5154 validator : function (val) {
5155 return val === null || val instanceof widget.Paginator;
5157 method : function () { this._updatePaginator.apply(this,arguments); }
5161 * @attribute caption
5162 * @description Value for the CAPTION element. String values are treated as
5163 * markup and inserted into the DOM with innerHTML. NB: Not supported in
5164 * ScrollingDataTable.
5167 this.setAttributeConfig("caption", {
5169 validator: lang.isString,
5170 method: function(sCaption) {
5171 this._initCaptionEl(sCaption);
5176 * @attribute draggableColumns
5177 * @description True if Columns are draggable to reorder, false otherwise.
5178 * The Drag & Drop Utility is required to enable this feature. Only top-level
5179 * and non-nested Columns are draggable. Write once.
5183 this.setAttributeConfig("draggableColumns", {
5185 validator: lang.isBoolean,
5186 method: function(oParam) {
5189 this._initDraggableColumns();
5192 this._destroyDraggableColumns();
5199 * @attribute renderLoopSize
5200 * @description A value greater than 0 enables DOM rendering of rows to be
5201 * executed from a non-blocking timeout queue and sets how many rows to be
5202 * rendered per timeout. Recommended for very large data sets.
5206 this.setAttributeConfig("renderLoopSize", {
5208 validator: lang.isNumber
5212 * @attribute sortFunction
5213 * @description Default Column sort function, receives the following args:
5215 * <dt>a {Object}</dt>
5216 * <dd>First sort argument.</dd>
5217 * <dt>b {Object}</dt>
5218 * <dd>Second sort argument.</dd>
5220 * <dt>desc {Boolean}</dt>
5221 * <dd>True if sort direction is descending, false if
5222 * sort direction is ascending.</dd>
5223 * <dt>field {String}</dt>
5224 * <dd>The field to sort by, from sortOptions.field</dd>
5228 this.setAttributeConfig("sortFunction", {
5229 value: function(a, b, desc, field) {
5230 var compare = YAHOO.util.Sort.compare,
5231 sorted = compare(a.getData(field),b.getData(field), desc);
5233 return compare(a.getCount(),b.getCount(), desc); // Bug 1932978
5242 * @attribute formatRow
5243 * @description A function that accepts a TR element and its associated Record
5244 * for custom formatting. The function must return TRUE in order to automatically
5245 * continue formatting of child TD elements, else TD elements will not be
5246 * automatically formatted.
5250 this.setAttributeConfig("formatRow", {
5252 validator: lang.isFunction
5256 * @attribute generateRequest
5257 * @description A function that converts an object literal of desired DataTable
5258 * states into a request value which is then passed to the DataSource's
5259 * sendRequest method in order to retrieve data for those states. This
5260 * function is passed an object literal of state data and a reference to the
5261 * DataTable instance:
5264 * <dt>pagination<dt>
5266 * <dt>offsetRecord</dt>
5267 * <dd>{Number} Index of the first Record of the desired page</dd>
5268 * <dt>rowsPerPage</dt>
5269 * <dd>{Number} Number of rows per page</dd>
5274 * <dd>{String} Key of sorted Column</dd>
5276 * <dd>{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
5279 * <dd>The DataTable instance</dd>
5282 * and by default returns a String of syntax:
5283 * "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
5285 * @default HTMLFunction
5287 this.setAttributeConfig("generateRequest", {
5288 value: function(oState, oSelf) {
5290 oState = oState || {pagination:null, sortedBy:null};
5291 var sort = encodeURIComponent((oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey());
5292 var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
5293 var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
5294 var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
5296 // Build the request
5297 return "sort=" + sort +
5299 "&startIndex=" + startIndex +
5300 ((results !== null) ? "&results=" + results : "");
5302 validator: lang.isFunction
5306 * @attribute initialRequest
5307 * @description Defines the initial request that gets sent to the DataSource
5308 * during initialization. Value is ignored if initialLoad is set to any value
5313 this.setAttributeConfig("initialRequest", {
5318 * @attribute initialLoad
5319 * @description Determines whether or not to load data at instantiation. By
5320 * default, will trigger a sendRequest() to the DataSource and pass in the
5321 * request defined by initialRequest. If set to false, data will not load
5322 * at instantiation. Alternatively, implementers who wish to work with a
5323 * custom payload may pass in an object literal with the following values:
5326 * <dt>request (MIXED)</dt>
5327 * <dd>Request value.</dd>
5329 * <dt>argument (MIXED)</dt>
5330 * <dd>Custom data that will be passed through to the callback function.</dd>
5334 * @type Boolean | Object
5337 this.setAttributeConfig("initialLoad", {
5342 * @attribute dynamicData
5343 * @description If true, sorting and pagination are relegated to the DataSource
5344 * for handling, using the request returned by the "generateRequest" function.
5345 * Each new DataSource response blows away all previous Records. False by default, so
5346 * sorting and pagination will be handled directly on the client side, without
5347 * causing any new requests for data from the DataSource.
5351 this.setAttributeConfig("dynamicData", {
5353 validator: lang.isBoolean
5357 * @attribute MSG_EMPTY
5358 * @description Message to display if DataTable has no data. String
5359 * values are treated as markup and inserted into the DOM with innerHTML.
5361 * @default "No records found."
5363 this.setAttributeConfig("MSG_EMPTY", {
5364 value: "No records found.",
5365 validator: lang.isString
5369 * @attribute MSG_LOADING
5370 * @description Message to display while DataTable is loading data. String
5371 * values are treated as markup and inserted into the DOM with innerHTML.
5373 * @default "Loading..."
5375 this.setAttributeConfig("MSG_LOADING", {
5376 value: "Loading...",
5377 validator: lang.isString
5381 * @attribute MSG_ERROR
5382 * @description Message to display while DataTable has data error. String
5383 * values are treated as markup and inserted into the DOM with innerHTML.
5385 * @default "Data error."
5387 this.setAttributeConfig("MSG_ERROR", {
5388 value: "Data error.",
5389 validator: lang.isString
5393 * @attribute MSG_SORTASC
5394 * @description Message to display in tooltip to sort Column in ascending
5395 * order. String values are treated as markup and inserted into the DOM as
5398 * @default "Click to sort ascending"
5400 this.setAttributeConfig("MSG_SORTASC", {
5401 value: "Click to sort ascending",
5402 validator: lang.isString,
5403 method: function(sParam) {
5405 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
5406 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_ASC) {
5407 allKeys[i]._elThLabel.firstChild.title = sParam;
5415 * @attribute MSG_SORTDESC
5416 * @description Message to display in tooltip to sort Column in descending
5417 * order. String values are treated as markup and inserted into the DOM as
5420 * @default "Click to sort descending"
5422 this.setAttributeConfig("MSG_SORTDESC", {
5423 value: "Click to sort descending",
5424 validator: lang.isString,
5425 method: function(sParam) {
5427 for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i<len; i++) {
5428 if(allKeys[i].sortable && this.getColumnSortDir(allKeys[i]) === DT.CLASS_DESC) {
5429 allKeys[i]._elThLabel.firstChild.title = sParam;
5437 * @attribute currencySymbol
5438 * @deprecated Use currencyOptions.
5440 this.setAttributeConfig("currencySymbol", {
5442 validator: lang.isString
5446 * Default config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
5447 * @attribute currencyOptions
5449 * @default {prefix: $, decimalPlaces:2, decimalSeparator:".", thousandsSeparator:","}
5451 this.setAttributeConfig("currencyOptions", {
5453 prefix: this.get("currencySymbol"), // TODO: deprecate currencySymbol
5455 decimalSeparator:".",
5456 thousandsSeparator:","
5461 * Default config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
5462 * @attribute dateOptions
5464 * @default {format:"%m/%d/%Y", locale:"en"}
5466 this.setAttributeConfig("dateOptions", {
5467 value: {format:"%m/%d/%Y", locale:"en"}
5471 * Default config passed to YAHOO.util.Number.format() by the 'number' Column formatter.
5472 * @attribute numberOptions
5474 * @default {decimalPlaces:0, thousandsSeparator:","}
5476 this.setAttributeConfig("numberOptions", {
5479 thousandsSeparator:","
5485 /////////////////////////////////////////////////////////////////////////////
5487 // Private member variables
5489 /////////////////////////////////////////////////////////////////////////////
5492 * True if instance is initialized, so as to fire the initEvent after render.
5502 * Index assigned to instance.
5511 * Counter for IDs assigned to TR elements.
5513 * @property _nTrCount
5520 * Counter for IDs assigned to TD elements.
5522 * @property _nTdCount
5529 * Unique id assigned to instance "yui-dtN", useful prefix for generating unique
5530 * DOM ID strings and log messages.
5541 * @property _oChainRender
5542 * @type YAHOO.util.Chain
5545 _oChainRender : null,
5548 * DOM reference to the container element for the DataTable instance into which
5549 * all other elements get created.
5551 * @property _elContainer
5555 _elContainer : null,
5558 * DOM reference to the mask element for the DataTable instance which disables it.
5567 * DOM reference to the TABLE element for the DataTable instance.
5569 * @property _elTable
5576 * DOM reference to the CAPTION element for the DataTable instance.
5578 * @property _elCaption
5585 * DOM reference to the COLGROUP element for the DataTable instance.
5587 * @property _elColgroup
5594 * DOM reference to the THEAD element for the DataTable instance.
5596 * @property _elThead
5603 * DOM reference to the primary TBODY element for the DataTable instance.
5605 * @property _elTbody
5612 * DOM reference to the secondary TBODY element used to display DataTable messages.
5614 * @property _elMsgTbody
5621 * DOM reference to the secondary TBODY element's single TR element used to display DataTable messages.
5623 * @property _elMsgTr
5630 * DOM reference to the secondary TBODY element's single TD element used to display DataTable messages.
5632 * @property _elMsgTd
5639 * Element reference to shared Column drag target.
5641 * @property _elColumnDragTarget
5645 _elColumnDragTarget : null,
5648 * Element reference to shared Column resizer proxy.
5650 * @property _elColumnResizerProxy
5654 _elColumnResizerProxy : null,
5657 * DataSource instance for the DataTable instance.
5659 * @property _oDataSource
5660 * @type YAHOO.util.DataSource
5663 _oDataSource : null,
5666 * ColumnSet instance for the DataTable instance.
5668 * @property _oColumnSet
5669 * @type YAHOO.widget.ColumnSet
5675 * RecordSet instance for the DataTable instance.
5677 * @property _oRecordSet
5678 * @type YAHOO.widget.RecordSet
5684 * The active CellEditor instance for the DataTable instance.
5686 * @property _oCellEditor
5687 * @type YAHOO.widget.CellEditor
5690 _oCellEditor : null,
5693 * ID string of first TR element of the current DataTable page.
5695 * @property _sFirstTrId
5702 * ID string of the last TR element of the current DataTable page.
5704 * @property _sLastTrId
5711 * Template row to create all new rows from.
5712 * @property _elTrTemplate
5713 * @type {HTMLElement}
5716 _elTrTemplate : null,
5719 * Sparse array of custom functions to set column widths for browsers that don't
5720 * support dynamic CSS rules. Functions are added at the index representing
5721 * the number of rows they update.
5723 * @property _aDynFunctions
5727 _aDynFunctions : [],
5732 * @property _disabled
5765 /////////////////////////////////////////////////////////////////////////////
5769 /////////////////////////////////////////////////////////////////////////////
5772 * Clears browser text selection. Useful to call on rowSelectEvent or
5773 * cellSelectEvent to prevent clicks or dblclicks from selecting text in the
5776 * @method clearTextSelection
5778 clearTextSelection : function() {
5780 if(window.getSelection) {
5781 sel = window.getSelection();
5783 else if(document.getSelection) {
5784 sel = document.getSelection();
5786 else if(document.selection) {
5787 sel = document.selection;
5793 else if (sel.removeAllRanges) {
5794 sel.removeAllRanges();
5796 else if(sel.collapse) {
5803 * Sets focus on the given element.
5806 * @param el {HTMLElement} Element.
5809 _focusEl : function(el) {
5810 el = el || this._elTbody;
5811 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
5812 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
5813 // strange unexpected things as the user clicks on buttons and other controls.
5814 setTimeout(function() {
5824 * Forces Gecko repaint.
5826 * @method _repaintGecko
5827 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
5830 _repaintGecko : (ua.gecko) ?
5832 el = el || this._elContainer;
5833 var parent = el.parentNode;
5834 var nextSibling = el.nextSibling;
5835 parent.insertBefore(parent.removeChild(el), nextSibling);
5839 * Forces Opera repaint.
5841 * @method _repaintOpera
5844 _repaintOpera : (ua.opera) ?
5847 document.documentElement.className += " ";
5848 document.documentElement.className = YAHOO.lang.trim(document.documentElement.className);
5853 * Forces Webkit repaint.
5855 * @method _repaintWebkit
5856 * @el {HTMLElement} (Optional) Element to repaint, otherwise entire document body.
5859 _repaintWebkit : (ua.webkit) ?
5861 el = el || this._elContainer;
5862 var parent = el.parentNode;
5863 var nextSibling = el.nextSibling;
5864 parent.insertBefore(parent.removeChild(el), nextSibling);
5891 * Initializes object literal of config values.
5893 * @method _initConfigs
5894 * @param oConfig {Object} Object literal of config values.
5897 _initConfigs : function(oConfigs) {
5898 if(!oConfigs || !lang.isObject(oConfigs)) {
5901 this.configs = oConfigs;
5905 * Initializes ColumnSet.
5907 * @method _initColumnSet
5908 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
5911 _initColumnSet : function(aColumnDefs) {
5912 var oColumn, i, len;
5914 if(this._oColumnSet) {
5915 // First clear _oDynStyles for existing ColumnSet and
5916 // uregister CellEditor Custom Events
5917 for(i=0, len=this._oColumnSet.keys.length; i<len; i++) {
5918 oColumn = this._oColumnSet.keys[i];
5919 DT._oDynStyles["."+this.getId()+"-col-"+oColumn.getSanitizedKey()+" ."+DT.CLASS_LINER] = undefined;
5920 if(oColumn.editor && oColumn.editor.unsubscribeAll) { // Backward compatibility
5921 oColumn.editor.unsubscribeAll();
5925 this._oColumnSet = null;
5926 this._clearTrTemplateEl();
5929 if(lang.isArray(aColumnDefs)) {
5930 this._oColumnSet = new YAHOO.widget.ColumnSet(aColumnDefs);
5932 // Backward compatibility
5933 else if(aColumnDefs instanceof YAHOO.widget.ColumnSet) {
5934 this._oColumnSet = aColumnDefs;
5937 // Register CellEditor Custom Events
5938 var allKeys = this._oColumnSet.keys;
5939 for(i=0, len=allKeys.length; i<len; i++) {
5940 oColumn = allKeys[i];
5941 if(oColumn.editor && oColumn.editor.subscribe) { // Backward incompatibility
5942 oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
5943 oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
5944 oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
5945 oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
5946 oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
5947 oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
5948 oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
5949 oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
5955 * Initializes DataSource.
5957 * @method _initDataSource
5958 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
5961 _initDataSource : function(oDataSource) {
5962 this._oDataSource = null;
5963 if(oDataSource && (lang.isFunction(oDataSource.sendRequest))) {
5964 this._oDataSource = oDataSource;
5966 // Backward compatibility
5968 var tmpTable = null;
5969 var tmpContainer = this._elContainer;
5971 //TODO: this will break if re-initing DS at runtime for SDT
5972 // Peek in container child nodes to see if TABLE already exists
5973 if(tmpContainer.hasChildNodes()) {
5974 var tmpChildren = tmpContainer.childNodes;
5975 for(i=0; i<tmpChildren.length; i++) {
5976 if(tmpChildren[i].nodeName && tmpChildren[i].nodeName.toLowerCase() == "table") {
5977 tmpTable = tmpChildren[i];
5982 var tmpFieldsArray = [];
5983 for(; i<this._oColumnSet.keys.length; i++) {
5984 tmpFieldsArray.push({key:this._oColumnSet.keys[i].key});
5987 this._oDataSource = new DS(tmpTable);
5988 this._oDataSource.responseType = DS.TYPE_HTMLTABLE;
5989 this._oDataSource.responseSchema = {fields: tmpFieldsArray};
5996 * Initializes RecordSet.
5998 * @method _initRecordSet
6001 _initRecordSet : function() {
6002 if(this._oRecordSet) {
6003 this._oRecordSet.reset();
6006 this._oRecordSet = new YAHOO.widget.RecordSet();
6011 * Initializes DOM elements.
6013 * @method _initDomElements
6014 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
6015 * return {Boolean} False in case of error, otherwise true
6018 _initDomElements : function(elContainer) {
6020 this._initContainerEl(elContainer);
6022 this._initTableEl(this._elContainer);
6024 this._initColgroupEl(this._elTable);
6026 this._initTheadEl(this._elTable);
6029 this._initMsgTbodyEl(this._elTable);
6032 this._initTbodyEl(this._elTable);
6034 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody) {
6043 * Destroy's the DataTable outer container element, if available.
6045 * @method _destroyContainerEl
6046 * @param elContainer {HTMLElement} Reference to the container element.
6049 _destroyContainerEl : function(elContainer) {
6050 var columns = this._oColumnSet.keys,
6053 Dom.removeClass(elContainer, DT.CLASS_DATATABLE);
6056 Ev.purgeElement( elContainer );
6057 Ev.purgeElement( this._elThead, true ); // recursive to get resize handles
6058 Ev.purgeElement( this._elTbody );
6059 Ev.purgeElement( this._elMsgTbody );
6061 // because change doesn't bubble, each select (via formatDropdown) gets
6062 // its own subscription
6063 elements = elContainer.getElementsByTagName( 'select' );
6065 if ( elements.length ) {
6066 Ev.detachListener( elements, 'change' );
6069 for ( i = columns.length - 1; i >= 0; --i ) {
6070 if ( columns[i].editor ) {
6071 Ev.purgeElement( columns[i].editor._elContainer );
6075 elContainer.innerHTML = "";
6077 this._elContainer = null;
6078 this._elColgroup = null;
6079 this._elThead = null;
6080 this._elTbody = null;
6084 * Initializes the DataTable outer container element, including a mask.
6086 * @method _initContainerEl
6087 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
6090 _initContainerEl : function(elContainer) {
6091 // Validate container
6092 elContainer = Dom.get(elContainer);
6094 if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
6096 this._destroyContainerEl(elContainer);
6098 Dom.addClass(elContainer, DT.CLASS_DATATABLE);
6099 Ev.addListener(elContainer, "focus", this._onTableFocus, this);
6100 Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
6101 this._elContainer = elContainer;
6103 var elMask = document.createElement("div");
6104 elMask.className = DT.CLASS_MASK;
6105 elMask.style.display = "none";
6106 this._elMask = elContainer.appendChild(elMask);
6111 * Destroy's the DataTable TABLE element, if available.
6113 * @method _destroyTableEl
6116 _destroyTableEl : function() {
6117 var elTable = this._elTable;
6119 Ev.purgeElement(elTable, true);
6120 elTable.parentNode.removeChild(elTable);
6121 this._elCaption = null;
6122 this._elColgroup = null;
6123 this._elThead = null;
6124 this._elTbody = null;
6129 * Creates HTML markup CAPTION element.
6131 * @method _initCaptionEl
6132 * @param sCaption {HTML} Caption value. String values are treated as markup and
6133 * inserted into the DOM with innerHTML.
6136 _initCaptionEl : function(sCaption) {
6137 if(this._elTable && sCaption) {
6138 // Create CAPTION element
6139 if(!this._elCaption) {
6140 this._elCaption = this._elTable.createCaption();
6142 // Set CAPTION value
6143 this._elCaption.innerHTML = sCaption;
6145 else if(this._elCaption) {
6146 this._elCaption.parentNode.removeChild(this._elCaption);
6151 * Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
6152 * container element.
6154 * @method _initTableEl
6155 * @param elContainer {HTMLElement} Container element into which to create TABLE.
6158 _initTableEl : function(elContainer) {
6161 this._destroyTableEl();
6164 this._elTable = elContainer.appendChild(document.createElement("table"));
6166 // Set SUMMARY attribute
6167 this._elTable.summary = this.get("summary");
6169 // Create CAPTION element
6170 if(this.get("caption")) {
6171 this._initCaptionEl(this.get("caption"));
6174 // Set up mouseover/mouseout events via mouseenter/mouseleave delegation
6175 Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this);
6176 Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this);
6177 Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-data>tr>td", this);
6178 Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-data>tr>td", this);
6179 Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-message>tr>td", this);
6180 Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-message>tr>td", this);
6185 * Destroy's the DataTable COLGROUP element, if available.
6187 * @method _destroyColgroupEl
6190 _destroyColgroupEl : function() {
6191 var elColgroup = this._elColgroup;
6193 var elTable = elColgroup.parentNode;
6194 Ev.purgeElement(elColgroup, true);
6195 elTable.removeChild(elColgroup);
6196 this._elColgroup = null;
6201 * Initializes COLGROUP and COL elements for managing minWidth.
6203 * @method _initColgroupEl
6204 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
6207 _initColgroupEl : function(elTable) {
6210 this._destroyColgroupEl();
6212 // Add COLs to DOCUMENT FRAGMENT
6213 var allCols = this._aColIds || [],
6214 allKeys = this._oColumnSet.keys,
6215 i = 0, len = allCols.length,
6217 elFragment = document.createDocumentFragment(),
6218 elColTemplate = document.createElement("col");
6220 for(i=0,len=allKeys.length; i<len; i++) {
6221 oColumn = allKeys[i];
6222 elCol = elFragment.appendChild(elColTemplate.cloneNode(false));
6226 var elColgroup = elTable.insertBefore(document.createElement("colgroup"), elTable.firstChild);
6227 elColgroup.appendChild(elFragment);
6228 this._elColgroup = elColgroup;
6233 * Adds a COL element to COLGROUP at given index.
6235 * @method _insertColgroupColEl
6236 * @param index {Number} Index of new COL element.
6239 _insertColgroupColEl : function(index) {
6240 if(lang.isNumber(index)&& this._elColgroup) {
6241 var nextSibling = this._elColgroup.childNodes[index] || null;
6242 this._elColgroup.insertBefore(document.createElement("col"), nextSibling);
6247 * Removes a COL element to COLGROUP at given index.
6249 * @method _removeColgroupColEl
6250 * @param index {Number} Index of removed COL element.
6253 _removeColgroupColEl : function(index) {
6254 if(lang.isNumber(index) && this._elColgroup && this._elColgroup.childNodes[index]) {
6255 this._elColgroup.removeChild(this._elColgroup.childNodes[index]);
6260 * Reorders a COL element from old index(es) to new index.
6262 * @method _reorderColgroupColEl
6263 * @param aKeyIndexes {Number[]} Array of indexes of removed COL element.
6264 * @param newIndex {Number} New index.
6267 _reorderColgroupColEl : function(aKeyIndexes, newIndex) {
6268 if(lang.isArray(aKeyIndexes) && lang.isNumber(newIndex) && this._elColgroup && (this._elColgroup.childNodes.length > aKeyIndexes[aKeyIndexes.length-1])) {
6272 for(i=aKeyIndexes.length-1; i>-1; i--) {
6273 tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
6276 var nextSibling = this._elColgroup.childNodes[newIndex] || null;
6277 for(i=tmpCols.length-1; i>-1; i--) {
6278 this._elColgroup.insertBefore(tmpCols[i], nextSibling);
6284 * Destroy's the DataTable THEAD element, if available.
6286 * @method _destroyTheadEl
6289 _destroyTheadEl : function() {
6290 var elThead = this._elThead;
6292 var elTable = elThead.parentNode;
6293 Ev.purgeElement(elThead, true);
6294 this._destroyColumnHelpers();
6295 elTable.removeChild(elThead);
6296 this._elThead = null;
6301 * Initializes THEAD element.
6303 * @method _initTheadEl
6304 * @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
6305 * @param {HTMLElement} Initialized THEAD element.
6308 _initTheadEl : function(elTable) {
6309 elTable = elTable || this._elTable;
6313 this._destroyTheadEl();
6315 //TODO: append to DOM later for performance
6316 var elThead = (this._elColgroup) ?
6317 elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
6318 elTable.appendChild(document.createElement("thead"));
6320 // Set up DOM events for THEAD
6321 Ev.addListener(elThead, "focus", this._onTheadFocus, this);
6322 Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
6323 Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
6324 Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
6325 Ev.addListener(elThead, "click", this._onTheadClick, this);
6327 // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
6328 // delegation at the TABLE level
6330 // Since we can't listen for click and dblclick on the same element...
6331 // Attach separately to THEAD and TBODY
6332 ///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
6334 var oColumnSet = this._oColumnSet,
6337 // Add TRs to the THEAD
6338 var colTree = oColumnSet.tree;
6340 for(i=0; i<colTree.length; i++) {
6341 var elTheadTr = elThead.appendChild(document.createElement("tr"));
6343 // ...and create TH cells
6344 for(j=0; j<colTree[i].length; j++) {
6345 oColumn = colTree[i][j];
6346 elTh = elTheadTr.appendChild(document.createElement("th"));
6347 this._initThEl(elTh,oColumn);
6350 // Set FIRST/LAST on THEAD rows
6352 Dom.addClass(elTheadTr, DT.CLASS_FIRST);
6354 if(i === (colTree.length-1)) {
6355 Dom.addClass(elTheadTr, DT.CLASS_LAST);
6360 // Set FIRST/LAST on edge TH elements using the values in ColumnSet headers array
6361 var aFirstHeaders = oColumnSet.headers[0] || [];
6362 for(i=0; i<aFirstHeaders.length; i++) {
6363 Dom.addClass(Dom.get(this.getId() +"-th-"+aFirstHeaders[i]), DT.CLASS_FIRST);
6365 var aLastHeaders = oColumnSet.headers[oColumnSet.headers.length-1] || [];
6366 for(i=0; i<aLastHeaders.length; i++) {
6367 Dom.addClass(Dom.get(this.getId() +"-th-"+aLastHeaders[i]), DT.CLASS_LAST);
6371 ///TODO: try _repaintGecko(this._elContainer) instead
6373 if(ua.webkit && ua.webkit < 420) {
6375 setTimeout(function() {
6376 elThead.style.display = "";
6378 elThead.style.display = 'none';
6381 this._elThead = elThead;
6383 // Column helpers needs _elThead to exist
6384 this._initColumnHelpers();
6389 * Populates TH element as defined by Column.
6392 * @param elTh {HTMLElement} TH element reference.
6393 * @param oColumn {YAHOO.widget.Column} Column object.
6396 _initThEl : function(elTh, oColumn) {
6397 elTh.id = this.getId() + "-th-" + oColumn.getSanitizedKey(); // Needed for accessibility, getColumn by TH, and ColumnDD
6398 elTh.innerHTML = "";
6399 elTh.rowSpan = oColumn.getRowspan();
6400 elTh.colSpan = oColumn.getColspan();
6401 oColumn._elTh = elTh;
6403 var elThLiner = elTh.appendChild(document.createElement("div"));
6404 elThLiner.id = elTh.id + "-liner"; // Needed for resizer
6405 elThLiner.className = DT.CLASS_LINER;
6406 oColumn._elThLiner = elThLiner;
6408 var elThLabel = elThLiner.appendChild(document.createElement("span"));
6409 elThLabel.className = DT.CLASS_LABEL;
6411 // Assign abbr attribute
6413 elTh.abbr = oColumn.abbr;
6415 // Clear minWidth on hidden Columns
6416 if(oColumn.hidden) {
6417 this._clearMinWidth(oColumn);
6420 elTh.className = this._getColumnClassNames(oColumn);
6422 // Set Column width...
6424 // Validate minWidth
6425 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
6426 oColumn.minWidth : oColumn.width;
6427 // ...for fallback cases
6428 if(DT._bDynStylesFallback) {
6429 elTh.firstChild.style.overflow = 'hidden';
6430 elTh.firstChild.style.width = nWidth + 'px';
6432 // ...for non fallback cases
6434 this._setColumnWidthDynStyles(oColumn, nWidth + 'px', 'hidden');
6438 this.formatTheadCell(elThLabel, oColumn, this.get("sortedBy"));
6439 oColumn._elThLabel = elThLabel;
6443 * Outputs markup into the given TH based on given Column.
6445 * @method formatTheadCell
6446 * @param elCellLabel {HTMLElement} The label SPAN element within the TH liner,
6447 * not the liner DIV element.
6448 * @param oColumn {YAHOO.widget.Column} Column instance.
6449 * @param oSortedBy {Object} Sort state object literal.
6451 formatTheadCell : function(elCellLabel, oColumn, oSortedBy) {
6452 var sKey = oColumn.getKey();
6453 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
6455 // Add accessibility link for sortable Columns
6456 if(oColumn.sortable) {
6457 // Calculate the direction
6458 var sSortClass = this.getColumnSortDir(oColumn, oSortedBy);
6459 var bDesc = (sSortClass === DT.CLASS_DESC);
6461 // This is the sorted Column
6462 if(oSortedBy && (oColumn.key === oSortedBy.key)) {
6463 bDesc = !(oSortedBy.dir === DT.CLASS_DESC);
6466 // Generate a unique HREF for visited status
6467 var sHref = this.getId() + "-href-" + oColumn.getSanitizedKey();
6469 // Generate a dynamic TITLE for sort status
6470 var sTitle = (bDesc) ? this.get("MSG_SORTDESC") : this.get("MSG_SORTASC");
6472 // Format the element
6473 elCellLabel.innerHTML = "<a href=\"" + sHref + "\" title=\"" + sTitle + "\" class=\"" + DT.CLASS_SORTABLE + "\">" + sLabel + "</a>";
6475 // Just display the label for non-sortable Columns
6477 elCellLabel.innerHTML = sLabel;
6482 * Disables DD from top-level Column TH elements.
6484 * @method _destroyDraggableColumns
6487 _destroyDraggableColumns : function() {
6489 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
6490 oColumn = this._oColumnSet.tree[0][i];
6492 oColumn._dd = oColumn._dd.unreg();
6493 Dom.removeClass(oColumn.getThEl(), DT.CLASS_DRAGGABLE);
6497 // Destroy column drag proxy
6498 this._destroyColumnDragTargetEl();
6502 * Initializes top-level Column TH elements into DD instances.
6504 * @method _initDraggableColumns
6507 _initDraggableColumns : function() {
6508 this._destroyDraggableColumns();
6510 var oColumn, elTh, elDragTarget;
6511 for(var i=0, len=this._oColumnSet.tree[0].length; i<len; i++) {
6512 oColumn = this._oColumnSet.tree[0][i];
6513 elTh = oColumn.getThEl();
6514 Dom.addClass(elTh, DT.CLASS_DRAGGABLE);
6515 elDragTarget = this._initColumnDragTargetEl();
6516 oColumn._dd = new YAHOO.widget.ColumnDD(this, oColumn, elTh, elDragTarget);
6524 * Destroys shared Column drag target.
6526 * @method _destroyColumnDragTargetEl
6529 _destroyColumnDragTargetEl : function() {
6530 if(this._elColumnDragTarget) {
6531 var el = this._elColumnDragTarget;
6532 YAHOO.util.Event.purgeElement(el);
6533 el.parentNode.removeChild(el);
6534 this._elColumnDragTarget = null;
6539 * Creates HTML markup for shared Column drag target.
6541 * @method _initColumnDragTargetEl
6542 * @return {HTMLElement} Reference to Column drag target.
6545 _initColumnDragTargetEl : function() {
6546 if(!this._elColumnDragTarget) {
6547 // Attach Column drag target element as first child of body
6548 var elColumnDragTarget = document.createElement('div');
6549 elColumnDragTarget.id = this.getId() + "-coltarget";
6550 elColumnDragTarget.className = DT.CLASS_COLTARGET;
6551 elColumnDragTarget.style.display = "none";
6552 document.body.insertBefore(elColumnDragTarget, document.body.firstChild);
6554 // Internal tracker of Column drag target
6555 this._elColumnDragTarget = elColumnDragTarget;
6558 return this._elColumnDragTarget;
6562 * Disables resizeability on key Column TH elements.
6564 * @method _destroyResizeableColumns
6567 _destroyResizeableColumns : function() {
6568 var aKeys = this._oColumnSet.keys;
6569 for(var i=0, len=aKeys.length; i<len; i++) {
6570 if(aKeys[i]._ddResizer) {
6571 aKeys[i]._ddResizer = aKeys[i]._ddResizer.unreg();
6572 Dom.removeClass(aKeys[i].getThEl(), DT.CLASS_RESIZEABLE);
6576 // Destroy resizer proxy
6577 this._destroyColumnResizerProxyEl();
6581 * Initializes resizeability on key Column TH elements.
6583 * @method _initResizeableColumns
6586 _initResizeableColumns : function() {
6587 this._destroyResizeableColumns();
6589 var oColumn, elTh, elThLiner, elThResizerLiner, elThResizer, elResizerProxy, cancelClick;
6590 for(var i=0, len=this._oColumnSet.keys.length; i<len; i++) {
6591 oColumn = this._oColumnSet.keys[i];
6592 if(oColumn.resizeable) {
6593 elTh = oColumn.getThEl();
6594 Dom.addClass(elTh, DT.CLASS_RESIZEABLE);
6595 elThLiner = oColumn.getThLinerEl();
6597 // Bug 1915349: So resizer is as tall as TH when rowspan > 1
6598 // Create a separate resizer liner with position:relative
6599 elThResizerLiner = elTh.appendChild(document.createElement("div"));
6600 elThResizerLiner.className = DT.CLASS_RESIZERLINER;
6602 // Move TH contents into the new resizer liner
6603 elThResizerLiner.appendChild(elThLiner);
6605 // Create the resizer
6606 elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
6607 elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
6608 elThResizer.className = DT.CLASS_RESIZER;
6609 oColumn._elResizer = elThResizer;
6611 // Create the resizer proxy, once per instance
6612 elResizerProxy = this._initColumnResizerProxyEl();
6613 oColumn._ddResizer = new YAHOO.util.ColumnResizer(
6614 this, oColumn, elTh, elThResizer, elResizerProxy);
6615 cancelClick = function(e) {
6616 Ev.stopPropagation(e);
6618 Ev.addListener(elThResizer,"click",cancelClick);
6627 * Destroys shared Column resizer proxy.
6629 * @method _destroyColumnResizerProxyEl
6630 * @return {HTMLElement} Reference to Column resizer proxy.
6633 _destroyColumnResizerProxyEl : function() {
6634 if(this._elColumnResizerProxy) {
6635 var el = this._elColumnResizerProxy;
6636 YAHOO.util.Event.purgeElement(el);
6637 el.parentNode.removeChild(el);
6638 this._elColumnResizerProxy = null;
6643 * Creates HTML markup for shared Column resizer proxy.
6645 * @method _initColumnResizerProxyEl
6646 * @return {HTMLElement} Reference to Column resizer proxy.
6649 _initColumnResizerProxyEl : function() {
6650 if(!this._elColumnResizerProxy) {
6651 // Attach Column resizer element as first child of body
6652 var elColumnResizerProxy = document.createElement("div");
6653 elColumnResizerProxy.id = this.getId() + "-colresizerproxy"; // Needed for ColumnResizer
6654 elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
6655 document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
6657 // Internal tracker of Column resizer proxy
6658 this._elColumnResizerProxy = elColumnResizerProxy;
6660 return this._elColumnResizerProxy;
6664 * Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
6666 * @method _destroyColumnHelpers
6669 _destroyColumnHelpers : function() {
6670 this._destroyDraggableColumns();
6671 this._destroyResizeableColumns();
6675 * Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
6677 * @method _initColumnHelpers
6680 _initColumnHelpers : function() {
6681 if(this.get("draggableColumns")) {
6682 this._initDraggableColumns();
6684 this._initResizeableColumns();
6688 * Destroy's the DataTable TBODY element, if available.
6690 * @method _destroyTbodyEl
6693 _destroyTbodyEl : function() {
6694 var elTbody = this._elTbody;
6696 var elTable = elTbody.parentNode;
6697 Ev.purgeElement(elTbody, true);
6698 elTable.removeChild(elTbody);
6699 this._elTbody = null;
6704 * Initializes TBODY element for data.
6706 * @method _initTbodyEl
6707 * @param elTable {HTMLElement} TABLE element into which to create TBODY .
6710 _initTbodyEl : function(elTable) {
6713 this._destroyTbodyEl();
6716 var elTbody = elTable.appendChild(document.createElement("tbody"));
6717 elTbody.tabIndex = 0;
6718 elTbody.className = DT.CLASS_DATA;
6720 // Set up DOM events for TBODY
6721 Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
6722 Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
6723 Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
6724 Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
6725 Ev.addListener(elTbody, "click", this._onTbodyClick, this);
6727 // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
6728 // delegation at the TABLE level
6730 // Since we can't listen for click and dblclick on the same element...
6731 // Attach separately to THEAD and TBODY
6732 ///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
6735 // IE puts focus outline in the wrong place
6737 elTbody.hideFocus=true;
6740 this._elTbody = elTbody;
6745 * Destroy's the DataTable message TBODY element, if available.
6747 * @method _destroyMsgTbodyEl
6750 _destroyMsgTbodyEl : function() {
6751 var elMsgTbody = this._elMsgTbody;
6753 var elTable = elMsgTbody.parentNode;
6754 Ev.purgeElement(elMsgTbody, true);
6755 elTable.removeChild(elMsgTbody);
6756 this._elTbody = null;
6761 * Initializes TBODY element for messaging.
6763 * @method _initMsgTbodyEl
6764 * @param elTable {HTMLElement} TABLE element into which to create TBODY
6767 _initMsgTbodyEl : function(elTable) {
6769 var elMsgTbody = document.createElement("tbody");
6770 elMsgTbody.className = DT.CLASS_MESSAGE;
6771 var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
6772 elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
6773 this._elMsgTr = elMsgTr;
6774 var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
6775 elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
6776 elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
6777 this._elMsgTd = elMsgTd;
6778 elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
6779 var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
6780 elMsgLiner.className = DT.CLASS_LINER;
6781 this._elMsgTbody = elMsgTbody;
6783 // Set up DOM events for TBODY
6784 Ev.addListener(elMsgTbody, "focus", this._onTbodyFocus, this);
6785 Ev.addListener(elMsgTbody, "mousedown", this._onTableMousedown, this);
6786 Ev.addListener(elMsgTbody, "mouseup", this._onTableMouseup, this);
6787 Ev.addListener(elMsgTbody, "keydown", this._onTbodyKeydown, this);
6788 Ev.addListener(elMsgTbody, "click", this._onTbodyClick, this);
6790 // Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
6791 // delegation at the TABLE level
6796 * Initialize internal event listeners
6798 * @method _initEvents
6801 _initEvents : function () {
6802 // Initialize Column sort
6803 this._initColumnSort();
6805 // Add the document level click listener
6806 YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
6808 // Paginator integration
6809 this.subscribe("paginatorChange",function () {
6810 this._handlePaginatorChange.apply(this,arguments);
6813 this.subscribe("initEvent",function () {
6814 this.renderPaginator();
6817 // Initialize CellEditor integration
6818 this._initCellEditing();
6822 * Initializes Column sorting.
6824 * @method _initColumnSort
6827 _initColumnSort : function() {
6828 this.subscribe("theadCellClickEvent", this.onEventSortColumn);
6830 // Backward compatibility
6831 var oSortedBy = this.get("sortedBy");
6833 if(oSortedBy.dir == "desc") {
6834 this._configs.sortedBy.value.dir = DT.CLASS_DESC;
6836 else if(oSortedBy.dir == "asc") {
6837 this._configs.sortedBy.value.dir = DT.CLASS_ASC;
6843 * Initializes CellEditor integration.
6845 * @method _initCellEditing
6848 _initCellEditing : function() {
6849 this.subscribe("editorBlurEvent",function () {
6850 this.onEditorBlurEvent.apply(this,arguments);
6852 this.subscribe("editorBlockEvent",function () {
6853 this.onEditorBlockEvent.apply(this,arguments);
6855 this.subscribe("editorUnblockEvent",function () {
6856 this.onEditorUnblockEvent.apply(this,arguments);
6892 // DOM MUTATION FUNCTIONS
6895 * Retruns classnames to represent current Column states.
6896 * @method _getColumnClassnames
6897 * @param oColumn {YAHOO.widget.Column} Column instance.
6898 * @param aAddClasses {String[]} An array of additional classnames to add to the
6900 * @return {String} A String of classnames to be assigned to TH or TD elements
6904 _getColumnClassNames : function (oColumn, aAddClasses) {
6908 if(lang.isString(oColumn.className)) {
6909 // Single custom class
6910 allClasses = [oColumn.className];
6912 else if(lang.isArray(oColumn.className)) {
6913 // Array of custom classes
6914 allClasses = oColumn.className;
6917 // no custom classes
6921 // Hook for setting width with via dynamic style uses key since ID is too disposable
6922 allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
6924 // Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
6925 allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
6927 var isSortedBy = this.get("sortedBy") || {};
6929 if(oColumn.key === isSortedBy.key) {
6930 allClasses[allClasses.length] = isSortedBy.dir || '';
6933 if(oColumn.hidden) {
6934 allClasses[allClasses.length] = DT.CLASS_HIDDEN;
6937 if(oColumn.selected) {
6938 allClasses[allClasses.length] = DT.CLASS_SELECTED;
6941 if(oColumn.sortable) {
6942 allClasses[allClasses.length] = DT.CLASS_SORTABLE;
6945 if(oColumn.resizeable) {
6946 allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
6949 if(oColumn.editor) {
6950 allClasses[allClasses.length] = DT.CLASS_EDITABLE;
6953 // Addtnl classes, including First/Last
6955 allClasses = allClasses.concat(aAddClasses);
6958 return allClasses.join(' ');
6962 * Clears TR element template in response to any Column state change.
6963 * @method _clearTrTemplateEl
6966 _clearTrTemplateEl : function () {
6967 this._elTrTemplate = null;
6971 * Returns a new TR element template with TD elements classed with current
6973 * @method _getTrTemplateEl
6974 * @return {HTMLElement} A TR element to be cloned and added to the DOM.
6977 _getTrTemplateEl : function (oRecord, index) {
6978 // Template is already available
6979 if(this._elTrTemplate) {
6980 return this._elTrTemplate;
6982 // Template needs to be created
6985 tr = d.createElement('tr'),
6986 td = d.createElement('td'),
6987 div = d.createElement('div');
6989 // Append the liner element
6990 td.appendChild(div);
6992 // Create TD elements into DOCUMENT FRAGMENT
6993 var df = document.createDocumentFragment(),
6994 allKeys = this._oColumnSet.keys,
6997 // Set state for each TD;
6999 for(var i=0, keysLen=allKeys.length; i<keysLen; i++) {
7000 // Clone the TD template
7001 elTd = td.cloneNode(true);
7003 // Format the base TD
7004 elTd = this._formatTdEl(allKeys[i], elTd, i, (i===keysLen-1));
7006 df.appendChild(elTd);
7009 tr.className = DT.CLASS_REC;
7010 this._elTrTemplate = tr;
7016 * Formats a basic TD element.
7017 * @method _formatTdEl
7018 * @param oColumn {YAHOO.widget.Column} Associated Column instance.
7019 * @param elTd {HTMLElement} An unformatted TD element.
7020 * @param index {Number} Column key index.
7021 * @param isLast {Boolean} True if Column is last key of the ColumnSet.
7022 * @return {HTMLElement} A formatted TD element.
7025 _formatTdEl : function (oColumn, elTd, index, isLast) {
7026 var oColumnSet = this._oColumnSet;
7028 // Set the TD's accessibility headers
7029 var allHeaders = oColumnSet.headers,
7030 allColHeaders = allHeaders[index],
7033 for(var j=0, headersLen=allColHeaders.length; j < headersLen; j++) {
7034 sHeader = this._sId + "-th-" + allColHeaders[j] + ' ';
7035 sTdHeaders += sHeader;
7037 elTd.headers = sTdHeaders;
7039 // Class the TD element
7040 var aAddClasses = [];
7042 aAddClasses[aAddClasses.length] = DT.CLASS_FIRST;
7045 aAddClasses[aAddClasses.length] = DT.CLASS_LAST;
7047 elTd.className = this._getColumnClassNames(oColumn, aAddClasses);
7049 // Class the liner element
7050 elTd.firstChild.className = DT.CLASS_LINER;
7052 // Set Column width for fallback cases
7053 if(oColumn.width && DT._bDynStylesFallback) {
7054 // Validate minWidth
7055 var nWidth = (oColumn.minWidth && (oColumn.width < oColumn.minWidth)) ?
7056 oColumn.minWidth : oColumn.width;
7057 elTd.firstChild.style.overflow = 'hidden';
7058 elTd.firstChild.style.width = nWidth + 'px';
7066 * Create a new TR element for a given Record and appends it with the correct
7067 * number of Column-state-classed TD elements. Striping is the responsibility of
7068 * the calling function, which may decide to stripe the single row, a subset of
7069 * rows, or all the rows.
7070 * @method _createTrEl
7071 * @param oRecord {YAHOO.widget.Record} Record instance
7072 * @return {HTMLElement} The new TR element. This must be added to the DOM.
7075 _addTrEl : function (oRecord) {
7076 var elTrTemplate = this._getTrTemplateEl();
7078 // Clone the TR template.
7079 var elTr = elTrTemplate.cloneNode(true);
7082 return this._updateTrEl(elTr,oRecord);
7086 * Formats the contents of the given TR's TD elements with data from the given
7087 * Record. Only innerHTML should change, nothing structural.
7089 * @method _updateTrEl
7090 * @param elTr {HTMLElement} The TR element to update.
7091 * @param oRecord {YAHOO.widget.Record} The associated Record instance.
7092 * @return {HTMLElement} DOM reference to the new TR element.
7095 _updateTrEl : function(elTr, oRecord) {
7096 var ok = this.get("formatRow") ? this.get("formatRow").call(this, elTr, oRecord) : true;
7098 // Hide the row to prevent constant reflows
7099 elTr.style.display = 'none';
7101 // Update TD elements with new data
7102 var allTds = elTr.childNodes,
7104 for(var i=0,len=allTds.length; i<len; ++i) {
7107 // Set the cell content
7108 this.formatCell(allTds[i].firstChild, oRecord, this._oColumnSet.keys[i]);
7111 // Redisplay the row for reflow
7112 elTr.style.display = '';
7115 // Record-to-TR association and tracking of FIRST/LAST
7116 var oldId = elTr.id,
7117 newId = oRecord.getId();
7118 if(this._sFirstTrId === oldId) {
7119 this._sFirstTrId = newId;
7121 if(this._sLastTrId === oldId) {
7122 this._sLastTrId = newId;
7130 * Deletes TR element by DOM reference or by DataTable page row index.
7132 * @method _deleteTrEl
7133 * @param row {HTMLElement | Number} TR element reference or Datatable page row index.
7134 * @return {Boolean} Returns true if successful, else returns false.
7137 _deleteTrEl : function(row) {
7140 // Get page row index for the element
7141 if(!lang.isNumber(row)) {
7142 rowIndex = Dom.get(row).sectionRowIndex;
7147 if(lang.isNumber(rowIndex) && (rowIndex > -2) && (rowIndex < this._elTbody.rows.length)) {
7148 // Cannot use tbody.deleteRow due to IE6 instability
7149 //return this._elTbody.deleteRow(rowIndex);
7150 return this._elTbody.removeChild(this._elTbody.rows[row]);
7183 // CSS/STATE FUNCTIONS
7189 * Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
7190 * of the DataTable page and updates internal tracker.
7192 * @method _unsetFirstRow
7195 _unsetFirstRow : function() {
7197 if(this._sFirstTrId) {
7198 Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
7199 this._sFirstTrId = null;
7204 * Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
7205 * of the DataTable page and updates internal tracker.
7207 * @method _setFirstRow
7210 _setFirstRow : function() {
7211 this._unsetFirstRow();
7212 var elTr = this.getFirstTrEl();
7215 Dom.addClass(elTr, DT.CLASS_FIRST);
7216 this._sFirstTrId = elTr.id;
7221 * Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
7222 * of the DataTable page and updates internal tracker.
7224 * @method _unsetLastRow
7227 _unsetLastRow : function() {
7228 // Unassign previous class
7229 if(this._sLastTrId) {
7230 Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
7231 this._sLastTrId = null;
7236 * Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
7237 * of the DataTable page and updates internal tracker.
7239 * @method _setLastRow
7242 _setLastRow : function() {
7243 this._unsetLastRow();
7244 var elTr = this.getLastTrEl();
7247 Dom.addClass(elTr, DT.CLASS_LAST);
7248 this._sLastTrId = elTr.id;
7253 * Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
7255 * @method _setRowStripes
7256 * @param row {HTMLElement | String | Number} (optional) HTML TR element reference
7257 * or string ID, or page row index of where to start striping.
7258 * @param range {Number} (optional) If given, how many rows to stripe, otherwise
7259 * stripe all the rows until the end.
7262 _setRowStripes : function(row, range) {
7263 // Default values stripe all rows
7264 var allRows = this._elTbody.rows,
7266 nEndIndex = allRows.length,
7267 aOdds = [], nOddIdx = 0,
7268 aEvens = [], nEvenIdx = 0;
7271 if((row !== null) && (row !== undefined)) {
7272 // Validate given start row
7273 var elStartRow = this.getTrEl(row);
7275 nStartIndex = elStartRow.sectionRowIndex;
7277 // Validate given range
7278 if(lang.isNumber(range) && (range > 1)) {
7279 nEndIndex = nStartIndex + range;
7284 for(var i=nStartIndex; i<nEndIndex; i++) {
7286 aOdds[nOddIdx++] = allRows[i];
7288 aEvens[nEvenIdx++] = allRows[i];
7293 Dom.replaceClass(aOdds, DT.CLASS_EVEN, DT.CLASS_ODD);
7296 if (aEvens.length) {
7297 Dom.replaceClass(aEvens, DT.CLASS_ODD, DT.CLASS_EVEN);
7302 * Assigns the class DT.CLASS_SELECTED to TR and TD elements.
7304 * @method _setSelections
7307 _setSelections : function() {
7308 // Keep track of selected rows
7309 var allSelectedRows = this.getSelectedRows();
7310 // Keep track of selected cells
7311 var allSelectedCells = this.getSelectedCells();
7312 // Anything to select?
7313 if((allSelectedRows.length>0) || (allSelectedCells.length > 0)) {
7314 var oColumnSet = this._oColumnSet,
7316 // Loop over each row
7317 for(var i=0; i<allSelectedRows.length; i++) {
7318 el = Dom.get(allSelectedRows[i]);
7320 Dom.addClass(el, DT.CLASS_SELECTED);
7323 // Loop over each cell
7324 for(i=0; i<allSelectedCells.length; i++) {
7325 el = Dom.get(allSelectedCells[i].recordId);
7327 Dom.addClass(el.childNodes[oColumnSet.getColumn(allSelectedCells[i].columnKey).getKeyIndex()], DT.CLASS_SELECTED);
7375 /////////////////////////////////////////////////////////////////////////////
7377 // Private DOM Event Handlers
7379 /////////////////////////////////////////////////////////////////////////////
7382 * Validates minWidths whenever the render chain ends.
7384 * @method _onRenderChainEnd
7387 _onRenderChainEnd : function() {
7388 // Hide loading message
7389 this.hideTableMessage();
7391 // Show empty message
7392 if(this._elTbody.rows.length === 0) {
7393 this.showTableMessage(this.get("MSG_EMPTY"), DT.CLASS_EMPTY);
7396 // Execute in timeout thread to give implementers a chance
7397 // to subscribe after the constructor
7399 setTimeout(function() {
7400 if((oSelf instanceof DT) && oSelf._sId) {
7403 oSelf._bInit = false;
7404 oSelf.fireEvent("initEvent");
7408 oSelf.fireEvent("renderEvent");
7409 // Backward compatibility
7410 oSelf.fireEvent("refreshEvent");
7412 // Post-render routine
7413 oSelf.validateColumnWidths();
7415 // Post-render event
7416 oSelf.fireEvent("postRenderEvent");
7418 /*if(YAHOO.example.Performance.trialStart) {
7419 YAHOO.example.Performance.trialStart = null;
7427 * Handles click events on the DOCUMENT.
7429 * @method _onDocumentClick
7430 * @param e {HTMLEvent} The click event.
7431 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7434 _onDocumentClick : function(e, oSelf) {
7435 var elTarget = Ev.getTarget(e);
7436 var elTag = elTarget.nodeName.toLowerCase();
7438 if(!Dom.isAncestor(oSelf._elContainer, elTarget)) {
7439 oSelf.fireEvent("tableBlurEvent");
7441 // Fires editorBlurEvent when click is not within the TABLE.
7442 // For cases when click is within the TABLE, due to timing issues,
7443 // the editorBlurEvent needs to get fired by the lower-level DOM click
7444 // handlers below rather than by the TABLE click handler directly.
7445 if(oSelf._oCellEditor) {
7446 if(oSelf._oCellEditor.getContainerEl) {
7447 var elContainer = oSelf._oCellEditor.getContainerEl();
7448 // Only if the click was not within the CellEditor container
7449 if(!Dom.isAncestor(elContainer, elTarget) &&
7450 (elContainer.id !== elTarget.id)) {
7451 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
7454 // Backward Compatibility
7455 else if(oSelf._oCellEditor.isActive) {
7456 // Only if the click was not within the Cell Editor container
7457 if(!Dom.isAncestor(oSelf._oCellEditor.container, elTarget) &&
7458 (oSelf._oCellEditor.container.id !== elTarget.id)) {
7459 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
7467 * Handles focus events on the DataTable instance.
7469 * @method _onTableFocus
7470 * @param e {HTMLEvent} The focus event.
7471 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7474 _onTableFocus : function(e, oSelf) {
7475 oSelf.fireEvent("tableFocusEvent");
7479 * Handles focus events on the THEAD element.
7481 * @method _onTheadFocus
7482 * @param e {HTMLEvent} The focus event.
7483 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7486 _onTheadFocus : function(e, oSelf) {
7487 oSelf.fireEvent("theadFocusEvent");
7488 oSelf.fireEvent("tableFocusEvent");
7492 * Handles focus events on the TBODY element.
7494 * @method _onTbodyFocus
7495 * @param e {HTMLEvent} The focus event.
7496 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7499 _onTbodyFocus : function(e, oSelf) {
7500 oSelf.fireEvent("tbodyFocusEvent");
7501 oSelf.fireEvent("tableFocusEvent");
7505 * Handles mouseover events on the DataTable instance.
7507 * @method _onTableMouseover
7508 * @param e {HTMLEvent} The mouseover event.
7509 * @param origTarget {HTMLElement} The mouseenter delegated element.
7510 * @param container {HTMLElement} The mouseenter delegation container.
7511 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7514 _onTableMouseover : function(e, origTarget, container, oSelf) {
7515 var elTarget = origTarget;
7516 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7517 var bKeepBubbling = true;
7518 while(elTarget && (elTag != "table")) {
7525 bKeepBubbling = oSelf.fireEvent("cellMouseoverEvent",{target:elTarget,event:e});
7528 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7529 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoverEvent",{target:elTarget,event:e});
7530 // Backward compatibility
7531 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoverEvent",{target:elTarget,event:e});
7535 bKeepBubbling = oSelf.fireEvent("theadCellMouseoverEvent",{target:elTarget,event:e});
7536 // Backward compatibility
7537 bKeepBubbling = oSelf.fireEvent("headerCellMouseoverEvent",{target:elTarget,event:e});
7540 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7541 bKeepBubbling = oSelf.fireEvent("theadRowMouseoverEvent",{target:elTarget,event:e});
7542 // Backward compatibility
7543 bKeepBubbling = oSelf.fireEvent("headerRowMouseoverEvent",{target:elTarget,event:e});
7546 bKeepBubbling = oSelf.fireEvent("rowMouseoverEvent",{target:elTarget,event:e});
7552 if(bKeepBubbling === false) {
7556 elTarget = elTarget.parentNode;
7558 elTag = elTarget.nodeName.toLowerCase();
7562 oSelf.fireEvent("tableMouseoverEvent",{target:(elTarget || oSelf._elContainer),event:e});
7566 * Handles mouseout events on the DataTable instance.
7568 * @method _onTableMouseout
7569 * @param e {HTMLEvent} The mouseout event.
7570 * @param origTarget {HTMLElement} The mouseleave delegated element.
7571 * @param container {HTMLElement} The mouseleave delegation container.
7572 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7575 _onTableMouseout : function(e, origTarget, container, oSelf) {
7576 var elTarget = origTarget;
7577 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7578 var bKeepBubbling = true;
7579 while(elTarget && (elTag != "table")) {
7586 bKeepBubbling = oSelf.fireEvent("cellMouseoutEvent",{target:elTarget,event:e});
7589 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7590 bKeepBubbling = oSelf.fireEvent("theadLabelMouseoutEvent",{target:elTarget,event:e});
7591 // Backward compatibility
7592 bKeepBubbling = oSelf.fireEvent("headerLabelMouseoutEvent",{target:elTarget,event:e});
7596 bKeepBubbling = oSelf.fireEvent("theadCellMouseoutEvent",{target:elTarget,event:e});
7597 // Backward compatibility
7598 bKeepBubbling = oSelf.fireEvent("headerCellMouseoutEvent",{target:elTarget,event:e});
7601 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7602 bKeepBubbling = oSelf.fireEvent("theadRowMouseoutEvent",{target:elTarget,event:e});
7603 // Backward compatibility
7604 bKeepBubbling = oSelf.fireEvent("headerRowMouseoutEvent",{target:elTarget,event:e});
7607 bKeepBubbling = oSelf.fireEvent("rowMouseoutEvent",{target:elTarget,event:e});
7613 if(bKeepBubbling === false) {
7617 elTarget = elTarget.parentNode;
7619 elTag = elTarget.nodeName.toLowerCase();
7623 oSelf.fireEvent("tableMouseoutEvent",{target:(elTarget || oSelf._elContainer),event:e});
7627 * Handles mousedown events on the DataTable instance.
7629 * @method _onTableMousedown
7630 * @param e {HTMLEvent} The mousedown event.
7631 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7634 _onTableMousedown : function(e, oSelf) {
7635 var elTarget = Ev.getTarget(e);
7636 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7637 var bKeepBubbling = true;
7638 while(elTarget && (elTag != "table")) {
7645 bKeepBubbling = oSelf.fireEvent("cellMousedownEvent",{target:elTarget,event:e});
7648 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7649 bKeepBubbling = oSelf.fireEvent("theadLabelMousedownEvent",{target:elTarget,event:e});
7650 // Backward compatibility
7651 bKeepBubbling = oSelf.fireEvent("headerLabelMousedownEvent",{target:elTarget,event:e});
7655 bKeepBubbling = oSelf.fireEvent("theadCellMousedownEvent",{target:elTarget,event:e});
7656 // Backward compatibility
7657 bKeepBubbling = oSelf.fireEvent("headerCellMousedownEvent",{target:elTarget,event:e});
7660 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7661 bKeepBubbling = oSelf.fireEvent("theadRowMousedownEvent",{target:elTarget,event:e});
7662 // Backward compatibility
7663 bKeepBubbling = oSelf.fireEvent("headerRowMousedownEvent",{target:elTarget,event:e});
7666 bKeepBubbling = oSelf.fireEvent("rowMousedownEvent",{target:elTarget,event:e});
7672 if(bKeepBubbling === false) {
7676 elTarget = elTarget.parentNode;
7678 elTag = elTarget.nodeName.toLowerCase();
7682 oSelf.fireEvent("tableMousedownEvent",{target:(elTarget || oSelf._elContainer),event:e});
7686 * Handles mouseup events on the DataTable instance.
7688 * @method _onTableMouseup
7689 * @param e {HTMLEvent} The mouseup event.
7690 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7693 _onTableMouseup : function(e, oSelf) {
7694 var elTarget = Ev.getTarget(e);
7695 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7696 var bKeepBubbling = true;
7697 while(elTarget && (elTag != "table")) {
7704 bKeepBubbling = oSelf.fireEvent("cellMouseupEvent",{target:elTarget,event:e});
7707 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7708 bKeepBubbling = oSelf.fireEvent("theadLabelMouseupEvent",{target:elTarget,event:e});
7709 // Backward compatibility
7710 bKeepBubbling = oSelf.fireEvent("headerLabelMouseupEvent",{target:elTarget,event:e});
7714 bKeepBubbling = oSelf.fireEvent("theadCellMouseupEvent",{target:elTarget,event:e});
7715 // Backward compatibility
7716 bKeepBubbling = oSelf.fireEvent("headerCellMouseupEvent",{target:elTarget,event:e});
7719 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7720 bKeepBubbling = oSelf.fireEvent("theadRowMouseupEvent",{target:elTarget,event:e});
7721 // Backward compatibility
7722 bKeepBubbling = oSelf.fireEvent("headerRowMouseupEvent",{target:elTarget,event:e});
7725 bKeepBubbling = oSelf.fireEvent("rowMouseupEvent",{target:elTarget,event:e});
7731 if(bKeepBubbling === false) {
7735 elTarget = elTarget.parentNode;
7737 elTag = elTarget.nodeName.toLowerCase();
7741 oSelf.fireEvent("tableMouseupEvent",{target:(elTarget || oSelf._elContainer),event:e});
7745 * Handles dblclick events on the DataTable instance.
7747 * @method _onTableDblclick
7748 * @param e {HTMLEvent} The dblclick event.
7749 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7752 _onTableDblclick : function(e, oSelf) {
7753 var elTarget = Ev.getTarget(e);
7754 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7755 var bKeepBubbling = true;
7756 while(elTarget && (elTag != "table")) {
7761 bKeepBubbling = oSelf.fireEvent("cellDblclickEvent",{target:elTarget,event:e});
7764 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7765 bKeepBubbling = oSelf.fireEvent("theadLabelDblclickEvent",{target:elTarget,event:e});
7766 // Backward compatibility
7767 bKeepBubbling = oSelf.fireEvent("headerLabelDblclickEvent",{target:elTarget,event:e});
7771 bKeepBubbling = oSelf.fireEvent("theadCellDblclickEvent",{target:elTarget,event:e});
7772 // Backward compatibility
7773 bKeepBubbling = oSelf.fireEvent("headerCellDblclickEvent",{target:elTarget,event:e});
7776 if(elTarget.parentNode.nodeName.toLowerCase() == "thead") {
7777 bKeepBubbling = oSelf.fireEvent("theadRowDblclickEvent",{target:elTarget,event:e});
7778 // Backward compatibility
7779 bKeepBubbling = oSelf.fireEvent("headerRowDblclickEvent",{target:elTarget,event:e});
7782 bKeepBubbling = oSelf.fireEvent("rowDblclickEvent",{target:elTarget,event:e});
7788 if(bKeepBubbling === false) {
7792 elTarget = elTarget.parentNode;
7794 elTag = elTarget.nodeName.toLowerCase();
7798 oSelf.fireEvent("tableDblclickEvent",{target:(elTarget || oSelf._elContainer),event:e});
7801 * Handles keydown events on the THEAD element.
7803 * @method _onTheadKeydown
7804 * @param e {HTMLEvent} The key event.
7805 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7808 _onTheadKeydown : function(e, oSelf) {
7809 var elTarget = Ev.getTarget(e);
7810 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7811 var bKeepBubbling = true;
7812 while(elTarget && (elTag != "table")) {
7818 // TODO: implement textareaKeyEvent
7821 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
7826 if(bKeepBubbling === false) {
7830 elTarget = elTarget.parentNode;
7832 elTag = elTarget.nodeName.toLowerCase();
7836 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
7840 * Handles keydown events on the TBODY element. Handles selection behavior,
7841 * provides hooks for ENTER to edit functionality.
7843 * @method _onTbodyKeydown
7844 * @param e {HTMLEvent} The key event.
7845 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7848 _onTbodyKeydown : function(e, oSelf) {
7849 var sMode = oSelf.get("selectionMode");
7851 if(sMode == "standard") {
7852 oSelf._handleStandardSelectionByKey(e);
7854 else if(sMode == "single") {
7855 oSelf._handleSingleSelectionByKey(e);
7857 else if(sMode == "cellblock") {
7858 oSelf._handleCellBlockSelectionByKey(e);
7860 else if(sMode == "cellrange") {
7861 oSelf._handleCellRangeSelectionByKey(e);
7863 else if(sMode == "singlecell") {
7864 oSelf._handleSingleCellSelectionByKey(e);
7867 if(oSelf._oCellEditor) {
7868 if(oSelf._oCellEditor.fireEvent) {
7869 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
7871 else if(oSelf._oCellEditor.isActive) {
7872 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
7876 var elTarget = Ev.getTarget(e);
7877 var elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase();
7878 var bKeepBubbling = true;
7879 while(elTarget && (elTag != "table")) {
7884 bKeepBubbling = oSelf.fireEvent("tbodyKeyEvent",{target:elTarget,event:e});
7889 if(bKeepBubbling === false) {
7893 elTarget = elTarget.parentNode;
7895 elTag = elTarget.nodeName.toLowerCase();
7899 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
7903 * Handles click events on the THEAD element.
7905 * @method _onTheadClick
7906 * @param e {HTMLEvent} The click event.
7907 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
7910 _onTheadClick : function(e, oSelf) {
7911 // This blurs the CellEditor
7912 if(oSelf._oCellEditor) {
7913 if(oSelf._oCellEditor.fireEvent) {
7914 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
7916 // Backward compatibility
7917 else if(oSelf._oCellEditor.isActive) {
7918 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
7922 var elTarget = Ev.getTarget(e),
7923 elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(),
7924 bKeepBubbling = true;
7925 while(elTarget && (elTag != "table")) {
7930 var sType = elTarget.type.toLowerCase();
7931 if(sType == "checkbox") {
7932 bKeepBubbling = oSelf.fireEvent("theadCheckboxClickEvent",{target:elTarget,event:e});
7934 else if(sType == "radio") {
7935 bKeepBubbling = oSelf.fireEvent("theadRadioClickEvent",{target:elTarget,event:e});
7937 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
7938 if(!elTarget.disabled) {
7939 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
7942 bKeepBubbling = false;
7945 else if (elTarget.disabled){
7946 bKeepBubbling = false;
7950 bKeepBubbling = oSelf.fireEvent("theadLinkClickEvent",{target:elTarget,event:e});
7953 if(!elTarget.disabled) {
7954 bKeepBubbling = oSelf.fireEvent("theadButtonClickEvent",{target:elTarget,event:e});
7957 bKeepBubbling = false;
7961 if(Dom.hasClass(elTarget, DT.CLASS_LABEL)) {
7962 bKeepBubbling = oSelf.fireEvent("theadLabelClickEvent",{target:elTarget,event:e});
7963 // Backward compatibility
7964 bKeepBubbling = oSelf.fireEvent("headerLabelClickEvent",{target:elTarget,event:e});
7968 bKeepBubbling = oSelf.fireEvent("theadCellClickEvent",{target:elTarget,event:e});
7969 // Backward compatibility
7970 bKeepBubbling = oSelf.fireEvent("headerCellClickEvent",{target:elTarget,event:e});
7973 bKeepBubbling = oSelf.fireEvent("theadRowClickEvent",{target:elTarget,event:e});
7974 // Backward compatibility
7975 bKeepBubbling = oSelf.fireEvent("headerRowClickEvent",{target:elTarget,event:e});
7980 if(bKeepBubbling === false) {
7984 elTarget = elTarget.parentNode;
7986 elTag = elTarget.nodeName.toLowerCase();
7990 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
7994 * Handles click events on the primary TBODY element.
7996 * @method _onTbodyClick
7997 * @param e {HTMLEvent} The click event.
7998 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
8001 _onTbodyClick : function(e, oSelf) {
8002 // This blurs the CellEditor
8003 if(oSelf._oCellEditor) {
8004 if(oSelf._oCellEditor.fireEvent) {
8005 oSelf._oCellEditor.fireEvent("blurEvent", {editor: oSelf._oCellEditor});
8007 else if(oSelf._oCellEditor.isActive) {
8008 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
8012 // Fire Custom Events
8013 var elTarget = Ev.getTarget(e),
8014 elTag = elTarget.nodeName && elTarget.nodeName.toLowerCase(),
8015 bKeepBubbling = true;
8016 while(elTarget && (elTag != "table")) {
8021 var sType = elTarget.type.toLowerCase();
8022 if(sType == "checkbox") {
8023 bKeepBubbling = oSelf.fireEvent("checkboxClickEvent",{target:elTarget,event:e});
8025 else if(sType == "radio") {
8026 bKeepBubbling = oSelf.fireEvent("radioClickEvent",{target:elTarget,event:e});
8028 else if((sType == "button") || (sType == "image") || (sType == "submit") || (sType == "reset")) {
8029 if(!elTarget.disabled) {
8030 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
8033 bKeepBubbling = false;
8036 else if (elTarget.disabled){
8037 bKeepBubbling = false;
8041 bKeepBubbling = oSelf.fireEvent("linkClickEvent",{target:elTarget,event:e});
8044 if(!elTarget.disabled) {
8045 bKeepBubbling = oSelf.fireEvent("buttonClickEvent",{target:elTarget,event:e});
8048 bKeepBubbling = false;
8052 bKeepBubbling = oSelf.fireEvent("cellClickEvent",{target:elTarget,event:e});
8055 bKeepBubbling = oSelf.fireEvent("rowClickEvent",{target:elTarget,event:e});
8060 if(bKeepBubbling === false) {
8064 elTarget = elTarget.parentNode;
8066 elTag = elTarget.nodeName.toLowerCase();
8070 oSelf.fireEvent("tableClickEvent",{target:(elTarget || oSelf._elContainer),event:e});
8074 * Handles change events on SELECT elements within DataTable.
8076 * @method _onDropdownChange
8077 * @param e {HTMLEvent} The change event.
8078 * @param oSelf {YAHOO.wiget.DataTable} DataTable instance.
8081 _onDropdownChange : function(e, oSelf) {
8082 var elTarget = Ev.getTarget(e);
8083 oSelf.fireEvent("dropdownChangeEvent", {event:e, target:elTarget});
8117 /////////////////////////////////////////////////////////////////////////////
8119 // Public member variables
8121 /////////////////////////////////////////////////////////////////////////////
8123 * Returns object literal of initial configs.
8132 /////////////////////////////////////////////////////////////////////////////
8136 /////////////////////////////////////////////////////////////////////////////
8139 * Returns unique id assigned to instance, which is a useful prefix for
8140 * generating unique DOM ID strings.
8143 * @return {String} Unique ID of the DataSource instance.
8145 getId : function() {
8150 * DataSource instance name, for logging.
8153 * @return {String} Unique name of the DataSource instance.
8156 toString : function() {
8157 return "DataTable instance " + this._sId;
8161 * Returns the DataTable instance's DataSource instance.
8163 * @method getDataSource
8164 * @return {YAHOO.util.DataSource} DataSource instance.
8166 getDataSource : function() {
8167 return this._oDataSource;
8171 * Returns the DataTable instance's ColumnSet instance.
8173 * @method getColumnSet
8174 * @return {YAHOO.widget.ColumnSet} ColumnSet instance.
8176 getColumnSet : function() {
8177 return this._oColumnSet;
8181 * Returns the DataTable instance's RecordSet instance.
8183 * @method getRecordSet
8184 * @return {YAHOO.widget.RecordSet} RecordSet instance.
8186 getRecordSet : function() {
8187 return this._oRecordSet;
8191 * Returns on object literal representing the DataTable instance's current
8192 * state with the following properties:
8194 * <dt>pagination</dt>
8195 * <dd>Instance of YAHOO.widget.Paginator</dd>
8200 * <dt>sortedBy.key</dt>
8201 * <dd>{String} Key of sorted Column</dd>
8202 * <dt>sortedBy.dir</dt>
8203 * <dd>{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC</dd>
8207 * <dt>selectedRows</dt>
8208 * <dd>Array of selected rows by Record ID.</dd>
8210 * <dt>selectedCells</dt>
8211 * <dd>Selected cells as an array of object literals:
8212 * {recordId:sRecordId, columnKey:sColumnKey}</dd>
8216 * @return {Object} DataTable instance state object literal values.
8218 getState : function() {
8220 totalRecords: this.get('paginator') ? this.get('paginator').get("totalRecords") : this._oRecordSet.getLength(),
8221 pagination: this.get("paginator") ? this.get("paginator").getState() : null,
8222 sortedBy: this.get("sortedBy"),
8223 selectedRows: this.getSelectedRows(),
8224 selectedCells: this.getSelectedCells()
8273 * Returns DOM reference to the DataTable's container element.
8275 * @method getContainerEl
8276 * @return {HTMLElement} Reference to DIV element.
8278 getContainerEl : function() {
8279 return this._elContainer;
8283 * Returns DOM reference to the DataTable's TABLE element.
8285 * @method getTableEl
8286 * @return {HTMLElement} Reference to TABLE element.
8288 getTableEl : function() {
8289 return this._elTable;
8293 * Returns DOM reference to the DataTable's THEAD element.
8295 * @method getTheadEl
8296 * @return {HTMLElement} Reference to THEAD element.
8298 getTheadEl : function() {
8299 return this._elThead;
8303 * Returns DOM reference to the DataTable's primary TBODY element.
8305 * @method getTbodyEl
8306 * @return {HTMLElement} Reference to TBODY element.
8308 getTbodyEl : function() {
8309 return this._elTbody;
8313 * Returns DOM reference to the DataTable's secondary TBODY element that is
8314 * used to display messages.
8316 * @method getMsgTbodyEl
8317 * @return {HTMLElement} Reference to TBODY element.
8319 getMsgTbodyEl : function() {
8320 return this._elMsgTbody;
8324 * Returns DOM reference to the TD element within the secondary TBODY that is
8325 * used to display messages.
8327 * @method getMsgTdEl
8328 * @return {HTMLElement} Reference to TD element.
8330 getMsgTdEl : function() {
8331 return this._elMsgTd;
8335 * Returns the corresponding TR reference for a given DOM element, ID string or
8336 * page row index. If the given identifier is a child of a TR element,
8337 * then DOM tree is traversed until a parent TR element is returned, otherwise
8338 * null. Returns null if the row is not considered a primary row (i.e., row
8342 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Which row to
8343 * get: by element reference, ID string, page row index, or Record.
8344 * @return {HTMLElement} Reference to TR element, or null.
8346 getTrEl : function(row) {
8348 if(row instanceof YAHOO.widget.Record) {
8349 return document.getElementById(row.getId());
8351 // By page row index
8352 else if(lang.isNumber(row)) {
8353 var dataRows = Dom.getElementsByClassName(DT.CLASS_REC, "tr", this._elTbody);
8354 return dataRows && dataRows[row] ? dataRows[row] : null;
8356 // By ID string or element reference
8358 var elRow = (lang.isString(row)) ? document.getElementById(row) : row;
8360 // Validate HTML element
8361 if(elRow && elRow.ownerDocument == document) {
8362 // Validate TR element
8363 if(elRow.nodeName.toLowerCase() != "tr") {
8364 // Traverse up the DOM to find the corresponding TR element
8365 elRow = Dom.getAncestorByTagName(elRow,"tr");
8376 * Returns DOM reference to the first primary TR element in the DataTable page, or null.
8378 * @method getFirstTrEl
8379 * @return {HTMLElement} Reference to TR element.
8381 getFirstTrEl : function() {
8382 var allRows = this._elTbody.rows,
8385 if(this.getRecord(allRows[i])) {
8395 * Returns DOM reference to the last primary TR element in the DataTable page, or null.
8397 * @method getLastTrEl
8398 * @return {HTMLElement} Reference to last TR element.
8400 getLastTrEl : function() {
8401 var allRows = this._elTbody.rows,
8404 if(this.getRecord(allRows[i])) {
8413 * Returns DOM reference to the next TR element from the given primary TR element, or null.
8415 * @method getNextTrEl
8416 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
8417 * reference, ID string, page row index, or Record from which to get next TR element.
8418 * @param forcePrimary {Boolean} (optional) If true, will only return TR elements
8419 * that correspond to Records. Non-primary rows (such as row expansions)
8421 * @return {HTMLElement} Reference to next TR element.
8423 getNextTrEl : function(row, forcePrimary) {
8424 var nThisTrIndex = this.getTrIndex(row);
8425 if(nThisTrIndex !== null) {
8426 var allRows = this._elTbody.rows;
8428 while(nThisTrIndex < allRows.length-1) {
8429 row = allRows[nThisTrIndex+1];
8430 if(this.getRecord(row)) {
8437 if(nThisTrIndex < allRows.length-1) {
8438 return allRows[nThisTrIndex+1];
8447 * Returns DOM reference to the previous TR element from the given primary TR element, or null.
8449 * @method getPreviousTrEl
8450 * @param row {HTMLElement | String | Number | YAHOO.widget.Record} Element
8451 * reference, ID string, page row index, or Record from which to get previous TR element.
8452 * @param forcePrimary {Boolean} (optional) If true, will only return TR elements
8453 * from rothat correspond to Records. Non-primary rows (such as row expansions)
8455 * @return {HTMLElement} Reference to previous TR element.
8457 getPreviousTrEl : function(row, forcePrimary) {
8458 var nThisTrIndex = this.getTrIndex(row);
8459 if(nThisTrIndex !== null) {
8460 var allRows = this._elTbody.rows;
8463 while(nThisTrIndex > 0) {
8464 row = allRows[nThisTrIndex-1];
8465 if(this.getRecord(row)) {
8472 if(nThisTrIndex > 0) {
8473 return allRows[nThisTrIndex-1];
8483 * Workaround for IE bug where hidden or not-in-dom elements cause cellIndex
8484 * value to be incorrect.
8486 * @method getCellIndex
8487 * @param cell {HTMLElement | Object} TD element or child of a TD element, or
8488 * object literal of syntax {record:oRecord, column:oColumn}.
8489 * @return {Number} TD.cellIndex value.
8491 getCellIndex : function(cell) {
8492 cell = this.getTdEl(cell);
8496 tr = cell.parentNode,
8497 allCells = tr.childNodes,
8498 len = allCells.length;
8500 if(allCells[i] == cell) {
8506 return cell.cellIndex;
8512 * Returns DOM reference to a TD liner element.
8514 * @method getTdLinerEl
8515 * @param cell {HTMLElement | Object} TD element or child of a TD element, or
8516 * object literal of syntax {record:oRecord, column:oColumn}.
8517 * @return {HTMLElement} Reference to TD liner element.
8519 getTdLinerEl : function(cell) {
8520 var elCell = this.getTdEl(cell);
8521 return elCell.firstChild || null;
8525 * Returns DOM reference to a TD element. Returns null if the row is not
8526 * considered a primary row (i.e., row extensions).
8529 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
8530 * object literal of syntax {record:oRecord, column:oColumn}.
8531 * @return {HTMLElement} Reference to TD element.
8533 getTdEl : function(cell) {
8535 var el = Dom.get(cell);
8537 // Validate HTML element
8538 if(el && (el.ownerDocument == document)) {
8539 // Validate TD element
8540 if(el.nodeName.toLowerCase() != "td") {
8541 // Traverse up the DOM to find the corresponding TR element
8542 elCell = Dom.getAncestorByTagName(el, "td");
8548 // Make sure the TD is in this TBODY or is not in DOM
8549 // Bug 2527707 and bug 2263558
8550 if(elCell && ((elCell.parentNode.parentNode == this._elTbody) ||
8551 (elCell.parentNode.parentNode === null) ||
8552 (elCell.parentNode.parentNode.nodeType === 11))) {
8553 // Now we can return the TD element
8558 var oRecord, nColKeyIndex;
8560 if(lang.isString(cell.columnKey) && lang.isString(cell.recordId)) {
8561 oRecord = this.getRecord(cell.recordId);
8562 var oColumn = this.getColumn(cell.columnKey);
8564 nColKeyIndex = oColumn.getKeyIndex();
8568 if(cell.record && cell.column && cell.column.getKeyIndex) {
8569 oRecord = cell.record;
8570 nColKeyIndex = cell.column.getKeyIndex();
8572 var elRow = this.getTrEl(oRecord);
8573 if((nColKeyIndex !== null) && elRow && elRow.cells && elRow.cells.length > 0) {
8574 return elRow.cells[nColKeyIndex] || null;
8582 * Returns DOM reference to the first primary TD element in the DataTable page (by default),
8583 * the first TD element of the optionally given row, or null.
8585 * @method getFirstTdEl
8586 * @param row {HTMLElement} (optional) row from which to get first TD
8587 * @return {HTMLElement} Reference to TD element.
8589 getFirstTdEl : function(row) {
8590 var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getFirstTrEl();
8592 if(elRow.cells && elRow.cells.length > 0) {
8593 return elRow.cells[0];
8595 else if(elRow.childNodes && elRow.childNodes.length > 0) {
8596 return elRow.childNodes[0];
8603 * Returns DOM reference to the last primary TD element in the DataTable page (by default),
8604 * the first TD element of the optionally given row, or null.
8606 * @method getLastTdEl
8607 * @param row {HTMLElement} (optional) row from which to get first TD
8608 * @return {HTMLElement} Reference to last TD element.
8610 getLastTdEl : function(row) {
8611 var elRow = lang.isValue(row) ? this.getTrEl(row) : this.getLastTrEl();
8613 if(elRow.cells && elRow.cells.length > 0) {
8614 return elRow.cells[elRow.cells.length-1];
8616 else if(elRow.childNodes && elRow.childNodes.length > 0) {
8617 return elRow.childNodes[elRow.childNodes.length-1];
8624 * Returns DOM reference to the next TD element from the given cell, or null.
8626 * @method getNextTdEl
8627 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8628 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
8629 * @return {HTMLElement} Reference to next TD element, or null.
8631 getNextTdEl : function(cell) {
8632 var elCell = this.getTdEl(cell);
8634 var nThisTdIndex = this.getCellIndex(elCell);
8635 var elRow = this.getTrEl(elCell);
8636 if(elRow.cells && (elRow.cells.length) > 0 && (nThisTdIndex < elRow.cells.length-1)) {
8637 return elRow.cells[nThisTdIndex+1];
8639 else if(elRow.childNodes && (elRow.childNodes.length) > 0 && (nThisTdIndex < elRow.childNodes.length-1)) {
8640 return elRow.childNodes[nThisTdIndex+1];
8643 var elNextRow = this.getNextTrEl(elRow);
8645 return elNextRow.cells[0];
8653 * Returns DOM reference to the previous TD element from the given cell, or null.
8655 * @method getPreviousTdEl
8656 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8657 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
8658 * @return {HTMLElement} Reference to previous TD element, or null.
8660 getPreviousTdEl : function(cell) {
8661 var elCell = this.getTdEl(cell);
8663 var nThisTdIndex = this.getCellIndex(elCell);
8664 var elRow = this.getTrEl(elCell);
8665 if(nThisTdIndex > 0) {
8666 if(elRow.cells && elRow.cells.length > 0) {
8667 return elRow.cells[nThisTdIndex-1];
8669 else if(elRow.childNodes && elRow.childNodes.length > 0) {
8670 return elRow.childNodes[nThisTdIndex-1];
8674 var elPreviousRow = this.getPreviousTrEl(elRow);
8676 return this.getLastTdEl(elPreviousRow);
8684 * Returns DOM reference to the above TD element from the given cell, or null.
8686 * @method getAboveTdEl
8687 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8688 * object literal of syntax {record:oRecord, column:oColumn} from which to get next TD element.
8689 * @param forcePrimary {Boolean} (optional) If true, will only return TD elements
8690 * from rows that correspond to Records. Non-primary rows (such as row expansions)
8692 * @return {HTMLElement} Reference to above TD element, or null.
8694 getAboveTdEl : function(cell, forcePrimary) {
8695 var elCell = this.getTdEl(cell);
8697 var elPreviousRow = this.getPreviousTrEl(elCell, forcePrimary);
8698 if(elPreviousRow ) {
8699 var cellIndex = this.getCellIndex(elCell);
8700 if(elPreviousRow.cells && elPreviousRow.cells.length > 0) {
8701 return elPreviousRow.cells[cellIndex] ? elPreviousRow.cells[cellIndex] : null;
8703 else if(elPreviousRow.childNodes && elPreviousRow.childNodes.length > 0) {
8704 return elPreviousRow.childNodes[cellIndex] ? elPreviousRow.childNodes[cellIndex] : null;
8712 * Returns DOM reference to the below TD element from the given cell, or null.
8714 * @method getBelowTdEl
8715 * @param cell {HTMLElement | String | Object} DOM element reference or string ID, or
8716 * object literal of syntax {record:oRecord, column:oColumn} from which to get previous TD element.
8717 * @param forcePrimary {Boolean} (optional) If true, will only return TD elements
8718 * from rows that correspond to Records. Non-primary rows (such as row expansions)
8720 * @return {HTMLElement} Reference to below TD element, or null.
8722 getBelowTdEl : function(cell, forcePrimary) {
8723 var elCell = this.getTdEl(cell);
8725 var elNextRow = this.getNextTrEl(elCell, forcePrimary);
8727 var cellIndex = this.getCellIndex(elCell);
8728 if(elNextRow.cells && elNextRow.cells.length > 0) {
8729 return elNextRow.cells[cellIndex] ? elNextRow.cells[cellIndex] : null;
8731 else if(elNextRow.childNodes && elNextRow.childNodes.length > 0) {
8732 return elNextRow.childNodes[cellIndex] ? elNextRow.childNodes[cellIndex] : null;
8740 * Returns DOM reference to a TH liner element. Needed to normalize for resizeable
8741 * Columns, which have an additional resizer liner DIV element between the TH
8742 * element and the liner DIV element.
8744 * @method getThLinerEl
8745 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
8746 * DOM element reference, or string ID.
8747 * @return {HTMLElement} Reference to TH liner element.
8749 getThLinerEl : function(theadCell) {
8750 var oColumn = this.getColumn(theadCell);
8751 return (oColumn) ? oColumn.getThLinerEl() : null;
8755 * Returns DOM reference to a TH element.
8758 * @param theadCell {YAHOO.widget.Column | HTMLElement | String} Column instance,
8759 * DOM element reference, or string ID.
8760 * @return {HTMLElement} Reference to TH element.
8762 getThEl : function(theadCell) {
8765 // Validate Column instance
8766 if(theadCell instanceof YAHOO.widget.Column) {
8767 var oColumn = theadCell;
8768 elTh = oColumn.getThEl();
8773 // Validate HTML element
8775 var el = Dom.get(theadCell);
8777 if(el && (el.ownerDocument == document)) {
8778 // Validate TH element
8779 if(el.nodeName.toLowerCase() != "th") {
8780 // Traverse up the DOM to find the corresponding TR element
8781 elTh = Dom.getAncestorByTagName(el,"th");
8795 * Returns the page row index of given primary row. Returns null if the row is not on the
8796 * current DataTable page, or if row is not considered a primary row (i.e., row
8799 * @method getTrIndex
8800 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} DOM or ID
8801 * string reference to an element within the DataTable page, a Record instance,
8802 * or a Record's RecordSet index.
8803 * @return {Number} Page row index, or null if data row does not exist or is not on current page.
8805 getTrIndex : function(row) {
8806 var record = this.getRecord(row),
8807 index = this.getRecordIndex(record),
8810 tr = this.getTrEl(record);
8812 return tr.sectionRowIndex;
8815 var oPaginator = this.get("paginator");
8817 return oPaginator.get('recordOffset') + index;
8875 * Loads new data. Convenience method that calls DataSource's sendRequest()
8876 * method under the hood.
8879 * @param oConfig {object} Optional configuration parameters:
8882 * <dt>request</dt><dd>Pass in a new request, or initialRequest is used.</dd>
8883 * <dt>callback</dt><dd>Pass in DataSource sendRequest() callback object, or the following is used:
8885 * <dt>success</dt><dd>datatable.onDataReturnInitializeTable</dd>
8886 * <dt>failure</dt><dd>datatable.onDataReturnInitializeTable</dd>
8887 * <dt>scope</dt><dd>datatable</dd>
8888 * <dt>argument</dt><dd>datatable.getState()</dd>
8891 * <dt>datasource</dt><dd>Pass in a new DataSource instance to override the current DataSource for this transaction.</dd>
8894 load : function(oConfig) {
8895 oConfig = oConfig || {};
8897 (oConfig.datasource || this._oDataSource).sendRequest(oConfig.request || this.get("initialRequest"), oConfig.callback || {
8898 success: this.onDataReturnInitializeTable,
8899 failure: this.onDataReturnInitializeTable,
8901 argument: this.getState()
8906 * Resets a RecordSet with the given data and populates the page view
8907 * with the new data. Any previous data, and selection and sort states are
8908 * cleared. New data should be added as a separate step.
8910 * @method initializeTable
8912 initializeTable : function() {
8916 // Clear the RecordSet
8917 this._oRecordSet.reset();
8919 // Clear the Paginator's totalRecords if paginating
8920 var pag = this.get('paginator');
8922 pag.set('totalRecords',0);
8926 this._unselectAllTrEls();
8927 this._unselectAllTdEls();
8928 this._aSelections = null;
8929 this._oAnchorRecord = null;
8930 this._oAnchorCell = null;
8933 this.set("sortedBy", null);
8937 * Internal wrapper calls run() on render Chain instance.
8939 * @method _runRenderChain
8942 _runRenderChain : function() {
8943 this._oChainRender.run();
8947 * Returns array of Records for current view. For example, if paginated, it
8948 * returns the subset of Records for current page.
8950 * @method _getViewRecords
8952 * @return {Array} Array of Records to display in current view.
8954 _getViewRecords : function() {
8955 // Paginator is enabled, show a subset of Records
8956 var oPaginator = this.get('paginator');
8958 return this._oRecordSet.getRecords(
8959 oPaginator.getStartIndex(),
8960 oPaginator.getRowsPerPage());
8962 // Not paginated, show all records
8964 return this._oRecordSet.getRecords();
8970 * Renders the view with existing Records from the RecordSet while
8971 * maintaining sort, pagination, and selection states. For performance, reuses
8972 * existing DOM elements when possible while deleting extraneous elements.
8976 render : function() {
8977 //YAHOO.example.Performance.trialStart = new Date();
8979 this._oChainRender.stop();
8981 this.fireEvent("beforeRenderEvent");
8984 allRecords = this._getViewRecords();
8987 // From the top, update in-place existing rows, so as to reuse DOM elements
8988 var elTbody = this._elTbody,
8989 loopN = this.get("renderLoopSize"),
8990 nRecordsLength = allRecords.length;
8993 if(nRecordsLength > 0) {
8994 elTbody.style.display = "none";
8995 while(elTbody.lastChild) {
8996 elTbody.removeChild(elTbody.lastChild);
8998 elTbody.style.display = "";
9000 // Set up the loop Chain to render rows
9001 this._oChainRender.add({
9002 method: function(oArg) {
9003 if((this instanceof DT) && this._sId) {
9004 var i = oArg.nCurrentRecord,
9005 endRecordIndex = ((oArg.nCurrentRecord+oArg.nLoopLength) > nRecordsLength) ?
9006 nRecordsLength : (oArg.nCurrentRecord+oArg.nLoopLength),
9009 elTbody.style.display = "none";
9011 for(; i<endRecordIndex; i++) {
9012 elRow = Dom.get(allRecords[i].getId());
9013 elRow = elRow || this._addTrEl(allRecords[i]);
9014 nextSibling = elTbody.childNodes[i] || null;
9015 elTbody.insertBefore(elRow, nextSibling);
9017 elTbody.style.display = "";
9019 // Set up for the next loop
9020 oArg.nCurrentRecord = i;
9024 iterations: (loopN > 0) ? Math.ceil(nRecordsLength/loopN) : 1,
9026 nCurrentRecord: 0,//nRecordsLength-1, // Start at first Record
9027 nLoopLength: (loopN > 0) ? loopN : nRecordsLength
9029 timeout: (loopN > 0) ? 0 : -1
9032 // Post-render tasks
9033 this._oChainRender.add({
9034 method: function(oArg) {
9035 if((this instanceof DT) && this._sId) {
9036 while(elTbody.rows.length > nRecordsLength) {
9037 elTbody.removeChild(elTbody.lastChild);
9039 this._setFirstRow();
9041 this._setRowStripes();
9042 this._setSelections();
9046 timeout: (loopN > 0) ? 0 : -1
9050 // Table has no rows
9052 // Set up the loop Chain to delete rows
9053 var nTotal = elTbody.rows.length;
9055 this._oChainRender.add({
9056 method: function(oArg) {
9057 if((this instanceof DT) && this._sId) {
9058 var i = oArg.nCurrent,
9059 loopN = oArg.nLoopLength,
9060 nIterEnd = (i - loopN < 0) ? 0 : i - loopN;
9062 elTbody.style.display = "none";
9064 for(; i>nIterEnd; i--) {
9065 elTbody.deleteRow(-1);
9067 elTbody.style.display = "";
9069 // Set up for the next loop
9074 iterations: (loopN > 0) ? Math.ceil(nTotal/loopN) : 1,
9077 nLoopLength: (loopN > 0) ? loopN : nTotal
9079 timeout: (loopN > 0) ? 0 : -1
9083 this._runRenderChain();
9087 * Disables DataTable UI.
9091 disable : function() {
9092 this._disabled = true;
9093 var elTable = this._elTable;
9094 var elMask = this._elMask;
9095 elMask.style.width = elTable.offsetWidth + "px";
9096 elMask.style.height = elTable.offsetHeight + "px";
9097 elMask.style.left = elTable.offsetLeft + "px";
9098 elMask.style.display = "";
9099 this.fireEvent("disableEvent");
9103 * Undisables DataTable UI.
9107 undisable : function() {
9108 this._disabled = false;
9109 this._elMask.style.display = "none";
9110 this.fireEvent("undisableEvent");
9114 * Returns disabled state.
9116 * @method isDisabled
9117 * @return {Boolean} True if UI is disabled, otherwise false
9119 isDisabled : function() {
9120 return this._disabled;
9124 * Nulls out the entire DataTable instance and related objects, removes attached
9125 * event listeners, and clears out DOM elements inside the container. After
9126 * calling this method, the instance reference should be expliclitly nulled by
9127 * implementer, as in myDataTable = null. Use with caution!
9131 destroy : function() {
9133 var instanceName = this.toString();
9135 this._oChainRender.stop();
9137 // Destroy ColumnDD and ColumnResizers
9138 this._destroyColumnHelpers();
9140 // Destroy all CellEditors
9142 for(var i=0, len=this._oColumnSet.flat.length; i<len; i++) {
9143 oCellEditor = this._oColumnSet.flat[i].editor;
9144 if(oCellEditor && oCellEditor.destroy) {
9145 oCellEditor.destroy();
9146 this._oColumnSet.flat[i].editor = null;
9150 // Destroy Paginator
9151 this._destroyPaginator();
9153 // Unhook custom events
9154 this._oRecordSet.unsubscribeAll();
9155 this.unsubscribeAll();
9157 // Unhook DOM events
9158 Ev.removeListener(document, "click", this._onDocumentClick);
9160 // Clear out the container
9161 this._destroyContainerEl(this._elContainer);
9164 for(var param in this) {
9165 if(lang.hasOwnProperty(this, param)) {
9170 // Clean up static values
9171 DT._nCurrentCount--;
9173 if(DT._nCurrentCount < 1) {
9174 if(DT._elDynStyleNode) {
9175 document.getElementsByTagName('head')[0].removeChild(DT._elDynStyleNode);
9176 DT._elDynStyleNode = null;
9183 * Displays message within secondary TBODY.
9185 * @method showTableMessage
9186 * @param sHTML {HTML} (optional) Value for innerHTML.
9187 * @param sClassName {String} (optional) Classname.
9189 showTableMessage : function(sHTML, sClassName) {
9190 var elCell = this._elMsgTd;
9191 if(lang.isString(sHTML)) {
9192 elCell.firstChild.innerHTML = sHTML;
9194 if(lang.isString(sClassName)) {
9195 elCell.className = sClassName;
9198 this._elMsgTbody.style.display = "";
9200 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
9204 * Hides secondary TBODY.
9206 * @method hideTableMessage
9208 hideTableMessage : function() {
9209 if(this._elMsgTbody.style.display != "none") {
9210 this._elMsgTbody.style.display = "none";
9211 this._elMsgTbody.parentNode.style.width = "";
9212 this.fireEvent("tableMsgHideEvent");
9217 * Brings focus to the TBODY element. Alias to focusTbodyEl.
9221 focus : function() {
9222 this.focusTbodyEl();
9226 * Brings focus to the THEAD element.
9228 * @method focusTheadEl
9230 focusTheadEl : function() {
9231 this._focusEl(this._elThead);
9235 * Brings focus to the TBODY element.
9237 * @method focusTbodyEl
9239 focusTbodyEl : function() {
9240 this._focusEl(this._elTbody);
9244 * Setting display:none on DataTable or any parent may impact width validations.
9245 * After setting display back to "", implementers should call this method to
9246 * manually perform those validations.
9250 onShow : function() {
9251 this.validateColumnWidths();
9253 for(var allKeys = this._oColumnSet.keys, i=0, len=allKeys.length, col; i<len; i++) {
9255 if(col._ddResizer) {
9256 col._ddResizer.resetResizerEl();
9327 // RECORDSET FUNCTIONS
9330 * Returns Record index for given TR element or page row index.
9332 * @method getRecordIndex
9333 * @param row {YAHOO.widget.Record | HTMLElement | Number} Record instance, TR
9334 * element reference or page row index.
9335 * @return {Number} Record's RecordSet index, or null.
9337 getRecordIndex : function(row) {
9340 if(!lang.isNumber(row)) {
9342 if(row instanceof YAHOO.widget.Record) {
9343 return this._oRecordSet.getRecordIndex(row);
9345 // By element reference
9347 // Find the TR element
9348 var el = this.getTrEl(row);
9350 nTrIndex = el.sectionRowIndex;
9354 // By page row index
9359 if(lang.isNumber(nTrIndex)) {
9360 var oPaginator = this.get("paginator");
9362 return oPaginator.get('recordOffset') + nTrIndex;
9373 * For the given identifier, returns the associated Record instance.
9376 * @param row {HTMLElement | Number | String} DOM reference to a TR element (or
9377 * child of a TR element), RecordSet position index, or Record ID.
9378 * @return {YAHOO.widget.Record} Record instance.
9380 getRecord : function(row) {
9381 var oRecord = this._oRecordSet.getRecord(row);
9384 // Validate TR element
9385 var elRow = this.getTrEl(row);
9387 oRecord = this._oRecordSet.getRecord(elRow.id);
9391 if(oRecord instanceof YAHOO.widget.Record) {
9392 return this._oRecordSet.getRecord(oRecord);
9447 * For the given identifier, returns the associated Column instance. Note: For
9448 * getting Columns by Column ID string, please use the method getColumnById().
9451 * @param column {HTMLElement | String | Number} TH/TD element (or child of a
9452 * TH/TD element), a Column key, or a ColumnSet key index.
9453 * @return {YAHOO.widget.Column} Column instance.
9455 getColumn : function(column) {
9456 var oColumn = this._oColumnSet.getColumn(column);
9459 // Validate TD element
9460 var elCell = this.getTdEl(column);
9462 oColumn = this._oColumnSet.getColumn(this.getCellIndex(elCell));
9464 // Validate TH element
9466 elCell = this.getThEl(column);
9469 var allColumns = this._oColumnSet.flat;
9470 for(var i=0, len=allColumns.length; i<len; i++) {
9471 if(allColumns[i].getThEl().id === elCell.id) {
9472 oColumn = allColumns[i];
9484 * For the given Column ID, returns the associated Column instance. Note: For
9485 * getting Columns by key, please use the method getColumn().
9487 * @method getColumnById
9488 * @param column {String} Column ID string.
9489 * @return {YAHOO.widget.Column} Column instance.
9491 getColumnById : function(column) {
9492 return this._oColumnSet.getColumnById(column);
9496 * For the given Column instance, returns next direction to sort.
9498 * @method getColumnSortDir
9499 * @param oColumn {YAHOO.widget.Column} Column instance.
9500 * @param oSortedBy {Object} (optional) Specify the state, or use current state.
9501 * @return {String} YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTableCLASS_DESC.
9503 getColumnSortDir : function(oColumn, oSortedBy) {
9504 // Backward compatibility
9505 if(oColumn.sortOptions && oColumn.sortOptions.defaultDir) {
9506 if(oColumn.sortOptions.defaultDir == "asc") {
9507 oColumn.sortOptions.defaultDir = DT.CLASS_ASC;
9509 else if (oColumn.sortOptions.defaultDir == "desc") {
9510 oColumn.sortOptions.defaultDir = DT.CLASS_DESC;
9514 // What is the Column's default sort direction?
9515 var sortDir = (oColumn.sortOptions && oColumn.sortOptions.defaultDir) ? oColumn.sortOptions.defaultDir : DT.CLASS_ASC;
9517 // Is the Column currently sorted?
9518 var bSorted = false;
9519 oSortedBy = oSortedBy || this.get("sortedBy");
9520 if(oSortedBy && (oSortedBy.key === oColumn.key)) {
9523 sortDir = (oSortedBy.dir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
9526 sortDir = (sortDir === DT.CLASS_ASC) ? DT.CLASS_DESC : DT.CLASS_ASC;
9533 * Overridable method gives implementers a hook to show loading message before
9536 * @method doBeforeSortColumn
9537 * @param oColumn {YAHOO.widget.Column} Column instance.
9538 * @param sSortDir {String} YAHOO.widget.DataTable.CLASS_ASC or
9539 * YAHOO.widget.DataTable.CLASS_DESC.
9540 * @return {Boolean} Return true to continue sorting Column.
9542 doBeforeSortColumn : function(oColumn, sSortDir) {
9543 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
9548 * Sorts given Column. If "dynamicData" is true, current selections are purged before
9549 * a request is sent to the DataSource for data for the new state (using the
9550 * request returned by "generateRequest()").
9552 * @method sortColumn
9553 * @param oColumn {YAHOO.widget.Column} Column instance.
9554 * @param sDir {String} (Optional) YAHOO.widget.DataTable.CLASS_ASC or
9555 * YAHOO.widget.DataTable.CLASS_DESC
9557 sortColumn : function(oColumn, sDir) {
9558 if(oColumn && (oColumn instanceof YAHOO.widget.Column)) {
9559 if(!oColumn.sortable) {
9560 Dom.addClass(this.getThEl(oColumn), DT.CLASS_SORTABLE);
9563 // Validate given direction
9564 if(sDir && (sDir !== DT.CLASS_ASC) && (sDir !== DT.CLASS_DESC)) {
9569 var sSortDir = sDir || this.getColumnSortDir(oColumn);
9571 // Is the Column currently sorted?
9572 var oSortedBy = this.get("sortedBy") || {};
9573 var bSorted = (oSortedBy.key === oColumn.key) ? true : false;
9575 var ok = this.doBeforeSortColumn(oColumn, sSortDir);
9578 if(this.get("dynamicData")) {
9579 // Get current state
9580 var oState = this.getState();
9582 // Reset record offset, if paginated
9583 if(oState.pagination) {
9584 oState.pagination.recordOffset = 0;
9587 // Update sortedBy to new values
9593 // Get the request for the new state
9594 var request = this.get("generateRequest")(oState, this);
9597 this.unselectAllRows();
9598 this.unselectAllCells();
9600 // Send request for new data
9602 success : this.onDataReturnSetRows,
9603 failure : this.onDataReturnSetRows,
9604 argument : oState, // Pass along the new state to the callback
9607 this._oDataSource.sendRequest(request, callback);
9611 // Is there a custom sort handler function defined?
9612 var sortFnc = (oColumn.sortOptions && lang.isFunction(oColumn.sortOptions.sortFunction)) ?
9613 // Custom sort function
9614 oColumn.sortOptions.sortFunction : null;
9617 if(!bSorted || sDir || sortFnc) {
9618 // Default sort function if necessary
9619 sortFnc = sortFnc || this.get("sortFunction");
9620 // Get the field to sort
9621 var sField = (oColumn.sortOptions && oColumn.sortOptions.field) ? oColumn.sortOptions.field : oColumn.field;
9624 this._oRecordSet.sortRecords(sortFnc, ((sSortDir == DT.CLASS_DESC) ? true : false), sField);
9626 // Just reverse the Records
9628 this._oRecordSet.reverseRecords();
9631 // Reset to first page if paginated
9632 var oPaginator = this.get('paginator');
9634 // Set page silently, so as not to fire change event.
9635 oPaginator.setPage(1,true);
9638 // Update UI via sortedBy
9640 this.set("sortedBy", {key:oColumn.key, dir:sSortDir, column:oColumn});
9643 this.fireEvent("columnSortEvent",{column:oColumn,dir:sSortDir});
9650 * Sets given Column to given pixel width. If new width is less than minimum
9651 * width, sets to minimum width. Updates oColumn.width value.
9653 * @method setColumnWidth
9654 * @param oColumn {YAHOO.widget.Column} Column instance.
9655 * @param nWidth {Number} New width in pixels. A null value auto-sizes Column,
9656 * subject to minWidth and maxAutoWidth validations.
9658 setColumnWidth : function(oColumn, nWidth) {
9659 if(!(oColumn instanceof YAHOO.widget.Column)) {
9660 oColumn = this.getColumn(oColumn);
9663 // Validate new width against minimum width
9664 if(lang.isNumber(nWidth)) {
9665 // This is why we must require a Number... :-|
9666 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
9669 oColumn.width = nWidth;
9671 // Resize the DOM elements
9672 this._setColumnWidth(oColumn, nWidth+"px");
9674 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
9676 // Unsets a width to auto-size
9677 else if(nWidth === null) {
9679 oColumn.width = nWidth;
9681 // Resize the DOM elements
9682 this._setColumnWidth(oColumn, "auto");
9683 this.validateColumnWidths(oColumn);
9684 this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
9687 // Bug 2339454: resize then sort misaligment
9688 this._clearTrTemplateEl();
9695 * Sets liner DIV elements of given Column to given width. When value should be
9696 * auto-calculated to fit content overflow is set to visible, otherwise overflow
9697 * is set to hidden. No validations against minimum width and no updating
9698 * Column.width value.
9700 * @method _setColumnWidth
9701 * @param oColumn {YAHOO.widget.Column} Column instance.
9702 * @param sWidth {String} New width value.
9703 * @param sOverflow {String} Should be "hidden" when Column width is explicitly
9704 * being set to a value, but should be "visible" when Column is meant to auto-fit content.
9707 _setColumnWidth : function(oColumn, sWidth, sOverflow) {
9708 if(oColumn && (oColumn.getKeyIndex() !== null)) {
9709 sOverflow = sOverflow || (((sWidth === '') || (sWidth === 'auto')) ? 'visible' : 'hidden');
9711 // Dynamic style algorithm
9712 if(!DT._bDynStylesFallback) {
9713 this._setColumnWidthDynStyles(oColumn, sWidth, sOverflow);
9715 // Dynamic function algorithm
9717 this._setColumnWidthDynFunction(oColumn, sWidth, sOverflow);
9725 * Updates width of a Column's liner DIV elements by dynamically creating a
9726 * STYLE node and writing and updating CSS style rules to it. If this fails during
9727 * runtime, the fallback method _setColumnWidthDynFunction() will be called.
9728 * Notes: This technique is not performant in IE6. IE7 crashes if DataTable is
9729 * nested within another TABLE element. For these cases, it is recommended to
9730 * use the method _setColumnWidthDynFunction by setting _bDynStylesFallback to TRUE.
9732 * @method _setColumnWidthDynStyles
9733 * @param oColumn {YAHOO.widget.Column} Column instance.
9734 * @param sWidth {String} New width value.
9737 _setColumnWidthDynStyles : function(oColumn, sWidth, sOverflow) {
9738 var s = DT._elDynStyleNode,
9741 // Create a new STYLE node
9743 s = document.createElement('style');
9744 s.type = 'text/css';
9745 s = document.getElementsByTagName('head').item(0).appendChild(s);
9746 DT._elDynStyleNode = s;
9749 // We have a STYLE node to update
9751 // Use unique classname for this Column instance as a hook for resizing
9752 var sClassname = "." + this.getId() + "-col-" + oColumn.getSanitizedKey() + " ." + DT.CLASS_LINER;
9754 // Hide for performance
9756 this._elTbody.style.display = 'none';
9759 rule = DT._oDynStyles[sClassname];
9761 // The Column does not yet have a rule
9763 if(s.styleSheet && s.styleSheet.addRule) {
9764 s.styleSheet.addRule(sClassname,"overflow:"+sOverflow);
9765 s.styleSheet.addRule(sClassname,'width:'+sWidth);
9766 rule = s.styleSheet.rules[s.styleSheet.rules.length-1];
9767 DT._oDynStyles[sClassname] = rule;
9769 else if(s.sheet && s.sheet.insertRule) {
9770 s.sheet.insertRule(sClassname+" {overflow:"+sOverflow+";width:"+sWidth+";}",s.sheet.cssRules.length);
9771 rule = s.sheet.cssRules[s.sheet.cssRules.length-1];
9772 DT._oDynStyles[sClassname] = rule;
9775 // We have a rule to update
9777 rule.style.overflow = sOverflow;
9778 rule.style.width = sWidth;
9783 this._elTbody.style.display = '';
9787 // That was not a success, we must call the fallback routine
9789 DT._bDynStylesFallback = true;
9790 this._setColumnWidthDynFunction(oColumn, sWidth);
9795 * Updates width of a Column's liner DIV elements by dynamically creating a
9796 * function to update all element style properties in one pass. Note: This
9797 * technique is not supported in sandboxed environments that prohibit EVALs.
9799 * @method _setColumnWidthDynFunction
9800 * @param oColumn {YAHOO.widget.Column} Column instance.
9801 * @param sWidth {String} New width value.
9804 _setColumnWidthDynFunction : function(oColumn, sWidth, sOverflow) {
9805 // TODO: why is this here?
9806 if(sWidth == 'auto') {
9810 // Create one function for each value of rows.length
9811 var rowslen = this._elTbody ? this._elTbody.rows.length : 0;
9813 // Dynamically create the function
9814 if (!this._aDynFunctions[rowslen]) {
9816 //Compile a custom function to do all the liner div width
9817 //assignments at the same time. A unique function is required
9818 //for each unique number of rows in _elTbody. This will
9819 //result in a function declaration like:
9820 //function (oColumn,sWidth,sOverflow) {
9821 // var colIdx = oColumn.getKeyIndex();
9822 // oColumn.getThLinerEl().style.overflow =
9823 // this._elTbody.rows[0].cells[colIdx].firstChild.style.overflow =
9824 // this._elTbody.rows[1].cells[colIdx].firstChild.style.overflow =
9825 // ... (for all row indices in this._elTbody.rows.length - 1)
9826 // this._elTbody.rows[99].cells[colIdx].firstChild.style.overflow =
9828 // oColumn.getThLinerEl().style.width =
9829 // this._elTbody.rows[0].cells[colIdx].firstChild.style.width =
9830 // this._elTbody.rows[1].cells[colIdx].firstChild.style.width =
9831 // ... (for all row indices in this._elTbody.rows.length - 1)
9832 // this._elTbody.rows[99].cells[colIdx].firstChild.style.width =
9838 'var colIdx=oColumn.getKeyIndex();',
9839 'oColumn.getThLinerEl().style.overflow='
9841 for (i=rowslen-1, j=2; i >= 0; --i) {
9842 resizerDef[j++] = 'this._elTbody.rows[';
9843 resizerDef[j++] = i;
9844 resizerDef[j++] = '].cells[colIdx].firstChild.style.overflow=';
9846 resizerDef[j] = 'sOverflow;';
9847 resizerDef[j+1] = 'oColumn.getThLinerEl().style.width=';
9848 for (i=rowslen-1, k=j+2; i >= 0; --i) {
9849 resizerDef[k++] = 'this._elTbody.rows[';
9850 resizerDef[k++] = i;
9851 resizerDef[k++] = '].cells[colIdx].firstChild.style.width=';
9853 resizerDef[k] = 'sWidth;';
9854 this._aDynFunctions[rowslen] =
9855 new Function('oColumn','sWidth','sOverflow',resizerDef.join(''));
9858 // Get the function to execute
9859 var resizerFn = this._aDynFunctions[rowslen];
9861 // TODO: Hide TBODY for performance in _setColumnWidthDynFunction?
9863 resizerFn.call(this,oColumn,sWidth,sOverflow);
9868 * For one or all Columns, when Column is not hidden, width is not set, and minWidth
9869 * and/or maxAutoWidth is set, validates auto-width against minWidth and maxAutoWidth.
9871 * @method validateColumnWidths
9872 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
9874 validateColumnWidths : function(oColumn) {
9875 var elColgroup = this._elColgroup;
9876 var elColgroupClone = elColgroup.cloneNode(true);
9877 var bNeedsValidation = false;
9878 var allKeys = this._oColumnSet.keys;
9880 // Validate just one Column's minWidth and/or maxAutoWidth
9881 if(oColumn && !oColumn.hidden && !oColumn.width && (oColumn.getKeyIndex() !== null)) {
9882 elThLiner = oColumn.getThLinerEl();
9883 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
9884 elColgroupClone.childNodes[oColumn.getKeyIndex()].style.width =
9886 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
9887 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
9888 bNeedsValidation = true;
9890 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
9891 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
9894 // Validate all Columns
9896 for(var i=0, len=allKeys.length; i<len; i++) {
9897 oColumn = allKeys[i];
9898 if(!oColumn.hidden && !oColumn.width) {
9899 elThLiner = oColumn.getThLinerEl();
9900 if((oColumn.minWidth > 0) && (elThLiner.offsetWidth < oColumn.minWidth)) {
9901 elColgroupClone.childNodes[i].style.width =
9903 (parseInt(Dom.getStyle(elThLiner,"paddingLeft"),10)|0) +
9904 (parseInt(Dom.getStyle(elThLiner,"paddingRight"),10)|0) + "px";
9905 bNeedsValidation = true;
9907 else if((oColumn.maxAutoWidth > 0) && (elThLiner.offsetWidth > oColumn.maxAutoWidth)) {
9908 this._setColumnWidth(oColumn, oColumn.maxAutoWidth+"px", "hidden");
9913 if(bNeedsValidation) {
9914 elColgroup.parentNode.replaceChild(elColgroupClone, elColgroup);
9915 this._elColgroup = elColgroupClone;
9922 * @method _clearMinWidth
9923 * @param oColumn {YAHOO.widget.Column} Which Column.
9926 _clearMinWidth : function(oColumn) {
9927 if(oColumn.getKeyIndex() !== null) {
9928 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = '';
9933 * Restores minWidth.
9935 * @method _restoreMinWidth
9936 * @param oColumn {YAHOO.widget.Column} Which Column.
9939 _restoreMinWidth : function(oColumn) {
9940 if(oColumn.minWidth && (oColumn.getKeyIndex() !== null)) {
9941 this._elColgroup.childNodes[oColumn.getKeyIndex()].style.width = oColumn.minWidth + 'px';
9946 * Hides given Column. NOTE: You cannot hide/show nested Columns. You can only
9947 * hide/show non-nested Columns, and top-level parent Columns (which will
9948 * hide/show all children Columns).
9950 * @method hideColumn
9951 * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column
9952 * instance, TH/TD element (or child of a TH/TD element), a Column key, or a
9953 * ColumnSet key index.
9955 hideColumn : function(oColumn) {
9956 if(!(oColumn instanceof YAHOO.widget.Column)) {
9957 oColumn = this.getColumn(oColumn);
9959 // Only top-level Columns can get hidden due to issues in FF2 and SF3
9960 if(oColumn && !oColumn.hidden && oColumn.getTreeIndex() !== null) {
9962 var allrows = this.getTbodyEl().rows;
9963 var l = allrows.length;
9964 var allDescendants = this._oColumnSet.getDescendants(oColumn);
9966 // Hide each nested Column
9967 for(var i=0, len=allDescendants.length; i<len; i++) {
9968 var thisColumn = allDescendants[i];
9969 thisColumn.hidden = true;
9971 // Style the head cell
9972 Dom.addClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
9974 // Does this Column have body cells?
9975 var thisKeyIndex = thisColumn.getKeyIndex();
9976 if(thisKeyIndex !== null) {
9978 this._clearMinWidth(oColumn);
9980 // Style the body cells
9981 for(var j=0;j<l;j++) {
9982 Dom.addClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
9986 this.fireEvent("columnHideEvent",{column:thisColumn});
9989 this._repaintOpera();
9990 this._clearTrTemplateEl();
9997 * Shows given Column. NOTE: You cannot hide/show nested Columns. You can only
9998 * hide/show non-nested Columns, and top-level parent Columns (which will
9999 * hide/show all children Columns).
10001 * @method showColumn
10002 * @param oColumn {YAHOO.widget.Column | HTMLElement | String | Number} Column
10003 * instance, TH/TD element (or child of a TH/TD element), a Column key, or a
10004 * ColumnSet key index.
10006 showColumn : function(oColumn) {
10007 if(!(oColumn instanceof YAHOO.widget.Column)) {
10008 oColumn = this.getColumn(oColumn);
10010 // Only top-level Columns can get hidden
10011 if(oColumn && oColumn.hidden && (oColumn.getTreeIndex() !== null)) {
10012 var allrows = this.getTbodyEl().rows;
10013 var l = allrows.length;
10014 var allDescendants = this._oColumnSet.getDescendants(oColumn);
10016 // Show each nested Column
10017 for(var i=0, len=allDescendants.length; i<len; i++) {
10018 var thisColumn = allDescendants[i];
10019 thisColumn.hidden = false;
10021 // Unstyle the head cell
10022 Dom.removeClass(thisColumn.getThEl(), DT.CLASS_HIDDEN);
10024 // Does this Column have body cells?
10025 var thisKeyIndex = thisColumn.getKeyIndex();
10026 if(thisKeyIndex !== null) {
10027 // Restore minWidth
10028 this._restoreMinWidth(oColumn);
10031 // Unstyle the body cells
10032 for(var j=0;j<l;j++) {
10033 Dom.removeClass(allrows[j].cells[thisKeyIndex],DT.CLASS_HIDDEN);
10037 this.fireEvent("columnShowEvent",{column:thisColumn});
10039 this._clearTrTemplateEl();
10046 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
10047 * non-nested Columns, and top-level parent Columns (which will remove all
10048 * children Columns).
10050 * @method removeColumn
10051 * @param oColumn {YAHOO.widget.Column} Column instance.
10052 * @return oColumn {YAHOO.widget.Column} Removed Column instance.
10054 removeColumn : function(oColumn) {
10056 if(!(oColumn instanceof YAHOO.widget.Column)) {
10057 oColumn = this.getColumn(oColumn);
10060 var nColTreeIndex = oColumn.getTreeIndex();
10061 if(nColTreeIndex !== null) {
10062 // Which key index(es)
10064 aKeyIndexes = oColumn.getKeyIndex();
10065 // Must be a parent Column
10066 if(aKeyIndexes === null) {
10067 var descKeyIndexes = [];
10068 var allDescendants = this._oColumnSet.getDescendants(oColumn);
10069 for(i=0, len=allDescendants.length; i<len; i++) {
10070 // Is this descendant a key Column?
10071 var thisKey = allDescendants[i].getKeyIndex();
10072 if(thisKey !== null) {
10073 descKeyIndexes[descKeyIndexes.length] = thisKey;
10076 if(descKeyIndexes.length > 0) {
10077 aKeyIndexes = descKeyIndexes;
10080 // Must be a key Column
10082 aKeyIndexes = [aKeyIndexes];
10085 if(aKeyIndexes !== null) {
10086 // Sort the indexes so we can remove from the right
10087 aKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
10089 // Destroy previous THEAD
10090 this._destroyTheadEl();
10092 // Create new THEAD
10093 var aOrigColumnDefs = this._oColumnSet.getDefinitions();
10094 oColumn = aOrigColumnDefs.splice(nColTreeIndex,1)[0];
10095 this._initColumnSet(aOrigColumnDefs);
10096 this._initTheadEl();
10099 for(i=aKeyIndexes.length-1; i>-1; i--) {
10100 this._removeColgroupColEl(aKeyIndexes[i]);
10104 var allRows = this._elTbody.rows;
10105 if(allRows.length > 0) {
10106 var loopN = this.get("renderLoopSize"),
10107 loopEnd = allRows.length;
10108 this._oChainRender.add({
10109 method: function(oArg) {
10110 if((this instanceof DT) && this._sId) {
10111 var i = oArg.nCurrentRow,
10112 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
10113 aIndexes = oArg.aIndexes,
10115 for(; i < len; ++i) {
10116 for(j = aIndexes.length-1; j>-1; j--) {
10117 allRows[i].removeChild(allRows[i].childNodes[aIndexes[j]]);
10120 oArg.nCurrentRow = i;
10123 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10124 argument: {nCurrentRow:0, aIndexes:aKeyIndexes},
10126 timeout: (loopN > 0) ? 0 : -1
10128 this._runRenderChain();
10131 this.fireEvent("columnRemoveEvent",{column:oColumn});
10139 * Inserts given Column at the index if given, otherwise at the end. NOTE: You
10140 * can only add non-nested Columns and top-level parent Columns. You cannot add
10141 * a nested Column to an existing parent.
10143 * @method insertColumn
10144 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
10145 * definition or a Column instance.
10146 * @param index {Number} (optional) New tree index.
10147 * @return oColumn {YAHOO.widget.Column} Inserted Column instance.
10149 insertColumn : function(oColumn, index) {
10151 if(oColumn instanceof YAHOO.widget.Column) {
10152 oColumn = oColumn.getDefinition();
10154 else if(oColumn.constructor !== Object) {
10158 // Validate index or append new Column to the end of the ColumnSet
10159 var oColumnSet = this._oColumnSet;
10160 if(!lang.isValue(index) || !lang.isNumber(index)) {
10161 index = oColumnSet.tree[0].length;
10164 // Destroy previous THEAD
10165 this._destroyTheadEl();
10167 // Create new THEAD
10168 var aNewColumnDefs = this._oColumnSet.getDefinitions();
10169 aNewColumnDefs.splice(index, 0, oColumn);
10170 this._initColumnSet(aNewColumnDefs);
10171 this._initTheadEl();
10173 // Need to refresh the reference
10174 oColumnSet = this._oColumnSet;
10175 var oNewColumn = oColumnSet.tree[0][index];
10177 // Get key index(es) for new Column
10179 descKeyIndexes = [];
10180 var allDescendants = oColumnSet.getDescendants(oNewColumn);
10181 for(i=0, len=allDescendants.length; i<len; i++) {
10182 // Is this descendant a key Column?
10183 var thisKey = allDescendants[i].getKeyIndex();
10184 if(thisKey !== null) {
10185 descKeyIndexes[descKeyIndexes.length] = thisKey;
10189 if(descKeyIndexes.length > 0) {
10190 // Sort the indexes
10191 var newIndex = descKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
10194 for(i=descKeyIndexes.length-1; i>-1; i--) {
10195 this._insertColgroupColEl(descKeyIndexes[i]);
10199 var allRows = this._elTbody.rows;
10200 if(allRows.length > 0) {
10201 var loopN = this.get("renderLoopSize"),
10202 loopEnd = allRows.length;
10204 // Get templates for each new TD
10205 var aTdTemplates = [],
10207 for(i=0, len=descKeyIndexes.length; i<len; i++) {
10208 var thisKeyIndex = descKeyIndexes[i];
10209 elTdTemplate = this._getTrTemplateEl().childNodes[i].cloneNode(true);
10210 elTdTemplate = this._formatTdEl(this._oColumnSet.keys[thisKeyIndex], elTdTemplate, thisKeyIndex, (thisKeyIndex===this._oColumnSet.keys.length-1));
10211 aTdTemplates[thisKeyIndex] = elTdTemplate;
10214 this._oChainRender.add({
10215 method: function(oArg) {
10216 if((this instanceof DT) && this._sId) {
10217 var i = oArg.nCurrentRow, j,
10218 descKeyIndexes = oArg.descKeyIndexes,
10219 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
10221 for(; i < len; ++i) {
10222 nextSibling = allRows[i].childNodes[newIndex] || null;
10223 for(j=descKeyIndexes.length-1; j>-1; j--) {
10224 allRows[i].insertBefore(oArg.aTdTemplates[descKeyIndexes[j]].cloneNode(true), nextSibling);
10227 oArg.nCurrentRow = i;
10230 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10231 argument: {nCurrentRow:0,aTdTemplates:aTdTemplates,descKeyIndexes:descKeyIndexes},
10233 timeout: (loopN > 0) ? 0 : -1
10235 this._runRenderChain();
10238 this.fireEvent("columnInsertEvent",{column:oColumn,index:index});
10244 * Removes given Column and inserts into given tree index. NOTE: You
10245 * can only reorder non-nested Columns and top-level parent Columns. You cannot
10246 * reorder a nested Column to an existing parent.
10248 * @method reorderColumn
10249 * @param oColumn {YAHOO.widget.Column} Column instance.
10250 * @param index {Number} New tree index.
10251 * @return oColumn {YAHOO.widget.Column} Reordered Column instance.
10253 reorderColumn : function(oColumn, index) {
10254 // Validate Column and new index
10255 if(!(oColumn instanceof YAHOO.widget.Column)) {
10256 oColumn = this.getColumn(oColumn);
10258 if(oColumn && YAHOO.lang.isNumber(index)) {
10259 var nOrigTreeIndex = oColumn.getTreeIndex();
10260 if((nOrigTreeIndex !== null) && (nOrigTreeIndex !== index)) {
10261 // Which key index(es)
10263 aOrigKeyIndexes = oColumn.getKeyIndex(),
10265 descKeyIndexes = [],
10267 // Must be a parent Column...
10268 if(aOrigKeyIndexes === null) {
10269 allDescendants = this._oColumnSet.getDescendants(oColumn);
10270 for(i=0, len=allDescendants.length; i<len; i++) {
10271 // Is this descendant a key Column?
10272 thisKey = allDescendants[i].getKeyIndex();
10273 if(thisKey !== null) {
10274 descKeyIndexes[descKeyIndexes.length] = thisKey;
10277 if(descKeyIndexes.length > 0) {
10278 aOrigKeyIndexes = descKeyIndexes;
10281 // ...or else must be a key Column
10283 aOrigKeyIndexes = [aOrigKeyIndexes];
10286 if(aOrigKeyIndexes !== null) {
10287 // Sort the indexes
10288 aOrigKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);});
10290 // Destroy previous THEAD
10291 this._destroyTheadEl();
10293 // Create new THEAD
10294 var aColumnDefs = this._oColumnSet.getDefinitions();
10295 var oColumnDef = aColumnDefs.splice(nOrigTreeIndex,1)[0];
10296 aColumnDefs.splice(index, 0, oColumnDef);
10297 this._initColumnSet(aColumnDefs);
10298 this._initTheadEl();
10300 // Need to refresh the reference
10301 var oNewColumn = this._oColumnSet.tree[0][index];
10303 // What are new key index(es)
10304 var aNewKeyIndexes = oNewColumn.getKeyIndex();
10305 // Must be a parent Column
10306 if(aNewKeyIndexes === null) {
10307 descKeyIndexes = [];
10308 allDescendants = this._oColumnSet.getDescendants(oNewColumn);
10309 for(i=0, len=allDescendants.length; i<len; i++) {
10310 // Is this descendant a key Column?
10311 thisKey = allDescendants[i].getKeyIndex();
10312 if(thisKey !== null) {
10313 descKeyIndexes[descKeyIndexes.length] = thisKey;
10316 if(descKeyIndexes.length > 0) {
10317 aNewKeyIndexes = descKeyIndexes;
10320 // Must be a key Column
10322 aNewKeyIndexes = [aNewKeyIndexes];
10325 // Sort the new indexes and grab the first one for the new location
10326 var newIndex = aNewKeyIndexes.sort(function(a, b) {return YAHOO.util.Sort.compare(a, b);})[0];
10329 this._reorderColgroupColEl(aOrigKeyIndexes, newIndex);
10332 var allRows = this._elTbody.rows;
10333 if(allRows.length > 0) {
10334 var loopN = this.get("renderLoopSize"),
10335 loopEnd = allRows.length;
10336 this._oChainRender.add({
10337 method: function(oArg) {
10338 if((this instanceof DT) && this._sId) {
10339 var i = oArg.nCurrentRow, j, tmpTds, nextSibling,
10340 len = loopN > 0 ? Math.min(i + loopN,allRows.length) : allRows.length,
10341 aIndexes = oArg.aIndexes, thisTr;
10343 for(; i < len; ++i) {
10345 thisTr = allRows[i];
10348 for(j=aIndexes.length-1; j>-1; j--) {
10349 tmpTds.push(thisTr.removeChild(thisTr.childNodes[aIndexes[j]]));
10353 nextSibling = thisTr.childNodes[newIndex] || null;
10354 for(j=tmpTds.length-1; j>-1; j--) {
10355 thisTr.insertBefore(tmpTds[j], nextSibling);
10358 oArg.nCurrentRow = i;
10361 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10362 argument: {nCurrentRow:0, aIndexes:aOrigKeyIndexes},
10364 timeout: (loopN > 0) ? 0 : -1
10366 this._runRenderChain();
10369 this.fireEvent("columnReorderEvent",{column:oNewColumn, oldIndex:nOrigTreeIndex});
10377 * Selects given Column. NOTE: You cannot select/unselect nested Columns. You can only
10378 * select/unselect non-nested Columns, and bottom-level key Columns.
10380 * @method selectColumn
10381 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10382 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10384 selectColumn : function(oColumn) {
10385 oColumn = this.getColumn(oColumn);
10386 if(oColumn && !oColumn.selected) {
10387 // Only bottom-level Columns can get hidden
10388 if(oColumn.getKeyIndex() !== null) {
10389 oColumn.selected = true;
10391 // Update head cell
10392 var elTh = oColumn.getThEl();
10393 Dom.addClass(elTh,DT.CLASS_SELECTED);
10395 // Update body cells
10396 var allRows = this.getTbodyEl().rows;
10397 var oChainRender = this._oChainRender;
10399 method: function(oArg) {
10400 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10401 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);
10406 iterations: allRows.length,
10407 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
10410 this._clearTrTemplateEl();
10412 this._elTbody.style.display = "none";
10413 this._runRenderChain();
10414 this._elTbody.style.display = "";
10416 this.fireEvent("columnSelectEvent",{column:oColumn});
10424 * Unselects given Column. NOTE: You cannot select/unselect nested Columns. You can only
10425 * select/unselect non-nested Columns, and bottom-level key Columns.
10427 * @method unselectColumn
10428 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10429 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10431 unselectColumn : function(oColumn) {
10432 oColumn = this.getColumn(oColumn);
10433 if(oColumn && oColumn.selected) {
10434 // Only bottom-level Columns can get hidden
10435 if(oColumn.getKeyIndex() !== null) {
10436 oColumn.selected = false;
10438 // Update head cell
10439 var elTh = oColumn.getThEl();
10440 Dom.removeClass(elTh,DT.CLASS_SELECTED);
10442 // Update body cells
10443 var allRows = this.getTbodyEl().rows;
10444 var oChainRender = this._oChainRender;
10446 method: function(oArg) {
10447 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10448 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_SELECTED);
10453 iterations:allRows.length,
10454 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()}
10457 this._clearTrTemplateEl();
10459 this._elTbody.style.display = "none";
10460 this._runRenderChain();
10461 this._elTbody.style.display = "";
10463 this.fireEvent("columnUnselectEvent",{column:oColumn});
10471 * Returns an array selected Column instances.
10473 * @method getSelectedColumns
10474 * @return {YAHOO.widget.Column[]} Array of Column instances.
10476 getSelectedColumns : function(oColumn) {
10477 var selectedColumns = [];
10478 var aKeys = this._oColumnSet.keys;
10479 for(var i=0,len=aKeys.length; i<len; i++) {
10480 if(aKeys[i].selected) {
10481 selectedColumns[selectedColumns.length] = aKeys[i];
10484 return selectedColumns;
10488 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
10489 * NOTE: You cannot highlight/unhighlight nested Columns. You can only
10490 * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
10492 * @method highlightColumn
10493 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10494 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10496 highlightColumn : function(column) {
10497 var oColumn = this.getColumn(column);
10498 // Only bottom-level Columns can get highlighted
10499 if(oColumn && (oColumn.getKeyIndex() !== null)) {
10500 // Update head cell
10501 var elTh = oColumn.getThEl();
10502 Dom.addClass(elTh,DT.CLASS_HIGHLIGHTED);
10504 // Update body cells
10505 var allRows = this.getTbodyEl().rows;
10506 var oChainRender = this._oChainRender;
10508 method: function(oArg) {
10509 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10510 Dom.addClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
10515 iterations:allRows.length,
10516 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
10519 this._elTbody.style.display = "none";
10520 this._runRenderChain();
10521 this._elTbody.style.display = "";
10523 this.fireEvent("columnHighlightEvent",{column:oColumn});
10530 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to cells of the given Column.
10531 * NOTE: You cannot highlight/unhighlight nested Columns. You can only
10532 * highlight/unhighlight non-nested Columns, and bottom-level key Columns.
10534 * @method unhighlightColumn
10535 * @param column {HTMLElement | String | Number} DOM reference or ID string to a
10536 * TH/TD element (or child of a TH/TD element), a Column key, or a ColumnSet key index.
10538 unhighlightColumn : function(column) {
10539 var oColumn = this.getColumn(column);
10540 // Only bottom-level Columns can get highlighted
10541 if(oColumn && (oColumn.getKeyIndex() !== null)) {
10542 // Update head cell
10543 var elTh = oColumn.getThEl();
10544 Dom.removeClass(elTh,DT.CLASS_HIGHLIGHTED);
10546 // Update body cells
10547 var allRows = this.getTbodyEl().rows;
10548 var oChainRender = this._oChainRender;
10550 method: function(oArg) {
10551 if((this instanceof DT) && this._sId && allRows[oArg.rowIndex] && allRows[oArg.rowIndex].cells[oArg.cellIndex]) {
10552 Dom.removeClass(allRows[oArg.rowIndex].cells[oArg.cellIndex],DT.CLASS_HIGHLIGHTED);
10557 iterations:allRows.length,
10558 argument: {rowIndex:0,cellIndex:oColumn.getKeyIndex()},
10561 this._elTbody.style.display = "none";
10562 this._runRenderChain();
10563 this._elTbody.style.display = "";
10565 this.fireEvent("columnUnhighlightEvent",{column:oColumn});
10617 * Adds one new Record of data into the RecordSet at the index if given,
10618 * otherwise at the end. If the new Record is in page view, the
10619 * corresponding DOM elements are also updated.
10622 * @param oData {Object} Object literal of data for the row.
10623 * @param index {Number} (optional) RecordSet position index at which to add data.
10625 addRow : function(oData, index) {
10626 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
10630 if(oData && lang.isObject(oData)) {
10631 var oRecord = this._oRecordSet.addRecord(oData, index);
10634 var oPaginator = this.get('paginator');
10638 // Update the paginator's totalRecords
10639 var totalRecords = oPaginator.get('totalRecords');
10640 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
10641 oPaginator.set('totalRecords',totalRecords + 1);
10644 recIndex = this.getRecordIndex(oRecord);
10645 var endRecIndex = (oPaginator.getPageRecords())[1];
10647 // New record affects the view
10648 if (recIndex <= endRecIndex) {
10649 // Defer UI updates to the render method
10653 this.fireEvent("rowAddEvent", {record:oRecord});
10658 recIndex = this.getRecordIndex(oRecord);
10659 if(lang.isNumber(recIndex)) {
10660 // Add the TR element
10661 this._oChainRender.add({
10662 method: function(oArg) {
10663 if((this instanceof DT) && this._sId) {
10664 var oRecord = oArg.record;
10665 var recIndex = oArg.recIndex;
10666 var elNewTr = this._addTrEl(oRecord);
10668 var elNext = (this._elTbody.rows[recIndex]) ? this._elTbody.rows[recIndex] : null;
10669 this._elTbody.insertBefore(elNewTr, elNext);
10672 if(recIndex === 0) {
10673 this._setFirstRow();
10675 if(elNext === null) {
10676 this._setLastRow();
10679 this._setRowStripes();
10681 this.hideTableMessage();
10683 this.fireEvent("rowAddEvent", {record:oRecord});
10687 argument: {record: oRecord, recIndex: recIndex},
10689 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
10691 this._runRenderChain();
10700 * Convenience method to add multiple rows.
10703 * @param aData {Object[]} Array of object literal data for the rows.
10704 * @param index {Number} (optional) RecordSet position index at which to add data.
10706 addRows : function(aData, index) {
10707 if(lang.isNumber(index) && (index < 0 || index > this._oRecordSet.getLength())) {
10711 if(lang.isArray(aData)) {
10712 var aRecords = this._oRecordSet.addRecords(aData, index);
10714 var recIndex = this.getRecordIndex(aRecords[0]);
10717 var oPaginator = this.get('paginator');
10719 // Update the paginator's totalRecords
10720 var totalRecords = oPaginator.get('totalRecords');
10721 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
10722 oPaginator.set('totalRecords',totalRecords + aRecords.length);
10725 var endRecIndex = (oPaginator.getPageRecords())[1];
10727 // At least one of the new records affects the view
10728 if (recIndex <= endRecIndex) {
10732 this.fireEvent("rowsAddEvent", {records:aRecords});
10737 // Add the TR elements
10738 var loopN = this.get("renderLoopSize");
10739 var loopEnd = recIndex + aData.length;
10740 var nRowsNeeded = (loopEnd - recIndex); // how many needed
10741 var isLast = (recIndex >= this._elTbody.rows.length);
10742 this._oChainRender.add({
10743 method: function(oArg) {
10744 if((this instanceof DT) && this._sId) {
10745 var aRecords = oArg.aRecords,
10746 i = oArg.nCurrentRow,
10747 j = oArg.nCurrentRecord,
10748 len = loopN > 0 ? Math.min(i + loopN,loopEnd) : loopEnd,
10749 df = document.createDocumentFragment(),
10750 elNext = (this._elTbody.rows[i]) ? this._elTbody.rows[i] : null;
10751 for(; i < len; i++, j++) {
10752 df.appendChild(this._addTrEl(aRecords[j]));
10754 this._elTbody.insertBefore(df, elNext);
10755 oArg.nCurrentRow = i;
10756 oArg.nCurrentRecord = j;
10759 iterations: (loopN > 0) ? Math.ceil(loopEnd/loopN) : 1,
10760 argument: {nCurrentRow:recIndex,nCurrentRecord:0,aRecords:aRecords},
10762 timeout: (loopN > 0) ? 0 : -1
10764 this._oChainRender.add({
10765 method: function(oArg) {
10766 var recIndex = oArg.recIndex;
10768 if(recIndex === 0) {
10769 this._setFirstRow();
10772 this._setLastRow();
10775 this._setRowStripes();
10777 this.fireEvent("rowsAddEvent", {records:aRecords});
10779 argument: {recIndex: recIndex, isLast: isLast},
10781 timeout: -1 // Needs to run immediately after the DOM insertions above
10783 this._runRenderChain();
10784 this.hideTableMessage();
10792 * For the given row, updates the associated Record with the given data. If the
10793 * row is on current page, the corresponding DOM elements are also updated.
10795 * @method updateRow
10796 * @param row {YAHOO.widget.Record | Number | HTMLElement | String}
10797 * Which row to update: By Record instance, by Record's RecordSet
10798 * position index, by HTMLElement reference to the TR element, or by ID string
10799 * of the TR element.
10800 * @param oData {Object} Object literal of data for the row.
10802 updateRow : function(row, oData) {
10804 if (!lang.isNumber(index)) {
10805 index = this.getRecordIndex(row);
10808 // Update the Record
10809 if(lang.isNumber(index) && (index >= 0)) {
10810 var oRecordSet = this._oRecordSet,
10811 oldRecord = oRecordSet.getRecord(index);
10814 var updatedRecord = this._oRecordSet.setRecord(oData, index),
10815 elRow = this.getTrEl(oldRecord),
10816 // Copy data from the Record for the event that gets fired later
10817 oldData = oldRecord ? oldRecord.getData() : null;
10819 if(updatedRecord) {
10820 // Update selected rows as necessary
10821 var tracker = this._aSelections || [],
10823 oldId = oldRecord.getId(),
10824 newId = updatedRecord.getId();
10825 for(; i<tracker.length; i++) {
10826 if((tracker[i] === oldId)) {
10827 tracker[i] = newId;
10829 else if(tracker[i].recordId === oldId) {
10830 tracker[i].recordId = newId;
10834 // Update anchors as necessary
10835 if(this._oAnchorRecord && this._oAnchorRecord.getId() === oldId) {
10836 this._oAnchorRecord = updatedRecord;
10838 if(this._oAnchorCell && this._oAnchorCell.record.getId() === oldId) {
10839 this._oAnchorCell.record = updatedRecord;
10842 // Update the TR only if row is on current page
10843 this._oChainRender.add({
10844 method: function() {
10845 if((this instanceof DT) && this._sId) {
10847 var oPaginator = this.get('paginator');
10849 var pageStartIndex = (oPaginator.getPageRecords())[0],
10850 pageLastIndex = (oPaginator.getPageRecords())[1];
10852 // At least one of the new records affects the view
10853 if ((index >= pageStartIndex) || (index <= pageLastIndex)) {
10859 this._updateTrEl(elRow, updatedRecord);
10862 this.getTbodyEl().appendChild(this._addTrEl(updatedRecord));
10865 this.fireEvent("rowUpdateEvent", {record:updatedRecord, oldData:oldData});
10869 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
10871 this._runRenderChain();
10880 * Starting with the given row, updates associated Records with the given data.
10881 * The number of rows to update are determined by the array of data provided.
10882 * Undefined data (i.e., not an object literal) causes a row to be skipped. If
10883 * any of the rows are on current page, the corresponding DOM elements are also
10886 * @method updateRows
10887 * @param startrow {YAHOO.widget.Record | Number | HTMLElement | String}
10888 * Starting row to update: By Record instance, by Record's RecordSet
10889 * position index, by HTMLElement reference to the TR element, or by ID string
10890 * of the TR element.
10891 * @param aData {Object[]} Array of object literal of data for the rows.
10893 updateRows : function(startrow, aData) {
10894 if(lang.isArray(aData)) {
10895 var startIndex = startrow,
10896 oRecordSet = this._oRecordSet,
10897 lastRowIndex = oRecordSet.getLength();
10899 if (!lang.isNumber(startrow)) {
10900 startIndex = this.getRecordIndex(startrow);
10903 if(lang.isNumber(startIndex) && (startIndex >= 0) && (startIndex < oRecordSet.getLength())) {
10904 var lastIndex = startIndex + aData.length,
10905 aOldRecords = oRecordSet.getRecords(startIndex, aData.length),
10906 aNewRecords = oRecordSet.setRecords(aData, startIndex);
10908 var tracker = this._aSelections || [],
10909 i=0, j, newRecord, newId, oldId,
10910 anchorRecord = this._oAnchorRecord ? this._oAnchorRecord.getId() : null,
10911 anchorCell = this._oAnchorCell ? this._oAnchorCell.record.getId() : null;
10912 for(; i<aOldRecords.length; i++) {
10913 oldId = aOldRecords[i].getId();
10914 newRecord = aNewRecords[i];
10915 newId = newRecord.getId();
10917 // Update selected rows as necessary
10918 for(j=0; j<tracker.length; j++) {
10919 if((tracker[j] === oldId)) {
10920 tracker[j] = newId;
10922 else if(tracker[j].recordId === oldId) {
10923 tracker[j].recordId = newId;
10927 // Update anchors as necessary
10928 if(anchorRecord && anchorRecord === oldId) {
10929 this._oAnchorRecord = newRecord;
10931 if(anchorCell && anchorCell === oldId) {
10932 this._oAnchorCell.record = newRecord;
10937 var oPaginator = this.get('paginator');
10939 var pageStartIndex = (oPaginator.getPageRecords())[0],
10940 pageLastIndex = (oPaginator.getPageRecords())[1];
10942 // At least one of the new records affects the view
10943 if ((startIndex >= pageStartIndex) || (lastIndex <= pageLastIndex)) {
10947 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
10952 // Update the TR elements
10953 var loopN = this.get("renderLoopSize"),
10954 rowCount = aData.length, // how many needed
10955 isLast = (lastIndex >= lastRowIndex),
10956 isAdding = (lastIndex > lastRowIndex);
10958 this._oChainRender.add({
10959 method: function(oArg) {
10960 if((this instanceof DT) && this._sId) {
10961 var aRecords = oArg.aRecords,
10962 i = oArg.nCurrentRow,
10963 j = oArg.nDataPointer,
10964 len = loopN > 0 ? Math.min(i+loopN, startIndex+aRecords.length) : startIndex+aRecords.length;
10966 for(; i < len; i++,j++) {
10967 if(isAdding && (i>=lastRowIndex)) {
10968 this._elTbody.appendChild(this._addTrEl(aRecords[j]));
10971 this._updateTrEl(this._elTbody.rows[i], aRecords[j]);
10974 oArg.nCurrentRow = i;
10975 oArg.nDataPointer = j;
10978 iterations: (loopN > 0) ? Math.ceil(rowCount/loopN) : 1,
10979 argument: {nCurrentRow:startIndex,aRecords:aNewRecords,nDataPointer:0,isAdding:isAdding},
10981 timeout: (loopN > 0) ? 0 : -1
10983 this._oChainRender.add({
10984 method: function(oArg) {
10985 var recIndex = oArg.recIndex;
10987 if(recIndex === 0) {
10988 this._setFirstRow();
10991 this._setLastRow();
10994 this._setRowStripes();
10996 this.fireEvent("rowsAddEvent", {newRecords:aNewRecords, oldRecords:aOldRecords});
10998 argument: {recIndex: startIndex, isLast: isLast},
11000 timeout: -1 // Needs to run immediately after the DOM insertions above
11002 this._runRenderChain();
11003 this.hideTableMessage();
11012 * Deletes the given row's Record from the RecordSet. If the row is on current page,
11013 * the corresponding DOM elements are also deleted.
11015 * @method deleteRow
11016 * @param row {HTMLElement | String | Number} DOM element reference or ID string
11017 * to DataTable page element or RecordSet index.
11019 deleteRow : function(row) {
11020 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
11021 if(lang.isNumber(nRecordIndex)) {
11022 var oRecord = this.getRecord(nRecordIndex);
11024 var nTrIndex = this.getTrIndex(nRecordIndex);
11026 // Remove from selection tracker if there
11027 var sRecordId = oRecord.getId();
11028 var tracker = this._aSelections || [];
11029 for(var j=tracker.length-1; j>-1; j--) {
11030 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
11031 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
11032 tracker.splice(j,1);
11036 // Delete Record from RecordSet
11037 var oData = this._oRecordSet.deleteRecord(nRecordIndex);
11041 // If paginated and the deleted row was on this or a prior page, just
11043 var oPaginator = this.get('paginator');
11045 // Update the paginator's totalRecords
11046 var totalRecords = oPaginator.get('totalRecords'),
11047 // must capture before the totalRecords change because
11048 // Paginator shifts to previous page automatically
11049 rng = oPaginator.getPageRecords();
11051 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
11052 oPaginator.set('totalRecords',totalRecords - 1);
11055 // The deleted record was on this or a prior page, re-render
11056 if (!rng || nRecordIndex <= rng[1]) {
11060 this._oChainRender.add({
11061 method: function() {
11062 if((this instanceof DT) && this._sId) {
11063 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex, oldData:oData, trElIndex:nTrIndex});
11067 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
11069 this._runRenderChain();
11073 if(lang.isNumber(nTrIndex)) {
11074 this._oChainRender.add({
11075 method: function() {
11076 if((this instanceof DT) && this._sId) {
11077 var isLast = (nRecordIndex === this._oRecordSet.getLength());//(nTrIndex == this.getLastTrEl().sectionRowIndex);
11078 this._deleteTrEl(nTrIndex);
11080 // Post-delete tasks
11081 if(this._elTbody.rows.length > 0) {
11083 if(nTrIndex === 0) {
11084 this._setFirstRow();
11087 this._setLastRow();
11090 if(nTrIndex != this._elTbody.rows.length) {
11091 this._setRowStripes(nTrIndex);
11095 this.fireEvent("rowDeleteEvent", {recordIndex:nRecordIndex,oldData:oData, trElIndex:nTrIndex});
11099 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
11101 this._runRenderChain();
11112 * Convenience method to delete multiple rows.
11114 * @method deleteRows
11115 * @param row {HTMLElement | String | Number} DOM element reference or ID string
11116 * to DataTable page element or RecordSet index.
11117 * @param count {Number} (optional) How many rows to delete. A negative value
11118 * will delete towards the beginning.
11120 deleteRows : function(row, count) {
11121 var nRecordIndex = (lang.isNumber(row)) ? row : this.getRecordIndex(row);
11122 if(lang.isNumber(nRecordIndex)) {
11123 var oRecord = this.getRecord(nRecordIndex);
11125 var nTrIndex = this.getTrIndex(nRecordIndex);
11127 // Remove from selection tracker if there
11128 var sRecordId = oRecord.getId();
11129 var tracker = this._aSelections || [];
11130 for(var j=tracker.length-1; j>-1; j--) {
11131 if((lang.isString(tracker[j]) && (tracker[j] === sRecordId)) ||
11132 (lang.isObject(tracker[j]) && (tracker[j].recordId === sRecordId))) {
11133 tracker.splice(j,1);
11137 // Delete Record from RecordSet
11138 var highIndex = nRecordIndex;
11139 var lowIndex = nRecordIndex;
11141 // Validate count and account for negative value
11142 if(count && lang.isNumber(count)) {
11143 highIndex = (count > 0) ? nRecordIndex + count -1 : nRecordIndex;
11144 lowIndex = (count > 0) ? nRecordIndex : nRecordIndex + count + 1;
11145 count = (count > 0) ? count : count*-1;
11148 count = highIndex - lowIndex + 1;
11155 var aData = this._oRecordSet.deleteRecords(lowIndex, count);
11159 var oPaginator = this.get('paginator'),
11160 loopN = this.get("renderLoopSize");
11161 // If paginated and the deleted row was on this or a prior page, just
11164 // Update the paginator's totalRecords
11165 var totalRecords = oPaginator.get('totalRecords'),
11166 // must capture before the totalRecords change because
11167 // Paginator shifts to previous page automatically
11168 rng = oPaginator.getPageRecords();
11170 if (totalRecords !== widget.Paginator.VALUE_UNLIMITED) {
11171 oPaginator.set('totalRecords',totalRecords - aData.length);
11174 // The records were on this or a prior page, re-render
11175 if (!rng || lowIndex <= rng[1]) {
11179 this._oChainRender.add({
11180 method: function(oArg) {
11181 if((this instanceof DT) && this._sId) {
11182 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
11186 timeout: (loopN > 0) ? 0 : -1
11188 this._runRenderChain();
11193 if(lang.isNumber(nTrIndex)) {
11194 // Delete the TR elements starting with highest index
11195 var loopEnd = lowIndex;
11196 var nRowsNeeded = count; // how many needed
11197 this._oChainRender.add({
11198 method: function(oArg) {
11199 if((this instanceof DT) && this._sId) {
11200 var i = oArg.nCurrentRow,
11201 len = (loopN > 0) ? (Math.max(i - loopN,loopEnd)-1) : loopEnd-1;
11202 for(; i>len; --i) {
11203 this._deleteTrEl(i);
11205 oArg.nCurrentRow = i;
11208 iterations: (loopN > 0) ? Math.ceil(count/loopN) : 1,
11209 argument: {nCurrentRow:highIndex},
11211 timeout: (loopN > 0) ? 0 : -1
11213 this._oChainRender.add({
11214 method: function() {
11215 // Post-delete tasks
11216 if(this._elTbody.rows.length > 0) {
11217 this._setFirstRow();
11218 this._setLastRow();
11219 this._setRowStripes();
11222 this.fireEvent("rowsDeleteEvent", {recordIndex:lowIndex, oldData:aData, count:count});
11225 timeout: -1 // Needs to run immediately after the DOM deletions above
11227 this._runRenderChain();
11285 * Outputs markup into the given TD based on given Record.
11287 * @method formatCell
11288 * @param elLiner {HTMLElement} The liner DIV element within the TD.
11289 * @param oRecord {YAHOO.widget.Record} (Optional) Record instance.
11290 * @param oColumn {YAHOO.widget.Column} (Optional) Column instance.
11292 formatCell : function(elLiner, oRecord, oColumn) {
11294 oRecord = this.getRecord(elLiner);
11297 oColumn = this.getColumn(this.getCellIndex(elLiner.parentNode));
11300 if(oRecord && oColumn) {
11301 var sField = oColumn.field;
11302 var oData = oRecord.getData(sField);
11304 var fnFormatter = typeof oColumn.formatter === 'function' ?
11305 oColumn.formatter :
11306 DT.Formatter[oColumn.formatter+''] ||
11307 DT.Formatter.defaultFormatter;
11309 // Apply special formatter
11311 fnFormatter.call(this, elLiner, oRecord, oColumn, oData);
11314 elLiner.innerHTML = oData;
11317 this.fireEvent("cellFormatEvent", {record:oRecord, column:oColumn, key:oColumn.key, el:elLiner});
11324 * For the given row and column, updates the Record with the given data. If the
11325 * cell is on current page, the corresponding DOM elements are also updated.
11327 * @method updateCell
11328 * @param oRecord {YAHOO.widget.Record} Record instance.
11329 * @param oColumn {YAHOO.widget.Column | String | Number} A Column key, or a ColumnSet key index.
11330 * @param oData {Object} New data value for the cell.
11331 * @param skipRender {Boolean} Skips render step. Editors that update multiple
11332 * cells in ScrollingDataTable should render only on the last call to updateCell().
11334 updateCell : function(oRecord, oColumn, oData, skipRender) {
11335 // Validate Column and Record
11336 oColumn = (oColumn instanceof YAHOO.widget.Column) ? oColumn : this.getColumn(oColumn);
11337 if(oColumn && oColumn.getField() && (oRecord instanceof YAHOO.widget.Record)) {
11338 var sKey = oColumn.getField(),
11340 // Copy data from the Record for the event that gets fired later
11341 //var oldData = YAHOO.widget.DataTable._cloneObject(oRecord.getData());
11342 oldData = oRecord.getData(sKey);
11344 // Update Record with new data
11345 this._oRecordSet.updateRecordValue(oRecord, sKey, oData);
11347 // Update the TD only if row is on current page
11348 var elTd = this.getTdEl({record: oRecord, column: oColumn});
11350 this._oChainRender.add({
11351 method: function() {
11352 if((this instanceof DT) && this._sId) {
11353 this.formatCell(elTd.firstChild, oRecord, oColumn);
11354 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
11358 timeout: (this.get("renderLoopSize") > 0) ? 0 : -1
11362 this._runRenderChain();
11366 this.fireEvent("cellUpdateEvent", {record:oRecord, column: oColumn, oldData:oldData});
11423 * Method executed during set() operation for the "paginator" attribute.
11424 * Adds and/or severs event listeners between DataTable and Paginator
11426 * @method _updatePaginator
11427 * @param newPag {Paginator} Paginator instance (or null) for DataTable to use
11430 _updatePaginator : function (newPag) {
11431 var oldPag = this.get('paginator');
11432 if (oldPag && newPag !== oldPag) {
11433 oldPag.unsubscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
11436 newPag.subscribe('changeRequest', this.onPaginatorChangeRequest, this, true);
11441 * Update the UI infrastructure in response to a "paginator" attribute change.
11443 * @method _handlePaginatorChange
11444 * @param e {Object} Change event object containing keys 'type','newValue',
11448 _handlePaginatorChange : function (e) {
11449 if (e.prevValue === e.newValue) { return; }
11451 var newPag = e.newValue,
11452 oldPag = e.prevValue,
11453 containers = this._defaultPaginatorContainers();
11456 if (oldPag.getContainerNodes()[0] == containers[0]) {
11457 oldPag.set('containers',[]);
11461 // Convenience: share the default containers if possible.
11462 // Otherwise, remove the default containers from the DOM.
11463 if (containers[0]) {
11464 if (newPag && !newPag.getContainerNodes().length) {
11465 newPag.set('containers',containers);
11467 // No new Paginator to use existing containers, OR new
11468 // Paginator has configured containers.
11469 for (var i = containers.length - 1; i >= 0; --i) {
11470 if (containers[i]) {
11471 containers[i].parentNode.removeChild(containers[i]);
11478 if (!this._bInit) {
11484 this.renderPaginator();
11490 * Returns the default containers used for Paginators. If create param is
11491 * passed, the containers will be created and added to the DataTable container.
11493 * @method _defaultPaginatorContainers
11494 * @param create {boolean} Create the default containers if not found
11497 _defaultPaginatorContainers : function (create) {
11498 var above_id = this._sId + '-paginator0',
11499 below_id = this._sId + '-paginator1',
11500 above = Dom.get(above_id),
11501 below = Dom.get(below_id);
11503 if (create && (!above || !below)) {
11504 // One above and one below the table
11506 above = document.createElement('div');
11507 above.id = above_id;
11508 Dom.addClass(above, DT.CLASS_PAGINATOR);
11510 this._elContainer.insertBefore(above,this._elContainer.firstChild);
11514 below = document.createElement('div');
11515 below.id = below_id;
11516 Dom.addClass(below, DT.CLASS_PAGINATOR);
11518 this._elContainer.appendChild(below);
11522 return [above,below];
11526 * Calls Paginator's destroy() method
11528 * @method _destroyPaginator
11531 _destroyPaginator : function () {
11532 var oldPag = this.get('paginator');
11539 * Renders the Paginator to the DataTable UI
11541 * @method renderPaginator
11543 renderPaginator : function () {
11544 var pag = this.get("paginator");
11545 if (!pag) { return; }
11547 // Add the containers if the Paginator is not configured with containers
11548 if (!pag.getContainerNodes().length) {
11549 pag.set('containers',this._defaultPaginatorContainers(true));
11556 * Overridable method gives implementers a hook to show loading message before
11557 * changing Paginator value.
11559 * @method doBeforePaginatorChange
11560 * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
11561 * @return {Boolean} Return true to continue changing Paginator value.
11563 doBeforePaginatorChange : function(oPaginatorState) {
11564 this.showTableMessage(this.get("MSG_LOADING"), DT.CLASS_LOADING);
11569 * Responds to new Pagination states. By default, updates the UI to reflect the
11570 * new state. If "dynamicData" is true, current selections are purged before
11571 * a request is sent to the DataSource for data for the new state (using the
11572 * request returned by "generateRequest()").
11574 * @method onPaginatorChangeRequest
11575 * @param oPaginatorState {Object} An object literal describing the proposed pagination state.
11577 onPaginatorChangeRequest : function (oPaginatorState) {
11578 var ok = this.doBeforePaginatorChange(oPaginatorState);
11580 // Server-side pagination
11581 if(this.get("dynamicData")) {
11582 // Get the current state
11583 var oState = this.getState();
11585 // Update pagination values
11586 oState.pagination = oPaginatorState;
11588 // Get the request for the new state
11589 var request = this.get("generateRequest")(oState, this);
11591 // Purge selections
11592 this.unselectAllRows();
11593 this.unselectAllCells();
11595 // Get the new data from the server
11597 success : this.onDataReturnSetRows,
11598 failure : this.onDataReturnSetRows,
11599 argument : oState, // Pass along the new state to the callback
11602 this._oDataSource.sendRequest(request, callback);
11604 // Client-side pagination
11606 // Set the core pagination values silently (the second param)
11607 // to avoid looping back through the changeRequest mechanism
11608 oPaginatorState.paginator.setStartIndex(oPaginatorState.recordOffset,true);
11609 oPaginatorState.paginator.setRowsPerPage(oPaginatorState.rowsPerPage,true);
11668 // SELECTION/HIGHLIGHTING
11671 * Reference to last highlighted cell element
11673 * @property _elLastHighlightedTd
11674 * @type HTMLElement
11677 _elLastHighlightedTd : null,
11680 * ID string of last highlighted row element
11682 * @property _sLastHighlightedTrElId
11686 //_sLastHighlightedTrElId : null,
11689 * Array to track row selections (by sRecordId) and/or cell selections
11690 * (by {recordId:sRecordId, columnKey:sColumnKey})
11692 * @property _aSelections
11696 _aSelections : null,
11699 * Record instance of the row selection anchor.
11701 * @property _oAnchorRecord
11702 * @type YAHOO.widget.Record
11705 _oAnchorRecord : null,
11708 * Object literal representing cell selection anchor:
11709 * {recordId:sRecordId, columnKey:sColumnKey}.
11711 * @property _oAnchorCell
11715 _oAnchorCell : null,
11718 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
11719 * from all TR elements on the page.
11721 * @method _unselectAllTrEls
11724 _unselectAllTrEls : function() {
11725 var selectedRows = Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
11726 Dom.removeClass(selectedRows, DT.CLASS_SELECTED);
11730 * Returns object literal of values that represent the selection trigger. Used
11731 * to determine selection behavior resulting from a key event.
11733 * @method _getSelectionTrigger
11736 _getSelectionTrigger : function() {
11737 var sMode = this.get("selectionMode");
11739 var oTriggerCell, oTriggerRecord, nTriggerRecordIndex, elTriggerRow, nTriggerTrIndex;
11742 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
11743 oTriggerCell = this.getLastSelectedCell();
11744 // No selected cells found
11745 if(!oTriggerCell) {
11749 oTriggerRecord = this.getRecord(oTriggerCell.recordId);
11750 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
11751 elTriggerRow = this.getTrEl(oTriggerRecord);
11752 nTriggerTrIndex = this.getTrIndex(elTriggerRow);
11754 // Selected cell not found on this page
11755 if(nTriggerTrIndex === null) {
11759 oTrigger.record = oTriggerRecord;
11760 oTrigger.recordIndex = nTriggerRecordIndex;
11761 oTrigger.el = this.getTdEl(oTriggerCell);
11762 oTrigger.trIndex = nTriggerTrIndex;
11763 oTrigger.column = this.getColumn(oTriggerCell.columnKey);
11764 oTrigger.colKeyIndex = oTrigger.column.getKeyIndex();
11765 oTrigger.cell = oTriggerCell;
11772 oTriggerRecord = this.getLastSelectedRecord();
11773 // No selected rows found
11774 if(!oTriggerRecord) {
11778 // Selected row found, but is it on current page?
11779 oTriggerRecord = this.getRecord(oTriggerRecord);
11780 nTriggerRecordIndex = this.getRecordIndex(oTriggerRecord);
11781 elTriggerRow = this.getTrEl(oTriggerRecord);
11782 nTriggerTrIndex = this.getTrIndex(elTriggerRow);
11784 // Selected row not found on this page
11785 if(nTriggerTrIndex === null) {
11789 oTrigger.record = oTriggerRecord;
11790 oTrigger.recordIndex = nTriggerRecordIndex;
11791 oTrigger.el = elTriggerRow;
11792 oTrigger.trIndex = nTriggerTrIndex;
11800 * Returns object literal of values that represent the selection anchor. Used
11801 * to determine selection behavior resulting from a user event.
11803 * @method _getSelectionAnchor
11804 * @param oTrigger {Object} (Optional) Object literal of selection trigger values
11805 * (for key events).
11808 _getSelectionAnchor : function(oTrigger) {
11809 var sMode = this.get("selectionMode");
11811 var oAnchorRecord, nAnchorRecordIndex, nAnchorTrIndex;
11814 if((sMode == "cellblock") || (sMode == "cellrange") || (sMode == "singlecell")) {
11815 // Validate anchor cell
11816 var oAnchorCell = this._oAnchorCell;
11819 oAnchorCell = this._oAnchorCell = oTrigger.cell;
11825 oAnchorRecord = this._oAnchorCell.record;
11826 nAnchorRecordIndex = this._oRecordSet.getRecordIndex(oAnchorRecord);
11827 nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
11828 // If anchor cell is not on this page...
11829 if(nAnchorTrIndex === null) {
11830 // ...set TR index equal to top TR
11831 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
11832 nAnchorTrIndex = 0;
11834 // ...set TR index equal to bottom TR
11836 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
11840 oAnchor.record = oAnchorRecord;
11841 oAnchor.recordIndex = nAnchorRecordIndex;
11842 oAnchor.trIndex = nAnchorTrIndex;
11843 oAnchor.column = this._oAnchorCell.column;
11844 oAnchor.colKeyIndex = oAnchor.column.getKeyIndex();
11845 oAnchor.cell = oAnchorCell;
11850 oAnchorRecord = this._oAnchorRecord;
11851 if(!oAnchorRecord) {
11853 oAnchorRecord = this._oAnchorRecord = oTrigger.record;
11860 nAnchorRecordIndex = this.getRecordIndex(oAnchorRecord);
11861 nAnchorTrIndex = this.getTrIndex(oAnchorRecord);
11862 // If anchor row is not on this page...
11863 if(nAnchorTrIndex === null) {
11864 // ...set TR index equal to top TR
11865 if(nAnchorRecordIndex < this.getRecordIndex(this.getFirstTrEl())) {
11866 nAnchorTrIndex = 0;
11868 // ...set TR index equal to bottom TR
11870 nAnchorTrIndex = this.getRecordIndex(this.getLastTrEl());
11874 oAnchor.record = oAnchorRecord;
11875 oAnchor.recordIndex = nAnchorRecordIndex;
11876 oAnchor.trIndex = nAnchorTrIndex;
11882 * Determines selection behavior resulting from a mouse event when selection mode
11883 * is set to "standard".
11885 * @method _handleStandardSelectionByMouse
11886 * @param oArgs.event {HTMLEvent} Event object.
11887 * @param oArgs.target {HTMLElement} Target element.
11890 _handleStandardSelectionByMouse : function(oArgs) {
11891 var elTarget = oArgs.target;
11893 // Validate target row
11894 var elTargetRow = this.getTrEl(elTarget);
11896 var e = oArgs.event;
11897 var bSHIFT = e.shiftKey;
11898 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
11900 var oTargetRecord = this.getRecord(elTargetRow);
11901 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
11903 var oAnchor = this._getSelectionAnchor();
11907 // Both SHIFT and CTRL
11908 if(bSHIFT && bCTRL) {
11911 if(this.isSelected(oAnchor.record)) {
11912 // Select all rows between anchor row and target row, including target row
11913 if(oAnchor.recordIndex < nTargetRecordIndex) {
11914 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex; i++) {
11915 if(!this.isSelected(i)) {
11920 // Select all rows between target row and anchor row, including target row
11922 for(i=oAnchor.recordIndex-1; i>=nTargetRecordIndex; i--) {
11923 if(!this.isSelected(i)) {
11930 // Unselect all rows between anchor row and target row
11931 if(oAnchor.recordIndex < nTargetRecordIndex) {
11932 for(i=oAnchor.recordIndex+1; i<=nTargetRecordIndex-1; i++) {
11933 if(this.isSelected(i)) {
11934 this.unselectRow(i);
11938 // Unselect all rows between target row and anchor row
11940 for(i=nTargetRecordIndex+1; i<=oAnchor.recordIndex-1; i++) {
11941 if(this.isSelected(i)) {
11942 this.unselectRow(i);
11946 // Select the target row
11947 this.selectRow(oTargetRecord);
11953 this._oAnchorRecord = oTargetRecord;
11955 // Toggle selection of target
11956 if(this.isSelected(oTargetRecord)) {
11957 this.unselectRow(oTargetRecord);
11960 this.selectRow(oTargetRecord);
11966 this.unselectAllRows();
11970 // Select all rows between anchor row and target row,
11971 // including the anchor row and target row
11972 if(oAnchor.recordIndex < nTargetRecordIndex) {
11973 for(i=oAnchor.recordIndex; i<=nTargetRecordIndex; i++) {
11977 // Select all rows between target row and anchor row,
11978 // including the target row and anchor row
11980 for(i=oAnchor.recordIndex; i>=nTargetRecordIndex; i--) {
11988 this._oAnchorRecord = oTargetRecord;
11990 // Select target row only
11991 this.selectRow(oTargetRecord);
11997 this._oAnchorRecord = oTargetRecord;
11999 // Toggle selection of target
12000 if(this.isSelected(oTargetRecord)) {
12001 this.unselectRow(oTargetRecord);
12004 this.selectRow(oTargetRecord);
12007 // Neither SHIFT nor CTRL
12009 this._handleSingleSelectionByMouse(oArgs);
12016 * Determines selection behavior resulting from a key event when selection mode
12017 * is set to "standard".
12019 * @method _handleStandardSelectionByKey
12020 * @param e {HTMLEvent} Event object.
12023 _handleStandardSelectionByKey : function(e) {
12024 var nKey = Ev.getCharCode(e);
12026 if((nKey == 38) || (nKey == 40)) {
12027 var bSHIFT = e.shiftKey;
12029 // Validate trigger
12030 var oTrigger = this._getSelectionTrigger();
12031 // Arrow selection only works if last selected row is on current page
12039 var oAnchor = this._getSelectionAnchor(oTrigger);
12041 // Determine which direction we're going to
12043 // Selecting down away from anchor row
12044 if((nKey == 40) && (oAnchor.recordIndex <= oTrigger.trIndex)) {
12045 this.selectRow(this.getNextTrEl(oTrigger.el));
12047 // Selecting up away from anchor row
12048 else if((nKey == 38) && (oAnchor.recordIndex >= oTrigger.trIndex)) {
12049 this.selectRow(this.getPreviousTrEl(oTrigger.el));
12051 // Unselect trigger
12053 this.unselectRow(oTrigger.el);
12057 this._handleSingleSelectionByKey(e);
12063 * Determines selection behavior resulting from a mouse event when selection mode
12064 * is set to "single".
12066 * @method _handleSingleSelectionByMouse
12067 * @param oArgs.event {HTMLEvent} Event object.
12068 * @param oArgs.target {HTMLElement} Target element.
12071 _handleSingleSelectionByMouse : function(oArgs) {
12072 var elTarget = oArgs.target;
12074 // Validate target row
12075 var elTargetRow = this.getTrEl(elTarget);
12077 var oTargetRecord = this.getRecord(elTargetRow);
12080 this._oAnchorRecord = oTargetRecord;
12082 // Select only target
12083 this.unselectAllRows();
12084 this.selectRow(oTargetRecord);
12089 * Determines selection behavior resulting from a key event when selection mode
12090 * is set to "single".
12092 * @method _handleSingleSelectionByKey
12093 * @param e {HTMLEvent} Event object.
12096 _handleSingleSelectionByKey : function(e) {
12097 var nKey = Ev.getCharCode(e);
12099 if((nKey == 38) || (nKey == 40)) {
12100 // Validate trigger
12101 var oTrigger = this._getSelectionTrigger();
12102 // Arrow selection only works if last selected row is on current page
12109 // Determine the new row to select
12111 if(nKey == 38) { // arrow up
12112 elNew = this.getPreviousTrEl(oTrigger.el);
12114 // Validate new row
12115 if(elNew === null) {
12116 //TODO: wrap around to last tr on current page
12117 //elNew = this.getLastTrEl();
12119 //TODO: wrap back to last tr of previous page
12121 // Top row selection is sticky
12122 elNew = this.getFirstTrEl();
12125 else if(nKey == 40) { // arrow down
12126 elNew = this.getNextTrEl(oTrigger.el);
12128 // Validate new row
12129 if(elNew === null) {
12130 //TODO: wrap around to first tr on current page
12131 //elNew = this.getFirstTrEl();
12133 //TODO: wrap forward to first tr of previous page
12135 // Bottom row selection is sticky
12136 elNew = this.getLastTrEl();
12140 // Unselect all rows
12141 this.unselectAllRows();
12143 // Select the new row
12144 this.selectRow(elNew);
12147 this._oAnchorRecord = this.getRecord(elNew);
12152 * Determines selection behavior resulting from a mouse event when selection mode
12153 * is set to "cellblock".
12155 * @method _handleCellBlockSelectionByMouse
12156 * @param oArgs.event {HTMLEvent} Event object.
12157 * @param oArgs.target {HTMLElement} Target element.
12160 _handleCellBlockSelectionByMouse : function(oArgs) {
12161 var elTarget = oArgs.target;
12163 // Validate target cell
12164 var elTargetCell = this.getTdEl(elTarget);
12166 var e = oArgs.event;
12167 var bSHIFT = e.shiftKey;
12168 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
12170 var elTargetRow = this.getTrEl(elTargetCell);
12171 var nTargetTrIndex = this.getTrIndex(elTargetRow);
12172 var oTargetColumn = this.getColumn(elTargetCell);
12173 var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
12174 var oTargetRecord = this.getRecord(elTargetRow);
12175 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
12176 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
12178 var oAnchor = this._getSelectionAnchor();
12180 var allRows = this.getTbodyEl().rows;
12181 var startIndex, endIndex, currentRow, i, j;
12183 // Both SHIFT and CTRL
12184 if(bSHIFT && bCTRL) {
12188 // Anchor is selected
12189 if(this.isSelected(oAnchor.cell)) {
12190 // All cells are on the same row
12191 if(oAnchor.recordIndex === nTargetRecordIndex) {
12192 // Select all cells between anchor cell and target cell, including target cell
12193 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12194 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
12195 this.selectCell(elTargetRow.cells[i]);
12198 // Select all cells between target cell and anchor cell, including target cell
12199 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12200 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
12201 this.selectCell(elTargetRow.cells[i]);
12205 // Anchor row is above target row
12206 else if(oAnchor.recordIndex < nTargetRecordIndex) {
12207 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
12208 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
12210 // Select all cells from startIndex to endIndex on rows between anchor row and target row
12211 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12212 for(j=startIndex; j<=endIndex; j++) {
12213 this.selectCell(allRows[i].cells[j]);
12217 // Anchor row is below target row
12219 startIndex = Math.min(oAnchor.trIndex, nTargetColKeyIndex);
12220 endIndex = Math.max(oAnchor.trIndex, nTargetColKeyIndex);
12222 // Select all cells from startIndex to endIndex on rows between target row and anchor row
12223 for(i=oAnchor.trIndex; i>=nTargetTrIndex; i--) {
12224 for(j=endIndex; j>=startIndex; j--) {
12225 this.selectCell(allRows[i].cells[j]);
12230 // Anchor cell is unselected
12232 // All cells are on the same row
12233 if(oAnchor.recordIndex === nTargetRecordIndex) {
12234 // Unselect all cells between anchor cell and target cell
12235 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12236 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
12237 this.unselectCell(elTargetRow.cells[i]);
12240 // Select all cells between target cell and anchor cell
12241 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12242 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
12243 this.unselectCell(elTargetRow.cells[i]);
12247 // Anchor row is above target row
12248 if(oAnchor.recordIndex < nTargetRecordIndex) {
12249 // Unselect all cells from anchor cell to target cell
12250 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12251 currentRow = allRows[i];
12252 for(j=0; j<currentRow.cells.length; j++) {
12253 // This is the anchor row, only unselect cells after the anchor cell
12254 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
12255 if(j>oAnchor.colKeyIndex) {
12256 this.unselectCell(currentRow.cells[j]);
12259 // This is the target row, only unelect cells before the target cell
12260 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
12261 if(j<nTargetColKeyIndex) {
12262 this.unselectCell(currentRow.cells[j]);
12265 // Unselect all cells on this row
12267 this.unselectCell(currentRow.cells[j]);
12272 // Anchor row is below target row
12274 // Unselect all cells from target cell to anchor cell
12275 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12276 currentRow = allRows[i];
12277 for(j=0; j<currentRow.cells.length; j++) {
12278 // This is the target row, only unselect cells after the target cell
12279 if(currentRow.sectionRowIndex == nTargetTrIndex) {
12280 if(j>nTargetColKeyIndex) {
12281 this.unselectCell(currentRow.cells[j]);
12284 // This is the anchor row, only unselect cells before the anchor cell
12285 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12286 if(j<oAnchor.colKeyIndex) {
12287 this.unselectCell(currentRow.cells[j]);
12290 // Unselect all cells on this row
12292 this.unselectCell(currentRow.cells[j]);
12298 // Select the target cell
12299 this.selectCell(elTargetCell);
12305 this._oAnchorCell = oTargetCell;
12307 // Toggle selection of target
12308 if(this.isSelected(oTargetCell)) {
12309 this.unselectCell(oTargetCell);
12312 this.selectCell(oTargetCell);
12319 this.unselectAllCells();
12323 // All cells are on the same row
12324 if(oAnchor.recordIndex === nTargetRecordIndex) {
12325 // Select all cells between anchor cell and target cell,
12326 // including the anchor cell and target cell
12327 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12328 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
12329 this.selectCell(elTargetRow.cells[i]);
12332 // Select all cells between target cell and anchor cell
12333 // including the target cell and anchor cell
12334 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12335 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
12336 this.selectCell(elTargetRow.cells[i]);
12340 // Anchor row is above target row
12341 else if(oAnchor.recordIndex < nTargetRecordIndex) {
12342 // Select the cellblock from anchor cell to target cell
12343 // including the anchor cell and the target cell
12344 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
12345 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
12347 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12348 for(j=startIndex; j<=endIndex; j++) {
12349 this.selectCell(allRows[i].cells[j]);
12353 // Anchor row is below target row
12355 // Select the cellblock from target cell to anchor cell
12356 // including the target cell and the anchor cell
12357 startIndex = Math.min(oAnchor.colKeyIndex, nTargetColKeyIndex);
12358 endIndex = Math.max(oAnchor.colKeyIndex, nTargetColKeyIndex);
12360 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12361 for(j=startIndex; j<=endIndex; j++) {
12362 this.selectCell(allRows[i].cells[j]);
12370 this._oAnchorCell = oTargetCell;
12372 // Select target only
12373 this.selectCell(oTargetCell);
12380 this._oAnchorCell = oTargetCell;
12382 // Toggle selection of target
12383 if(this.isSelected(oTargetCell)) {
12384 this.unselectCell(oTargetCell);
12387 this.selectCell(oTargetCell);
12391 // Neither SHIFT nor CTRL
12393 this._handleSingleCellSelectionByMouse(oArgs);
12399 * Determines selection behavior resulting from a key event when selection mode
12400 * is set to "cellblock".
12402 * @method _handleCellBlockSelectionByKey
12403 * @param e {HTMLEvent} Event object.
12406 _handleCellBlockSelectionByKey : function(e) {
12407 var nKey = Ev.getCharCode(e);
12408 var bSHIFT = e.shiftKey;
12409 if((nKey == 9) || !bSHIFT) {
12410 this._handleSingleCellSelectionByKey(e);
12414 if((nKey > 36) && (nKey < 41)) {
12415 // Validate trigger
12416 var oTrigger = this._getSelectionTrigger();
12417 // Arrow selection only works if last selected row is on current page
12425 var oAnchor = this._getSelectionAnchor(oTrigger);
12427 var i, startIndex, endIndex, elNew, elNewRow;
12428 var allRows = this.getTbodyEl().rows;
12429 var elThisRow = oTrigger.el.parentNode;
12431 // Determine which direction we're going to
12433 if(nKey == 40) { // arrow down
12434 // Selecting away from anchor cell
12435 if(oAnchor.recordIndex <= oTrigger.recordIndex) {
12436 // Select the horiz block on the next row...
12437 // ...making sure there is room below the trigger row
12438 elNewRow = this.getNextTrEl(oTrigger.el);
12440 startIndex = oAnchor.colKeyIndex;
12441 endIndex = oTrigger.colKeyIndex;
12443 if(startIndex > endIndex) {
12444 for(i=startIndex; i>=endIndex; i--) {
12445 elNew = elNewRow.cells[i];
12446 this.selectCell(elNew);
12451 for(i=startIndex; i<=endIndex; i++) {
12452 elNew = elNewRow.cells[i];
12453 this.selectCell(elNew);
12458 // Unselecting towards anchor cell
12460 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12461 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12462 // Unselect the horiz block on this row towards the next row
12463 for(i=startIndex; i<=endIndex; i++) {
12464 this.unselectCell(elThisRow.cells[i]);
12469 else if(nKey == 38) {
12470 // Selecting away from anchor cell
12471 if(oAnchor.recordIndex >= oTrigger.recordIndex) {
12472 // Select the horiz block on the previous row...
12473 // ...making sure there is room
12474 elNewRow = this.getPreviousTrEl(oTrigger.el);
12476 // Select in order from anchor to trigger...
12477 startIndex = oAnchor.colKeyIndex;
12478 endIndex = oTrigger.colKeyIndex;
12480 if(startIndex > endIndex) {
12481 for(i=startIndex; i>=endIndex; i--) {
12482 elNew = elNewRow.cells[i];
12483 this.selectCell(elNew);
12488 for(i=startIndex; i<=endIndex; i++) {
12489 elNew = elNewRow.cells[i];
12490 this.selectCell(elNew);
12495 // Unselecting towards anchor cell
12497 startIndex = Math.min(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12498 endIndex = Math.max(oAnchor.colKeyIndex, oTrigger.colKeyIndex);
12499 // Unselect the horiz block on this row towards the previous row
12500 for(i=startIndex; i<=endIndex; i++) {
12501 this.unselectCell(elThisRow.cells[i]);
12506 else if(nKey == 39) {
12507 // Selecting away from anchor cell
12508 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
12509 // Select the next vert block to the right...
12510 // ...making sure there is room
12511 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
12512 // Select in order from anchor to trigger...
12513 startIndex = oAnchor.trIndex;
12514 endIndex = oTrigger.trIndex;
12516 if(startIndex > endIndex) {
12517 for(i=startIndex; i>=endIndex; i--) {
12518 elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
12519 this.selectCell(elNew);
12524 for(i=startIndex; i<=endIndex; i++) {
12525 elNew = allRows[i].cells[oTrigger.colKeyIndex+1];
12526 this.selectCell(elNew);
12531 // Unselecting towards anchor cell
12533 // Unselect the vert block on this column towards the right
12534 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
12535 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
12536 for(i=startIndex; i<=endIndex; i++) {
12537 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
12542 else if(nKey == 37) {
12543 // Selecting away from anchor cell
12544 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
12545 //Select the previous vert block to the left
12546 if(oTrigger.colKeyIndex > 0) {
12547 // Select in order from anchor to trigger...
12548 startIndex = oAnchor.trIndex;
12549 endIndex = oTrigger.trIndex;
12551 if(startIndex > endIndex) {
12552 for(i=startIndex; i>=endIndex; i--) {
12553 elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
12554 this.selectCell(elNew);
12559 for(i=startIndex; i<=endIndex; i++) {
12560 elNew = allRows[i].cells[oTrigger.colKeyIndex-1];
12561 this.selectCell(elNew);
12566 // Unselecting towards anchor cell
12568 // Unselect the vert block on this column towards the left
12569 startIndex = Math.min(oAnchor.trIndex, oTrigger.trIndex);
12570 endIndex = Math.max(oAnchor.trIndex, oTrigger.trIndex);
12571 for(i=startIndex; i<=endIndex; i++) {
12572 this.unselectCell(allRows[i].cells[oTrigger.colKeyIndex]);
12580 * Determines selection behavior resulting from a mouse event when selection mode
12581 * is set to "cellrange".
12583 * @method _handleCellRangeSelectionByMouse
12584 * @param oArgs.event {HTMLEvent} Event object.
12585 * @param oArgs.target {HTMLElement} Target element.
12588 _handleCellRangeSelectionByMouse : function(oArgs) {
12589 var elTarget = oArgs.target;
12591 // Validate target cell
12592 var elTargetCell = this.getTdEl(elTarget);
12594 var e = oArgs.event;
12595 var bSHIFT = e.shiftKey;
12596 var bCTRL = e.ctrlKey || ((navigator.userAgent.toLowerCase().indexOf("mac") != -1) && e.metaKey);
12598 var elTargetRow = this.getTrEl(elTargetCell);
12599 var nTargetTrIndex = this.getTrIndex(elTargetRow);
12600 var oTargetColumn = this.getColumn(elTargetCell);
12601 var nTargetColKeyIndex = oTargetColumn.getKeyIndex();
12602 var oTargetRecord = this.getRecord(elTargetRow);
12603 var nTargetRecordIndex = this._oRecordSet.getRecordIndex(oTargetRecord);
12604 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
12606 var oAnchor = this._getSelectionAnchor();
12608 var allRows = this.getTbodyEl().rows;
12609 var currentRow, i, j;
12611 // Both SHIFT and CTRL
12612 if(bSHIFT && bCTRL) {
12616 // Anchor is selected
12617 if(this.isSelected(oAnchor.cell)) {
12618 // All cells are on the same row
12619 if(oAnchor.recordIndex === nTargetRecordIndex) {
12620 // Select all cells between anchor cell and target cell, including target cell
12621 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12622 for(i=oAnchor.colKeyIndex+1; i<=nTargetColKeyIndex; i++) {
12623 this.selectCell(elTargetRow.cells[i]);
12626 // Select all cells between target cell and anchor cell, including target cell
12627 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12628 for(i=nTargetColKeyIndex; i<oAnchor.colKeyIndex; i++) {
12629 this.selectCell(elTargetRow.cells[i]);
12633 // Anchor row is above target row
12634 else if(oAnchor.recordIndex < nTargetRecordIndex) {
12635 // Select all cells on anchor row from anchor cell to the end of the row
12636 for(i=oAnchor.colKeyIndex+1; i<elTargetRow.cells.length; i++) {
12637 this.selectCell(elTargetRow.cells[i]);
12640 // Select all cells on all rows between anchor row and target row
12641 for(i=oAnchor.trIndex+1; i<nTargetTrIndex; i++) {
12642 for(j=0; j<allRows[i].cells.length; j++){
12643 this.selectCell(allRows[i].cells[j]);
12647 // Select all cells on target row from first cell to the target cell
12648 for(i=0; i<=nTargetColKeyIndex; i++) {
12649 this.selectCell(elTargetRow.cells[i]);
12652 // Anchor row is below target row
12654 // Select all cells on target row from target cell to the end of the row
12655 for(i=nTargetColKeyIndex; i<elTargetRow.cells.length; i++) {
12656 this.selectCell(elTargetRow.cells[i]);
12659 // Select all cells on all rows between target row and anchor row
12660 for(i=nTargetTrIndex+1; i<oAnchor.trIndex; i++) {
12661 for(j=0; j<allRows[i].cells.length; j++){
12662 this.selectCell(allRows[i].cells[j]);
12666 // Select all cells on anchor row from first cell to the anchor cell
12667 for(i=0; i<oAnchor.colKeyIndex; i++) {
12668 this.selectCell(elTargetRow.cells[i]);
12672 // Anchor cell is unselected
12674 // All cells are on the same row
12675 if(oAnchor.recordIndex === nTargetRecordIndex) {
12676 // Unselect all cells between anchor cell and target cell
12677 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12678 for(i=oAnchor.colKeyIndex+1; i<nTargetColKeyIndex; i++) {
12679 this.unselectCell(elTargetRow.cells[i]);
12682 // Select all cells between target cell and anchor cell
12683 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12684 for(i=nTargetColKeyIndex+1; i<oAnchor.colKeyIndex; i++) {
12685 this.unselectCell(elTargetRow.cells[i]);
12689 // Anchor row is above target row
12690 if(oAnchor.recordIndex < nTargetRecordIndex) {
12691 // Unselect all cells from anchor cell to target cell
12692 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12693 currentRow = allRows[i];
12694 for(j=0; j<currentRow.cells.length; j++) {
12695 // This is the anchor row, only unselect cells after the anchor cell
12696 if(currentRow.sectionRowIndex === oAnchor.trIndex) {
12697 if(j>oAnchor.colKeyIndex) {
12698 this.unselectCell(currentRow.cells[j]);
12701 // This is the target row, only unelect cells before the target cell
12702 else if(currentRow.sectionRowIndex === nTargetTrIndex) {
12703 if(j<nTargetColKeyIndex) {
12704 this.unselectCell(currentRow.cells[j]);
12707 // Unselect all cells on this row
12709 this.unselectCell(currentRow.cells[j]);
12714 // Anchor row is below target row
12716 // Unselect all cells from target cell to anchor cell
12717 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12718 currentRow = allRows[i];
12719 for(j=0; j<currentRow.cells.length; j++) {
12720 // This is the target row, only unselect cells after the target cell
12721 if(currentRow.sectionRowIndex == nTargetTrIndex) {
12722 if(j>nTargetColKeyIndex) {
12723 this.unselectCell(currentRow.cells[j]);
12726 // This is the anchor row, only unselect cells before the anchor cell
12727 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12728 if(j<oAnchor.colKeyIndex) {
12729 this.unselectCell(currentRow.cells[j]);
12732 // Unselect all cells on this row
12734 this.unselectCell(currentRow.cells[j]);
12740 // Select the target cell
12741 this.selectCell(elTargetCell);
12747 this._oAnchorCell = oTargetCell;
12749 // Toggle selection of target
12750 if(this.isSelected(oTargetCell)) {
12751 this.unselectCell(oTargetCell);
12754 this.selectCell(oTargetCell);
12761 this.unselectAllCells();
12765 // All cells are on the same row
12766 if(oAnchor.recordIndex === nTargetRecordIndex) {
12767 // Select all cells between anchor cell and target cell,
12768 // including the anchor cell and target cell
12769 if(oAnchor.colKeyIndex < nTargetColKeyIndex) {
12770 for(i=oAnchor.colKeyIndex; i<=nTargetColKeyIndex; i++) {
12771 this.selectCell(elTargetRow.cells[i]);
12774 // Select all cells between target cell and anchor cell
12775 // including the target cell and anchor cell
12776 else if(nTargetColKeyIndex < oAnchor.colKeyIndex) {
12777 for(i=nTargetColKeyIndex; i<=oAnchor.colKeyIndex; i++) {
12778 this.selectCell(elTargetRow.cells[i]);
12782 // Anchor row is above target row
12783 else if(oAnchor.recordIndex < nTargetRecordIndex) {
12784 // Select all cells from anchor cell to target cell
12785 // including the anchor cell and target cell
12786 for(i=oAnchor.trIndex; i<=nTargetTrIndex; i++) {
12787 currentRow = allRows[i];
12788 for(j=0; j<currentRow.cells.length; j++) {
12789 // This is the anchor row, only select the anchor cell and after
12790 if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12791 if(j>=oAnchor.colKeyIndex) {
12792 this.selectCell(currentRow.cells[j]);
12795 // This is the target row, only select the target cell and before
12796 else if(currentRow.sectionRowIndex == nTargetTrIndex) {
12797 if(j<=nTargetColKeyIndex) {
12798 this.selectCell(currentRow.cells[j]);
12801 // Select all cells on this row
12803 this.selectCell(currentRow.cells[j]);
12808 // Anchor row is below target row
12810 // Select all cells from target cell to anchor cell,
12811 // including the target cell and anchor cell
12812 for(i=nTargetTrIndex; i<=oAnchor.trIndex; i++) {
12813 currentRow = allRows[i];
12814 for(j=0; j<currentRow.cells.length; j++) {
12815 // This is the target row, only select the target cell and after
12816 if(currentRow.sectionRowIndex == nTargetTrIndex) {
12817 if(j>=nTargetColKeyIndex) {
12818 this.selectCell(currentRow.cells[j]);
12821 // This is the anchor row, only select the anchor cell and before
12822 else if(currentRow.sectionRowIndex == oAnchor.trIndex) {
12823 if(j<=oAnchor.colKeyIndex) {
12824 this.selectCell(currentRow.cells[j]);
12827 // Select all cells on this row
12829 this.selectCell(currentRow.cells[j]);
12838 this._oAnchorCell = oTargetCell;
12840 // Select target only
12841 this.selectCell(oTargetCell);
12850 this._oAnchorCell = oTargetCell;
12852 // Toggle selection of target
12853 if(this.isSelected(oTargetCell)) {
12854 this.unselectCell(oTargetCell);
12857 this.selectCell(oTargetCell);
12861 // Neither SHIFT nor CTRL
12863 this._handleSingleCellSelectionByMouse(oArgs);
12869 * Determines selection behavior resulting from a key event when selection mode
12870 * is set to "cellrange".
12872 * @method _handleCellRangeSelectionByKey
12873 * @param e {HTMLEvent} Event object.
12876 _handleCellRangeSelectionByKey : function(e) {
12877 var nKey = Ev.getCharCode(e);
12878 var bSHIFT = e.shiftKey;
12879 if((nKey == 9) || !bSHIFT) {
12880 this._handleSingleCellSelectionByKey(e);
12884 if((nKey > 36) && (nKey < 41)) {
12885 // Validate trigger
12886 var oTrigger = this._getSelectionTrigger();
12887 // Arrow selection only works if last selected row is on current page
12895 var oAnchor = this._getSelectionAnchor(oTrigger);
12897 var i, elNewRow, elNew;
12898 var allRows = this.getTbodyEl().rows;
12899 var elThisRow = oTrigger.el.parentNode;
12903 elNewRow = this.getNextTrEl(oTrigger.el);
12905 // Selecting away from anchor cell
12906 if(oAnchor.recordIndex <= oTrigger.recordIndex) {
12907 // Select all cells to the end of this row
12908 for(i=oTrigger.colKeyIndex+1; i<elThisRow.cells.length; i++){
12909 elNew = elThisRow.cells[i];
12910 this.selectCell(elNew);
12913 // Select some of the cells on the next row down
12915 for(i=0; i<=oTrigger.colKeyIndex; i++){
12916 elNew = elNewRow.cells[i];
12917 this.selectCell(elNew);
12921 // Unselecting towards anchor cell
12923 // Unselect all cells to the end of this row
12924 for(i=oTrigger.colKeyIndex; i<elThisRow.cells.length; i++){
12925 this.unselectCell(elThisRow.cells[i]);
12928 // Unselect some of the cells on the next row down
12930 for(i=0; i<oTrigger.colKeyIndex; i++){
12931 this.unselectCell(elNewRow.cells[i]);
12937 else if(nKey == 38) {
12938 elNewRow = this.getPreviousTrEl(oTrigger.el);
12940 // Selecting away from anchor cell
12941 if(oAnchor.recordIndex >= oTrigger.recordIndex) {
12942 // Select all the cells to the beginning of this row
12943 for(i=oTrigger.colKeyIndex-1; i>-1; i--){
12944 elNew = elThisRow.cells[i];
12945 this.selectCell(elNew);
12948 // Select some of the cells from the end of the previous row
12950 for(i=elThisRow.cells.length-1; i>=oTrigger.colKeyIndex; i--){
12951 elNew = elNewRow.cells[i];
12952 this.selectCell(elNew);
12956 // Unselecting towards anchor cell
12958 // Unselect all the cells to the beginning of this row
12959 for(i=oTrigger.colKeyIndex; i>-1; i--){
12960 this.unselectCell(elThisRow.cells[i]);
12963 // Unselect some of the cells from the end of the previous row
12965 for(i=elThisRow.cells.length-1; i>oTrigger.colKeyIndex; i--){
12966 this.unselectCell(elNewRow.cells[i]);
12972 else if(nKey == 39) {
12973 elNewRow = this.getNextTrEl(oTrigger.el);
12975 // Selecting away from anchor cell
12976 if(oAnchor.recordIndex < oTrigger.recordIndex) {
12977 // Select the next cell to the right
12978 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
12979 elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
12980 this.selectCell(elNew);
12982 // Select the first cell of the next row
12983 else if(elNewRow) {
12984 elNew = elNewRow.cells[0];
12985 this.selectCell(elNew);
12988 // Unselecting towards anchor cell
12989 else if(oAnchor.recordIndex > oTrigger.recordIndex) {
12990 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
12992 // Unselect this cell towards the right
12993 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
12995 // Unselect this cells towards the first cell of the next row
12999 // Anchor is on this row
13001 // Selecting away from anchor
13002 if(oAnchor.colKeyIndex <= oTrigger.colKeyIndex) {
13003 // Select the next cell to the right
13004 if(oTrigger.colKeyIndex < elThisRow.cells.length-1) {
13005 elNew = elThisRow.cells[oTrigger.colKeyIndex+1];
13006 this.selectCell(elNew);
13008 // Select the first cell on the next row
13009 else if(oTrigger.trIndex < allRows.length-1){
13010 elNew = elNewRow.cells[0];
13011 this.selectCell(elNew);
13014 // Unselecting towards anchor
13016 // Unselect this cell towards the right
13017 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
13022 else if(nKey == 37) {
13023 elNewRow = this.getPreviousTrEl(oTrigger.el);
13025 // Unselecting towards the anchor
13026 if(oAnchor.recordIndex < oTrigger.recordIndex) {
13027 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
13029 // Unselect this cell towards the left
13030 if(oTrigger.colKeyIndex > 0) {
13032 // Unselect this cell towards the last cell of the previous row
13036 // Selecting towards the anchor
13037 else if(oAnchor.recordIndex > oTrigger.recordIndex) {
13038 // Select the next cell to the left
13039 if(oTrigger.colKeyIndex > 0) {
13040 elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
13041 this.selectCell(elNew);
13043 // Select the last cell of the previous row
13044 else if(oTrigger.trIndex > 0){
13045 elNew = elNewRow.cells[elNewRow.cells.length-1];
13046 this.selectCell(elNew);
13049 // Anchor is on this row
13051 // Selecting away from anchor cell
13052 if(oAnchor.colKeyIndex >= oTrigger.colKeyIndex) {
13053 // Select the next cell to the left
13054 if(oTrigger.colKeyIndex > 0) {
13055 elNew = elThisRow.cells[oTrigger.colKeyIndex-1];
13056 this.selectCell(elNew);
13058 // Select the last cell of the previous row
13059 else if(oTrigger.trIndex > 0){
13060 elNew = elNewRow.cells[elNewRow.cells.length-1];
13061 this.selectCell(elNew);
13064 // Unselecting towards anchor cell
13066 this.unselectCell(elThisRow.cells[oTrigger.colKeyIndex]);
13068 // Unselect this cell towards the left
13069 if(oTrigger.colKeyIndex > 0) {
13071 // Unselect this cell towards the last cell of the previous row
13081 * Determines selection behavior resulting from a mouse event when selection mode
13082 * is set to "singlecell".
13084 * @method _handleSingleCellSelectionByMouse
13085 * @param oArgs.event {HTMLEvent} Event object.
13086 * @param oArgs.target {HTMLElement} Target element.
13089 _handleSingleCellSelectionByMouse : function(oArgs) {
13090 var elTarget = oArgs.target;
13092 // Validate target cell
13093 var elTargetCell = this.getTdEl(elTarget);
13095 var elTargetRow = this.getTrEl(elTargetCell);
13096 var oTargetRecord = this.getRecord(elTargetRow);
13097 var oTargetColumn = this.getColumn(elTargetCell);
13098 var oTargetCell = {record:oTargetRecord, column:oTargetColumn};
13101 this._oAnchorCell = oTargetCell;
13103 // Select only target
13104 this.unselectAllCells();
13105 this.selectCell(oTargetCell);
13110 * Determines selection behavior resulting from a key event when selection mode
13111 * is set to "singlecell".
13113 * @method _handleSingleCellSelectionByKey
13114 * @param e {HTMLEvent} Event object.
13117 _handleSingleCellSelectionByKey : function(e) {
13118 var nKey = Ev.getCharCode(e);
13119 if((nKey == 9) || ((nKey > 36) && (nKey < 41))) {
13120 var bSHIFT = e.shiftKey;
13122 // Validate trigger
13123 var oTrigger = this._getSelectionTrigger();
13124 // Arrow selection only works if last selected row is on current page
13129 // Determine the new cell to select
13131 if(nKey == 40) { // Arrow down
13132 elNew = this.getBelowTdEl(oTrigger.el);
13134 // Validate new cell
13135 if(elNew === null) {
13136 //TODO: wrap around to first tr on current page
13138 //TODO: wrap forward to first tr of next page
13140 // Bottom selection is sticky
13141 elNew = oTrigger.el;
13144 else if(nKey == 38) { // Arrow up
13145 elNew = this.getAboveTdEl(oTrigger.el);
13147 // Validate new cell
13148 if(elNew === null) {
13149 //TODO: wrap around to last tr on current page
13151 //TODO: wrap back to last tr of previous page
13153 // Top selection is sticky
13154 elNew = oTrigger.el;
13157 else if((nKey == 39) || (!bSHIFT && (nKey == 9))) { // Arrow right or tab
13158 elNew = this.getNextTdEl(oTrigger.el);
13160 // Validate new cell
13161 if(elNew === null) {
13162 //TODO: wrap around to first td on current page
13164 //TODO: wrap forward to first td of next page
13166 // Top-left selection is sticky, and release TAB focus
13167 //elNew = oTrigger.el;
13171 else if((nKey == 37) || (bSHIFT && (nKey == 9))) { // Arrow left or shift-tab
13172 elNew = this.getPreviousTdEl(oTrigger.el);
13174 // Validate new cell
13175 if(elNew === null) {
13176 //TODO: wrap around to last td on current page
13178 //TODO: wrap back to last td of previous page
13180 // Bottom-right selection is sticky, and release TAB focus
13181 //elNew = oTrigger.el;
13188 // Unselect all cells
13189 this.unselectAllCells();
13191 // Select the new cell
13192 this.selectCell(elNew);
13195 this._oAnchorCell = {record:this.getRecord(elNew), column:this.getColumn(elNew)};
13200 * Returns array of selected TR elements on the page.
13202 * @method getSelectedTrEls
13203 * @return {HTMLElement[]} Array of selected TR elements.
13205 getSelectedTrEls : function() {
13206 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"tr",this._elTbody);
13210 * Sets given row to the selected state.
13212 * @method selectRow
13213 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
13214 * reference or ID string, Record instance, or RecordSet position index.
13216 selectRow : function(row) {
13217 var oRecord, elRow;
13219 if(row instanceof YAHOO.widget.Record) {
13220 oRecord = this._oRecordSet.getRecord(row);
13221 elRow = this.getTrEl(oRecord);
13223 else if(lang.isNumber(row)) {
13224 oRecord = this.getRecord(row);
13225 elRow = this.getTrEl(oRecord);
13228 elRow = this.getTrEl(row);
13229 oRecord = this.getRecord(elRow);
13233 // Update selection trackers
13234 var tracker = this._aSelections || [];
13235 var sRecordId = oRecord.getId();
13238 // Remove if already there:
13239 // Use Array.indexOf if available...
13240 /*if(tracker.indexOf && (tracker.indexOf(sRecordId) > -1)) {
13241 tracker.splice(tracker.indexOf(sRecordId),1);
13243 if(tracker.indexOf) {
13244 index = tracker.indexOf(sRecordId);
13247 // ...or do it the old-fashioned way
13249 for(var j=tracker.length-1; j>-1; j--) {
13250 if(tracker[j] === sRecordId){
13257 tracker.splice(index,1);
13261 tracker.push(sRecordId);
13262 this._aSelections = tracker;
13265 if(!this._oAnchorRecord) {
13266 this._oAnchorRecord = oRecord;
13271 Dom.addClass(elRow, DT.CLASS_SELECTED);
13274 this.fireEvent("rowSelectEvent", {record:oRecord, el:elRow});
13281 * Sets given row to the unselected state.
13283 * @method unselectRow
13284 * @param row {HTMLElement | String | YAHOO.widget.Record | Number} HTML element
13285 * reference or ID string, Record instance, or RecordSet position index.
13287 unselectRow : function(row) {
13288 var elRow = this.getTrEl(row);
13291 if(row instanceof YAHOO.widget.Record) {
13292 oRecord = this._oRecordSet.getRecord(row);
13294 else if(lang.isNumber(row)) {
13295 oRecord = this.getRecord(row);
13298 oRecord = this.getRecord(elRow);
13302 // Update selection trackers
13303 var tracker = this._aSelections || [];
13304 var sRecordId = oRecord.getId();
13307 // Use Array.indexOf if available...
13308 if(tracker.indexOf) {
13309 index = tracker.indexOf(sRecordId);
13311 // ...or do it the old-fashioned way
13313 for(var j=tracker.length-1; j>-1; j--) {
13314 if(tracker[j] === sRecordId){
13322 tracker.splice(index,1);
13323 this._aSelections = tracker;
13326 Dom.removeClass(elRow, DT.CLASS_SELECTED);
13328 this.fireEvent("rowUnselectEvent", {record:oRecord, el:elRow});
13336 * Clears out all row selections.
13338 * @method unselectAllRows
13340 unselectAllRows : function() {
13341 // Remove all rows from tracker
13342 var tracker = this._aSelections || [],
13345 for(var j=tracker.length-1; j>-1; j--) {
13346 if(lang.isString(tracker[j])){
13347 recId = tracker.splice(j,1);
13348 removed[removed.length] = this.getRecord(lang.isArray(recId) ? recId[0] : recId);
13353 this._aSelections = tracker;
13356 this._unselectAllTrEls();
13358 this.fireEvent("unselectAllRowsEvent", {records: removed});
13362 * Convenience method to remove the class YAHOO.widget.DataTable.CLASS_SELECTED
13363 * from all TD elements in the internal tracker.
13365 * @method _unselectAllTdEls
13368 _unselectAllTdEls : function() {
13369 var selectedCells = Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
13370 Dom.removeClass(selectedCells, DT.CLASS_SELECTED);
13374 * Returns array of selected TD elements on the page.
13376 * @method getSelectedTdEls
13377 * @return {HTMLElement[]} Array of selected TD elements.
13379 getSelectedTdEls : function() {
13380 return Dom.getElementsByClassName(DT.CLASS_SELECTED,"td",this._elTbody);
13384 * Sets given cell to the selected state.
13386 * @method selectCell
13387 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
13388 * object literal of syntax {record:oRecord, column:oColumn}.
13390 selectCell : function(cell) {
13391 //TODO: accept {record} in selectCell()
13392 var elCell = this.getTdEl(cell);
13395 var oRecord = this.getRecord(elCell);
13396 var oColumn = this.getColumn(this.getCellIndex(elCell));
13397 var sColumnKey = oColumn.getKey();
13399 if(oRecord && sColumnKey) {
13401 var tracker = this._aSelections || [];
13402 var sRecordId = oRecord.getId();
13405 for(var j=tracker.length-1; j>-1; j--) {
13406 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
13407 tracker.splice(j,1);
13413 tracker.push({recordId:sRecordId, columnKey:sColumnKey});
13416 this._aSelections = tracker;
13417 if(!this._oAnchorCell) {
13418 this._oAnchorCell = {record:oRecord, column:oColumn};
13422 Dom.addClass(elCell, DT.CLASS_SELECTED);
13424 this.fireEvent("cellSelectEvent", {record:oRecord, column:oColumn, key: sColumnKey, el:elCell});
13431 * Sets given cell to the unselected state.
13433 * @method unselectCell
13434 * @param cell {HTMLElement | String | Object} TD element or child of a TD element, or
13435 * object literal of syntax {record:oRecord, column:oColumn}.
13436 * @param cell {HTMLElement | String} DOM element reference or ID string
13437 * to DataTable page element or RecordSet index.
13439 unselectCell : function(cell) {
13440 var elCell = this.getTdEl(cell);
13443 var oRecord = this.getRecord(elCell);
13444 var oColumn = this.getColumn(this.getCellIndex(elCell));
13445 var sColumnKey = oColumn.getKey();
13447 if(oRecord && sColumnKey) {
13449 var tracker = this._aSelections || [];
13450 var id = oRecord.getId();
13453 for(var j=tracker.length-1; j>-1; j--) {
13454 if((tracker[j].recordId === id) && (tracker[j].columnKey === sColumnKey)){
13455 // Remove from tracker
13456 tracker.splice(j,1);
13459 this._aSelections = tracker;
13462 Dom.removeClass(elCell, DT.CLASS_SELECTED);
13464 this.fireEvent("cellUnselectEvent", {record:oRecord, column: oColumn, key:sColumnKey, el:elCell});
13473 * Clears out all cell selections.
13475 * @method unselectAllCells
13477 unselectAllCells : function() {
13478 // Remove all cells from tracker
13479 var tracker = this._aSelections || [];
13480 for(var j=tracker.length-1; j>-1; j--) {
13481 if(lang.isObject(tracker[j])){
13482 tracker.splice(j,1);
13487 this._aSelections = tracker;
13490 this._unselectAllTdEls();
13492 //TODO: send data to unselectAllCellsEvent handler
13493 this.fireEvent("unselectAllCellsEvent");
13497 * Returns true if given item is selected, false otherwise.
13499 * @method isSelected
13500 * @param o {String | HTMLElement | YAHOO.widget.Record | Number
13501 * {record:YAHOO.widget.Record, column:YAHOO.widget.Column} } TR or TD element by
13502 * reference or ID string, a Record instance, a RecordSet position index,
13503 * or an object literal representation
13505 * @return {Boolean} True if item is selected.
13507 isSelected : function(o) {
13508 if(o && (o.ownerDocument == document)) {
13509 return (Dom.hasClass(this.getTdEl(o),DT.CLASS_SELECTED) || Dom.hasClass(this.getTrEl(o),DT.CLASS_SELECTED));
13512 var oRecord, sRecordId, j;
13513 var tracker = this._aSelections;
13514 if(tracker && tracker.length > 0) {
13515 // Looking for a Record?
13516 if(o instanceof YAHOO.widget.Record) {
13519 else if(lang.isNumber(o)) {
13520 oRecord = this.getRecord(o);
13523 sRecordId = oRecord.getId();
13526 // Use Array.indexOf if available...
13527 if(tracker.indexOf) {
13528 if(tracker.indexOf(sRecordId) > -1) {
13532 // ...or do it the old-fashioned way
13534 for(j=tracker.length-1; j>-1; j--) {
13535 if(tracker[j] === sRecordId){
13541 // Looking for a cell
13542 else if(o.record && o.column){
13543 sRecordId = o.record.getId();
13544 var sColumnKey = o.column.getKey();
13546 for(j=tracker.length-1; j>-1; j--) {
13547 if((tracker[j].recordId === sRecordId) && (tracker[j].columnKey === sColumnKey)){
13558 * Returns selected rows as an array of Record IDs.
13560 * @method getSelectedRows
13561 * @return {String[]} Array of selected rows by Record ID.
13563 getSelectedRows : function() {
13564 var aSelectedRows = [];
13565 var tracker = this._aSelections || [];
13566 for(var j=0; j<tracker.length; j++) {
13567 if(lang.isString(tracker[j])){
13568 aSelectedRows.push(tracker[j]);
13571 return aSelectedRows;
13575 * Returns selected cells as an array of object literals:
13576 * {recordId:sRecordId, columnKey:sColumnKey}.
13578 * @method getSelectedCells
13579 * @return {Object[]} Array of selected cells by Record ID and Column ID.
13581 getSelectedCells : function() {
13582 var aSelectedCells = [];
13583 var tracker = this._aSelections || [];
13584 for(var j=0; j<tracker.length; j++) {
13585 if(tracker[j] && lang.isObject(tracker[j])){
13586 aSelectedCells.push(tracker[j]);
13589 return aSelectedCells;
13593 * Returns last selected Record ID.
13595 * @method getLastSelectedRecord
13596 * @return {String} Record ID of last selected row.
13598 getLastSelectedRecord : function() {
13599 var tracker = this._aSelections;
13600 if(tracker && tracker.length > 0) {
13601 for(var i=tracker.length-1; i>-1; i--) {
13602 if(lang.isString(tracker[i])){
13610 * Returns last selected cell as an object literal:
13611 * {recordId:sRecordId, columnKey:sColumnKey}.
13613 * @method getLastSelectedCell
13614 * @return {Object} Object literal representation of a cell.
13616 getLastSelectedCell : function() {
13617 var tracker = this._aSelections;
13618 if(tracker && tracker.length > 0) {
13619 for(var i=tracker.length-1; i>-1; i--) {
13620 if(tracker[i].recordId && tracker[i].columnKey){
13628 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given row.
13630 * @method highlightRow
13631 * @param row {HTMLElement | String} DOM element reference or ID string.
13633 highlightRow : function(row) {
13634 var elRow = this.getTrEl(row);
13637 // Make sure previous row is unhighlighted
13638 /* if(this._sLastHighlightedTrElId) {
13639 Dom.removeClass(this._sLastHighlightedTrElId,DT.CLASS_HIGHLIGHTED);
13641 var oRecord = this.getRecord(elRow);
13642 Dom.addClass(elRow,DT.CLASS_HIGHLIGHTED);
13643 //this._sLastHighlightedTrElId = elRow.id;
13644 this.fireEvent("rowHighlightEvent", {record:oRecord, el:elRow});
13650 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given row.
13652 * @method unhighlightRow
13653 * @param row {HTMLElement | String} DOM element reference or ID string.
13655 unhighlightRow : function(row) {
13656 var elRow = this.getTrEl(row);
13659 var oRecord = this.getRecord(elRow);
13660 Dom.removeClass(elRow,DT.CLASS_HIGHLIGHTED);
13661 this.fireEvent("rowUnhighlightEvent", {record:oRecord, el:elRow});
13667 * Assigns the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED to the given cell.
13669 * @method highlightCell
13670 * @param cell {HTMLElement | String} DOM element reference or ID string.
13672 highlightCell : function(cell) {
13673 var elCell = this.getTdEl(cell);
13676 // Make sure previous cell is unhighlighted
13677 if(this._elLastHighlightedTd) {
13678 this.unhighlightCell(this._elLastHighlightedTd);
13681 var oRecord = this.getRecord(elCell);
13682 var oColumn = this.getColumn(this.getCellIndex(elCell));
13683 var sColumnKey = oColumn.getKey();
13684 Dom.addClass(elCell,DT.CLASS_HIGHLIGHTED);
13685 this._elLastHighlightedTd = elCell;
13686 this.fireEvent("cellHighlightEvent", {record:oRecord, column:oColumn, key:sColumnKey, el:elCell});
13692 * Removes the class YAHOO.widget.DataTable.CLASS_HIGHLIGHTED from the given cell.
13694 * @method unhighlightCell
13695 * @param cell {HTMLElement | String} DOM element reference or ID string.
13697 unhighlightCell : function(cell) {
13698 var elCell = this.getTdEl(cell);
13701 var oRecord = this.getRecord(elCell);
13702 Dom.removeClass(elCell,DT.CLASS_HIGHLIGHTED);
13703 this._elLastHighlightedTd = null;
13704 this.fireEvent("cellUnhighlightEvent", {record:oRecord, column:this.getColumn(this.getCellIndex(elCell)), key:this.getColumn(this.getCellIndex(elCell)).getKey(), el:elCell});
13756 * Assigns CellEditor instance to existing Column.
13757 * @method addCellEditor
13758 * @param oColumn {YAHOO.widget.Column} Column instance.
13759 * @param oEditor {YAHOO.wdiget.CellEditor} CellEditor instance.
13761 addCellEditor : function(oColumn, oEditor) {
13762 oColumn.editor = oEditor;
13763 oColumn.editor.subscribe("showEvent", this._onEditorShowEvent, this, true);
13764 oColumn.editor.subscribe("keydownEvent", this._onEditorKeydownEvent, this, true);
13765 oColumn.editor.subscribe("revertEvent", this._onEditorRevertEvent, this, true);
13766 oColumn.editor.subscribe("saveEvent", this._onEditorSaveEvent, this, true);
13767 oColumn.editor.subscribe("cancelEvent", this._onEditorCancelEvent, this, true);
13768 oColumn.editor.subscribe("blurEvent", this._onEditorBlurEvent, this, true);
13769 oColumn.editor.subscribe("blockEvent", this._onEditorBlockEvent, this, true);
13770 oColumn.editor.subscribe("unblockEvent", this._onEditorUnblockEvent, this, true);
13774 * Returns current CellEditor instance, or null.
13775 * @method getCellEditor
13776 * @return {YAHOO.widget.CellEditor} CellEditor instance.
13778 getCellEditor : function() {
13779 return this._oCellEditor;
13784 * Activates and shows CellEditor instance for the given cell while deactivating and
13785 * canceling previous CellEditor. It is baked into DataTable that only one CellEditor
13786 * can be active at any given time.
13788 * @method showCellEditor
13789 * @param elCell {HTMLElement | String} Cell to edit.
13791 showCellEditor : function(elCell, oRecord, oColumn) {
13792 // Get a particular CellEditor
13793 elCell = this.getTdEl(elCell);
13795 oColumn = this.getColumn(elCell);
13796 if(oColumn && oColumn.editor) {
13797 var oCellEditor = this._oCellEditor;
13798 // Clean up active CellEditor
13800 if(this._oCellEditor.cancel) {
13801 this._oCellEditor.cancel();
13803 else if(oCellEditor.isActive) {
13804 this.cancelCellEditor();
13808 if(oColumn.editor instanceof YAHOO.widget.BaseCellEditor) {
13810 oCellEditor = oColumn.editor;
13811 var ok = oCellEditor.attach(this, elCell);
13813 oCellEditor.render();
13814 oCellEditor.move();
13815 ok = this.doBeforeShowCellEditor(oCellEditor);
13817 oCellEditor.show();
13818 this._oCellEditor = oCellEditor;
13822 // Backward compatibility
13824 if(!oRecord || !(oRecord instanceof YAHOO.widget.Record)) {
13825 oRecord = this.getRecord(elCell);
13827 if(!oColumn || !(oColumn instanceof YAHOO.widget.Column)) {
13828 oColumn = this.getColumn(elCell);
13830 if(oRecord && oColumn) {
13831 if(!this._oCellEditor || this._oCellEditor.container) {
13832 this._initCellEditorEl();
13835 // Update Editor values
13836 oCellEditor = this._oCellEditor;
13837 oCellEditor.cell = elCell;
13838 oCellEditor.record = oRecord;
13839 oCellEditor.column = oColumn;
13840 oCellEditor.validator = (oColumn.editorOptions &&
13841 lang.isFunction(oColumn.editorOptions.validator)) ?
13842 oColumn.editorOptions.validator : null;
13843 oCellEditor.value = oRecord.getData(oColumn.key);
13844 oCellEditor.defaultValue = null;
13847 var elContainer = oCellEditor.container;
13848 var x = Dom.getX(elCell);
13849 var y = Dom.getY(elCell);
13851 // SF doesn't get xy for cells in scrolling table
13852 // when tbody display is set to block
13853 if(isNaN(x) || isNaN(y)) {
13854 x = elCell.offsetLeft + // cell pos relative to table
13855 Dom.getX(this._elTbody.parentNode) - // plus table pos relative to document
13856 this._elTbody.scrollLeft; // minus tbody scroll
13857 y = elCell.offsetTop + // cell pos relative to table
13858 Dom.getY(this._elTbody.parentNode) - // plus table pos relative to document
13859 this._elTbody.scrollTop + // minus tbody scroll
13860 this._elThead.offsetHeight; // account for fixed THEAD cells
13863 elContainer.style.left = x + "px";
13864 elContainer.style.top = y + "px";
13866 // Hook to customize the UI
13867 this.doBeforeShowCellEditor(this._oCellEditor);
13869 //TODO: This is temporarily up here due so elements can be focused
13871 elContainer.style.display = "";
13874 Ev.addListener(elContainer, "keydown", function(e, oSelf) {
13875 // ESC hides Cell Editor
13876 if((e.keyCode == 27)) {
13877 oSelf.cancelCellEditor();
13878 oSelf.focusTbodyEl();
13881 oSelf.fireEvent("editorKeydownEvent", {editor:oSelf._oCellEditor, event:e});
13885 // Render Editor markup
13887 if(lang.isString(oColumn.editor)) {
13888 switch(oColumn.editor) {
13890 fnEditor = DT.editCheckbox;
13893 fnEditor = DT.editDate;
13896 fnEditor = DT.editDropdown;
13899 fnEditor = DT.editRadio;
13902 fnEditor = DT.editTextarea;
13905 fnEditor = DT.editTextbox;
13911 else if(lang.isFunction(oColumn.editor)) {
13912 fnEditor = oColumn.editor;
13916 // Create DOM input elements
13917 fnEditor(this._oCellEditor, this);
13919 // Show Save/Cancel buttons
13920 if(!oColumn.editorOptions || !oColumn.editorOptions.disableBtns) {
13921 this.showCellEditorBtns(elContainer);
13924 oCellEditor.isActive = true;
13926 //TODO: verify which args to pass
13927 this.fireEvent("editorShowEvent", {editor:oCellEditor});
13941 * Backward compatibility.
13943 * @method _initCellEditorEl
13945 * @deprecated Use BaseCellEditor class.
13947 _initCellEditorEl : function() {
13948 // Attach Cell Editor container element as first child of body
13949 var elCellEditor = document.createElement("div");
13950 elCellEditor.id = this._sId + "-celleditor";
13951 elCellEditor.style.display = "none";
13952 elCellEditor.tabIndex = 0;
13953 Dom.addClass(elCellEditor, DT.CLASS_EDITOR);
13954 var elFirstChild = Dom.getFirstChild(document.body);
13956 elCellEditor = Dom.insertBefore(elCellEditor, elFirstChild);
13959 elCellEditor = document.body.appendChild(elCellEditor);
13962 // Internal tracker of Cell Editor values
13963 var oCellEditor = {};
13964 oCellEditor.container = elCellEditor;
13965 oCellEditor.value = null;
13966 oCellEditor.isActive = false;
13967 this._oCellEditor = oCellEditor;
13971 * Overridable abstract method to customize CellEditor before showing.
13973 * @method doBeforeShowCellEditor
13974 * @param oCellEditor {YAHOO.widget.CellEditor} The CellEditor instance.
13975 * @return {Boolean} Return true to continue showing CellEditor.
13977 doBeforeShowCellEditor : function(oCellEditor) {
13982 * Saves active CellEditor input to Record and upates DOM UI.
13984 * @method saveCellEditor
13986 saveCellEditor : function() {
13987 if(this._oCellEditor) {
13988 if(this._oCellEditor.save) {
13989 this._oCellEditor.save();
13991 // Backward compatibility
13992 else if(this._oCellEditor.isActive) {
13993 var newData = this._oCellEditor.value;
13994 // Copy the data to pass to the event
13995 //var oldData = YAHOO.widget.DataTable._cloneObject(this._oCellEditor.record.getData(this._oCellEditor.column.key));
13996 var oldData = this._oCellEditor.record.getData(this._oCellEditor.column.key);
13998 // Validate input data
13999 if(this._oCellEditor.validator) {
14000 newData = this._oCellEditor.value = this._oCellEditor.validator.call(this, newData, oldData, this._oCellEditor);
14001 if(newData === null ) {
14002 this.resetCellEditor();
14003 this.fireEvent("editorRevertEvent",
14004 {editor:this._oCellEditor, oldData:oldData, newData:newData});
14008 // Update the Record
14009 this._oRecordSet.updateRecordValue(this._oCellEditor.record, this._oCellEditor.column.key, this._oCellEditor.value);
14011 this.formatCell(this._oCellEditor.cell.firstChild, this._oCellEditor.record, this._oCellEditor.column);
14014 this._oChainRender.add({
14015 method: function() {
14016 this.validateColumnWidths();
14020 this._oChainRender.run();
14021 // Clear out the Cell Editor
14022 this.resetCellEditor();
14024 this.fireEvent("editorSaveEvent",
14025 {editor:this._oCellEditor, oldData:oldData, newData:newData});
14031 * Cancels active CellEditor.
14033 * @method cancelCellEditor
14035 cancelCellEditor : function() {
14036 if(this._oCellEditor) {
14037 if(this._oCellEditor.cancel) {
14038 this._oCellEditor.cancel();
14040 // Backward compatibility
14041 else if(this._oCellEditor.isActive) {
14042 this.resetCellEditor();
14043 //TODO: preserve values for the event?
14044 this.fireEvent("editorCancelEvent", {editor:this._oCellEditor});
14051 * Destroys active CellEditor instance and UI.
14053 * @method destroyCellEditor
14055 destroyCellEditor : function() {
14056 if(this._oCellEditor) {
14057 this._oCellEditor.destroy();
14058 this._oCellEditor = null;
14063 * Passes through showEvent of the active CellEditor.
14065 * @method _onEditorShowEvent
14066 * @param oArgs {Object} Custom Event args.
14069 _onEditorShowEvent : function(oArgs) {
14070 this.fireEvent("editorShowEvent", oArgs);
14074 * Passes through keydownEvent of the active CellEditor.
14075 * @param oArgs {Object} Custom Event args.
14077 * @method _onEditorKeydownEvent
14080 _onEditorKeydownEvent : function(oArgs) {
14081 this.fireEvent("editorKeydownEvent", oArgs);
14085 * Passes through revertEvent of the active CellEditor.
14087 * @method _onEditorRevertEvent
14088 * @param oArgs {Object} Custom Event args.
14091 _onEditorRevertEvent : function(oArgs) {
14092 this.fireEvent("editorRevertEvent", oArgs);
14096 * Passes through saveEvent of the active CellEditor.
14098 * @method _onEditorSaveEvent
14099 * @param oArgs {Object} Custom Event args.
14102 _onEditorSaveEvent : function(oArgs) {
14103 this.fireEvent("editorSaveEvent", oArgs);
14107 * Passes through cancelEvent of the active CellEditor.
14109 * @method _onEditorCancelEvent
14110 * @param oArgs {Object} Custom Event args.
14113 _onEditorCancelEvent : function(oArgs) {
14114 this.fireEvent("editorCancelEvent", oArgs);
14118 * Passes through blurEvent of the active CellEditor.
14120 * @method _onEditorBlurEvent
14121 * @param oArgs {Object} Custom Event args.
14124 _onEditorBlurEvent : function(oArgs) {
14125 this.fireEvent("editorBlurEvent", oArgs);
14129 * Passes through blockEvent of the active CellEditor.
14131 * @method _onEditorBlockEvent
14132 * @param oArgs {Object} Custom Event args.
14135 _onEditorBlockEvent : function(oArgs) {
14136 this.fireEvent("editorBlockEvent", oArgs);
14140 * Passes through unblockEvent of the active CellEditor.
14142 * @method _onEditorUnblockEvent
14143 * @param oArgs {Object} Custom Event args.
14146 _onEditorUnblockEvent : function(oArgs) {
14147 this.fireEvent("editorUnblockEvent", oArgs);
14151 * Public handler of the editorBlurEvent. By default, saves on blur if
14152 * disableBtns is true, otherwise cancels on blur.
14154 * @method onEditorBlurEvent
14155 * @param oArgs {Object} Custom Event args.
14157 onEditorBlurEvent : function(oArgs) {
14158 if(oArgs.editor.disableBtns) {
14160 if(oArgs.editor.save) { // Backward incompatible
14161 oArgs.editor.save();
14164 else if(oArgs.editor.cancel) { // Backward incompatible
14166 oArgs.editor.cancel();
14171 * Public handler of the editorBlockEvent. By default, disables DataTable UI.
14173 * @method onEditorBlockEvent
14174 * @param oArgs {Object} Custom Event args.
14176 onEditorBlockEvent : function(oArgs) {
14181 * Public handler of the editorUnblockEvent. By default, undisables DataTable UI.
14183 * @method onEditorUnblockEvent
14184 * @param oArgs {Object} Custom Event args.
14186 onEditorUnblockEvent : function(oArgs) {
14227 // ABSTRACT METHODS
14230 * Overridable method gives implementers a hook to access data before
14231 * it gets added to RecordSet and rendered to the TBODY.
14233 * @method doBeforeLoadData
14234 * @param sRequest {String} Original request.
14235 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14236 * @param oPayload {MIXED} additional arguments
14237 * @return {Boolean} Return true to continue loading data into RecordSet and
14238 * updating DataTable with new Records, false to cancel.
14240 doBeforeLoadData : function(sRequest, oResponse, oPayload) {
14306 /////////////////////////////////////////////////////////////////////////////
14308 // Public Custom Event Handlers
14310 /////////////////////////////////////////////////////////////////////////////
14313 * Custom event handler to sort Column.
14315 * @method onEventSortColumn
14316 * @param oArgs.event {HTMLEvent} Event object.
14317 * @param oArgs.target {HTMLElement} Target element.
14319 onEventSortColumn : function(oArgs) {
14320 //TODO: support form elements in sortable columns
14321 var evt = oArgs.event;
14322 var target = oArgs.target;
14324 var el = this.getThEl(target) || this.getTdEl(target);
14326 var oColumn = this.getColumn(el);
14327 if(oColumn.sortable) {
14329 this.sortColumn(oColumn);
14337 * Custom event handler to select Column.
14339 * @method onEventSelectColumn
14340 * @param oArgs.event {HTMLEvent} Event object.
14341 * @param oArgs.target {HTMLElement} Target element.
14343 onEventSelectColumn : function(oArgs) {
14344 this.selectColumn(oArgs.target);
14348 * Custom event handler to highlight Column. Accounts for spurious
14349 * caused-by-child events.
14351 * @method onEventHighlightColumn
14352 * @param oArgs.event {HTMLEvent} Event object.
14353 * @param oArgs.target {HTMLElement} Target element.
14355 onEventHighlightColumn : function(oArgs) {
14356 this.highlightColumn(oArgs.target);
14360 * Custom event handler to unhighlight Column. Accounts for spurious
14361 * caused-by-child events.
14363 * @method onEventUnhighlightColumn
14364 * @param oArgs.event {HTMLEvent} Event object.
14365 * @param oArgs.target {HTMLElement} Target element.
14367 onEventUnhighlightColumn : function(oArgs) {
14368 this.unhighlightColumn(oArgs.target);
14372 * Custom event handler to manage selection according to desktop paradigm.
14374 * @method onEventSelectRow
14375 * @param oArgs.event {HTMLEvent} Event object.
14376 * @param oArgs.target {HTMLElement} Target element.
14378 onEventSelectRow : function(oArgs) {
14379 var sMode = this.get("selectionMode");
14380 if(sMode == "single") {
14381 this._handleSingleSelectionByMouse(oArgs);
14384 this._handleStandardSelectionByMouse(oArgs);
14389 * Custom event handler to select cell.
14391 * @method onEventSelectCell
14392 * @param oArgs.event {HTMLEvent} Event object.
14393 * @param oArgs.target {HTMLElement} Target element.
14395 onEventSelectCell : function(oArgs) {
14396 var sMode = this.get("selectionMode");
14397 if(sMode == "cellblock") {
14398 this._handleCellBlockSelectionByMouse(oArgs);
14400 else if(sMode == "cellrange") {
14401 this._handleCellRangeSelectionByMouse(oArgs);
14404 this._handleSingleCellSelectionByMouse(oArgs);
14409 * Custom event handler to highlight row. Accounts for spurious
14410 * caused-by-child events.
14412 * @method onEventHighlightRow
14413 * @param oArgs.event {HTMLEvent} Event object.
14414 * @param oArgs.target {HTMLElement} Target element.
14416 onEventHighlightRow : function(oArgs) {
14417 this.highlightRow(oArgs.target);
14421 * Custom event handler to unhighlight row. Accounts for spurious
14422 * caused-by-child events.
14424 * @method onEventUnhighlightRow
14425 * @param oArgs.event {HTMLEvent} Event object.
14426 * @param oArgs.target {HTMLElement} Target element.
14428 onEventUnhighlightRow : function(oArgs) {
14429 this.unhighlightRow(oArgs.target);
14433 * Custom event handler to highlight cell. Accounts for spurious
14434 * caused-by-child events.
14436 * @method onEventHighlightCell
14437 * @param oArgs.event {HTMLEvent} Event object.
14438 * @param oArgs.target {HTMLElement} Target element.
14440 onEventHighlightCell : function(oArgs) {
14441 this.highlightCell(oArgs.target);
14445 * Custom event handler to unhighlight cell. Accounts for spurious
14446 * caused-by-child events.
14448 * @method onEventUnhighlightCell
14449 * @param oArgs.event {HTMLEvent} Event object.
14450 * @param oArgs.target {HTMLElement} Target element.
14452 onEventUnhighlightCell : function(oArgs) {
14453 this.unhighlightCell(oArgs.target);
14457 * Custom event handler to format cell.
14459 * @method onEventFormatCell
14460 * @param oArgs.event {HTMLEvent} Event object.
14461 * @param oArgs.target {HTMLElement} Target element.
14463 onEventFormatCell : function(oArgs) {
14464 var target = oArgs.target;
14466 var elCell = this.getTdEl(target);
14468 var oColumn = this.getColumn(this.getCellIndex(elCell));
14469 this.formatCell(elCell.firstChild, this.getRecord(elCell), oColumn);
14476 * Custom event handler to edit cell.
14478 * @method onEventShowCellEditor
14479 * @param oArgs.event {HTMLEvent} Event object.
14480 * @param oArgs.target {HTMLElement} Target element.
14482 onEventShowCellEditor : function(oArgs) {
14483 if(!this.isDisabled()) {
14484 this.showCellEditor(oArgs.target);
14489 * Custom event handler to save active CellEditor input.
14491 * @method onEventSaveCellEditor
14493 onEventSaveCellEditor : function(oArgs) {
14494 if(this._oCellEditor) {
14495 if(this._oCellEditor.save) {
14496 this._oCellEditor.save();
14498 // Backward compatibility
14500 this.saveCellEditor();
14506 * Custom event handler to cancel active CellEditor.
14508 * @method onEventCancelCellEditor
14510 onEventCancelCellEditor : function(oArgs) {
14511 if(this._oCellEditor) {
14512 if(this._oCellEditor.cancel) {
14513 this._oCellEditor.cancel();
14515 // Backward compatibility
14517 this.cancelCellEditor();
14523 * Callback function receives data from DataSource and populates an entire
14524 * DataTable with Records and TR elements, clearing previous Records, if any.
14526 * @method onDataReturnInitializeTable
14527 * @param sRequest {String} Original request.
14528 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14529 * @param oPayload {MIXED} (optional) Additional argument(s)
14531 onDataReturnInitializeTable : function(sRequest, oResponse, oPayload) {
14532 if((this instanceof DT) && this._sId) {
14533 this.initializeTable();
14535 this.onDataReturnSetRows(sRequest,oResponse,oPayload);
14540 * Callback function receives reponse from DataSource, replaces all existing
14541 * Records in RecordSet, updates TR elements with new data, and updates state
14542 * UI for pagination and sorting from payload data, if necessary.
14544 * @method onDataReturnReplaceRows
14545 * @param oRequest {MIXED} Original generated request.
14546 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14547 * @param oPayload {MIXED} (optional) Additional argument(s)
14549 onDataReturnReplaceRows : function(oRequest, oResponse, oPayload) {
14550 if((this instanceof DT) && this._sId) {
14551 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
14553 // Pass data through abstract method for any transformations
14554 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload),
14555 pag = this.get('paginator'),
14559 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14561 this._oRecordSet.reset();
14563 if (this.get('dynamicData')) {
14564 if (oPayload && oPayload.pagination &&
14565 lang.isNumber(oPayload.pagination.recordOffset)) {
14566 index = oPayload.pagination.recordOffset;
14568 index = pag.getStartIndex();
14572 this._oRecordSet.setRecords(oResponse.results, index | 0);
14575 this._handleDataReturnPayload(oRequest, oResponse, oPayload);
14581 else if(ok && oResponse.error) {
14582 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14588 * Callback function receives data from DataSource and appends to an existing
14589 * DataTable new Records and, if applicable, creates or updates
14590 * corresponding TR elements.
14592 * @method onDataReturnAppendRows
14593 * @param sRequest {String} Original request.
14594 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14595 * @param oPayload {MIXED} (optional) Additional argument(s)
14597 onDataReturnAppendRows : function(sRequest, oResponse, oPayload) {
14598 if((this instanceof DT) && this._sId) {
14599 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
14601 // Pass data through abstract method for any transformations
14602 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
14604 // Data ok to append
14605 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14607 this.addRows(oResponse.results);
14610 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
14613 else if(ok && oResponse.error) {
14614 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14620 * Callback function receives data from DataSource and inserts new records
14621 * starting at the index specified in oPayload.insertIndex. The value for
14622 * oPayload.insertIndex can be populated when sending the request to the DataSource,
14623 * or by accessing oPayload.insertIndex with the doBeforeLoadData() method at runtime.
14624 * If applicable, creates or updates corresponding TR elements.
14626 * @method onDataReturnInsertRows
14627 * @param sRequest {String} Original request.
14628 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14629 * @param oPayload {MIXED} Argument payload, looks in oPayload.insertIndex.
14631 onDataReturnInsertRows : function(sRequest, oResponse, oPayload) {
14632 if((this instanceof DT) && this._sId) {
14633 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
14635 // Pass data through abstract method for any transformations
14636 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
14638 // Data ok to append
14639 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14641 this.addRows(oResponse.results, (oPayload ? oPayload.insertIndex : 0));
14644 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
14647 else if(ok && oResponse.error) {
14648 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14654 * Callback function receives data from DataSource and incrementally updates Records
14655 * starting at the index specified in oPayload.updateIndex. The value for
14656 * oPayload.updateIndex can be populated when sending the request to the DataSource,
14657 * or by accessing oPayload.updateIndex with the doBeforeLoadData() method at runtime.
14658 * If applicable, creates or updates corresponding TR elements.
14660 * @method onDataReturnUpdateRows
14661 * @param sRequest {String} Original request.
14662 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14663 * @param oPayload {MIXED} Argument payload, looks in oPayload.updateIndex.
14665 onDataReturnUpdateRows : function(sRequest, oResponse, oPayload) {
14666 if((this instanceof DT) && this._sId) {
14667 this.fireEvent("dataReturnEvent", {request:sRequest,response:oResponse,payload:oPayload});
14669 // Pass data through abstract method for any transformations
14670 var ok = this.doBeforeLoadData(sRequest, oResponse, oPayload);
14672 // Data ok to append
14673 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14675 this.updateRows((oPayload ? oPayload.updateIndex : 0), oResponse.results);
14678 this._handleDataReturnPayload(sRequest, oResponse, oPayload);
14681 else if(ok && oResponse.error) {
14682 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14688 * Callback function receives reponse from DataSource and populates the
14689 * RecordSet with the results.
14691 * @method onDataReturnSetRows
14692 * @param oRequest {MIXED} Original generated request.
14693 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14694 * @param oPayload {MIXED} (optional) Additional argument(s)
14696 onDataReturnSetRows : function(oRequest, oResponse, oPayload) {
14697 if((this instanceof DT) && this._sId) {
14698 this.fireEvent("dataReturnEvent", {request:oRequest,response:oResponse,payload:oPayload});
14700 // Pass data through abstract method for any transformations
14701 var ok = this.doBeforeLoadData(oRequest, oResponse, oPayload),
14702 pag = this.get('paginator'),
14706 if(ok && oResponse && !oResponse.error && lang.isArray(oResponse.results)) {
14708 if (this.get('dynamicData')) {
14709 if (oPayload && oPayload.pagination &&
14710 lang.isNumber(oPayload.pagination.recordOffset)) {
14711 index = oPayload.pagination.recordOffset;
14713 index = pag.getStartIndex();
14716 this._oRecordSet.reset(); // Bug 2290604: dyanmic data shouldn't keep accumulating by default
14719 this._oRecordSet.setRecords(oResponse.results, index | 0);
14722 this._handleDataReturnPayload(oRequest, oResponse, oPayload);
14728 else if(ok && oResponse.error) {
14729 this.showTableMessage(this.get("MSG_ERROR"), DT.CLASS_ERROR);
14737 * Hook to update oPayload before consumption.
14739 * @method handleDataReturnPayload
14740 * @param oRequest {MIXED} Original generated request.
14741 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14742 * @param oPayload {MIXED} State values.
14743 * @return oPayload {MIXED} State values.
14745 handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
14746 return oPayload || {};
14750 * Updates the DataTable with state data sent in an onDataReturn* payload.
14752 * @method _handleDataReturnPayload
14753 * @param oRequest {MIXED} Original generated request.
14754 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
14755 * @param oPayload {MIXED} State values
14758 _handleDataReturnPayload : function (oRequest, oResponse, oPayload) {
14759 oPayload = this.handleDataReturnPayload(oRequest, oResponse, oPayload);
14761 // Update pagination
14762 var oPaginator = this.get('paginator');
14764 // Update totalRecords
14765 if(this.get("dynamicData")) {
14766 if (widget.Paginator.isNumeric(oPayload.totalRecords)) {
14767 oPaginator.set('totalRecords',oPayload.totalRecords);
14771 oPaginator.set('totalRecords',this._oRecordSet.getLength());
14773 // Update other paginator values
14774 if (lang.isObject(oPayload.pagination)) {
14775 oPaginator.set('rowsPerPage',oPayload.pagination.rowsPerPage);
14776 oPaginator.set('recordOffset',oPayload.pagination.recordOffset);
14781 if (oPayload.sortedBy) {
14782 // Set the sorting values in preparation for refresh
14783 this.set('sortedBy', oPayload.sortedBy);
14785 // Backwards compatibility for sorting
14786 else if (oPayload.sorting) {
14787 // Set the sorting values in preparation for refresh
14788 this.set('sortedBy', oPayload.sorting);
14825 /////////////////////////////////////////////////////////////////////////////
14829 /////////////////////////////////////////////////////////////////////////////
14832 * Fired when the DataTable's rows are rendered from an initialized state.
14838 * Fired before the DataTable's DOM is rendered or modified.
14840 * @event beforeRenderEvent
14844 * Fired when the DataTable's DOM is rendered or modified.
14846 * @event renderEvent
14850 * Fired when the DataTable's post-render routine is complete, including
14851 * Column width validations.
14853 * @event postRenderEvent
14857 * Fired when the DataTable is disabled.
14859 * @event disableEvent
14863 * Fired when the DataTable is undisabled.
14865 * @event undisableEvent
14869 * Fired when data is returned from DataSource but before it is consumed by
14872 * @event dataReturnEvent
14873 * @param oArgs.request {String} Original request.
14874 * @param oArgs.response {Object} Response object.
14878 * Fired when the DataTable has a focus event.
14880 * @event tableFocusEvent
14884 * Fired when the DataTable THEAD element has a focus event.
14886 * @event theadFocusEvent
14890 * Fired when the DataTable TBODY element has a focus event.
14892 * @event tbodyFocusEvent
14896 * Fired when the DataTable has a blur event.
14898 * @event tableBlurEvent
14901 /*TODO implement theadBlurEvent
14902 * Fired when the DataTable THEAD element has a blur event.
14904 * @event theadBlurEvent
14907 /*TODO: implement tbodyBlurEvent
14908 * Fired when the DataTable TBODY element has a blur event.
14910 * @event tbodyBlurEvent
14914 * Fired when the DataTable has a key event.
14916 * @event tableKeyEvent
14917 * @param oArgs.event {HTMLEvent} The event object.
14918 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14922 * Fired when the DataTable THEAD element has a key event.
14924 * @event theadKeyEvent
14925 * @param oArgs.event {HTMLEvent} The event object.
14926 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14930 * Fired when the DataTable TBODY element has a key event.
14932 * @event tbodyKeyEvent
14933 * @param oArgs.event {HTMLEvent} The event object.
14934 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14938 * Fired when the DataTable has a mouseover.
14940 * @event tableMouseoverEvent
14941 * @param oArgs.event {HTMLEvent} The event object.
14942 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14947 * Fired when the DataTable has a mouseout.
14949 * @event tableMouseoutEvent
14950 * @param oArgs.event {HTMLEvent} The event object.
14951 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14956 * Fired when the DataTable has a mousedown.
14958 * @event tableMousedownEvent
14959 * @param oArgs.event {HTMLEvent} The event object.
14960 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14965 * Fired when the DataTable has a mouseup.
14967 * @event tableMouseupEvent
14968 * @param oArgs.event {HTMLEvent} The event object.
14969 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14974 * Fired when the DataTable has a click.
14976 * @event tableClickEvent
14977 * @param oArgs.event {HTMLEvent} The event object.
14978 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14983 * Fired when the DataTable has a dblclick.
14985 * @event tableDblclickEvent
14986 * @param oArgs.event {HTMLEvent} The event object.
14987 * @param oArgs.target {HTMLElement} The DataTable's TABLE element.
14992 * Fired when a message is shown in the DataTable's message element.
14994 * @event tableMsgShowEvent
14995 * @param oArgs.html {HTML} The HTML displayed.
14996 * @param oArgs.className {String} The className assigned.
15001 * Fired when the DataTable's message element is hidden.
15003 * @event tableMsgHideEvent
15007 * Fired when a THEAD row has a mouseover.
15009 * @event theadRowMouseoverEvent
15010 * @param oArgs.event {HTMLEvent} The event object.
15011 * @param oArgs.target {HTMLElement} The TR element.
15015 * Fired when a THEAD row has a mouseout.
15017 * @event theadRowMouseoutEvent
15018 * @param oArgs.event {HTMLEvent} The event object.
15019 * @param oArgs.target {HTMLElement} The TR element.
15023 * Fired when a THEAD row has a mousedown.
15025 * @event theadRowMousedownEvent
15026 * @param oArgs.event {HTMLEvent} The event object.
15027 * @param oArgs.target {HTMLElement} The TR element.
15031 * Fired when a THEAD row has a mouseup.
15033 * @event theadRowMouseupEvent
15034 * @param oArgs.event {HTMLEvent} The event object.
15035 * @param oArgs.target {HTMLElement} The TR element.
15039 * Fired when a THEAD row has a click.
15041 * @event theadRowClickEvent
15042 * @param oArgs.event {HTMLEvent} The event object.
15043 * @param oArgs.target {HTMLElement} The TR element.
15047 * Fired when a THEAD row has a dblclick.
15049 * @event theadRowDblclickEvent
15050 * @param oArgs.event {HTMLEvent} The event object.
15051 * @param oArgs.target {HTMLElement} The TR element.
15055 * Fired when a THEAD cell has a mouseover.
15057 * @event theadCellMouseoverEvent
15058 * @param oArgs.event {HTMLEvent} The event object.
15059 * @param oArgs.target {HTMLElement} The TH element.
15064 * Fired when a THEAD cell has a mouseout.
15066 * @event theadCellMouseoutEvent
15067 * @param oArgs.event {HTMLEvent} The event object.
15068 * @param oArgs.target {HTMLElement} The TH element.
15073 * Fired when a THEAD cell has a mousedown.
15075 * @event theadCellMousedownEvent
15076 * @param oArgs.event {HTMLEvent} The event object.
15077 * @param oArgs.target {HTMLElement} The TH element.
15081 * Fired when a THEAD cell has a mouseup.
15083 * @event theadCellMouseupEvent
15084 * @param oArgs.event {HTMLEvent} The event object.
15085 * @param oArgs.target {HTMLElement} The TH element.
15089 * Fired when a THEAD cell has a click.
15091 * @event theadCellClickEvent
15092 * @param oArgs.event {HTMLEvent} The event object.
15093 * @param oArgs.target {HTMLElement} The TH element.
15097 * Fired when a THEAD cell has a dblclick.
15099 * @event theadCellDblclickEvent
15100 * @param oArgs.event {HTMLEvent} The event object.
15101 * @param oArgs.target {HTMLElement} The TH element.
15105 * Fired when a THEAD label has a mouseover.
15107 * @event theadLabelMouseoverEvent
15108 * @param oArgs.event {HTMLEvent} The event object.
15109 * @param oArgs.target {HTMLElement} The SPAN element.
15114 * Fired when a THEAD label has a mouseout.
15116 * @event theadLabelMouseoutEvent
15117 * @param oArgs.event {HTMLEvent} The event object.
15118 * @param oArgs.target {HTMLElement} The SPAN element.
15123 * Fired when a THEAD label has a mousedown.
15125 * @event theadLabelMousedownEvent
15126 * @param oArgs.event {HTMLEvent} The event object.
15127 * @param oArgs.target {HTMLElement} The SPAN element.
15131 * Fired when a THEAD label has a mouseup.
15133 * @event theadLabelMouseupEvent
15134 * @param oArgs.event {HTMLEvent} The event object.
15135 * @param oArgs.target {HTMLElement} The SPAN element.
15139 * Fired when a THEAD label has a click.
15141 * @event theadLabelClickEvent
15142 * @param oArgs.event {HTMLEvent} The event object.
15143 * @param oArgs.target {HTMLElement} The SPAN element.
15147 * Fired when a THEAD label has a dblclick.
15149 * @event theadLabelDblclickEvent
15150 * @param oArgs.event {HTMLEvent} The event object.
15151 * @param oArgs.target {HTMLElement} The SPAN element.
15155 * Fired when a column is sorted.
15157 * @event columnSortEvent
15158 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15159 * @param oArgs.dir {String} Sort direction: YAHOO.widget.DataTable.CLASS_ASC
15160 * or YAHOO.widget.DataTable.CLASS_DESC.
15164 * Fired when a column width is set.
15166 * @event columnSetWidthEvent
15167 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15168 * @param oArgs.width {Number} The width in pixels.
15172 * Fired when a column width is unset.
15174 * @event columnUnsetWidthEvent
15175 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15179 * Fired when a column is drag-resized.
15181 * @event columnResizeEvent
15182 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15183 * @param oArgs.target {HTMLElement} The TH element.
15184 * @param oArgs.width {Number} Width in pixels.
15188 * Fired when a Column is moved to a new index.
15190 * @event columnReorderEvent
15191 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15192 * @param oArgs.oldIndex {Number} The previous tree index position.
15196 * Fired when a column is hidden.
15198 * @event columnHideEvent
15199 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15203 * Fired when a column is shown.
15205 * @event columnShowEvent
15206 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15210 * Fired when a column is selected.
15212 * @event columnSelectEvent
15213 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15217 * Fired when a column is unselected.
15219 * @event columnUnselectEvent
15220 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15223 * Fired when a column is removed.
15225 * @event columnRemoveEvent
15226 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15230 * Fired when a column is inserted.
15232 * @event columnInsertEvent
15233 * @param oArgs.column {YAHOO.widget.Column} The Column instance.
15234 * @param oArgs.index {Number} The index position.
15238 * Fired when a column is highlighted.
15240 * @event columnHighlightEvent
15241 * @param oArgs.column {YAHOO.widget.Column} The highlighted Column.
15245 * Fired when a column is unhighlighted.
15247 * @event columnUnhighlightEvent
15248 * @param oArgs.column {YAHOO.widget.Column} The unhighlighted Column.
15253 * Fired when a row has a mouseover.
15255 * @event rowMouseoverEvent
15256 * @param oArgs.event {HTMLEvent} The event object.
15257 * @param oArgs.target {HTMLElement} The TR element.
15261 * Fired when a row has a mouseout.
15263 * @event rowMouseoutEvent
15264 * @param oArgs.event {HTMLEvent} The event object.
15265 * @param oArgs.target {HTMLElement} The TR element.
15269 * Fired when a row has a mousedown.
15271 * @event rowMousedownEvent
15272 * @param oArgs.event {HTMLEvent} The event object.
15273 * @param oArgs.target {HTMLElement} The TR element.
15277 * Fired when a row has a mouseup.
15279 * @event rowMouseupEvent
15280 * @param oArgs.event {HTMLEvent} The event object.
15281 * @param oArgs.target {HTMLElement} The TR element.
15285 * Fired when a row has a click.
15287 * @event rowClickEvent
15288 * @param oArgs.event {HTMLEvent} The event object.
15289 * @param oArgs.target {HTMLElement} The TR element.
15293 * Fired when a row has a dblclick.
15295 * @event rowDblclickEvent
15296 * @param oArgs.event {HTMLEvent} The event object.
15297 * @param oArgs.target {HTMLElement} The TR element.
15301 * Fired when a row is added.
15303 * @event rowAddEvent
15304 * @param oArgs.record {YAHOO.widget.Record} The added Record.
15308 * Fired when rows are added.
15310 * @event rowsAddEvent
15311 * @param oArgs.record {YAHOO.widget.Record[]} The added Records.
15315 * Fired when a row is updated.
15317 * @event rowUpdateEvent
15318 * @param oArgs.record {YAHOO.widget.Record} The updated Record.
15319 * @param oArgs.oldData {Object} Object literal of the old data.
15323 * Fired when a row is deleted.
15325 * @event rowDeleteEvent
15326 * @param oArgs.oldData {Object} Object literal of the deleted data.
15327 * @param oArgs.recordIndex {Number} Index of the deleted Record.
15328 * @param oArgs.trElIndex {Number} Index of the deleted TR element, if on current page.
15332 * Fired when rows are deleted.
15334 * @event rowsDeleteEvent
15335 * @param oArgs.oldData {Object[]} Array of object literals of the deleted data.
15336 * @param oArgs.recordIndex {Number} Index of the first deleted Record.
15337 * @param oArgs.count {Number} Number of deleted Records.
15341 * Fired when a row is selected.
15343 * @event rowSelectEvent
15344 * @param oArgs.el {HTMLElement} The selected TR element, if applicable.
15345 * @param oArgs.record {YAHOO.widget.Record} The selected Record.
15349 * Fired when a row is unselected.
15351 * @event rowUnselectEvent
15352 * @param oArgs.el {HTMLElement} The unselected TR element, if applicable.
15353 * @param oArgs.record {YAHOO.widget.Record} The unselected Record.
15357 * Fired when all row selections are cleared.
15359 * @event unselectAllRowsEvent
15363 * Fired when a row is highlighted.
15365 * @event rowHighlightEvent
15366 * @param oArgs.el {HTMLElement} The highlighted TR element.
15367 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
15371 * Fired when a row is unhighlighted.
15373 * @event rowUnhighlightEvent
15374 * @param oArgs.el {HTMLElement} The highlighted TR element.
15375 * @param oArgs.record {YAHOO.widget.Record} The highlighted Record.
15379 * Fired when a cell is updated.
15381 * @event cellUpdateEvent
15382 * @param oArgs.record {YAHOO.widget.Record} The updated Record.
15383 * @param oArgs.column {YAHOO.widget.Column} The updated Column.
15384 * @param oArgs.oldData {Object} Original data value of the updated cell.
15388 * Fired when a cell has a mouseover.
15390 * @event cellMouseoverEvent
15391 * @param oArgs.event {HTMLEvent} The event object.
15392 * @param oArgs.target {HTMLElement} The TD element.
15396 * Fired when a cell has a mouseout.
15398 * @event cellMouseoutEvent
15399 * @param oArgs.event {HTMLEvent} The event object.
15400 * @param oArgs.target {HTMLElement} The TD element.
15404 * Fired when a cell has a mousedown.
15406 * @event cellMousedownEvent
15407 * @param oArgs.event {HTMLEvent} The event object.
15408 * @param oArgs.target {HTMLElement} The TD element.
15412 * Fired when a cell has a mouseup.
15414 * @event cellMouseupEvent
15415 * @param oArgs.event {HTMLEvent} The event object.
15416 * @param oArgs.target {HTMLElement} The TD element.
15420 * Fired when a cell has a click.
15422 * @event cellClickEvent
15423 * @param oArgs.event {HTMLEvent} The event object.
15424 * @param oArgs.target {HTMLElement} The TD element.
15428 * Fired when a cell has a dblclick.
15430 * @event cellDblclickEvent
15431 * @param oArgs.event {HTMLEvent} The event object.
15432 * @param oArgs.target {HTMLElement} The TD element.
15436 * Fired when a cell is formatted.
15438 * @event cellFormatEvent
15439 * @param oArgs.el {HTMLElement} The formatted TD element.
15440 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15441 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15442 * @param oArgs.key {String} (deprecated) The key of the formatted cell.
15446 * Fired when a cell is selected.
15448 * @event cellSelectEvent
15449 * @param oArgs.el {HTMLElement} The selected TD element.
15450 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15451 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15452 * @param oArgs.key {String} (deprecated) The key of the selected cell.
15456 * Fired when a cell is unselected.
15458 * @event cellUnselectEvent
15459 * @param oArgs.el {HTMLElement} The unselected TD element.
15460 * @param oArgs.record {YAHOO.widget.Record} The associated Record.
15461 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15462 * @param oArgs.key {String} (deprecated) The key of the unselected cell.
15467 * Fired when a cell is highlighted.
15469 * @event cellHighlightEvent
15470 * @param oArgs.el {HTMLElement} The highlighted TD element.
15471 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15472 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15473 * @param oArgs.key {String} (deprecated) The key of the highlighted cell.
15478 * Fired when a cell is unhighlighted.
15480 * @event cellUnhighlightEvent
15481 * @param oArgs.el {HTMLElement} The unhighlighted TD element.
15482 * @param oArgs.record {YAHOO.widget.Record} The associated Record instance.
15483 * @param oArgs.column {YAHOO.widget.Column} The associated Column instance.
15484 * @param oArgs.key {String} (deprecated) The key of the unhighlighted cell.
15489 * Fired when all cell selections are cleared.
15491 * @event unselectAllCellsEvent
15495 * Fired when a CellEditor is shown.
15497 * @event editorShowEvent
15498 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15502 * Fired when a CellEditor has a keydown.
15504 * @event editorKeydownEvent
15505 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15506 * @param oArgs.event {HTMLEvent} The event object.
15510 * Fired when a CellEditor input is reverted.
15512 * @event editorRevertEvent
15513 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15514 * @param oArgs.newData {Object} New data value from form input field.
15515 * @param oArgs.oldData {Object} Old data value.
15519 * Fired when a CellEditor input is saved.
15521 * @event editorSaveEvent
15522 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15523 * @param oArgs.newData {Object} New data value from form input field.
15524 * @param oArgs.oldData {Object} Old data value.
15528 * Fired when a CellEditor input is canceled.
15530 * @event editorCancelEvent
15531 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15535 * Fired when a CellEditor has a blur event.
15537 * @event editorBlurEvent
15538 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15542 * Fired when a CellEditor is blocked.
15544 * @event editorBlockEvent
15545 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15549 * Fired when a CellEditor is unblocked.
15551 * @event editorUnblockEvent
15552 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
15560 * Fired when a link is clicked.
15562 * @event linkClickEvent
15563 * @param oArgs.event {HTMLEvent} The event object.
15564 * @param oArgs.target {HTMLElement} The A element.
15568 * Fired when a BUTTON element or INPUT element of type "button", "image",
15569 * "submit", "reset" is clicked.
15571 * @event buttonClickEvent
15572 * @param oArgs.event {HTMLEvent} The event object.
15573 * @param oArgs.target {HTMLElement} The BUTTON element.
15577 * Fired when a CHECKBOX element is clicked.
15579 * @event checkboxClickEvent
15580 * @param oArgs.event {HTMLEvent} The event object.
15581 * @param oArgs.target {HTMLElement} The CHECKBOX element.
15585 * Fired when a SELECT element is changed.
15587 * @event dropdownChangeEvent
15588 * @param oArgs.event {HTMLEvent} The event object.
15589 * @param oArgs.target {HTMLElement} The SELECT element.
15593 * Fired when a RADIO element is clicked.
15595 * @event radioClickEvent
15596 * @param oArgs.event {HTMLEvent} The event object.
15597 * @param oArgs.target {HTMLElement} The RADIO element.
15625 /////////////////////////////////////////////////////////////////////////////
15629 /////////////////////////////////////////////////////////////////////////////
15632 * @method showCellEditorBtns
15633 * @deprecated Use CellEditor.renderBtns()
15635 showCellEditorBtns : function(elContainer) {
15637 var elBtnsDiv = elContainer.appendChild(document.createElement("div"));
15638 Dom.addClass(elBtnsDiv, DT.CLASS_BUTTON);
15641 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
15642 Dom.addClass(elSaveBtn, DT.CLASS_DEFAULT);
15643 elSaveBtn.innerHTML = "OK";
15644 Ev.addListener(elSaveBtn, "click", function(oArgs, oSelf) {
15645 oSelf.onEventSaveCellEditor(oArgs, oSelf);
15646 oSelf.focusTbodyEl();
15650 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
15651 elCancelBtn.innerHTML = "Cancel";
15652 Ev.addListener(elCancelBtn, "click", function(oArgs, oSelf) {
15653 oSelf.onEventCancelCellEditor(oArgs, oSelf);
15654 oSelf.focusTbodyEl();
15660 * @method resetCellEditor
15661 * @deprecated Use destroyCellEditor
15663 resetCellEditor : function() {
15664 var elContainer = this._oCellEditor.container;
15665 elContainer.style.display = "none";
15666 Ev.purgeElement(elContainer, true);
15667 elContainer.innerHTML = "";
15668 this._oCellEditor.value = null;
15669 this._oCellEditor.isActive = false;
15674 * @event editorUpdateEvent
15675 * @deprecated Use CellEditor class.
15680 * @deprecated Use getTbodyEl().
15682 getBody : function() {
15683 // Backward compatibility
15684 return this.getTbodyEl();
15689 * @deprecated Use getTdEl().
15691 getCell : function(index) {
15692 // Backward compatibility
15693 return this.getTdEl(index);
15698 * @deprecated Use getTrEl().
15700 getRow : function(index) {
15701 // Backward compatibility
15702 return this.getTrEl(index);
15706 * @method refreshView
15707 * @deprecated Use render.
15709 refreshView : function() {
15710 // Backward compatibility
15716 * @deprecated Use selectRow.
15718 select : function(els) {
15719 // Backward compatibility
15720 if(!lang.isArray(els)) {
15723 for(var i=0; i<els.length; i++) {
15724 this.selectRow(els[i]);
15729 * @method onEventEditCell
15730 * @deprecated Use onEventShowCellEditor.
15732 onEventEditCell : function(oArgs) {
15733 // Backward compatibility
15734 this.onEventShowCellEditor(oArgs);
15738 * @method _syncColWidths
15739 * @deprecated Use validateColumnWidths.
15741 _syncColWidths : function() {
15742 // Backward compatibility
15743 this.validateColumnWidths();
15747 * @event headerRowMouseoverEvent
15748 * @deprecated Use theadRowMouseoverEvent.
15752 * @event headerRowMouseoutEvent
15753 * @deprecated Use theadRowMouseoutEvent.
15757 * @event headerRowMousedownEvent
15758 * @deprecated Use theadRowMousedownEvent.
15762 * @event headerRowClickEvent
15763 * @deprecated Use theadRowClickEvent.
15767 * @event headerRowDblclickEvent
15768 * @deprecated Use theadRowDblclickEvent.
15772 * @event headerCellMouseoverEvent
15773 * @deprecated Use theadCellMouseoverEvent.
15777 * @event headerCellMouseoutEvent
15778 * @deprecated Use theadCellMouseoutEvent.
15782 * @event headerCellMousedownEvent
15783 * @deprecated Use theadCellMousedownEvent.
15787 * @event headerCellClickEvent
15788 * @deprecated Use theadCellClickEvent.
15792 * @event headerCellDblclickEvent
15793 * @deprecated Use theadCellDblclickEvent.
15797 * @event headerLabelMouseoverEvent
15798 * @deprecated Use theadLabelMouseoverEvent.
15802 * @event headerLabelMouseoutEvent
15803 * @deprecated Use theadLabelMouseoutEvent.
15807 * @event headerLabelMousedownEvent
15808 * @deprecated Use theadLabelMousedownEvent.
15812 * @event headerLabelClickEvent
15813 * @deprecated Use theadLabelClickEvent.
15817 * @event headerLabelDbllickEvent
15818 * @deprecated Use theadLabelDblclickEvent.
15824 * Alias for onDataReturnSetRows for backward compatibility
15825 * @method onDataReturnSetRecords
15826 * @deprecated Use onDataReturnSetRows
15828 DT.prototype.onDataReturnSetRecords = DT.prototype.onDataReturnSetRows;
15831 * Alias for onPaginatorChange for backward compatibility
15832 * @method onPaginatorChange
15833 * @deprecated Use onPaginatorChangeRequest
15835 DT.prototype.onPaginatorChange = DT.prototype.onPaginatorChangeRequest;
15837 /////////////////////////////////////////////////////////////////////////////
15839 // Deprecated static APIs
15841 /////////////////////////////////////////////////////////////////////////////
15843 * @method DataTable.editCheckbox
15844 * @deprecated Use YAHOO.widget.CheckboxCellEditor.
15846 DT.editCheckbox = function() {};
15849 * @method DataTable.editDate
15850 * @deprecated Use YAHOO.widget.DateCellEditor.
15852 DT.editDate = function() {};
15855 * @method DataTable.editDropdown
15856 * @deprecated Use YAHOO.widget.DropdownCellEditor.
15858 DT.editDropdown = function() {};
15861 * @method DataTable.editRadio
15862 * @deprecated Use YAHOO.widget.RadioCellEditor.
15864 DT.editRadio = function() {};
15867 * @method DataTable.editTextarea
15868 * @deprecated Use YAHOO.widget.TextareaCellEditor
15870 DT.editTextarea = function() {};
15873 * @method DataTable.editTextbox
15874 * @deprecated Use YAHOO.widget.TextboxCellEditor
15876 DT.editTextbox= function() {};
15882 var lang = YAHOO.lang,
15884 widget = YAHOO.widget,
15889 DS = util.DataSourceBase,
15890 DT = widget.DataTable,
15891 Pag = widget.Paginator;
15894 * The ScrollingDataTable class extends the DataTable class to provide
15895 * functionality for x-scrolling, y-scrolling, and xy-scrolling.
15897 * @namespace YAHOO.widget
15898 * @class ScrollingDataTable
15899 * @extends YAHOO.widget.DataTable
15901 * @param elContainer {HTMLElement} Container element for the TABLE.
15902 * @param aColumnDefs {Object[]} Array of object literal Column definitions.
15903 * @param oDataSource {YAHOO.util.DataSource} DataSource instance.
15904 * @param oConfigs {object} (optional) Object literal of configuration values.
15906 widget.ScrollingDataTable = function(elContainer,aColumnDefs,oDataSource,oConfigs) {
15907 oConfigs = oConfigs || {};
15909 // Prevent infinite loop
15910 if(oConfigs.scrollable) {
15911 oConfigs.scrollable = false;
15916 widget.ScrollingDataTable.superclass.constructor.call(this, elContainer,aColumnDefs,oDataSource,oConfigs);
15918 // Once per instance
15919 this.subscribe("columnShowEvent", this._onColumnChange);
15922 var SDT = widget.ScrollingDataTable;
15924 /////////////////////////////////////////////////////////////////////////////
15926 // Public constants
15928 /////////////////////////////////////////////////////////////////////////////
15929 lang.augmentObject(SDT, {
15932 * Class name assigned to inner DataTable header container.
15934 * @property DataTable.CLASS_HEADER
15938 * @default "yui-dt-hd"
15940 CLASS_HEADER : "yui-dt-hd",
15943 * Class name assigned to inner DataTable body container.
15945 * @property DataTable.CLASS_BODY
15949 * @default "yui-dt-bd"
15951 CLASS_BODY : "yui-dt-bd"
15954 lang.extend(SDT, DT, {
15957 * Container for fixed header TABLE element.
15959 * @property _elHdContainer
15960 * @type HTMLElement
15963 _elHdContainer : null,
15966 * Fixed header TABLE element.
15968 * @property _elHdTable
15969 * @type HTMLElement
15975 * Container for scrolling body TABLE element.
15977 * @property _elBdContainer
15978 * @type HTMLElement
15981 _elBdContainer : null,
15984 * Body THEAD element.
15986 * @property _elBdThead
15987 * @type HTMLElement
15993 * Offscreen container to temporarily clone SDT for auto-width calculation.
15995 * @property _elTmpContainer
15996 * @type HTMLElement
15999 _elTmpContainer : null,
16002 * Offscreen TABLE element for auto-width calculation.
16004 * @property _elTmpTable
16005 * @type HTMLElement
16008 _elTmpTable : null,
16011 * True if x-scrollbar is currently visible.
16012 * @property _bScrollbarX
16016 _bScrollbarX : null,
16032 /////////////////////////////////////////////////////////////////////////////
16034 // Superclass methods
16036 /////////////////////////////////////////////////////////////////////////////
16039 * Implementation of Element's abstract method. Sets up config values.
16041 * @method initAttributes
16042 * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
16046 initAttributes : function(oConfigs) {
16047 oConfigs = oConfigs || {};
16048 SDT.superclass.initAttributes.call(this, oConfigs);
16052 * @description Table width for scrollable tables (e.g., "40em").
16055 this.setAttributeConfig("width", {
16057 validator: lang.isString,
16058 method: function(oParam) {
16059 if(this._elHdContainer && this._elBdContainer) {
16060 this._elHdContainer.style.width = oParam;
16061 this._elBdContainer.style.width = oParam;
16062 this._syncScrollX();
16063 this._syncScrollOverhang();
16069 * @attribute height
16070 * @description Table body height for scrollable tables, not including headers (e.g., "40em").
16073 this.setAttributeConfig("height", {
16075 validator: lang.isString,
16076 method: function(oParam) {
16077 if(this._elHdContainer && this._elBdContainer) {
16078 this._elBdContainer.style.height = oParam;
16079 this._syncScrollX();
16080 this._syncScrollY();
16081 this._syncScrollOverhang();
16087 * @attribute COLOR_COLUMNFILLER
16088 * @description CSS color value assigned to header filler on scrollable tables.
16090 * @default "#F2F2F2"
16092 this.setAttributeConfig("COLOR_COLUMNFILLER", {
16094 validator: lang.isString,
16095 method: function(oParam) {
16096 if(this._elHdContainer) {
16097 this._elHdContainer.style.backgroundColor = oParam;
16104 * Initializes internal variables.
16109 _init : function() {
16110 this._elHdContainer = null;
16111 this._elHdTable = null;
16112 this._elBdContainer = null;
16113 this._elBdThead = null;
16114 this._elTmpContainer = null;
16115 this._elTmpTable = null;
16119 * Initializes DOM elements for a ScrollingDataTable, including creation of
16120 * two separate TABLE elements.
16122 * @method _initDomElements
16123 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
16124 * return {Boolean} False in case of error, otherwise true
16127 _initDomElements : function(elContainer) {
16128 // Outer and inner containers
16129 this._initContainerEl(elContainer);
16130 if(this._elContainer && this._elHdContainer && this._elBdContainer) {
16132 this._initTableEl();
16134 if(this._elHdTable && this._elTable) {
16136 ///this._initColgroupEl(this._elHdTable, this._elTable);
16137 this._initColgroupEl(this._elHdTable);
16140 this._initTheadEl(this._elHdTable, this._elTable);
16143 this._initTbodyEl(this._elTable);
16145 this._initMsgTbodyEl(this._elTable);
16148 if(!this._elContainer || !this._elTable || !this._elColgroup || !this._elThead || !this._elTbody || !this._elMsgTbody ||
16149 !this._elHdTable || !this._elBdThead) {
16158 * Destroy's the DataTable outer and inner container elements, if available.
16160 * @method _destroyContainerEl
16161 * @param elContainer {HTMLElement} Reference to the container element.
16164 _destroyContainerEl : function(elContainer) {
16165 Dom.removeClass(elContainer, DT.CLASS_SCROLLABLE);
16166 SDT.superclass._destroyContainerEl.call(this, elContainer);
16167 this._elHdContainer = null;
16168 this._elBdContainer = null;
16172 * Initializes the DataTable outer container element and creates inner header
16173 * and body container elements.
16175 * @method _initContainerEl
16176 * @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
16179 _initContainerEl : function(elContainer) {
16180 SDT.superclass._initContainerEl.call(this, elContainer);
16182 if(this._elContainer) {
16183 elContainer = this._elContainer; // was constructor input, now is DOM ref
16184 Dom.addClass(elContainer, DT.CLASS_SCROLLABLE);
16186 // Container for header TABLE
16187 var elHdContainer = document.createElement("div");
16188 elHdContainer.style.width = this.get("width") || "";
16189 elHdContainer.style.backgroundColor = this.get("COLOR_COLUMNFILLER");
16190 Dom.addClass(elHdContainer, SDT.CLASS_HEADER);
16191 this._elHdContainer = elHdContainer;
16192 elContainer.appendChild(elHdContainer);
16194 // Container for body TABLE
16195 var elBdContainer = document.createElement("div");
16196 elBdContainer.style.width = this.get("width") || "";
16197 elBdContainer.style.height = this.get("height") || "";
16198 Dom.addClass(elBdContainer, SDT.CLASS_BODY);
16199 Ev.addListener(elBdContainer, "scroll", this._onScroll, this); // to sync horiz scroll headers
16200 this._elBdContainer = elBdContainer;
16201 elContainer.appendChild(elBdContainer);
16206 * Creates HTML markup CAPTION element.
16208 * @method _initCaptionEl
16209 * @param sCaption {String} Text for caption.
16212 _initCaptionEl : function(sCaption) {
16213 // Not yet supported
16214 /*if(this._elHdTable && sCaption) {
16215 // Create CAPTION element
16216 if(!this._elCaption) {
16217 this._elCaption = this._elHdTable.createCaption();
16219 // Set CAPTION value
16220 this._elCaption.innerHTML = sCaption;
16222 else if(this._elCaption) {
16223 this._elCaption.parentNode.removeChild(this._elCaption);
16228 * Destroy's the DataTable head TABLE element, if available.
16230 * @method _destroyHdTableEl
16233 _destroyHdTableEl : function() {
16234 var elTable = this._elHdTable;
16236 Ev.purgeElement(elTable, true);
16237 elTable.parentNode.removeChild(elTable);
16239 // A little out of place, but where else can we null out these extra elements?
16240 ///this._elBdColgroup = null;
16241 this._elBdThead = null;
16246 * Initializes ScrollingDataTable TABLE elements into the two inner containers.
16248 * @method _initTableEl
16251 _initTableEl : function() {
16253 if(this._elHdContainer) {
16254 this._destroyHdTableEl();
16257 this._elHdTable = this._elHdContainer.appendChild(document.createElement("table"));
16259 // Set up mouseover/mouseout events via mouseenter/mouseleave delegation
16260 Ev.delegate(this._elHdTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this);
16261 Ev.delegate(this._elHdTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this);
16264 SDT.superclass._initTableEl.call(this, this._elBdContainer);
16268 * Initializes ScrollingDataTable THEAD elements into the two inner containers.
16270 * @method _initTheadEl
16271 * @param elHdTable {HTMLElement} (optional) Fixed header TABLE element reference.
16272 * @param elTable {HTMLElement} (optional) TABLE element reference.
16275 _initTheadEl : function(elHdTable, elTable) {
16276 elHdTable = elHdTable || this._elHdTable;
16277 elTable = elTable || this._elTable;
16279 // Scrolling body's THEAD
16280 this._initBdTheadEl(elTable);
16281 // Standard fixed head THEAD
16282 SDT.superclass._initTheadEl.call(this, elHdTable);
16286 * SDT changes ID so as not to duplicate the accessibility TH IDs.
16288 * @method _initThEl
16289 * @param elTh {HTMLElement} TH element reference.
16290 * @param oColumn {YAHOO.widget.Column} Column object.
16293 _initThEl : function(elTh, oColumn) {
16294 SDT.superclass._initThEl.call(this, elTh, oColumn);
16295 elTh.id = this.getId() +"-fixedth-" + oColumn.getSanitizedKey(); // Needed for getColumn by TH and ColumnDD
16299 * Destroy's the DataTable body THEAD element, if available.
16301 * @method _destroyBdTheadEl
16304 _destroyBdTheadEl : function() {
16305 var elBdThead = this._elBdThead;
16307 var elTable = elBdThead.parentNode;
16308 Ev.purgeElement(elBdThead, true);
16309 elTable.removeChild(elBdThead);
16310 this._elBdThead = null;
16312 this._destroyColumnHelpers();
16317 * Initializes body THEAD element.
16319 * @method _initBdTheadEl
16320 * @param elTable {HTMLElement} TABLE element into which to create THEAD.
16321 * @return {HTMLElement} Initialized THEAD element.
16324 _initBdTheadEl : function(elTable) {
16326 // Destroy previous
16327 this._destroyBdTheadEl();
16329 var elThead = elTable.insertBefore(document.createElement("thead"), elTable.firstChild);
16331 // Add TRs to the THEAD;
16332 var oColumnSet = this._oColumnSet,
16333 colTree = oColumnSet.tree,
16334 elTh, elTheadTr, oColumn, i, j, k, len;
16336 for(i=0, k=colTree.length; i<k; i++) {
16337 elTheadTr = elThead.appendChild(document.createElement("tr"));
16339 // ...and create TH cells
16340 for(j=0, len=colTree[i].length; j<len; j++) {
16341 oColumn = colTree[i][j];
16342 elTh = elTheadTr.appendChild(document.createElement("th"));
16343 this._initBdThEl(elTh,oColumn,i,j);
16346 this._elBdThead = elThead;
16351 * Populates TH element for the body THEAD element.
16353 * @method _initBdThEl
16354 * @param elTh {HTMLElement} TH element reference.
16355 * @param oColumn {YAHOO.widget.Column} Column object.
16358 _initBdThEl : function(elTh, oColumn) {
16359 elTh.id = this.getId()+"-th-" + oColumn.getSanitizedKey(); // Needed for accessibility
16360 elTh.rowSpan = oColumn.getRowspan();
16361 elTh.colSpan = oColumn.getColspan();
16362 // Assign abbr attribute
16364 elTh.abbr = oColumn.abbr;
16367 // TODO: strip links and form elements
16368 var sKey = oColumn.getKey();
16369 var sLabel = lang.isValue(oColumn.label) ? oColumn.label : sKey;
16370 elTh.innerHTML = sLabel;
16374 * Initializes ScrollingDataTable TBODY element for data
16376 * @method _initTbodyEl
16377 * @param elTable {HTMLElement} TABLE element into which to create TBODY .
16380 _initTbodyEl : function(elTable) {
16381 SDT.superclass._initTbodyEl.call(this, elTable);
16383 // Bug 2105534 - Safari 3 gap
16384 // Bug 2492591 - IE8 offsetTop
16385 elTable.style.marginTop = (this._elTbody.offsetTop > 0) ?
16386 "-"+this._elTbody.offsetTop+"px" : 0;
16418 * Sets focus on the given element.
16421 * @param el {HTMLElement} Element.
16424 _focusEl : function(el) {
16425 el = el || this._elTbody;
16427 this._storeScrollPositions();
16428 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
16429 // The timeout is necessary in both IE and Firefox 1.5, to prevent scripts from doing
16430 // strange unexpected things as the user clicks on buttons and other controls.
16432 // Bug 1921135: Wrap the whole thing in a setTimeout
16433 setTimeout(function() {
16434 setTimeout(function() {
16437 oSelf._restoreScrollPositions();
16464 * Internal wrapper calls run() on render Chain instance.
16466 * @method _runRenderChain
16469 _runRenderChain : function() {
16470 this._storeScrollPositions();
16471 this._oChainRender.run();
16475 * Stores scroll positions so they can be restored after a render.
16477 * @method _storeScrollPositions
16480 _storeScrollPositions : function() {
16481 this._nScrollTop = this._elBdContainer.scrollTop;
16482 this._nScrollLeft = this._elBdContainer.scrollLeft;
16486 * Clears stored scroll positions to interrupt the automatic restore mechanism.
16487 * Useful for setting scroll positions programmatically rather than as part of
16488 * the post-render cleanup process.
16490 * @method clearScrollPositions
16493 clearScrollPositions : function() {
16494 this._nScrollTop = 0;
16495 this._nScrollLeft = 0;
16499 * Restores scroll positions to stored value.
16501 * @method _retoreScrollPositions
16504 _restoreScrollPositions : function() {
16505 // Reset scroll positions
16506 if(this._nScrollTop) {
16507 this._elBdContainer.scrollTop = this._nScrollTop;
16508 this._nScrollTop = null;
16510 if(this._nScrollLeft) {
16511 this._elBdContainer.scrollLeft = this._nScrollLeft;
16513 this._elHdContainer.scrollLeft = this._nScrollLeft;
16514 this._nScrollLeft = null;
16519 * Helper function calculates and sets a validated width for a Column in a ScrollingDataTable.
16521 * @method _validateColumnWidth
16522 * @param oColumn {YAHOO.widget.Column} Column instance.
16523 * @param elTd {HTMLElement} TD element to validate against.
16526 _validateColumnWidth : function(oColumn, elTd) {
16527 // Only Columns without widths that are not hidden
16528 if(!oColumn.width && !oColumn.hidden) {
16529 var elTh = oColumn.getThEl();
16530 // Unset a calculated auto-width
16531 if(oColumn._calculatedWidth) {
16532 this._setColumnWidth(oColumn, "auto", "visible");
16534 // Compare auto-widths
16535 if(elTh.offsetWidth !== elTd.offsetWidth) {
16536 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
16537 oColumn.getThLinerEl() : elTd.firstChild;
16539 // Grab the wider liner width, unless the minWidth is wider
16540 var newWidth = Math.max(0,
16541 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
16544 var sOverflow = 'visible';
16546 // Now validate against maxAutoWidth
16547 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
16548 newWidth = oColumn.maxAutoWidth;
16549 sOverflow = "hidden";
16552 // Set to the wider auto-width
16553 this._elTbody.style.display = "none";
16554 this._setColumnWidth(oColumn, newWidth+'px', sOverflow);
16555 oColumn._calculatedWidth = newWidth;
16556 this._elTbody.style.display = "";
16562 * For one or all Columns of a ScrollingDataTable, when Column is not hidden,
16563 * and width is not set, syncs widths of header and body cells and
16564 * validates that width against minWidth and/or maxAutoWidth as necessary.
16566 * @method validateColumnWidths
16567 * @param oArg.column {YAHOO.widget.Column} (optional) One Column to validate. If null, all Columns' widths are validated.
16569 validateColumnWidths : function(oColumn) {
16570 // Validate there is at least one TR with proper TDs
16571 var allKeys = this._oColumnSet.keys,
16572 allKeysLength = allKeys.length,
16573 elRow = this.getFirstTrEl();
16575 // Reset overhang for IE
16577 this._setOverhangValue(1);
16580 if(allKeys && elRow && (elRow.childNodes.length === allKeysLength)) {
16581 // Temporarily unsnap container since it causes inaccurate calculations
16582 var sWidth = this.get("width");
16584 this._elHdContainer.style.width = "";
16585 this._elBdContainer.style.width = "";
16587 this._elContainer.style.width = "";
16589 //Validate just one Column
16590 if(oColumn && lang.isNumber(oColumn.getKeyIndex())) {
16591 this._validateColumnWidth(oColumn, elRow.childNodes[oColumn.getKeyIndex()]);
16593 // Iterate through all Columns to unset calculated widths in one pass
16595 var elTd, todos = [], thisTodo, i, len;
16596 for(i=0; i<allKeysLength; i++) {
16597 oColumn = allKeys[i];
16598 // Only Columns without widths that are not hidden, unset a calculated auto-width
16599 if(!oColumn.width && !oColumn.hidden && oColumn._calculatedWidth) {
16600 todos[todos.length] = oColumn;
16604 this._elTbody.style.display = "none";
16605 for(i=0, len=todos.length; i<len; i++) {
16606 this._setColumnWidth(todos[i], "auto", "visible");
16608 this._elTbody.style.display = "";
16612 // Iterate through all Columns and make the store the adjustments to make in one pass
16613 for(i=0; i<allKeysLength; i++) {
16614 oColumn = allKeys[i];
16615 elTd = elRow.childNodes[i];
16616 // Only Columns without widths that are not hidden
16617 if(!oColumn.width && !oColumn.hidden) {
16618 var elTh = oColumn.getThEl();
16620 // Compare auto-widths
16621 if(elTh.offsetWidth !== elTd.offsetWidth) {
16622 var elWider = (elTh.offsetWidth > elTd.offsetWidth) ?
16623 oColumn.getThLinerEl() : elTd.firstChild;
16625 // Grab the wider liner width, unless the minWidth is wider
16626 var newWidth = Math.max(0,
16627 (elWider.offsetWidth -(parseInt(Dom.getStyle(elWider,"paddingLeft"),10)|0) - (parseInt(Dom.getStyle(elWider,"paddingRight"),10)|0)),
16630 var sOverflow = 'visible';
16632 // Now validate against maxAutoWidth
16633 if((oColumn.maxAutoWidth > 0) && (newWidth > oColumn.maxAutoWidth)) {
16634 newWidth = oColumn.maxAutoWidth;
16635 sOverflow = "hidden";
16638 todos[todos.length] = [oColumn, newWidth, sOverflow];
16643 this._elTbody.style.display = "none";
16644 for(i=0, len=todos.length; i<len; i++) {
16645 thisTodo = todos[i];
16646 // Set to the wider auto-width
16647 this._setColumnWidth(thisTodo[0], thisTodo[1]+"px", thisTodo[2]);
16648 thisTodo[0]._calculatedWidth = thisTodo[1];
16650 this._elTbody.style.display = "";
16653 // Resnap unsnapped containers
16655 this._elHdContainer.style.width = sWidth;
16656 this._elBdContainer.style.width = sWidth;
16660 this._syncScroll();
16661 this._restoreScrollPositions();
16665 * Syncs padding around scrollable tables, including Column header right-padding
16666 * and container width and height.
16668 * @method _syncScroll
16671 _syncScroll : function() {
16672 this._syncScrollX();
16673 this._syncScrollY();
16674 this._syncScrollOverhang();
16677 this._elHdContainer.scrollLeft = this._elBdContainer.scrollLeft;
16678 if(!this.get("width")) {
16680 document.body.style += '';
16686 * Snaps container width for y-scrolling tables.
16688 * @method _syncScrollY
16691 _syncScrollY : function() {
16692 var elTbody = this._elTbody,
16693 elBdContainer = this._elBdContainer;
16695 // X-scrolling not enabled
16696 if(!this.get("width")) {
16697 // Snap outer container width to content
16698 this._elContainer.style.width =
16699 (elBdContainer.scrollHeight > elBdContainer.clientHeight) ?
16700 // but account for y-scrollbar since it is visible
16701 (elTbody.parentNode.clientWidth + 19) + "px" :
16702 // no y-scrollbar, just borders
16703 (elTbody.parentNode.clientWidth + 2) + "px";
16708 * Snaps container height for x-scrolling tables in IE. Syncs message TBODY width.
16710 * @method _syncScrollX
16713 _syncScrollX : function() {
16714 var elTbody = this._elTbody,
16715 elBdContainer = this._elBdContainer;
16717 // IE 6 and 7 only when y-scrolling not enabled
16718 if(!this.get("height") && (ua.ie)) {
16719 // Snap outer container height to content
16720 elBdContainer.style.height =
16721 // but account for x-scrollbar if it is visible
16722 (elBdContainer.scrollWidth > elBdContainer.offsetWidth ) ?
16723 (elTbody.parentNode.offsetHeight + 18) + "px" :
16724 elTbody.parentNode.offsetHeight + "px";
16727 // Sync message tbody
16728 if(this._elTbody.rows.length === 0) {
16729 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
16732 this._elMsgTbody.parentNode.style.width = "";
16737 * Adds/removes Column header overhang as necesary.
16739 * @method _syncScrollOverhang
16742 _syncScrollOverhang : function() {
16743 var elBdContainer = this._elBdContainer,
16744 // Overhang should be either 1 (default) or 18px, depending on the location of the right edge of the table
16747 // Y-scrollbar is visible, which is when the overhang needs to jut out
16748 if((elBdContainer.scrollHeight > elBdContainer.clientHeight) &&
16749 // X-scrollbar is also visible, which means the right is jagged, not flush with the Column
16750 (elBdContainer.scrollWidth > elBdContainer.clientWidth)) {
16754 this._setOverhangValue(nPadding);
16759 * Sets Column header overhang to given width.
16761 * @method _setOverhangValue
16762 * @param nBorderWidth {Number} Value of new border for overhang.
16765 _setOverhangValue : function(nBorderWidth) {
16766 var aLastHeaders = this._oColumnSet.headers[this._oColumnSet.headers.length-1] || [],
16767 len = aLastHeaders.length,
16768 sPrefix = this._sId+"-fixedth-",
16769 sValue = nBorderWidth + "px solid " + this.get("COLOR_COLUMNFILLER");
16771 this._elThead.style.display = "none";
16772 for(var i=0; i<len; i++) {
16773 Dom.get(sPrefix+aLastHeaders[i]).style.borderRight = sValue;
16775 this._elThead.style.display = "";
16816 * Returns DOM reference to the DataTable's fixed header container element.
16818 * @method getHdContainerEl
16819 * @return {HTMLElement} Reference to DIV element.
16821 getHdContainerEl : function() {
16822 return this._elHdContainer;
16826 * Returns DOM reference to the DataTable's scrolling body container element.
16828 * @method getBdContainerEl
16829 * @return {HTMLElement} Reference to DIV element.
16831 getBdContainerEl : function() {
16832 return this._elBdContainer;
16836 * Returns DOM reference to the DataTable's fixed header TABLE element.
16838 * @method getHdTableEl
16839 * @return {HTMLElement} Reference to TABLE element.
16841 getHdTableEl : function() {
16842 return this._elHdTable;
16846 * Returns DOM reference to the DataTable's scrolling body TABLE element.
16848 * @method getBdTableEl
16849 * @return {HTMLElement} Reference to TABLE element.
16851 getBdTableEl : function() {
16852 return this._elTable;
16856 * Disables ScrollingDataTable UI.
16860 disable : function() {
16861 var elMask = this._elMask;
16862 elMask.style.width = this._elBdContainer.offsetWidth + "px";
16863 elMask.style.height = this._elHdContainer.offsetHeight + this._elBdContainer.offsetHeight + "px";
16864 elMask.style.display = "";
16865 this.fireEvent("disableEvent");
16869 * Removes given Column. NOTE: You cannot remove nested Columns. You can only remove
16870 * non-nested Columns, and top-level parent Columns (which will remove all
16871 * children Columns).
16873 * @method removeColumn
16874 * @param oColumn {YAHOO.widget.Column} Column instance.
16875 * @return oColumn {YAHOO.widget.Column} Removed Column instance.
16877 removeColumn : function(oColumn) {
16878 // Store scroll pos
16879 var hdPos = this._elHdContainer.scrollLeft;
16880 var bdPos = this._elBdContainer.scrollLeft;
16882 // Call superclass method
16883 oColumn = SDT.superclass.removeColumn.call(this, oColumn);
16885 // Restore scroll pos
16886 this._elHdContainer.scrollLeft = hdPos;
16887 this._elBdContainer.scrollLeft = bdPos;
16893 * Inserts given Column at the index if given, otherwise at the end. NOTE: You
16894 * can only add non-nested Columns and top-level parent Columns. You cannot add
16895 * a nested Column to an existing parent.
16897 * @method insertColumn
16898 * @param oColumn {Object | YAHOO.widget.Column} Object literal Column
16899 * definition or a Column instance.
16900 * @param index {Number} (optional) New tree index.
16901 * @return oColumn {YAHOO.widget.Column} Inserted Column instance.
16903 insertColumn : function(oColumn, index) {
16904 // Store scroll pos
16905 var hdPos = this._elHdContainer.scrollLeft;
16906 var bdPos = this._elBdContainer.scrollLeft;
16908 // Call superclass method
16909 var oNewColumn = SDT.superclass.insertColumn.call(this, oColumn, index);
16911 // Restore scroll pos
16912 this._elHdContainer.scrollLeft = hdPos;
16913 this._elBdContainer.scrollLeft = bdPos;
16919 * Removes given Column and inserts into given tree index. NOTE: You
16920 * can only reorder non-nested Columns and top-level parent Columns. You cannot
16921 * reorder a nested Column to an existing parent.
16923 * @method reorderColumn
16924 * @param oColumn {YAHOO.widget.Column} Column instance.
16925 * @param index {Number} New tree index.
16927 reorderColumn : function(oColumn, index) {
16928 // Store scroll pos
16929 var hdPos = this._elHdContainer.scrollLeft;
16930 var bdPos = this._elBdContainer.scrollLeft;
16932 // Call superclass method
16933 var oNewColumn = SDT.superclass.reorderColumn.call(this, oColumn, index);
16935 // Restore scroll pos
16936 this._elHdContainer.scrollLeft = hdPos;
16937 this._elBdContainer.scrollLeft = bdPos;
16943 * Sets given Column to given pixel width. If new width is less than minWidth
16944 * width, sets to minWidth. Updates oColumn.width value.
16946 * @method setColumnWidth
16947 * @param oColumn {YAHOO.widget.Column} Column instance.
16948 * @param nWidth {Number} New width in pixels.
16950 setColumnWidth : function(oColumn, nWidth) {
16951 oColumn = this.getColumn(oColumn);
16953 this._storeScrollPositions();
16955 // Validate new width against minWidth
16956 if(lang.isNumber(nWidth)) {
16957 nWidth = (nWidth > oColumn.minWidth) ? nWidth : oColumn.minWidth;
16960 oColumn.width = nWidth;
16962 // Resize the DOM elements
16963 this._setColumnWidth(oColumn, nWidth+"px");
16964 this._syncScroll();
16966 this.fireEvent("columnSetWidthEvent",{column:oColumn,width:nWidth});
16968 // Unsets a width to auto-size
16969 else if(nWidth === null) {
16971 oColumn.width = nWidth;
16973 // Resize the DOM elements
16974 this._setColumnWidth(oColumn, "auto");
16975 this.validateColumnWidths(oColumn);
16976 this.fireEvent("columnUnsetWidthEvent",{column:oColumn});
16979 // Bug 2339454: resize then sort misaligment
16980 this._clearTrTemplateEl();
16987 * Scrolls to given row or cell
16990 * @param to {YAHOO.widget.Record | HTMLElement } Itme to scroll to.
16992 scrollTo : function(to) {
16993 var td = this.getTdEl(to);
16995 this.clearScrollPositions();
16996 this.getBdContainerEl().scrollLeft = td.offsetLeft;
16997 this.getBdContainerEl().scrollTop = td.parentNode.offsetTop;
17000 var tr = this.getTrEl(to);
17002 this.clearScrollPositions();
17003 this.getBdContainerEl().scrollTop = tr.offsetTop;
17009 * Displays message within secondary TBODY.
17011 * @method showTableMessage
17012 * @param sHTML {String} (optional) Value for innerHTMlang.
17013 * @param sClassName {String} (optional) Classname.
17015 showTableMessage : function(sHTML, sClassName) {
17016 var elCell = this._elMsgTd;
17017 if(lang.isString(sHTML)) {
17018 elCell.firstChild.innerHTML = sHTML;
17020 if(lang.isString(sClassName)) {
17021 Dom.addClass(elCell.firstChild, sClassName);
17024 // Needed for SDT only
17025 var elThead = this.getTheadEl();
17026 var elTable = elThead.parentNode;
17027 var newWidth = elTable.offsetWidth;
17028 this._elMsgTbody.parentNode.style.width = this.getTheadEl().parentNode.offsetWidth + "px";
17030 this._elMsgTbody.style.display = "";
17032 this.fireEvent("tableMsgShowEvent", {html:sHTML, className:sClassName});
17047 /////////////////////////////////////////////////////////////////////////////
17049 // Private Custom Event Handlers
17051 /////////////////////////////////////////////////////////////////////////////
17054 * Handles Column mutations
17056 * @method onColumnChange
17057 * @param oArgs {Object} Custom Event data.
17059 _onColumnChange : function(oArg) {
17060 // Figure out which Column changed
17061 var oColumn = (oArg.column) ? oArg.column :
17062 (oArg.editor) ? oArg.editor.column : null;
17063 this._storeScrollPositions();
17064 this.validateColumnWidths(oColumn);
17081 /////////////////////////////////////////////////////////////////////////////
17083 // Private DOM Event Handlers
17085 /////////////////////////////////////////////////////////////////////////////
17088 * Syncs scrolltop and scrollleft of all TABLEs.
17090 * @method _onScroll
17091 * @param e {HTMLEvent} The scroll event.
17092 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
17095 _onScroll : function(e, oSelf) {
17096 oSelf._elHdContainer.scrollLeft = oSelf._elBdContainer.scrollLeft;
17098 if(oSelf._oCellEditor && oSelf._oCellEditor.isActive) {
17099 oSelf.fireEvent("editorBlurEvent", {editor:oSelf._oCellEditor});
17100 oSelf.cancelCellEditor();
17103 var elTarget = Ev.getTarget(e);
17104 var elTag = elTarget.nodeName.toLowerCase();
17105 oSelf.fireEvent("tableScrollEvent", {event:e, target:elTarget});
17109 * Handles keydown events on the THEAD element.
17111 * @method _onTheadKeydown
17112 * @param e {HTMLEvent} The key event.
17113 * @param oSelf {YAHOO.widget.ScrollingDataTable} ScrollingDataTable instance.
17116 _onTheadKeydown : function(e, oSelf) {
17117 // If tabbing to next TH label link causes THEAD to scroll,
17118 // need to sync scrollLeft with TBODY
17119 if(Ev.getCharCode(e) === 9) {
17120 setTimeout(function() {
17121 if((oSelf instanceof SDT) && oSelf._sId) {
17122 oSelf._elBdContainer.scrollLeft = oSelf._elHdContainer.scrollLeft;
17127 var elTarget = Ev.getTarget(e);
17128 var elTag = elTarget.nodeName.toLowerCase();
17129 var bKeepBubbling = true;
17130 while(elTarget && (elTag != "table")) {
17136 // TODO: implement textareaKeyEvent
17139 bKeepBubbling = oSelf.fireEvent("theadKeyEvent",{target:elTarget,event:e});
17144 if(bKeepBubbling === false) {
17148 elTarget = elTarget.parentNode;
17150 elTag = elTarget.nodeName.toLowerCase();
17154 oSelf.fireEvent("tableKeyEvent",{target:(elTarget || oSelf._elContainer),event:e});
17161 * Fired when a fixed scrolling DataTable has a scroll.
17163 * @event tableScrollEvent
17164 * @param oArgs.event {HTMLEvent} The event object.
17165 * @param oArgs.target {HTMLElement} The DataTable's CONTAINER element (in IE)
17166 * or the DataTable's TBODY element (everyone else).
17179 var lang = YAHOO.lang,
17181 widget = YAHOO.widget,
17187 DT = widget.DataTable;
17188 /****************************************************************************/
17189 /****************************************************************************/
17190 /****************************************************************************/
17193 * The BaseCellEditor class provides base functionality common to all inline cell
17194 * editors for a DataTable widget.
17196 * @namespace YAHOO.widget
17197 * @class BaseCellEditor
17198 * @uses YAHOO.util.EventProvider
17200 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
17201 * @param oConfigs {Object} (Optional) Object literal of configs.
17203 widget.BaseCellEditor = function(sType, oConfigs) {
17204 this._sId = this._sId || Dom.generateId(null, "yui-ceditor"); // "yui-ceditor" + YAHOO.widget.BaseCellEditor._nCount++;
17205 YAHOO.widget.BaseCellEditor._nCount++;
17206 this._sType = sType;
17209 this._initConfigs(oConfigs);
17211 // Create Custom Events
17212 this._initEvents();
17214 // UI needs to be drawn
17215 this._needsRender = true;
17218 var BCE = widget.BaseCellEditor;
17220 /////////////////////////////////////////////////////////////////////////////
17224 /////////////////////////////////////////////////////////////////////////////
17225 lang.augmentObject(BCE, {
17228 * Global instance counter.
17230 * @property CellEditor._nCount
17239 * Class applied to CellEditor container.
17241 * @property CellEditor.CLASS_CELLEDITOR
17244 * @default "yui-ceditor"
17246 CLASS_CELLEDITOR : "yui-ceditor"
17251 /////////////////////////////////////////////////////////////////////////////
17255 /////////////////////////////////////////////////////////////////////////////
17257 * Unique id assigned to instance "yui-ceditorN", useful prefix for generating unique
17258 * DOM ID strings and log messages.
17276 * DataTable instance.
17278 * @property _oDataTable
17279 * @type YAHOO.widget.DataTable
17282 _oDataTable : null,
17287 * @property _oColumn
17288 * @type YAHOO.widget.Column
17297 * @property _oRecord
17298 * @type YAHOO.widget.Record
17308 * @type HTMLElement
17315 * Container for inline editor.
17317 * @property _elContainer
17318 * @type HTMLElement
17321 _elContainer : null,
17324 * Reference to Cancel button, if available.
17326 * @property _elCancelBtn
17327 * @type HTMLElement
17331 _elCancelBtn : null,
17334 * Reference to Save button, if available.
17336 * @property _elSaveBtn
17337 * @type HTMLElement
17350 /////////////////////////////////////////////////////////////////////////////
17354 /////////////////////////////////////////////////////////////////////////////
17357 * Initialize configs.
17359 * @method _initConfigs
17362 _initConfigs : function(oConfigs) {
17363 // Object literal defines CellEditor configs
17364 if(oConfigs && YAHOO.lang.isObject(oConfigs)) {
17365 for(var sConfig in oConfigs) {
17367 this[sConfig] = oConfigs[sConfig];
17374 * Initialize Custom Events.
17376 * @method _initEvents
17379 _initEvents : function() {
17380 this.createEvent("showEvent");
17381 this.createEvent("keydownEvent");
17382 this.createEvent("invalidDataEvent");
17383 this.createEvent("revertEvent");
17384 this.createEvent("saveEvent");
17385 this.createEvent("cancelEvent");
17386 this.createEvent("blurEvent");
17387 this.createEvent("blockEvent");
17388 this.createEvent("unblockEvent");
17392 * Initialize container element.
17394 * @method _initContainerEl
17397 _initContainerEl : function() {
17398 if(this._elContainer) {
17399 YAHOO.util.Event.purgeElement(this._elContainer, true);
17400 this._elContainer.innerHTML = "";
17403 var elContainer = document.createElement("div");
17404 elContainer.id = this.getId() + "-container"; // Needed for tracking blur event
17405 elContainer.style.display = "none";
17406 elContainer.tabIndex = 0;
17408 this.className = lang.isArray(this.className) ? this.className : this.className ? [this.className] : [];
17409 this.className[this.className.length] = DT.CLASS_EDITOR;
17410 elContainer.className = this.className.join(" ");
17412 document.body.insertBefore(elContainer, document.body.firstChild);
17413 this._elContainer = elContainer;
17417 * Initialize container shim element.
17419 * @method _initShimEl
17422 _initShimEl : function() {
17424 if(this.useIFrame) {
17425 if(!this._elIFrame) {
17426 var elIFrame = document.createElement("iframe");
17427 elIFrame.src = "javascript:false";
17428 elIFrame.frameBorder = 0;
17429 elIFrame.scrolling = "no";
17430 elIFrame.style.display = "none";
17431 elIFrame.className = DT.CLASS_EDITOR_SHIM;
17432 elIFrame.tabIndex = -1;
17433 elIFrame.role = "presentation";
17434 elIFrame.title = "Presentational iframe shim";
17435 document.body.insertBefore(elIFrame, document.body.firstChild);
17436 this._elIFrame = elIFrame;
17442 * Hides CellEditor UI at end of interaction.
17446 _hide : function() {
17447 this.getContainerEl().style.display = "none";
17448 if(this._elIFrame) {
17449 this._elIFrame.style.display = "none";
17451 this.isActive = false;
17452 this.getDataTable()._oCellEditor = null;
17465 /////////////////////////////////////////////////////////////////////////////
17467 // Public properties
17469 /////////////////////////////////////////////////////////////////////////////
17471 * Implementer defined function that can submit the input value to a server. This
17472 * function must accept the arguments fnCallback and oNewValue. When the submission
17473 * is complete, the function must also call fnCallback(bSuccess, oNewValue) to
17474 * finish the save routine in the CellEditor. This function can also be used to
17475 * perform extra validation or input value manipulation.
17477 * @property asyncSubmitter
17478 * @type HTMLFunction
17480 asyncSubmitter : null,
17491 * Default value in case Record data is undefined. NB: Null values will not trigger
17492 * the default value.
17494 * @property defaultValue
17498 defaultValue : null,
17501 * Validator function for input data, called from the DataTable instance scope,
17502 * receives the arguments (inputValue, currentValue, editorInstance) and returns
17503 * either the validated (or type-converted) value or undefined.
17505 * @property validator
17506 * @type HTMLFunction
17512 * If validation is enabled, resets input field of invalid data.
17514 * @property resetInvalidData
17518 resetInvalidData : true,
17521 * True if currently active.
17523 * @property isActive
17529 * Text to display on Save button.
17531 * @property LABEL_SAVE
17535 LABEL_SAVE : "Save",
17538 * Text to display on Cancel button.
17540 * @property LABEL_CANCEL
17542 * @default "Cancel"
17544 LABEL_CANCEL : "Cancel",
17547 * True if Save/Cancel buttons should not be displayed in the CellEditor.
17549 * @property disableBtns
17553 disableBtns : false,
17556 * True if iframe shim for container element should be enabled.
17558 * @property useIFrame
17565 * Custom CSS class or array of classes applied to the container element.
17567 * @property className
17568 * @type String || String[]
17576 /////////////////////////////////////////////////////////////////////////////
17580 /////////////////////////////////////////////////////////////////////////////
17582 * CellEditor instance name, for logging.
17585 * @return {String} Unique name of the CellEditor instance.
17588 toString : function() {
17589 return "CellEditor instance " + this._sId;
17593 * CellEditor unique ID.
17596 * @return {String} Unique ID of the CellEditor instance.
17599 getId : function() {
17604 * Returns reference to associated DataTable instance.
17606 * @method getDataTable
17607 * @return {YAHOO.widget.DataTable} DataTable instance.
17610 getDataTable : function() {
17611 return this._oDataTable;
17615 * Returns reference to associated Column instance.
17617 * @method getColumn
17618 * @return {YAHOO.widget.Column} Column instance.
17621 getColumn : function() {
17622 return this._oColumn;
17626 * Returns reference to associated Record instance.
17628 * @method getRecord
17629 * @return {YAHOO.widget.Record} Record instance.
17632 getRecord : function() {
17633 return this._oRecord;
17639 * Returns reference to associated TD element.
17642 * @return {HTMLElement} TD element.
17645 getTdEl : function() {
17650 * Returns container element.
17652 * @method getContainerEl
17653 * @return {HTMLElement} Reference to container element.
17656 getContainerEl : function() {
17657 return this._elContainer;
17661 * Nulls out the entire CellEditor instance and related objects, removes attached
17662 * event listeners, and clears out DOM elements inside the container, removes
17663 * container from the DOM.
17667 destroy : function() {
17668 this.unsubscribeAll();
17670 // Column is late-binding in attach()
17671 var oColumn = this.getColumn();
17673 oColumn.editor = null;
17676 var elContainer = this.getContainerEl();
17678 Ev.purgeElement(elContainer, true);
17679 elContainer.parentNode.removeChild(elContainer);
17684 * Renders DOM elements and attaches event listeners.
17688 render : function() {
17689 if (!this._needsRender) {
17693 this._initContainerEl();
17694 this._initShimEl();
17697 Ev.addListener(this.getContainerEl(), "keydown", function(e, oSelf) {
17698 // ESC cancels Cell Editor
17699 if((e.keyCode == 27)) {
17700 var target = Ev.getTarget(e);
17701 // workaround for Mac FF3 bug that disabled clicks when ESC hit when
17702 // select is open. [bug 2273056]
17703 if (target.nodeName && target.nodeName.toLowerCase() === 'select') {
17708 // Pass through event
17709 oSelf.fireEvent("keydownEvent", {editor:oSelf, event:e});
17714 // Show Save/Cancel buttons
17715 if(!this.disableBtns) {
17719 this.doAfterRender();
17720 this._needsRender = false;
17724 * Renders Save/Cancel buttons.
17726 * @method renderBtns
17728 renderBtns : function() {
17730 var elBtnsDiv = this.getContainerEl().appendChild(document.createElement("div"));
17731 elBtnsDiv.className = DT.CLASS_BUTTON;
17734 var elSaveBtn = elBtnsDiv.appendChild(document.createElement("button"));
17735 elSaveBtn.className = DT.CLASS_DEFAULT;
17736 elSaveBtn.innerHTML = this.LABEL_SAVE;
17737 Ev.addListener(elSaveBtn, "click", function(oArgs) {
17740 this._elSaveBtn = elSaveBtn;
17743 var elCancelBtn = elBtnsDiv.appendChild(document.createElement("button"));
17744 elCancelBtn.innerHTML = this.LABEL_CANCEL;
17745 Ev.addListener(elCancelBtn, "click", function(oArgs) {
17748 this._elCancelBtn = elCancelBtn;
17752 * Attach CellEditor for a new interaction.
17755 * @param oDataTable {YAHOO.widget.DataTable} Associated DataTable instance.
17756 * @param elCell {HTMLElement} Cell to edit.
17758 attach : function(oDataTable, elCell) {
17760 if(oDataTable instanceof YAHOO.widget.DataTable) {
17761 this._oDataTable = oDataTable;
17764 elCell = oDataTable.getTdEl(elCell);
17766 this._elTd = elCell;
17769 var oColumn = oDataTable.getColumn(elCell);
17771 this._oColumn = oColumn;
17774 var oRecord = oDataTable.getRecord(elCell);
17776 this._oRecord = oRecord;
17777 var value = oRecord.getData(this.getColumn().getField());
17778 this.value = (value !== undefined) ? value : this.defaultValue;
17788 * Moves container into position for display.
17792 move : function() {
17794 var elContainer = this.getContainerEl(),
17795 elTd = this.getTdEl(),
17796 x = Dom.getX(elTd),
17797 y = Dom.getY(elTd);
17799 //TODO: remove scrolling logic
17800 // SF doesn't get xy for cells in scrolling table
17801 // when tbody display is set to block
17802 if(isNaN(x) || isNaN(y)) {
17803 var elTbody = this.getDataTable().getTbodyEl();
17804 x = elTd.offsetLeft + // cell pos relative to table
17805 Dom.getX(elTbody.parentNode) - // plus table pos relative to document
17806 elTbody.scrollLeft; // minus tbody scroll
17807 y = elTd.offsetTop + // cell pos relative to table
17808 Dom.getY(elTbody.parentNode) - // plus table pos relative to document
17809 elTbody.scrollTop + // minus tbody scroll
17810 this.getDataTable().getTheadEl().offsetHeight; // account for fixed THEAD cells
17813 elContainer.style.left = x + "px";
17814 elContainer.style.top = y + "px";
17816 if(this._elIFrame) {
17817 this._elIFrame.style.left = x + "px";
17818 this._elIFrame.style.top = y + "px";
17823 * Displays CellEditor UI in the correct position.
17827 show : function() {
17828 var elContainer = this.getContainerEl(),
17829 elIFrame = this._elIFrame;
17831 this.isActive = true;
17832 elContainer.style.display = "";
17834 elIFrame.style.width = elContainer.offsetWidth + "px";
17835 elIFrame.style.height = elContainer.offsetHeight + "px";
17836 elIFrame.style.display = "";
17839 this.fireEvent("showEvent", {editor:this});
17847 block : function() {
17848 this.fireEvent("blockEvent", {editor:this});
17852 * Fires unblockEvent
17856 unblock : function() {
17857 this.fireEvent("unblockEvent", {editor:this});
17861 * Saves value of CellEditor and hides UI.
17865 save : function() {
17867 var inputValue = this.getInputValue();
17868 var validValue = inputValue;
17870 // Validate new value
17871 if(this.validator) {
17872 validValue = this.validator.call(this.getDataTable(), inputValue, this.value, this);
17873 if(validValue === undefined ) {
17874 if(this.resetInvalidData) {
17877 this.fireEvent("invalidDataEvent",
17878 {editor:this, oldData:this.value, newData:inputValue});
17884 var finishSave = function(bSuccess, oNewValue) {
17885 var oOrigValue = oSelf.value;
17887 // Update new value
17888 oSelf.value = oNewValue;
17889 oSelf.getDataTable().updateCell(oSelf.getRecord(), oSelf.getColumn(), oNewValue);
17894 oSelf.fireEvent("saveEvent",
17895 {editor:oSelf, oldData:oOrigValue, newData:oSelf.value});
17899 oSelf.fireEvent("revertEvent",
17900 {editor:oSelf, oldData:oOrigValue, newData:oNewValue});
17906 if(lang.isFunction(this.asyncSubmitter)) {
17907 this.asyncSubmitter.call(this, finishSave, validValue);
17910 finishSave(true, validValue);
17915 * Cancels CellEditor input and hides UI.
17919 cancel : function() {
17920 if(this.isActive) {
17922 this.fireEvent("cancelEvent", {editor:this});
17929 * Renders form elements.
17931 * @method renderForm
17933 renderForm : function() {
17934 // To be implemented by subclass
17938 * Access to add additional event listeners.
17940 * @method doAfterRender
17942 doAfterRender : function() {
17943 // To be implemented by subclass
17948 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
17949 * to save input without them.
17951 * @method handleDisabledBtns
17953 handleDisabledBtns : function() {
17954 // To be implemented by subclass
17958 * Resets CellEditor UI to initial state.
17960 * @method resetForm
17962 resetForm : function() {
17963 // To be implemented by subclass
17967 * Sets focus in CellEditor.
17971 focus : function() {
17972 // To be implemented by subclass
17976 * Retrieves input value from CellEditor.
17978 * @method getInputValue
17980 getInputValue : function() {
17981 // To be implemented by subclass
17986 lang.augmentProto(BCE, util.EventProvider);
17989 /////////////////////////////////////////////////////////////////////////////
17993 /////////////////////////////////////////////////////////////////////////////
17996 * Fired when a CellEditor is shown.
17999 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
18003 * Fired when a CellEditor has a keydown.
18005 * @event keydownEvent
18006 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
18007 * @param oArgs.event {HTMLEvent} The event object.
18011 * Fired when a CellEditor input is reverted due to invalid data.
18013 * @event invalidDataEvent
18014 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
18015 * @param oArgs.newData {Object} New data value from form input field.
18016 * @param oArgs.oldData {Object} Old data value.
18020 * Fired when a CellEditor input is reverted due to asyncSubmitter failure.
18022 * @event revertEvent
18023 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
18024 * @param oArgs.newData {Object} New data value from form input field.
18025 * @param oArgs.oldData {Object} Old data value.
18029 * Fired when a CellEditor input is saved.
18032 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
18033 * @param oArgs.newData {Object} New data value from form input field.
18034 * @param oArgs.oldData {Object} Old data value.
18038 * Fired when a CellEditor input is canceled.
18040 * @event cancelEvent
18041 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
18045 * Fired when a CellEditor has a blur event.
18048 * @param oArgs.editor {YAHOO.widget.CellEditor} The CellEditor instance.
18064 /****************************************************************************/
18065 /****************************************************************************/
18066 /****************************************************************************/
18069 * The CheckboxCellEditor class provides functionality for inline editing
18070 * DataTable cell data with checkboxes.
18072 * @namespace YAHOO.widget
18073 * @class CheckboxCellEditor
18074 * @extends YAHOO.widget.BaseCellEditor
18076 * @param oConfigs {Object} (Optional) Object literal of configs.
18078 widget.CheckboxCellEditor = function(oConfigs) {
18079 oConfigs = oConfigs || {};
18080 this._sId = this._sId || Dom.generateId(null, "yui-checkboxceditor"); // "yui-checkboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18081 YAHOO.widget.BaseCellEditor._nCount++;
18082 widget.CheckboxCellEditor.superclass.constructor.call(this, oConfigs.type || "checkbox", oConfigs);
18085 // CheckboxCellEditor extends BaseCellEditor
18086 lang.extend(widget.CheckboxCellEditor, BCE, {
18088 /////////////////////////////////////////////////////////////////////////////
18090 // CheckboxCellEditor public properties
18092 /////////////////////////////////////////////////////////////////////////////
18094 * Array of checkbox values. Can either be a simple array (e.g., ["red","green","blue"])
18095 * or a an array of objects (e.g., [{label:"red", value:"#FF0000"},
18096 * {label:"green", value:"#00FF00"}, {label:"blue", value:"#0000FF"}]). String
18097 * values are treated as markup and inserted into the DOM as innerHTML.
18099 * @property checkboxOptions
18100 * @type HTML[] | Object[]
18102 checkboxOptions : null,
18105 * Reference to the checkbox elements.
18107 * @property checkboxes
18108 * @type HTMLElement[]
18113 * Array of checked values
18120 /////////////////////////////////////////////////////////////////////////////
18122 // CheckboxCellEditor public methods
18124 /////////////////////////////////////////////////////////////////////////////
18127 * Render a form with input(s) type=checkbox.
18129 * @method renderForm
18131 renderForm : function() {
18132 if(lang.isArray(this.checkboxOptions)) {
18133 var checkboxOption, checkboxValue, checkboxId, elLabel, j, len;
18135 // Create the checkbox buttons in an IE-friendly way...
18136 for(j=0,len=this.checkboxOptions.length; j<len; j++) {
18137 checkboxOption = this.checkboxOptions[j];
18138 checkboxValue = lang.isValue(checkboxOption.value) ?
18139 checkboxOption.value : checkboxOption;
18141 checkboxId = this.getId() + "-chk" + j;
18142 this.getContainerEl().innerHTML += "<input type=\"checkbox\"" +
18143 " id=\"" + checkboxId + "\"" + // Needed for label
18144 " value=\"" + checkboxValue + "\" />";
18146 // Create the labels in an IE-friendly way
18147 elLabel = this.getContainerEl().appendChild(document.createElement("label"));
18148 elLabel.htmlFor = checkboxId;
18149 elLabel.innerHTML = lang.isValue(checkboxOption.label) ?
18150 checkboxOption.label : checkboxOption;
18153 // Store the reference to the checkbox elements
18154 var allCheckboxes = [];
18155 for(j=0; j<len; j++) {
18156 allCheckboxes[allCheckboxes.length] = this.getContainerEl().childNodes[j*2];
18158 this.checkboxes = allCheckboxes;
18160 if(this.disableBtns) {
18161 this.handleDisabledBtns();
18169 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18170 * to save input without them.
18172 * @method handleDisabledBtns
18174 handleDisabledBtns : function() {
18175 Ev.addListener(this.getContainerEl(), "click", function(v){
18176 if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
18184 * Resets CheckboxCellEditor UI to initial state.
18186 * @method resetForm
18188 resetForm : function() {
18189 // Normalize to array
18190 var originalValues = lang.isArray(this.value) ? this.value : [this.value];
18192 // Match checks to value
18193 for(var i=0, j=this.checkboxes.length; i<j; i++) {
18194 this.checkboxes[i].checked = false;
18195 for(var k=0, len=originalValues.length; k<len; k++) {
18196 if(this.checkboxes[i].value == originalValues[k]) {
18197 this.checkboxes[i].checked = true;
18204 * Sets focus in CheckboxCellEditor.
18208 focus : function() {
18209 this.checkboxes[0].focus();
18213 * Retrieves input value from CheckboxCellEditor.
18215 * @method getInputValue
18217 getInputValue : function() {
18218 var checkedValues = [];
18219 for(var i=0, j=this.checkboxes.length; i<j; i++) {
18220 if(this.checkboxes[i].checked) {
18221 checkedValues[checkedValues.length] = this.checkboxes[i].value;
18224 return checkedValues;
18229 // Copy static members to CheckboxCellEditor class
18230 lang.augmentObject(widget.CheckboxCellEditor, BCE);
18239 /****************************************************************************/
18240 /****************************************************************************/
18241 /****************************************************************************/
18244 * The DataCellEditor class provides functionality for inline editing
18245 * DataTable cell data with a YUI Calendar.
18247 * @namespace YAHOO.widget
18248 * @class DateCellEditor
18249 * @extends YAHOO.widget.BaseCellEditor
18251 * @param oConfigs {Object} (Optional) Object literal of configs.
18253 widget.DateCellEditor = function(oConfigs) {
18254 oConfigs = oConfigs || {};
18255 this._sId = this._sId || Dom.generateId(null, "yui-dateceditor"); // "yui-dateceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18256 YAHOO.widget.BaseCellEditor._nCount++;
18257 widget.DateCellEditor.superclass.constructor.call(this, oConfigs.type || "date", oConfigs);
18260 // CheckboxCellEditor extends BaseCellEditor
18261 lang.extend(widget.DateCellEditor, BCE, {
18263 /////////////////////////////////////////////////////////////////////////////
18265 // DateCellEditor public properties
18267 /////////////////////////////////////////////////////////////////////////////
18269 * Reference to Calendar instance.
18271 * @property calendar
18272 * @type YAHOO.widget.Calendar
18277 * Configs for the calendar instance, to be passed to Calendar constructor.
18279 * @property calendarOptions
18282 calendarOptions : null,
18287 * @property defaultValue
18289 * @default new Date()
18291 defaultValue : new Date(),
18294 /////////////////////////////////////////////////////////////////////////////
18296 // DateCellEditor public methods
18298 /////////////////////////////////////////////////////////////////////////////
18301 * Render a Calendar.
18303 * @method renderForm
18305 renderForm : function() {
18307 if(YAHOO.widget.Calendar) {
18308 var calContainer = this.getContainerEl().appendChild(document.createElement("div"));
18309 calContainer.id = this.getId() + "-dateContainer"; // Needed for Calendar constructor
18311 new YAHOO.widget.Calendar(this.getId() + "-date",
18312 calContainer.id, this.calendarOptions);
18314 calContainer.style.cssFloat = "none";
18317 calendar.hideEvent.subscribe(function() {this.cancel();}, this, true);
18320 var calFloatClearer = this.getContainerEl().appendChild(document.createElement("div"));
18321 calFloatClearer.style.clear = "both";
18324 this.calendar = calendar;
18326 if(this.disableBtns) {
18327 this.handleDisabledBtns();
18336 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18337 * to save input without them.
18339 * @method handleDisabledBtns
18341 handleDisabledBtns : function() {
18342 this.calendar.selectEvent.subscribe(function(v){
18349 * Resets DateCellEditor UI to initial state.
18351 * @method resetForm
18353 resetForm : function() {
18354 var value = this.value || (new Date());
18355 this.calendar.select(value);
18356 this.calendar.cfg.setProperty("pagedate",value,false);
18357 this.calendar.render();
18359 this.calendar.show();
18363 * Sets focus in DateCellEditor.
18367 focus : function() {
18368 // To be impmlemented by subclass
18372 * Retrieves input value from DateCellEditor.
18374 * @method getInputValue
18376 getInputValue : function() {
18377 return this.calendar.getSelectedDates()[0];
18382 // Copy static members to DateCellEditor class
18383 lang.augmentObject(widget.DateCellEditor, BCE);
18393 /****************************************************************************/
18394 /****************************************************************************/
18395 /****************************************************************************/
18398 * The DropdownCellEditor class provides functionality for inline editing
18399 * DataTable cell data a SELECT element.
18401 * @namespace YAHOO.widget
18402 * @class DropdownCellEditor
18403 * @extends YAHOO.widget.BaseCellEditor
18405 * @param oConfigs {Object} (Optional) Object literal of configs.
18407 widget.DropdownCellEditor = function(oConfigs) {
18408 oConfigs = oConfigs || {};
18409 this._sId = this._sId || Dom.generateId(null, "yui-dropdownceditor"); // "yui-dropdownceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18410 YAHOO.widget.BaseCellEditor._nCount++;
18411 widget.DropdownCellEditor.superclass.constructor.call(this, oConfigs.type || "dropdown", oConfigs);
18414 // DropdownCellEditor extends BaseCellEditor
18415 lang.extend(widget.DropdownCellEditor, BCE, {
18417 /////////////////////////////////////////////////////////////////////////////
18419 // DropdownCellEditor public properties
18421 /////////////////////////////////////////////////////////////////////////////
18423 * Array of dropdown values. Can either be a simple array (e.g.,
18424 * ["Alabama","Alaska","Arizona","Arkansas"]) or a an array of objects (e.g.,
18425 * [{label:"Alabama", value:"AL"}, {label:"Alaska", value:"AK"},
18426 * {label:"Arizona", value:"AZ"}, {label:"Arkansas", value:"AR"}]). String
18427 * values are treated as markup and inserted into the DOM as innerHTML.
18429 * @property dropdownOptions
18430 * @type HTML[] | Object[]
18432 dropdownOptions : null,
18435 * Reference to Dropdown element.
18437 * @property dropdown
18438 * @type HTMLElement
18443 * Enables multi-select.
18445 * @property multiple
18451 * Specifies number of visible options.
18458 /////////////////////////////////////////////////////////////////////////////
18460 // DropdownCellEditor public methods
18462 /////////////////////////////////////////////////////////////////////////////
18465 * Render a form with select element.
18467 * @method renderForm
18469 renderForm : function() {
18470 var elDropdown = this.getContainerEl().appendChild(document.createElement("select"));
18471 elDropdown.style.zoom = 1;
18472 if(this.multiple) {
18473 elDropdown.multiple = "multiple";
18475 if(lang.isNumber(this.size)) {
18476 elDropdown.size = this.size;
18478 this.dropdown = elDropdown;
18480 if(lang.isArray(this.dropdownOptions)) {
18481 var dropdownOption, elOption;
18482 for(var i=0, j=this.dropdownOptions.length; i<j; i++) {
18483 dropdownOption = this.dropdownOptions[i];
18484 elOption = document.createElement("option");
18485 elOption.value = (lang.isValue(dropdownOption.value)) ?
18486 dropdownOption.value : dropdownOption;
18487 elOption.innerHTML = (lang.isValue(dropdownOption.label)) ?
18488 dropdownOption.label : dropdownOption;
18489 elOption = elDropdown.appendChild(elOption);
18492 if(this.disableBtns) {
18493 this.handleDisabledBtns();
18499 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18500 * to save input without them.
18502 * @method handleDisabledBtns
18504 handleDisabledBtns : function() {
18505 // Save on blur for multi-select
18506 if(this.multiple) {
18507 Ev.addListener(this.dropdown, "blur", function(v){
18512 // Save on change for single-select
18515 Ev.addListener(this.dropdown, "change", function(v){
18521 // Bug 2529274: "change" event is not keyboard accessible in IE6
18522 Ev.addListener(this.dropdown, "blur", function(v){
18525 Ev.addListener(this.dropdown, "click", function(v){
18533 * Resets DropdownCellEditor UI to initial state.
18535 * @method resetForm
18537 resetForm : function() {
18538 var allOptions = this.dropdown.options,
18539 i=0, j=allOptions.length;
18541 // Look for multi-select selections
18542 if(lang.isArray(this.value)) {
18543 var allValues = this.value,
18544 m=0, n=allValues.length,
18546 // Reset all selections and stash options in a value hash
18548 allOptions[i].selected = false;
18549 hash[allOptions[i].value] = allOptions[i];
18552 if(hash[allValues[m]]) {
18553 hash[allValues[m]].selected = true;
18557 // Only need to look for a single selection
18560 if(this.value == allOptions[i].value) {
18561 allOptions[i].selected = true;
18568 * Sets focus in DropdownCellEditor.
18572 focus : function() {
18573 this.getDataTable()._focusEl(this.dropdown);
18577 * Retrieves input value from DropdownCellEditor.
18579 * @method getInputValue
18581 getInputValue : function() {
18582 var allOptions = this.dropdown.options;
18584 // Look for multiple selections
18585 if(this.multiple) {
18587 i=0, j=allOptions.length;
18589 if(allOptions[i].selected) {
18590 values.push(allOptions[i].value);
18595 // Only need to look for single selection
18597 return allOptions[allOptions.selectedIndex].value;
18603 // Copy static members to DropdownCellEditor class
18604 lang.augmentObject(widget.DropdownCellEditor, BCE);
18611 /****************************************************************************/
18612 /****************************************************************************/
18613 /****************************************************************************/
18616 * The RadioCellEditor class provides functionality for inline editing
18617 * DataTable cell data with radio buttons.
18619 * @namespace YAHOO.widget
18620 * @class RadioCellEditor
18621 * @extends YAHOO.widget.BaseCellEditor
18623 * @param oConfigs {Object} (Optional) Object literal of configs.
18625 widget.RadioCellEditor = function(oConfigs) {
18626 oConfigs = oConfigs || {};
18627 this._sId = this._sId || Dom.generateId(null, "yui-radioceditor"); // "yui-radioceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18628 YAHOO.widget.BaseCellEditor._nCount++;
18629 widget.RadioCellEditor.superclass.constructor.call(this, oConfigs.type || "radio", oConfigs);
18632 // RadioCellEditor extends BaseCellEditor
18633 lang.extend(widget.RadioCellEditor, BCE, {
18635 /////////////////////////////////////////////////////////////////////////////
18637 // RadioCellEditor public properties
18639 /////////////////////////////////////////////////////////////////////////////
18641 * Reference to radio elements.
18644 * @type HTMLElement[]
18649 * Array of radio values. Can either be a simple array (e.g., ["yes","no","maybe"])
18650 * or a an array of objects (e.g., [{label:"yes", value:1}, {label:"no", value:-1},
18651 * {label:"maybe", value:0}]). String values are treated as markup and inserted
18652 * into the DOM as innerHTML.
18654 * @property radioOptions
18655 * @type HTML[] | Object[]
18657 radioOptions : null,
18659 /////////////////////////////////////////////////////////////////////////////
18661 // RadioCellEditor public methods
18663 /////////////////////////////////////////////////////////////////////////////
18666 * Render a form with input(s) type=radio.
18668 * @method renderForm
18670 renderForm : function() {
18671 if(lang.isArray(this.radioOptions)) {
18672 var radioOption, radioValue, radioId, elLabel;
18674 // Create the radio buttons in an IE-friendly way
18675 for(var i=0, len=this.radioOptions.length; i<len; i++) {
18676 radioOption = this.radioOptions[i];
18677 radioValue = lang.isValue(radioOption.value) ?
18678 radioOption.value : radioOption;
18679 radioId = this.getId() + "-radio" + i;
18680 this.getContainerEl().innerHTML += "<input type=\"radio\"" +
18681 " name=\"" + this.getId() + "\"" +
18682 " value=\"" + radioValue + "\"" +
18683 " id=\"" + radioId + "\" />"; // Needed for label
18685 // Create the labels in an IE-friendly way
18686 elLabel = this.getContainerEl().appendChild(document.createElement("label"));
18687 elLabel.htmlFor = radioId;
18688 elLabel.innerHTML = (lang.isValue(radioOption.label)) ?
18689 radioOption.label : radioOption;
18692 // Store the reference to the checkbox elements
18693 var allRadios = [],
18695 for(var j=0; j<len; j++) {
18696 elRadio = this.getContainerEl().childNodes[j*2];
18697 allRadios[allRadios.length] = elRadio;
18699 this.radios = allRadios;
18701 if(this.disableBtns) {
18702 this.handleDisabledBtns();
18710 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18711 * to save input without them.
18713 * @method handleDisabledBtns
18715 handleDisabledBtns : function() {
18716 Ev.addListener(this.getContainerEl(), "click", function(v){
18717 if(Ev.getTarget(v).tagName.toLowerCase() === "input") {
18725 * Resets RadioCellEditor UI to initial state.
18727 * @method resetForm
18729 resetForm : function() {
18730 for(var i=0, j=this.radios.length; i<j; i++) {
18731 var elRadio = this.radios[i];
18732 if(this.value == elRadio.value) {
18733 elRadio.checked = true;
18740 * Sets focus in RadioCellEditor.
18744 focus : function() {
18745 for(var i=0, j=this.radios.length; i<j; i++) {
18746 if(this.radios[i].checked) {
18747 this.radios[i].focus();
18754 * Retrieves input value from RadioCellEditor.
18756 * @method getInputValue
18758 getInputValue : function() {
18759 for(var i=0, j=this.radios.length; i<j; i++) {
18760 if(this.radios[i].checked) {
18761 return this.radios[i].value;
18768 // Copy static members to RadioCellEditor class
18769 lang.augmentObject(widget.RadioCellEditor, BCE);
18776 /****************************************************************************/
18777 /****************************************************************************/
18778 /****************************************************************************/
18781 * The TextareaCellEditor class provides functionality for inline editing
18782 * DataTable cell data with a TEXTAREA element.
18784 * @namespace YAHOO.widget
18785 * @class TextareaCellEditor
18786 * @extends YAHOO.widget.BaseCellEditor
18788 * @param oConfigs {Object} (Optional) Object literal of configs.
18790 widget.TextareaCellEditor = function(oConfigs) {
18791 oConfigs = oConfigs || {};
18792 this._sId = this._sId || Dom.generateId(null, "yui-textareaceditor");// "yui-textareaceditor" + ;
18793 YAHOO.widget.BaseCellEditor._nCount++;
18794 widget.TextareaCellEditor.superclass.constructor.call(this, oConfigs.type || "textarea", oConfigs);
18797 // TextareaCellEditor extends BaseCellEditor
18798 lang.extend(widget.TextareaCellEditor, BCE, {
18800 /////////////////////////////////////////////////////////////////////////////
18802 // TextareaCellEditor public properties
18804 /////////////////////////////////////////////////////////////////////////////
18806 * Reference to textarea element.
18808 * @property textarea
18809 * @type HTMLElement
18814 /////////////////////////////////////////////////////////////////////////////
18816 // TextareaCellEditor public methods
18818 /////////////////////////////////////////////////////////////////////////////
18821 * Render a form with textarea.
18823 * @method renderForm
18825 renderForm : function() {
18826 var elTextarea = this.getContainerEl().appendChild(document.createElement("textarea"));
18827 this.textarea = elTextarea;
18829 if(this.disableBtns) {
18830 this.handleDisabledBtns();
18835 * After rendering form, if disabledBtns is set to true, then sets up a mechanism
18836 * to save input without them.
18838 * @method handleDisabledBtns
18840 handleDisabledBtns : function() {
18841 Ev.addListener(this.textarea, "blur", function(v){
18848 * Moves TextareaCellEditor UI to a cell.
18852 move : function() {
18853 this.textarea.style.width = this.getTdEl().offsetWidth + "px";
18854 this.textarea.style.height = "3em";
18855 YAHOO.widget.TextareaCellEditor.superclass.move.call(this);
18859 * Resets TextareaCellEditor UI to initial state.
18861 * @method resetForm
18863 resetForm : function() {
18864 this.textarea.value = this.value;
18868 * Sets focus in TextareaCellEditor.
18872 focus : function() {
18873 // Bug 2303181, Bug 2263600
18874 this.getDataTable()._focusEl(this.textarea);
18875 this.textarea.select();
18879 * Retrieves input value from TextareaCellEditor.
18881 * @method getInputValue
18883 getInputValue : function() {
18884 return this.textarea.value;
18889 // Copy static members to TextareaCellEditor class
18890 lang.augmentObject(widget.TextareaCellEditor, BCE);
18900 /****************************************************************************/
18901 /****************************************************************************/
18902 /****************************************************************************/
18905 * The TextboxCellEditor class provides functionality for inline editing
18906 * DataTable cell data with an INPUT TYPE=TEXT element.
18908 * @namespace YAHOO.widget
18909 * @class TextboxCellEditor
18910 * @extends YAHOO.widget.BaseCellEditor
18912 * @param oConfigs {Object} (Optional) Object literal of configs.
18914 widget.TextboxCellEditor = function(oConfigs) {
18915 oConfigs = oConfigs || {};
18916 this._sId = this._sId || Dom.generateId(null, "yui-textboxceditor");// "yui-textboxceditor" + YAHOO.widget.BaseCellEditor._nCount++;
18917 YAHOO.widget.BaseCellEditor._nCount++;
18918 widget.TextboxCellEditor.superclass.constructor.call(this, oConfigs.type || "textbox", oConfigs);
18921 // TextboxCellEditor extends BaseCellEditor
18922 lang.extend(widget.TextboxCellEditor, BCE, {
18924 /////////////////////////////////////////////////////////////////////////////
18926 // TextboxCellEditor public properties
18928 /////////////////////////////////////////////////////////////////////////////
18930 * Reference to the textbox element.
18932 * @property textbox
18936 /////////////////////////////////////////////////////////////////////////////
18938 // TextboxCellEditor public methods
18940 /////////////////////////////////////////////////////////////////////////////
18943 * Render a form with input type=text.
18945 * @method renderForm
18947 renderForm : function() {
18949 // Bug 1802582: SF3/Mac needs a form element wrapping the input
18950 if(ua.webkit>420) {
18951 elTextbox = this.getContainerEl().appendChild(document.createElement("form")).appendChild(document.createElement("input"));
18954 elTextbox = this.getContainerEl().appendChild(document.createElement("input"));
18956 elTextbox.type = "text";
18957 this.textbox = elTextbox;
18959 // Save on enter by default
18960 // Bug: 1802582 Set up a listener on each textbox to track on keypress
18961 // since SF/OP can't preventDefault on keydown
18962 Ev.addListener(elTextbox, "keypress", function(v){
18963 if((v.keyCode === 13)) {
18964 // Prevent form submit
18965 YAHOO.util.Event.preventDefault(v);
18970 if(this.disableBtns) {
18971 // By default this is no-op since enter saves by default
18972 this.handleDisabledBtns();
18977 * Moves TextboxCellEditor UI to a cell.
18981 move : function() {
18982 this.textbox.style.width = this.getTdEl().offsetWidth + "px";
18983 widget.TextboxCellEditor.superclass.move.call(this);
18987 * Resets TextboxCellEditor UI to initial state.
18989 * @method resetForm
18991 resetForm : function() {
18992 this.textbox.value = lang.isValue(this.value) ? this.value.toString() : "";
18996 * Sets focus in TextboxCellEditor.
19000 focus : function() {
19001 // Bug 2303181, Bug 2263600
19002 this.getDataTable()._focusEl(this.textbox);
19003 this.textbox.select();
19007 * Returns new value for TextboxCellEditor.
19009 * @method getInputValue
19011 getInputValue : function() {
19012 return this.textbox.value;
19017 // Copy static members to TextboxCellEditor class
19018 lang.augmentObject(widget.TextboxCellEditor, BCE);
19026 /////////////////////////////////////////////////////////////////////////////
19028 // DataTable extension
19030 /////////////////////////////////////////////////////////////////////////////
19033 * CellEditor subclasses.
19034 * @property DataTable.Editors
19039 checkbox : widget.CheckboxCellEditor,
19040 "date" : widget.DateCellEditor,
19041 dropdown : widget.DropdownCellEditor,
19042 radio : widget.RadioCellEditor,
19043 textarea : widget.TextareaCellEditor,
19044 textbox : widget.TextboxCellEditor
19047 /****************************************************************************/
19048 /****************************************************************************/
19049 /****************************************************************************/
19052 * Factory class for instantiating a BaseCellEditor subclass.
19054 * @namespace YAHOO.widget
19055 * @class CellEditor
19056 * @extends YAHOO.widget.BaseCellEditor
19058 * @param sType {String} Type indicator, to map to YAHOO.widget.DataTable.Editors.
19059 * @param oConfigs {Object} (Optional) Object literal of configs.
19061 widget.CellEditor = function(sType, oConfigs) {
19062 // Point to one of the subclasses
19063 if(sType && DT.Editors[sType]) {
19064 lang.augmentObject(BCE, DT.Editors[sType]);
19065 return new DT.Editors[sType](oConfigs);
19068 return new BCE(null, oConfigs);
19072 var CE = widget.CellEditor;
19074 // Copy static members to CellEditor class
19075 lang.augmentObject(CE, BCE);
19080 YAHOO.register("datatable", YAHOO.widget.DataTable, {version: "2.9.0", build: "2800"});