YUI.add('yui2-datatable', function(Y) {
var YAHOO = Y.YUI2;
/*
Copyright (c) 2011, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.com/yui/license.html
version: 2.9.0
*/
/**
* 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:
*
*
method - {Function} REQUIRED the callback function.
*
scope - {Object} the scope from which to execute the callback. Default is the global window scope.
*
argument - {Array} parameters to be passed to method as individual arguments.
*
timeout - {number} millisecond delay to wait after previous callback completion before executing this callback. Negative values cause immediate blocking execution. Default 0.
*
until - {Function} boolean function executed before each iteration. Return true to indicate completion and proceed to the next callback.
*
iterations - {Number} number of times to execute the callback before proceeding to the next callback in the chain. Incompatible with until.
*
*
* @namespace YAHOO.util
* @class Chain
* @constructor
* @param callback* {Function|Object} Any number of callbacks to initialize the queue
*/
YAHOO.util.Chain = function () {
/**
* The callback queue
* @property q
* @type {Array}
* @private
*/
this.q = [].slice.call(arguments);
/**
* Event fired when the callback queue is emptied via execution (not via
* a call to chain.stop().
* @event end
*/
this.createEvent('end');
};
YAHOO.util.Chain.prototype = {
/**
* 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.
* @property id
* @type {number}
* @private
*/
id : 0,
/**
* Begin executing the chain, or resume execution from the last paused position.
* @method run
* @return {Chain} the Chain instance
*/
run : function () {
// Grab the first callback in the queue
var c = this.q[0],
fn;
// If there is no callback in the queue or the Chain is currently
// in an execution mode, return
if (!c) {
this.fireEvent('end');
return this;
} else if (this.id) {
return this;
}
fn = c.method || c;
if (typeof fn === 'function') {
var o = c.scope || {},
args = c.argument || [],
ms = c.timeout || 0,
me = this;
if (!(args instanceof Array)) {
args = [args];
}
// Execute immediately if the callback timeout is negative.
if (ms < 0) {
this.id = ms;
if (c.until) {
for (;!c.until();) {
// Execute the callback from scope, with argument
fn.apply(o,args);
}
} else if (c.iterations) {
for (;c.iterations-- > 0;) {
fn.apply(o,args);
}
} else {
fn.apply(o,args);
}
this.q.shift();
this.id = 0;
return this.run();
} else {
// If the until condition is set, check if we're done
if (c.until) {
if (c.until()) {
// Shift this callback from the queue and execute the next
// callback
this.q.shift();
return this.run();
}
// Otherwise if either iterations is not set or we're
// executing the last iteration, shift callback from the queue
} else if (!c.iterations || !--c.iterations) {
this.q.shift();
}
// Otherwise set to execute after the configured timeout
this.id = setTimeout(function () {
// Execute the callback from scope, with argument
fn.apply(o,args);
// Check if the Chain was not paused from inside the callback
if (me.id) {
// Indicate ready to run state
me.id = 0;
// Start the fun all over again
me.run();
}
},ms);
}
}
return this;
},
/**
* Add a callback to the end of the queue
* @method add
* @param c {Function|Object} the callback function ref or object literal
* @return {Chain} the Chain instance
*/
add : function (c) {
this.q.push(c);
return this;
},
/**
* Pause the execution of the Chain after the current execution of the
* current callback completes. If called interstitially, clears the
* timeout for the pending callback. Paused Chains can be restarted with
* chain.run()
* @method pause
* @return {Chain} the Chain instance
*/
pause: function () {
// Conditional added for Caja compatibility
if (this.id > 0) {
clearTimeout(this.id);
}
this.id = 0;
return this;
},
/**
* Stop and clear the Chain's queue after the current execution of the
* current callback completes.
* @method stop
* @return {Chain} the Chain instance
*/
stop : function () {
this.pause();
this.q = [];
return this;
}
};
YAHOO.lang.augmentProto(YAHOO.util.Chain,YAHOO.util.EventProvider);
/**
* Augments the Event Utility with a delegate method that
* facilitates easy creation of delegated event listeners. (Note: Using CSS
* selectors as the filtering criteria for delegated event listeners requires
* inclusion of the Selector Utility.)
*
* @module event-delegate
* @title Event Utility Event Delegation Module
* @namespace YAHOO.util
* @requires event
*/
(function () {
var Event = YAHOO.util.Event,
Lang = YAHOO.lang,
delegates = [],
getMatch = function(el, selector, container) {
var returnVal;
if (!el || el === container) {
returnVal = false;
}
else {
returnVal = YAHOO.util.Selector.test(el, selector) ? el: getMatch(el.parentNode, selector, container);
}
return returnVal;
};
Lang.augmentObject(Event, {
/**
* Creates a delegate function used to call event listeners specified
* via the YAHOO.util.Event.delegate method.
*
* @method _createDelegate
*
* @param {Function} fn The method (event listener) to call.
* @param {Function|string} filter Function or CSS selector used to
* determine for what element(s) the event listener should be called.
* @param {Object} obj An arbitrary object that will be
* passed as a parameter to the listener.
* @param {Boolean|object} overrideContext If true, the value of the
* obj parameter becomes the execution context
* of the listener. If an object, this object
* becomes the execution context.
* @return {Function} Function that will call the event listener
* specified by the YAHOO.util.Event.delegate method.
* @private
* @for Event
* @static
*/
_createDelegate: function (fn, filter, obj, overrideContext) {
return function (event) {
var container = this,
target = Event.getTarget(event),
selector = filter,
// The user might have specified the document object
// as the delegation container, in which case it is not
// nessary to scope the provided CSS selector(s) to the
// delegation container
bDocument = (container.nodeType === 9),
matchedEl,
context,
sID,
sIDSelector;
if (Lang.isFunction(filter)) {
matchedEl = filter(target);
}
else if (Lang.isString(filter)) {
if (!bDocument) {
sID = container.id;
if (!sID) {
sID = Event.generateId(container);
}
// Scope all selectors to the container
sIDSelector = ("#" + sID + " ");
selector = (sIDSelector + filter).replace(/,/gi, ("," + sIDSelector));
}
if (YAHOO.util.Selector.test(target, selector)) {
matchedEl = target;
}
else if (YAHOO.util.Selector.test(target, ((selector.replace(/,/gi, " *,")) + " *"))) {
// The target is a descendant of an element matching
// the selector, so crawl up to find the ancestor that
// matches the selector
matchedEl = getMatch(target, selector, container);
}
}
if (matchedEl) {
// The default context for delegated listeners is the
// element that matched the filter.
context = matchedEl;
if (overrideContext) {
if (overrideContext === true) {
context = obj;
} else {
context = overrideContext;
}
}
// Call the listener passing in the container and the
// element that matched the filter in case the user
// needs those.
return fn.call(context, event, matchedEl, container, obj);
}
};
},
/**
* Appends a delegated event listener. Delegated event listeners
* receive three arguments by default: the DOM event, the element
* specified by the filtering function or CSS selector, and the
* container element (the element to which the event listener is
* bound). (Note: Using the delegate method requires the event-delegate
* module. Using CSS selectors as the filtering criteria for delegated
* event listeners requires inclusion of the Selector Utility.)
*
* @method delegate
*
* @param {String|HTMLElement|Array|NodeList} container An id, an element
* reference, or a collection of ids and/or elements to assign the
* listener to.
* @param {String} type The type of event listener to append
* @param {Function} fn The method the event invokes
* @param {Function|string} filter Function or CSS selector used to
* determine for what element(s) the event listener should be called.
* When a function is specified, the function should return an
* HTML element. Using a CSS Selector requires the inclusion of the
* CSS Selector Utility.
* @param {Object} obj An arbitrary object that will be
* passed as a parameter to the listener
* @param {Boolean|object} overrideContext If true, the value of the obj parameter becomes
* the execution context of the listener. If an
* object, this object becomes the execution
* context.
* @return {Boolean} Returns true if the action was successful or defered,
* false if one or more of the elements
* could not have the listener attached,
* or if the operation throws an exception.
* @static
* @for Event
*/
delegate: function (container, type, fn, filter, obj, overrideContext) {
var sType = type,
fnMouseDelegate,
fnDelegate;
if (Lang.isString(filter) && !YAHOO.util.Selector) {
return false;
}
if (type == "mouseenter" || type == "mouseleave") {
if (!Event._createMouseDelegate) {
return false;
}
// Look up the real event--either mouseover or mouseout
sType = Event._getType(type);
fnMouseDelegate = Event._createMouseDelegate(fn, obj, overrideContext);
fnDelegate = Event._createDelegate(function (event, matchedEl, container) {
return fnMouseDelegate.call(matchedEl, event, container);
}, filter, obj, overrideContext);
}
else {
fnDelegate = Event._createDelegate(fn, filter, obj, overrideContext);
}
delegates.push([container, sType, fn, fnDelegate]);
return Event.on(container, sType, fnDelegate);
},
/**
* Removes a delegated event listener.
*
* @method removeDelegate
*
* @param {String|HTMLElement|Array|NodeList} container An id, an element
* reference, or a collection of ids and/or elements to remove
* the listener from.
* @param {String} type The type of event to remove.
* @param {Function} fn The method the event invokes. If fn is
* undefined, then all event listeners for the type of event are
* removed.
* @return {boolean} Returns true if the unbind was successful, false
* otherwise.
* @static
* @for Event
*/
removeDelegate: function (container, type, fn) {
var sType = type,
returnVal = false,
index,
cacheItem;
// Look up the real event--either mouseover or mouseout
if (type == "mouseenter" || type == "mouseleave") {
sType = Event._getType(type);
}
index = Event._getCacheIndex(delegates, container, sType, fn);
if (index >= 0) {
cacheItem = delegates[index];
}
if (container && cacheItem) {
returnVal = Event.removeListener(cacheItem[0], cacheItem[1], cacheItem[3]);
if (returnVal) {
delete delegates[index][2];
delete delegates[index][3];
delegates.splice(index, 1);
}
}
return returnVal;
}
});
}());
/**
* Augments the Event Utility with support for the mouseenter and mouseleave
* events: A mouseenter event fires the first time the mouse enters an
* element; a mouseleave event first the first time the mouse leaves an
* element.
*
* @module event-mouseenter
* @title Event Utility mouseenter and mouseout Module
* @namespace YAHOO.util
* @requires event
*/
(function () {
var Event = YAHOO.util.Event,
Lang = YAHOO.lang,
addListener = Event.addListener,
removeListener = Event.removeListener,
getListeners = Event.getListeners,
delegates = [],
specialTypes = {
mouseenter: "mouseover",
mouseleave: "mouseout"
},
remove = function(el, type, fn) {
var index = Event._getCacheIndex(delegates, el, type, fn),
cacheItem,
returnVal;
if (index >= 0) {
cacheItem = delegates[index];
}
if (el && cacheItem) {
// removeListener will translate the value of type
returnVal = removeListener.call(Event, cacheItem[0], type, cacheItem[3]);
if (returnVal) {
delete delegates[index][2];
delete delegates[index][3];
delegates.splice(index, 1);
}
}
return returnVal;
};
Lang.augmentObject(Event._specialTypes, specialTypes);
Lang.augmentObject(Event, {
/**
* Creates a delegate function used to call mouseover and mouseleave
* event listeners specified via the
* YAHOO.util.Event.addListener
* or YAHOO.util.Event.on method.
*
* @method _createMouseDelegate
*
* @param {Function} fn The method (event listener) to call
* @param {Object} obj An arbitrary object that will be
* passed as a parameter to the listener
* @param {Boolean|object} overrideContext If true, the value of the
* obj parameter becomes the execution context
* of the listener. If an object, this object
* becomes the execution context.
* @return {Function} Function that will call the event listener
* specified by either the YAHOO.util.Event.addListener
* or YAHOO.util.Event.on method.
* @private
* @static
* @for Event
*/
_createMouseDelegate: function (fn, obj, overrideContext) {
return function (event, container) {
var el = this,
relatedTarget = Event.getRelatedTarget(event),
context,
args;
if (el != relatedTarget && !YAHOO.util.Dom.isAncestor(el, relatedTarget)) {
context = el;
if (overrideContext) {
if (overrideContext === true) {
context = obj;
} else {
context = overrideContext;
}
}
// The default args passed back to a mouseenter or
// mouseleave listener are: the event, and any object
// the user passed when subscribing
args = [event, obj];
// Add the element and delegation container as arguments
// when delegating mouseenter and mouseleave
if (container) {
args.splice(1, 0, el, container);
}
return fn.apply(context, args);
}
};
},
addListener: function (el, type, fn, obj, overrideContext) {
var fnDelegate,
returnVal;
if (specialTypes[type]) {
fnDelegate = Event._createMouseDelegate(fn, obj, overrideContext);
fnDelegate.mouseDelegate = true;
delegates.push([el, type, fn, fnDelegate]);
// addListener will translate the value of type
returnVal = addListener.call(Event, el, type, fnDelegate);
}
else {
returnVal = addListener.apply(Event, arguments);
}
return returnVal;
},
removeListener: function (el, type, fn) {
var returnVal;
if (specialTypes[type]) {
returnVal = remove.apply(Event, arguments);
}
else {
returnVal = removeListener.apply(Event, arguments);
}
return returnVal;
},
getListeners: function (el, type) {
// If the user specified the type as mouseover or mouseout,
// need to filter out those used by mouseenter and mouseleave.
// If the user specified the type as mouseenter or mouseleave,
// need to filter out the true mouseover and mouseout listeners.
var listeners = [],
elListeners,
bMouseOverOrOut = (type === "mouseover" || type === "mouseout"),
bMouseDelegate,
i,
l;
if (type && (bMouseOverOrOut || specialTypes[type])) {
elListeners = getListeners.call(Event, el, this._getType(type));
if (elListeners) {
for (i=elListeners.length-1; i>-1; i--) {
l = elListeners[i];
bMouseDelegate = l.fn.mouseDelegate;
if ((specialTypes[type] && bMouseDelegate) || (bMouseOverOrOut && !bMouseDelegate)) {
listeners.push(l);
}
}
}
}
else {
listeners = getListeners.apply(Event, arguments);
}
return (listeners && listeners.length) ? listeners : null;
}
}, true);
Event.on = Event.addListener;
}());
YAHOO.register("event-mouseenter", YAHOO.util.Event, {version: "2.9.0", build: "2800"});
var Y = YAHOO,
Y_DOM = YAHOO.util.Dom,
EMPTY_ARRAY = [],
Y_UA = Y.env.ua,
Y_Lang = Y.lang,
Y_DOC = document,
Y_DOCUMENT_ELEMENT = Y_DOC.documentElement,
Y_DOM_inDoc = Y_DOM.inDocument,
Y_mix = Y_Lang.augmentObject,
Y_guid = Y_DOM.generateId,
Y_getDoc = function(element) {
var doc = Y_DOC;
if (element) {
doc = (element.nodeType === 9) ? element : // element === document
element.ownerDocument || // element === DOM node
element.document || // element === window
Y_DOC; // default
}
return doc;
},
Y_Array = function(o, startIdx) {
var l, a, start = startIdx || 0;
// IE errors when trying to slice HTMLElement collections
try {
return Array.prototype.slice.call(o, start);
} catch (e) {
a = [];
l = o.length;
for (; start < l; start++) {
a.push(o[start]);
}
return a;
}
},
Y_DOM_allById = function(id, root) {
root = root || Y_DOC;
var nodes = [],
ret = [],
i,
node;
if (root.querySelectorAll) {
ret = root.querySelectorAll('[id="' + id + '"]');
} else if (root.all) {
nodes = root.all(id);
if (nodes) {
// root.all may return HTMLElement or HTMLCollection.
// some elements are also HTMLCollection (FORM, SELECT).
if (nodes.nodeName) {
if (nodes.id === id) { // avoid false positive on name
ret.push(nodes);
nodes = EMPTY_ARRAY; // done, no need to filter
} else { // prep for filtering
nodes = [nodes];
}
}
if (nodes.length) {
// filter out matches on node.name
// and element.id as reference to element with id === 'id'
for (i = 0; node = nodes[i++];) {
if (node.id === id ||
(node.attributes && node.attributes.id &&
node.attributes.id.value === id)) {
ret.push(node);
}
}
}
}
} else {
ret = [Y_getDoc(root).getElementById(id)];
}
return ret;
};
/**
* The selector-native module provides support for native querySelector
* @module dom
* @submodule selector-native
* @for Selector
*/
/**
* Provides support for using CSS selectors to query the DOM
* @class Selector
* @static
* @for Selector
*/
var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
OWNER_DOCUMENT = 'ownerDocument',
Selector = {
_foundCache: [],
useNative: true,
_compare: ('sourceIndex' in Y_DOCUMENT_ELEMENT) ?
function(nodeA, nodeB) {
var a = nodeA.sourceIndex,
b = nodeB.sourceIndex;
if (a === b) {
return 0;
} else if (a > b) {
return 1;
}
return -1;
} : (Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION] ?
function(nodeA, nodeB) {
if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
return -1;
} else {
return 1;
}
} :
function(nodeA, nodeB) {
var rangeA, rangeB, compare;
if (nodeA && nodeB) {
rangeA = nodeA[OWNER_DOCUMENT].createRange();
rangeA.setStart(nodeA, 0);
rangeB = nodeB[OWNER_DOCUMENT].createRange();
rangeB.setStart(nodeB, 0);
compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
}
return compare;
}),
_sort: function(nodes) {
if (nodes) {
nodes = Y_Array(nodes, 0, true);
if (nodes.sort) {
nodes.sort(Selector._compare);
}
}
return nodes;
},
_deDupe: function(nodes) {
var ret = [],
i, node;
for (i = 0; (node = nodes[i++]);) {
if (!node._found) {
ret[ret.length] = node;
node._found = true;
}
}
for (i = 0; (node = ret[i++]);) {
node._found = null;
node.removeAttribute('_found');
}
return ret;
},
/**
* Retrieves a set of nodes based on a given CSS selector.
* @method query
*
* @param {string} selector The CSS Selector to test the node against.
* @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
* @param {Boolean} firstOnly optional Whether or not to return only the first match.
* @return {Array} An array of nodes that match the given selector.
* @static
*/
query: function(selector, root, firstOnly, skipNative) {
if (typeof root == 'string') {
root = Y_DOM.get(root);
if (!root) {
return (firstOnly) ? null : [];
}
} else {
root = root || Y_DOC;
}
var ret = [],
useNative = (Selector.useNative && Y_DOC.querySelector && !skipNative),
queries = [[selector, root]],
query,
result,
i,
fn = (useNative) ? Selector._nativeQuery : Selector._bruteQuery;
if (selector && fn) {
// split group into seperate queries
if (!skipNative && // already done if skipping
(!useNative || root.tagName)) { // split native when element scoping is needed
queries = Selector._splitQueries(selector, root);
}
for (i = 0; (query = queries[i++]);) {
result = fn(query[0], query[1], firstOnly);
if (!firstOnly) { // coerce DOM Collection to Array
result = Y_Array(result, 0, true);
}
if (result) {
ret = ret.concat(result);
}
}
if (queries.length > 1) { // remove dupes and sort by doc order
ret = Selector._sort(Selector._deDupe(ret));
}
}
Y.log('query: ' + selector + ' returning: ' + ret.length, 'info', 'Selector');
return (firstOnly) ? (ret[0] || null) : ret;
},
// allows element scoped queries to begin with combinator
// e.g. query('> p', document.body) === query('body > p')
_splitQueries: function(selector, node) {
var groups = selector.split(','),
queries = [],
prefix = '',
i, len;
if (node) {
// enforce for element scoping
if (node.tagName) {
node.id = node.id || Y_guid();
prefix = '[id="' + node.id + '"] ';
}
for (i = 0, len = groups.length; i < len; ++i) {
selector = prefix + groups[i];
queries.push([selector, node]);
}
}
return queries;
},
_nativeQuery: function(selector, root, one) {
if (Y_UA.webkit && selector.indexOf(':checked') > -1 &&
(Selector.pseudos && Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected"
return Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
}
try {
//Y.log('trying native query with: ' + selector, 'info', 'selector-native');
return root['querySelector' + (one ? '' : 'All')](selector);
} catch(e) { // fallback to brute if available
//Y.log('native query error; reverting to brute query with: ' + selector, 'info', 'selector-native');
return Selector.query(selector, root, one, true); // redo with skipNative true
}
},
filter: function(nodes, selector) {
var ret = [],
i, node;
if (nodes && selector) {
for (i = 0; (node = nodes[i++]);) {
if (Selector.test(node, selector)) {
ret[ret.length] = node;
}
}
} else {
Y.log('invalid filter input (nodes: ' + nodes +
', selector: ' + selector + ')', 'warn', 'Selector');
}
return ret;
},
test: function(node, selector, root) {
var ret = false,
groups = selector.split(','),
useFrag = false,
parent,
item,
items,
frag,
i, j, group;
if (node && node.tagName) { // only test HTMLElements
// we need a root if off-doc
if (!root && !Y_DOM_inDoc(node)) {
parent = node.parentNode;
if (parent) {
root = parent;
} else { // only use frag when no parent to query
frag = node[OWNER_DOCUMENT].createDocumentFragment();
frag.appendChild(node);
root = frag;
useFrag = true;
}
}
root = root || node[OWNER_DOCUMENT];
if (!node.id) {
node.id = Y_guid();
}
for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
group += '[id="' + node.id + '"]';
items = Selector.query(group, root);
for (j = 0; item = items[j++];) {
if (item === node) {
ret = true;
break;
}
}
if (ret) {
break;
}
}
if (useFrag) { // cleanup
frag.removeChild(node);
}
}
return ret;
}
};
YAHOO.util.Selector = Selector;
/**
* The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
* @module dom
* @submodule selector-css2
* @for Selector
*/
/**
* Provides helper methods for collecting and filtering DOM elements.
*/
var PARENT_NODE = 'parentNode',
TAG_NAME = 'tagName',
ATTRIBUTES = 'attributes',
COMBINATOR = 'combinator',
PSEUDOS = 'pseudos',
SelectorCSS2 = {
_reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move?
SORT_RESULTS: true,
_children: function(node, tag) {
var ret = node.children,
i,
children = [],
childNodes,
child;
if (node.children && tag && node.children.tags) {
children = node.children.tags(tag);
} else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
childNodes = ret || node.childNodes;
ret = [];
for (i = 0; (child = childNodes[i++]);) {
if (child.tagName) {
if (!tag || tag === child.tagName) {
ret.push(child);
}
}
}
}
return ret || [];
},
_re: {
//attr: /(\[.*\])/g,
attr: /(\[[^\]]*\])/g,
//esc: /\\[:\[][\w\d\]]*/gi,
esc: /\\[:\[\]\(\)#\.\'\>+~"]/gi,
//pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\))*)/i
pseudos: /(\([^\)]*\))/g
},
/**
* Mapping of shorthand tokens to corresponding attribute selector
* @property shorthand
* @type object
*/
shorthand: {
//'\\#([^\\s\\\\(\\[:]*)': '[id=$1]',
'\\#(-?[_a-z]+[-\\w\\uE000]*)': '[id=$1]',
//'\\#([^\\s\\\.:\\[\\]]*)': '[id=$1]',
//'\\.([^\\s\\\\(\\[:]*)': '[className=$1]'
'\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]'
},
/**
* List of operators and corresponding boolean functions.
* These functions are passed the attribute and the current node's value of the attribute.
* @property operators
* @type object
*/
operators: {
'': function(node, attr) { return !!node.getAttribute(attr); }, // Just test for existence of attribute
//'': '.+',
//'=': '^{val}$', // equality
'~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
'|=': '^{val}(?:-|$)' // optional hyphen-delimited
},
pseudos: {
'first-child': function(node) {
return Selector._children(node[PARENT_NODE])[0] === node;
}
},
_bruteQuery: function(selector, root, firstOnly) {
var ret = [],
nodes = [],
tokens = Selector._tokenize(selector),
token = tokens[tokens.length - 1],
rootDoc = Y_getDoc(root),
child,
id,
className,
tagName;
// if we have an initial ID, set to root when in document
/*
if (tokens[0] && rootDoc === root &&
(id = tokens[0].id) &&
rootDoc.getElementById(id)) {
root = rootDoc.getElementById(id);
}
*/
if (token) {
// prefilter nodes
id = token.id;
className = token.className;
tagName = token.tagName || '*';
if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
// try ID first, unless no root.all && root not in document
// (root.all works off document, but not getElementById)
// TODO: move to allById?
if (id && (root.all || (root.nodeType === 9 || Y_DOM_inDoc(root)))) {
nodes = Y_DOM_allById(id, root);
// try className
} else if (className) {
nodes = root.getElementsByClassName(className);
} else { // default to tagName
nodes = root.getElementsByTagName(tagName);
}
} else { // brute getElementsByTagName('*')
child = root.firstChild;
while (child) {
if (child.tagName) { // only collect HTMLElements
nodes.push(child);
}
child = child.nextSilbing || child.firstChild;
}
}
if (nodes.length) {
ret = Selector._filterNodes(nodes, tokens, firstOnly);
}
}
return ret;
},
_filterNodes: function(nodes, tokens, firstOnly) {
var i = 0,
j,
len = tokens.length,
n = len - 1,
result = [],
node = nodes[0],
tmpNode = node,
getters = Selector.getters,
operator,
combinator,
token,
path,
pass,
//FUNCTION = 'function',
value,
tests,
test;
//do {
for (i = 0; (tmpNode = node = nodes[i++]);) {
n = len - 1;
path = null;
testLoop:
while (tmpNode && tmpNode.tagName) {
token = tokens[n];
tests = token.tests;
j = tests.length;
if (j && !pass) {
while ((test = tests[--j])) {
operator = test[1];
if (getters[test[0]]) {
value = getters[test[0]](tmpNode, test[0]);
} else {
value = tmpNode[test[0]];
// use getAttribute for non-standard attributes
if (value === undefined && tmpNode.getAttribute) {
value = tmpNode.getAttribute(test[0]);
}
}
if ((operator === '=' && value !== test[2]) || // fast path for equality
(typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
operator.test && !operator.test(value)) || // regex test
(!operator.test && // protect against RegExp as function (webkit)
typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
// skip non element nodes or non-matching tags
if ((tmpNode = tmpNode[path])) {
while (tmpNode &&
(!tmpNode.tagName ||
(token.tagName && token.tagName !== tmpNode.tagName))
) {
tmpNode = tmpNode[path];
}
}
continue testLoop;
}
}
}
n--; // move to next token
// now that we've passed the test, move up the tree by combinator
if (!pass && (combinator = token.combinator)) {
path = combinator.axis;
tmpNode = tmpNode[path];
// skip non element nodes
while (tmpNode && !tmpNode.tagName) {
tmpNode = tmpNode[path];
}
if (combinator.direct) { // one pass only
path = null;
}
} else { // success if we made it this far
result.push(node);
if (firstOnly) {
return result;
}
break;
}
}
}// while (tmpNode = node = nodes[++i]);
node = tmpNode = null;
return result;
},
combinators: {
' ': {
axis: 'parentNode'
},
'>': {
axis: 'parentNode',
direct: true
},
'+': {
axis: 'previousSibling',
direct: true
}
},
_parsers: [
{
name: ATTRIBUTES,
//re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
fn: function(match, token) {
var operator = match[2] || '',
operators = Selector.operators,
escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
test;
// add prefiltering for ID and CLASS
if ((match[1] === 'id' && operator === '=') ||
(match[1] === 'className' &&
Y_DOCUMENT_ELEMENT.getElementsByClassName &&
(operator === '~=' || operator === '='))) {
token.prefilter = match[1];
match[3] = escVal;
// escape all but ID for prefilter, which may run through QSA (via Dom.allById)
token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
}
// add tests
if (operator in operators) {
test = operators[operator];
if (typeof test === 'string') {
match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
test = new RegExp(test.replace('{val}', match[3]));
}
match[2] = test;
}
if (!token.last || token.prefilter !== match[1]) {
return match.slice(1);
}
}
},
{
name: TAG_NAME,
re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
fn: function(match, token) {
var tag = match[1].toUpperCase();
token.tagName = tag;
if (tag !== '*' && (!token.last || token.prefilter)) {
return [TAG_NAME, '=', tag];
}
if (!token.prefilter) {
token.prefilter = 'tagName';
}
}
},
{
name: COMBINATOR,
re: /^\s*([>+~]|\s)\s*/,
fn: function(match, token) {
}
},
{
name: PSEUDOS,
re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
fn: function(match, token) {
var test = Selector[PSEUDOS][match[1]];
if (test) { // reorder match array and unescape special chars for tests
if (match[2]) {
match[2] = match[2].replace(/\\/g, '');
}
return [match[2], test];
} else { // selector token not supported (possibly missing CSS3 module)
return false;
}
}
}
],
_getToken: function(token) {
return {
tagName: null,
id: null,
className: null,
attributes: {},
combinator: null,
tests: []
};
},
/**
Break selector into token units per simple selector.
Combinator is attached to the previous token.
*/
_tokenize: function(selector) {
selector = selector || '';
selector = Selector._replaceShorthand(Y_Lang.trim(selector));
var token = Selector._getToken(), // one token per simple selector (left selector holds combinator)
query = selector, // original query for debug report
tokens = [], // array of tokens
found = false, // whether or not any matches were found this pass
match, // the regex match
test,
i, parser;
/*
Search for selector patterns, store, and strip them from the selector string
until no patterns match (invalid selector) or we run out of chars.
Multiple attributes and pseudos are allowed, in any order.
for example:
'form:first-child[type=button]:not(button)[lang|=en]'
*/
outer:
do {
found = false; // reset after full pass
for (i = 0; (parser = Selector._parsers[i++]);) {
if ( (match = parser.re.exec(selector)) ) { // note assignment
if (parser.name !== COMBINATOR ) {
token.selector = selector;
}
selector = selector.replace(match[0], ''); // strip current match from selector
if (!selector.length) {
token.last = true;
}
if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
match[1] = Selector._attrFilters[match[1]];
}
test = parser.fn(match, token);
if (test === false) { // selector not supported
found = false;
break outer;
} else if (test) {
token.tests.push(test);
}
if (!selector.length || parser.name === COMBINATOR) {
tokens.push(token);
token = Selector._getToken(token);
if (parser.name === COMBINATOR) {
token.combinator = Selector.combinators[match[1]];
}
}
found = true;
}
}
} while (found && selector.length);
if (!found || selector.length) { // not fully parsed
Y.log('query: ' + query + ' contains unsupported token in: ' + selector, 'warn', 'Selector');
tokens = [];
}
return tokens;
},
_replaceShorthand: function(selector) {
var shorthand = Selector.shorthand,
esc = selector.match(Selector._re.esc), // pull escaped colon, brackets, etc.
attrs,
pseudos,
re, i, len;
if (esc) {
selector = selector.replace(Selector._re.esc, '\uE000');
}
attrs = selector.match(Selector._re.attr);
pseudos = selector.match(Selector._re.pseudos);
if (attrs) {
selector = selector.replace(Selector._re.attr, '\uE001');
}
if (pseudos) {
selector = selector.replace(Selector._re.pseudos, '\uE002');
}
for (re in shorthand) {
if (shorthand.hasOwnProperty(re)) {
selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
}
}
if (attrs) {
for (i = 0, len = attrs.length; i < len; ++i) {
selector = selector.replace(/\uE001/, attrs[i]);
}
}
if (pseudos) {
for (i = 0, len = pseudos.length; i < len; ++i) {
selector = selector.replace(/\uE002/, pseudos[i]);
}
}
selector = selector.replace(/\[/g, '\uE003');
selector = selector.replace(/\]/g, '\uE004');
selector = selector.replace(/\(/g, '\uE005');
selector = selector.replace(/\)/g, '\uE006');
if (esc) {
for (i = 0, len = esc.length; i < len; ++i) {
selector = selector.replace('\uE000', esc[i]);
}
}
return selector;
},
_attrFilters: {
'class': 'className',
'for': 'htmlFor'
},
getters: {
href: function(node, attr) {
return Y_DOM.getAttribute(node, attr);
}
}
};
Y_mix(Selector, SelectorCSS2, true);
Selector.getters.src = Selector.getters.rel = Selector.getters.href;
// IE wants class with native queries
if (Selector.useNative && Y_DOC.querySelector) {
Selector.shorthand['\\.([^\\s\\\\(\\[:]*)'] = '[class~=$1]';
}
/**
* The selector css3 module provides support for css3 selectors.
* @module dom
* @submodule selector-css3
* @for Selector
*/
/*
an+b = get every _a_th node starting at the _b_th
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
1n+b = get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n")
an+0 = get every _a_th element, "0" may be omitted
*/
Selector._reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;
Selector._getNth = function(node, expr, tag, reverse) {
Selector._reNth.test(expr);
var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_)
n = RegExp.$2, // "n"
oddeven = RegExp.$3, // "odd" or "even"
b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_
result = [],
siblings = Selector._children(node.parentNode, tag),
op;
if (oddeven) {
a = 2; // always every other
op = '+';
n = 'n';
b = (oddeven === 'odd') ? 1 : 0;
} else if ( isNaN(a) ) {
a = (n) ? 1 : 0; // start from the first or no repeat
}
if (a === 0) { // just the first
if (reverse) {
b = siblings.length - b + 1;
}
if (siblings[b - 1] === node) {
return true;
} else {
return false;
}
} else if (a < 0) {
reverse = !!reverse;
a = Math.abs(a);
}
if (!reverse) {
for (var i = b - 1, len = siblings.length; i < len; i += a) {
if ( i >= 0 && siblings[i] === node ) {
return true;
}
}
} else {
for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) {
if ( i < len && siblings[i] === node ) {
return true;
}
}
}
return false;
};
Y_mix(Selector.pseudos, {
'root': function(node) {
return node === node.ownerDocument.documentElement;
},
'nth-child': function(node, expr) {
return Selector._getNth(node, expr);
},
'nth-last-child': function(node, expr) {
return Selector._getNth(node, expr, null, true);
},
'nth-of-type': function(node, expr) {
return Selector._getNth(node, expr, node.tagName);
},
'nth-last-of-type': function(node, expr) {
return Selector._getNth(node, expr, node.tagName, true);
},
'last-child': function(node) {
var children = Selector._children(node.parentNode);
return children[children.length - 1] === node;
},
'first-of-type': function(node) {
return Selector._children(node.parentNode, node.tagName)[0] === node;
},
'last-of-type': function(node) {
var children = Selector._children(node.parentNode, node.tagName);
return children[children.length - 1] === node;
},
'only-child': function(node) {
var children = Selector._children(node.parentNode);
return children.length === 1 && children[0] === node;
},
'only-of-type': function(node) {
var children = Selector._children(node.parentNode, node.tagName);
return children.length === 1 && children[0] === node;
},
'empty': function(node) {
return node.childNodes.length === 0;
},
'not': function(node, expr) {
return !Selector.test(node, expr);
},
'contains': function(node, expr) {
var text = node.innerText || node.textContent || '';
return text.indexOf(expr) > -1;
},
'checked': function(node) {
return (node.checked === true || node.selected === true);
},
enabled: function(node) {
return (node.disabled !== undefined && !node.disabled);
},
disabled: function(node) {
return (node.disabled);
}
});
Y_mix(Selector.operators, {
'^=': '^{val}', // Match starts with value
'!=': function(node, attr, val) { return node[attr] !== val; }, // Match starts with value
'$=': '{val}$', // Match ends with value
'*=': '{val}' // Match contains value as substring
});
Selector.combinators['~'] = {
axis: 'previousSibling'
};
YAHOO.register("selector", YAHOO.util.Selector, {version: "2.9.0", build: "2800"});
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
var Dom = YAHOO.util.Dom;
/**
* The ColumnSet class defines and manages a DataTable's Columns,
* including nested hierarchies and access to individual Column instances.
*
* @namespace YAHOO.widget
* @class ColumnSet
* @uses YAHOO.util.EventProvider
* @constructor
* @param aDefinitions {Object[]} Array of object literals that define cells in
* the THEAD.
*/
YAHOO.widget.ColumnSet = function(aDefinitions) {
this._sId = Dom.generateId(null, "yui-cs"); // "yui-cs" + YAHOO.widget.ColumnSet._nCount;
// First clone the defs
aDefinitions = YAHOO.widget.DataTable._cloneObject(aDefinitions);
this._init(aDefinitions);
YAHOO.widget.ColumnSet._nCount++;
};
/////////////////////////////////////////////////////////////////////////////
//
// Private member variables
//
/////////////////////////////////////////////////////////////////////////////
/**
* Internal class variable to index multiple ColumnSet instances.
*
* @property ColumnSet._nCount
* @type Number
* @private
* @static
*/
YAHOO.widget.ColumnSet._nCount = 0;
YAHOO.widget.ColumnSet.prototype = {
/**
* Unique instance name.
*
* @property _sId
* @type String
* @private
*/
_sId : null,
/**
* Array of object literal Column definitions passed to the constructor.
*
* @property _aDefinitions
* @type Object[]
* @private
*/
_aDefinitions : null,
/////////////////////////////////////////////////////////////////////////////
//
// Public member variables
//
/////////////////////////////////////////////////////////////////////////////
/**
* Top-down tree representation of Column hierarchy.
*
* @property tree
* @type YAHOO.widget.Column[]
*/
tree : null,
/**
* Flattened representation of all Columns.
*
* @property flat
* @type YAHOO.widget.Column[]
* @default []
*/
flat : null,
/**
* Array of Columns that map one-to-one to a table column.
*
* @property keys
* @type YAHOO.widget.Column[]
* @default []
*/
keys : null,
/**
* ID index of nested parent hierarchies for HEADERS accessibility attribute.
*
* @property headers
* @type String[]
* @default []
*/
headers : null,
/////////////////////////////////////////////////////////////////////////////
//
// Private methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Initializes ColumnSet instance with data from Column definitions.
*
* @method _init
* @param aDefinitions {Object[]} Array of object literals that define cells in
* the THEAD .
* @private
*/
_init : function(aDefinitions) {
// DOM tree representation of all Columns
var tree = [];
// Flat representation of all Columns
var flat = [];
// Flat representation of only Columns that are meant to display data
var keys = [];
// Array of HEADERS attribute values for all keys in the "keys" array
var headers = [];
// Tracks current node list depth being tracked
var nodeDepth = -1;
// Internal recursive function to define Column instances
var parseColumns = function(nodeList, parent) {
// One level down
nodeDepth++;
// Create corresponding tree node if not already there for this depth
if(!tree[nodeDepth]) {
tree[nodeDepth] = [];
}
// Parse each node at this depth for attributes and any children
for(var j=0; j maxRowDepth) {
maxRowDepth = tmpRowDepth;
}
}
}
};
// Count max row depth for each row
for(var m=0; m-1; i--) {
if(allColumns[i]._sId === column) {
return allColumns[i];
}
}
}
return null;
},
/**
* Returns Column instance with given key or ColumnSet key index.
*
* @method getColumn
* @param column {String | Number} Column key or ColumnSet key index.
* @return {YAHOO.widget.Column} Column instance.
*/
getColumn : function(column) {
if(YAHOO.lang.isNumber(column) && this.keys[column]) {
return this.keys[column];
}
else if(YAHOO.lang.isString(column)) {
var allColumns = this.flat;
var aColumns = [];
for(var i=0; i 1) {
return aColumns;
}
}
return null;
},
/**
* Public accessor returns array of given Column's desendants (if any), including itself.
*
* @method getDescendants
* @parem {YAHOO.widget.Column} Column instance.
* @return {Array} Array including the Column itself and all descendants (if any).
*/
getDescendants : function(oColumn) {
var oSelf = this;
var allDescendants = [];
var i;
// Recursive function to loop thru all children
var parse = function(oParent) {
allDescendants.push(oParent);
// This Column has children
if(oParent.children) {
for(i=0; i
*
elLiner
*
The element to write innerHTML to.
*
oRecord
*
The associated Record for the row.
*
oColumn
*
The Column instance for the cell.
*
oData
*
The data value for the cell.
*
*
* @property formatter
* @type String || HTMLFunction
*/
formatter : null,
/**
* Config passed to YAHOO.util.Number.format() by the 'currency' Column formatter.
*
* @property currencyOptions
* @type Object
* @default null
*/
currencyOptions : null,
/**
* Config passed to YAHOO.util.Date.format() by the 'date' Column formatter.
*
* @property dateOptions
* @type Object
* @default null
*/
dateOptions : null,
/**
* Array of dropdown values for formatter:"dropdown" cases. Can either be a
* simple array (e.g., ["Alabama","Alaska","Arizona","Arkansas"]) or a an
* array of objects (e.g., [{label:"Alabama", value:"AL"},
* {label:"Alaska", value:"AK"}, {label:"Arizona", value:"AZ"},
* {label:"Arkansas", value:"AR"}]). String values are treated as markup and
* inserted into the DOM as innerHTML.
*
* @property dropdownOptions
* @type HTML[] | Object[]
*/
dropdownOptions : null,
/**
* A CellEditor instance, otherwise Column is not editable.
*
* @property editor
* @type YAHOO.widget.CellEditor
*/
editor : null,
/**
* True if Column is resizeable, false otherwise. The Drag & Drop Utility is
* required to enable this feature. Only bottom-level and non-nested Columns are
* resizeble.
*
* @property resizeable
* @type Boolean
* @default false
*/
resizeable : false,
/**
* True if Column is sortable, false otherwise.
*
* @property sortable
* @type Boolean
* @default false
*/
sortable : false,
/**
* @property sortOptions.defaultOrder
* @deprecated Use sortOptions.defaultDir.
*/
/**
* Default sort direction for Column: YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC.
*
* @property sortOptions.defaultDir
* @type String
* @default null
*/
/**
* Custom field to sort on.
*
* @property sortOptions.field
* @type String
* @default null
*/
/**
* Custom sort handler. Signature: sortFunction(a, b, desc, field) where field is the sortOptions.field value
*
* @property sortOptions.sortFunction
* @type Function
* @default null
*/
sortOptions : null,
/////////////////////////////////////////////////////////////////////////////
//
// Public methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Returns unique ID string.
*
* @method getId
* @return {String} Unique ID string.
*/
getId : function() {
return this._sId;
},
/**
* Column instance name, for logging.
*
* @method toString
* @return {String} Column's unique name.
*/
toString : function() {
return "Column instance " + this._sId;
},
/**
* Returns object literal definition.
*
* @method getDefinition
* @return {Object} Object literal definition.
*/
getDefinition : function() {
var oDefinition = {};
// Update the definition
oDefinition.abbr = this.abbr;
oDefinition.className = this.className;
oDefinition.editor = this.editor;
oDefinition.editorOptions = this.editorOptions; //TODO: deprecated
oDefinition.field = this.field;
oDefinition.formatter = this.formatter;
oDefinition.hidden = this.hidden;
oDefinition.key = this.key;
oDefinition.label = this.label;
oDefinition.minWidth = this.minWidth;
oDefinition.maxAutoWidth = this.maxAutoWidth;
oDefinition.resizeable = this.resizeable;
oDefinition.selected = this.selected;
oDefinition.sortable = this.sortable;
oDefinition.sortOptions = this.sortOptions;
oDefinition.width = this.width;
// Bug 2529147
oDefinition._calculatedWidth = this._calculatedWidth;
return oDefinition;
},
/**
* Returns unique Column key.
*
* @method getKey
* @return {String} Column key.
*/
getKey : function() {
return this.key;
},
/**
* Returns field.
*
* @method getField
* @return {String} Column field.
*/
getField : function() {
return this.field;
},
/**
* Returns Column key which has been sanitized for DOM (class and ID) usage
* starts with letter, contains only letters, numbers, hyphen, or period.
*
* @method getSanitizedKey
* @return {String} Sanitized Column key.
*/
getSanitizedKey : function() {
return this.getKey().replace(/[^\w\-]/g,"");
},
/**
* Public accessor returns Column's current position index within its
* ColumnSet's keys array, if applicable. Only non-nested and bottom-level
* child Columns will return a value.
*
* @method getKeyIndex
* @return {Number} Position index, or null.
*/
getKeyIndex : function() {
return this._nKeyIndex;
},
/**
* Public accessor returns Column's current position index within its
* ColumnSet's tree array, if applicable. Only non-nested and top-level parent
* Columns will return a value;
*
* @method getTreeIndex
* @return {Number} Position index, or null.
*/
getTreeIndex : function() {
return this._nTreeIndex;
},
/**
* Public accessor returns Column's parent instance if any, or null otherwise.
*
* @method getParent
* @return {YAHOO.widget.Column} Column's parent instance.
*/
getParent : function() {
return this._oParent;
},
/**
* Public accessor returns Column's calculated COLSPAN value.
*
* @method getColspan
* @return {Number} Column's COLSPAN value.
*/
getColspan : function() {
return this._nColspan;
},
// Backward compatibility
getColSpan : function() {
return this.getColspan();
},
/**
* Public accessor returns Column's calculated ROWSPAN value.
*
* @method getRowspan
* @return {Number} Column's ROWSPAN value.
*/
getRowspan : function() {
return this._nRowspan;
},
/**
* Returns DOM reference to the key TH element.
*
* @method getThEl
* @return {HTMLElement} TH element.
*/
getThEl : function() {
return this._elTh;
},
/**
* Returns DOM reference to the TH's liner DIV element. Introduced since
* resizeable Columns may have an extra resizer liner, making the DIV liner
* not reliably the TH element's first child.
*
* @method getThLInerEl
* @return {HTMLElement} TH element.
*/
getThLinerEl : function() {
return this._elThLiner;
},
/**
* Returns DOM reference to the resizer element, or null.
*
* @method getResizerEl
* @return {HTMLElement} DIV element.
*/
getResizerEl : function() {
return this._elResizer;
},
// Backward compatibility
/**
* @method getColEl
* @deprecated Use getThEl
*/
getColEl : function() {
return this.getThEl();
},
getIndex : function() {
return this.getKeyIndex();
},
format : function() {
}
};
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
/**
* Sort static utility to support Column sorting.
*
* @namespace YAHOO.util
* @class Sort
* @static
*/
YAHOO.util.Sort = {
/////////////////////////////////////////////////////////////////////////////
//
// Public methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Comparator function for simple case-insensitive string sorting.
*
* @method compare
* @param a {Object} First sort argument.
* @param b {Object} Second sort argument.
* @param desc {Boolean} True if sort direction is descending, false if
* sort direction is ascending.
* @return {Boolean} Return -1 when a < b. Return 0 when a = b.
* Return 1 when a > b.
*/
compare: function(a, b, desc) {
if((a === null) || (typeof a == "undefined")) {
if((b === null) || (typeof b == "undefined")) {
return 0;
}
else {
return 1;
}
}
else if((b === null) || (typeof b == "undefined")) {
return -1;
}
if(a.constructor == String) {
a = a.toLowerCase();
}
if(b.constructor == String) {
b = b.toLowerCase();
}
if(a < b) {
return (desc) ? 1 : -1;
}
else if (a > b) {
return (desc) ? -1 : 1;
}
else {
return 0;
}
}
};
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
/**
* ColumnDD subclasses DragDrop to support rearrangeable Columns.
*
* @namespace YAHOO.util
* @class ColumnDD
* @extends YAHOO.util.DDProxy
* @constructor
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param elTh {HTMLElement} TH element reference.
* @param elTarget {HTMLElement} Drag target element.
*/
YAHOO.widget.ColumnDD = function(oDataTable, oColumn, elTh, elTarget) {
if(oDataTable && oColumn && elTh && elTarget) {
this.datatable = oDataTable;
this.table = oDataTable.getTableEl();
this.column = oColumn;
this.headCell = elTh;
this.pointer = elTarget;
this.newIndex = null;
this.init(elTh);
this.initFrame(); // Needed for DDProxy
this.invalidHandleTypes = {};
// Set top/bottom padding to account for children of nested columns
this.setPadding(10, 0, (this.datatable.getTheadEl().offsetHeight + 10) , 0);
YAHOO.util.Event.on(window, 'resize', function() {
this.initConstraints();
}, this, true);
}
else {
}
};
if(YAHOO.util.DDProxy) {
YAHOO.extend(YAHOO.widget.ColumnDD, YAHOO.util.DDProxy, {
initConstraints: function() {
//Get the top, right, bottom and left positions
var region = YAHOO.util.Dom.getRegion(this.table),
//Get the element we are working on
el = this.getEl(),
//Get the xy position of it
xy = YAHOO.util.Dom.getXY(el),
//Get the width and height
width = parseInt(YAHOO.util.Dom.getStyle(el, 'width'), 10),
height = parseInt(YAHOO.util.Dom.getStyle(el, 'height'), 10),
//Set left to x minus left
left = ((xy[0] - region.left) + 15), //Buffer of 15px
//Set right to right minus x minus width
right = ((region.right - xy[0] - width) + 15);
//Set the constraints based on the above calculations
this.setXConstraint(left, right);
this.setYConstraint(10, 10);
},
_resizeProxy: function() {
YAHOO.widget.ColumnDD.superclass._resizeProxy.apply(this, arguments);
var dragEl = this.getDragEl(),
el = this.getEl();
YAHOO.util.Dom.setStyle(this.pointer, 'height', (this.table.parentNode.offsetHeight + 10) + 'px');
YAHOO.util.Dom.setStyle(this.pointer, 'display', 'block');
var xy = YAHOO.util.Dom.getXY(el);
YAHOO.util.Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
YAHOO.util.Dom.setStyle(dragEl, 'height', this.datatable.getContainerEl().offsetHeight + "px");
YAHOO.util.Dom.setStyle(dragEl, 'width', (parseInt(YAHOO.util.Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
YAHOO.util.Dom.setXY(this.dragEl, xy);
},
onMouseDown: function() {
this.initConstraints();
this.resetConstraints();
},
clickValidator: function(e) {
if(!this.column.hidden) {
var target = YAHOO.util.Event.getTarget(e);
return ( this.isValidHandleChild(target) &&
(this.id == this.handleElId ||
this.DDM.handleWasClicked(target, this.id)) );
}
},
onDragOver: function(ev, id) {
// Validate target as a Column
var target = this.datatable.getColumn(id);
if(target) {
// Validate target as a top-level parent
var targetIndex = target.getTreeIndex();
while((targetIndex === null) && target.getParent()) {
target = target.getParent();
targetIndex = target.getTreeIndex();
}
if(targetIndex !== null) {
// Are we placing to left or right of target?
var elTarget = target.getThEl();
var newIndex = targetIndex;
var mouseX = YAHOO.util.Event.getPageX(ev),
targetX = YAHOO.util.Dom.getX(elTarget),
midX = targetX + ((YAHOO.util.Dom.get(elTarget).offsetWidth)/2),
currentIndex = this.column.getTreeIndex();
if (mouseX < midX) {
YAHOO.util.Dom.setX(this.pointer, targetX);
} else {
var targetWidth = parseInt(elTarget.offsetWidth, 10);
YAHOO.util.Dom.setX(this.pointer, (targetX + targetWidth));
newIndex++;
}
if (targetIndex > currentIndex) {
newIndex--;
}
if(newIndex < 0) {
newIndex = 0;
}
else if(newIndex > this.datatable.getColumnSet().tree[0].length) {
newIndex = this.datatable.getColumnSet().tree[0].length;
}
this.newIndex = newIndex;
}
}
},
onDragDrop: function() {
this.datatable.reorderColumn(this.column, this.newIndex);
},
endDrag: function() {
this.newIndex = null;
YAHOO.util.Dom.setStyle(this.pointer, 'display', 'none');
}
});
}
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
/**
* ColumnResizer subclasses DragDrop to support resizeable Columns.
*
* @namespace YAHOO.util
* @class ColumnResizer
* @extends YAHOO.util.DDProxy
* @constructor
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param elTh {HTMLElement} TH element reference.
* @param sHandleElId {String} DOM ID of the handle element that causes the resize.
* @param elProxy {HTMLElement} Resizer proxy element.
*/
YAHOO.util.ColumnResizer = function(oDataTable, oColumn, elTh, sHandleId, elProxy) {
if(oDataTable && oColumn && elTh && sHandleId) {
this.datatable = oDataTable;
this.column = oColumn;
this.headCell = elTh;
this.headCellLiner = oColumn.getThLinerEl();
this.resizerLiner = elTh.firstChild;
this.init(sHandleId, sHandleId, {dragOnly:true, dragElId: elProxy.id});
this.initFrame(); // Needed for proxy
this.resetResizerEl(); // Needed when rowspan > 0
// Set right padding for bug 1858462
this.setPadding(0, 1, 0, 0);
}
else {
}
};
if(YAHOO.util.DD) {
YAHOO.extend(YAHOO.util.ColumnResizer, YAHOO.util.DDProxy, {
/////////////////////////////////////////////////////////////////////////////
//
// Public methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Resets resizer element.
*
* @method resetResizerEl
*/
resetResizerEl : function() {
var resizerStyle = YAHOO.util.Dom.get(this.handleElId).style;
resizerStyle.left = "auto";
resizerStyle.right = 0;
resizerStyle.top = "auto";
resizerStyle.bottom = 0;
resizerStyle.height = this.headCell.offsetHeight+"px";
},
/////////////////////////////////////////////////////////////////////////////
//
// Public DOM event handlers
//
/////////////////////////////////////////////////////////////////////////////
/**
* Handles mouseup events on the Column resizer.
*
* @method onMouseUp
* @param e {string} The mouseup event
*/
onMouseUp : function(e) {
// Reset height of all resizer els in case TH's have changed height
var allKeys = this.datatable.getColumnSet().keys,
col;
for(var i=0, len=allKeys.length; i YAHOO.util.Dom.getX(this.headCellLiner)) {
var offsetX = newX - this.startX;
var newWidth = this.startWidth + offsetX - this.nLinerPadding;
if(newWidth > 0) {
this.datatable.setColumnWidth(this.column, newWidth);
}
}
}
});
}
/////////////////////////////////////////////////////////////////////////////
//
// Deprecated
//
/////////////////////////////////////////////////////////////////////////////
/**
* @property editorOptions
* @deprecated Pass configs directly to CellEditor constructor.
*/
(function () {
var lang = YAHOO.lang,
util = YAHOO.util,
widget = YAHOO.widget,
Dom = util.Dom,
Ev = util.Event,
DT = widget.DataTable;
/****************************************************************************/
/****************************************************************************/
/****************************************************************************/
/**
* A RecordSet defines and manages a set of Records.
*
* @namespace YAHOO.widget
* @class RecordSet
* @param data {Object || Object[]} An object literal or an array of data.
* @constructor
*/
YAHOO.widget.RecordSet = function(data) {
this._init(data);
};
var RS = widget.RecordSet;
/**
* Internal class variable to name multiple Recordset instances.
*
* @property RecordSet._nCount
* @type Number
* @private
* @static
*/
RS._nCount = 0;
RS.prototype = {
/////////////////////////////////////////////////////////////////////////////
//
// Private member variables
//
/////////////////////////////////////////////////////////////////////////////
/**
* Unique String identifier assigned at instantiation.
*
* @property _sId
* @type String
* @private
*/
_sId : null,
/**
* Internal counter of how many Records are in the RecordSet.
*
* @property _length
* @type Number
* @private
* @deprecated No longer used
*/
//_length : null,
/////////////////////////////////////////////////////////////////////////////
//
// Private methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Initializer.
*
* @method _init
* @param data {Object || Object[]} An object literal or an array of data.
* @private
*/
_init : function(data) {
// Internal variables
this._sId = Dom.generateId(null, "yui-rs");// "yui-rs" + widget.RecordSet._nCount;
widget.RecordSet._nCount++;
this._records = [];
//this._length = 0;
this._initEvents();
if(data) {
if(lang.isArray(data)) {
this.addRecords(data);
}
else if(lang.isObject(data)) {
this.addRecord(data);
}
}
},
/**
* Initializes custom events.
*
* @method _initEvents
* @private
*/
_initEvents : function() {
this.createEvent("recordAddEvent");
this.createEvent("recordsAddEvent");
this.createEvent("recordSetEvent");
this.createEvent("recordsSetEvent");
this.createEvent("recordUpdateEvent");
this.createEvent("recordDeleteEvent");
this.createEvent("recordsDeleteEvent");
this.createEvent("resetEvent");
this.createEvent("recordValueUpdateEvent");
},
/**
* Adds one Record to the RecordSet at the given index. If index is null,
* then adds the Record to the end of the RecordSet.
*
* @method _addRecord
* @param oData {Object} An object literal of data.
* @param index {Number} (optional) Position index.
* @return {YAHOO.widget.Record} A Record instance.
* @private
*/
_addRecord : function(oData, index) {
var oRecord = new YAHOO.widget.Record(oData);
if(YAHOO.lang.isNumber(index) && (index > -1)) {
this._records.splice(index,0,oRecord);
}
else {
//index = this.getLength();
//this._records[index] = oRecord;
this._records[this._records.length] = oRecord;
}
//this._length++;
return oRecord;
},
/**
* Sets/replaces one Record to the RecordSet at the given index. Existing
* Records with higher indexes are not shifted. If no index specified, the
* Record is added to the end of the RecordSet.
*
* @method _setRecord
* @param oData {Object} An object literal of data.
* @param index {Number} (optional) Position index.
* @return {YAHOO.widget.Record} A Record instance.
* @private
*/
_setRecord : function(oData, index) {
if (!lang.isNumber(index) || index < 0) {
index = this._records.length;
}
return (this._records[index] = new widget.Record(oData));
/*
if(lang.isNumber(index) && (index > -1)) {
this._records[index] = oRecord;
if((index+1) > this.getLength()) {
this._length = index+1;
}
}
else {
this._records[this.getLength()] = oRecord;
this._length++;
}
return oRecord;
*/
},
/**
* Deletes Records from the RecordSet at the given index. If range is null,
* then only one Record is deleted.
*
* @method _deleteRecord
* @param index {Number} Position index.
* @param range {Number} (optional) How many Records to delete
* @private
*/
_deleteRecord : function(index, range) {
if(!lang.isNumber(range) || (range < 0)) {
range = 1;
}
this._records.splice(index, range);
//this._length = this._length - range;
},
/////////////////////////////////////////////////////////////////////////////
//
// Public methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Returns unique name of the RecordSet instance.
*
* @method getId
* @return {String} Unique name of the RecordSet instance.
*/
getId : function() {
return this._sId;
},
/**
* Public accessor to the unique name of the RecordSet instance.
*
* @method toString
* @return {String} Unique name of the RecordSet instance.
*/
toString : function() {
return "RecordSet instance " + this._sId;
},
/**
* Returns the number of Records held in the RecordSet.
*
* @method getLength
* @return {Number} Number of records in the RecordSet.
*/
getLength : function() {
//return this._length;
return this._records.length;
},
/**
* Returns Record by ID or RecordSet position index.
*
* @method getRecord
* @param record {YAHOO.widget.Record | Number | String} Record instance,
* RecordSet position index, or Record ID.
* @return {YAHOO.widget.Record} Record object.
*/
getRecord : function(record) {
var i;
if(record instanceof widget.Record) {
for(i=0; i -1) && (record < this.getLength())) {
return this._records[record];
}
}
else if(lang.isString(record)) {
for(i=0; i-1; i--) {
if(this._records[i] && oRecord.getId() === this._records[i].getId()) {
return i;
}
}
}
return null;
},
/**
* Adds one Record to the RecordSet at the given index. If index is null,
* then adds the Record to the end of the RecordSet.
*
* @method addRecord
* @param oData {Object} An object literal of data.
* @param index {Number} (optional) Position index.
* @return {YAHOO.widget.Record} A Record instance.
*/
addRecord : function(oData, index) {
if(lang.isObject(oData)) {
var oRecord = this._addRecord(oData, index);
this.fireEvent("recordAddEvent",{record:oRecord,data:oData});
return oRecord;
}
else {
return null;
}
},
/**
* Adds multiple Records at once to the RecordSet at the given index with the
* given object literal data. If index is null, then the new Records are
* added to the end of the RecordSet.
*
* @method addRecords
* @param aData {Object[]} An object literal data or an array of data object literals.
* @param index {Number} (optional) Position index.
* @return {YAHOO.widget.Record[]} An array of Record instances.
*/
addRecords : function(aData, index) {
if(lang.isArray(aData)) {
var newRecords = [],
idx,i,len;
index = lang.isNumber(index) ? index : this._records.length;
idx = index;
// Can't go backwards bc we need to preserve order
for(i=0,len=aData.length; i -1) && (index < this.getLength())) {
var oData = this.getRecord(index).getData();
this._deleteRecord(index);
this.fireEvent("recordDeleteEvent",{data:oData,index:index});
return oData;
}
else {
return null;
}
},
/**
* Removes the Record at the given position index from the RecordSet. If a range
* is also provided, removes that many Records, starting from the index. Length
* of RecordSet is correspondingly shortened.
*
* @method deleteRecords
* @param index {Number} Record's RecordSet position index.
* @param range {Number} (optional) How many Records to delete.
* @return {Object[]} An array of copies of the data held by the deleted Records.
*/
deleteRecords : function(index, range) {
if(!lang.isNumber(range)) {
range = 1;
}
if(lang.isNumber(index) && (index > -1) && (index < this.getLength())) {
var recordsToDelete = this.getRecords(index, range);
var deletedData = [], // this mistakenly held Records, not data
deletedObjects = []; // this passes data only
for(var i=0; i" + sValue + "";
//}
},
/**
* Formats a CHECKBOX element.
*
* @method DataTable.formatCheckbox
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object | Boolean | HTML} Data value for the cell. Can be a simple
* Boolean to indicate whether checkbox is checked or not. Can be object literal
* {checked:bBoolean, label:sLabel}. String values are treated as markup
* and inserted into the DOM with innerHTML.
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @static
*/
formatCheckbox : function(el, oRecord, oColumn, oData, oDataTable) {
var bChecked = oData;
bChecked = (bChecked) ? " checked=\"checked\"" : "";
el.innerHTML = "";
},
/**
* Formats currency. Default unit is USD.
*
* @method DataTable.formatCurrency
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Number} Data value for the cell.
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @static
*/
formatCurrency : function(el, oRecord, oColumn, oData, oDataTable) {
var oDT = oDataTable || this;
el.innerHTML = util.Number.format(oData, oColumn.currencyOptions || oDT.get("currencyOptions"));
},
/**
* Formats JavaScript Dates.
*
* @method DataTable.formatDate
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} Data value for the cell, or null. String values are
* treated as markup and inserted into the DOM with innerHTML.
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @static
*/
formatDate : function(el, oRecord, oColumn, oData, oDataTable) {
var oDT = oDataTable || this,
oConfig = oColumn.dateOptions || oDT.get("dateOptions");
el.innerHTML = util.Date.format(oData, oConfig, oConfig.locale);
},
/**
* Formats SELECT elements.
*
* @method DataTable.formatDropdown
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} Data value for the cell, or null. String values may
* be treated as markup and inserted into the DOM with innerHTML as element
* label.
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @static
*/
formatDropdown : function(el, oRecord, oColumn, oData, oDataTable) {
var oDT = oDataTable || this,
selectedValue = (lang.isValue(oData)) ? oData : oRecord.getData(oColumn.field),
options = (lang.isArray(oColumn.dropdownOptions)) ?
oColumn.dropdownOptions : null,
selectEl,
collection = el.getElementsByTagName("select");
// Create the form element only once, so we can attach the onChange listener
if(collection.length === 0) {
// Create SELECT element
selectEl = document.createElement("select");
selectEl.className = DT.CLASS_DROPDOWN;
selectEl = el.appendChild(selectEl);
// Add event listener
Ev.addListener(selectEl,"change",oDT._onDropdownChange,oDT);
}
selectEl = collection[0];
// Update the form element
if(selectEl) {
// Clear out previous options
selectEl.innerHTML = "";
// We have options to populate
if(options) {
// Create OPTION elements
for(var i=0; i" + selectedValue + "";
}
}
else {
el.innerHTML = lang.isValue(oData) ? oData : "";
}
},
/**
* Formats emails.
*
* @method DataTable.formatEmail
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {String} Data value for the cell, or null. Values are
* HTML-escaped.
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @static
*/
formatEmail : function(el, oRecord, oColumn, oData, oDataTable) {
if(lang.isString(oData)) {
oData = lang.escapeHTML(oData);
el.innerHTML = "" + oData + "";
}
else {
el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : "";
}
},
/**
* Formats links.
*
* @method DataTable.formatLink
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {String} Data value for the cell, or null. Values are
* HTML-escaped
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @static
*/
formatLink : function(el, oRecord, oColumn, oData, oDataTable) {
if(lang.isString(oData)) {
oData = lang.escapeHTML(oData);
el.innerHTML = "" + oData + "";
}
else {
el.innerHTML = lang.isValue(oData) ? lang.escapeHTML(oData.toString()) : "";
}
},
/**
* Formats numbers.
*
* @method DataTable.formatNumber
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} Data value for the cell, or null.
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @static
*/
formatNumber : function(el, oRecord, oColumn, oData, oDataTable) {
var oDT = oDataTable || this;
el.innerHTML = util.Number.format(oData, oColumn.numberOptions || oDT.get("numberOptions"));
},
/**
* Formats INPUT TYPE=RADIO elements.
*
* @method DataTable.formatRadio
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} (Optional) Data value for the cell.
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @static
*/
formatRadio : function(el, oRecord, oColumn, oData, oDataTable) {
var oDT = oDataTable || this,
bChecked = oData;
bChecked = (bChecked) ? " checked=\"checked\"" : "";
el.innerHTML = "";
},
/**
* Formats text strings.
*
* @method DataTable.formatText
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {String} (Optional) Data value for the cell. Values are
* HTML-escaped.
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @static
*/
formatText : function(el, oRecord, oColumn, oData, oDataTable) {
var value = (lang.isValue(oData)) ? oData : "";
el.innerHTML = lang.escapeHTML(value.toString());
},
/**
* Formats TEXTAREA elements.
*
* @method DataTable.formatTextarea
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} (Optional) Data value for the cell. Values are
* HTML-escaped.
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @static
*/
formatTextarea : function(el, oRecord, oColumn, oData, oDataTable) {
var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "",
markup = "";
el.innerHTML = markup;
},
/**
* Formats INPUT TYPE=TEXT elements.
*
* @method DataTable.formatTextbox
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {Object} (Optional) Data value for the cell. Values are
* HTML-escaped.
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @static
*/
formatTextbox : function(el, oRecord, oColumn, oData, oDataTable) {
var value = (lang.isValue(oData)) ? lang.escapeHTML(oData.toString()) : "",
markup = "";
el.innerHTML = markup;
},
/**
* Default cell formatter
*
* @method DataTable.formatDefault
* @param el {HTMLElement} The element to format with markup.
* @param oRecord {YAHOO.widget.Record} Record instance.
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param oData {HTML} (Optional) Data value for the cell. String values are
* treated as markup and inserted into the DOM with innerHTML.
* @param oDataTable {YAHOO.widget.DataTable} DataTable instance.
* @static
*/
formatDefault : function(el, oRecord, oColumn, oData, oDataTable) {
el.innerHTML = (lang.isValue(oData) && oData !== "") ? oData.toString() : " ";
},
/**
* Validates data value to type Number, doing type conversion as
* necessary. A valid Number value is return, else null is returned
* if input value does not validate.
*
*
* @method DataTable.validateNumber
* @param oData {Object} Data to validate.
* @static
*/
validateNumber : function(oData) {
//Convert to number
var number = oData * 1;
// Validate
if(lang.isNumber(number)) {
return number;
}
else {
return undefined;
}
}
});
// Done in separate step so referenced functions are defined.
/**
* Registry of cell formatting functions, enables shortcut pointers in Column
* definition formatter value (i.e., {key:"myColumn", formatter:"date"}).
* @property DataTable.Formatter
* @type Object
* @static
*/
DT.Formatter = {
button : DT.formatButton,
checkbox : DT.formatCheckbox,
currency : DT.formatCurrency,
"date" : DT.formatDate,
dropdown : DT.formatDropdown,
email : DT.formatEmail,
link : DT.formatLink,
"number" : DT.formatNumber,
radio : DT.formatRadio,
text : DT.formatText,
textarea : DT.formatTextarea,
textbox : DT.formatTextbox,
defaultFormatter : DT.formatDefault
};
lang.extend(DT, util.Element, {
/////////////////////////////////////////////////////////////////////////////
//
// Superclass methods
//
/////////////////////////////////////////////////////////////////////////////
/**
* Implementation of Element's abstract method. Sets up config values.
*
* @method initAttributes
* @param oConfigs {Object} (Optional) Object literal definition of configuration values.
* @private
*/
initAttributes : function(oConfigs) {
oConfigs = oConfigs || {};
DT.superclass.initAttributes.call(this, oConfigs);
/**
* @attribute summary
* @description String value for the SUMMARY attribute.
* @type String
* @default ""
*/
this.setAttributeConfig("summary", {
value: "",
validator: lang.isString,
method: function(sSummary) {
if(this._elTable) {
this._elTable.summary = sSummary;
}
}
});
/**
* @attribute selectionMode
* @description Specifies row or cell selection mode. Accepts the following strings:
*
*
"standard"
*
Standard row selection with support for modifier keys to enable
* multiple selections.
*
*
"single"
*
Row selection with modifier keys disabled to not allow
* multiple selections.
*
*
"singlecell"
*
Cell selection with modifier keys disabled to not allow
* multiple selections.
*
*
"cellblock"
*
Cell selection with support for modifier keys to enable multiple
* selections in a block-fashion, like a spreadsheet.
*
*
"cellrange"
*
Cell selection with support for modifier keys to enable multiple
* selections in a range-fashion, like a calendar.
*
*
* @default "standard"
* @type String
*/
this.setAttributeConfig("selectionMode", {
value: "standard",
validator: lang.isString
});
/**
* @attribute sortedBy
* @description Object literal provides metadata for initial sort values if
* data will arrive pre-sorted:
*
*
sortedBy.key
*
{String} Key of sorted Column
*
sortedBy.dir
*
{String} Initial sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC
*
* @type Object | null
*/
this.setAttributeConfig("sortedBy", {
value: null,
// TODO: accepted array for nested sorts
validator: function(oNewSortedBy) {
if(oNewSortedBy) {
return (lang.isObject(oNewSortedBy) && oNewSortedBy.key);
}
else {
return (oNewSortedBy === null);
}
},
method: function(oNewSortedBy) {
// Stash the previous value
var oOldSortedBy = this.get("sortedBy");
// Workaround for bug 1827195
this._configs.sortedBy.value = oNewSortedBy;
// Remove ASC/DESC from TH
var oOldColumn,
nOldColumnKeyIndex,
oNewColumn,
nNewColumnKeyIndex;
if(this._elThead) {
if(oOldSortedBy && oOldSortedBy.key && oOldSortedBy.dir) {
oOldColumn = this._oColumnSet.getColumn(oOldSortedBy.key);
nOldColumnKeyIndex = oOldColumn.getKeyIndex();
// Remove previous UI from THEAD
var elOldTh = oOldColumn.getThEl();
Dom.removeClass(elOldTh, oOldSortedBy.dir);
this.formatTheadCell(oOldColumn.getThLinerEl().firstChild, oOldColumn, oNewSortedBy);
}
if(oNewSortedBy) {
oNewColumn = (oNewSortedBy.column) ? oNewSortedBy.column : this._oColumnSet.getColumn(oNewSortedBy.key);
nNewColumnKeyIndex = oNewColumn.getKeyIndex();
// Update THEAD with new UI
var elNewTh = oNewColumn.getThEl();
// Backward compatibility
if(oNewSortedBy.dir && ((oNewSortedBy.dir == "asc") || (oNewSortedBy.dir == "desc"))) {
var newClass = (oNewSortedBy.dir == "desc") ?
DT.CLASS_DESC :
DT.CLASS_ASC;
Dom.addClass(elNewTh, newClass);
}
else {
var sortClass = oNewSortedBy.dir || DT.CLASS_ASC;
Dom.addClass(elNewTh, sortClass);
}
this.formatTheadCell(oNewColumn.getThLinerEl().firstChild, oNewColumn, oNewSortedBy);
}
}
if(this._elTbody) {
// Update TBODY UI
this._elTbody.style.display = "none";
var allRows = this._elTbody.rows,
allCells;
for(var i=allRows.length-1; i>-1; i--) {
allCells = allRows[i].childNodes;
if(allCells[nOldColumnKeyIndex]) {
Dom.removeClass(allCells[nOldColumnKeyIndex], oOldSortedBy.dir);
}
if(allCells[nNewColumnKeyIndex]) {
Dom.addClass(allCells[nNewColumnKeyIndex], oNewSortedBy.dir);
}
}
this._elTbody.style.display = "";
}
this._clearTrTemplateEl();
}
});
/**
* @attribute paginator
* @description An instance of YAHOO.widget.Paginator.
* @default null
* @type {Object|YAHOO.widget.Paginator}
*/
this.setAttributeConfig("paginator", {
value : null,
validator : function (val) {
return val === null || val instanceof widget.Paginator;
},
method : function () { this._updatePaginator.apply(this,arguments); }
});
/**
* @attribute caption
* @description Value for the CAPTION element. String values are treated as
* markup and inserted into the DOM with innerHTML. NB: Not supported in
* ScrollingDataTable.
* @type HTML
*/
this.setAttributeConfig("caption", {
value: null,
validator: lang.isString,
method: function(sCaption) {
this._initCaptionEl(sCaption);
}
});
/**
* @attribute draggableColumns
* @description True if Columns are draggable to reorder, false otherwise.
* The Drag & Drop Utility is required to enable this feature. Only top-level
* and non-nested Columns are draggable. Write once.
* @default false
* @type Boolean
*/
this.setAttributeConfig("draggableColumns", {
value: false,
validator: lang.isBoolean,
method: function(oParam) {
if(this._elThead) {
if(oParam) {
this._initDraggableColumns();
}
else {
this._destroyDraggableColumns();
}
}
}
});
/**
* @attribute renderLoopSize
* @description A value greater than 0 enables DOM rendering of rows to be
* executed from a non-blocking timeout queue and sets how many rows to be
* rendered per timeout. Recommended for very large data sets.
* @type Number
* @default 0
*/
this.setAttributeConfig("renderLoopSize", {
value: 0,
validator: lang.isNumber
});
/**
* @attribute sortFunction
* @description Default Column sort function, receives the following args:
*
*
a {Object}
*
First sort argument.
*
b {Object}
*
Second sort argument.
*
desc {Boolean}
*
True if sort direction is descending, false if
* sort direction is ascending.
*
field {String}
*
The field to sort by, from sortOptions.field
*
* @type function
*/
this.setAttributeConfig("sortFunction", {
value: function(a, b, desc, field) {
var compare = YAHOO.util.Sort.compare,
sorted = compare(a.getData(field),b.getData(field), desc);
if(sorted === 0) {
return compare(a.getCount(),b.getCount(), desc); // Bug 1932978
}
else {
return sorted;
}
}
});
/**
* @attribute formatRow
* @description A function that accepts a TR element and its associated Record
* for custom formatting. The function must return TRUE in order to automatically
* continue formatting of child TD elements, else TD elements will not be
* automatically formatted.
* @type function
* @default null
*/
this.setAttributeConfig("formatRow", {
value: null,
validator: lang.isFunction
});
/**
* @attribute generateRequest
* @description A function that converts an object literal of desired DataTable
* states into a request value which is then passed to the DataSource's
* sendRequest method in order to retrieve data for those states. This
* function is passed an object literal of state data and a reference to the
* DataTable instance:
*
*
*
pagination
*
*
offsetRecord
*
{Number} Index of the first Record of the desired page
*
rowsPerPage
*
{Number} Number of rows per page
*
*
sortedBy
*
*
key
*
{String} Key of sorted Column
*
dir
*
{String} Sort direction, either YAHOO.widget.DataTable.CLASS_ASC or YAHOO.widget.DataTable.CLASS_DESC
*
*
self
*
The DataTable instance
*
*
* and by default returns a String of syntax:
* "sort={sortColumn}&dir={sortDir}&startIndex={pageStartIndex}&results={rowsPerPage}"
* @type function
* @default HTMLFunction
*/
this.setAttributeConfig("generateRequest", {
value: function(oState, oSelf) {
// Set defaults
oState = oState || {pagination:null, sortedBy:null};
var sort = encodeURIComponent((oState.sortedBy) ? oState.sortedBy.key : oSelf.getColumnSet().keys[0].getKey());
var dir = (oState.sortedBy && oState.sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? "desc" : "asc";
var startIndex = (oState.pagination) ? oState.pagination.recordOffset : 0;
var results = (oState.pagination) ? oState.pagination.rowsPerPage : null;
// Build the request
return "sort=" + sort +
"&dir=" + dir +
"&startIndex=" + startIndex +
((results !== null) ? "&results=" + results : "");
},
validator: lang.isFunction
});
/**
* @attribute initialRequest
* @description Defines the initial request that gets sent to the DataSource
* during initialization. Value is ignored if initialLoad is set to any value
* other than true.
* @type MIXED
* @default null
*/
this.setAttributeConfig("initialRequest", {
value: null
});
/**
* @attribute initialLoad
* @description Determines whether or not to load data at instantiation. By
* default, will trigger a sendRequest() to the DataSource and pass in the
* request defined by initialRequest. If set to false, data will not load
* at instantiation. Alternatively, implementers who wish to work with a
* custom payload may pass in an object literal with the following values:
*
*
*
request (MIXED)
*
Request value.
*
*
argument (MIXED)
*
Custom data that will be passed through to the callback function.
*
*
*
* @type Boolean | Object
* @default true
*/
this.setAttributeConfig("initialLoad", {
value: true
});
/**
* @attribute dynamicData
* @description If true, sorting and pagination are relegated to the DataSource
* for handling, using the request returned by the "generateRequest" function.
* Each new DataSource response blows away all previous Records. False by default, so
* sorting and pagination will be handled directly on the client side, without
* causing any new requests for data from the DataSource.
* @type Boolean
* @default false
*/
this.setAttributeConfig("dynamicData", {
value: false,
validator: lang.isBoolean
});
/**
* @attribute MSG_EMPTY
* @description Message to display if DataTable has no data. String
* values are treated as markup and inserted into the DOM with innerHTML.
* @type HTML
* @default "No records found."
*/
this.setAttributeConfig("MSG_EMPTY", {
value: "No records found.",
validator: lang.isString
});
/**
* @attribute MSG_LOADING
* @description Message to display while DataTable is loading data. String
* values are treated as markup and inserted into the DOM with innerHTML.
* @type HTML
* @default "Loading..."
*/
this.setAttributeConfig("MSG_LOADING", {
value: "Loading...",
validator: lang.isString
});
/**
* @attribute MSG_ERROR
* @description Message to display while DataTable has data error. String
* values are treated as markup and inserted into the DOM with innerHTML.
* @type HTML
* @default "Data error."
*/
this.setAttributeConfig("MSG_ERROR", {
value: "Data error.",
validator: lang.isString
});
/**
* @attribute MSG_SORTASC
* @description Message to display in tooltip to sort Column in ascending
* order. String values are treated as markup and inserted into the DOM as
* innerHTML.
* @type HTML
* @default "Click to sort ascending"
*/
this.setAttributeConfig("MSG_SORTASC", {
value: "Click to sort ascending",
validator: lang.isString,
method: function(sParam) {
if(this._elThead) {
for(var i=0, allKeys=this.getColumnSet().keys, len=allKeys.length; i= 0; --i ) {
if ( columns[i].editor ) {
Ev.purgeElement( columns[i].editor._elContainer );
}
}
elContainer.innerHTML = "";
this._elContainer = null;
this._elColgroup = null;
this._elThead = null;
this._elTbody = null;
},
/**
* Initializes the DataTable outer container element, including a mask.
*
* @method _initContainerEl
* @param elContainer {HTMLElement | String} HTML DIV element by reference or ID.
* @private
*/
_initContainerEl : function(elContainer) {
// Validate container
elContainer = Dom.get(elContainer);
if(elContainer && elContainer.nodeName && (elContainer.nodeName.toLowerCase() == "div")) {
// Destroy previous
this._destroyContainerEl(elContainer);
Dom.addClass(elContainer, DT.CLASS_DATATABLE);
Ev.addListener(elContainer, "focus", this._onTableFocus, this);
Ev.addListener(elContainer, "dblclick", this._onTableDblclick, this);
this._elContainer = elContainer;
var elMask = document.createElement("div");
elMask.className = DT.CLASS_MASK;
elMask.style.display = "none";
this._elMask = elContainer.appendChild(elMask);
}
},
/**
* Destroy's the DataTable TABLE element, if available.
*
* @method _destroyTableEl
* @private
*/
_destroyTableEl : function() {
var elTable = this._elTable;
if(elTable) {
Ev.purgeElement(elTable, true);
elTable.parentNode.removeChild(elTable);
this._elCaption = null;
this._elColgroup = null;
this._elThead = null;
this._elTbody = null;
}
},
/**
* Creates HTML markup CAPTION element.
*
* @method _initCaptionEl
* @param sCaption {HTML} Caption value. String values are treated as markup and
* inserted into the DOM with innerHTML.
* @private
*/
_initCaptionEl : function(sCaption) {
if(this._elTable && sCaption) {
// Create CAPTION element
if(!this._elCaption) {
this._elCaption = this._elTable.createCaption();
}
// Set CAPTION value
this._elCaption.innerHTML = sCaption;
}
else if(this._elCaption) {
this._elCaption.parentNode.removeChild(this._elCaption);
}
},
/**
* Creates HTML markup for TABLE, COLGROUP, THEAD and TBODY elements in outer
* container element.
*
* @method _initTableEl
* @param elContainer {HTMLElement} Container element into which to create TABLE.
* @private
*/
_initTableEl : function(elContainer) {
if(elContainer) {
// Destroy previous
this._destroyTableEl();
// Create TABLE
this._elTable = elContainer.appendChild(document.createElement("table"));
// Set SUMMARY attribute
this._elTable.summary = this.get("summary");
// Create CAPTION element
if(this.get("caption")) {
this._initCaptionEl(this.get("caption"));
}
// Set up mouseover/mouseout events via mouseenter/mouseleave delegation
Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "thead ."+DT.CLASS_LABEL, this);
Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "thead ."+DT.CLASS_LABEL, this);
Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-data>tr>td", this);
Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-data>tr>td", this);
Ev.delegate(this._elTable, "mouseenter", this._onTableMouseover, "tbody.yui-dt-message>tr>td", this);
Ev.delegate(this._elTable, "mouseleave", this._onTableMouseout, "tbody.yui-dt-message>tr>td", this);
}
},
/**
* Destroy's the DataTable COLGROUP element, if available.
*
* @method _destroyColgroupEl
* @private
*/
_destroyColgroupEl : function() {
var elColgroup = this._elColgroup;
if(elColgroup) {
var elTable = elColgroup.parentNode;
Ev.purgeElement(elColgroup, true);
elTable.removeChild(elColgroup);
this._elColgroup = null;
}
},
/**
* Initializes COLGROUP and COL elements for managing minWidth.
*
* @method _initColgroupEl
* @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
* @private
*/
_initColgroupEl : function(elTable) {
if(elTable) {
// Destroy previous
this._destroyColgroupEl();
// Add COLs to DOCUMENT FRAGMENT
var allCols = this._aColIds || [],
allKeys = this._oColumnSet.keys,
i = 0, len = allCols.length,
elCol, oColumn,
elFragment = document.createDocumentFragment(),
elColTemplate = document.createElement("col");
for(i=0,len=allKeys.length; i aKeyIndexes[aKeyIndexes.length-1])) {
var i,
tmpCols = [];
// Remove COL
for(i=aKeyIndexes.length-1; i>-1; i--) {
tmpCols.push(this._elColgroup.removeChild(this._elColgroup.childNodes[aKeyIndexes[i]]));
}
// Insert COL
var nextSibling = this._elColgroup.childNodes[newIndex] || null;
for(i=tmpCols.length-1; i>-1; i--) {
this._elColgroup.insertBefore(tmpCols[i], nextSibling);
}
}
},
/**
* Destroy's the DataTable THEAD element, if available.
*
* @method _destroyTheadEl
* @private
*/
_destroyTheadEl : function() {
var elThead = this._elThead;
if(elThead) {
var elTable = elThead.parentNode;
Ev.purgeElement(elThead, true);
this._destroyColumnHelpers();
elTable.removeChild(elThead);
this._elThead = null;
}
},
/**
* Initializes THEAD element.
*
* @method _initTheadEl
* @param elTable {HTMLElement} TABLE element into which to create COLGROUP.
* @param {HTMLElement} Initialized THEAD element.
* @private
*/
_initTheadEl : function(elTable) {
elTable = elTable || this._elTable;
if(elTable) {
// Destroy previous
this._destroyTheadEl();
//TODO: append to DOM later for performance
var elThead = (this._elColgroup) ?
elTable.insertBefore(document.createElement("thead"), this._elColgroup.nextSibling) :
elTable.appendChild(document.createElement("thead"));
// Set up DOM events for THEAD
Ev.addListener(elThead, "focus", this._onTheadFocus, this);
Ev.addListener(elThead, "keydown", this._onTheadKeydown, this);
Ev.addListener(elThead, "mousedown", this._onTableMousedown, this);
Ev.addListener(elThead, "mouseup", this._onTableMouseup, this);
Ev.addListener(elThead, "click", this._onTheadClick, this);
// Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
// delegation at the TABLE level
// Since we can't listen for click and dblclick on the same element...
// Attach separately to THEAD and TBODY
///Ev.addListener(elThead, "dblclick", this._onTableDblclick, this);
var oColumnSet = this._oColumnSet,
oColumn, i,j, l;
// Add TRs to the THEAD
var colTree = oColumnSet.tree;
var elTh;
for(i=0; i" + sLabel + "";
}
// Just display the label for non-sortable Columns
else {
elCellLabel.innerHTML = sLabel;
}
},
/**
* Disables DD from top-level Column TH elements.
*
* @method _destroyDraggableColumns
* @private
*/
_destroyDraggableColumns : function() {
var oColumn, elTh;
for(var i=0, len=this._oColumnSet.tree[0].length; i 1
// Create a separate resizer liner with position:relative
elThResizerLiner = elTh.appendChild(document.createElement("div"));
elThResizerLiner.className = DT.CLASS_RESIZERLINER;
// Move TH contents into the new resizer liner
elThResizerLiner.appendChild(elThLiner);
// Create the resizer
elThResizer = elThResizerLiner.appendChild(document.createElement("div"));
elThResizer.id = elTh.id + "-resizer"; // Needed for ColumnResizer
elThResizer.className = DT.CLASS_RESIZER;
oColumn._elResizer = elThResizer;
// Create the resizer proxy, once per instance
elResizerProxy = this._initColumnResizerProxyEl();
oColumn._ddResizer = new YAHOO.util.ColumnResizer(
this, oColumn, elTh, elThResizer, elResizerProxy);
cancelClick = function(e) {
Ev.stopPropagation(e);
};
Ev.addListener(elThResizer,"click",cancelClick);
}
}
}
else {
}
},
/**
* Destroys shared Column resizer proxy.
*
* @method _destroyColumnResizerProxyEl
* @return {HTMLElement} Reference to Column resizer proxy.
* @private
*/
_destroyColumnResizerProxyEl : function() {
if(this._elColumnResizerProxy) {
var el = this._elColumnResizerProxy;
YAHOO.util.Event.purgeElement(el);
el.parentNode.removeChild(el);
this._elColumnResizerProxy = null;
}
},
/**
* Creates HTML markup for shared Column resizer proxy.
*
* @method _initColumnResizerProxyEl
* @return {HTMLElement} Reference to Column resizer proxy.
* @private
*/
_initColumnResizerProxyEl : function() {
if(!this._elColumnResizerProxy) {
// Attach Column resizer element as first child of body
var elColumnResizerProxy = document.createElement("div");
elColumnResizerProxy.id = this.getId() + "-colresizerproxy"; // Needed for ColumnResizer
elColumnResizerProxy.className = DT.CLASS_RESIZERPROXY;
document.body.insertBefore(elColumnResizerProxy, document.body.firstChild);
// Internal tracker of Column resizer proxy
this._elColumnResizerProxy = elColumnResizerProxy;
}
return this._elColumnResizerProxy;
},
/**
* Destroys elements associated with Column functionality: ColumnDD and ColumnResizers.
*
* @method _destroyColumnHelpers
* @private
*/
_destroyColumnHelpers : function() {
this._destroyDraggableColumns();
this._destroyResizeableColumns();
},
/**
* Initializes elements associated with Column functionality: ColumnDD and ColumnResizers.
*
* @method _initColumnHelpers
* @private
*/
_initColumnHelpers : function() {
if(this.get("draggableColumns")) {
this._initDraggableColumns();
}
this._initResizeableColumns();
},
/**
* Destroy's the DataTable TBODY element, if available.
*
* @method _destroyTbodyEl
* @private
*/
_destroyTbodyEl : function() {
var elTbody = this._elTbody;
if(elTbody) {
var elTable = elTbody.parentNode;
Ev.purgeElement(elTbody, true);
elTable.removeChild(elTbody);
this._elTbody = null;
}
},
/**
* Initializes TBODY element for data.
*
* @method _initTbodyEl
* @param elTable {HTMLElement} TABLE element into which to create TBODY .
* @private
*/
_initTbodyEl : function(elTable) {
if(elTable) {
// Destroy previous
this._destroyTbodyEl();
// Create TBODY
var elTbody = elTable.appendChild(document.createElement("tbody"));
elTbody.tabIndex = 0;
elTbody.className = DT.CLASS_DATA;
// Set up DOM events for TBODY
Ev.addListener(elTbody, "focus", this._onTbodyFocus, this);
Ev.addListener(elTbody, "mousedown", this._onTableMousedown, this);
Ev.addListener(elTbody, "mouseup", this._onTableMouseup, this);
Ev.addListener(elTbody, "keydown", this._onTbodyKeydown, this);
Ev.addListener(elTbody, "click", this._onTbodyClick, this);
// Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
// delegation at the TABLE level
// Since we can't listen for click and dblclick on the same element...
// Attach separately to THEAD and TBODY
///Ev.addListener(elTbody, "dblclick", this._onTableDblclick, this);
// IE puts focus outline in the wrong place
if(ua.ie) {
elTbody.hideFocus=true;
}
this._elTbody = elTbody;
}
},
/**
* Destroy's the DataTable message TBODY element, if available.
*
* @method _destroyMsgTbodyEl
* @private
*/
_destroyMsgTbodyEl : function() {
var elMsgTbody = this._elMsgTbody;
if(elMsgTbody) {
var elTable = elMsgTbody.parentNode;
Ev.purgeElement(elMsgTbody, true);
elTable.removeChild(elMsgTbody);
this._elTbody = null;
}
},
/**
* Initializes TBODY element for messaging.
*
* @method _initMsgTbodyEl
* @param elTable {HTMLElement} TABLE element into which to create TBODY
* @private
*/
_initMsgTbodyEl : function(elTable) {
if(elTable) {
var elMsgTbody = document.createElement("tbody");
elMsgTbody.className = DT.CLASS_MESSAGE;
var elMsgTr = elMsgTbody.appendChild(document.createElement("tr"));
elMsgTr.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
this._elMsgTr = elMsgTr;
var elMsgTd = elMsgTr.appendChild(document.createElement("td"));
elMsgTd.colSpan = this._oColumnSet.keys.length || 1;
elMsgTd.className = DT.CLASS_FIRST + " " + DT.CLASS_LAST;
this._elMsgTd = elMsgTd;
elMsgTbody = elTable.insertBefore(elMsgTbody, this._elTbody);
var elMsgLiner = elMsgTd.appendChild(document.createElement("div"));
elMsgLiner.className = DT.CLASS_LINER;
this._elMsgTbody = elMsgTbody;
// Set up DOM events for TBODY
Ev.addListener(elMsgTbody, "focus", this._onTbodyFocus, this);
Ev.addListener(elMsgTbody, "mousedown", this._onTableMousedown, this);
Ev.addListener(elMsgTbody, "mouseup", this._onTableMouseup, this);
Ev.addListener(elMsgTbody, "keydown", this._onTbodyKeydown, this);
Ev.addListener(elMsgTbody, "click", this._onTbodyClick, this);
// Bug 2528073: mouseover/mouseout handled via mouseenter/mouseleave
// delegation at the TABLE level
}
},
/**
* Initialize internal event listeners
*
* @method _initEvents
* @private
*/
_initEvents : function () {
// Initialize Column sort
this._initColumnSort();
// Add the document level click listener
YAHOO.util.Event.addListener(document, "click", this._onDocumentClick, this);
// Paginator integration
this.subscribe("paginatorChange",function () {
this._handlePaginatorChange.apply(this,arguments);
});
this.subscribe("initEvent",function () {
this.renderPaginator();
});
// Initialize CellEditor integration
this._initCellEditing();
},
/**
* Initializes Column sorting.
*
* @method _initColumnSort
* @private
*/
_initColumnSort : function() {
this.subscribe("theadCellClickEvent", this.onEventSortColumn);
// Backward compatibility
var oSortedBy = this.get("sortedBy");
if(oSortedBy) {
if(oSortedBy.dir == "desc") {
this._configs.sortedBy.value.dir = DT.CLASS_DESC;
}
else if(oSortedBy.dir == "asc") {
this._configs.sortedBy.value.dir = DT.CLASS_ASC;
}
}
},
/**
* Initializes CellEditor integration.
*
* @method _initCellEditing
* @private
*/
_initCellEditing : function() {
this.subscribe("editorBlurEvent",function () {
this.onEditorBlurEvent.apply(this,arguments);
});
this.subscribe("editorBlockEvent",function () {
this.onEditorBlockEvent.apply(this,arguments);
});
this.subscribe("editorUnblockEvent",function () {
this.onEditorUnblockEvent.apply(this,arguments);
});
},
// DOM MUTATION FUNCTIONS
/**
* Retruns classnames to represent current Column states.
* @method _getColumnClassnames
* @param oColumn {YAHOO.widget.Column} Column instance.
* @param aAddClasses {String[]} An array of additional classnames to add to the
* return value.
* @return {String} A String of classnames to be assigned to TH or TD elements
* for given Column.
* @private
*/
_getColumnClassNames : function (oColumn, aAddClasses) {
var allClasses;
// Add CSS classes
if(lang.isString(oColumn.className)) {
// Single custom class
allClasses = [oColumn.className];
}
else if(lang.isArray(oColumn.className)) {
// Array of custom classes
allClasses = oColumn.className;
}
else {
// no custom classes
allClasses = [];
}
// Hook for setting width with via dynamic style uses key since ID is too disposable
allClasses[allClasses.length] = this.getId() + "-col-" +oColumn.getSanitizedKey();
// Column key - minus any chars other than "A-Z", "a-z", "0-9", "_", "-", ".", or ":"
allClasses[allClasses.length] = "yui-dt-col-" +oColumn.getSanitizedKey();
var isSortedBy = this.get("sortedBy") || {};
// Sorted
if(oColumn.key === isSortedBy.key) {
allClasses[allClasses.length] = isSortedBy.dir || '';
}
// Hidden
if(oColumn.hidden) {
allClasses[allClasses.length] = DT.CLASS_HIDDEN;
}
// Selected
if(oColumn.selected) {
allClasses[allClasses.length] = DT.CLASS_SELECTED;
}
// Sortable
if(oColumn.sortable) {
allClasses[allClasses.length] = DT.CLASS_SORTABLE;
}
// Resizeable
if(oColumn.resizeable) {
allClasses[allClasses.length] = DT.CLASS_RESIZEABLE;
}
// Editable
if(oColumn.editor) {
allClasses[allClasses.length] = DT.CLASS_EDITABLE;
}
// Addtnl classes, including First/Last
if(aAddClasses) {
allClasses = allClasses.concat(aAddClasses);
}
return allClasses.join(' ');
},
/**
* Clears TR element template in response to any Column state change.
* @method _clearTrTemplateEl
* @private
*/
_clearTrTemplateEl : function () {
this._elTrTemplate = null;
},
/**
* Returns a new TR element template with TD elements classed with current
* Column states.
* @method _getTrTemplateEl
* @return {HTMLElement} A TR element to be cloned and added to the DOM.
* @private
*/
_getTrTemplateEl : function (oRecord, index) {
// Template is already available
if(this._elTrTemplate) {
return this._elTrTemplate;
}
// Template needs to be created
else {
var d = document,
tr = d.createElement('tr'),
td = d.createElement('td'),
div = d.createElement('div');
// Append the liner element
td.appendChild(div);
// Create TD elements into DOCUMENT FRAGMENT
var df = document.createDocumentFragment(),
allKeys = this._oColumnSet.keys,
elTd;
// Set state for each TD;
var aAddClasses;
for(var i=0, keysLen=allKeys.length; i -2) && (rowIndex < this._elTbody.rows.length)) {
// Cannot use tbody.deleteRow due to IE6 instability
//return this._elTbody.deleteRow(rowIndex);
return this._elTbody.removeChild(this._elTbody.rows[row]);
}
else {
return null;
}
},
// CSS/STATE FUNCTIONS
/**
* Removes the class YAHOO.widget.DataTable.CLASS_FIRST from the first TR element
* of the DataTable page and updates internal tracker.
*
* @method _unsetFirstRow
* @private
*/
_unsetFirstRow : function() {
// Remove FIRST
if(this._sFirstTrId) {
Dom.removeClass(this._sFirstTrId, DT.CLASS_FIRST);
this._sFirstTrId = null;
}
},
/**
* Assigns the class YAHOO.widget.DataTable.CLASS_FIRST to the first TR element
* of the DataTable page and updates internal tracker.
*
* @method _setFirstRow
* @private
*/
_setFirstRow : function() {
this._unsetFirstRow();
var elTr = this.getFirstTrEl();
if(elTr) {
// Set FIRST
Dom.addClass(elTr, DT.CLASS_FIRST);
this._sFirstTrId = elTr.id;
}
},
/**
* Removes the class YAHOO.widget.DataTable.CLASS_LAST from the last TR element
* of the DataTable page and updates internal tracker.
*
* @method _unsetLastRow
* @private
*/
_unsetLastRow : function() {
// Unassign previous class
if(this._sLastTrId) {
Dom.removeClass(this._sLastTrId, DT.CLASS_LAST);
this._sLastTrId = null;
}
},
/**
* Assigns the class YAHOO.widget.DataTable.CLASS_LAST to the last TR element
* of the DataTable page and updates internal tracker.
*
* @method _setLastRow
* @private
*/
_setLastRow : function() {
this._unsetLastRow();
var elTr = this.getLastTrEl();
if(elTr) {
// Assign class
Dom.addClass(elTr, DT.CLASS_LAST);
this._sLastTrId = elTr.id;
}
},
/**
* Assigns the classes DT.CLASS_EVEN and DT.CLASS_ODD to one, many, or all TR elements.
*
* @method _setRowStripes
* @param row {HTMLElement | String | Number} (optional) HTML TR element reference
* or string ID, or page row index of where to start striping.
* @param range {Number} (optional) If given, how many rows to stripe, otherwise
* stripe all the rows until the end.
* @private
*/
_setRowStripes : function(row, range) {
// Default values stripe all rows
var allRows = this._elTbody.rows,
nStartIndex = 0,
nEndIndex = allRows.length,
aOdds = [], nOddIdx = 0,
aEvens = [], nEvenIdx = 0;
// Stripe a subset
if((row !== null) && (row !== undefined)) {
// Validate given start row
var elStartRow = this.getTrEl(row);
if(elStartRow) {
nStartIndex = elStartRow.sectionRowIndex;
// Validate given range
if(lang.isNumber(range) && (range > 1)) {
nEndIndex = nStartIndex + range;
}
}
}
for(var i=nStartIndex; i0) || (allSelectedCells.length > 0)) {
var oColumnSet = this._oColumnSet,
el;
// Loop over each row
for(var i=0; i
*